commit 38355d244258bcf5a3477476d8a26f3c6f0149ce Author: Waylon S. Walker Date: Thu Mar 31 20:20:07 2022 -0500 init diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..e510ee8 --- /dev/null +++ b/.envrc @@ -0,0 +1,41 @@ +#!/bin/bash +# shortcut for creating new virtual environments +venvnew() { + python3 -m venv .venv --prompt $(basename $PWD) + source .venv/bin/activate + + python3 -m pip install pip --upgrade + + if [ -d "src" ]; then + pip install -e "src[all]" || \ + pip install -e "src[dev]" || \ + pip install -e "src" + elif [[ -f "setup.py" ]]; then + pip install -e . + elif [[ -f "requirements.txt" ]]; then + pip install pre-commit + pre-commit install + fi + + if [[ -f ".pre-commit-config.yaml" ]]; then + pip install pre-commit + pre-commit install + fi + + pip show kedro > /dev/null 2>&1 && pip install kedro-lsp + + pip install \ + black \ + flake8 \ + ipython \ + isort \ + lolcat \ + mypy \ + pyflyby \ + rich \ + rope + } + +source .venv/bin/activate > /dev/null 2>&1 || venvnew + +echo $(basename $PWD) | lolcat diff --git a/.venv/bin/Activate.ps1 b/.venv/bin/Activate.ps1 new file mode 100644 index 0000000..2fb3852 --- /dev/null +++ b/.venv/bin/Activate.ps1 @@ -0,0 +1,241 @@ +<# +.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" diff --git a/.venv/bin/activate b/.venv/bin/activate new file mode 100644 index 0000000..bfb4ea1 --- /dev/null +++ b/.venv/bin/activate @@ -0,0 +1,76 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/home/walkers/git/creeper-adventure/.venv" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + if [ "x(creeper-adventure) " != x ] ; then + PS1="(creeper-adventure) ${PS1:-}" + else + if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then + # special case for Aspen magic directories + # see https://aspen.io/ + PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1" + else + PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1" + fi + fi + export PS1 +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r +fi diff --git a/.venv/bin/activate.csh b/.venv/bin/activate.csh new file mode 100644 index 0000000..c9a91a7 --- /dev/null +++ b/.venv/bin/activate.csh @@ -0,0 +1,37 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +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/walkers/git/creeper-adventure/.venv" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + if (".venv" != "") then + set env_name = ".venv" + else + if (`basename "VIRTUAL_ENV"` == "__") then + # special case for Aspen magic directories + # see https://aspen.io/ + set env_name = `basename \`dirname "$VIRTUAL_ENV"\`` + else + set env_name = `basename "$VIRTUAL_ENV"` + endif + endif + set prompt = "[$env_name] $prompt" + unset env_name +endif + +alias pydoc python -m pydoc + +rehash diff --git a/.venv/bin/activate.fish b/.venv/bin/activate.fish new file mode 100644 index 0000000..b861b3e --- /dev/null +++ b/.venv/bin/activate.fish @@ -0,0 +1,75 @@ +# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org) +# you cannot run it directly + +function deactivate -d "Exit virtualenv and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + functions -e fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + + set -e VIRTUAL_ENV + if test "$argv[1]" != "nondestructive" + # Self destruct! + functions -e deactivate + end +end + +# unset irrelevant variables +deactivate nondestructive + +set -gx VIRTUAL_ENV "/home/walkers/git/creeper-adventure/.venv" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# unset PYTHONHOME if set +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # save the current fish_prompt function as the function _old_fish_prompt + functions -c fish_prompt _old_fish_prompt + + # with the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command + set -l old_status $status + + # Prompt override? + if test -n "(creeper-adventure) " + printf "%s%s" "(creeper-adventure) " (set_color normal) + else + # ...Otherwise, prepend env + set -l _checkbase (basename "$VIRTUAL_ENV") + if test $_checkbase = "__" + # special case for Aspen magic directories + # see https://aspen.io/ + printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal) + else + printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal) + end + end + + # Restore the return status of the previous command. + echo "exit $old_status" | . + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/.venv/bin/black b/.venv/bin/black new file mode 100755 index 0000000..3b9062d --- /dev/null +++ b/.venv/bin/black @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from black import patched_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(patched_main()) diff --git a/.venv/bin/blackd b/.venv/bin/blackd new file mode 100755 index 0000000..a545823 --- /dev/null +++ b/.venv/bin/blackd @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from blackd import patched_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(patched_main()) diff --git a/.venv/bin/cmark b/.venv/bin/cmark new file mode 100755 index 0000000..02770af --- /dev/null +++ b/.venv/bin/cmark @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from commonmark.cmark import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/collect-exports b/.venv/bin/collect-exports new file mode 100755 index 0000000..8740c3f --- /dev/null +++ b/.venv/bin/collect-exports @@ -0,0 +1,78 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +collect-exports module1 module2... + +Collect all exports in the specified modules and generate "from foo import +..." lines for public members defined in those modules. + +Print the result to stdout. + +""" + +# pyflyby/collect-exports +# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + + +from __future__ import absolute_import, division, with_statement +from __future__ import print_function + +import sys + +from pyflyby._cmdline import hfmt, parse_args +from pyflyby._importdb import ImportDB +from pyflyby._log import logger +from pyflyby._modules import ModuleHandle + + +def main(): + def addopts(parser): + parser.add_option("--ignore-known", default=False, action='store_true', + help=hfmt(''' + Don't list imports already in the + known-imports database.''')) + parser.add_option("--no-ignore-known", dest="ignore_known", + action='store_false', + help=hfmt(''' + (Default) List all imports, including those + already in the known-imports database.''')) + parser.add_option("--expand-known", default=False, action='store_true', + help=hfmt(''' + Scan all modules mentioned in known-imports + database.''')) + parser.add_option("--no-expand-known", dest="expand_known", + action='store_false', + help=hfmt(''' + (Default) Scan only modules listed explicitly + on the command line.''')) + options, args = parse_args(addopts, import_format_params=True) + if options.expand_known: + db = ImportDB.get_default(".") + known = db.known_imports.imports + args += sorted(set( + [_f for _f in [i.split.module_name for i in known] if _f])) + bad_module_names = [] + for module_name in args: + module = ModuleHandle(module_name) + try: + imports = module.exports + except Exception as e: + logger.warning("couldn't get exports for %s; ignoring: %s: %s", + module, type(e).__name__, e) + bad_module_names.append(module_name) + continue + if not imports: + continue + if options.ignore_known: + db = ImportDB.get_default(module.__file__) + imports = imports.without_imports(db) + sys.stdout.write(imports.pretty_print( + allow_conflicts=True, params=options.params)) + if bad_module_names: + print("collect-exports: there were problems with: %s" % ( + ' '.join(bad_module_names)), file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/collect-imports b/.venv/bin/collect-imports new file mode 100755 index 0000000..c3c5fa5 --- /dev/null +++ b/.venv/bin/collect-imports @@ -0,0 +1,59 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +collect-imports *.py +collect-imports < foo.py + +Collect all imports from named files or stdin, and combine them into a single +block of import statements. Print the result to stdout. + +""" +# pyflyby/collect-imports +# Copyright (C) 2011, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import absolute_import, division, with_statement + +import re +import sys + +from pyflyby._cmdline import filename_args, hfmt, parse_args +from pyflyby._importclns import ImportSet +from pyflyby._importdb import ImportDB + + +def main(): + def addopts(parser): + parser.add_option("--ignore-known", default=False, action='store_true', + help=hfmt(''' + Don't list imports already in the + known-imports database.''')) + parser.add_option("--no-ignore-known", dest="ignore_known", + action='store_false', + help=hfmt(''' + (Default) List all imports, including those + already in the known-imports database.''')) + parser.add_option("--include", + default=[], action="append", + help=hfmt(''' + Include only imports under the given package.''')) + options, args = parse_args(addopts, import_format_params=True) + filenames = filename_args(args) + importset = ImportSet(filenames, ignore_nonimports=True) + if options.include: + regexps = [ + re.escape(prefix) if prefix.endswith(".") else + re.escape(prefix) + "([.]|$)" + for prefix in options.include + ] + regexp = re.compile("|".join(regexps)) + match = lambda imp: regexp.match(imp.fullname) + importset = ImportSet([imp for imp in importset if match(imp)]) + if options.ignore_known: + db = ImportDB.get_default(".") + importset = importset.without_imports(db.known_imports) + sys.stdout.write(importset.pretty_print( + allow_conflicts=True, params=options.params)) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/dmypy b/.venv/bin/dmypy new file mode 100755 index 0000000..8487e81 --- /dev/null +++ b/.venv/bin/dmypy @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.dmypy.client import console_entry +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(console_entry()) diff --git a/.venv/bin/find-import b/.venv/bin/find-import new file mode 100755 index 0000000..565d899 --- /dev/null +++ b/.venv/bin/find-import @@ -0,0 +1,40 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +Usage: find-import names... + +Prints how to import given name(s). +""" +# pyflyby/find-import +# Copyright (C) 2011, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import absolute_import, division, with_statement +from __future__ import print_function + +from pyflyby._cmdline import parse_args, syntax +from pyflyby._importdb import ImportDB +from pyflyby._log import logger + + +def main(): + options, args = parse_args(import_format_params=True) + if not args: + syntax() + db = ImportDB.get_default(".") + known = db.known_imports.by_import_as + errors = 0 + for arg in args: + try: + imports = known[arg] + except KeyError: + errors += 1 + logger.error("Can't find import for %r", arg) + else: + for imp in imports: + print(imp.pretty_print(params=options.params), end=' ') + if errors: + raise SystemExit(1) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/flake8 b/.venv/bin/flake8 new file mode 100755 index 0000000..1a564de --- /dev/null +++ b/.venv/bin/flake8 @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from flake8.main.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/identify-cli b/.venv/bin/identify-cli new file mode 100755 index 0000000..0a3d8c1 --- /dev/null +++ b/.venv/bin/identify-cli @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from identify.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/ipython b/.venv/bin/ipython new file mode 100755 index 0000000..c9ee1bc --- /dev/null +++ b/.venv/bin/ipython @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from IPython import start_ipython +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(start_ipython()) diff --git a/.venv/bin/ipython3 b/.venv/bin/ipython3 new file mode 100755 index 0000000..c9ee1bc --- /dev/null +++ b/.venv/bin/ipython3 @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from IPython import start_ipython +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(start_ipython()) diff --git a/.venv/bin/isort b/.venv/bin/isort new file mode 100755 index 0000000..e793c10 --- /dev/null +++ b/.venv/bin/isort @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from isort.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/isort-identify-imports b/.venv/bin/isort-identify-imports new file mode 100755 index 0000000..9cbb06c --- /dev/null +++ b/.venv/bin/isort-identify-imports @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from isort.main import identify_imports_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(identify_imports_main()) diff --git a/.venv/bin/list-bad-xrefs b/.venv/bin/list-bad-xrefs new file mode 100755 index 0000000..f9f8560 --- /dev/null +++ b/.venv/bin/list-bad-xrefs @@ -0,0 +1,36 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +Usage: list-bad-xrefs modules... filenames... + +Prints the bad docstring cross-references in the given modules. + +Similar to running C{epydoc -v}, but: + - The output is organized so that it is easy to identify the code needing + fixing. + - If a cross-reference is to an external module, its references are included + automatically. +""" +# pyflyby/list-bad-xrefs +# Copyright (C) 2011, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import absolute_import, division, with_statement +from __future__ import print_function + +from pyflyby._cmdline import parse_args, syntax +from pyflyby._docxref import find_bad_doc_cross_references + + +def main(): + options, args = parse_args() + if not args: + syntax() + for rec in find_bad_doc_cross_references(args): + module, linenos, container_name, identifier = rec + for lineno in linenos or ["?"]: + print("%s:%s: undefined docstring cross-reference in %s: %s" % ( + module.filename, lineno, container_name, identifier)) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/lolcat b/.venv/bin/lolcat new file mode 100755 index 0000000..0fa5f8c --- /dev/null +++ b/.venv/bin/lolcat @@ -0,0 +1,242 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# +# "THE BEER-WARE LICENSE" (Revision 43~maze) +# +# wrote these files. As long as you retain this notice you +# can do whatever you want with this stuff. If we meet some day, and you think +# this stuff is worth it, you can buy me a beer in return. + +from __future__ import print_function + +import atexit +import math +import os +import random +import re +import sys +import time +from signal import signal, SIGPIPE, SIG_DFL + +PY3 = sys.version_info >= (3,) + +# override default handler so no exceptions on SIGPIPE +signal(SIGPIPE, SIG_DFL) + +# Reset terminal colors at exit +def reset(): + sys.stdout.write('\x1b[0m') + sys.stdout.flush() + +atexit.register(reset) + + +STRIP_ANSI = re.compile(r'\x1b\[(\d+)(;\d+)?(;\d+)?[m|K]') +COLOR_ANSI = ( + (0x00, 0x00, 0x00), (0xcd, 0x00, 0x00), + (0x00, 0xcd, 0x00), (0xcd, 0xcd, 0x00), + (0x00, 0x00, 0xee), (0xcd, 0x00, 0xcd), + (0x00, 0xcd, 0xcd), (0xe5, 0xe5, 0xe5), + (0x7f, 0x7f, 0x7f), (0xff, 0x00, 0x00), + (0x00, 0xff, 0x00), (0xff, 0xff, 0x00), + (0x5c, 0x5c, 0xff), (0xff, 0x00, 0xff), + (0x00, 0xff, 0xff), (0xff, 0xff, 0xff), +) + + +class stdoutWin(): + def __init__(self): + self.output = sys.stdout + self.string = '' + self.i = 0 + + def isatty(self): + return self.output.isatty() + + def write(self,s): + self.string = self.string + s + + def flush(self): + return self.output.flush() + + def prints(self): + string = 'echo|set /p="%s"' %(self.string) + os.system(string) + self.i += 1 + self.string = '' + + def println(self): + print() + self.prints() + + +class LolCat(object): + def __init__(self, mode=256, output=sys.stdout): + self.mode =mode + self.output = output + + def _distance(self, rgb1, rgb2): + return sum(map(lambda c: (c[0] - c[1]) ** 2, + zip(rgb1, rgb2))) + + def ansi(self, rgb): + r, g, b = rgb + + if self.mode in (8, 16): + colors = COLOR_ANSI[:self.mode] + matches = [(self._distance(c, map(int, rgb)), i) for i, c in enumerate(colors)] + matches.sort() + color = matches[0][1] + + return '3%d' % (color,) + else: + gray_possible = True + sep = 2.5 + + while gray_possible: + if r < sep or g < sep or b < sep: + gray = r < sep and g < sep and b < sep + gray_possible = False + + sep += 42.5 + + if gray: + color = 232 + int(float(sum(rgb) / 33.0)) + else: + color = sum([16]+[int(6 * float(val)/256) * mod + for val, mod in zip(rgb, [36, 6, 1])]) + + return '38;5;%d' % (color,) + + def wrap(self, *codes): + return '\x1b[%sm' % (''.join(codes),) + + def rainbow(self, freq, i): + r = math.sin(freq * i) * 127 + 128 + g = math.sin(freq * i + 2 * math.pi / 3) * 127 + 128 + b = math.sin(freq * i + 4 * math.pi / 3) * 127 + 128 + return [r, g, b] + + def cat(self, fd, options): + if options.animate: + self.output.write('\x1b[?25l') + + for line in fd: + options.os += 1 + self.println(line, options) + + if options.animate: + self.output.write('\x1b[?25h') + + def println(self, s, options): + s = s.rstrip() + if options.force or self.output.isatty(): + s = STRIP_ANSI.sub('', s) + + if options.animate: + self.println_ani(s, options) + else: + self.println_plain(s, options) + + self.output.write('\n') + self.output.flush() + if os.name == 'nt': + self.output.println() + + def println_ani(self, s, options): + if not s: + return + + for i in range(1, options.duration): + self.output.write('\x1b[%dD' % (len(s),)) + self.output.flush() + options.os += options.spread + self.println_plain(s, options) + time.sleep(1.0 / options.speed) + + def println_plain(self, s, options): + for i, c in enumerate(s if PY3 else s.decode(options.charset_py2, 'replace')): + rgb = self.rainbow(options.freq, options.os + i / options.spread) + self.output.write(''.join([ + self.wrap(self.ansi(rgb)), + c if PY3 else c.encode(options.charset_py2, 'replace'), + ])) + if os.name == 'nt': + self.output.print() + + +def detect_mode(term_hint='xterm-256color'): + ''' + Poor-mans color mode detection. + ''' + if 'ANSICON' in os.environ: + return 16 + elif os.environ.get('ConEmuANSI', 'OFF') == 'ON': + return 256 + else: + term = os.environ.get('TERM', term_hint) + if term.endswith('-256color') or term in ('xterm', 'screen'): + return 256 + elif term.endswith('-color') or term in ('rxvt',): + return 16 + else: + return 256 # optimistic default + + +def run(): + """Main entry point.""" + import optparse + + parser = optparse.OptionParser(usage=r'%prog [] [file ...]') + parser.add_option('-p', '--spread', type='float', default=3.0, + help='Rainbow spread') + parser.add_option('-F', '--freq', type='float', default=0.1, + help='Rainbow frequency') + parser.add_option('-S', '--seed', type='int', default=0, + help='Rainbow seed') + parser.add_option('-a', '--animate', action='store_true', default=False, + help='Enable psychedelics') + parser.add_option('-d', '--duration', type='int', default=12, + help='Animation duration') + parser.add_option('-s', '--speed', type='float', default=20.0, + help='Animation speed') + parser.add_option('-f', '--force', action='store_true', default=False, + help='Force colour even when stdout is not a tty') + + parser.add_option('-3', action='store_const', dest='mode', const=8, + help='Force 3 bit colour mode') + parser.add_option('-4', action='store_const', dest='mode', const=16, + help='Force 4 bit colour mode') + parser.add_option('-8', action='store_const', dest='mode', const=256, + help='Force 8 bit colour mode') + + parser.add_option('-c', '--charset-py2', default='utf-8', + help='Manually set a charset to convert from, for python 2.7') + + options, args = parser.parse_args() + options.os = random.randint(0, 256) if options.seed == 0 else options.seed + options.mode = options.mode or detect_mode() + + if os.name == 'nt': + lolcat = LolCat(mode=options.mode,output=stdoutWin()) + else: + lolcat = LolCat(mode=options.mode) + + if not args: + args = ['-'] + + for filename in args: + try: + if filename == '-': + lolcat.cat(sys.stdin, options) + else: + with open(filename, 'r', errors='backslashreplace') as handle: + lolcat.cat(handle, options) + except IOError as error: + sys.stderr.write(str(error) + '\n') + except KeyboardInterrupt: + sys.stderr.write('\n') + # exit 130 for terminated-by-ctrl-c, from http://tldp.org/LDP/abs/html/exitcodes.html + return 130 + +if __name__ == '__main__': + sys.exit(run()) diff --git a/.venv/bin/mypy b/.venv/bin/mypy new file mode 100755 index 0000000..532175d --- /dev/null +++ b/.venv/bin/mypy @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.__main__ import console_entry +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(console_entry()) diff --git a/.venv/bin/mypyc b/.venv/bin/mypyc new file mode 100755 index 0000000..76e4ba2 --- /dev/null +++ b/.venv/bin/mypyc @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from mypyc.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/nodeenv b/.venv/bin/nodeenv new file mode 100755 index 0000000..23f1fdd --- /dev/null +++ b/.venv/bin/nodeenv @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from nodeenv import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/pip b/.venv/bin/pip new file mode 100755 index 0000000..dc8e383 --- /dev/null +++ b/.venv/bin/pip @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.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()) diff --git a/.venv/bin/pip3 b/.venv/bin/pip3 new file mode 100755 index 0000000..dc8e383 --- /dev/null +++ b/.venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.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()) diff --git a/.venv/bin/pip3.8 b/.venv/bin/pip3.8 new file mode 100755 index 0000000..dc8e383 --- /dev/null +++ b/.venv/bin/pip3.8 @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.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()) diff --git a/.venv/bin/pre-commit b/.venv/bin/pre-commit new file mode 100755 index 0000000..93c51be --- /dev/null +++ b/.venv/bin/pre-commit @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pre_commit.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/pre-commit-validate-config b/.venv/bin/pre-commit-validate-config new file mode 100755 index 0000000..37e8d08 --- /dev/null +++ b/.venv/bin/pre-commit-validate-config @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pre_commit.clientlib import validate_config_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(validate_config_main()) diff --git a/.venv/bin/pre-commit-validate-manifest b/.venv/bin/pre-commit-validate-manifest new file mode 100755 index 0000000..ec9f1a3 --- /dev/null +++ b/.venv/bin/pre-commit-validate-manifest @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pre_commit.clientlib import validate_manifest_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(validate_manifest_main()) diff --git a/.venv/bin/prune-broken-imports b/.venv/bin/prune-broken-imports new file mode 100755 index 0000000..289de97 --- /dev/null +++ b/.venv/bin/prune-broken-imports @@ -0,0 +1,35 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +prune-broken-imports *.py +prune-broken-imports < foo.py + +Removes broken imports. + +Note: This actually executes imports. + +If filenames are given on the command line, rewrites them. Otherwise, if +stdin is not a tty, read from stdin and write to stdout. + +Only top-level import statements are touched. + +""" +# pyflyby/prune-broken-imports +# Copyright (C) 2012, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import absolute_import, division, with_statement + +from pyflyby._cmdline import parse_args, process_actions +from pyflyby._imports2s import remove_broken_imports + + +def main(): + options, args = parse_args( + import_format_params=True, modify_action_params=True) + def modify(x): + return remove_broken_imports(x, params=options.params) + process_actions(args, options.actions, modify) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/py b/.venv/bin/py new file mode 100755 index 0000000..de237bd --- /dev/null +++ b/.venv/bin/py @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pyflyby._py import py_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(py_main()) diff --git a/.venv/bin/py3 b/.venv/bin/py3 new file mode 100755 index 0000000..de237bd --- /dev/null +++ b/.venv/bin/py3 @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pyflyby._py import py_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(py_main()) diff --git a/.venv/bin/pycodestyle b/.venv/bin/pycodestyle new file mode 100755 index 0000000..1ee6679 --- /dev/null +++ b/.venv/bin/pycodestyle @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pycodestyle import _main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(_main()) diff --git a/.venv/bin/pyflakes b/.venv/bin/pyflakes new file mode 100755 index 0000000..be80352 --- /dev/null +++ b/.venv/bin/pyflakes @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pyflakes.api import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/pyflyby-diff b/.venv/bin/pyflyby-diff new file mode 100755 index 0000000..0c05931 --- /dev/null +++ b/.venv/bin/pyflyby-diff @@ -0,0 +1,32 @@ +#!/bin/bash -e + +# License for THIS FILE ONLY: CC0 Public Domain Dedication +# http://creativecommons.org/publicdomain/zero/1.0/ + +# Get the directory containing to the symlink target of the script. +if script=$(readlink -e "$0" 2>/dev/null) && [[ "$script" -ef "$0" ]]; then + scriptdir=$(dirname "$script") +elif script=$(realpath "$0" 2>/dev/null) && [[ "$script" -ef "$0" ]]; then + scriptdir=$(dirname "$script") +elif script=$(greadlink -e "$0" 2>/dev/null) && [[ "$script" -ef "$0" ]]; then + scriptdir=$(dirname "$script") +else + scriptdir=$( + d=$(dirname "$0") + b=$(basename "$0") + cd "$d" + if l=$(readlink "$b"); then + ld=$(dirname "$l") + cd "$ld" + fi + pwd + ) +fi + +PATH="$scriptdir:$PATH" + +if [[ -t 1 ]] && type -p diff-colorize >/dev/null; then + diff -u "$@" | diff-colorize +else + diff -u "$@" +fi diff --git a/.venv/bin/pygmentize b/.venv/bin/pygmentize new file mode 100755 index 0000000..a59c939 --- /dev/null +++ b/.venv/bin/pygmentize @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pygments.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/python b/.venv/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/.venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/.venv/bin/python3 b/.venv/bin/python3 new file mode 120000 index 0000000..4e3a685 --- /dev/null +++ b/.venv/bin/python3 @@ -0,0 +1 @@ +/home/walkers/.pyenv/versions/3.8.12/bin/python3 \ No newline at end of file diff --git a/.venv/bin/reformat-imports b/.venv/bin/reformat-imports new file mode 100755 index 0000000..dc696d4 --- /dev/null +++ b/.venv/bin/reformat-imports @@ -0,0 +1,28 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +reformat-imports *.py +reformat-imports < foo.py + +Reformats the top-level 'import' blocks within the python module/script. + +""" +# pyflyby/reformat-imports +# Copyright (C) 2011, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import absolute_import, division, with_statement + +from pyflyby._cmdline import parse_args, process_actions +from pyflyby._imports2s import reformat_import_statements + + +def main(): + options, args = parse_args( + import_format_params=True, modify_action_params=True) + def modify(x): + return reformat_import_statements(x, params=options.params) + process_actions(args, options.actions, modify) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/replace-star-imports b/.venv/bin/replace-star-imports new file mode 100755 index 0000000..cb28624 --- /dev/null +++ b/.venv/bin/replace-star-imports @@ -0,0 +1,38 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +replace-star-imports *.py +replace-star-imports < foo.py + +Replaces:: + from foo.bar import * +with:: + from foo.bar import (f1, f2, ...) + +Note: This actually executes imports. + +If filenames are given on the command line, rewrites them. Otherwise, if +stdin is not a tty, read from stdin and write to stdout. + +Only top-level import statements are touched. + +""" +# pyflyby/replace-star-imports +# Copyright (C) 2012, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import absolute_import, division, with_statement + +from pyflyby._cmdline import parse_args, process_actions +from pyflyby._imports2s import replace_star_imports + + +def main(): + options, args = parse_args( + import_format_params=True, modify_action_params=True) + def modify(x): + return replace_star_imports(x, params=options.params) + process_actions(args, options.actions, modify) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/stubgen b/.venv/bin/stubgen new file mode 100755 index 0000000..9f15d08 --- /dev/null +++ b/.venv/bin/stubgen @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.stubgen import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/stubtest b/.venv/bin/stubtest new file mode 100755 index 0000000..189cc02 --- /dev/null +++ b/.venv/bin/stubtest @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.stubtest import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/tidy-imports b/.venv/bin/tidy-imports new file mode 100755 index 0000000..094dd79 --- /dev/null +++ b/.venv/bin/tidy-imports @@ -0,0 +1,156 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +tidy-imports *.py +tidy-imports < foo.py + +Automatically improves python import statements. + + - Adds missing imports and mandatory imports. + - Removes unused imports. + - Nicely formats imports (sorts, aligns, wraps). + +If filenames are given on the command line, rewrites them. Otherwise, if +stdin is not a tty, read from stdin and write to stdout. + +Only top-level import statements are touched. + +""" + +# pyflyby/tidy-imports +# Copyright (C) 2011, 2012, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import print_function, absolute_import, division, with_statement + +from distutils.spawn import find_executable +import subprocess +import sys + +from pyflyby._cmdline import hfmt, parse_args, process_actions +from pyflyby._imports2s import (canonicalize_imports, + fix_unused_and_missing_imports, + replace_star_imports, + transform_imports) +from pyflyby._log import logger + + +def main(): + def addopts(parser): + parser.add_option('--add-missing', + default=True, action='store_true', + help=hfmt(''' + (Default) Add missing imports.''')) + parser.add_option('--no-add-missing', dest='add_missing', + default=True, action='store_false', + help=hfmt(''' + Don't add missing imports.''')) + parser.add_option('--remove-unused', + default="AUTOMATIC", action='store_true', + help=hfmt(''' + Remove unused imports + (default unless filename == __init__.py).''')) + parser.add_option('--no-remove-unused', dest='remove_unused', + action='store_false', + help=hfmt(''' + Don't remove unused imports + (default if filename == __init__.py).''')) + parser.add_option('--add-mandatory', + default=True, action='store_true', + help=hfmt(''' + (Default) Add mandatory imports.''')) + parser.add_option('--no-add-mandatory', dest='add_mandatory', + default=True, action='store_false', + help=hfmt(''' + Don't add mandatory imports.''')) + parser.add_option('--replace-star-imports', + default=False, action='store_true', + help=hfmt(''' + Replace 'from foo.bar import *' with full list + of imports before removing unused imports.''')) + parser.add_option('--no-replace-star-imports', + dest='replace_star_imports', + action='store_false', + help=hfmt(''' + (Default) Don't replace 'from foo.bar import + *'.''')) + parser.add_option('--canonicalize', + default=True, action='store_true', + help=hfmt(''' + (Default) Replace imports with canonical + equivalent imports, according to database.''')) + parser.add_option('--no-canonicalize', dest='canonicalize', + default=True, action='store_false', + help=hfmt(''' + Don't canonicalize imports.''')) + parser.add_option('--py23-fallback', dest='py23_fallback', + default=True, action='store_true', + help=hfmt(''' + (Default) Automatically fallback to + python2/python3 if the source file has a syntax + error.''')) + parser.add_option('--no-py23-fallback', dest='py23_fallback', + default=True, action='store_false', + help=hfmt(''' + Do not automatically fallback to + python2/python3 if the source file has a syntax + error.''')) + + + def transform_callback(option, opt_str, value, group): + k, v = value.split("=", 1) + group.values.transformations[k] = v + parser.add_option("--transform", action='callback', + type="string", callback=transform_callback, + metavar="OLD=NEW", + dest="transformations", default={}, + help=hfmt(''' + Replace OLD with NEW in imports. + May be specified multiple times.''')) + def no_add_callback(option, opt_str, value, group): + group.values.add_missing = False + group.values.add_mandatory = False + parser.add_option('--no-add', action='callback', + callback=no_add_callback, + help=hfmt(''' + Equivalent to --no-add-missing + --no-add-mandatory.''')) + options, args = parse_args( + addopts, import_format_params=True, modify_action_params=True) + def modify(x): + if options.canonicalize: + x = canonicalize_imports(x, params=options.params) + if options.transformations: + x = transform_imports(x, options.transformations, + params=options.params) + if options.replace_star_imports: + x = replace_star_imports(x, params=options.params) + return fix_unused_and_missing_imports( + x, params=options.params, + add_missing=options.add_missing, + remove_unused=options.remove_unused, + add_mandatory=options.add_mandatory, + ) + + if options.py23_fallback: + try: + process_actions(args, options.actions, modify, + reraise_exceptions=SyntaxError) + except SyntaxError as e: + python = 'python2' if sys.version_info[0] == 3 else 'python3' + python_full = find_executable(python) + if not python_full: + logger.error("Fallback failed: could not find %s", python) + raise + logger.info("SyntaxError detected ({}), falling back to {}".format( + e, python)) + args = [python_full] + sys.argv + ['--no-py23-fallback'] + try: + raise SystemExit(subprocess.call(args)) + except KeyboardInterrupt: + sys.exit(1) + else: + process_actions(args, options.actions, modify) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/transform-imports b/.venv/bin/transform-imports new file mode 100755 index 0000000..ea1bfaf --- /dev/null +++ b/.venv/bin/transform-imports @@ -0,0 +1,48 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +""" +transform-imports --transform aa.bb=xx.yy *.py +transform-imports --transform aa.bb=xx.yy < foo.py + +Transforms:: + from aa.bb.cc import dd, ee + from aa import bb +to:: + from xx.yy.cc import dd, ee + from xx import yy as bb + +If filenames are given on the command line, rewrites them. Otherwise, if +stdin is not a tty, read from stdin and write to stdout. + +""" + +# pyflyby/transform-imports +# Copyright (C) 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import absolute_import, division, with_statement + +from pyflyby._cmdline import hfmt, parse_args, process_actions +from pyflyby._imports2s import transform_imports + + +def main(): + transformations = {} + def addopts(parser): + def callback(option, opt_str, value, group): + k, v = value.split("=", 1) + transformations[k] = v + parser.add_option("--transform", action='callback', + type="string", callback=callback, + metavar="OLD=NEW", + help=hfmt(''' + Replace OLD with NEW in imports. + May be specified multiple times.''')) + options, args = parse_args( + addopts, import_format_params=True, modify_action_params=True) + def modify(x): + return transform_imports(x, transformations, params=options.params) + process_actions(args, options.actions, modify) + + +if __name__ == '__main__': + main() diff --git a/.venv/bin/virtualenv b/.venv/bin/virtualenv new file mode 100755 index 0000000..391f9ab --- /dev/null +++ b/.venv/bin/virtualenv @@ -0,0 +1,8 @@ +#!/home/walkers/git/creeper-adventure/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from virtualenv.__main__ import run_with_catch +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run_with_catch()) diff --git a/.venv/etc/pyflyby/__pycache__/canonical.cpython-38.pyc b/.venv/etc/pyflyby/__pycache__/canonical.cpython-38.pyc new file mode 100644 index 0000000..712ff97 Binary files /dev/null and b/.venv/etc/pyflyby/__pycache__/canonical.cpython-38.pyc differ diff --git a/.venv/etc/pyflyby/__pycache__/common.cpython-38.pyc b/.venv/etc/pyflyby/__pycache__/common.cpython-38.pyc new file mode 100644 index 0000000..d18aa65 Binary files /dev/null and b/.venv/etc/pyflyby/__pycache__/common.cpython-38.pyc differ diff --git a/.venv/etc/pyflyby/__pycache__/forget.cpython-38.pyc b/.venv/etc/pyflyby/__pycache__/forget.cpython-38.pyc new file mode 100644 index 0000000..e8ad9cc Binary files /dev/null and b/.venv/etc/pyflyby/__pycache__/forget.cpython-38.pyc differ diff --git a/.venv/etc/pyflyby/__pycache__/mandatory.cpython-38.pyc b/.venv/etc/pyflyby/__pycache__/mandatory.cpython-38.pyc new file mode 100644 index 0000000..d8fbd3c Binary files /dev/null and b/.venv/etc/pyflyby/__pycache__/mandatory.cpython-38.pyc differ diff --git a/.venv/etc/pyflyby/__pycache__/numpy.cpython-38.pyc b/.venv/etc/pyflyby/__pycache__/numpy.cpython-38.pyc new file mode 100644 index 0000000..c18067a Binary files /dev/null and b/.venv/etc/pyflyby/__pycache__/numpy.cpython-38.pyc differ diff --git a/.venv/etc/pyflyby/__pycache__/std.cpython-38.pyc b/.venv/etc/pyflyby/__pycache__/std.cpython-38.pyc new file mode 100644 index 0000000..da57b90 Binary files /dev/null and b/.venv/etc/pyflyby/__pycache__/std.cpython-38.pyc differ diff --git a/.venv/etc/pyflyby/canonical.py b/.venv/etc/pyflyby/canonical.py new file mode 100644 index 0000000..5fb3826 --- /dev/null +++ b/.venv/etc/pyflyby/canonical.py @@ -0,0 +1,10 @@ + +# To make tidy-imports automatically transform imports, create a dictionary +# like this: +# __canonical_imports__ = { +# "oldmodule.oldname": "newmodule.newname" +# } +# +# You can write this in any *.py file in your $PYFLYBY_PATH, +# e.g. ~/.pyflyby/canonical.py. + diff --git a/.venv/etc/pyflyby/common.py b/.venv/etc/pyflyby/common.py new file mode 100644 index 0000000..e4f4531 --- /dev/null +++ b/.venv/etc/pyflyby/common.py @@ -0,0 +1,27 @@ +from Crypto.Cipher import AES +import IPython +import blist +from blist import sorteddict +import cssutils +import dateutil +import dateutil.parser +import kerberos +import mutagen +import perl +import pexpect +import pstats +import pyflyby +from pyflyby import xreload +import pylab +import pyodbc +import pysvn +import pytest +import pytz +import requests +import sqlalchemy +import sqlalchemy.orm +import sqlalchemy.sql +import sympy +import xlrd +import yaml +from yaml import MarkedYAMLError, YAMLError, YAMLObject diff --git a/.venv/etc/pyflyby/forget.py b/.venv/etc/pyflyby/forget.py new file mode 100644 index 0000000..311f43f --- /dev/null +++ b/.venv/etc/pyflyby/forget.py @@ -0,0 +1,10 @@ +# To remove imports from the set of known imports, write, for example: +# __forget_imports__ = [ +# 'numpy.sin', +# ] +# +# You can write this in any *.py file in your $PYFLYBY_PATH, +# e.g. ~/.pyflyby/forget.py. +# +# This can be useful if you're inheriting somebody else's import database. +# You can inherit most of the parent database, but exclude certain imports. diff --git a/.venv/etc/pyflyby/mandatory.py b/.venv/etc/pyflyby/mandatory.py new file mode 100644 index 0000000..6f7401f --- /dev/null +++ b/.venv/etc/pyflyby/mandatory.py @@ -0,0 +1,10 @@ +# The following __mandatory_imports__ line makes tidy-imports add the +# mentioned imports to all files. +# You can also include imports other than __future__ imports. +# +# You can write this in any *.py file in your $PYFLYBY_PATH, +# e.g. ~/.pyflyby/mandatory.py. + +# __mandatory_imports__ = [ +# 'from __future__ import absolute_import, division', +# ] diff --git a/.venv/etc/pyflyby/numpy.py b/.venv/etc/pyflyby/numpy.py new file mode 100644 index 0000000..65d8a83 --- /dev/null +++ b/.venv/etc/pyflyby/numpy.py @@ -0,0 +1,156 @@ +import matplotlib +from matplotlib import pyplot +import matplotlib.colors +from matplotlib.colors import ColorConverter +from matplotlib.font_manager import FontProperties +from matplotlib.patches import Rectangle +from matplotlib.pyplot import (clf, draw, figure, gca, gcf, grid, + ioff, legend, plot, savefig, scatter, + show, subplot, title, xlabel, ylabel, + ylim) +from matplotlib.ticker import (Formatter, Locator, NullFormatter, + NullLocator) +import numexpr +import numpy as np, numpy as npy, numpy +from numpy import (Inf, NAN, NaN, abs as aabs, absolute, + add, all as aall, allclose, alltrue, + amax, amin, angle, any as aany, + append as aappend, apply_along_axis, + apply_over_axes, arange, arccos, + arccosh, arcsin, arcsinh, arctan, + arctan2, arctanh, argmax, argmin, + argsort, argwhere, around, array, + array2string, array_equal, array_equiv, + array_repr, array_split, array_str, + asanyarray, asarray, asarray_chkfinite, + ascontiguousarray, asfarray, + asfortranarray, asmatrix, asscalar, + atleast_1d, atleast_2d, atleast_3d, + average, bartlett, base_repr, + binary_repr, bincount, bitwise_and, + bitwise_not, bitwise_or, bitwise_xor, + blackman, bmat, bool8, bool_, + broadcast, broadcast_arrays, byte, + byte_bounds, c_, can_cast, cdouble, + ceil, cfloat, character, chararray, + choose, clip, clongdouble, clongfloat, + column_stack, common_type, + compare_chararrays, compat, complex128, + complex64, complex_, complexfloating, + concatenate, conj, conjugate, convolve, + copy, copysign, corrcoef, correlate, + cos, cosh, cov, cross, csingle, + ctypeslib, cumprod, cumproduct, cumsum, + deg2rad, degrees, diag, diag_indices, + diag_indices_from, diagflat, diagonal, + diff, digitize, disp, divide, dot, + double, dsplit, dstack, dtype, ediff1d, + einsum, emath, empty, empty_like, + equal, exp, exp2, expand_dims, expm1, + extract, eye, fabs, + fastCopyAndTranspose, fill_diagonal, + find_common_type, fix, flatiter, + flatnonzero, fliplr, flipud, float32, + float64, float_, floating, floor, + floor_divide, fmax, fmin, fmod, frexp, + frombuffer, fromfile, fromfunction, + fromiter, frompyfunc, fromregex, + fromstring, gradient, greater, + greater_equal, hamming, hanning, + histogram, histogram2d, histogramdd, + hsplit, hstack, hypot, i0, identity, + iinfo, imag, in1d, index_exp, indices, + inexact, inf, inner, int0, int16, + int32, int64, int8, int_, intc, + integer, interp, intersect1d, intp, + invert, ipmt, irr, iscomplex, + iscomplexobj, isfinite, isfortran, + isinf, isnan, isneginf, isposinf, + isreal, isrealobj, isscalar, issctype, + issubclass_, issubdtype, issubsctype, + iterable, ix_, kaiser, kron, ldexp, + left_shift, less, less_equal, lexsort, + linalg, linspace, little_endian, + loadtxt, log, log as logarithm, log10, + log1p, log2, logaddexp, logaddexp2, + logical_and, logical_not, logical_or, + logical_xor, logspace, longcomplex, + longdouble, longfloat, longlong, + mafromtxt, mask_indices, mat, matrix, + maximum, mean, median, memmap, + meshgrid, mgrid, minimum, mintypecode, + mirr, mod, modf, msort, multiply, nan, + nan_to_num, nanargmax, nanargmin, + nanmax, nanmin, nansum, nbytes, + ndarray, ndenumerate, ndim, ndindex, + negative, newaxis, newbuffer, + nextafter, nonzero, not_equal, nper, + npv, number, object0, object_, ogrid, + ones, ones_like, outer, packbits, pi, + piecewise, pkgload, place, pmt, poly, + poly1d, polyadd, polyder, polydiv, + polyfit, polyint, polymul, polynomial, + polysub, polyval, power, ppmt, prod, + product, ptp, putmask, pv, r_, rad2deg, + radians, rank, rate, ravel, real, + real_if_close, recarray, recfromcsv, + recfromtxt, reciprocal, record, + remainder, repeat, reshape, resize, + restoredot, right_shift, rint, roll, + rollaxis, roots, rot90, round, round_, + row_stack, s_, searchsorted, + select as aselect, setbufsize, + setdiff1d, setxor1d, shape, short, + show_config, sign, signbit, + signedinteger, sin, sinc, single, + singlecomplex, sinh, size as asize, + sometrue, sort as asort, sort, + sort_complex, spacing, split, sqrt, + square, squeeze, std, str_, string0, + string_, subtract, sum, swapaxes, take, + tan, tanh, tensordot, testing, tile, + trace, transpose, trapz, tri, tril, + tril_indices, tril_indices_from, + trim_zeros, triu, triu_indices, + triu_indices_from, true_divide, trunc, + ubyte, ufunc, uint, uint0, uint16, + uint32, uint64, uint8, uintc, uintp, + ulonglong, unicode0, unicode_, union1d, + unique, unpackbits, unravel_index, + unsignedinteger, unwrap, ushort, + vander, var, vdot, vectorize, void, + void0, vsplit, vstack, where, zeros, + zeros_like) +from numpy.core.umath_tests import inner1d +from numpy.fft import (fft, fft2, fftn, ifft, ifft2, ifftn, + irfft, irfft2, irfftn, rfft, rfft2, + rfftn) +from numpy.lib import recfunctions as recf, recfunctions +from numpy.lib.stride_tricks import as_strided +import numpy.linalg +from numpy.linalg import cholesky, det, eigh, inv, pinv, svd +from numpy.random import (normal, rand, randint, randn, + random as arandom, shuffle) +import numpy.testing +import numpy.version +import pandas, pandas as pd +from pandas import DataFrame, Series, TimeSeries +import pylab as pl, pylab +import scipy +from scipy import integrate, optimize, special, stats +import scipy.cluster.hierarchy +import scipy.integrate +import scipy.interpolate +from scipy.interpolate import InterpolatedUnivariateSpline, interp1d +import scipy.linalg +import scipy.optimize +from scipy.optimize import (curve_fit, fmin_l_bfgs_b, fsolve, + leastsq) +from scipy.optimize.zeros import bisect +import scipy.special +from scipy.special import gamma, gammainc, gammaincinv, ndtri +import scipy.stats +from scipy.stats import (chisqprob, distributions, + scoreatpercentile, uniform) +from scipy.stats.distributions \ + import norm diff --git a/.venv/etc/pyflyby/std.py b/.venv/etc/pyflyby/std.py new file mode 100644 index 0000000..bce6ee0 --- /dev/null +++ b/.venv/etc/pyflyby/std.py @@ -0,0 +1,331 @@ +import UserDict +from UserDict import DictMixin +from UserList import UserList +from _strptime import TimeRE +import abc +import argparse +import ast +import atexit +import base64 +from base64 import b64decode, b64encode +import binascii +from binascii import hexlify, unhexlify +import bisect +import bootstrap +import bz2 +import cProfile +import cgi +import collections +from collections import defaultdict, deque, namedtuple +import commands +import contextlib +from contextlib import closing, contextmanager, nested +import copy +import csv +import ctypes +from ctypes import CDLL +import datetime +import decimal +from decimal import Decimal +import decorator +import difflib +from difflib import SequenceMatcher, context_diff +import dis +import email +from email import encoders +from email.encoders import encode_base64 +from email.message import Message +from email.mime.audio import MIMEAudio +from email.utils import COMMASPACE, formatdate +import errno +from errno import (E2BIG, EACCES, EADDRINUSE, + EADDRNOTAVAIL, EAFNOSUPPORT, EAGAIN, + EALREADY, EBADF, EBADMSG, EBUSY, + ECHILD, ECONNABORTED, ECONNREFUSED, + ECONNRESET, EDEADLK, EDESTADDRREQ, + EDOM, EDQUOT, EEXIST, EFAULT, EFBIG, + EHOSTDOWN, EHOSTUNREACH, EIDRM, EILSEQ, + EINPROGRESS, EINTR, EINVAL, EIO, + EISCONN, EISDIR, ELOOP, EMFILE, EMLINK, + EMSGSIZE, EMULTIHOP, ENAMETOOLONG, + ENETDOWN, ENETRESET, ENETUNREACH, + ENFILE, ENOBUFS, ENODATA, ENODEV, + ENOENT, ENOEXEC, ENOLCK, ENOLINK, + ENOMEM, ENOMSG, ENOPROTOOPT, ENOSPC, + ENOSR, ENOSTR, ENOSYS, ENOTBLK, + ENOTCONN, ENOTDIR, ENOTEMPTY, ENOTSOCK, + ENOTSUP, ENOTTY, ENXIO, EOPNOTSUPP, + EOVERFLOW, EPERM, EPFNOSUPPORT, EPIPE, + EPROTO, EPROTONOSUPPORT, EPROTOTYPE, + ERANGE, EREMOTE, EROFS, ESHUTDOWN, + ESOCKTNOSUPPORT, ESPIPE, ESRCH, ESTALE, + ETIME, ETIMEDOUT, ETOOMANYREFS, + ETXTBSY, EUSERS, EWOULDBLOCK, EXDEV) +import exceptions +import fcntl +import filecmp +import fileinput +import functional +import functools +from functools import (partial, reduce, total_ordering, + update_wrapper, wraps) +import gc +import getpass +from getpass import getuser +import glob +import grp +from grp import getgrall, getgrgid, getgrnam +import gzip +import h5py +import hashlib +from hashlib import (md5, sha1, sha224, sha256, sha384, + sha512) +import heapq +import imp +import inspect +from inspect import ArgSpec, getargspec +import io +import itertools +from itertools import (chain, count, groupby, islice, product, + repeat, tee) +import json +from keyword import iskeyword +import linecache +import locale +import logging +from lxml import etree +import marshal +import math +import matplotlib +import mimetypes +import misc.double +import mmap +import new +import numbers +from numbers import (Complex, Integral, Number, Rational, + Real) +import operator +from operator import add, indexOf, itemgetter, mul +import optparse +from optparse import (BadOptionError, OptParseError, + OptionParser, OptionValueError) +import os +from os import (chmod, close, getcwd, getenv, geteuid, + getpid, getuid, makedirs, mkdir, + mkfifo, path, remove, rename, system, + unlink) +import os.path +from os.path import (abspath, basename, dirname, exists, + getsize, isfile, normpath, realpath) +import parser +import pdb +import pickle +from pickle import PickleError, UnpicklingError +import pickletools +import pkg_resources +from pkg_resources import load_entry_point +import pkgutil +import pprint +import psutil +import pwd +from pwd import getpwall, getpwnam, getpwuid +import random +from random import shuffle +import re +import resource +import select +import shlex +import shutil +from shutil import (copyfile, copyfileobj, copystat, + copytree, rmtree) +import signal +import six +from six import StringIO +from six.moves import (builtins, cPickle, configparser, + copyreg, email_mime_base, + email_mime_image, email_mime_multipart, + email_mime_text, http_client, map, + queue, zip) +from six.moves.urllib.parse import urlencode +from six.moves.urllib.request import urlopen +import smtplib +from smtplib import (SMTP, SMTPAuthenticationError, + SMTPConnectError, SMTPDataError, + SMTPException, SMTPHeloError, + SMTPRecipientsRefused, + SMTPResponseException, + SMTPSenderRefused, + SMTPServerDisconnected, SMTP_SSL) +import socket +from socket import (AF_APPLETALK, AF_ASH, AF_ATMPVC, + AF_ATMSVC, AF_AX25, AF_BRIDGE, + AF_DECnet, AF_ECONET, AF_INET, + AF_INET6, AF_IPX, AF_IRDA, AF_KEY, + AF_LLC, AF_NETBEUI, AF_NETLINK, + AF_NETROM, AF_PACKET, AF_PPPOX, + AF_ROSE, AF_ROUTE, AF_SECURITY, AF_SNA, + AF_TIPC, AF_UNIX, AF_UNSPEC, + AF_WANPIPE, AF_X25, AI_ADDRCONFIG, + AI_ALL, AI_CANONNAME, AI_NUMERICHOST, + AI_NUMERICSERV, AI_PASSIVE, + AI_V4MAPPED, CAPI, EAI_ADDRFAMILY, + EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, + EAI_FAMILY, EAI_MEMORY, EAI_NODATA, + EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, + EAI_SOCKTYPE, EAI_SYSTEM, + INADDR_ALLHOSTS_GROUP, INADDR_ANY, + INADDR_BROADCAST, INADDR_LOOPBACK, + INADDR_MAX_LOCAL_GROUP, INADDR_NONE, + INADDR_UNSPEC_GROUP, IPPORT_RESERVED, + IPPORT_USERRESERVED, IPPROTO_AH, + IPPROTO_DSTOPTS, IPPROTO_EGP, + IPPROTO_ESP, IPPROTO_FRAGMENT, + IPPROTO_GRE, IPPROTO_HOPOPTS, + IPPROTO_ICMP, IPPROTO_ICMPV6, + IPPROTO_IDP, IPPROTO_IGMP, IPPROTO_IP, + IPPROTO_IPIP, IPPROTO_IPV6, + IPPROTO_NONE, IPPROTO_PIM, IPPROTO_PUP, + IPPROTO_RAW, IPPROTO_ROUTING, + IPPROTO_RSVP, IPPROTO_TCP, IPPROTO_TP, + IPPROTO_UDP, IPV6_CHECKSUM, + IPV6_DSTOPTS, IPV6_HOPLIMIT, + IPV6_HOPOPTS, IPV6_JOIN_GROUP, + IPV6_LEAVE_GROUP, IPV6_MULTICAST_HOPS, + IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, + IPV6_NEXTHOP, IPV6_PKTINFO, + IPV6_RECVDSTOPTS, IPV6_RECVHOPLIMIT, + IPV6_RECVHOPOPTS, IPV6_RECVPKTINFO, + IPV6_RECVRTHDR, IPV6_RECVTCLASS, + IPV6_RTHDR, IPV6_RTHDRDSTOPTS, + IPV6_RTHDR_TYPE_0, IPV6_TCLASS, + IPV6_UNICAST_HOPS, IPV6_V6ONLY, + IP_ADD_MEMBERSHIP, + IP_DEFAULT_MULTICAST_LOOP, + IP_DEFAULT_MULTICAST_TTL, + IP_DROP_MEMBERSHIP, IP_HDRINCL, + IP_MAX_MEMBERSHIPS, IP_MULTICAST_IF, + IP_MULTICAST_LOOP, IP_MULTICAST_TTL, + IP_OPTIONS, IP_RECVOPTS, + IP_RECVRETOPTS, IP_RETOPTS, IP_TOS, + IP_TTL, MSG_CTRUNC, MSG_DONTROUTE, + MSG_DONTWAIT, MSG_EOR, MSG_OOB, + MSG_PEEK, MSG_TRUNC, MSG_WAITALL, + NETLINK_DNRTMSG, NETLINK_FIREWALL, + NETLINK_IP6_FW, NETLINK_NFLOG, + NETLINK_ROUTE, NETLINK_USERSOCK, + NETLINK_XFRM, NI_DGRAM, NI_MAXHOST, + NI_MAXSERV, NI_NAMEREQD, NI_NOFQDN, + NI_NUMERICHOST, NI_NUMERICSERV, + PACKET_BROADCAST, PACKET_FASTROUTE, + PACKET_HOST, PACKET_LOOPBACK, + PACKET_MULTICAST, PACKET_OTHERHOST, + PACKET_OUTGOING, PF_PACKET, SHUT_RD, + SHUT_RDWR, SHUT_WR, SOCK_DGRAM, + SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, + SOCK_STREAM, SOL_IP, SOL_SOCKET, + SOL_TCP, SOL_TIPC, SOL_UDP, SOMAXCONN, + SO_ACCEPTCONN, SO_ATTACH_FILTER, + SO_BINDTODEVICE, SO_BROADCAST, + SO_BSDCOMPAT, SO_DEBUG, + SO_DETACH_FILTER, SO_DONTROUTE, + SO_ERROR, SO_KEEPALIVE, SO_LINGER, + SO_NO_CHECK, SO_OOBINLINE, SO_PASSCRED, + SO_PASSSEC, SO_PEERCRED, SO_PEERNAME, + SO_PEERSEC, SO_PRIORITY, SO_RCVBUF, + SO_RCVBUFFORCE, SO_RCVLOWAT, + SO_RCVTIMEO, SO_REUSEADDR, + SO_SECURITY_AUTHENTICATION, + SO_SECURITY_ENCRYPTION_NETWORK, + SO_SECURITY_ENCRYPTION_TRANSPORT, + SO_SNDBUF, SO_SNDBUFFORCE, SO_SNDLOWAT, + SO_SNDTIMEO, SO_TIMESTAMP, + SO_TIMESTAMPNS, SO_TYPE, SocketType, + TCP_CONGESTION, TCP_CORK, + TCP_DEFER_ACCEPT, TCP_INFO, + TCP_KEEPCNT, TCP_KEEPIDLE, + TCP_KEEPINTVL, TCP_LINGER2, TCP_MAXSEG, + TCP_MD5SIG, TCP_MD5SIG_MAXKEYLEN, + TCP_NODELAY, TCP_QUICKACK, TCP_SYNCNT, + TCP_WINDOW_CLAMP, TIPC_ADDR_ID, + TIPC_ADDR_NAME, TIPC_ADDR_NAMESEQ, + TIPC_CFG_SRV, TIPC_CLUSTER_SCOPE, + TIPC_CONN_TIMEOUT, + TIPC_CRITICAL_IMPORTANCE, + TIPC_DEST_DROPPABLE, + TIPC_HIGH_IMPORTANCE, TIPC_IMPORTANCE, + TIPC_LOW_IMPORTANCE, + TIPC_MEDIUM_IMPORTANCE, + TIPC_NODE_SCOPE, TIPC_PUBLISHED, + TIPC_SRC_DROPPABLE, + TIPC_SUBSCR_TIMEOUT, TIPC_SUB_CANCEL, + TIPC_SUB_PORTS, TIPC_SUB_SERVICE, + TIPC_TOP_SRV, TIPC_WAIT_FOREVER, + TIPC_WITHDRAWN, TIPC_ZONE_SCOPE, + gaierror, getaddrinfo, getfqdn, + gethostbyaddr, gethostbyname, + gethostbyname_ex, gethostname, + getnameinfo, getprotobyname, + getservbyname, getservbyport, htonl, + htons, inet_aton, inet_ntoa, inet_ntop, + inet_pton, ntohl, ntohs, + setdefaulttimeout, socketpair) +import sqlite3 +import ssl +import stat +from stat import (ST_MTIME, S_IFMT, S_IMODE, S_ISBLK, + S_ISCHR, S_ISDIR, S_ISFIFO, S_ISLNK, + S_ISREG, S_ISSOCK) +import string +import struct +import subprocess +from subprocess import (CalledProcessError, PIPE, Popen, call, + check_call) +import symbol +import sys +from sys import exit, getsizeof, stderr, stdout +import tempfile +from tempfile import (NamedTemporaryFile, + SpooledTemporaryFile, TemporaryFile, + mkdtemp, mkstemp, mktemp) +import termios +import textwrap +from textwrap import dedent +import threading +from threading import (BoundedSemaphore, Condition, Lock, + RLock, Semaphore, Thread, Timer, + currentThread, current_thread) +import time +from time import (asctime, ctime, gmtime, localtime, + mktime, sleep, strftime, strptime) +import timeit +import token +import traceback +import types +from types import (BooleanType, BufferType, + BuiltinFunctionType, BuiltinMethodType, + ClassType, CodeType, ComplexType, + DictProxyType, DictType, + DictionaryType, EllipsisType, FileType, + FloatType, FrameType, FunctionType, + GeneratorType, GetSetDescriptorType, + InstanceType, IntType, LambdaType, + ListType, LongType, + MemberDescriptorType, MethodType, + ModuleType, NoneType, + NotImplementedType, ObjectType, + SliceType, StringType, StringTypes, + TracebackType, TupleType, TypeType, + UnboundMethodType, UnicodeType, + XRangeType) +import unittest +import urllib +import urllib2 +import warnings +import weakref +from weakref import (CallableProxyType, ProxyType, + ProxyTypes, ReferenceType, + WeakKeyDictionary, WeakValueDictionary, + getweakrefcount, getweakrefs) +from xml.dom import minidom +import xml.parsers.expat +import zlib diff --git a/.venv/include/site/python3.8/pygame/_camera.h b/.venv/include/site/python3.8/pygame/_camera.h new file mode 100644 index 0000000..075ef6f --- /dev/null +++ b/.venv/include/site/python3.8/pygame/_camera.h @@ -0,0 +1,26 @@ +/* + pygame - Python Game Library + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#ifndef _CAMERA_H +#define _CAMERA_H + +#include "_pygame.h" +#include "camera.h" + +#endif diff --git a/.venv/include/site/python3.8/pygame/_pygame.h b/.venv/include/site/python3.8/pygame/_pygame.h new file mode 100644 index 0000000..23da37f --- /dev/null +++ b/.venv/include/site/python3.8/pygame/_pygame.h @@ -0,0 +1,326 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +/* This will use PYGAMEAPI_EXTERN_SLOTS instead + * of PYGAMEAPI_DEFINE_SLOTS for base modules. + */ +#ifndef _PYGAME_INTERNAL_H +#define _PYGAME_INTERNAL_H + +#include "pgplatform.h" +/* + If PY_SSIZE_T_CLEAN is defined before including Python.h, length is a + Py_ssize_t rather than an int for all # variants of formats (s#, y#, etc.) +*/ +#define PY_SSIZE_T_CLEAN +#include + +/* Ensure PyPy-specific code is not in use when running on GraalPython (PR + * #2580) */ +#if defined(GRAALVM_PYTHON) && defined(PYPY_VERSION) +#undef PYPY_VERSION +#endif + +#include + +/* SDL 1.2 constants removed from SDL 2 */ +typedef enum { + SDL_HWSURFACE = 0, + SDL_RESIZABLE = SDL_WINDOW_RESIZABLE, + SDL_ASYNCBLIT = 0, + SDL_OPENGL = SDL_WINDOW_OPENGL, + SDL_OPENGLBLIT = 0, + SDL_ANYFORMAT = 0, + SDL_HWPALETTE = 0, + SDL_DOUBLEBUF = 0, + SDL_FULLSCREEN = SDL_WINDOW_FULLSCREEN, + SDL_HWACCEL = 0, + SDL_SRCCOLORKEY = 0, + SDL_RLEACCELOK = 0, + SDL_SRCALPHA = 0, + SDL_NOFRAME = SDL_WINDOW_BORDERLESS, + SDL_GL_SWAP_CONTROL = 0, + TIMER_RESOLUTION = 0 +} PygameVideoFlags; + +/* the wheel button constants were removed from SDL 2 */ +typedef enum { + PGM_BUTTON_LEFT = SDL_BUTTON_LEFT, + PGM_BUTTON_RIGHT = SDL_BUTTON_RIGHT, + PGM_BUTTON_MIDDLE = SDL_BUTTON_MIDDLE, + PGM_BUTTON_WHEELUP = 4, + PGM_BUTTON_WHEELDOWN = 5, + PGM_BUTTON_X1 = SDL_BUTTON_X1 + 2, + PGM_BUTTON_X2 = SDL_BUTTON_X2 + 2, + PGM_BUTTON_KEEP = 0x80 +} PygameMouseFlags; + +typedef enum { + /* Any SDL_* events here are for backward compatibility. */ + SDL_NOEVENT = 0, + + SDL_ACTIVEEVENT = SDL_USEREVENT, + SDL_VIDEORESIZE, + SDL_VIDEOEXPOSE, + + PGE_MIDIIN, + PGE_MIDIOUT, + PGE_KEYREPEAT, /* Special internal pygame event, for managing key-presses + */ + + /* DO NOT CHANGE THE ORDER OF EVENTS HERE */ + PGE_WINDOWSHOWN, + PGE_WINDOWHIDDEN, + PGE_WINDOWEXPOSED, + PGE_WINDOWMOVED, + PGE_WINDOWRESIZED, + PGE_WINDOWSIZECHANGED, + PGE_WINDOWMINIMIZED, + PGE_WINDOWMAXIMIZED, + PGE_WINDOWRESTORED, + PGE_WINDOWENTER, + PGE_WINDOWLEAVE, + PGE_WINDOWFOCUSGAINED, + PGE_WINDOWFOCUSLOST, + PGE_WINDOWCLOSE, + PGE_WINDOWTAKEFOCUS, + PGE_WINDOWHITTEST, + + /* Here we define PGPOST_* events, events that act as a one-to-one + * proxy for SDL events (and some extra events too!), the proxy is used + * internally when pygame users use event.post() + * + * At a first glance, these may look redundant, but they are really + * important, especially with event blocking. If proxy events are + * not there, blocked events dont make it to our event filter, and + * that can break a lot of stuff. + * + * IMPORTANT NOTE: Do not post events directly with these proxy types, + * use the appropriate functions from event.c, that handle these proxy + * events for you. + * Proxy events are for internal use only */ + PGPOST_EVENTBEGIN, /* mark start of proxy-events */ + PGPOST_ACTIVEEVENT = PGPOST_EVENTBEGIN, + PGPOST_AUDIODEVICEADDED, + PGPOST_AUDIODEVICEREMOVED, + PGPOST_CONTROLLERAXISMOTION, + PGPOST_CONTROLLERBUTTONDOWN, + PGPOST_CONTROLLERBUTTONUP, + PGPOST_CONTROLLERDEVICEADDED, + PGPOST_CONTROLLERDEVICEREMOVED, + PGPOST_CONTROLLERDEVICEREMAPPED, + PGPOST_CONTROLLERTOUCHPADDOWN, + PGPOST_CONTROLLERTOUCHPADMOTION, + PGPOST_CONTROLLERTOUCHPADUP, + PGPOST_DOLLARGESTURE, + PGPOST_DOLLARRECORD, + PGPOST_DROPFILE, + PGPOST_DROPTEXT, + PGPOST_DROPBEGIN, + PGPOST_DROPCOMPLETE, + PGPOST_FINGERMOTION, + PGPOST_FINGERDOWN, + PGPOST_FINGERUP, + PGPOST_KEYDOWN, + PGPOST_KEYUP, + PGPOST_JOYAXISMOTION, + PGPOST_JOYBALLMOTION, + PGPOST_JOYHATMOTION, + PGPOST_JOYBUTTONDOWN, + PGPOST_JOYBUTTONUP, + PGPOST_JOYDEVICEADDED, + PGPOST_JOYDEVICEREMOVED, + PGPOST_MIDIIN, + PGPOST_MIDIOUT, + PGPOST_MOUSEMOTION, + PGPOST_MOUSEBUTTONDOWN, + PGPOST_MOUSEBUTTONUP, + PGPOST_MOUSEWHEEL, + PGPOST_MULTIGESTURE, + PGPOST_NOEVENT, + PGPOST_QUIT, + PGPOST_SYSWMEVENT, + PGPOST_TEXTEDITING, + PGPOST_TEXTINPUT, + PGPOST_VIDEORESIZE, + PGPOST_VIDEOEXPOSE, + PGPOST_WINDOWSHOWN, + PGPOST_WINDOWHIDDEN, + PGPOST_WINDOWEXPOSED, + PGPOST_WINDOWMOVED, + PGPOST_WINDOWRESIZED, + PGPOST_WINDOWSIZECHANGED, + PGPOST_WINDOWMINIMIZED, + PGPOST_WINDOWMAXIMIZED, + PGPOST_WINDOWRESTORED, + PGPOST_WINDOWENTER, + PGPOST_WINDOWLEAVE, + PGPOST_WINDOWFOCUSGAINED, + PGPOST_WINDOWFOCUSLOST, + PGPOST_WINDOWCLOSE, + PGPOST_WINDOWTAKEFOCUS, + PGPOST_WINDOWHITTEST, + + PGE_USEREVENT, /* this event must stay in this position only */ + + PG_NUMEVENTS = + SDL_LASTEVENT /* Not an event. Indicates end of user events. */ +} PygameEventCode; + +typedef enum { + SDL_APPFOCUSMOUSE, + SDL_APPINPUTFOCUS, + SDL_APPACTIVE +} PygameAppCode; + +/* Surface flags: based on SDL 1.2 flags */ +typedef enum { + PGS_SWSURFACE = 0x00000000, + PGS_HWSURFACE = 0x00000001, + PGS_ASYNCBLIT = 0x00000004, + + PGS_ANYFORMAT = 0x10000000, + PGS_HWPALETTE = 0x20000000, + PGS_DOUBLEBUF = 0x40000000, + PGS_FULLSCREEN = 0x80000000, + PGS_SCALED = 0x00000200, + + PGS_OPENGL = 0x00000002, + PGS_OPENGLBLIT = 0x0000000A, + PGS_RESIZABLE = 0x00000010, + PGS_NOFRAME = 0x00000020, + PGS_SHOWN = 0x00000040, /* Added from SDL 2 */ + PGS_HIDDEN = 0x00000080, /* Added from SDL 2 */ + + PGS_HWACCEL = 0x00000100, + PGS_SRCCOLORKEY = 0x00001000, + PGS_RLEACCELOK = 0x00002000, + PGS_RLEACCEL = 0x00004000, + PGS_SRCALPHA = 0x00010000, + PGS_PREALLOC = 0x01000000 +} PygameSurfaceFlags; + +// TODO Implement check below in a way that does not break CI +/* New buffer protocol (PEP 3118) implemented on all supported Py versions. +#if !defined(Py_TPFLAGS_HAVE_NEWBUFFER) +#error No support for PEP 3118/Py_TPFLAGS_HAVE_NEWBUFFER. Please use a +supported Python version. #endif */ + +#define RAISE(x, y) (PyErr_SetString((x), (y)), (PyObject *)NULL) +#define DEL_ATTR_NOT_SUPPORTED_CHECK(name, value) \ + do { \ + if (!value) { \ + if (name) { \ + PyErr_Format(PyExc_AttributeError, \ + "Cannot delete attribute %s", name); \ + } \ + else { \ + PyErr_SetString(PyExc_AttributeError, \ + "Cannot delete attribute"); \ + } \ + return -1; \ + } \ + } while (0) + +/* + * Initialization checks + */ + +#define VIDEO_INIT_CHECK() \ + if (!SDL_WasInit(SDL_INIT_VIDEO)) \ + return RAISE(pgExc_SDLError, "video system not initialized") + +#define CDROM_INIT_CHECK() \ + if (!SDL_WasInit(SDL_INIT_CDROM)) \ + return RAISE(pgExc_SDLError, "cdrom system not initialized") + +#define JOYSTICK_INIT_CHECK() \ + if (!SDL_WasInit(SDL_INIT_JOYSTICK)) \ + return RAISE(pgExc_SDLError, "joystick system not initialized") + +/* thread check */ +#ifdef WITH_THREAD +#define PG_CHECK_THREADS() (1) +#else /* ~WITH_THREAD */ +#define PG_CHECK_THREADS() \ + (RAISE(PyExc_NotImplementedError, "Python built without thread support")) +#endif /* ~WITH_THREAD */ + +#define PyType_Init(x) (((x).ob_type) = &PyType_Type) + +/* + * event module internals + */ +struct pgEventObject { + PyObject_HEAD int type; + PyObject *dict; +}; + +/* + * surflock module internals + */ +typedef struct { + PyObject_HEAD PyObject *surface; + PyObject *lockobj; + PyObject *weakrefs; +} pgLifetimeLockObject; + +/* + * surface module internals + */ +struct pgSubSurface_Data { + PyObject *owner; + int pixeloffset; + int offsetx, offsety; +}; + +/* + * color module internals + */ +struct pgColorObject { + PyObject_HEAD Uint8 data[4]; + Uint8 len; +}; + +/* + * include public API + */ +#include "include/_pygame.h" + +/* Slot counts. + * Remember to keep these constants up to date. + */ + +#define PYGAMEAPI_RECT_NUMSLOTS 5 +#define PYGAMEAPI_JOYSTICK_NUMSLOTS 2 +#define PYGAMEAPI_DISPLAY_NUMSLOTS 2 +#define PYGAMEAPI_SURFACE_NUMSLOTS 4 +#define PYGAMEAPI_SURFLOCK_NUMSLOTS 8 +#define PYGAMEAPI_RWOBJECT_NUMSLOTS 7 +#define PYGAMEAPI_PIXELARRAY_NUMSLOTS 2 +#define PYGAMEAPI_COLOR_NUMSLOTS 5 +#define PYGAMEAPI_MATH_NUMSLOTS 2 +#define PYGAMEAPI_CDROM_NUMSLOTS 2 +#define PYGAMEAPI_BASE_NUMSLOTS 24 +#define PYGAMEAPI_EVENT_NUMSLOTS 6 + +#endif /* _PYGAME_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/_surface.h b/.venv/include/site/python3.8/pygame/_surface.h new file mode 100644 index 0000000..b2b4644 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/_surface.h @@ -0,0 +1,30 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + Copyright (C) 2007 Marcus von Appen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +#ifndef _SURFACE_H +#define _SURFACE_H + +#include "_pygame.h" +#include "surface.h" + +#endif diff --git a/.venv/include/site/python3.8/pygame/camera.h b/.venv/include/site/python3.8/pygame/camera.h new file mode 100644 index 0000000..6806dfe --- /dev/null +++ b/.venv/include/site/python3.8/pygame/camera.h @@ -0,0 +1,252 @@ +#ifndef CAMERA_H +#define CAMERA_H +/* + pygame - Python Game Library + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#include "pygame.h" +#include "pgcompat.h" +#include "doc/camera_doc.h" + +#if defined(__unix__) +#include +#include +#include +#include +#include + +#include /* low-level i/o */ +#include +#include +#include +#include +#include +#include +#include + +/* on freebsd there is no asm/types */ +#ifdef linux +#include /* for videodev2.h */ +#endif + +#include +#endif + +#if defined(__WIN32__) +#define PYGAME_WINDOWS_CAMERA 1 + +#include +#include +#include +#include +#include +#include +#endif + +/* some constants used which are not defined on non-v4l machines. */ +#ifndef V4L2_PIX_FMT_RGB24 +#define V4L2_PIX_FMT_RGB24 'RGB3' +#endif +#ifndef V4L2_PIX_FMT_RGB444 +#define V4L2_PIX_FMT_RGB444 'R444' +#endif +#ifndef V4L2_PIX_FMT_YUYV +#define V4L2_PIX_FMT_YUYV 'YUYV' +#endif +#ifndef V4L2_PIX_FMT_XBGR32 +#define V4L2_PIX_FMT_XBGR32 'XR24' +#endif + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) +#define SAT(c) \ + if (c & (~255)) { \ + if (c < 0) \ + c = 0; \ + else \ + c = 255; \ + } +#define SAT2(c) ((c) & (~255) ? ((c) < 0 ? 0 : 255) : (c)) +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define RGB_OUT 1 +#define YUV_OUT 2 +#define HSV_OUT 4 +#define CAM_V4L \ + 1 /* deprecated. the incomplete support in pygame was removed */ +#define CAM_V4L2 2 + +struct buffer { + void *start; + size_t length; +}; + +#if defined(__unix__) +typedef struct pgCameraObject { + PyObject_HEAD char *device_name; + int camera_type; + unsigned long pixelformat; + unsigned int color_out; + struct buffer *buffers; + unsigned int n_buffers; + int width; + int height; + int size; + int hflip; + int vflip; + int brightness; + int fd; +} pgCameraObject; +#elif defined(PYGAME_WINDOWS_CAMERA) +typedef struct pgCameraObject { + PyObject_HEAD WCHAR *device_name; + IMFSourceReader *reader; + IMFTransform *transform; + IMFVideoProcessorControl *control; + IMFMediaBuffer *buf; + IMFMediaBuffer *raw_buf; + int buffer_ready; + short open; /* used to signal the update_function to exit */ + HANDLE t_handle; + HRESULT t_error; + int t_error_line; + int width; + int height; + int hflip; + int vflip; + int last_vflip; + int color_out; + unsigned long pixelformat; +} pgCameraObject; + +#else +/* generic definition. + */ + +typedef struct pgCameraObject { + PyObject_HEAD char *device_name; + int camera_type; + unsigned long pixelformat; + unsigned int color_out; + struct buffer *buffers; + unsigned int n_buffers; + int width; + int height; + int size; + int hflip; + int vflip; + int brightness; + int fd; +} pgCameraObject; +#endif + +/* internal functions for colorspace conversion */ +void +colorspace(SDL_Surface *src, SDL_Surface *dst, int cspace); +void +rgb24_to_rgb(const void *src, void *dst, int length, SDL_PixelFormat *format); +void +bgr32_to_rgb(const void *src, void *dst, int length, SDL_PixelFormat *format); +void +rgb444_to_rgb(const void *src, void *dst, int length, SDL_PixelFormat *format); +void +rgb_to_yuv(const void *src, void *dst, int length, unsigned long source, + SDL_PixelFormat *format); +void +rgb_to_hsv(const void *src, void *dst, int length, unsigned long source, + SDL_PixelFormat *format); +void +yuyv_to_rgb(const void *src, void *dst, int length, SDL_PixelFormat *format); +void +yuyv_to_yuv(const void *src, void *dst, int length, SDL_PixelFormat *format); +void +uyvy_to_rgb(const void *src, void *dst, int length, SDL_PixelFormat *format); +void +uyvy_to_yuv(const void *src, void *dst, int length, SDL_PixelFormat *format); +void +sbggr8_to_rgb(const void *src, void *dst, int width, int height, + SDL_PixelFormat *format); +void +yuv420_to_rgb(const void *src, void *dst, int width, int height, + SDL_PixelFormat *format); +void +yuv420_to_yuv(const void *src, void *dst, int width, int height, + SDL_PixelFormat *format); + +#if defined(__unix__) +/* internal functions specific to v4l2 */ +char ** +v4l2_list_cameras(int *num_devices); +int +v4l2_get_control(int fd, int id, int *value); +int +v4l2_set_control(int fd, int id, int value); +PyObject * +v4l2_read_raw(pgCameraObject *self); +int +v4l2_xioctl(int fd, int request, void *arg); +int +v4l2_process_image(pgCameraObject *self, const void *image, + unsigned int buffer_size, SDL_Surface *surf); +int +v4l2_query_buffer(pgCameraObject *self); +int +v4l2_read_frame(pgCameraObject *self, SDL_Surface *surf); +int +v4l2_stop_capturing(pgCameraObject *self); +int +v4l2_start_capturing(pgCameraObject *self); +int +v4l2_uninit_device(pgCameraObject *self); +int +v4l2_init_mmap(pgCameraObject *self); +int +v4l2_init_device(pgCameraObject *self); +int +v4l2_close_device(pgCameraObject *self); +int +v4l2_open_device(pgCameraObject *self); + +#elif defined(PYGAME_WINDOWS_CAMERA) +/* internal functions specific to WINDOWS */ +WCHAR ** +windows_list_cameras(int *num_devices); +int +windows_init_device(pgCameraObject *self); +int +windows_open_device(pgCameraObject *self); +IMFActivate * +windows_device_from_name(WCHAR *device_name); +int +windows_close_device(pgCameraObject *self); +int +windows_read_frame(pgCameraObject *self, SDL_Surface *surf); +int +windows_frame_ready(pgCameraObject *self, int *result); +PyObject * +windows_read_raw(pgCameraObject *self); +int +windows_process_image(pgCameraObject *self, BYTE *data, DWORD buffer_size, + SDL_Surface *surf); +void +windows_dealloc_device(pgCameraObject *self); +int +windows_init_device(pgCameraObject *self); + +#endif + +#endif /* !CAMERA_H */ diff --git a/.venv/include/site/python3.8/pygame/font.h b/.venv/include/site/python3.8/pygame/font.h new file mode 100644 index 0000000..f5eedb2 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/font.h @@ -0,0 +1,15 @@ +#ifndef PGFONT_INTERNAL_H +#define PGFONT_INTERNAL_H + +#include + +/* test font initialization */ +#define FONT_INIT_CHECK() \ + if (!(*(int *)PyFONT_C_API[2])) \ + return RAISE(pgExc_SDLError, "font system not initialized") + +#include "include/pygame_font.h" + +#define PYGAMEAPI_FONT_NUMSLOTS 3 + +#endif /* ~PGFONT_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/freetype.h b/.venv/include/site/python3.8/pygame/freetype.h new file mode 100644 index 0000000..fd86bc2 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/freetype.h @@ -0,0 +1,114 @@ +/* + pygame - Python Game Library + Copyright (C) 2009 Vicent Marti + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +#ifndef _PYGAME_FREETYPE_INTERNAL_H_ +#define _PYGAME_FREETYPE_INTERNAL_H_ + +#include "pgcompat.h" +#include "pgplatform.h" + +#include +#include FT_FREETYPE_H +#include FT_CACHE_H +#include FT_XFREE86_H +#include FT_TRIGONOMETRY_H + +/********************************************************** + * Global module constants + **********************************************************/ + +/* Render styles */ +#define FT_STYLE_NORMAL 0x00 +#define FT_STYLE_STRONG 0x01 +#define FT_STYLE_OBLIQUE 0x02 +#define FT_STYLE_UNDERLINE 0x04 +#define FT_STYLE_WIDE 0x08 +#define FT_STYLE_DEFAULT 0xFF + +/* Bounding box modes */ +#define FT_BBOX_EXACT FT_GLYPH_BBOX_SUBPIXELS +#define FT_BBOX_EXACT_GRIDFIT FT_GLYPH_BBOX_GRIDFIT +#define FT_BBOX_PIXEL FT_GLYPH_BBOX_TRUNCATE +#define FT_BBOX_PIXEL_GRIDFIT FT_GLYPH_BBOX_PIXELS + +/* Rendering flags */ +#define FT_RFLAG_NONE (0) +#define FT_RFLAG_ANTIALIAS (1 << 0) +#define FT_RFLAG_AUTOHINT (1 << 1) +#define FT_RFLAG_VERTICAL (1 << 2) +#define FT_RFLAG_HINTED (1 << 3) +#define FT_RFLAG_KERNING (1 << 4) +#define FT_RFLAG_TRANSFORM (1 << 5) +#define FT_RFLAG_PAD (1 << 6) +#define FT_RFLAG_ORIGIN (1 << 7) +#define FT_RFLAG_UCS4 (1 << 8) +#define FT_RFLAG_USE_BITMAP_STRIKES (1 << 9) +#define FT_RFLAG_DEFAULTS \ + (FT_RFLAG_HINTED | FT_RFLAG_USE_BITMAP_STRIKES | FT_RFLAG_ANTIALIAS) + +#define FT_RENDER_NEWBYTEARRAY 0x0 +#define FT_RENDER_NEWSURFACE 0x1 +#define FT_RENDER_EXISTINGSURFACE 0x2 + +/********************************************************** + * Global module types + **********************************************************/ + +typedef struct _scale_s { + FT_UInt x, y; +} Scale_t; +typedef FT_Angle Angle_t; + +struct fontinternals_; +struct freetypeinstance_; + +typedef struct { + FT_Long font_index; + FT_Open_Args open_args; +} pgFontId; + +typedef struct { + PyObject_HEAD pgFontId id; + PyObject *path; + int is_scalable; + int is_bg_col_set; + + Scale_t face_size; + FT_Int16 style; + FT_Int16 render_flags; + double strength; + double underline_adjustment; + FT_UInt resolution; + Angle_t rotation; + FT_Matrix transform; + FT_Byte fgcolor[4]; + FT_Byte bgcolor[4]; + + struct freetypeinstance_ *freetype; /* Personal reference */ + struct fontinternals_ *_internals; +} pgFontObject; + +#define pgFont_IS_ALIVE(o) (((pgFontObject *)(o))->_internals != 0) + +/* import public API */ +#include "include/pygame_freetype.h" + +#define PYGAMEAPI_FREETYPE_NUMSLOTS 2 + +#endif /* ~_PYGAME_FREETYPE_INTERNAL_H_ */ diff --git a/.venv/include/site/python3.8/pygame/include/_pygame.h b/.venv/include/site/python3.8/pygame/include/_pygame.h new file mode 100644 index 0000000..9817f96 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/_pygame.h @@ -0,0 +1,497 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +#ifndef _PYGAME_H +#define _PYGAME_H + +/** This header file includes all the definitions for the + ** base pygame extensions. This header only requires + ** Python includes (and SDL.h for functions that use SDL types). + ** The reason for functions prototyped with #define's is + ** to allow for maximum Python portability. It also uses + ** Python as the runtime linker, which allows for late binding. + '' For more information on this style of development, read + ** the Python docs on this subject. + ** http://www.python.org/doc/current/ext/using-cobjects.html + ** + ** If using this to build your own derived extensions, + ** you'll see that the functions available here are mainly + ** used to help convert between python objects and SDL objects. + ** Since this library doesn't add a lot of functionality to + ** the SDL library, it doesn't need to offer a lot either. + ** + ** When initializing your extension module, you must manually + ** import the modules you want to use. (this is the part about + ** using python as the runtime linker). Each module has its + ** own import_xxx() routine. You need to perform this import + ** after you have initialized your own module, and before + ** you call any routines from that module. Since every module + ** in pygame does this, there are plenty of examples. + ** + ** The base module does include some useful conversion routines + ** that you are free to use in your own extension. + **/ + +#include "pgplatform.h" +#include + +/* version macros (defined since version 1.9.5) */ +#define PG_MAJOR_VERSION 2 +#define PG_MINOR_VERSION 1 +#define PG_PATCH_VERSION 2 +#define PG_VERSIONNUM(MAJOR, MINOR, PATCH) \ + (1000 * (MAJOR) + 100 * (MINOR) + (PATCH)) +#define PG_VERSION_ATLEAST(MAJOR, MINOR, PATCH) \ + (PG_VERSIONNUM(PG_MAJOR_VERSION, PG_MINOR_VERSION, PG_PATCH_VERSION) >= \ + PG_VERSIONNUM(MAJOR, MINOR, PATCH)) + +#include "pgcompat.h" + +/* Flag indicating a pg_buffer; used for assertions within callbacks */ +#ifndef NDEBUG +#define PyBUF_PYGAME 0x4000 +#endif +#define PyBUF_HAS_FLAG(f, F) (((f) & (F)) == (F)) + +/* Array information exchange struct C type; inherits from Py_buffer + * + * Pygame uses its own Py_buffer derived C struct as an internal representation + * of an imported array buffer. The extended Py_buffer allows for a + * per-instance release callback, + */ +typedef void (*pybuffer_releaseproc)(Py_buffer *); + +typedef struct pg_bufferinfo_s { + Py_buffer view; + PyObject *consumer; /* Input: Borrowed reference */ + pybuffer_releaseproc release_buffer; +} pg_buffer; + +#include "pgimport.h" + +/* + * BASE module + */ +#ifndef PYGAMEAPI_BASE_INTERNAL +#define pgExc_SDLError ((PyObject *)PYGAMEAPI_GET_SLOT(base, 0)) + +#define pg_RegisterQuit \ + (*(void (*)(void (*)(void)))PYGAMEAPI_GET_SLOT(base, 1)) + +#define pg_IntFromObj \ + (*(int (*)(PyObject *, int *))PYGAMEAPI_GET_SLOT(base, 2)) + +#define pg_IntFromObjIndex \ + (*(int (*)(PyObject *, int, int *))PYGAMEAPI_GET_SLOT(base, 3)) + +#define pg_TwoIntsFromObj \ + (*(int (*)(PyObject *, int *, int *))PYGAMEAPI_GET_SLOT(base, 4)) + +#define pg_FloatFromObj \ + (*(int (*)(PyObject *, float *))PYGAMEAPI_GET_SLOT(base, 5)) + +#define pg_FloatFromObjIndex \ + (*(int (*)(PyObject *, int, float *))PYGAMEAPI_GET_SLOT(base, 6)) + +#define pg_TwoFloatsFromObj \ + (*(int (*)(PyObject *, float *, float *))PYGAMEAPI_GET_SLOT(base, 7)) + +#define pg_UintFromObj \ + (*(int (*)(PyObject *, Uint32 *))PYGAMEAPI_GET_SLOT(base, 8)) + +#define pg_UintFromObjIndex \ + (*(int (*)(PyObject *, int, Uint32 *))PYGAMEAPI_GET_SLOT(base, 9)) + +#define pg_mod_autoinit (*(int (*)(const char *))PYGAMEAPI_GET_SLOT(base, 10)) + +#define pg_mod_autoquit (*(void (*)(const char *))PYGAMEAPI_GET_SLOT(base, 11)) + +#define pg_RGBAFromObj \ + (*(int (*)(PyObject *, Uint8 *))PYGAMEAPI_GET_SLOT(base, 12)) + +#define pgBuffer_AsArrayInterface \ + (*(PyObject * (*)(Py_buffer *)) PYGAMEAPI_GET_SLOT(base, 13)) + +#define pgBuffer_AsArrayStruct \ + (*(PyObject * (*)(Py_buffer *)) PYGAMEAPI_GET_SLOT(base, 14)) + +#define pgObject_GetBuffer \ + (*(int (*)(PyObject *, pg_buffer *, int))PYGAMEAPI_GET_SLOT(base, 15)) + +#define pgBuffer_Release (*(void (*)(pg_buffer *))PYGAMEAPI_GET_SLOT(base, 16)) + +#define pgDict_AsBuffer \ + (*(int (*)(pg_buffer *, PyObject *, int))PYGAMEAPI_GET_SLOT(base, 17)) + +#define pgExc_BufferError ((PyObject *)PYGAMEAPI_GET_SLOT(base, 18)) + +#define pg_GetDefaultWindow \ + (*(SDL_Window * (*)(void)) PYGAMEAPI_GET_SLOT(base, 19)) + +#define pg_SetDefaultWindow \ + (*(void (*)(SDL_Window *))PYGAMEAPI_GET_SLOT(base, 20)) + +#define pg_GetDefaultWindowSurface \ + (*(pgSurfaceObject * (*)(void)) PYGAMEAPI_GET_SLOT(base, 21)) + +#define pg_SetDefaultWindowSurface \ + (*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(base, 22)) + +#define pg_EnvShouldBlendAlphaSDL2 \ + (*(char *(*)(void))PYGAMEAPI_GET_SLOT(base, 23)) + +#define import_pygame_base() IMPORT_PYGAME_MODULE(base) +#endif /* ~PYGAMEAPI_BASE_INTERNAL */ + +typedef struct { + PyObject_HEAD SDL_Rect r; + PyObject *weakreflist; +} pgRectObject; + +#define pgRect_AsRect(x) (((pgRectObject *)x)->r) +#ifndef PYGAMEAPI_RECT_INTERNAL +#define pgRect_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(rect, 0)) + +#define pgRect_Check(x) ((x)->ob_type == &pgRect_Type) +#define pgRect_New (*(PyObject * (*)(SDL_Rect *)) PYGAMEAPI_GET_SLOT(rect, 1)) + +#define pgRect_New4 \ + (*(PyObject * (*)(int, int, int, int)) PYGAMEAPI_GET_SLOT(rect, 2)) + +#define pgRect_FromObject \ + (*(SDL_Rect * (*)(PyObject *, SDL_Rect *)) PYGAMEAPI_GET_SLOT(rect, 3)) + +#define pgRect_Normalize (*(void (*)(SDL_Rect *))PYGAMEAPI_GET_SLOT(rect, 4)) + +#define import_pygame_rect() IMPORT_PYGAME_MODULE(rect) +#endif /* ~PYGAMEAPI_RECT_INTERNAL */ + +/* + * CDROM module + */ + +typedef struct { + PyObject_HEAD int id; +} pgCDObject; + +#define pgCD_AsID(x) (((pgCDObject *)x)->id) +#ifndef PYGAMEAPI_CDROM_INTERNAL +#define pgCD_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(cdrom, 0)) + +#define pgCD_Check(x) ((x)->ob_type == &pgCD_Type) +#define pgCD_New (*(PyObject * (*)(int)) PYGAMEAPI_GET_SLOT(cdrom, 1)) + +#define import_pygame_cd() IMPORT_PYGAME_MODULE(cdrom) +#endif + +/* + * JOYSTICK module + */ +typedef struct pgJoystickObject { + PyObject_HEAD int id; + SDL_Joystick *joy; + + /* Joysticks form an intrusive linked list. + * + * Note that we don't maintain refcounts for these so they are weakrefs + * from the Python side. + */ + struct pgJoystickObject *next; + struct pgJoystickObject *prev; +} pgJoystickObject; + +#define pgJoystick_AsID(x) (((pgJoystickObject *)x)->id) +#define pgJoystick_AsSDL(x) (((pgJoystickObject *)x)->joy) + +#ifndef PYGAMEAPI_JOYSTICK_INTERNAL +#define pgJoystick_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(joystick, 0)) + +#define pgJoystick_Check(x) ((x)->ob_type == &pgJoystick_Type) +#define pgJoystick_New (*(PyObject * (*)(int)) PYGAMEAPI_GET_SLOT(joystick, 1)) + +#define import_pygame_joystick() IMPORT_PYGAME_MODULE(joystick) +#endif + +/* + * DISPLAY module + */ + +typedef struct { + Uint32 hw_available : 1; + Uint32 wm_available : 1; + Uint32 blit_hw : 1; + Uint32 blit_hw_CC : 1; + Uint32 blit_hw_A : 1; + Uint32 blit_sw : 1; + Uint32 blit_sw_CC : 1; + Uint32 blit_sw_A : 1; + Uint32 blit_fill : 1; + Uint32 video_mem; + SDL_PixelFormat *vfmt; + SDL_PixelFormat vfmt_data; + int current_w; + int current_h; +} pg_VideoInfo; + +typedef struct { + PyObject_HEAD pg_VideoInfo info; +} pgVidInfoObject; + +#define pgVidInfo_AsVidInfo(x) (((pgVidInfoObject *)x)->info) + +#ifndef PYGAMEAPI_DISPLAY_INTERNAL +#define pgVidInfo_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(display, 0)) + +#define pgVidInfo_Check(x) ((x)->ob_type == &pgVidInfo_Type) +#define pgVidInfo_New \ + (*(PyObject * (*)(pg_VideoInfo *)) PYGAMEAPI_GET_SLOT(display, 1)) + +#define import_pygame_display() IMPORT_PYGAME_MODULE(display) +#endif /* ~PYGAMEAPI_DISPLAY_INTERNAL */ + +/* + * SURFACE module + */ +struct pgSubSurface_Data; +struct SDL_Surface; + +typedef struct { + PyObject_HEAD struct SDL_Surface *surf; + int owner; + struct pgSubSurface_Data *subsurface; /* ptr to subsurface data (if a + * subsurface)*/ + PyObject *weakreflist; + PyObject *locklist; + PyObject *dependency; +} pgSurfaceObject; +#define pgSurface_AsSurface(x) (((pgSurfaceObject *)x)->surf) + +#ifndef PYGAMEAPI_SURFACE_INTERNAL +#define pgSurface_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(surface, 0)) + +#define pgSurface_Check(x) \ + (PyObject_IsInstance((x), (PyObject *)&pgSurface_Type)) +#define pgSurface_New2 \ + (*(pgSurfaceObject * (*)(SDL_Surface *, int)) \ + PYGAMEAPI_GET_SLOT(surface, 1)) + +#define pgSurface_SetSurface \ + (*(int (*)(pgSurfaceObject *, SDL_Surface *, int))PYGAMEAPI_GET_SLOT( \ + surface, 3)) + +#define pgSurface_Blit \ + (*(int (*)(pgSurfaceObject *, pgSurfaceObject *, SDL_Rect *, SDL_Rect *, \ + int))PYGAMEAPI_GET_SLOT(surface, 2)) + +#define import_pygame_surface() \ + do { \ + IMPORT_PYGAME_MODULE(surface); \ + if (PyErr_Occurred() != NULL) \ + break; \ + IMPORT_PYGAME_MODULE(surflock); \ + } while (0) + +#define pgSurface_New(surface) pgSurface_New2((surface), 1) +#define pgSurface_NewNoOwn(surface) pgSurface_New2((surface), 0) + +#endif /* ~PYGAMEAPI_SURFACE_INTERNAL */ + +/* + * SURFLOCK module + * auto imported/initialized by surface + */ +#ifndef PYGAMEAPI_SURFLOCK_INTERNAL +#define pgLifetimeLock_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(surflock, 0)) + +#define pgLifetimeLock_Check(x) ((x)->ob_type == &pgLifetimeLock_Type) + +#define pgSurface_Prep(x) \ + if ((x)->subsurface) \ + (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 1)))(x) + +#define pgSurface_Unprep(x) \ + if ((x)->subsurface) \ + (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 2)))(x) + +#define pgSurface_Lock \ + (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 3)) + +#define pgSurface_Unlock \ + (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 4)) + +#define pgSurface_LockBy \ + (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 5)) + +#define pgSurface_UnlockBy \ + (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 6)) + +#define pgSurface_LockLifetime \ + (*(PyObject * (*)(PyObject *, PyObject *)) PYGAMEAPI_GET_SLOT(surflock, 7)) +#endif + +/* + * EVENT module + */ +typedef struct pgEventObject pgEventObject; + +#ifndef PYGAMEAPI_EVENT_INTERNAL +#define pgEvent_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(event, 0)) + +#define pgEvent_Check(x) ((x)->ob_type == &pgEvent_Type) + +#define pgEvent_New \ + (*(PyObject * (*)(SDL_Event *)) PYGAMEAPI_GET_SLOT(event, 1)) + +#define pgEvent_New2 \ + (*(PyObject * (*)(int, PyObject *)) PYGAMEAPI_GET_SLOT(event, 2)) + +#define pgEvent_FillUserEvent \ + (*(int (*)(pgEventObject *, SDL_Event *))PYGAMEAPI_GET_SLOT(event, 3)) + +#define pg_EnableKeyRepeat (*(int (*)(int, int))PYGAMEAPI_GET_SLOT(event, 4)) + +#define pg_GetKeyRepeat (*(void (*)(int *, int *))PYGAMEAPI_GET_SLOT(event, 5)) + +#define import_pygame_event() IMPORT_PYGAME_MODULE(event) +#endif + +/* + * RWOBJECT module + * the rwobject are only needed for C side work, not accessable from python. + */ +#ifndef PYGAMEAPI_RWOBJECT_INTERNAL +#define pgRWops_FromObject \ + (*(SDL_RWops * (*)(PyObject *)) PYGAMEAPI_GET_SLOT(rwobject, 0)) + +#define pgRWops_IsFileObject \ + (*(int (*)(SDL_RWops *))PYGAMEAPI_GET_SLOT(rwobject, 1)) + +#define pg_EncodeFilePath \ + (*(PyObject * (*)(PyObject *, PyObject *)) PYGAMEAPI_GET_SLOT(rwobject, 2)) + +#define pg_EncodeString \ + (*(PyObject * (*)(PyObject *, const char *, const char *, PyObject *)) \ + PYGAMEAPI_GET_SLOT(rwobject, 3)) + +#define pgRWops_FromFileObject \ + (*(SDL_RWops * (*)(PyObject *)) PYGAMEAPI_GET_SLOT(rwobject, 4)) + +#define pgRWops_ReleaseObject \ + (*(int (*)(SDL_RWops *))PYGAMEAPI_GET_SLOT(rwobject, 5)) + +#define pgRWops_GetFileExtension \ + (*(char *(*)(SDL_RWops *))PYGAMEAPI_GET_SLOT(rwobject, 6)) + +#define import_pygame_rwobject() IMPORT_PYGAME_MODULE(rwobject) + +#endif + +/* + * PixelArray module + */ +#ifndef PYGAMEAPI_PIXELARRAY_INTERNAL +#define PyPixelArray_Type ((PyTypeObject *)PYGAMEAPI_GET_SLOT(pixelarray, 0)) + +#define PyPixelArray_Check(x) ((x)->ob_type == &PyPixelArray_Type) +#define PyPixelArray_New (*(PyObject * (*)) PYGAMEAPI_GET_SLOT(pixelarray, 1)) + +#define import_pygame_pixelarray() IMPORT_PYGAME_MODULE(pixelarray) +#endif /* PYGAMEAPI_PIXELARRAY_INTERNAL */ + +/* + * Color module + */ +typedef struct pgColorObject pgColorObject; + +#ifndef PYGAMEAPI_COLOR_INTERNAL +#define pgColor_Type (*(PyObject *)PYGAMEAPI_GET_SLOT(color, 0)) + +#define pgColor_Check(x) ((x)->ob_type == &pgColor_Type) +#define pgColor_New (*(PyObject * (*)(Uint8 *)) PYGAMEAPI_GET_SLOT(color, 1)) + +#define pgColor_NewLength \ + (*(PyObject * (*)(Uint8 *, Uint8)) PYGAMEAPI_GET_SLOT(color, 3)) + +#define pg_RGBAFromColorObj \ + (*(int (*)(PyObject *, Uint8 *))PYGAMEAPI_GET_SLOT(color, 2)) + +#define pg_RGBAFromFuzzyColorObj \ + (*(int (*)(PyObject *, Uint8 *))PYGAMEAPI_GET_SLOT(color, 4)) + +#define pgColor_AsArray(x) (((pgColorObject *)x)->data) +#define pgColor_NumComponents(x) (((pgColorObject *)x)->len) + +#define import_pygame_color() IMPORT_PYGAME_MODULE(color) +#endif /* PYGAMEAPI_COLOR_INTERNAL */ + +/* + * Math module + */ +#ifndef PYGAMEAPI_MATH_INTERNAL +#define pgVector2_Check(x) \ + ((x)->ob_type == (PyTypeObject *)PYGAMEAPI_GET_SLOT(math, 0)) + +#define pgVector3_Check(x) \ + ((x)->ob_type == (PyTypeObject *)PYGAMEAPI_GET_SLOT(math, 1)) +/* +#define pgVector2_New \ + (*(PyObject*(*)) \ + PYGAMEAPI_GET_SLOT(PyGAME_C_API, 1)) +*/ +#define import_pygame_math() IMPORT_PYGAME_MODULE(math) +#endif /* PYGAMEAPI_MATH_INTERNAL */ + +#define IMPORT_PYGAME_MODULE _IMPORT_PYGAME_MODULE + +/* + * base pygame API slots + * disable slots with NO_PYGAME_C_API + */ +#ifdef PYGAME_H +PYGAMEAPI_DEFINE_SLOTS(base); +PYGAMEAPI_DEFINE_SLOTS(rect); +PYGAMEAPI_DEFINE_SLOTS(cdrom); +PYGAMEAPI_DEFINE_SLOTS(joystick); +PYGAMEAPI_DEFINE_SLOTS(display); +PYGAMEAPI_DEFINE_SLOTS(surface); +PYGAMEAPI_DEFINE_SLOTS(surflock); +PYGAMEAPI_DEFINE_SLOTS(event); +PYGAMEAPI_DEFINE_SLOTS(rwobject); +PYGAMEAPI_DEFINE_SLOTS(pixelarray); +PYGAMEAPI_DEFINE_SLOTS(color); +PYGAMEAPI_DEFINE_SLOTS(math); +#else /* ~PYGAME_H */ +PYGAMEAPI_EXTERN_SLOTS(base); +PYGAMEAPI_EXTERN_SLOTS(rect); +PYGAMEAPI_EXTERN_SLOTS(cdrom); +PYGAMEAPI_EXTERN_SLOTS(joystick); +PYGAMEAPI_EXTERN_SLOTS(display); +PYGAMEAPI_EXTERN_SLOTS(surface); +PYGAMEAPI_EXTERN_SLOTS(surflock); +PYGAMEAPI_EXTERN_SLOTS(event); +PYGAMEAPI_EXTERN_SLOTS(rwobject); +PYGAMEAPI_EXTERN_SLOTS(pixelarray); +PYGAMEAPI_EXTERN_SLOTS(color); +PYGAMEAPI_EXTERN_SLOTS(math); +#endif /* ~PYGAME_H */ + +#endif /* PYGAME_H */ diff --git a/.venv/include/site/python3.8/pygame/include/bitmask.h b/.venv/include/site/python3.8/pygame/include/bitmask.h new file mode 100644 index 0000000..eee09b7 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/bitmask.h @@ -0,0 +1,171 @@ +/* + Bitmask 1.7 - A pixel-perfect collision detection library. + + Copyright (C) 2002-2005 Ulf Ekstrom except for the bitcount + function which is copyright (C) Donald W. Gillies, 1992. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef BITMASK_H +#define BITMASK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +/* Define INLINE for different compilers. If your compiler does not + support inlining then there might be a performance hit in + bitmask_overlap_area(). +*/ +#ifndef INLINE +#ifdef __GNUC__ +#define INLINE inline +#else +#ifdef _MSC_VER +#define INLINE __inline +#else +#define INLINE +#endif +#endif +#endif + +#define BITMASK_W unsigned long int +#define BITMASK_W_LEN (sizeof(BITMASK_W) * CHAR_BIT) +#define BITMASK_W_MASK (BITMASK_W_LEN - 1) +#define BITMASK_N(n) ((BITMASK_W)1 << (n)) + +typedef struct bitmask { + int w, h; + BITMASK_W bits[1]; +} bitmask_t; + +/* Creates a bitmask of width w and height h, where + w and h must both be greater than or equal to 0. + The mask is automatically cleared when created. + */ +bitmask_t * +bitmask_create(int w, int h); + +/* Frees all the memory allocated by bitmask_create for m. */ +void +bitmask_free(bitmask_t *m); + +/* Create a copy of the given bitmask. */ +bitmask_t * +bitmask_copy(bitmask_t *m); + +/* Clears all bits in the mask */ +void +bitmask_clear(bitmask_t *m); + +/* Sets all bits in the mask */ +void +bitmask_fill(bitmask_t *m); + +/* Flips all bits in the mask */ +void +bitmask_invert(bitmask_t *m); + +/* Counts the bits in the mask */ +unsigned int +bitmask_count(bitmask_t *m); + +/* Returns nonzero if the bit at (x,y) is set. Coordinates start at + (0,0) */ +static INLINE int +bitmask_getbit(const bitmask_t *m, int x, int y) +{ + return (m->bits[x / BITMASK_W_LEN * m->h + y] & + BITMASK_N(x & BITMASK_W_MASK)) != 0; +} + +/* Sets the bit at (x,y) */ +static INLINE void +bitmask_setbit(bitmask_t *m, int x, int y) +{ + m->bits[x / BITMASK_W_LEN * m->h + y] |= BITMASK_N(x & BITMASK_W_MASK); +} + +/* Clears the bit at (x,y) */ +static INLINE void +bitmask_clearbit(bitmask_t *m, int x, int y) +{ + m->bits[x / BITMASK_W_LEN * m->h + y] &= ~BITMASK_N(x & BITMASK_W_MASK); +} + +/* Returns nonzero if the masks overlap with the given offset. + The overlap tests uses the following offsets (which may be negative): + + +----+----------.. + |A | yoffset + | +-+----------.. + +--|B + |xoffset + | | + : : +*/ +int +bitmask_overlap(const bitmask_t *a, const bitmask_t *b, int xoffset, + int yoffset); + +/* Like bitmask_overlap(), but will also give a point of intersection. + x and y are given in the coordinates of mask a, and are untouched + if there is no overlap. */ +int +bitmask_overlap_pos(const bitmask_t *a, const bitmask_t *b, int xoffset, + int yoffset, int *x, int *y); + +/* Returns the number of overlapping 'pixels' */ +int +bitmask_overlap_area(const bitmask_t *a, const bitmask_t *b, int xoffset, + int yoffset); + +/* Fills a mask with the overlap of two other masks. A bitwise AND. */ +void +bitmask_overlap_mask(const bitmask_t *a, const bitmask_t *b, bitmask_t *c, + int xoffset, int yoffset); + +/* Draws mask b onto mask a (bitwise OR). Can be used to compose large + (game background?) mask from several submasks, which may speed up + the testing. */ + +void +bitmask_draw(bitmask_t *a, const bitmask_t *b, int xoffset, int yoffset); + +void +bitmask_erase(bitmask_t *a, const bitmask_t *b, int xoffset, int yoffset); + +/* Return a new scaled bitmask, with dimensions w*h. The quality of the + scaling may not be perfect for all circumstances, but it should + be reasonable. If either w or h is 0 a clear 1x1 mask is returned. */ +bitmask_t * +bitmask_scale(const bitmask_t *m, int w, int h); + +/* Convolve b into a, drawing the output into o, shifted by offset. If offset + * is 0, then the (x,y) bit will be set if and only if + * bitmask_overlap(a, b, x - b->w - 1, y - b->h - 1) returns true. + * + * Modifies bits o[xoffset ... xoffset + a->w + b->w - 1) + * [yoffset ... yoffset + a->h + b->h - 1). */ +void +bitmask_convolve(const bitmask_t *a, const bitmask_t *b, bitmask_t *o, + int xoffset, int yoffset); + +#ifdef __cplusplus +} /* End of extern "C" { */ +#endif + +#endif diff --git a/.venv/include/site/python3.8/pygame/include/pgcompat.h b/.venv/include/site/python3.8/pygame/include/pgcompat.h new file mode 100644 index 0000000..4a11ca0 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pgcompat.h @@ -0,0 +1,108 @@ +/* Python 2.x/3.x and SDL compatibility tools + */ + +#if !defined(PGCOMPAT_H) +#define PGCOMPAT_H + +#include + +/* define common types where SDL is not included */ +#ifndef SDL_VERSION_ATLEAST +#ifdef _MSC_VER +typedef unsigned __int8 uint8_t; +typedef unsigned __int32 uint32_t; +#else +#include +#endif +typedef uint32_t Uint32; +typedef uint8_t Uint8; +#endif /* no SDL */ + +#if defined(SDL_VERSION_ATLEAST) + +#ifndef SDL_WINDOW_VULKAN +#define SDL_WINDOW_VULKAN 0 +#endif + +#ifndef SDL_WINDOW_ALWAYS_ON_TOP +#define SDL_WINDOW_ALWAYS_ON_TOP 0 +#endif + +#ifndef SDL_WINDOW_SKIP_TASKBAR +#define SDL_WINDOW_SKIP_TASKBAR 0 +#endif + +#ifndef SDL_WINDOW_UTILITY +#define SDL_WINDOW_UTILITY 0 +#endif + +#ifndef SDL_WINDOW_TOOLTIP +#define SDL_WINDOW_TOOLTIP 0 +#endif + +#ifndef SDL_WINDOW_POPUP_MENU +#define SDL_WINDOW_POPUP_MENU 0 +#endif + +#ifndef SDL_WINDOW_INPUT_GRABBED +#define SDL_WINDOW_INPUT_GRABBED 0 +#endif + +#ifndef SDL_WINDOW_INPUT_FOCUS +#define SDL_WINDOW_INPUT_FOCUS 0 +#endif + +#ifndef SDL_WINDOW_MOUSE_FOCUS +#define SDL_WINDOW_MOUSE_FOCUS 0 +#endif + +#ifndef SDL_WINDOW_FOREIGN +#define SDL_WINDOW_FOREIGN 0 +#endif + +#ifndef SDL_WINDOW_ALLOW_HIGHDPI +#define SDL_WINDOW_ALLOW_HIGHDPI 0 +#endif + +#ifndef SDL_WINDOW_MOUSE_CAPTURE +#define SDL_WINDOW_MOUSE_CAPTURE 0 +#endif + +#ifndef SDL_WINDOW_ALWAYS_ON_TOP +#define SDL_WINDOW_ALWAYS_ON_TOP 0 +#endif + +#ifndef SDL_WINDOW_SKIP_TASKBAR +#define SDL_WINDOW_SKIP_TASKBAR 0 +#endif + +#ifndef SDL_WINDOW_UTILITY +#define SDL_WINDOW_UTILITY 0 +#endif + +#ifndef SDL_WINDOW_TOOLTIP +#define SDL_WINDOW_TOOLTIP 0 +#endif + +#ifndef SDL_WINDOW_POPUP_MENU +#define SDL_WINDOW_POPUP_MENU 0 +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 4) +/* To control the use of: + * SDL_AUDIODEVICEADDED + * SDL_AUDIODEVICEREMOVED + * + * Ref: https://wiki.libsdl.org/SDL_EventType + * Ref: https://wiki.libsdl.org/SDL_AudioDeviceEvent + */ +#define SDL2_AUDIODEVICE_SUPPORTED +#endif + +#ifndef SDL_MOUSEWHEEL_FLIPPED +#define NO_SDL_MOUSEWHEEL_FLIPPED +#endif + +#endif /* defined(SDL_VERSION_ATLEAST) */ + +#endif /* ~defined(PGCOMPAT_H) */ diff --git a/.venv/include/site/python3.8/pygame/include/pgimport.h b/.venv/include/site/python3.8/pygame/include/pgimport.h new file mode 100644 index 0000000..16a36db --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pgimport.h @@ -0,0 +1,80 @@ +#ifndef PGIMPORT_H +#define PGIMPORT_H + +/* Prefix when initializing module */ +#define MODPREFIX "" +/* Prefix when importing module */ +#define IMPPREFIX "pygame." + +#ifdef __SYMBIAN32__ + +/* On Symbian there is no pygame package. The extensions are built-in or in + * sys\bin. */ +#undef MODPREFIX +#undef IMPPREFIX +#define MODPREFIX "pygame_" +#define IMPPREFIX "pygame_" + +#endif /* __SYMBIAN32__ */ + +#include "pgcompat.h" + +#define PYGAMEAPI_LOCAL_ENTRY "_PYGAME_C_API" +#define PG_CAPSULE_NAME(m) (IMPPREFIX m "." PYGAMEAPI_LOCAL_ENTRY) + +/* + * fill API slots defined by PYGAMEAPI_DEFINE_SLOTS/PYGAMEAPI_EXTERN_SLOTS + */ +#define _IMPORT_PYGAME_MODULE(module) \ + { \ + PyObject *_mod_##module = PyImport_ImportModule(IMPPREFIX #module); \ + \ + if (_mod_##module != NULL) { \ + PyObject *_c_api = \ + PyObject_GetAttrString(_mod_##module, PYGAMEAPI_LOCAL_ENTRY); \ + \ + Py_DECREF(_mod_##module); \ + if (_c_api != NULL && PyCapsule_CheckExact(_c_api)) { \ + void **localptr = (void **)PyCapsule_GetPointer( \ + _c_api, PG_CAPSULE_NAME(#module)); \ + _PGSLOTS_##module = localptr; \ + } \ + Py_XDECREF(_c_api); \ + } \ + } + +#define PYGAMEAPI_IS_IMPORTED(module) (_PGSLOTS_##module != NULL) + +/* + * source file must include one of these in order to use _IMPORT_PYGAME_MODULE. + * this is set by import_pygame_*() functions. + * disable with NO_PYGAME_C_API + */ +#define PYGAMEAPI_DEFINE_SLOTS(module) void **_PGSLOTS_##module = NULL +#define PYGAMEAPI_EXTERN_SLOTS(module) extern void **_PGSLOTS_##module +#define PYGAMEAPI_GET_SLOT(module, index) _PGSLOTS_##module[(index)] + +/* + * disabled API with NO_PYGAME_C_API; do nothing instead + */ +#ifdef NO_PYGAME_C_API + +#undef PYGAMEAPI_DEFINE_SLOTS +#undef PYGAMEAPI_EXTERN_SLOTS + +#define PYGAMEAPI_DEFINE_SLOTS(module) +#define PYGAMEAPI_EXTERN_SLOTS(module) + +/* intentionally leave this defined to cause a compiler error * +#define PYGAMEAPI_GET_SLOT(api_root, index) +#undef PYGAMEAPI_GET_SLOT*/ + +#undef _IMPORT_PYGAME_MODULE +#define _IMPORT_PYGAME_MODULE(module) + +#endif /* NO_PYGAME_C_API */ + +#define encapsulate_api(ptr, module) \ + PyCapsule_New(ptr, PG_CAPSULE_NAME(module), NULL) + +#endif /* ~PGIMPORT_H */ diff --git a/.venv/include/site/python3.8/pygame/include/pgplatform.h b/.venv/include/site/python3.8/pygame/include/pgplatform.h new file mode 100644 index 0000000..c73cc24 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pgplatform.h @@ -0,0 +1,92 @@ +/* platform/compiler adjustments */ +#ifndef PG_PLATFORM_H +#define PG_PLATFORM_H + +#if defined(HAVE_SNPRINTF) /* defined in python.h (pyerrors.h) and SDL.h \ + (SDL_config.h) */ +#undef HAVE_SNPRINTF /* remove GCC redefine warning */ +#endif /* HAVE_SNPRINTF */ + +#ifndef PG_INLINE +#if defined(__clang__) +#define PG_INLINE __inline__ __attribute__((__unused__)) +#elif defined(__GNUC__) +#define PG_INLINE __inline__ +#elif defined(_MSC_VER) +#define PG_INLINE __inline +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define PG_INLINE inline +#else +#define PG_INLINE +#endif +#endif /* ~PG_INLINE */ + +// Worth trying this on MSVC/win32 builds to see if provides any speed up +#ifndef PG_FORCEINLINE +#if defined(__clang__) +#define PG_FORCEINLINE __inline__ __attribute__((__unused__)) +#elif defined(__GNUC__) +#define PG_FORCEINLINE __inline__ +#elif defined(_MSC_VER) +#define PG_FORCEINLINE __forceinline +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define PG_FORCEINLINE inline +#else +#define PG_FORCEINLINE +#endif +#endif /* ~PG_FORCEINLINE */ + +/* This is unconditionally defined in Python.h */ +#if defined(_POSIX_C_SOURCE) +#undef _POSIX_C_SOURCE +#endif + +/* No signal() */ +#if defined(__SYMBIAN32__) && defined(HAVE_SIGNAL_H) +#undef HAVE_SIGNAL_H +#endif + +#if defined(HAVE_SNPRINTF) +#undef HAVE_SNPRINTF +#endif + +/* SDL needs WIN32 */ +#if !defined(WIN32) && \ + (defined(MS_WIN32) || defined(_WIN32) || defined(__WIN32) || \ + defined(__WIN32__) || defined(_WINDOWS)) +#define WIN32 +#endif + +/* Commenting out SSE4_2 stuff because it does not do runtime detection. +#ifndef PG_TARGET_SSE4_2 +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ == 4 && +__GNUC_MINOR__ >= 9) || __GNUC__ >= 5 )) +//The old gcc 4.8 on centos used by manylinux1 does not seem to get sse4.2 +intrinsics #define PG_FUNCTION_TARGET_SSE4_2 __attribute__((target("sse4.2"))) +// No else; we define the fallback later +#endif +#endif +*/ +/* ~PG_TARGET_SSE4_2 */ + +/* +#ifdef PG_FUNCTION_TARGET_SSE4_2 +#if !defined(__SSE4_2__) && !defined(PG_COMPILE_SSE4_2) +#if defined(__x86_64__) || defined(__i386__) +#define PG_COMPILE_SSE4_2 1 +#endif +#endif +#endif +*/ +/* ~PG_TARGET_SSE4_2 */ + +/* Fallback definition of target attribute */ +#ifndef PG_FUNCTION_TARGET_SSE4_2 +#define PG_FUNCTION_TARGET_SSE4_2 +#endif + +#ifndef PG_COMPILE_SSE4_2 +#define PG_COMPILE_SSE4_2 0 +#endif + +#endif /* ~PG_PLATFORM_H */ diff --git a/.venv/include/site/python3.8/pygame/include/pygame.h b/.venv/include/site/python3.8/pygame/include/pygame.h new file mode 100644 index 0000000..3772ae6 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pygame.h @@ -0,0 +1,34 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +/* To allow the Pygame C api to be globally shared by all code within an + * extension module built from multiple C files, only include the pygame.h + * header within the top level C file, the one which calls the + * 'import_pygame_*' macros. All other C source files of the module should + * include _pygame.h instead. + */ +#ifndef PYGAME_H +#define PYGAME_H + +#include "_pygame.h" + +#endif diff --git a/.venv/include/site/python3.8/pygame/include/pygame_bufferproxy.h b/.venv/include/site/python3.8/pygame/include/pygame_bufferproxy.h new file mode 100644 index 0000000..9284ff2 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pygame_bufferproxy.h @@ -0,0 +1,56 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + Copyright (C) 2007 Rene Dudfield, Richard Goedeken + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +/* Bufferproxy module C api. */ +#if !defined(PG_BUFPROXY_HEADER) +#define PG_BUFPROXY_HEADER + +#include + +typedef PyObject *(*_pgbufproxy_new_t)(PyObject *, getbufferproc); +typedef PyObject *(*_pgbufproxy_get_obj_t)(PyObject *); +typedef int (*_pgbufproxy_trip_t)(PyObject *); + +#ifndef PYGAMEAPI_BUFPROXY_INTERNAL + +#include "pgimport.h" + +PYGAMEAPI_DEFINE_SLOTS(bufferproxy); + +#define pgBufproxy_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(bufferproxy, 0)) + +#define pgBufproxy_Check(x) ((x)->ob_type == &pgBufproxy_Type) + +#define pgBufproxy_New (*(_pgbufproxy_new_t)PYGAMEAPI_GET_SLOT(bufferproxy, 1)) + +#define pgBufproxy_GetParent \ + (*(_pgbufproxy_get_obj_t)PYGAMEAPI_GET_SLOT(bufferproxy, 2)) + +#define pgBufproxy_Trip \ + (*(_pgbufproxy_trip_t)PYGAMEAPI_GET_SLOT(bufferproxy, 3)) + +#define import_pygame_bufferproxy() _IMPORT_PYGAME_MODULE(bufferproxy) + +#endif /* ~PYGAMEAPI_BUFPROXY_INTERNAL */ + +#endif /* ~defined(PG_BUFPROXY_HEADER) */ diff --git a/.venv/include/site/python3.8/pygame/include/pygame_font.h b/.venv/include/site/python3.8/pygame/include/pygame_font.h new file mode 100644 index 0000000..aae41bf --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pygame_font.h @@ -0,0 +1,50 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +#include +#include "pgplatform.h" + +struct TTF_Font; + +typedef struct { + PyObject_HEAD TTF_Font *font; + PyObject *weakreflist; + unsigned int ttf_init_generation; +} PyFontObject; +#define PyFont_AsFont(x) (((PyFontObject *)x)->font) + +#ifndef PYGAMEAPI_FONT_INTERNAL + +#include "pgimport.h" + +PYGAMEAPI_DEFINE_SLOTS(font); + +#define PyFont_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(font, 0)) +#define PyFont_Check(x) ((x)->ob_type == &PyFont_Type) + +#define PyFont_New (*(PyObject * (*)(TTF_Font *)) PYGAMEAPI_GET_SLOT(font, 1)) + +/*slot 2 taken by FONT_INIT_CHECK*/ + +#define import_pygame_font() _IMPORT_PYGAME_MODULE(font) + +#endif diff --git a/.venv/include/site/python3.8/pygame/include/pygame_freetype.h b/.venv/include/site/python3.8/pygame/include/pygame_freetype.h new file mode 100644 index 0000000..90172cc --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pygame_freetype.h @@ -0,0 +1,42 @@ +/* + pygame - Python Game Library + Copyright (C) 2009 Vicent Marti + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +#ifndef PYGAME_FREETYPE_H_ +#define PYGAME_FREETYPE_H_ + +#include "pgplatform.h" +#include "pgimport.h" +#include "pgcompat.h" + +#ifndef PYGAME_FREETYPE_INTERNAL + +PYGAMEAPI_DEFINE_SLOTS(_freetype); + +#define pgFont_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(_freetype, 0)) + +#define pgFont_Check(x) ((x)->ob_type == &pgFont_Type) + +#define pgFont_New \ + (*(PyObject * (*)(const char *, long)) PYGAMEAPI_GET_SLOT(_freetype, 1)) + +#define import_pygame_freetype() _IMPORT_PYGAME_MODULE(_freetype) + +#endif /* PYGAME_FREETYPE_INTERNAL */ + +#endif /* PYGAME_FREETYPE_H_ */ diff --git a/.venv/include/site/python3.8/pygame/include/pygame_mask.h b/.venv/include/site/python3.8/pygame/include/pygame_mask.h new file mode 100644 index 0000000..8dd8f17 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pygame_mask.h @@ -0,0 +1,45 @@ +/* + pygame - Python Game Library + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef PGMASK_H +#define PGMASK_H + +#include +#include "bitmask.h" + +typedef struct { + PyObject_HEAD bitmask_t *mask; + void *bufdata; +} pgMaskObject; + +#define pgMask_AsBitmap(x) (((pgMaskObject *)x)->mask) + +#ifndef PYGAMEAPI_MASK_INTERNAL + +#include "pgimport.h" + +PYGAMEAPI_DEFINE_SLOTS(mask); + +#define pgMask_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(mask, 0)) +#define pgMask_Check(x) ((x)->ob_type == &pgMask_Type) + +#define import_pygame_mask() _IMPORT_PYGAME_MODULE(mask) + +#endif /* ~PYGAMEAPI_MASK_INTERNAL */ + +#endif /* ~PGMASK_H */ diff --git a/.venv/include/site/python3.8/pygame/include/pygame_mixer.h b/.venv/include/site/python3.8/pygame/include/pygame_mixer.h new file mode 100644 index 0000000..e19d273 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/pygame_mixer.h @@ -0,0 +1,71 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +#ifndef PGMIXER_H +#define PGMIXER_H + +#include +#include + +#include "pgcompat.h" + +struct Mix_Chunk; + +typedef struct { + PyObject_HEAD Mix_Chunk *chunk; + Uint8 *mem; + PyObject *weakreflist; +} pgSoundObject; + +typedef struct { + PyObject_HEAD int chan; +} pgChannelObject; + +#define pgSound_AsChunk(x) (((pgSoundObject *)x)->chunk) +#define pgChannel_AsInt(x) (((pgChannelObject *)x)->chan) + +#include "pgimport.h" + +#ifndef PYGAMEAPI_MIXER_INTERNAL + +PYGAMEAPI_DEFINE_SLOTS(mixer); + +#define pgSound_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(mixer, 0)) + +#define pgSound_Check(x) ((x)->ob_type == &pgSound_Type) + +#define pgSound_New \ + (*(PyObject * (*)(Mix_Chunk *)) PYGAMEAPI_GET_SLOT(mixer, 1)) + +#define pgSound_Play \ + (*(PyObject * (*)(PyObject *, PyObject *)) PYGAMEAPI_GET_SLOT(mixer, 2)) + +#define pgChannel_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(mixer, 3)) +#define pgChannel_Check(x) ((x)->ob_type == &pgChannel_Type) + +#define pgChannel_New (*(PyObject * (*)(int)) PYGAMEAPI_GET_SLOT(mixer, 4)) + +#define import_pygame_mixer() _IMPORT_PYGAME_MODULE(mixer) + +#endif /* PYGAMEAPI_MIXER_INTERNAL */ + +#endif /* ~PGMIXER_H */ diff --git a/.venv/include/site/python3.8/pygame/include/sse2neon.h b/.venv/include/site/python3.8/pygame/include/sse2neon.h new file mode 100644 index 0000000..a3e3ac0 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/include/sse2neon.h @@ -0,0 +1,6203 @@ +#ifndef SSE2NEON_H +#define SSE2NEON_H + +// This header file provides a simple API translation layer +// between SSE intrinsics to their corresponding Arm/Aarch64 NEON versions +// +// This header file does not yet translate all of the SSE intrinsics. +// +// Contributors to this work are: +// John W. Ratcliff +// Brandon Rowlett +// Ken Fast +// Eric van Beurden +// Alexander Potylitsin +// Hasindu Gamaarachchi +// Jim Huang +// Mark Cheng +// Malcolm James MacLeod +// Devin Hussey (easyaspi314) +// Sebastian Pop +// Developer Ecosystem Engineering +// Danila Kutenin +// François Turban (JishinMaster) +// Pei-Hsuan Hung +// Yang-Hao Yuan + +/* + * sse2neon is freely redistributable under the MIT License. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Tunable configurations */ + +/* Enable precise implementation of _mm_min_ps and _mm_max_ps + * This would slow down the computation a bit, but gives consistent result with + * x86 SSE2. (e.g. would solve a hole or NaN pixel in the rendering result) + */ +#ifndef SSE2NEON_PRECISE_MINMAX +#define SSE2NEON_PRECISE_MINMAX (0) +#endif + +#if defined(__GNUC__) || defined(__clang__) +#pragma push_macro("FORCE_INLINE") +#pragma push_macro("ALIGN_STRUCT") +#define FORCE_INLINE static inline __attribute__((always_inline)) +#define ALIGN_STRUCT(x) __attribute__((aligned(x))) +#else +#error "Macro name collisions may happen with unsupported compiler." +#ifdef FORCE_INLINE +#undef FORCE_INLINE +#endif +#define FORCE_INLINE static inline +#ifndef ALIGN_STRUCT +#define ALIGN_STRUCT(x) __declspec(align(x)) +#endif +#endif + +#include +#include + +// These cause the build to fail on raspberry pi with 'unsupported target' +// and don't seem to do anything particularly useful +///* Architecture-specific build options */ +///* FIXME: #pragma GCC push_options is only available on GCC */ +//#if defined(__GNUC__) +//#if defined(__arm__) && __ARM_ARCH == 7 +///* According to ARM C Language Extensions Architecture specification, +// * __ARM_NEON is defined to a value indicating the Advanced SIMD (NEON) +// * architecture supported. +// */ +//#if !defined(__ARM_NEON) || !defined(__ARM_NEON__) +//#error "You must enable NEON instructions (e.g. -mfpu=neon) to use SSE2NEON." +//#endif +//#pragma GCC push_options +//#pragma GCC target("fpu=neon") +//#elif defined(__aarch64__) +//#pragma GCC push_options +//#pragma GCC target("+simd") +//#else +//#error "Unsupported target. Must be either ARMv7-A+NEON or ARMv8-A." +//#endif +//#endif + +#include + +/* Rounding functions require either Aarch64 instructions or libm failback */ +#if !defined(__aarch64__) +#include +#endif + +/* "__has_builtin" can be used to query support for built-in functions + * provided by gcc/clang and other compilers that support it. + */ +#ifndef __has_builtin /* GCC prior to 10 or non-clang compilers */ +/* Compatibility with gcc <= 9 */ +#if __GNUC__ <= 9 +#define __has_builtin(x) HAS##x +#define HAS__builtin_popcount 1 +#define HAS__builtin_popcountll 1 +#else +#define __has_builtin(x) 0 +#endif +#endif + +/** + * MACRO for shuffle parameter for _mm_shuffle_ps(). + * Argument fp3 is a digit[0123] that represents the fp from argument "b" + * of mm_shuffle_ps that will be placed in fp3 of result. fp2 is the same + * for fp2 in result. fp1 is a digit[0123] that represents the fp from + * argument "a" of mm_shuffle_ps that will be places in fp1 of result. + * fp0 is the same for fp0 of result. + */ +#define _MM_SHUFFLE(fp3, fp2, fp1, fp0) \ + (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) + +/* Rounding mode macros. */ +#define _MM_FROUND_TO_NEAREST_INT 0x00 +#define _MM_FROUND_TO_NEG_INF 0x01 +#define _MM_FROUND_TO_POS_INF 0x02 +#define _MM_FROUND_TO_ZERO 0x03 +#define _MM_FROUND_CUR_DIRECTION 0x04 +#define _MM_FROUND_NO_EXC 0x08 + +/* indicate immediate constant argument in a given range */ +#define __constrange(a, b) const + +/* A few intrinsics accept traditional data types like ints or floats, but + * most operate on data types that are specific to SSE. + * If a vector type ends in d, it contains doubles, and if it does not have + * a suffix, it contains floats. An integer vector type can contain any type + * of integer, from chars to shorts to unsigned long longs. + */ +typedef int64x1_t __m64; +typedef float32x4_t __m128; /* 128-bit vector containing 4 floats */ +// On ARM 32-bit architecture, the float64x2_t is not supported. +// The data type __m128d should be represented in a different way for related +// intrinsic conversion. +#if defined(__aarch64__) +typedef float64x2_t __m128d; /* 128-bit vector containing 2 doubles */ +#else +typedef float32x4_t __m128d; +#endif +typedef int64x2_t __m128i; /* 128-bit vector containing integers */ + +/* type-safe casting between types */ + +#define vreinterpretq_m128_f16(x) vreinterpretq_f32_f16(x) +#define vreinterpretq_m128_f32(x) (x) +#define vreinterpretq_m128_f64(x) vreinterpretq_f32_f64(x) + +#define vreinterpretq_m128_u8(x) vreinterpretq_f32_u8(x) +#define vreinterpretq_m128_u16(x) vreinterpretq_f32_u16(x) +#define vreinterpretq_m128_u32(x) vreinterpretq_f32_u32(x) +#define vreinterpretq_m128_u64(x) vreinterpretq_f32_u64(x) + +#define vreinterpretq_m128_s8(x) vreinterpretq_f32_s8(x) +#define vreinterpretq_m128_s16(x) vreinterpretq_f32_s16(x) +#define vreinterpretq_m128_s32(x) vreinterpretq_f32_s32(x) +#define vreinterpretq_m128_s64(x) vreinterpretq_f32_s64(x) + +#define vreinterpretq_f16_m128(x) vreinterpretq_f16_f32(x) +#define vreinterpretq_f32_m128(x) (x) +#define vreinterpretq_f64_m128(x) vreinterpretq_f64_f32(x) + +#define vreinterpretq_u8_m128(x) vreinterpretq_u8_f32(x) +#define vreinterpretq_u16_m128(x) vreinterpretq_u16_f32(x) +#define vreinterpretq_u32_m128(x) vreinterpretq_u32_f32(x) +#define vreinterpretq_u64_m128(x) vreinterpretq_u64_f32(x) + +#define vreinterpretq_s8_m128(x) vreinterpretq_s8_f32(x) +#define vreinterpretq_s16_m128(x) vreinterpretq_s16_f32(x) +#define vreinterpretq_s32_m128(x) vreinterpretq_s32_f32(x) +#define vreinterpretq_s64_m128(x) vreinterpretq_s64_f32(x) + +#define vreinterpretq_m128i_s8(x) vreinterpretq_s64_s8(x) +#define vreinterpretq_m128i_s16(x) vreinterpretq_s64_s16(x) +#define vreinterpretq_m128i_s32(x) vreinterpretq_s64_s32(x) +#define vreinterpretq_m128i_s64(x) (x) + +#define vreinterpretq_m128i_u8(x) vreinterpretq_s64_u8(x) +#define vreinterpretq_m128i_u16(x) vreinterpretq_s64_u16(x) +#define vreinterpretq_m128i_u32(x) vreinterpretq_s64_u32(x) +#define vreinterpretq_m128i_u64(x) vreinterpretq_s64_u64(x) + +#define vreinterpretq_s8_m128i(x) vreinterpretq_s8_s64(x) +#define vreinterpretq_s16_m128i(x) vreinterpretq_s16_s64(x) +#define vreinterpretq_s32_m128i(x) vreinterpretq_s32_s64(x) +#define vreinterpretq_s64_m128i(x) (x) + +#define vreinterpretq_u8_m128i(x) vreinterpretq_u8_s64(x) +#define vreinterpretq_u16_m128i(x) vreinterpretq_u16_s64(x) +#define vreinterpretq_u32_m128i(x) vreinterpretq_u32_s64(x) +#define vreinterpretq_u64_m128i(x) vreinterpretq_u64_s64(x) + +#define vreinterpret_m64_s8(x) vreinterpret_s64_s8(x) +#define vreinterpret_m64_s16(x) vreinterpret_s64_s16(x) +#define vreinterpret_m64_s32(x) vreinterpret_s64_s32(x) +#define vreinterpret_m64_s64(x) (x) + +#define vreinterpret_m64_u8(x) vreinterpret_s64_u8(x) +#define vreinterpret_m64_u16(x) vreinterpret_s64_u16(x) +#define vreinterpret_m64_u32(x) vreinterpret_s64_u32(x) +#define vreinterpret_m64_u64(x) vreinterpret_s64_u64(x) + +#define vreinterpret_m64_f16(x) vreinterpret_s64_f16(x) +#define vreinterpret_m64_f32(x) vreinterpret_s64_f32(x) +#define vreinterpret_m64_f64(x) vreinterpret_s64_f64(x) + +#define vreinterpret_u8_m64(x) vreinterpret_u8_s64(x) +#define vreinterpret_u16_m64(x) vreinterpret_u16_s64(x) +#define vreinterpret_u32_m64(x) vreinterpret_u32_s64(x) +#define vreinterpret_u64_m64(x) vreinterpret_u64_s64(x) + +#define vreinterpret_s8_m64(x) vreinterpret_s8_s64(x) +#define vreinterpret_s16_m64(x) vreinterpret_s16_s64(x) +#define vreinterpret_s32_m64(x) vreinterpret_s32_s64(x) +#define vreinterpret_s64_m64(x) (x) + +#define vreinterpret_f32_m64(x) vreinterpret_f32_s64(x) + +#if defined(__aarch64__) +#define vreinterpretq_m128d_s32(x) vreinterpretq_f64_s32(x) +#define vreinterpretq_m128d_s64(x) vreinterpretq_f64_s64(x) + +#define vreinterpretq_m128d_f64(x) (x) + +#define vreinterpretq_s64_m128d(x) vreinterpretq_s64_f64(x) + +#define vreinterpretq_f64_m128d(x) (x) +#else +#define vreinterpretq_m128d_s32(x) vreinterpretq_f32_s32(x) +#define vreinterpretq_m128d_s64(x) vreinterpretq_f32_s64(x) + +#define vreinterpretq_m128d_f32(x) (x) + +#define vreinterpretq_s64_m128d(x) vreinterpretq_s64_f32(x) + +#define vreinterpretq_f32_m128d(x) (x) +#endif + +// A struct is defined in this header file called 'SIMDVec' which can be used +// by applications which attempt to access the contents of an _m128 struct +// directly. It is important to note that accessing the __m128 struct directly +// is bad coding practice by Microsoft: @see: +// https://msdn.microsoft.com/en-us/library/ayeb3ayc.aspx +// +// However, some legacy source code may try to access the contents of an __m128 +// struct directly so the developer can use the SIMDVec as an alias for it. Any +// casting must be done manually by the developer, as you cannot cast or +// otherwise alias the base NEON data type for intrinsic operations. +// +// union intended to allow direct access to an __m128 variable using the names +// that the MSVC compiler provides. This union should really only be used when +// trying to access the members of the vector as integer values. GCC/clang +// allow native access to the float members through a simple array access +// operator (in C since 4.6, in C++ since 4.8). +// +// Ideally direct accesses to SIMD vectors should not be used since it can cause +// a performance hit. If it really is needed however, the original __m128 +// variable can be aliased with a pointer to this union and used to access +// individual components. The use of this union should be hidden behind a macro +// that is used throughout the codebase to access the members instead of always +// declaring this type of variable. +typedef union ALIGN_STRUCT(16) SIMDVec { + float m128_f32[4]; // as floats - DON'T USE. Added for convenience. + int8_t m128_i8[16]; // as signed 8-bit integers. + int16_t m128_i16[8]; // as signed 16-bit integers. + int32_t m128_i32[4]; // as signed 32-bit integers. + int64_t m128_i64[2]; // as signed 64-bit integers. + uint8_t m128_u8[16]; // as unsigned 8-bit integers. + uint16_t m128_u16[8]; // as unsigned 16-bit integers. + uint32_t m128_u32[4]; // as unsigned 32-bit integers. + uint64_t m128_u64[2]; // as unsigned 64-bit integers. +} SIMDVec; + +// casting using SIMDVec +#define vreinterpretq_nth_u64_m128i(x, n) (((SIMDVec *) &x)->m128_u64[n]) +#define vreinterpretq_nth_u32_m128i(x, n) (((SIMDVec *) &x)->m128_u32[n]) +#define vreinterpretq_nth_u8_m128i(x, n) (((SIMDVec *) &x)->m128_u8[n]) + +/* Backwards compatibility for compilers with lack of specific type support */ + +// Older gcc does not define vld1q_u8_x4 type +#if defined(__GNUC__) && !defined(__clang__) +#if __GNUC__ <= 9 +FORCE_INLINE uint8x16x4_t vld1q_u8_x4(const uint8_t *p) +{ + uint8x16x4_t ret; + ret.val[0] = vld1q_u8(p + 0); + ret.val[1] = vld1q_u8(p + 16); + ret.val[2] = vld1q_u8(p + 32); + ret.val[3] = vld1q_u8(p + 48); + return ret; +} +#endif +#endif + +/* Function Naming Conventions + * The naming convention of SSE intrinsics is straightforward. A generic SSE + * intrinsic function is given as follows: + * _mm__ + * + * The parts of this format are given as follows: + * 1. describes the operation performed by the intrinsic + * 2. identifies the data type of the function's primary arguments + * + * This last part, , is a little complicated. It identifies the + * content of the input values, and can be set to any of the following values: + * + ps - vectors contain floats (ps stands for packed single-precision) + * + pd - vectors cantain doubles (pd stands for packed double-precision) + * + epi8/epi16/epi32/epi64 - vectors contain 8-bit/16-bit/32-bit/64-bit + * signed integers + * + epu8/epu16/epu32/epu64 - vectors contain 8-bit/16-bit/32-bit/64-bit + * unsigned integers + * + si128 - unspecified 128-bit vector or 256-bit vector + * + m128/m128i/m128d - identifies input vector types when they are different + * than the type of the returned vector + * + * For example, _mm_setzero_ps. The _mm implies that the function returns + * a 128-bit vector. The _ps at the end implies that the argument vectors + * contain floats. + * + * A complete example: Byte Shuffle - pshufb (_mm_shuffle_epi8) + * // Set packed 16-bit integers. 128 bits, 8 short, per 16 bits + * __m128i v_in = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8); + * // Set packed 8-bit integers + * // 128 bits, 16 chars, per 8 bits + * __m128i v_perm = _mm_setr_epi8(1, 0, 2, 3, 8, 9, 10, 11, + * 4, 5, 12, 13, 6, 7, 14, 15); + * // Shuffle packed 8-bit integers + * __m128i v_out = _mm_shuffle_epi8(v_in, v_perm); // pshufb + * + * Data (Number, Binary, Byte Index): + +------+------+-------------+------+------+-------------+ + | 1 | 2 | 3 | 4 | Number + +------+------+------+------+------+------+------+------+ + | 0000 | 0001 | 0000 | 0010 | 0000 | 0011 | 0000 | 0100 | Binary + +------+------+------+------+------+------+------+------+ + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | Index + +------+------+------+------+------+------+------+------+ + + +------+------+------+------+------+------+------+------+ + | 5 | 6 | 7 | 8 | Number + +------+------+------+------+------+------+------+------+ + | 0000 | 0101 | 0000 | 0110 | 0000 | 0111 | 0000 | 1000 | Binary + +------+------+------+------+------+------+------+------+ + | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Index + +------+------+------+------+------+------+------+------+ + * Index (Byte Index): + +------+------+------+------+------+------+------+------+ + | 1 | 0 | 2 | 3 | 8 | 9 | 10 | 11 | + +------+------+------+------+------+------+------+------+ + + +------+------+------+------+------+------+------+------+ + | 4 | 5 | 12 | 13 | 6 | 7 | 14 | 15 | + +------+------+------+------+------+------+------+------+ + * Result: + +------+------+------+------+------+------+------+------+ + | 1 | 0 | 2 | 3 | 8 | 9 | 10 | 11 | Index + +------+------+------+------+------+------+------+------+ + | 0001 | 0000 | 0000 | 0010 | 0000 | 0101 | 0000 | 0110 | Binary + +------+------+------+------+------+------+------+------+ + | 256 | 2 | 5 | 6 | Number + +------+------+------+------+------+------+------+------+ + + +------+------+------+------+------+------+------+------+ + | 4 | 5 | 12 | 13 | 6 | 7 | 14 | 15 | Index + +------+------+------+------+------+------+------+------+ + | 0000 | 0011 | 0000 | 0111 | 0000 | 0100 | 0000 | 1000 | Binary + +------+------+------+------+------+------+------+------+ + | 3 | 7 | 4 | 8 | Number + +------+------+------+------+------+------+-------------+ + */ + +/* Set/get methods */ + +/* Constants for use with _mm_prefetch. */ +enum _mm_hint { + _MM_HINT_NTA = 0, /* load data to L1 and L2 cache, mark it as NTA */ + _MM_HINT_T0 = 1, /* load data to L1 and L2 cache */ + _MM_HINT_T1 = 2, /* load data to L2 cache only */ + _MM_HINT_T2 = 3, /* load data to L2 cache only, mark it as NTA */ + _MM_HINT_ENTA = 4, /* exclusive version of _MM_HINT_NTA */ + _MM_HINT_ET0 = 5, /* exclusive version of _MM_HINT_T0 */ + _MM_HINT_ET1 = 6, /* exclusive version of _MM_HINT_T1 */ + _MM_HINT_ET2 = 7 /* exclusive version of _MM_HINT_T2 */ +}; + +// Loads one cache line of data from address p to a location closer to the +// processor. https://msdn.microsoft.com/en-us/library/84szxsww(v=vs.100).aspx +FORCE_INLINE void _mm_prefetch(const void *p, int i) +{ + (void) i; + __builtin_prefetch(p); +} + +// Copy the lower single-precision (32-bit) floating-point element of a to dst. +// +// dst[31:0] := a[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_f32 +FORCE_INLINE float _mm_cvtss_f32(__m128 a) +{ + return vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); +} + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer, and store the result in dst. +// +// dst[31:0] := Convert_FP32_To_Int32(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si32 +#define _mm_cvtss_si32(a) _mm_cvt_ss2si(a) + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 64-bit integer, and store the result in dst. +// +// dst[63:0] := Convert_FP32_To_Int64(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si64 +FORCE_INLINE int _mm_cvtss_si64(__m128 a) +{ +#if defined(__aarch64__) + return vgetq_lane_s64( + vreinterpretq_s64_s32(vcvtnq_s32_f32(vreinterpretq_f32_m128(a))), 0); +#else + float32_t data = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + float32_t diff = data - floor(data); + if (diff > 0.5) + return (int64_t) ceil(data); + if (diff == 0.5) { + int64_t f = (int64_t) floor(data); + int64_t c = (int64_t) ceil(data); + return c & 1 ? f : c; + } + return (int64_t) floor(data); +#endif +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 32-bit integers with truncation, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ps2pi +FORCE_INLINE __m64 _mm_cvtt_ps2pi(__m128 a) +{ + return vreinterpret_m64_s32( + vget_low_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)))); +} + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer with truncation, and store the result in dst. +// +// dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ss2si +FORCE_INLINE int _mm_cvtt_ss2si(__m128 a) +{ + return vgetq_lane_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)), 0); +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 32-bit integers with truncation, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttps_pi32 +#define _mm_cvttps_pi32(a) _mm_cvtt_ps2pi(a) + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer with truncation, and store the result in dst. +// +// dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si32 +#define _mm_cvttss_si32(a) _mm_cvtt_ss2si(a) + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 64-bit integer with truncation, and store the result in dst. +// +// dst[63:0] := Convert_FP32_To_Int64_Truncate(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si64 +FORCE_INLINE int64_t _mm_cvttss_si64(__m128 a) +{ + return vgetq_lane_s64( + vmovl_s32(vget_low_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)))), 0); +} + +// Sets the 128-bit value to zero +// https://msdn.microsoft.com/en-us/library/vstudio/ys7dw0kh(v=vs.100).aspx +FORCE_INLINE __m128i _mm_setzero_si128(void) +{ + return vreinterpretq_m128i_s32(vdupq_n_s32(0)); +} + +// Clears the four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/tk1t2tbz(v=vs.100).aspx +FORCE_INLINE __m128 _mm_setzero_ps(void) +{ + return vreinterpretq_m128_f32(vdupq_n_f32(0)); +} + +// Return vector of type __m128d with all elements set to zero. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setzero_pd +FORCE_INLINE __m128d _mm_setzero_pd(void) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vdupq_n_f64(0)); +#else + return vreinterpretq_m128d_f32(vdupq_n_f32(0)); +#endif +} + +// Sets the four single-precision, floating-point values to w. +// +// r0 := r1 := r2 := r3 := w +// +// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx +FORCE_INLINE __m128 _mm_set1_ps(float _w) +{ + return vreinterpretq_m128_f32(vdupq_n_f32(_w)); +} + +// Sets the four single-precision, floating-point values to w. +// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx +FORCE_INLINE __m128 _mm_set_ps1(float _w) +{ + return vreinterpretq_m128_f32(vdupq_n_f32(_w)); +} + +// Sets the four single-precision, floating-point values to the four inputs. +// https://msdn.microsoft.com/en-us/library/vstudio/afh0zf75(v=vs.100).aspx +FORCE_INLINE __m128 _mm_set_ps(float w, float z, float y, float x) +{ + float ALIGN_STRUCT(16) data[4] = {x, y, z, w}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +} + +// Copy single-precision (32-bit) floating-point element a to the lower element +// of dst, and zero the upper 3 elements. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_ss +FORCE_INLINE __m128 _mm_set_ss(float a) +{ + float ALIGN_STRUCT(16) data[4] = {a, 0, 0, 0}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +} + +// Sets the four single-precision, floating-point values to the four inputs in +// reverse order. +// https://msdn.microsoft.com/en-us/library/vstudio/d2172ct3(v=vs.100).aspx +FORCE_INLINE __m128 _mm_setr_ps(float w, float z, float y, float x) +{ + float ALIGN_STRUCT(16) data[4] = {w, z, y, x}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +} + +// Sets the 8 signed 16-bit integer values in reverse order. +// +// Return Value +// r0 := w0 +// r1 := w1 +// ... +// r7 := w7 +FORCE_INLINE __m128i _mm_setr_epi16(short w0, + short w1, + short w2, + short w3, + short w4, + short w5, + short w6, + short w7) +{ + int16_t ALIGN_STRUCT(16) data[8] = {w0, w1, w2, w3, w4, w5, w6, w7}; + return vreinterpretq_m128i_s16(vld1q_s16((int16_t *) data)); +} + +// Sets the 4 signed 32-bit integer values in reverse order +// https://technet.microsoft.com/en-us/library/security/27yb3ee5(v=vs.90).aspx +FORCE_INLINE __m128i _mm_setr_epi32(int i3, int i2, int i1, int i0) +{ + int32_t ALIGN_STRUCT(16) data[4] = {i3, i2, i1, i0}; + return vreinterpretq_m128i_s32(vld1q_s32(data)); +} + +// Set packed 64-bit integers in dst with the supplied values in reverse order. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setr_epi64 +FORCE_INLINE __m128i _mm_setr_epi64(__m64 e1, __m64 e0) +{ + return vreinterpretq_m128i_s64(vcombine_s64(e1, e0)); +} + +// Sets the 16 signed 8-bit integer values to b. +// +// r0 := b +// r1 := b +// ... +// r15 := b +// +// https://msdn.microsoft.com/en-us/library/6e14xhyf(v=vs.100).aspx +FORCE_INLINE __m128i _mm_set1_epi8(signed char w) +{ + return vreinterpretq_m128i_s8(vdupq_n_s8(w)); +} + +// Sets the 8 signed 16-bit integer values to w. +// +// r0 := w +// r1 := w +// ... +// r7 := w +// +// https://msdn.microsoft.com/en-us/library/k0ya3x0e(v=vs.90).aspx +FORCE_INLINE __m128i _mm_set1_epi16(short w) +{ + return vreinterpretq_m128i_s16(vdupq_n_s16(w)); +} + +// Sets the 16 signed 8-bit integer values. +// https://msdn.microsoft.com/en-us/library/x0cx8zd3(v=vs.90).aspx +FORCE_INLINE __m128i _mm_set_epi8(signed char b15, + signed char b14, + signed char b13, + signed char b12, + signed char b11, + signed char b10, + signed char b9, + signed char b8, + signed char b7, + signed char b6, + signed char b5, + signed char b4, + signed char b3, + signed char b2, + signed char b1, + signed char b0) +{ + int8_t ALIGN_STRUCT(16) + data[16] = {(int8_t) b0, (int8_t) b1, (int8_t) b2, (int8_t) b3, + (int8_t) b4, (int8_t) b5, (int8_t) b6, (int8_t) b7, + (int8_t) b8, (int8_t) b9, (int8_t) b10, (int8_t) b11, + (int8_t) b12, (int8_t) b13, (int8_t) b14, (int8_t) b15}; + return (__m128i) vld1q_s8(data); +} + +// Sets the 8 signed 16-bit integer values. +// https://msdn.microsoft.com/en-au/library/3e0fek84(v=vs.90).aspx +FORCE_INLINE __m128i _mm_set_epi16(short i7, + short i6, + short i5, + short i4, + short i3, + short i2, + short i1, + short i0) +{ + int16_t ALIGN_STRUCT(16) data[8] = {i0, i1, i2, i3, i4, i5, i6, i7}; + return vreinterpretq_m128i_s16(vld1q_s16(data)); +} + +// Sets the 16 signed 8-bit integer values in reverse order. +// https://msdn.microsoft.com/en-us/library/2khb9c7k(v=vs.90).aspx +FORCE_INLINE __m128i _mm_setr_epi8(signed char b0, + signed char b1, + signed char b2, + signed char b3, + signed char b4, + signed char b5, + signed char b6, + signed char b7, + signed char b8, + signed char b9, + signed char b10, + signed char b11, + signed char b12, + signed char b13, + signed char b14, + signed char b15) +{ + int8_t ALIGN_STRUCT(16) + data[16] = {(int8_t) b0, (int8_t) b1, (int8_t) b2, (int8_t) b3, + (int8_t) b4, (int8_t) b5, (int8_t) b6, (int8_t) b7, + (int8_t) b8, (int8_t) b9, (int8_t) b10, (int8_t) b11, + (int8_t) b12, (int8_t) b13, (int8_t) b14, (int8_t) b15}; + return (__m128i) vld1q_s8(data); +} + +// Sets the 4 signed 32-bit integer values to i. +// +// r0 := i +// r1 := i +// r2 := i +// r3 := I +// +// https://msdn.microsoft.com/en-us/library/vstudio/h4xscxat(v=vs.100).aspx +FORCE_INLINE __m128i _mm_set1_epi32(int _i) +{ + return vreinterpretq_m128i_s32(vdupq_n_s32(_i)); +} + +// Sets the 2 signed 64-bit integer values to i. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/whtfzhzk(v=vs.100) +FORCE_INLINE __m128i _mm_set1_epi64(__m64 _i) +{ + return vreinterpretq_m128i_s64(vdupq_n_s64((int64_t) _i)); +} + +// Sets the 2 signed 64-bit integer values to i. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set1_epi64x +FORCE_INLINE __m128i _mm_set1_epi64x(int64_t _i) +{ + return vreinterpretq_m128i_s64(vdupq_n_s64(_i)); +} + +// Sets the 4 signed 32-bit integer values. +// https://msdn.microsoft.com/en-us/library/vstudio/019beekt(v=vs.100).aspx +FORCE_INLINE __m128i _mm_set_epi32(int i3, int i2, int i1, int i0) +{ + int32_t ALIGN_STRUCT(16) data[4] = {i0, i1, i2, i3}; + return vreinterpretq_m128i_s32(vld1q_s32(data)); +} + +// Returns the __m128i structure with its two 64-bit integer values +// initialized to the values of the two 64-bit integers passed in. +// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx +FORCE_INLINE __m128i _mm_set_epi64x(int64_t i1, int64_t i2) +{ + int64_t ALIGN_STRUCT(16) data[2] = {i2, i1}; + return vreinterpretq_m128i_s64(vld1q_s64(data)); +} + +// Returns the __m128i structure with its two 64-bit integer values +// initialized to the values of the two 64-bit integers passed in. +// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx +FORCE_INLINE __m128i _mm_set_epi64(__m64 i1, __m64 i2) +{ + return _mm_set_epi64x((int64_t) i1, (int64_t) i2); +} + +// Set packed double-precision (64-bit) floating-point elements in dst with the +// supplied values. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_pd +FORCE_INLINE __m128d _mm_set_pd(double e1, double e0) +{ + double ALIGN_STRUCT(16) data[2] = {e0, e1}; +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vld1q_f64((float64_t *) data)); +#else + return vreinterpretq_m128d_f32(vld1q_f32((float32_t *) data)); +#endif +} + +// Stores four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/s3h4ay6y(v=vs.100).aspx +FORCE_INLINE void _mm_store_ps(float *p, __m128 a) +{ + vst1q_f32(p, vreinterpretq_f32_m128(a)); +} + +// Stores four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/44e30x22(v=vs.100).aspx +FORCE_INLINE void _mm_storeu_ps(float *p, __m128 a) +{ + vst1q_f32(p, vreinterpretq_f32_m128(a)); +} + +// Stores four 32-bit integer values as (as a __m128i value) at the address p. +// https://msdn.microsoft.com/en-us/library/vstudio/edk11s13(v=vs.100).aspx +FORCE_INLINE void _mm_store_si128(__m128i *p, __m128i a) +{ + vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a)); +} + +// Stores four 32-bit integer values as (as a __m128i value) at the address p. +// https://msdn.microsoft.com/en-us/library/vstudio/edk11s13(v=vs.100).aspx +FORCE_INLINE void _mm_storeu_si128(__m128i *p, __m128i a) +{ + vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a)); +} + +// Stores the lower single - precision, floating - point value. +// https://msdn.microsoft.com/en-us/library/tzz10fbx(v=vs.100).aspx +FORCE_INLINE void _mm_store_ss(float *p, __m128 a) +{ + vst1q_lane_f32(p, vreinterpretq_f32_m128(a), 0); +} + +// Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point +// elements) from a into memory. mem_addr must be aligned on a 16-byte boundary +// or a general-protection exception may be generated. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_pd +FORCE_INLINE void _mm_store_pd(double *mem_addr, __m128d a) +{ +#if defined(__aarch64__) + vst1q_f64((float64_t *) mem_addr, vreinterpretq_f64_m128d(a)); +#else + vst1q_f32((float32_t *) mem_addr, vreinterpretq_f32_m128d(a)); +#endif +} + +// Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point +// elements) from a into memory. mem_addr does not need to be aligned on any +// particular boundary. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_pd +FORCE_INLINE void _mm_storeu_pd(double *mem_addr, __m128d a) +{ + _mm_store_pd(mem_addr, a); +} + +// Reads the lower 64 bits of b and stores them into the lower 64 bits of a. +// https://msdn.microsoft.com/en-us/library/hhwf428f%28v=vs.90%29.aspx +FORCE_INLINE void _mm_storel_epi64(__m128i *a, __m128i b) +{ + uint64x1_t hi = vget_high_u64(vreinterpretq_u64_m128i(*a)); + uint64x1_t lo = vget_low_u64(vreinterpretq_u64_m128i(b)); + *a = vreinterpretq_m128i_u64(vcombine_u64(lo, hi)); +} + +// Stores the lower two single-precision floating point values of a to the +// address p. +// +// *p0 := a0 +// *p1 := a1 +// +// https://msdn.microsoft.com/en-us/library/h54t98ks(v=vs.90).aspx +FORCE_INLINE void _mm_storel_pi(__m64 *p, __m128 a) +{ + *p = vreinterpret_m64_f32(vget_low_f32(a)); +} + +// Stores the upper two single-precision, floating-point values of a to the +// address p. +// +// *p0 := a2 +// *p1 := a3 +// +// https://msdn.microsoft.com/en-us/library/a7525fs8(v%3dvs.90).aspx +FORCE_INLINE void _mm_storeh_pi(__m64 *p, __m128 a) +{ + *p = vreinterpret_m64_f32(vget_high_f32(a)); +} + +// Loads a single single-precision, floating-point value, copying it into all +// four words +// https://msdn.microsoft.com/en-us/library/vstudio/5cdkf716(v=vs.100).aspx +FORCE_INLINE __m128 _mm_load1_ps(const float *p) +{ + return vreinterpretq_m128_f32(vld1q_dup_f32(p)); +} + +// Load a single-precision (32-bit) floating-point element from memory into all +// elements of dst. +// +// dst[31:0] := MEM[mem_addr+31:mem_addr] +// dst[63:32] := MEM[mem_addr+31:mem_addr] +// dst[95:64] := MEM[mem_addr+31:mem_addr] +// dst[127:96] := MEM[mem_addr+31:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_ps1 +#define _mm_load_ps1 _mm_load1_ps + +// Sets the lower two single-precision, floating-point values with 64 +// bits of data loaded from the address p; the upper two values are passed +// through from a. +// +// Return Value +// r0 := *p0 +// r1 := *p1 +// r2 := a2 +// r3 := a3 +// +// https://msdn.microsoft.com/en-us/library/s57cyak2(v=vs.100).aspx +FORCE_INLINE __m128 _mm_loadl_pi(__m128 a, __m64 const *p) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vld1_f32((const float32_t *) p), vget_high_f32(a))); +} + +// Load 4 single-precision (32-bit) floating-point elements from memory into dst +// in reverse order. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// dst[31:0] := MEM[mem_addr+127:mem_addr+96] +// dst[63:32] := MEM[mem_addr+95:mem_addr+64] +// dst[95:64] := MEM[mem_addr+63:mem_addr+32] +// dst[127:96] := MEM[mem_addr+31:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_ps +FORCE_INLINE __m128 _mm_loadr_ps(const float *p) +{ + float32x4_t v = vrev64q_f32(vld1q_f32(p)); + return vreinterpretq_m128_f32(vextq_f32(v, v, 2)); +} + +// Sets the upper two single-precision, floating-point values with 64 +// bits of data loaded from the address p; the lower two values are passed +// through from a. +// +// r0 := a0 +// r1 := a1 +// r2 := *p0 +// r3 := *p1 +// +// https://msdn.microsoft.com/en-us/library/w92wta0x(v%3dvs.100).aspx +FORCE_INLINE __m128 _mm_loadh_pi(__m128 a, __m64 const *p) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vget_low_f32(a), vld1_f32((const float32_t *) p))); +} + +// Loads four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/zzd50xxt(v=vs.100).aspx +FORCE_INLINE __m128 _mm_load_ps(const float *p) +{ + return vreinterpretq_m128_f32(vld1q_f32(p)); +} + +// Loads four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/x1b16s7z%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_loadu_ps(const float *p) +{ + // for neon, alignment doesn't matter, so _mm_load_ps and _mm_loadu_ps are + // equivalent for neon + return vreinterpretq_m128_f32(vld1q_f32(p)); +} + +// Load unaligned 16-bit integer from memory into the first element of dst. +// +// dst[15:0] := MEM[mem_addr+15:mem_addr] +// dst[MAX:16] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si16 +FORCE_INLINE __m128i _mm_loadu_si16(const void *p) +{ + return vreinterpretq_m128i_s16( + vsetq_lane_s16(*(const int16_t *) p, vdupq_n_s16(0), 0)); +} + +// Load unaligned 64-bit integer from memory into the first element of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[MAX:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si64 +FORCE_INLINE __m128i _mm_loadu_si64(const void *p) +{ + return vreinterpretq_m128i_s64( + vcombine_s64(vld1_s64((const int64_t *) p), vdup_n_s64(0))); +} + +// Load a double-precision (64-bit) floating-point element from memory into the +// lower of dst, and zero the upper element. mem_addr does not need to be +// aligned on any particular boundary. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_sd +FORCE_INLINE __m128d _mm_load_sd(const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vsetq_lane_f64(*p, vdupq_n_f64(0), 0)); +#else + const float *fp = (const float *) p; + float ALIGN_STRUCT(16) data[4] = {fp[0], fp[1], 0, 0}; + return vreinterpretq_m128d_f32(vld1q_f32(data)); +#endif +} + +// Loads two double-precision from 16-byte aligned memory, floating-point +// values. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd +FORCE_INLINE __m128d _mm_load_pd(const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vld1q_f64(p)); +#else + const float *fp = (const float *) p; + float ALIGN_STRUCT(16) data[4] = {fp[0], fp[1], fp[2], fp[3]}; + return vreinterpretq_m128d_f32(vld1q_f32(data)); +#endif +} + +// Loads two double-precision from unaligned memory, floating-point values. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_pd +FORCE_INLINE __m128d _mm_loadu_pd(const double *p) +{ + return _mm_load_pd(p); +} + +// Loads an single - precision, floating - point value into the low word and +// clears the upper three words. +// https://msdn.microsoft.com/en-us/library/548bb9h4%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_load_ss(const float *p) +{ + return vreinterpretq_m128_f32(vsetq_lane_f32(*p, vdupq_n_f32(0), 0)); +} + +FORCE_INLINE __m128i _mm_loadl_epi64(__m128i const *p) +{ + /* Load the lower 64 bits of the value pointed to by p into the + * lower 64 bits of the result, zeroing the upper 64 bits of the result. + */ + return vreinterpretq_m128i_s32( + vcombine_s32(vld1_s32((int32_t const *) p), vcreate_s32(0))); +} + +// Load a double-precision (64-bit) floating-point element from memory into the +// lower element of dst, and copy the upper element from a to dst. mem_addr does +// not need to be aligned on any particular boundary. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := a[127:64] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadl_pd +FORCE_INLINE __m128d _mm_loadl_pd(__m128d a, const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcombine_f64(vld1_f64(p), vget_high_f64(vreinterpretq_f64_m128d(a)))); +#else + return vreinterpretq_m128d_f32( + vcombine_f32(vld1_f32((const float *) p), + vget_high_f32(vreinterpretq_f32_m128d(a)))); +#endif +} + +// Load 2 double-precision (64-bit) floating-point elements from memory into dst +// in reverse order. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// dst[63:0] := MEM[mem_addr+127:mem_addr+64] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_pd +FORCE_INLINE __m128d _mm_loadr_pd(const double *p) +{ +#if defined(__aarch64__) + float64x2_t v = vld1q_f64(p); + return vreinterpretq_m128d_f64(vextq_f64(v, v, 1)); +#else + int64x2_t v = vld1q_s64((const int64_t *) p); + return vreinterpretq_m128d_s64(vextq_s64(v, v, 1)); +#endif +} + +// Sets the low word to the single-precision, floating-point value of b +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/35hdzazd(v=vs.100) +FORCE_INLINE __m128 _mm_move_ss(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vsetq_lane_f32(vgetq_lane_f32(vreinterpretq_f32_m128(b), 0), + vreinterpretq_f32_m128(a), 0)); +} + +// Copy the lower 64-bit integer in a to the lower element of dst, and zero the +// upper element. +// +// dst[63:0] := a[63:0] +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_move_epi64 +FORCE_INLINE __m128i _mm_move_epi64(__m128i a) +{ + return vreinterpretq_m128i_s64( + vsetq_lane_s64(0, vreinterpretq_s64_m128i(a), 1)); +} + +// Return vector of type __m128 with undefined elements. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_undefined_ps +FORCE_INLINE __m128 _mm_undefined_ps(void) +{ + __m128 a; + return a; +} + +/* Logic/Binary operations */ + +// Computes the bitwise AND-NOT of the four single-precision, floating-point +// values of a and b. +// +// r0 := ~a0 & b0 +// r1 := ~a1 & b1 +// r2 := ~a2 & b2 +// r3 := ~a3 & b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/68h7wd02(v=vs.100).aspx +FORCE_INLINE __m128 _mm_andnot_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + vbicq_s32(vreinterpretq_s32_m128(b), + vreinterpretq_s32_m128(a))); // *NOTE* argument swap +} + +// Compute the bitwise NOT of packed double-precision (64-bit) floating-point +// elements in a and then AND with b, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// dst[i+63:i] := ((NOT a[i+63:i]) AND b[i+63:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_andnot_pd +FORCE_INLINE __m128d _mm_andnot_pd(__m128d a, __m128d b) +{ + // *NOTE* argument swap + return vreinterpretq_m128d_s64( + vbicq_s64(vreinterpretq_s64_m128d(b), vreinterpretq_s64_m128d(a))); +} + +// Computes the bitwise AND of the 128-bit value in b and the bitwise NOT of the +// 128-bit value in a. +// +// r := (~a) & b +// +// https://msdn.microsoft.com/en-us/library/vstudio/1beaceh8(v=vs.100).aspx +FORCE_INLINE __m128i _mm_andnot_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vbicq_s32(vreinterpretq_s32_m128i(b), + vreinterpretq_s32_m128i(a))); // *NOTE* argument swap +} + +// Computes the bitwise AND of the 128-bit value in a and the 128-bit value in +// b. +// +// r := a & b +// +// https://msdn.microsoft.com/en-us/library/vstudio/6d1txsa8(v=vs.100).aspx +FORCE_INLINE __m128i _mm_and_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vandq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Computes the bitwise AND of the four single-precision, floating-point values +// of a and b. +// +// r0 := a0 & b0 +// r1 := a1 & b1 +// r2 := a2 & b2 +// r3 := a3 & b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/73ck1xc5(v=vs.100).aspx +FORCE_INLINE __m128 _mm_and_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + vandq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b))); +} + +// Compute the bitwise AND of packed double-precision (64-bit) floating-point +// elements in a and b, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// dst[i+63:i] := a[i+63:i] AND b[i+63:i] +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_and_pd +FORCE_INLINE __m128d _mm_and_pd(__m128d a, __m128d b) +{ + return vreinterpretq_m128d_s64( + vandq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); +} + +// Computes the bitwise OR of the four single-precision, floating-point values +// of a and b. +// https://msdn.microsoft.com/en-us/library/vstudio/7ctdsyy0(v=vs.100).aspx +FORCE_INLINE __m128 _mm_or_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + vorrq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b))); +} + +// Computes bitwise EXOR (exclusive-or) of the four single-precision, +// floating-point values of a and b. +// https://msdn.microsoft.com/en-us/library/ss6k3wk8(v=vs.100).aspx +FORCE_INLINE __m128 _mm_xor_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + veorq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b))); +} + +// Compute the bitwise XOR of packed double-precision (64-bit) floating-point +// elements in a and b, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// dst[i+63:i] := a[i+63:i] XOR b[i+63:i] +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_xor_pd +FORCE_INLINE __m128d _mm_xor_pd(__m128d a, __m128d b) +{ + return vreinterpretq_m128d_s64( + veorq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); +} + +// Computes the bitwise OR of the 128-bit value in a and the 128-bit value in b. +// +// r := a | b +// +// https://msdn.microsoft.com/en-us/library/vstudio/ew8ty0db(v=vs.100).aspx +FORCE_INLINE __m128i _mm_or_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vorrq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Computes the bitwise XOR of the 128-bit value in a and the 128-bit value in +// b. https://msdn.microsoft.com/en-us/library/fzt08www(v=vs.100).aspx +FORCE_INLINE __m128i _mm_xor_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + veorq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Duplicate odd-indexed single-precision (32-bit) floating-point elements +// from a, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movehdup_ps +FORCE_INLINE __m128 _mm_movehdup_ps(__m128 a) +{ +#if __has_builtin(__builtin_shufflevector) + return vreinterpretq_m128_f32(__builtin_shufflevector( + vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 1, 1, 3, 3)); +#else + float32_t a1 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 1); + float32_t a3 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 3); + float ALIGN_STRUCT(16) data[4] = {a1, a1, a3, a3}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +#endif +} + +// Duplicate even-indexed single-precision (32-bit) floating-point elements +// from a, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_moveldup_ps +FORCE_INLINE __m128 _mm_moveldup_ps(__m128 a) +{ +#if __has_builtin(__builtin_shufflevector) + return vreinterpretq_m128_f32(__builtin_shufflevector( + vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 0, 0, 2, 2)); +#else + float32_t a0 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + float32_t a2 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 2); + float ALIGN_STRUCT(16) data[4] = {a0, a0, a2, a2}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +#endif +} + +// Moves the upper two values of B into the lower two values of A. +// +// r3 := a3 +// r2 := a2 +// r1 := b3 +// r0 := b2 +FORCE_INLINE __m128 _mm_movehl_ps(__m128 __A, __m128 __B) +{ + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(__A)); + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(__B)); + return vreinterpretq_m128_f32(vcombine_f32(b32, a32)); +} + +// Moves the lower two values of B into the upper two values of A. +// +// r3 := b1 +// r2 := b0 +// r1 := a1 +// r0 := a0 +FORCE_INLINE __m128 _mm_movelh_ps(__m128 __A, __m128 __B) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(__A)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(__B)); + return vreinterpretq_m128_f32(vcombine_f32(a10, b10)); +} + +// Compute the absolute value of packed signed 32-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// dst[i+31:i] := ABS(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi32 +FORCE_INLINE __m128i _mm_abs_epi32(__m128i a) +{ + return vreinterpretq_m128i_s32(vabsq_s32(vreinterpretq_s32_m128i(a))); +} + +// Compute the absolute value of packed signed 16-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// dst[i+15:i] := ABS(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi16 +FORCE_INLINE __m128i _mm_abs_epi16(__m128i a) +{ + return vreinterpretq_m128i_s16(vabsq_s16(vreinterpretq_s16_m128i(a))); +} + +// Compute the absolute value of packed signed 8-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 15 +// i := j*8 +// dst[i+7:i] := ABS(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi8 +FORCE_INLINE __m128i _mm_abs_epi8(__m128i a) +{ + return vreinterpretq_m128i_s8(vabsq_s8(vreinterpretq_s8_m128i(a))); +} + +// Compute the absolute value of packed signed 32-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 1 +// i := j*32 +// dst[i+31:i] := ABS(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi32 +FORCE_INLINE __m64 _mm_abs_pi32(__m64 a) +{ + return vreinterpret_m64_s32(vabs_s32(vreinterpret_s32_m64(a))); +} + +// Compute the absolute value of packed signed 16-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := ABS(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi16 +FORCE_INLINE __m64 _mm_abs_pi16(__m64 a) +{ + return vreinterpret_m64_s16(vabs_s16(vreinterpret_s16_m64(a))); +} + +// Compute the absolute value of packed signed 8-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := ABS(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi8 +FORCE_INLINE __m64 _mm_abs_pi8(__m64 a) +{ + return vreinterpret_m64_s8(vabs_s8(vreinterpret_s8_m64(a))); +} + +// Takes the upper 64 bits of a and places it in the low end of the result +// Takes the lower 64 bits of b and places it into the high end of the result. +FORCE_INLINE __m128 _mm_shuffle_ps_1032(__m128 a, __m128 b) +{ + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a32, b10)); +} + +// takes the lower two 32-bit values from a and swaps them and places in high +// end of result takes the higher two 32 bit values from b and swaps them and +// places in low end of result. +FORCE_INLINE __m128 _mm_shuffle_ps_2301(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32x2_t b23 = vrev64_f32(vget_high_f32(vreinterpretq_f32_m128(b))); + return vreinterpretq_m128_f32(vcombine_f32(a01, b23)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0321(__m128 a, __m128 b) +{ + float32x2_t a21 = vget_high_f32( + vextq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 3)); + float32x2_t b03 = vget_low_f32( + vextq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b), 3)); + return vreinterpretq_m128_f32(vcombine_f32(a21, b03)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2103(__m128 a, __m128 b) +{ + float32x2_t a03 = vget_low_f32( + vextq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 3)); + float32x2_t b21 = vget_high_f32( + vextq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b), 3)); + return vreinterpretq_m128_f32(vcombine_f32(a03, b21)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_1010(__m128 a, __m128 b) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a10, b10)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_1001(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a01, b10)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0101(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32x2_t b01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(b))); + return vreinterpretq_m128_f32(vcombine_f32(a01, b01)); +} + +// keeps the low 64 bits of b in the low and puts the high 64 bits of a in the +// high +FORCE_INLINE __m128 _mm_shuffle_ps_3210(__m128 a, __m128 b) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a10, b32)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0011(__m128 a, __m128 b) +{ + float32x2_t a11 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(a)), 1); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + return vreinterpretq_m128_f32(vcombine_f32(a11, b00)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0022(__m128 a, __m128 b) +{ + float32x2_t a22 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(a)), 0); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + return vreinterpretq_m128_f32(vcombine_f32(a22, b00)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2200(__m128 a, __m128 b) +{ + float32x2_t a00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(a)), 0); + float32x2_t b22 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(b)), 0); + return vreinterpretq_m128_f32(vcombine_f32(a00, b22)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_3202(__m128 a, __m128 b) +{ + float32_t a0 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + float32x2_t a22 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(a)), 0); + float32x2_t a02 = vset_lane_f32(a0, a22, 1); /* TODO: use vzip ?*/ + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a02, b32)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_1133(__m128 a, __m128 b) +{ + float32x2_t a33 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(a)), 1); + float32x2_t b11 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 1); + return vreinterpretq_m128_f32(vcombine_f32(a33, b11)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2010(__m128 a, __m128 b) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32_t b2 = vgetq_lane_f32(vreinterpretq_f32_m128(b), 2); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + float32x2_t b20 = vset_lane_f32(b2, b00, 1); + return vreinterpretq_m128_f32(vcombine_f32(a10, b20)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2001(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32_t b2 = vgetq_lane_f32(b, 2); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + float32x2_t b20 = vset_lane_f32(b2, b00, 1); + return vreinterpretq_m128_f32(vcombine_f32(a01, b20)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2032(__m128 a, __m128 b) +{ + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32_t b2 = vgetq_lane_f32(b, 2); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + float32x2_t b20 = vset_lane_f32(b2, b00, 1); + return vreinterpretq_m128_f32(vcombine_f32(a32, b20)); +} + +// NEON does not support a general purpose permute intrinsic +// Selects four specific single-precision, floating-point values from a and b, +// based on the mask i. +// +// C equivalent: +// __m128 _mm_shuffle_ps_default(__m128 a, __m128 b, +// __constrange(0, 255) int imm) { +// __m128 ret; +// ret[0] = a[imm & 0x3]; ret[1] = a[(imm >> 2) & 0x3]; +// ret[2] = b[(imm >> 4) & 0x03]; ret[3] = b[(imm >> 6) & 0x03]; +// return ret; +// } +// +// https://msdn.microsoft.com/en-us/library/vstudio/5f0858x0(v=vs.100).aspx +#define _mm_shuffle_ps_default(a, b, imm) \ + __extension__({ \ + float32x4_t ret; \ + ret = vmovq_n_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(a), (imm) & (0x3))); \ + ret = vsetq_lane_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(a), ((imm) >> 2) & 0x3), \ + ret, 1); \ + ret = vsetq_lane_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(b), ((imm) >> 4) & 0x3), \ + ret, 2); \ + ret = vsetq_lane_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(b), ((imm) >> 6) & 0x3), \ + ret, 3); \ + vreinterpretq_m128_f32(ret); \ + }) + +// FORCE_INLINE __m128 _mm_shuffle_ps(__m128 a, __m128 b, __constrange(0,255) +// int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shuffle_ps(a, b, imm) \ + __extension__({ \ + float32x4_t _input1 = vreinterpretq_f32_m128(a); \ + float32x4_t _input2 = vreinterpretq_f32_m128(b); \ + float32x4_t _shuf = __builtin_shufflevector( \ + _input1, _input2, (imm) & (0x3), ((imm) >> 2) & 0x3, \ + (((imm) >> 4) & 0x3) + 4, (((imm) >> 6) & 0x3) + 4); \ + vreinterpretq_m128_f32(_shuf); \ + }) +#else // generic +#define _mm_shuffle_ps(a, b, imm) \ + __extension__({ \ + __m128 ret; \ + switch (imm) { \ + case _MM_SHUFFLE(1, 0, 3, 2): \ + ret = _mm_shuffle_ps_1032((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 3, 0, 1): \ + ret = _mm_shuffle_ps_2301((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 3, 2, 1): \ + ret = _mm_shuffle_ps_0321((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 1, 0, 3): \ + ret = _mm_shuffle_ps_2103((a), (b)); \ + break; \ + case _MM_SHUFFLE(1, 0, 1, 0): \ + ret = _mm_movelh_ps((a), (b)); \ + break; \ + case _MM_SHUFFLE(1, 0, 0, 1): \ + ret = _mm_shuffle_ps_1001((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 1, 0, 1): \ + ret = _mm_shuffle_ps_0101((a), (b)); \ + break; \ + case _MM_SHUFFLE(3, 2, 1, 0): \ + ret = _mm_shuffle_ps_3210((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 0, 1, 1): \ + ret = _mm_shuffle_ps_0011((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 0, 2, 2): \ + ret = _mm_shuffle_ps_0022((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 2, 0, 0): \ + ret = _mm_shuffle_ps_2200((a), (b)); \ + break; \ + case _MM_SHUFFLE(3, 2, 0, 2): \ + ret = _mm_shuffle_ps_3202((a), (b)); \ + break; \ + case _MM_SHUFFLE(3, 2, 3, 2): \ + ret = _mm_movehl_ps((b), (a)); \ + break; \ + case _MM_SHUFFLE(1, 1, 3, 3): \ + ret = _mm_shuffle_ps_1133((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 0, 1, 0): \ + ret = _mm_shuffle_ps_2010((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 0, 0, 1): \ + ret = _mm_shuffle_ps_2001((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 0, 3, 2): \ + ret = _mm_shuffle_ps_2032((a), (b)); \ + break; \ + default: \ + ret = _mm_shuffle_ps_default((a), (b), (imm)); \ + break; \ + } \ + ret; \ + }) +#endif + +// Takes the upper 64 bits of a and places it in the low end of the result +// Takes the lower 64 bits of a and places it into the high end of the result. +FORCE_INLINE __m128i _mm_shuffle_epi_1032(__m128i a) +{ + int32x2_t a32 = vget_high_s32(vreinterpretq_s32_m128i(a)); + int32x2_t a10 = vget_low_s32(vreinterpretq_s32_m128i(a)); + return vreinterpretq_m128i_s32(vcombine_s32(a32, a10)); +} + +// takes the lower two 32-bit values from a and swaps them and places in low end +// of result takes the higher two 32 bit values from a and swaps them and places +// in high end of result. +FORCE_INLINE __m128i _mm_shuffle_epi_2301(__m128i a) +{ + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + int32x2_t a23 = vrev64_s32(vget_high_s32(vreinterpretq_s32_m128i(a))); + return vreinterpretq_m128i_s32(vcombine_s32(a01, a23)); +} + +// rotates the least significant 32 bits into the most signficant 32 bits, and +// shifts the rest down +FORCE_INLINE __m128i _mm_shuffle_epi_0321(__m128i a) +{ + return vreinterpretq_m128i_s32( + vextq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(a), 1)); +} + +// rotates the most significant 32 bits into the least signficant 32 bits, and +// shifts the rest up +FORCE_INLINE __m128i _mm_shuffle_epi_2103(__m128i a) +{ + return vreinterpretq_m128i_s32( + vextq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(a), 3)); +} + +// gets the lower 64 bits of a, and places it in the upper 64 bits +// gets the lower 64 bits of a and places it in the lower 64 bits +FORCE_INLINE __m128i _mm_shuffle_epi_1010(__m128i a) +{ + int32x2_t a10 = vget_low_s32(vreinterpretq_s32_m128i(a)); + return vreinterpretq_m128i_s32(vcombine_s32(a10, a10)); +} + +// gets the lower 64 bits of a, swaps the 0 and 1 elements, and places it in the +// lower 64 bits gets the lower 64 bits of a, and places it in the upper 64 bits +FORCE_INLINE __m128i _mm_shuffle_epi_1001(__m128i a) +{ + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + int32x2_t a10 = vget_low_s32(vreinterpretq_s32_m128i(a)); + return vreinterpretq_m128i_s32(vcombine_s32(a01, a10)); +} + +// gets the lower 64 bits of a, swaps the 0 and 1 elements and places it in the +// upper 64 bits gets the lower 64 bits of a, swaps the 0 and 1 elements, and +// places it in the lower 64 bits +FORCE_INLINE __m128i _mm_shuffle_epi_0101(__m128i a) +{ + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + return vreinterpretq_m128i_s32(vcombine_s32(a01, a01)); +} + +FORCE_INLINE __m128i _mm_shuffle_epi_2211(__m128i a) +{ + int32x2_t a11 = vdup_lane_s32(vget_low_s32(vreinterpretq_s32_m128i(a)), 1); + int32x2_t a22 = vdup_lane_s32(vget_high_s32(vreinterpretq_s32_m128i(a)), 0); + return vreinterpretq_m128i_s32(vcombine_s32(a11, a22)); +} + +FORCE_INLINE __m128i _mm_shuffle_epi_0122(__m128i a) +{ + int32x2_t a22 = vdup_lane_s32(vget_high_s32(vreinterpretq_s32_m128i(a)), 0); + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + return vreinterpretq_m128i_s32(vcombine_s32(a22, a01)); +} + +FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a) +{ + int32x2_t a32 = vget_high_s32(vreinterpretq_s32_m128i(a)); + int32x2_t a33 = vdup_lane_s32(vget_high_s32(vreinterpretq_s32_m128i(a)), 1); + return vreinterpretq_m128i_s32(vcombine_s32(a32, a33)); +} + +// Shuffle packed 8-bit integers in a according to shuffle control mask in the +// corresponding 8-bit element of b, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_epi8 +FORCE_INLINE __m128i _mm_shuffle_epi8(__m128i a, __m128i b) +{ + int8x16_t tbl = vreinterpretq_s8_m128i(a); // input a + uint8x16_t idx = vreinterpretq_u8_m128i(b); // input b + uint8x16_t idx_masked = + vandq_u8(idx, vdupq_n_u8(0x8F)); // avoid using meaningless bits +#if defined(__aarch64__) + return vreinterpretq_m128i_s8(vqtbl1q_s8(tbl, idx_masked)); +#elif defined(__GNUC__) + int8x16_t ret; + // %e and %f represent the even and odd D registers + // respectively. + __asm__ __volatile__( + "vtbl.8 %e[ret], {%e[tbl], %f[tbl]}, %e[idx]\n" + "vtbl.8 %f[ret], {%e[tbl], %f[tbl]}, %f[idx]\n" + : [ret] "=&w"(ret) + : [tbl] "w"(tbl), [idx] "w"(idx_masked)); + return vreinterpretq_m128i_s8(ret); +#else + // use this line if testing on aarch64 + int8x8x2_t a_split = {vget_low_s8(tbl), vget_high_s8(tbl)}; + return vreinterpretq_m128i_s8( + vcombine_s8(vtbl2_s8(a_split, vget_low_u8(idx_masked)), + vtbl2_s8(a_split, vget_high_u8(idx_masked)))); +#endif +} + +// C equivalent: +// __m128i _mm_shuffle_epi32_default(__m128i a, +// __constrange(0, 255) int imm) { +// __m128i ret; +// ret[0] = a[imm & 0x3]; ret[1] = a[(imm >> 2) & 0x3]; +// ret[2] = a[(imm >> 4) & 0x03]; ret[3] = a[(imm >> 6) & 0x03]; +// return ret; +// } +#define _mm_shuffle_epi32_default(a, imm) \ + __extension__({ \ + int32x4_t ret; \ + ret = vmovq_n_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm) & (0x3))); \ + ret = vsetq_lane_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), ((imm) >> 2) & 0x3), \ + ret, 1); \ + ret = vsetq_lane_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), ((imm) >> 4) & 0x3), \ + ret, 2); \ + ret = vsetq_lane_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), ((imm) >> 6) & 0x3), \ + ret, 3); \ + vreinterpretq_m128i_s32(ret); \ + }) + +// FORCE_INLINE __m128i _mm_shuffle_epi32_splat(__m128i a, __constrange(0,255) +// int imm) +#if defined(__aarch64__) +#define _mm_shuffle_epi32_splat(a, imm) \ + __extension__({ \ + vreinterpretq_m128i_s32( \ + vdupq_laneq_s32(vreinterpretq_s32_m128i(a), (imm))); \ + }) +#else +#define _mm_shuffle_epi32_splat(a, imm) \ + __extension__({ \ + vreinterpretq_m128i_s32( \ + vdupq_n_s32(vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm)))); \ + }) +#endif + +// Shuffles the 4 signed or unsigned 32-bit integers in a as specified by imm. +// https://msdn.microsoft.com/en-us/library/56f67xbk%28v=vs.90%29.aspx +// FORCE_INLINE __m128i _mm_shuffle_epi32(__m128i a, +// __constrange(0,255) int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shuffle_epi32(a, imm) \ + __extension__({ \ + int32x4_t _input = vreinterpretq_s32_m128i(a); \ + int32x4_t _shuf = __builtin_shufflevector( \ + _input, _input, (imm) & (0x3), ((imm) >> 2) & 0x3, \ + ((imm) >> 4) & 0x3, ((imm) >> 6) & 0x3); \ + vreinterpretq_m128i_s32(_shuf); \ + }) +#else // generic +#define _mm_shuffle_epi32(a, imm) \ + __extension__({ \ + __m128i ret; \ + switch (imm) { \ + case _MM_SHUFFLE(1, 0, 3, 2): \ + ret = _mm_shuffle_epi_1032((a)); \ + break; \ + case _MM_SHUFFLE(2, 3, 0, 1): \ + ret = _mm_shuffle_epi_2301((a)); \ + break; \ + case _MM_SHUFFLE(0, 3, 2, 1): \ + ret = _mm_shuffle_epi_0321((a)); \ + break; \ + case _MM_SHUFFLE(2, 1, 0, 3): \ + ret = _mm_shuffle_epi_2103((a)); \ + break; \ + case _MM_SHUFFLE(1, 0, 1, 0): \ + ret = _mm_shuffle_epi_1010((a)); \ + break; \ + case _MM_SHUFFLE(1, 0, 0, 1): \ + ret = _mm_shuffle_epi_1001((a)); \ + break; \ + case _MM_SHUFFLE(0, 1, 0, 1): \ + ret = _mm_shuffle_epi_0101((a)); \ + break; \ + case _MM_SHUFFLE(2, 2, 1, 1): \ + ret = _mm_shuffle_epi_2211((a)); \ + break; \ + case _MM_SHUFFLE(0, 1, 2, 2): \ + ret = _mm_shuffle_epi_0122((a)); \ + break; \ + case _MM_SHUFFLE(3, 3, 3, 2): \ + ret = _mm_shuffle_epi_3332((a)); \ + break; \ + case _MM_SHUFFLE(0, 0, 0, 0): \ + ret = _mm_shuffle_epi32_splat((a), 0); \ + break; \ + case _MM_SHUFFLE(1, 1, 1, 1): \ + ret = _mm_shuffle_epi32_splat((a), 1); \ + break; \ + case _MM_SHUFFLE(2, 2, 2, 2): \ + ret = _mm_shuffle_epi32_splat((a), 2); \ + break; \ + case _MM_SHUFFLE(3, 3, 3, 3): \ + ret = _mm_shuffle_epi32_splat((a), 3); \ + break; \ + default: \ + ret = _mm_shuffle_epi32_default((a), (imm)); \ + break; \ + } \ + ret; \ + }) +#endif + +// Shuffles the lower 4 signed or unsigned 16-bit integers in a as specified +// by imm. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/y41dkk37(v=vs.100) +// FORCE_INLINE __m128i _mm_shufflelo_epi16_function(__m128i a, +// __constrange(0,255) int +// imm) +#define _mm_shufflelo_epi16_function(a, imm) \ + __extension__({ \ + int16x8_t ret = vreinterpretq_s16_m128i(a); \ + int16x4_t lowBits = vget_low_s16(ret); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, (imm) & (0x3)), ret, 0); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, ((imm) >> 2) & 0x3), ret, \ + 1); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, ((imm) >> 4) & 0x3), ret, \ + 2); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, ((imm) >> 6) & 0x3), ret, \ + 3); \ + vreinterpretq_m128i_s16(ret); \ + }) + +// FORCE_INLINE __m128i _mm_shufflelo_epi16(__m128i a, +// __constrange(0,255) int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shufflelo_epi16(a, imm) \ + __extension__({ \ + int16x8_t _input = vreinterpretq_s16_m128i(a); \ + int16x8_t _shuf = __builtin_shufflevector( \ + _input, _input, ((imm) & (0x3)), (((imm) >> 2) & 0x3), \ + (((imm) >> 4) & 0x3), (((imm) >> 6) & 0x3), 4, 5, 6, 7); \ + vreinterpretq_m128i_s16(_shuf); \ + }) +#else // generic +#define _mm_shufflelo_epi16(a, imm) _mm_shufflelo_epi16_function((a), (imm)) +#endif + +// Shuffles the upper 4 signed or unsigned 16-bit integers in a as specified +// by imm. +// https://msdn.microsoft.com/en-us/library/13ywktbs(v=vs.100).aspx +// FORCE_INLINE __m128i _mm_shufflehi_epi16_function(__m128i a, +// __constrange(0,255) int +// imm) +#define _mm_shufflehi_epi16_function(a, imm) \ + __extension__({ \ + int16x8_t ret = vreinterpretq_s16_m128i(a); \ + int16x4_t highBits = vget_high_s16(ret); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, (imm) & (0x3)), ret, 4); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, ((imm) >> 2) & 0x3), ret, \ + 5); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, ((imm) >> 4) & 0x3), ret, \ + 6); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, ((imm) >> 6) & 0x3), ret, \ + 7); \ + vreinterpretq_m128i_s16(ret); \ + }) + +// FORCE_INLINE __m128i _mm_shufflehi_epi16(__m128i a, +// __constrange(0,255) int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shufflehi_epi16(a, imm) \ + __extension__({ \ + int16x8_t _input = vreinterpretq_s16_m128i(a); \ + int16x8_t _shuf = __builtin_shufflevector( \ + _input, _input, 0, 1, 2, 3, ((imm) & (0x3)) + 4, \ + (((imm) >> 2) & 0x3) + 4, (((imm) >> 4) & 0x3) + 4, \ + (((imm) >> 6) & 0x3) + 4); \ + vreinterpretq_m128i_s16(_shuf); \ + }) +#else // generic +#define _mm_shufflehi_epi16(a, imm) _mm_shufflehi_epi16_function((a), (imm)) +#endif + +// Blend packed 16-bit integers from a and b using control mask imm8, and store +// the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF imm8[j] +// dst[i+15:i] := b[i+15:i] +// ELSE +// dst[i+15:i] := a[i+15:i] +// FI +// ENDFOR +// FORCE_INLINE __m128i _mm_blend_epi16(__m128i a, __m128i b, +// __constrange(0,255) int imm) +#define _mm_blend_epi16(a, b, imm) \ + __extension__({ \ + const uint16_t _mask[8] = {((imm) & (1 << 0)) ? 0xFFFF : 0x0000, \ + ((imm) & (1 << 1)) ? 0xFFFF : 0x0000, \ + ((imm) & (1 << 2)) ? 0xFFFF : 0x0000, \ + ((imm) & (1 << 3)) ? 0xFFFF : 0x0000, \ + ((imm) & (1 << 4)) ? 0xFFFF : 0x0000, \ + ((imm) & (1 << 5)) ? 0xFFFF : 0x0000, \ + ((imm) & (1 << 6)) ? 0xFFFF : 0x0000, \ + ((imm) & (1 << 7)) ? 0xFFFF : 0x0000}; \ + uint16x8_t _mask_vec = vld1q_u16(_mask); \ + uint16x8_t _a = vreinterpretq_u16_m128i(a); \ + uint16x8_t _b = vreinterpretq_u16_m128i(b); \ + vreinterpretq_m128i_u16(vbslq_u16(_mask_vec, _b, _a)); \ + }) + +// Blend packed 8-bit integers from a and b using mask, and store the results in +// dst. +// +// FOR j := 0 to 15 +// i := j*8 +// IF mask[i+7] +// dst[i+7:i] := b[i+7:i] +// ELSE +// dst[i+7:i] := a[i+7:i] +// FI +// ENDFOR +FORCE_INLINE __m128i _mm_blendv_epi8(__m128i _a, __m128i _b, __m128i _mask) +{ + // Use a signed shift right to create a mask with the sign bit + uint8x16_t mask = + vreinterpretq_u8_s8(vshrq_n_s8(vreinterpretq_s8_m128i(_mask), 7)); + uint8x16_t a = vreinterpretq_u8_m128i(_a); + uint8x16_t b = vreinterpretq_u8_m128i(_b); + return vreinterpretq_m128i_u8(vbslq_u8(mask, b, a)); +} + +/* Shifts */ + + +// Shift packed 16-bit integers in a right by imm while shifting in sign +// bits, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi16 +FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm) +{ + const int count = (imm & ~15) ? 15 : imm; + return (__m128i) vshlq_s16((int16x8_t) a, vdupq_n_s16(-count)); +} + +// Shifts the 8 signed or unsigned 16-bit integers in a left by count bits while +// shifting in zeros. +// +// r0 := a0 << count +// r1 := a1 << count +// ... +// r7 := a7 << count +// +// https://msdn.microsoft.com/en-us/library/es73bcsy(v=vs.90).aspx +#define _mm_slli_epi16(a, imm) \ + __extension__({ \ + __m128i ret; \ + if ((imm) <= 0) { \ + ret = a; \ + } else if ((imm) > 15) { \ + ret = _mm_setzero_si128(); \ + } else { \ + ret = vreinterpretq_m128i_s16( \ + vshlq_n_s16(vreinterpretq_s16_m128i(a), (imm))); \ + } \ + ret; \ + }) + +// Shifts the 4 signed or unsigned 32-bit integers in a left by count bits while +// shifting in zeros. : +// https://msdn.microsoft.com/en-us/library/z2k3bbtb%28v=vs.90%29.aspx +// FORCE_INLINE __m128i _mm_slli_epi32(__m128i a, __constrange(0,255) int imm) +FORCE_INLINE __m128i _mm_slli_epi32(__m128i a, int imm) +{ + if (imm <= 0) /* TODO: add constant range macro: [0, 255] */ + return a; + if (imm > 31) /* TODO: add unlikely macro */ + return _mm_setzero_si128(); + return vreinterpretq_m128i_s32( + vshlq_s32(vreinterpretq_s32_m128i(a), vdupq_n_s32(imm))); +} + +// Shift packed 64-bit integers in a left by imm8 while shifting in zeros, and +// store the results in dst. +FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm) +{ + if (imm <= 0) /* TODO: add constant range macro: [0, 255] */ + return a; + if (imm > 63) /* TODO: add unlikely macro */ + return _mm_setzero_si128(); + return vreinterpretq_m128i_s64( + vshlq_s64(vreinterpretq_s64_m128i(a), vdupq_n_s64(imm))); +} + +// Shift packed 16-bit integers in a right by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF imm8[7:0] > 15 +// dst[i+15:i] := 0 +// ELSE +// dst[i+15:i] := ZeroExtend16(a[i+15:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi16 +#define _mm_srli_epi16(a, imm) \ + __extension__({ \ + __m128i ret; \ + if ((imm) == 0) { \ + ret = a; \ + } else if (0 < (imm) && (imm) < 16) { \ + ret = vreinterpretq_m128i_u16( \ + vshlq_u16(vreinterpretq_u16_m128i(a), vdupq_n_s16(-imm))); \ + } else { \ + ret = _mm_setzero_si128(); \ + } \ + ret; \ + }) + +// Shift packed 32-bit integers in a right by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF imm8[7:0] > 31 +// dst[i+31:i] := 0 +// ELSE +// dst[i+31:i] := ZeroExtend32(a[i+31:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi32 +// FORCE_INLINE __m128i _mm_srli_epi32(__m128i a, __constrange(0,255) int imm) +#define _mm_srli_epi32(a, imm) \ + __extension__({ \ + __m128i ret; \ + if ((imm) == 0) { \ + ret = a; \ + } else if (0 < (imm) && (imm) < 32) { \ + ret = vreinterpretq_m128i_u32( \ + vshlq_u32(vreinterpretq_u32_m128i(a), vdupq_n_s32(-imm))); \ + } else { \ + ret = _mm_setzero_si128(); \ + } \ + ret; \ + }) + +// Shift packed 64-bit integers in a right by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// IF imm8[7:0] > 63 +// dst[i+63:i] := 0 +// ELSE +// dst[i+63:i] := ZeroExtend64(a[i+63:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi64 +#define _mm_srli_epi64(a, imm) \ + __extension__({ \ + __m128i ret; \ + if ((imm) == 0) { \ + ret = a; \ + } else if (0 < (imm) && (imm) < 64) { \ + ret = vreinterpretq_m128i_u64( \ + vshlq_u64(vreinterpretq_u64_m128i(a), vdupq_n_s64(-imm))); \ + } else { \ + ret = _mm_setzero_si128(); \ + } \ + ret; \ + }) + +// Shift packed 32-bit integers in a right by imm8 while shifting in sign bits, +// and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF imm8[7:0] > 31 +// dst[i+31:i] := (a[i+31] ? 0xFFFFFFFF : 0x0) +// ELSE +// dst[i+31:i] := SignExtend32(a[i+31:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi32 +// FORCE_INLINE __m128i _mm_srai_epi32(__m128i a, __constrange(0,255) int imm) +#define _mm_srai_epi32(a, imm) \ + __extension__({ \ + __m128i ret; \ + if ((imm) == 0) { \ + ret = a; \ + } else if (0 < (imm) && (imm) < 32) { \ + ret = vreinterpretq_m128i_s32( \ + vshlq_s32(vreinterpretq_s32_m128i(a), vdupq_n_s32(-imm))); \ + } else { \ + ret = vreinterpretq_m128i_s32( \ + vshrq_n_s32(vreinterpretq_s32_m128i(a), 31)); \ + } \ + ret; \ + }) + +// Shifts the 128 - bit value in a right by imm bytes while shifting in +// zeros.imm must be an immediate. +// +// r := srl(a, imm*8) +// +// https://msdn.microsoft.com/en-us/library/305w28yz(v=vs.100).aspx +// FORCE_INLINE _mm_srli_si128(__m128i a, __constrange(0,255) int imm) +#define _mm_srli_si128(a, imm) \ + __extension__({ \ + __m128i ret; \ + if ((imm) <= 0) { \ + ret = a; \ + } else if ((imm) > 15) { \ + ret = _mm_setzero_si128(); \ + } else { \ + ret = vreinterpretq_m128i_s8( \ + vextq_s8(vreinterpretq_s8_m128i(a), vdupq_n_s8(0), (imm))); \ + } \ + ret; \ + }) + +// Shifts the 128-bit value in a left by imm bytes while shifting in zeros. imm +// must be an immediate. +// +// r := a << (imm * 8) +// +// https://msdn.microsoft.com/en-us/library/34d3k2kt(v=vs.100).aspx +// FORCE_INLINE __m128i _mm_slli_si128(__m128i a, __constrange(0,255) int imm) +#define _mm_slli_si128(a, imm) \ + __extension__({ \ + __m128i ret; \ + if ((imm) <= 0) { \ + ret = a; \ + } else if ((imm) > 15) { \ + ret = _mm_setzero_si128(); \ + } else { \ + ret = vreinterpretq_m128i_s8(vextq_s8( \ + vdupq_n_s8(0), vreinterpretq_s8_m128i(a), 16 - (imm))); \ + } \ + ret; \ + }) + +// Shifts the 8 signed or unsigned 16-bit integers in a left by count bits while +// shifting in zeros. +// +// r0 := a0 << count +// r1 := a1 << count +// ... +// r7 := a7 << count +// +// https://msdn.microsoft.com/en-us/library/c79w388h(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_sll_epi16(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (c > 15) + return _mm_setzero_si128(); + + int16x8_t vc = vdupq_n_s16((int16_t) c); + return vreinterpretq_m128i_s16(vshlq_s16(vreinterpretq_s16_m128i(a), vc)); +} + +// Shifts the 4 signed or unsigned 32-bit integers in a left by count bits while +// shifting in zeros. +// +// r0 := a0 << count +// r1 := a1 << count +// r2 := a2 << count +// r3 := a3 << count +// +// https://msdn.microsoft.com/en-us/library/6fe5a6s9(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_sll_epi32(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (c > 31) + return _mm_setzero_si128(); + + int32x4_t vc = vdupq_n_s32((int32_t) c); + return vreinterpretq_m128i_s32(vshlq_s32(vreinterpretq_s32_m128i(a), vc)); +} + +// Shifts the 2 signed or unsigned 64-bit integers in a left by count bits while +// shifting in zeros. +// +// r0 := a0 << count +// r1 := a1 << count +// +// https://msdn.microsoft.com/en-us/library/6ta9dffd(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_sll_epi64(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (c > 63) + return _mm_setzero_si128(); + + int64x2_t vc = vdupq_n_s64((int64_t) c); + return vreinterpretq_m128i_s64(vshlq_s64(vreinterpretq_s64_m128i(a), vc)); +} + +// Shifts the 8 signed or unsigned 16-bit integers in a right by count bits +// while shifting in zeros. +// +// r0 := srl(a0, count) +// r1 := srl(a1, count) +// ... +// r7 := srl(a7, count) +// +// https://msdn.microsoft.com/en-us/library/wd5ax830(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_srl_epi16(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (c > 15) + return _mm_setzero_si128(); + + int16x8_t vc = vdupq_n_s16(-(int16_t) c); + return vreinterpretq_m128i_u16(vshlq_u16(vreinterpretq_u16_m128i(a), vc)); +} + +// Shifts the 4 signed or unsigned 32-bit integers in a right by count bits +// while shifting in zeros. +// +// r0 := srl(a0, count) +// r1 := srl(a1, count) +// r2 := srl(a2, count) +// r3 := srl(a3, count) +// +// https://msdn.microsoft.com/en-us/library/a9cbttf4(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_srl_epi32(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (c > 31) + return _mm_setzero_si128(); + + int32x4_t vc = vdupq_n_s32(-(int32_t) c); + return vreinterpretq_m128i_u32(vshlq_u32(vreinterpretq_u32_m128i(a), vc)); +} + +// Shifts the 2 signed or unsigned 64-bit integers in a right by count bits +// while shifting in zeros. +// +// r0 := srl(a0, count) +// r1 := srl(a1, count) +// +// https://msdn.microsoft.com/en-us/library/yf6cf9k8(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (c > 63) + return _mm_setzero_si128(); + + int64x2_t vc = vdupq_n_s64(-(int64_t) c); + return vreinterpretq_m128i_u64(vshlq_u64(vreinterpretq_u64_m128i(a), vc)); +} + +// NEON does not provide a version of this function. +// Creates a 16-bit mask from the most significant bits of the 16 signed or +// unsigned 8-bit integers in a and zero extends the upper bits. +// https://msdn.microsoft.com/en-us/library/vstudio/s090c8fk(v=vs.100).aspx +FORCE_INLINE int _mm_movemask_epi8(__m128i a) +{ +#if defined(__aarch64__) + uint8x16_t input = vreinterpretq_u8_m128i(a); + const int8_t ALIGN_STRUCT(16) + xr[16] = {-7, -6, -5, -4, -3, -2, -1, 0, -7, -6, -5, -4, -3, -2, -1, 0}; + const uint8x16_t mask_and = vdupq_n_u8(0x80); + const int8x16_t mask_shift = vld1q_s8(xr); + const uint8x16_t mask_result = + vshlq_u8(vandq_u8(input, mask_and), mask_shift); + uint8x8_t lo = vget_low_u8(mask_result); + uint8x8_t hi = vget_high_u8(mask_result); + + return vaddv_u8(lo) + (vaddv_u8(hi) << 8); +#else + // Use increasingly wide shifts+adds to collect the sign bits + // together. + // Since the widening shifts would be rather confusing to follow in little + // endian, everything will be illustrated in big endian order instead. This + // has a different result - the bits would actually be reversed on a big + // endian machine. + + // Starting input (only half the elements are shown): + // 89 ff 1d c0 00 10 99 33 + uint8x16_t input = vreinterpretq_u8_m128i(a); + + // Shift out everything but the sign bits with an unsigned shift right. + // + // Bytes of the vector:: + // 89 ff 1d c0 00 10 99 33 + // \ \ \ \ \ \ \ \ high_bits = (uint16x4_t)(input >> 7) + // | | | | | | | | + // 01 01 00 01 00 00 01 00 + // + // Bits of first important lane(s): + // 10001001 (89) + // \______ + // | + // 00000001 (01) + uint16x8_t high_bits = vreinterpretq_u16_u8(vshrq_n_u8(input, 7)); + + // Merge the even lanes together with a 16-bit unsigned shift right + add. + // 'xx' represents garbage data which will be ignored in the final result. + // In the important bytes, the add functions like a binary OR. + // + // 01 01 00 01 00 00 01 00 + // \_ | \_ | \_ | \_ | paired16 = (uint32x4_t)(input + (input >> 7)) + // \| \| \| \| + // xx 03 xx 01 xx 00 xx 02 + // + // 00000001 00000001 (01 01) + // \_______ | + // \| + // xxxxxxxx xxxxxx11 (xx 03) + uint32x4_t paired16 = + vreinterpretq_u32_u16(vsraq_n_u16(high_bits, high_bits, 7)); + + // Repeat with a wider 32-bit shift + add. + // xx 03 xx 01 xx 00 xx 02 + // \____ | \____ | paired32 = (uint64x1_t)(paired16 + (paired16 >> + // 14)) + // \| \| + // xx xx xx 0d xx xx xx 02 + // + // 00000011 00000001 (03 01) + // \\_____ || + // '----.\|| + // xxxxxxxx xxxx1101 (xx 0d) + uint64x2_t paired32 = + vreinterpretq_u64_u32(vsraq_n_u32(paired16, paired16, 14)); + + // Last, an even wider 64-bit shift + add to get our result in the low 8 bit + // lanes. xx xx xx 0d xx xx xx 02 + // \_________ | paired64 = (uint8x8_t)(paired32 + (paired32 >> + // 28)) + // \| + // xx xx xx xx xx xx xx d2 + // + // 00001101 00000010 (0d 02) + // \ \___ | | + // '---. \| | + // xxxxxxxx 11010010 (xx d2) + uint8x16_t paired64 = + vreinterpretq_u8_u64(vsraq_n_u64(paired32, paired32, 28)); + + // Extract the low 8 bits from each 64-bit lane with 2 8-bit extracts. + // xx xx xx xx xx xx xx d2 + // || return paired64[0] + // d2 + // Note: Little endian would return the correct value 4b (01001011) instead. + return vgetq_lane_u8(paired64, 0) | ((int) vgetq_lane_u8(paired64, 8) << 8); +#endif +} + +// Copy the lower 64-bit integer in a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movepi64_pi64 +FORCE_INLINE __m64 _mm_movepi64_pi64(__m128i a) +{ + return vreinterpret_m64_s64(vget_low_s64(vreinterpretq_s64_m128i(a))); +} + +// Copy the 64-bit integer a to the lower element of dst, and zero the upper +// element. +// +// dst[63:0] := a[63:0] +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movpi64_epi64 +FORCE_INLINE __m128i _mm_movpi64_epi64(__m64 a) +{ + return vreinterpretq_m128i_s64( + vcombine_s64(vreinterpret_s64_m64(a), vdup_n_s64(0))); +} + +// NEON does not provide this method +// Creates a 4-bit mask from the most significant bits of the four +// single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/4490ys29(v=vs.100).aspx +FORCE_INLINE int _mm_movemask_ps(__m128 a) +{ + uint32x4_t input = vreinterpretq_u32_m128(a); +#if defined(__aarch64__) + static const int32x4_t shift = {0, 1, 2, 3}; + uint32x4_t tmp = vshrq_n_u32(input, 31); + return vaddvq_u32(vshlq_u32(tmp, shift)); +#else + // Uses the exact same method as _mm_movemask_epi8, see that for details. + // Shift out everything but the sign bits with a 32-bit unsigned shift + // right. + uint64x2_t high_bits = vreinterpretq_u64_u32(vshrq_n_u32(input, 31)); + // Merge the two pairs together with a 64-bit unsigned shift right + add. + uint8x16_t paired = + vreinterpretq_u8_u64(vsraq_n_u64(high_bits, high_bits, 31)); + // Extract the result. + return vgetq_lane_u8(paired, 0) | (vgetq_lane_u8(paired, 8) << 2); +#endif +} + +// Compute the bitwise NOT of a and then AND with a 128-bit vector containing +// all 1's, and return 1 if the result is zero, otherwise return 0. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_ones +FORCE_INLINE int _mm_test_all_ones(__m128i a) +{ + return (uint64_t)(vgetq_lane_s64(a, 0) & vgetq_lane_s64(a, 1)) == + ~(uint64_t) 0; +} + +// Compute the bitwise AND of 128 bits (representing integer data) in a and +// mask, and return 1 if the result is zero, otherwise return 0. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_zeros +FORCE_INLINE int _mm_test_all_zeros(__m128i a, __m128i mask) +{ + int64x2_t a_and_mask = + vandq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(mask)); + return (vgetq_lane_s64(a_and_mask, 0) | vgetq_lane_s64(a_and_mask, 1)) ? 0 + : 1; +} + +/* Math operations */ + +// Subtracts the four single-precision, floating-point values of a and b. +// +// r0 := a0 - b0 +// r1 := a1 - b1 +// r2 := a2 - b2 +// r3 := a3 - b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/1zad2k61(v=vs.100).aspx +FORCE_INLINE __m128 _mm_sub_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vsubq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Subtract the lower single-precision (32-bit) floating-point element in b from +// the lower single-precision (32-bit) floating-point element in a, store the +// result in the lower element of dst, and copy the upper 3 packed elements from +// a to the upper elements of dst. +// +// dst[31:0] := a[31:0] - b[31:0] +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_ss +FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_sub_ps(a, b)); +} + +// Subtract 2 packed 64-bit integers in b from 2 packed 64-bit integers in a, +// and store the results in dst. +// r0 := a0 - b0 +// r1 := a1 - b1 +FORCE_INLINE __m128i _mm_sub_epi64(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s64( + vsubq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); +} + +// Subtracts the 4 signed or unsigned 32-bit integers of b from the 4 signed or +// unsigned 32-bit integers of a. +// +// r0 := a0 - b0 +// r1 := a1 - b1 +// r2 := a2 - b2 +// r3 := a3 - b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/fhh866h0(v=vs.100).aspx +FORCE_INLINE __m128i _mm_sub_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vsubq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +FORCE_INLINE __m128i _mm_sub_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +FORCE_INLINE __m128i _mm_sub_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vsubq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Subtract 64-bit integer b from 64-bit integer a, and store the result in dst. +// +// dst[63:0] := a[63:0] - b[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_si64 +FORCE_INLINE __m64 _mm_sub_si64(__m64 a, __m64 b) +{ + return vreinterpret_m64_s64( + vsub_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b))); +} + +// Subtracts the 8 unsigned 16-bit integers of bfrom the 8 unsigned 16-bit +// integers of a and saturates.. +// https://technet.microsoft.com/en-us/subscriptions/index/f44y0s19(v=vs.90).aspx +FORCE_INLINE __m128i _mm_subs_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vqsubq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Subtracts the 16 unsigned 8-bit integers of b from the 16 unsigned 8-bit +// integers of a and saturates. +// +// r0 := UnsignedSaturate(a0 - b0) +// r1 := UnsignedSaturate(a1 - b1) +// ... +// r15 := UnsignedSaturate(a15 - b15) +// +// https://technet.microsoft.com/en-us/subscriptions/yadkxc18(v=vs.90) +FORCE_INLINE __m128i _mm_subs_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vqsubq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Subtracts the 16 signed 8-bit integers of b from the 16 signed 8-bit integers +// of a and saturates. +// +// r0 := SignedSaturate(a0 - b0) +// r1 := SignedSaturate(a1 - b1) +// ... +// r15 := SignedSaturate(a15 - b15) +// +// https://technet.microsoft.com/en-us/subscriptions/by7kzks1(v=vs.90) +FORCE_INLINE __m128i _mm_subs_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vqsubq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Subtracts the 8 signed 16-bit integers of b from the 8 signed 16-bit integers +// of a and saturates. +// +// r0 := SignedSaturate(a0 - b0) +// r1 := SignedSaturate(a1 - b1) +// ... +// r7 := SignedSaturate(a7 - b7) +// +// https://technet.microsoft.com/en-us/subscriptions/3247z5b8(v=vs.90) +FORCE_INLINE __m128i _mm_subs_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vqsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +FORCE_INLINE __m128i _mm_adds_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vqaddq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Negate packed 8-bit integers in a when the corresponding signed +// 8-bit integer in b is negative, and store the results in dst. +// Element in dst are zeroed out when the corresponding element +// in b is zero. +// +// for i in 0..15 +// if b[i] < 0 +// r[i] := -a[i] +// else if b[i] == 0 +// r[i] := 0 +// else +// r[i] := a[i] +// fi +// done +FORCE_INLINE __m128i _mm_sign_epi8(__m128i _a, __m128i _b) +{ + int8x16_t a = vreinterpretq_s8_m128i(_a); + int8x16_t b = vreinterpretq_s8_m128i(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFF : 0 + uint8x16_t ltMask = vreinterpretq_u8_s8(vshrq_n_s8(b, 7)); + + // (b == 0) ? 0xFF : 0 +#if defined(__aarch64__) + int8x16_t zeroMask = vreinterpretq_s8_u8(vceqzq_s8(b)); +#else + int8x16_t zeroMask = vreinterpretq_s8_u8(vceqq_s8(b, vdupq_n_s8(0))); +#endif + + // bitwise select either a or nagative 'a' (vnegq_s8(a) return nagative 'a') + // based on ltMask + int8x16_t masked = vbslq_s8(ltMask, vnegq_s8(a), a); + // res = masked & (~zeroMask) + int8x16_t res = vbicq_s8(masked, zeroMask); + + return vreinterpretq_m128i_s8(res); +} + +// Negate packed 16-bit integers in a when the corresponding signed +// 16-bit integer in b is negative, and store the results in dst. +// Element in dst are zeroed out when the corresponding element +// in b is zero. +// +// for i in 0..7 +// if b[i] < 0 +// r[i] := -a[i] +// else if b[i] == 0 +// r[i] := 0 +// else +// r[i] := a[i] +// fi +// done +FORCE_INLINE __m128i _mm_sign_epi16(__m128i _a, __m128i _b) +{ + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFF : 0 + uint16x8_t ltMask = vreinterpretq_u16_s16(vshrq_n_s16(b, 15)); + // (b == 0) ? 0xFFFF : 0 +#if defined(__aarch64__) + int16x8_t zeroMask = vreinterpretq_s16_u16(vceqzq_s16(b)); +#else + int16x8_t zeroMask = vreinterpretq_s16_u16(vceqq_s16(b, vdupq_n_s16(0))); +#endif + + // bitwise select either a or negative 'a' (vnegq_s16(a) equals to negative + // 'a') based on ltMask + int16x8_t masked = vbslq_s16(ltMask, vnegq_s16(a), a); + // res = masked & (~zeroMask) + int16x8_t res = vbicq_s16(masked, zeroMask); + return vreinterpretq_m128i_s16(res); +} + +// Negate packed 32-bit integers in a when the corresponding signed +// 32-bit integer in b is negative, and store the results in dst. +// Element in dst are zeroed out when the corresponding element +// in b is zero. +// +// for i in 0..3 +// if b[i] < 0 +// r[i] := -a[i] +// else if b[i] == 0 +// r[i] := 0 +// else +// r[i] := a[i] +// fi +// done +FORCE_INLINE __m128i _mm_sign_epi32(__m128i _a, __m128i _b) +{ + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFFFFFF : 0 + uint32x4_t ltMask = vreinterpretq_u32_s32(vshrq_n_s32(b, 31)); + + // (b == 0) ? 0xFFFFFFFF : 0 +#if defined(__aarch64__) + int32x4_t zeroMask = vreinterpretq_s32_u32(vceqzq_s32(b)); +#else + int32x4_t zeroMask = vreinterpretq_s32_u32(vceqq_s32(b, vdupq_n_s32(0))); +#endif + + // bitwise select either a or negative 'a' (vnegq_s32(a) equals to negative + // 'a') based on ltMask + int32x4_t masked = vbslq_s32(ltMask, vnegq_s32(a), a); + // res = masked & (~zeroMask) + int32x4_t res = vbicq_s32(masked, zeroMask); + return vreinterpretq_m128i_s32(res); +} + +// Negate packed 16-bit integers in a when the corresponding signed 16-bit +// integer in b is negative, and store the results in dst. Element in dst are +// zeroed out when the corresponding element in b is zero. +// +// FOR j := 0 to 3 +// i := j*16 +// IF b[i+15:i] < 0 +// dst[i+15:i] := -(a[i+15:i]) +// ELSE IF b[i+15:i] == 0 +// dst[i+15:i] := 0 +// ELSE +// dst[i+15:i] := a[i+15:i] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi16 +FORCE_INLINE __m64 _mm_sign_pi16(__m64 _a, __m64 _b) +{ + int16x4_t a = vreinterpret_s16_m64(_a); + int16x4_t b = vreinterpret_s16_m64(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFF : 0 + uint16x4_t ltMask = vreinterpret_u16_s16(vshr_n_s16(b, 15)); + + // (b == 0) ? 0xFFFF : 0 +#if defined(__aarch64__) + int16x4_t zeroMask = vreinterpret_s16_u16(vceqz_s16(b)); +#else + int16x4_t zeroMask = vreinterpret_s16_u16(vceq_s16(b, vdup_n_s16(0))); +#endif + + // bitwise select either a or nagative 'a' (vneg_s16(a) return nagative 'a') + // based on ltMask + int16x4_t masked = vbsl_s16(ltMask, vneg_s16(a), a); + // res = masked & (~zeroMask) + int16x4_t res = vbic_s16(masked, zeroMask); + + return vreinterpret_m64_s16(res); +} + +// Negate packed 32-bit integers in a when the corresponding signed 32-bit +// integer in b is negative, and store the results in dst. Element in dst are +// zeroed out when the corresponding element in b is zero. +// +// FOR j := 0 to 1 +// i := j*32 +// IF b[i+31:i] < 0 +// dst[i+31:i] := -(a[i+31:i]) +// ELSE IF b[i+31:i] == 0 +// dst[i+31:i] := 0 +// ELSE +// dst[i+31:i] := a[i+31:i] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi32 +FORCE_INLINE __m64 _mm_sign_pi32(__m64 _a, __m64 _b) +{ + int32x2_t a = vreinterpret_s32_m64(_a); + int32x2_t b = vreinterpret_s32_m64(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFFFFFF : 0 + uint32x2_t ltMask = vreinterpret_u32_s32(vshr_n_s32(b, 31)); + + // (b == 0) ? 0xFFFFFFFF : 0 +#if defined(__aarch64__) + int32x2_t zeroMask = vreinterpret_s32_u32(vceqz_s32(b)); +#else + int32x2_t zeroMask = vreinterpret_s32_u32(vceq_s32(b, vdup_n_s32(0))); +#endif + + // bitwise select either a or nagative 'a' (vneg_s32(a) return nagative 'a') + // based on ltMask + int32x2_t masked = vbsl_s32(ltMask, vneg_s32(a), a); + // res = masked & (~zeroMask) + int32x2_t res = vbic_s32(masked, zeroMask); + + return vreinterpret_m64_s32(res); +} + +// Negate packed 8-bit integers in a when the corresponding signed 8-bit integer +// in b is negative, and store the results in dst. Element in dst are zeroed out +// when the corresponding element in b is zero. +// +// FOR j := 0 to 7 +// i := j*8 +// IF b[i+7:i] < 0 +// dst[i+7:i] := -(a[i+7:i]) +// ELSE IF b[i+7:i] == 0 +// dst[i+7:i] := 0 +// ELSE +// dst[i+7:i] := a[i+7:i] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi8 +FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b) +{ + int8x8_t a = vreinterpret_s8_m64(_a); + int8x8_t b = vreinterpret_s8_m64(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFF : 0 + uint8x8_t ltMask = vreinterpret_u8_s8(vshr_n_s8(b, 7)); + + // (b == 0) ? 0xFF : 0 +#if defined(__aarch64__) + int8x8_t zeroMask = vreinterpret_s8_u8(vceqz_s8(b)); +#else + int8x8_t zeroMask = vreinterpret_s8_u8(vceq_s8(b, vdup_n_s8(0))); +#endif + + // bitwise select either a or nagative 'a' (vneg_s8(a) return nagative 'a') + // based on ltMask + int8x8_t masked = vbsl_s8(ltMask, vneg_s8(a), a); + // res = masked & (~zeroMask) + int8x8_t res = vbic_s8(masked, zeroMask); + + return vreinterpret_m64_s8(res); +} + +// Average packed unsigned 16-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu16 +FORCE_INLINE __m64 _mm_avg_pu16(__m64 a, __m64 b) +{ + return vreinterpret_m64_u16( + vrhadd_u16(vreinterpret_u16_m64(a), vreinterpret_u16_m64(b))); +} + +// Average packed unsigned 8-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu8 +FORCE_INLINE __m64 _mm_avg_pu8(__m64 a, __m64 b) +{ + return vreinterpret_m64_u8( + vrhadd_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); +} + +// Average packed unsigned 8-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgb +#define _m_pavgb(a, b) _mm_avg_pu8(a, b) + +// Average packed unsigned 16-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgw +#define _m_pavgw(a, b) _mm_avg_pu16(a, b) + +// Computes the average of the 16 unsigned 8-bit integers in a and the 16 +// unsigned 8-bit integers in b and rounds. +// +// r0 := (a0 + b0) / 2 +// r1 := (a1 + b1) / 2 +// ... +// r15 := (a15 + b15) / 2 +// +// https://msdn.microsoft.com/en-us/library/vstudio/8zwh554a(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_avg_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vrhaddq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Computes the average of the 8 unsigned 16-bit integers in a and the 8 +// unsigned 16-bit integers in b and rounds. +// +// r0 := (a0 + b0) / 2 +// r1 := (a1 + b1) / 2 +// ... +// r7 := (a7 + b7) / 2 +// +// https://msdn.microsoft.com/en-us/library/vstudio/y13ca3c8(v=vs.90).aspx +FORCE_INLINE __m128i _mm_avg_epu16(__m128i a, __m128i b) +{ + return (__m128i) vrhaddq_u16(vreinterpretq_u16_m128i(a), + vreinterpretq_u16_m128i(b)); +} + +// Adds the four single-precision, floating-point values of a and b. +// +// r0 := a0 + b0 +// r1 := a1 + b1 +// r2 := a2 + b2 +// r3 := a3 + b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/c9848chc(v=vs.100).aspx +FORCE_INLINE __m128 _mm_add_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vaddq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Add packed double-precision (64-bit) floating-point elements in a and b, and +// store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_pd +FORCE_INLINE __m128d _mm_add_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vaddq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2]; + c[0] = da[0] + db[0]; + c[1] = da[1] + db[1]; + return vld1q_f32((float32_t *) c); +#endif +} + +// Add 64-bit integers a and b, and store the result in dst. +// +// dst[63:0] := a[63:0] + b[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_si64 +FORCE_INLINE __m64 _mm_add_si64(__m64 a, __m64 b) +{ + return vreinterpret_m64_s64( + vadd_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b))); +} + +// adds the scalar single-precision floating point values of a and b. +// https://msdn.microsoft.com/en-us/library/be94x2y6(v=vs.100).aspx +FORCE_INLINE __m128 _mm_add_ss(__m128 a, __m128 b) +{ + float32_t b0 = vgetq_lane_f32(vreinterpretq_f32_m128(b), 0); + float32x4_t value = vsetq_lane_f32(b0, vdupq_n_f32(0), 0); + // the upper values in the result must be the remnants of . + return vreinterpretq_m128_f32(vaddq_f32(a, value)); +} + +// Adds the 4 signed or unsigned 64-bit integers in a to the 4 signed or +// unsigned 32-bit integers in b. +// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx +FORCE_INLINE __m128i _mm_add_epi64(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s64( + vaddq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); +} + +// Adds the 4 signed or unsigned 32-bit integers in a to the 4 signed or +// unsigned 32-bit integers in b. +// +// r0 := a0 + b0 +// r1 := a1 + b1 +// r2 := a2 + b2 +// r3 := a3 + b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx +FORCE_INLINE __m128i _mm_add_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vaddq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Adds the 8 signed or unsigned 16-bit integers in a to the 8 signed or +// unsigned 16-bit integers in b. +// https://msdn.microsoft.com/en-us/library/fceha5k4(v=vs.100).aspx +FORCE_INLINE __m128i _mm_add_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vaddq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Adds the 16 signed or unsigned 8-bit integers in a to the 16 signed or +// unsigned 8-bit integers in b. +// https://technet.microsoft.com/en-us/subscriptions/yc7tcyzs(v=vs.90) +FORCE_INLINE __m128i _mm_add_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vaddq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Adds the 8 signed 16-bit integers in a to the 8 signed 16-bit integers in b +// and saturates. +// +// r0 := SignedSaturate(a0 + b0) +// r1 := SignedSaturate(a1 + b1) +// ... +// r7 := SignedSaturate(a7 + b7) +// +// https://msdn.microsoft.com/en-us/library/1a306ef8(v=vs.100).aspx +FORCE_INLINE __m128i _mm_adds_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vqaddq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Add packed signed 8-bit integers in a and b using saturation, and store the +// results in dst. +// +// FOR j := 0 to 15 +// i := j*8 +// dst[i+7:i] := Saturate8( a[i+7:i] + b[i+7:i] ) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_adds_epi8 +FORCE_INLINE __m128i _mm_adds_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vqaddq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Adds the 16 unsigned 8-bit integers in a to the 16 unsigned 8-bit integers in +// b and saturates.. +// https://msdn.microsoft.com/en-us/library/9hahyddy(v=vs.100).aspx +FORCE_INLINE __m128i _mm_adds_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vqaddq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Multiplies the 8 signed or unsigned 16-bit integers from a by the 8 signed or +// unsigned 16-bit integers from b. +// +// r0 := (a0 * b0)[15:0] +// r1 := (a1 * b1)[15:0] +// ... +// r7 := (a7 * b7)[15:0] +// +// https://msdn.microsoft.com/en-us/library/vstudio/9ks1472s(v=vs.100).aspx +FORCE_INLINE __m128i _mm_mullo_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vmulq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Multiplies the 4 signed or unsigned 32-bit integers from a by the 4 signed or +// unsigned 32-bit integers from b. +// https://msdn.microsoft.com/en-us/library/vstudio/bb531409(v=vs.100).aspx +FORCE_INLINE __m128i _mm_mullo_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vmulq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Multiply the packed unsigned 16-bit integers in a and b, producing +// intermediate 32-bit integers, and store the high 16 bits of the intermediate +// integers in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// tmp[31:0] := a[i+15:i] * b[i+15:i] +// dst[i+15:i] := tmp[31:16] +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmulhuw +#define _m_pmulhuw(a, b) _mm_mulhi_pu16(a, b) + +// Multiplies the four single-precision, floating-point values of a and b. +// +// r0 := a0 * b0 +// r1 := a1 * b1 +// r2 := a2 * b2 +// r3 := a3 * b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/22kbk6t9(v=vs.100).aspx +FORCE_INLINE __m128 _mm_mul_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vmulq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Multiply packed double-precision (64-bit) floating-point elements in a and b, +// and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_pd +FORCE_INLINE __m128d _mm_mul_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vmulq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2]; + c[0] = da[0] * db[0]; + c[1] = da[1] * db[1]; + return vld1q_f32((float32_t *) c); +#endif +} + +// Multiply the lower single-precision (32-bit) floating-point element in a and +// b, store the result in the lower element of dst, and copy the upper 3 packed +// elements from a to the upper elements of dst. +// +// dst[31:0] := a[31:0] * b[31:0] +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_ss +FORCE_INLINE __m128 _mm_mul_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_mul_ps(a, b)); +} + +// Multiply the low unsigned 32-bit integers from each packed 64-bit element in +// a and b, and store the unsigned 64-bit results in dst. +// +// r0 := (a0 & 0xFFFFFFFF) * (b0 & 0xFFFFFFFF) +// r1 := (a2 & 0xFFFFFFFF) * (b2 & 0xFFFFFFFF) +FORCE_INLINE __m128i _mm_mul_epu32(__m128i a, __m128i b) +{ + // vmull_u32 upcasts instead of masking, so we downcast. + uint32x2_t a_lo = vmovn_u64(vreinterpretq_u64_m128i(a)); + uint32x2_t b_lo = vmovn_u64(vreinterpretq_u64_m128i(b)); + return vreinterpretq_m128i_u64(vmull_u32(a_lo, b_lo)); +} + +// Multiply the low unsigned 32-bit integers from a and b, and store the +// unsigned 64-bit result in dst. +// +// dst[63:0] := a[31:0] * b[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_su32 +FORCE_INLINE __m64 _mm_mul_su32(__m64 a, __m64 b) +{ + return vreinterpret_m64_u64(vget_low_u64( + vmull_u32(vreinterpret_u32_m64(a), vreinterpret_u32_m64(b)))); +} + +// Multiply the low signed 32-bit integers from each packed 64-bit element in +// a and b, and store the signed 64-bit results in dst. +// +// r0 := (int64_t)(int32_t)a0 * (int64_t)(int32_t)b0 +// r1 := (int64_t)(int32_t)a2 * (int64_t)(int32_t)b2 +FORCE_INLINE __m128i _mm_mul_epi32(__m128i a, __m128i b) +{ + // vmull_s32 upcasts instead of masking, so we downcast. + int32x2_t a_lo = vmovn_s64(vreinterpretq_s64_m128i(a)); + int32x2_t b_lo = vmovn_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vmull_s32(a_lo, b_lo)); +} + +// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit +// integers from b. +// +// r0 := (a0 * b0) + (a1 * b1) +// r1 := (a2 * b2) + (a3 * b3) +// r2 := (a4 * b4) + (a5 * b5) +// r3 := (a6 * b6) + (a7 * b7) +// https://msdn.microsoft.com/en-us/library/yht36sa6(v=vs.90).aspx +FORCE_INLINE __m128i _mm_madd_epi16(__m128i a, __m128i b) +{ + int32x4_t low = vmull_s16(vget_low_s16(vreinterpretq_s16_m128i(a)), + vget_low_s16(vreinterpretq_s16_m128i(b))); + int32x4_t high = vmull_s16(vget_high_s16(vreinterpretq_s16_m128i(a)), + vget_high_s16(vreinterpretq_s16_m128i(b))); + + int32x2_t low_sum = vpadd_s32(vget_low_s32(low), vget_high_s32(low)); + int32x2_t high_sum = vpadd_s32(vget_low_s32(high), vget_high_s32(high)); + + return vreinterpretq_m128i_s32(vcombine_s32(low_sum, high_sum)); +} + +// Multiply packed signed 16-bit integers in a and b, producing intermediate +// signed 32-bit integers. Shift right by 15 bits while rounding up, and store +// the packed 16-bit integers in dst. +// +// r0 := Round(((int32_t)a0 * (int32_t)b0) >> 15) +// r1 := Round(((int32_t)a1 * (int32_t)b1) >> 15) +// r2 := Round(((int32_t)a2 * (int32_t)b2) >> 15) +// ... +// r7 := Round(((int32_t)a7 * (int32_t)b7) >> 15) +FORCE_INLINE __m128i _mm_mulhrs_epi16(__m128i a, __m128i b) +{ + // Has issues due to saturation + // return vreinterpretq_m128i_s16(vqrdmulhq_s16(a, b)); + + // Multiply + int32x4_t mul_lo = vmull_s16(vget_low_s16(vreinterpretq_s16_m128i(a)), + vget_low_s16(vreinterpretq_s16_m128i(b))); + int32x4_t mul_hi = vmull_s16(vget_high_s16(vreinterpretq_s16_m128i(a)), + vget_high_s16(vreinterpretq_s16_m128i(b))); + + // Rounding narrowing shift right + // narrow = (int16_t)((mul + 16384) >> 15); + int16x4_t narrow_lo = vrshrn_n_s32(mul_lo, 15); + int16x4_t narrow_hi = vrshrn_n_s32(mul_hi, 15); + + // Join together + return vreinterpretq_m128i_s16(vcombine_s16(narrow_lo, narrow_hi)); +} + +// Vertically multiply each unsigned 8-bit integer from a with the corresponding +// signed 8-bit integer from b, producing intermediate signed 16-bit integers. +// Horizontally add adjacent pairs of intermediate signed 16-bit integers, +// and pack the saturated results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// dst[i+15:i] := Saturate_To_Int16( a[i+15:i+8]*b[i+15:i+8] + +// a[i+7:i]*b[i+7:i] ) +// ENDFOR +FORCE_INLINE __m128i _mm_maddubs_epi16(__m128i _a, __m128i _b) +{ +#if defined(__aarch64__) + uint8x16_t a = vreinterpretq_u8_m128i(_a); + int8x16_t b = vreinterpretq_s8_m128i(_b); + int16x8_t tl = vmulq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(a))), + vmovl_s8(vget_low_s8(b))); + int16x8_t th = vmulq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(a))), + vmovl_s8(vget_high_s8(b))); + return vreinterpretq_m128i_s16( + vqaddq_s16(vuzp1q_s16(tl, th), vuzp2q_s16(tl, th))); +#else + // This would be much simpler if x86 would choose to zero extend OR sign + // extend, not both. This could probably be optimized better. + uint16x8_t a = vreinterpretq_u16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); + + // Zero extend a + int16x8_t a_odd = vreinterpretq_s16_u16(vshrq_n_u16(a, 8)); + int16x8_t a_even = vreinterpretq_s16_u16(vbicq_u16(a, vdupq_n_u16(0xff00))); + + // Sign extend by shifting left then shifting right. + int16x8_t b_even = vshrq_n_s16(vshlq_n_s16(b, 8), 8); + int16x8_t b_odd = vshrq_n_s16(b, 8); + + // multiply + int16x8_t prod1 = vmulq_s16(a_even, b_even); + int16x8_t prod2 = vmulq_s16(a_odd, b_odd); + + // saturated add + return vreinterpretq_m128i_s16(vqaddq_s16(prod1, prod2)); +#endif +} + +// Computes the fused multiple add product of 32-bit floating point numbers. +// +// Return Value +// Multiplies A and B, and adds C to the temporary result before returning it. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_fmadd +FORCE_INLINE __m128 _mm_fmadd_ps(__m128 a, __m128 b, __m128 c) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32(vfmaq_f32(vreinterpretq_f32_m128(c), + vreinterpretq_f32_m128(b), + vreinterpretq_f32_m128(a))); +#else + return _mm_add_ps(_mm_mul_ps(a, b), c); +#endif +} + +// Alternatively add and subtract packed single-precision (32-bit) +// floating-point elements in a to/from packed elements in b, and store the +// results in dst. +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=addsub_ps +FORCE_INLINE __m128 _mm_addsub_ps(__m128 a, __m128 b) +{ + __m128 mask = {-1.0f, 1.0f, -1.0f, 1.0f}; + return _mm_fmadd_ps(b, mask, a); +} + +// Compute the absolute differences of packed unsigned 8-bit integers in a and +// b, then horizontally sum each consecutive 8 differences to produce two +// unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low +// 16 bits of 64-bit elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_epu8 +FORCE_INLINE __m128i _mm_sad_epu8(__m128i a, __m128i b) +{ + uint16x8_t t = vpaddlq_u8(vabdq_u8((uint8x16_t) a, (uint8x16_t) b)); + uint16_t r0 = t[0] + t[1] + t[2] + t[3]; + uint16_t r4 = t[4] + t[5] + t[6] + t[7]; + uint16x8_t r = vsetq_lane_u16(r0, vdupq_n_u16(0), 0); + return (__m128i) vsetq_lane_u16(r4, r, 4); +} + +// Compute the absolute differences of packed unsigned 8-bit integers in a and +// b, then horizontally sum each consecutive 8 differences to produce four +// unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low +// 16 bits of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_pu8 +FORCE_INLINE __m64 _mm_sad_pu8(__m64 a, __m64 b) +{ + uint16x4_t t = + vpaddl_u8(vabd_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); + uint16_t r0 = t[0] + t[1] + t[2] + t[3]; + return vreinterpret_m64_u16(vset_lane_u16(r0, vdup_n_u16(0), 0)); +} + +// Compute the absolute differences of packed unsigned 8-bit integers in a and +// b, then horizontally sum each consecutive 8 differences to produce four +// unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low +// 16 bits of dst. +// +// FOR j := 0 to 7 +// i := j*8 +// tmp[i+7:i] := ABS(a[i+7:i] - b[i+7:i]) +// ENDFOR +// dst[15:0] := tmp[7:0] + tmp[15:8] + tmp[23:16] + tmp[31:24] + tmp[39:32] + +// tmp[47:40] + tmp[55:48] + tmp[63:56] dst[63:16] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_psadbw +#define _m_psadbw(a, b) _mm_sad_pu8(a, b) + +// Divides the four single-precision, floating-point values of a and b. +// +// r0 := a0 / b0 +// r1 := a1 / b1 +// r2 := a2 / b2 +// r3 := a3 / b3 +// +// https://msdn.microsoft.com/en-us/library/edaw8147(v=vs.100).aspx +FORCE_INLINE __m128 _mm_div_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vdivq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x4_t recip0 = vrecpeq_f32(vreinterpretq_f32_m128(b)); + float32x4_t recip1 = + vmulq_f32(recip0, vrecpsq_f32(recip0, vreinterpretq_f32_m128(b))); + return vreinterpretq_m128_f32(vmulq_f32(vreinterpretq_f32_m128(a), recip1)); +#endif +} + +// Divides the scalar single-precision floating point value of a by b. +// https://msdn.microsoft.com/en-us/library/4y73xa49(v=vs.100).aspx +FORCE_INLINE __m128 _mm_div_ss(__m128 a, __m128 b) +{ + float32_t value = + vgetq_lane_f32(vreinterpretq_f32_m128(_mm_div_ps(a, b)), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0)); +} + +// Compute the approximate reciprocal of packed single-precision (32-bit) +// floating-point elements in a, and store the results in dst. The maximum +// relative error for this approximation is less than 1.5*2^-12. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ps +FORCE_INLINE __m128 _mm_rcp_ps(__m128 in) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vdivq_f32(vdupq_n_f32(1.0f), vreinterpretq_f32_m128(in))); +#else + float32x4_t recip = vrecpeq_f32(vreinterpretq_f32_m128(in)); + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(in))); + return vreinterpretq_m128_f32(recip); +#endif +} + +// Compute the approximate reciprocal of the lower single-precision (32-bit) +// floating-point element in a, store the result in the lower element of dst, +// and copy the upper 3 packed elements from a to the upper elements of dst. The +// maximum relative error for this approximation is less than 1.5*2^-12. +// +// dst[31:0] := (1.0 / a[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ss +FORCE_INLINE __m128 _mm_rcp_ss(__m128 a) +{ + return _mm_move_ss(a, _mm_rcp_ps(a)); +} + +// Computes the approximations of square roots of the four single-precision, +// floating-point values of a. First computes reciprocal square roots and then +// reciprocals of the four values. +// +// r0 := sqrt(a0) +// r1 := sqrt(a1) +// r2 := sqrt(a2) +// r3 := sqrt(a3) +// +// https://msdn.microsoft.com/en-us/library/vstudio/8z67bwwk(v=vs.100).aspx +FORCE_INLINE __m128 _mm_sqrt_ps(__m128 in) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32(vsqrtq_f32(vreinterpretq_f32_m128(in))); +#else + float32x4_t recipsq = vrsqrteq_f32(vreinterpretq_f32_m128(in)); + float32x4_t sq = vrecpeq_f32(recipsq); + // ??? use step versions of both sqrt and recip for better accuracy? + return vreinterpretq_m128_f32(sq); +#endif +} + +// Computes the approximation of the square root of the scalar single-precision +// floating point value of in. +// https://msdn.microsoft.com/en-us/library/ahfsc22d(v=vs.100).aspx +FORCE_INLINE __m128 _mm_sqrt_ss(__m128 in) +{ + float32_t value = + vgetq_lane_f32(vreinterpretq_f32_m128(_mm_sqrt_ps(in)), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(in), 0)); +} + +// Computes the approximations of the reciprocal square roots of the four +// single-precision floating point values of in. +// https://msdn.microsoft.com/en-us/library/22hfsh53(v=vs.100).aspx +FORCE_INLINE __m128 _mm_rsqrt_ps(__m128 in) +{ + return vreinterpretq_m128_f32(vrsqrteq_f32(vreinterpretq_f32_m128(in))); +} + +// Compute the approximate reciprocal square root of the lower single-precision +// (32-bit) floating-point element in a, store the result in the lower element +// of dst, and copy the upper 3 packed elements from a to the upper elements of +// dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rsqrt_ss +FORCE_INLINE __m128 _mm_rsqrt_ss(__m128 in) +{ + return vsetq_lane_f32(vgetq_lane_f32(_mm_rsqrt_ps(in), 0), in, 0); +} + +// Compare packed signed 16-bit integers in a and b, and store packed maximum +// values in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := MAX(a[i+15:i], b[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pi16 +FORCE_INLINE __m64 _mm_max_pi16(__m64 a, __m64 b) +{ + return vreinterpret_m64_s16( + vmax_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); +} + +// Compare packed signed 16-bit integers in a and b, and store packed maximum +// values in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := MAX(a[i+15:i], b[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pi16 +#define _m_pmaxsw(a, b) _mm_max_pi16(a, b) + +// Computes the maximums of the four single-precision, floating-point values of +// a and b. +// https://msdn.microsoft.com/en-us/library/vstudio/ff5d607a(v=vs.100).aspx +FORCE_INLINE __m128 _mm_max_ps(__m128 a, __m128 b) +{ +#if SSE2NEON_PRECISE_MINMAX + float32x4_t _a = vreinterpretq_f32_m128(a); + float32x4_t _b = vreinterpretq_f32_m128(b); + return vbslq_f32(vcltq_f32(_b, _a), _a, _b); +#else + return vreinterpretq_m128_f32( + vmaxq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#endif +} + +// Compare packed unsigned 8-bit integers in a and b, and store packed maximum +// values in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := MAX(a[i+7:i], b[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pu8 +FORCE_INLINE __m64 _mm_max_pu8(__m64 a, __m64 b) +{ + return vreinterpret_m64_u8( + vmax_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); +} + +// Compare packed unsigned 8-bit integers in a and b, and store packed maximum +// values in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := MAX(a[i+7:i], b[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pu8 +#define _m_pmaxub(a, b) _mm_max_pu8(a, b) + +// Compare packed signed 16-bit integers in a and b, and store packed minimum +// values in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := MIN(a[i+15:i], b[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pi16 +FORCE_INLINE __m64 _mm_min_pi16(__m64 a, __m64 b) +{ + return vreinterpret_m64_s16( + vmin_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); +} + +// Compare packed signed 16-bit integers in a and b, and store packed minimum +// values in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := MIN(a[i+15:i], b[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pi16 +#define _m_pminsw(a, b) _mm_min_pi16(a, b) + +// Computes the minima of the four single-precision, floating-point values of a +// and b. +// https://msdn.microsoft.com/en-us/library/vstudio/wh13kadz(v=vs.100).aspx +FORCE_INLINE __m128 _mm_min_ps(__m128 a, __m128 b) +{ +#if SSE2NEON_PRECISE_MINMAX + float32x4_t _a = vreinterpretq_f32_m128(a); + float32x4_t _b = vreinterpretq_f32_m128(b); + return vbslq_f32(vcltq_f32(_a, _b), _a, _b); +#else + return vreinterpretq_m128_f32( + vminq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#endif +} + +// Compare packed unsigned 8-bit integers in a and b, and store packed minimum +// values in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := MIN(a[i+7:i], b[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pu8 +FORCE_INLINE __m64 _mm_min_pu8(__m64 a, __m64 b) +{ + return vreinterpret_m64_u8( + vmin_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); +} + +// Compare packed unsigned 8-bit integers in a and b, and store packed minimum +// values in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := MIN(a[i+7:i], b[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pu8 +#define _m_pminub(a, b) _mm_min_pu8(a, b) + +// Computes the maximum of the two lower scalar single-precision floating point +// values of a and b. +// https://msdn.microsoft.com/en-us/library/s6db5esz(v=vs.100).aspx +FORCE_INLINE __m128 _mm_max_ss(__m128 a, __m128 b) +{ + float32_t value = vgetq_lane_f32(_mm_max_ps(a, b), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0)); +} + +// Computes the minimum of the two lower scalar single-precision floating point +// values of a and b. +// https://msdn.microsoft.com/en-us/library/0a9y7xaa(v=vs.100).aspx +FORCE_INLINE __m128 _mm_min_ss(__m128 a, __m128 b) +{ + float32_t value = vgetq_lane_f32(_mm_min_ps(a, b), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0)); +} + +// Computes the pairwise maxima of the 16 unsigned 8-bit integers from a and the +// 16 unsigned 8-bit integers from b. +// https://msdn.microsoft.com/en-us/library/st6634za(v=vs.100).aspx +FORCE_INLINE __m128i _mm_max_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vmaxq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Computes the pairwise minima of the 16 unsigned 8-bit integers from a and the +// 16 unsigned 8-bit integers from b. +// https://msdn.microsoft.com/ko-kr/library/17k8cf58(v=vs.100).aspxx +FORCE_INLINE __m128i _mm_min_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vminq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Computes the pairwise minima of the 8 signed 16-bit integers from a and the 8 +// signed 16-bit integers from b. +// https://msdn.microsoft.com/en-us/library/vstudio/6te997ew(v=vs.100).aspx +FORCE_INLINE __m128i _mm_min_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vminq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Compare packed signed 8-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epi8 +FORCE_INLINE __m128i _mm_max_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vmaxq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compare packed unsigned 16-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu16 +FORCE_INLINE __m128i _mm_max_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vmaxq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Compare packed signed 8-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epi8 +FORCE_INLINE __m128i _mm_min_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vminq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compare packed unsigned 16-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epu16 +FORCE_INLINE __m128i _mm_min_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vminq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Computes the pairwise maxima of the 8 signed 16-bit integers from a and the 8 +// signed 16-bit integers from b. +// https://msdn.microsoft.com/en-us/LIBRary/3x060h7c(v=vs.100).aspx +FORCE_INLINE __m128i _mm_max_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vmaxq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// epi versions of min/max +// Computes the pariwise maximums of the four signed 32-bit integer values of a +// and b. +// +// A 128-bit parameter that can be defined with the following equations: +// r0 := (a0 > b0) ? a0 : b0 +// r1 := (a1 > b1) ? a1 : b1 +// r2 := (a2 > b2) ? a2 : b2 +// r3 := (a3 > b3) ? a3 : b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/bb514055(v=vs.100).aspx +FORCE_INLINE __m128i _mm_max_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vmaxq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Computes the pariwise minima of the four signed 32-bit integer values of a +// and b. +// +// A 128-bit parameter that can be defined with the following equations: +// r0 := (a0 < b0) ? a0 : b0 +// r1 := (a1 < b1) ? a1 : b1 +// r2 := (a2 < b2) ? a2 : b2 +// r3 := (a3 < b3) ? a3 : b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/bb531476(v=vs.100).aspx +FORCE_INLINE __m128i _mm_min_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vminq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compare packed unsigned 32-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32 +FORCE_INLINE __m128i _mm_max_epu32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vmaxq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b))); +} + +// Compare packed unsigned 32-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32 +FORCE_INLINE __m128i _mm_min_epu32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vminq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b))); +} + +// Multiply the packed unsigned 16-bit integers in a and b, producing +// intermediate 32-bit integers, and store the high 16 bits of the intermediate +// integers in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_pu16 +FORCE_INLINE __m64 _mm_mulhi_pu16(__m64 a, __m64 b) +{ + return vreinterpret_m64_u16(vshrn_n_u32( + vmull_u16(vreinterpret_u16_m64(a), vreinterpret_u16_m64(b)), 16)); +} + +// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit +// integers from b. +// +// r0 := (a0 * b0)[31:16] +// r1 := (a1 * b1)[31:16] +// ... +// r7 := (a7 * b7)[31:16] +// +// https://msdn.microsoft.com/en-us/library/vstudio/59hddw1d(v=vs.100).aspx +FORCE_INLINE __m128i _mm_mulhi_epi16(__m128i a, __m128i b) +{ + /* FIXME: issue with large values because of result saturation */ + // int16x8_t ret = vqdmulhq_s16(vreinterpretq_s16_m128i(a), + // vreinterpretq_s16_m128i(b)); /* =2*a*b */ return + // vreinterpretq_m128i_s16(vshrq_n_s16(ret, 1)); + int16x4_t a3210 = vget_low_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b3210 = vget_low_s16(vreinterpretq_s16_m128i(b)); + int32x4_t ab3210 = vmull_s16(a3210, b3210); /* 3333222211110000 */ + int16x4_t a7654 = vget_high_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b7654 = vget_high_s16(vreinterpretq_s16_m128i(b)); + int32x4_t ab7654 = vmull_s16(a7654, b7654); /* 7777666655554444 */ + uint16x8x2_t r = + vuzpq_u16(vreinterpretq_u16_s32(ab3210), vreinterpretq_u16_s32(ab7654)); + return vreinterpretq_m128i_u16(r.val[1]); +} + +// Multiply the packed unsigned 16-bit integers in a and b, producing +// intermediate 32-bit integers, and store the high 16 bits of the intermediate +// integers in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_epu16 +FORCE_INLINE __m128i _mm_mulhi_epu16(__m128i a, __m128i b) +{ + uint16x4_t a3210 = vget_low_u16(vreinterpretq_u16_m128i(a)); + uint16x4_t b3210 = vget_low_u16(vreinterpretq_u16_m128i(b)); + uint32x4_t ab3210 = vmull_u16(a3210, b3210); +#if defined(__aarch64__) + uint32x4_t ab7654 = + vmull_high_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b)); + uint16x8_t r = vuzp2q_u16(vreinterpretq_u16_u32(ab3210), + vreinterpretq_u16_u32(ab7654)); + return vreinterpretq_m128i_u16(r); +#else + uint16x4_t a7654 = vget_high_u16(vreinterpretq_u16_m128i(a)); + uint16x4_t b7654 = vget_high_u16(vreinterpretq_u16_m128i(b)); + uint32x4_t ab7654 = vmull_u16(a7654, b7654); + uint16x8x2_t r = + vuzpq_u16(vreinterpretq_u16_u32(ab3210), vreinterpretq_u16_u32(ab7654)); + return vreinterpretq_m128i_u16(r.val[1]); +#endif +} + +// Computes pairwise add of each argument as single-precision, floating-point +// values a and b. +// https://msdn.microsoft.com/en-us/library/yd9wecaa.aspx +FORCE_INLINE __m128 _mm_hadd_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vpaddq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32( + vcombine_f32(vpadd_f32(a10, a32), vpadd_f32(b10, b32))); +#endif +} + +// Computes pairwise add of each argument as a 16-bit signed or unsigned integer +// values a and b. +FORCE_INLINE __m128i _mm_hadd_epi16(__m128i _a, __m128i _b) +{ + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); +#if defined(__aarch64__) + return vreinterpretq_m128i_s16(vpaddq_s16(a, b)); +#else + return vreinterpretq_m128i_s16( + vcombine_s16(vpadd_s16(vget_low_s16(a), vget_high_s16(a)), + vpadd_s16(vget_low_s16(b), vget_high_s16(b)))); +#endif +} + +// Horizontally substract adjacent pairs of single-precision (32-bit) +// floating-point elements in a and b, and pack the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_ps +FORCE_INLINE __m128 _mm_hsub_ps(__m128 _a, __m128 _b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32(vsubq_f32( + vuzp1q_f32(vreinterpretq_f32_m128(_a), vreinterpretq_f32_m128(_b)), + vuzp2q_f32(vreinterpretq_f32_m128(_a), vreinterpretq_f32_m128(_b)))); +#else + float32x4x2_t c = + vuzpq_f32(vreinterpretq_f32_m128(_a), vreinterpretq_f32_m128(_b)); + return vreinterpretq_m128_f32(vsubq_f32(c.val[0], c.val[1])); +#endif +} + +// Horizontally add adjacent pairs of 16-bit integers in a and b, and pack the +// signed 16-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi16 +FORCE_INLINE __m64 _mm_hadd_pi16(__m64 a, __m64 b) +{ + return vreinterpret_m64_s16( + vpadd_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); +} + +// Horizontally add adjacent pairs of 32-bit integers in a and b, and pack the +// signed 32-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi32 +FORCE_INLINE __m64 _mm_hadd_pi32(__m64 a, __m64 b) +{ + return vreinterpret_m64_s32( + vpadd_s32(vreinterpret_s32_m64(a), vreinterpret_s32_m64(b))); +} + +// Computes pairwise difference of each argument as a 16-bit signed or unsigned +// integer values a and b. +FORCE_INLINE __m128i _mm_hsub_epi16(__m128i _a, __m128i _b) +{ + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + // Interleave using vshrn/vmovn + // [a0|a2|a4|a6|b0|b2|b4|b6] + // [a1|a3|a5|a7|b1|b3|b5|b7] + int16x8_t ab0246 = vcombine_s16(vmovn_s32(a), vmovn_s32(b)); + int16x8_t ab1357 = vcombine_s16(vshrn_n_s32(a, 16), vshrn_n_s32(b, 16)); + // Subtract + return vreinterpretq_m128i_s16(vsubq_s16(ab0246, ab1357)); +} + +// Computes saturated pairwise sub of each argument as a 16-bit signed +// integer values a and b. +FORCE_INLINE __m128i _mm_hadds_epi16(__m128i _a, __m128i _b) +{ +#if defined(__aarch64__) + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); + return vreinterpretq_s64_s16( + vqaddq_s16(vuzp1q_s16(a, b), vuzp2q_s16(a, b))); +#else + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + // Interleave using vshrn/vmovn + // [a0|a2|a4|a6|b0|b2|b4|b6] + // [a1|a3|a5|a7|b1|b3|b5|b7] + int16x8_t ab0246 = vcombine_s16(vmovn_s32(a), vmovn_s32(b)); + int16x8_t ab1357 = vcombine_s16(vshrn_n_s32(a, 16), vshrn_n_s32(b, 16)); + // Saturated add + return vreinterpretq_m128i_s16(vqaddq_s16(ab0246, ab1357)); +#endif +} + +// Computes saturated pairwise difference of each argument as a 16-bit signed +// integer values a and b. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsubs_epi16 +FORCE_INLINE __m128i _mm_hsubs_epi16(__m128i _a, __m128i _b) +{ +#if defined(__aarch64__) + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); + return vreinterpretq_s64_s16( + vqsubq_s16(vuzp1q_s16(a, b), vuzp2q_s16(a, b))); +#else + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + // Interleave using vshrn/vmovn + // [a0|a2|a4|a6|b0|b2|b4|b6] + // [a1|a3|a5|a7|b1|b3|b5|b7] + int16x8_t ab0246 = vcombine_s16(vmovn_s32(a), vmovn_s32(b)); + int16x8_t ab1357 = vcombine_s16(vshrn_n_s32(a, 16), vshrn_n_s32(b, 16)); + // Saturated subtract + return vreinterpretq_m128i_s16(vqsubq_s16(ab0246, ab1357)); +#endif +} + +// Computes pairwise add of each argument as a 32-bit signed or unsigned integer +// values a and b. +FORCE_INLINE __m128i _mm_hadd_epi32(__m128i _a, __m128i _b) +{ + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + return vreinterpretq_m128i_s32( + vcombine_s32(vpadd_s32(vget_low_s32(a), vget_high_s32(a)), + vpadd_s32(vget_low_s32(b), vget_high_s32(b)))); +} + +// Computes pairwise difference of each argument as a 32-bit signed or unsigned +// integer values a and b. +FORCE_INLINE __m128i _mm_hsub_epi32(__m128i _a, __m128i _b) +{ + int64x2_t a = vreinterpretq_s64_m128i(_a); + int64x2_t b = vreinterpretq_s64_m128i(_b); + // Interleave using vshrn/vmovn + // [a0|a2|b0|b2] + // [a1|a2|b1|b3] + int32x4_t ab02 = vcombine_s32(vmovn_s64(a), vmovn_s64(b)); + int32x4_t ab13 = vcombine_s32(vshrn_n_s64(a, 32), vshrn_n_s64(b, 32)); + // Subtract + return vreinterpretq_m128i_s32(vsubq_s32(ab02, ab13)); +} + +// Kahan summation for accurate summation of floating-point numbers. +// http://blog.zachbjornson.com/2019/08/11/fast-float-summation.html +FORCE_INLINE void sse2neon_kadd_f32(float *sum, float *c, float y) +{ + y -= *c; + float t = *sum + y; + *c = (t - *sum) - y; + *sum = t; +} + +// Conditionally multiply the packed single-precision (32-bit) floating-point +// elements in a and b using the high 4 bits in imm8, sum the four products, +// and conditionally store the sum in dst using the low 4 bits of imm. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_dp_ps +FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm) +{ +#if defined(__aarch64__) + /* shortcuts */ + if (imm == 0xFF) { + return _mm_set1_ps(vaddvq_f32(_mm_mul_ps(a, b))); + } + if (imm == 0x7F) { + float32x4_t m = _mm_mul_ps(a, b); + m[3] = 0; + return _mm_set1_ps(vaddvq_f32(m)); + } +#endif + + float s = 0, c = 0; + float32x4_t f32a = vreinterpretq_f32_m128(a); + float32x4_t f32b = vreinterpretq_f32_m128(b); + + /* To improve the accuracy of floating-point summation, Kahan algorithm + * is used for each operation. + */ + if (imm & (1 << 4)) + sse2neon_kadd_f32(&s, &c, f32a[0] * f32b[0]); + if (imm & (1 << 5)) + sse2neon_kadd_f32(&s, &c, f32a[1] * f32b[1]); + if (imm & (1 << 6)) + sse2neon_kadd_f32(&s, &c, f32a[2] * f32b[2]); + if (imm & (1 << 7)) + sse2neon_kadd_f32(&s, &c, f32a[3] * f32b[3]); + s += c; + + float32x4_t res = { + (imm & 0x1) ? s : 0, + (imm & 0x2) ? s : 0, + (imm & 0x4) ? s : 0, + (imm & 0x8) ? s : 0, + }; + return vreinterpretq_m128_f32(res); +} + +/* Compare operations */ + +// Compares for less than +// https://msdn.microsoft.com/en-us/library/vstudio/f330yhc8(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmplt_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for less than +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fy94wye7(v=vs.100) +FORCE_INLINE __m128 _mm_cmplt_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmplt_ps(a, b)); +} + +// Compares for greater than. +// +// r0 := (a0 > b0) ? 0xffffffff : 0x0 +// r1 := (a1 > b1) ? 0xffffffff : 0x0 +// r2 := (a2 > b2) ? 0xffffffff : 0x0 +// r3 := (a3 > b3) ? 0xffffffff : 0x0 +// +// https://msdn.microsoft.com/en-us/library/vstudio/11dy102s(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpgt_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for greater than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/1xyyyy9e(v=vs.100) +FORCE_INLINE __m128 _mm_cmpgt_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpgt_ps(a, b)); +} + +// Compares for greater than or equal. +// https://msdn.microsoft.com/en-us/library/vstudio/fs813y2t(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpge_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for greater than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/kesh3ddc(v=vs.100) +FORCE_INLINE __m128 _mm_cmpge_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpge_ps(a, b)); +} + +// Compares for less than or equal. +// +// r0 := (a0 <= b0) ? 0xffffffff : 0x0 +// r1 := (a1 <= b1) ? 0xffffffff : 0x0 +// r2 := (a2 <= b2) ? 0xffffffff : 0x0 +// r3 := (a3 <= b3) ? 0xffffffff : 0x0 +// +// https://msdn.microsoft.com/en-us/library/vstudio/1s75w83z(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmple_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for less than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/a7x0hbhw(v=vs.100) +FORCE_INLINE __m128 _mm_cmple_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmple_ps(a, b)); +} + +// Compares for equality. +// https://msdn.microsoft.com/en-us/library/vstudio/36aectz5(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpeq_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for equality. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/k423z28e(v=vs.100) +FORCE_INLINE __m128 _mm_cmpeq_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpeq_ps(a, b)); +} + +// Compares for inequality. +// https://msdn.microsoft.com/en-us/library/sf44thbx(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpneq_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32(vmvnq_u32( + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); +} + +// Compares for inequality. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ekya8fh4(v=vs.100) +FORCE_INLINE __m128 _mm_cmpneq_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpneq_ps(a, b)); +} + +// Compares for not greater than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/wsexys62(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnge_ps(__m128 a, __m128 b) +{ + return _mm_cmplt_ps(a, b); +} + +// Compares for not greater than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fk2y80s8(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnge_ss(__m128 a, __m128 b) +{ + return _mm_cmplt_ss(a, b); +} + +// Compares for not greater than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/d0xh7w0s(v=vs.100) +FORCE_INLINE __m128 _mm_cmpngt_ps(__m128 a, __m128 b) +{ + return _mm_cmple_ps(a, b); +} + +// Compares for not greater than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100) +FORCE_INLINE __m128 _mm_cmpngt_ss(__m128 a, __m128 b) +{ + return _mm_cmple_ss(a, b); +} + +// Compares for not less than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/6a330kxw(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnle_ps(__m128 a, __m128 b) +{ + return _mm_cmpgt_ps(a, b); +} + +// Compares for not less than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnle_ss(__m128 a, __m128 b) +{ + return _mm_cmpgt_ss(a, b); +} + +// Compares for not less than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/4686bbdw(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnlt_ps(__m128 a, __m128 b) +{ + return _mm_cmpge_ps(a, b); +} + +// Compares for not less than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/56b9z2wf(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnlt_ss(__m128 a, __m128 b) +{ + return _mm_cmpge_ss(a, b); +} + +// Compares the 16 signed or unsigned 8-bit integers in a and the 16 signed or +// unsigned 8-bit integers in b for equality. +// https://msdn.microsoft.com/en-us/library/windows/desktop/bz5xk21a(v=vs.90).aspx +FORCE_INLINE __m128i _mm_cmpeq_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vceqq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compares the 8 signed or unsigned 16-bit integers in a and the 8 signed or +// unsigned 16-bit integers in b for equality. +// https://msdn.microsoft.com/en-us/library/2ay060te(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpeq_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vceqq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Compare packed 32-bit integers in a and b for equality, and store the results +// in dst +FORCE_INLINE __m128i _mm_cmpeq_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vceqq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compare packed 64-bit integers in a and b for equality, and store the results +// in dst +FORCE_INLINE __m128i _mm_cmpeq_epi64(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_u64( + vceqq_u64(vreinterpretq_u64_m128i(a), vreinterpretq_u64_m128i(b))); +#else + // ARMv7 lacks vceqq_u64 + // (a == b) -> (a_lo == b_lo) && (a_hi == b_hi) + uint32x4_t cmp = + vceqq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b)); + uint32x4_t swapped = vrev64q_u32(cmp); + return vreinterpretq_m128i_u32(vandq_u32(cmp, swapped)); +#endif +} + +// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers +// in b for lesser than. +// https://msdn.microsoft.com/en-us/library/windows/desktop/9s46csht(v=vs.90).aspx +FORCE_INLINE __m128i _mm_cmplt_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vcltq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers +// in b for greater than. +// +// r0 := (a0 > b0) ? 0xff : 0x0 +// r1 := (a1 > b1) ? 0xff : 0x0 +// ... +// r15 := (a15 > b15) ? 0xff : 0x0 +// +// https://msdn.microsoft.com/zh-tw/library/wf45zt2b(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpgt_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vcgtq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers +// in b for less than. +// +// r0 := (a0 < b0) ? 0xffff : 0x0 +// r1 := (a1 < b1) ? 0xffff : 0x0 +// ... +// r7 := (a7 < b7) ? 0xffff : 0x0 +// +// https://technet.microsoft.com/en-us/library/t863edb2(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmplt_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vcltq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers +// in b for greater than. +// +// r0 := (a0 > b0) ? 0xffff : 0x0 +// r1 := (a1 > b1) ? 0xffff : 0x0 +// ... +// r7 := (a7 > b7) ? 0xffff : 0x0 +// +// https://technet.microsoft.com/en-us/library/xd43yfsa(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpgt_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vcgtq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + + +// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers +// in b for less than. +// https://msdn.microsoft.com/en-us/library/vstudio/4ak0bf5d(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmplt_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vcltq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers +// in b for greater than. +// https://msdn.microsoft.com/en-us/library/vstudio/1s9f2z0y(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpgt_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vcgtq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compares the 2 signed 64-bit integers in a and the 2 signed 64-bit integers +// in b for greater than. +FORCE_INLINE __m128i _mm_cmpgt_epi64(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_u64( + vcgtq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); +#else + // ARMv7 lacks vcgtq_s64. + // This is based off of Clang's SSE2 polyfill: + // (a > b) -> ((a_hi > b_hi) || (a_lo > b_lo && a_hi == b_hi)) + + // Mask the sign bit out since we need a signed AND an unsigned comparison + // and it is ugly to try and split them. + int32x4_t mask = vreinterpretq_s32_s64(vdupq_n_s64(0x80000000ull)); + int32x4_t a_mask = veorq_s32(vreinterpretq_s32_m128i(a), mask); + int32x4_t b_mask = veorq_s32(vreinterpretq_s32_m128i(b), mask); + // Check if a > b + int64x2_t greater = vreinterpretq_s64_u32(vcgtq_s32(a_mask, b_mask)); + // Copy upper mask to lower mask + // a_hi > b_hi + int64x2_t gt_hi = vshrq_n_s64(greater, 63); + // Copy lower mask to upper mask + // a_lo > b_lo + int64x2_t gt_lo = vsliq_n_s64(greater, greater, 32); + // Compare for equality + int64x2_t equal = vreinterpretq_s64_u32(vceqq_s32(a_mask, b_mask)); + // Copy upper mask to lower mask + // a_hi == b_hi + int64x2_t eq_hi = vshrq_n_s64(equal, 63); + // a_hi > b_hi || (a_lo > b_lo && a_hi == b_hi) + int64x2_t ret = vorrq_s64(gt_hi, vandq_s64(gt_lo, eq_hi)); + return vreinterpretq_m128i_s64(ret); +#endif +} + +// Compares the four 32-bit floats in a and b to check if any values are NaN. +// Ordered compare between each value returns true for "orderable" and false for +// "not orderable" (NaN). +// https://msdn.microsoft.com/en-us/library/vstudio/0h9w00fx(v=vs.100).aspx see +// also: +// http://stackoverflow.com/questions/8627331/what-does-ordered-unordered-comparison-mean +// http://stackoverflow.com/questions/29349621/neon-isnanval-intrinsics +FORCE_INLINE __m128 _mm_cmpord_ps(__m128 a, __m128 b) +{ + // Note: NEON does not have ordered compare builtin + // Need to compare a eq a and b eq b to check for NaN + // Do AND of results to get final + uint32x4_t ceqaa = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t ceqbb = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_u32(vandq_u32(ceqaa, ceqbb)); +} + +// Compares for ordered. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/343t62da(v=vs.100) +FORCE_INLINE __m128 _mm_cmpord_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpord_ps(a, b)); +} + +// Compares for unordered. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/khy6fk1t(v=vs.100) +FORCE_INLINE __m128 _mm_cmpunord_ps(__m128 a, __m128 b) +{ + uint32x4_t f32a = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t f32b = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_u32(vmvnq_u32(vandq_u32(f32a, f32b))); +} + +// Compares for unordered. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/2as2387b(v=vs.100) +FORCE_INLINE __m128 _mm_cmpunord_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpunord_ps(a, b)); +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a less than operation. : +// https://msdn.microsoft.com/en-us/library/2kwe606b(v=vs.90).aspx Important +// note!! The documentation on MSDN is incorrect! If either of the values is a +// NAN the docs say you will get a one, but in fact, it will return a zero!! +FORCE_INLINE int _mm_comilt_ss(__m128 a, __m128 b) +{ + uint32x4_t a_not_nan = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t b_not_nan = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + uint32x4_t a_and_b_not_nan = vandq_u32(a_not_nan, b_not_nan); + uint32x4_t a_lt_b = + vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return (vgetq_lane_u32(vandq_u32(a_and_b_not_nan, a_lt_b), 0) != 0) ? 1 : 0; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a greater than operation. : +// https://msdn.microsoft.com/en-us/library/b0738e0t(v=vs.100).aspx +FORCE_INLINE int _mm_comigt_ss(__m128 a, __m128 b) +{ + // return vgetq_lane_u32(vcgtq_f32(vreinterpretq_f32_m128(a), + // vreinterpretq_f32_m128(b)), 0); + uint32x4_t a_not_nan = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t b_not_nan = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + uint32x4_t a_and_b_not_nan = vandq_u32(a_not_nan, b_not_nan); + uint32x4_t a_gt_b = + vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return (vgetq_lane_u32(vandq_u32(a_and_b_not_nan, a_gt_b), 0) != 0) ? 1 : 0; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a less than or equal operation. : +// https://msdn.microsoft.com/en-us/library/1w4t7c57(v=vs.90).aspx +FORCE_INLINE int _mm_comile_ss(__m128 a, __m128 b) +{ + // return vgetq_lane_u32(vcleq_f32(vreinterpretq_f32_m128(a), + // vreinterpretq_f32_m128(b)), 0); + uint32x4_t a_not_nan = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t b_not_nan = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + uint32x4_t a_and_b_not_nan = vandq_u32(a_not_nan, b_not_nan); + uint32x4_t a_le_b = + vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return (vgetq_lane_u32(vandq_u32(a_and_b_not_nan, a_le_b), 0) != 0) ? 1 : 0; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a greater than or equal operation. : +// https://msdn.microsoft.com/en-us/library/8t80des6(v=vs.100).aspx +FORCE_INLINE int _mm_comige_ss(__m128 a, __m128 b) +{ + // return vgetq_lane_u32(vcgeq_f32(vreinterpretq_f32_m128(a), + // vreinterpretq_f32_m128(b)), 0); + uint32x4_t a_not_nan = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t b_not_nan = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + uint32x4_t a_and_b_not_nan = vandq_u32(a_not_nan, b_not_nan); + uint32x4_t a_ge_b = + vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return (vgetq_lane_u32(vandq_u32(a_and_b_not_nan, a_ge_b), 0) != 0) ? 1 : 0; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using an equality operation. : +// https://msdn.microsoft.com/en-us/library/93yx2h2b(v=vs.100).aspx +FORCE_INLINE int _mm_comieq_ss(__m128 a, __m128 b) +{ + // return vgetq_lane_u32(vceqq_f32(vreinterpretq_f32_m128(a), + // vreinterpretq_f32_m128(b)), 0); + uint32x4_t a_not_nan = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t b_not_nan = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + uint32x4_t a_and_b_not_nan = vandq_u32(a_not_nan, b_not_nan); + uint32x4_t a_eq_b = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return (vgetq_lane_u32(vandq_u32(a_and_b_not_nan, a_eq_b), 0) != 0) ? 1 : 0; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using an inequality operation. : +// https://msdn.microsoft.com/en-us/library/bafh5e0a(v=vs.90).aspx +FORCE_INLINE int _mm_comineq_ss(__m128 a, __m128 b) +{ + // return !vgetq_lane_u32(vceqq_f32(vreinterpretq_f32_m128(a), + // vreinterpretq_f32_m128(b)), 0); + uint32x4_t a_not_nan = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t b_not_nan = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + uint32x4_t a_or_b_nan = vmvnq_u32(vandq_u32(a_not_nan, b_not_nan)); + uint32x4_t a_neq_b = vmvnq_u32( + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + return (vgetq_lane_u32(vorrq_u32(a_or_b_nan, a_neq_b), 0) != 0) ? 1 : 0; +} + +// according to the documentation, these intrinsics behave the same as the +// non-'u' versions. We'll just alias them here. +#define _mm_ucomilt_ss _mm_comilt_ss +#define _mm_ucomile_ss _mm_comile_ss +#define _mm_ucomigt_ss _mm_comigt_ss +#define _mm_ucomige_ss _mm_comige_ss +#define _mm_ucomieq_ss _mm_comieq_ss +#define _mm_ucomineq_ss _mm_comineq_ss + +/* Conversions */ + +// Convert packed signed 32-bit integers in b to packed single-precision +// (32-bit) floating-point elements, store the results in the lower 2 elements +// of dst, and copy the upper 2 packed elements from a to the upper elements of +// dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[63:32] := Convert_Int32_To_FP32(b[63:32]) +// dst[95:64] := a[95:64] +// dst[127:96] := a[127:96] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_pi2ps +FORCE_INLINE __m128 _mm_cvt_pi2ps(__m128 a, __m64 b) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vcvt_f32_s32(vreinterpret_s32_m64(b)), + vget_high_f32(vreinterpretq_f32_m128(a)))); +} + +// Convert the signed 32-bit integer b to a single-precision (32-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper 3 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_si2ss +FORCE_INLINE __m128 _mm_cvt_si2ss(__m128 a, int b) +{ + return vreinterpretq_m128_f32( + vsetq_lane_f32((float) b, vreinterpretq_f32_m128(a), 0)); +} + +// Convert the signed 32-bit integer b to a single-precision (32-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper 3 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi32_ss +#define _mm_cvtsi32_ss(a, b) _mm_cvt_si2ss(a, b) + +// Convert the signed 64-bit integer b to a single-precision (32-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper 3 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int64_To_FP32(b[63:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64_ss +FORCE_INLINE __m128 _mm_cvtsi64_ss(__m128 a, int64_t b) +{ + return vreinterpretq_m128_f32( + vsetq_lane_f32((float) b, vreinterpretq_f32_m128(a), 0)); +} + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer, and store the result in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ss2si +FORCE_INLINE int _mm_cvt_ss2si(__m128 a) +{ +#if defined(__aarch64__) + return vgetq_lane_s32(vcvtnq_s32_f32(vreinterpretq_f32_m128(a)), 0); +#else + float32_t data = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + float32_t diff = data - floor(data); + if (diff > 0.5) + return (int32_t) ceil(data); + if (diff == 0.5) { + int32_t f = (int32_t) floor(data); + int32_t c = (int32_t) ceil(data); + return c & 1 ? f : c; + } + return (int32_t) floor(data); +#endif +} + +// Convert packed 16-bit integers in a to packed single-precision (32-bit) +// floating-point elements, and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// m := j*32 +// dst[m+31:m] := Convert_Int16_To_FP32(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi16_ps +FORCE_INLINE __m128 _mm_cvtpi16_ps(__m64 a) +{ + return vreinterpretq_m128_f32( + vcvtq_f32_s32(vmovl_s16(vreinterpret_s16_m64(a)))); +} + +// Convert packed 32-bit integers in b to packed single-precision (32-bit) +// floating-point elements, store the results in the lower 2 elements of dst, +// and copy the upper 2 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[63:32] := Convert_Int32_To_FP32(b[63:32]) +// dst[95:64] := a[95:64] +// dst[127:96] := a[127:96] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32_ps +FORCE_INLINE __m128 _mm_cvtpi32_ps(__m128 a, __m64 b) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vcvt_f32_s32(vreinterpret_s32_m64(b)), + vget_high_f32(vreinterpretq_f32_m128(a)))); +} + +// Convert packed signed 32-bit integers in a to packed single-precision +// (32-bit) floating-point elements, store the results in the lower 2 elements +// of dst, then covert the packed signed 32-bit integers in b to +// single-precision (32-bit) floating-point element, and store the results in +// the upper 2 elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(a[31:0]) +// dst[63:32] := Convert_Int32_To_FP32(a[63:32]) +// dst[95:64] := Convert_Int32_To_FP32(b[31:0]) +// dst[127:96] := Convert_Int32_To_FP32(b[63:32]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32x2_ps +FORCE_INLINE __m128 _mm_cvtpi32x2_ps(__m64 a, __m64 b) +{ + return vreinterpretq_m128_f32(vcvtq_f32_s32( + vcombine_s32(vreinterpret_s32_m64(a), vreinterpret_s32_m64(b)))); +} + +// Convert the lower packed 8-bit integers in a to packed single-precision +// (32-bit) floating-point elements, and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*8 +// m := j*32 +// dst[m+31:m] := Convert_Int8_To_FP32(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi8_ps +FORCE_INLINE __m128 _mm_cvtpi8_ps(__m64 a) +{ + return vreinterpretq_m128_f32(vcvtq_f32_s32( + vmovl_s16(vget_low_s16(vmovl_s8(vreinterpret_s8_m64(a)))))); +} + +// Convert packed unsigned 16-bit integers in a to packed single-precision +// (32-bit) floating-point elements, and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// m := j*32 +// dst[m+31:m] := Convert_UInt16_To_FP32(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu16_ps +FORCE_INLINE __m128 _mm_cvtpu16_ps(__m64 a) +{ + return vreinterpretq_m128_f32( + vcvtq_f32_u32(vmovl_u16(vreinterpret_u16_m64(a)))); +} + +// Convert the lower packed unsigned 8-bit integers in a to packed +// single-precision (32-bit) floating-point elements, and store the results in +// dst. +// +// FOR j := 0 to 3 +// i := j*8 +// m := j*32 +// dst[m+31:m] := Convert_UInt8_To_FP32(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu8_ps +FORCE_INLINE __m128 _mm_cvtpu8_ps(__m64 a) +{ + return vreinterpretq_m128_f32(vcvtq_f32_u32( + vmovl_u16(vget_low_u16(vmovl_u8(vreinterpret_u8_m64(a)))))); +} + +// Converts the four single-precision, floating-point values of a to signed +// 32-bit integer values using truncate. +// https://msdn.microsoft.com/en-us/library/vstudio/1h005y6x(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cvttps_epi32(__m128 a) +{ + return vreinterpretq_m128i_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a))); +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 64-bit integer with truncation, and store the result in dst. +// +// dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64 +FORCE_INLINE int64_t _mm_cvttsd_si64(__m128d a) +{ +#if defined(__aarch64__) + return vgetq_lane_s64(vcvtq_s64_f64(vreinterpretq_f64_m128d(a)), 0); +#else + double ret = *((double *) &a); + return (int64_t) ret; +#endif +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 64-bit integer with truncation, and store the result in dst. +// +// dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64x +#define _mm_cvttsd_si64x(a) _mm_cvttsd_si64(a) + +// Converts the four signed 32-bit integer values of a to single-precision, +// floating-point values +// https://msdn.microsoft.com/en-us/library/vstudio/36bwxcx5(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cvtepi32_ps(__m128i a) +{ + return vreinterpretq_m128_f32(vcvtq_f32_s32(vreinterpretq_s32_m128i(a))); +} + +// Converts the four unsigned 8-bit integers in the lower 16 bits to four +// unsigned 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepu8_epi16(__m128i a) +{ + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx xxxx DCBA */ + uint16x8_t u16x8 = vmovl_u8(vget_low_u8(u8x16)); /* 0x0x 0x0x 0D0C 0B0A */ + return vreinterpretq_m128i_u16(u16x8); +} + +// Converts the four unsigned 8-bit integers in the lower 32 bits to four +// unsigned 32-bit integers. +// https://msdn.microsoft.com/en-us/library/bb531467%28v=vs.100%29.aspx +FORCE_INLINE __m128i _mm_cvtepu8_epi32(__m128i a) +{ + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx xxxx DCBA */ + uint16x8_t u16x8 = vmovl_u8(vget_low_u8(u8x16)); /* 0x0x 0x0x 0D0C 0B0A */ + uint32x4_t u32x4 = vmovl_u16(vget_low_u16(u16x8)); /* 000D 000C 000B 000A */ + return vreinterpretq_m128i_u32(u32x4); +} + +// Converts the two unsigned 8-bit integers in the lower 16 bits to two +// unsigned 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepu8_epi64(__m128i a) +{ + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx xxxx xxBA */ + uint16x8_t u16x8 = vmovl_u8(vget_low_u8(u8x16)); /* 0x0x 0x0x 0x0x 0B0A */ + uint32x4_t u32x4 = vmovl_u16(vget_low_u16(u16x8)); /* 000x 000x 000B 000A */ + uint64x2_t u64x2 = vmovl_u32(vget_low_u32(u32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_u64(u64x2); +} + +// Converts the four unsigned 8-bit integers in the lower 16 bits to four +// unsigned 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi8_epi16(__m128i a) +{ + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx DCBA */ + int16x8_t s16x8 = vmovl_s8(vget_low_s8(s8x16)); /* 0x0x 0x0x 0D0C 0B0A */ + return vreinterpretq_m128i_s16(s16x8); +} + +// Converts the four unsigned 8-bit integers in the lower 32 bits to four +// unsigned 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi8_epi32(__m128i a) +{ + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx DCBA */ + int16x8_t s16x8 = vmovl_s8(vget_low_s8(s8x16)); /* 0x0x 0x0x 0D0C 0B0A */ + int32x4_t s32x4 = vmovl_s16(vget_low_s16(s16x8)); /* 000D 000C 000B 000A */ + return vreinterpretq_m128i_s32(s32x4); +} + +// Converts the two signed 8-bit integers in the lower 32 bits to four +// signed 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepi8_epi64(__m128i a) +{ + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx xxBA */ + int16x8_t s16x8 = vmovl_s8(vget_low_s8(s8x16)); /* 0x0x 0x0x 0x0x 0B0A */ + int32x4_t s32x4 = vmovl_s16(vget_low_s16(s16x8)); /* 000x 000x 000B 000A */ + int64x2_t s64x2 = vmovl_s32(vget_low_s32(s32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_s64(s64x2); +} + +// Converts the four signed 16-bit integers in the lower 64 bits to four signed +// 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi16_epi32(__m128i a) +{ + return vreinterpretq_m128i_s32( + vmovl_s16(vget_low_s16(vreinterpretq_s16_m128i(a)))); +} + +// Converts the two signed 16-bit integers in the lower 32 bits two signed +// 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi16_epi64(__m128i a) +{ + int16x8_t s16x8 = vreinterpretq_s16_m128i(a); /* xxxx xxxx xxxx 0B0A */ + int32x4_t s32x4 = vmovl_s16(vget_low_s16(s16x8)); /* 000x 000x 000B 000A */ + int64x2_t s64x2 = vmovl_s32(vget_low_s32(s32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_s64(s64x2); +} + +// Converts the four unsigned 16-bit integers in the lower 64 bits to four +// unsigned 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepu16_epi32(__m128i a) +{ + return vreinterpretq_m128i_u32( + vmovl_u16(vget_low_u16(vreinterpretq_u16_m128i(a)))); +} + +// Converts the two unsigned 16-bit integers in the lower 32 bits to two +// unsigned 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepu16_epi64(__m128i a) +{ + uint16x8_t u16x8 = vreinterpretq_u16_m128i(a); /* xxxx xxxx xxxx 0B0A */ + uint32x4_t u32x4 = vmovl_u16(vget_low_u16(u16x8)); /* 000x 000x 000B 000A */ + uint64x2_t u64x2 = vmovl_u32(vget_low_u32(u32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_u64(u64x2); +} + +// Converts the two unsigned 32-bit integers in the lower 64 bits to two +// unsigned 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepu32_epi64(__m128i a) +{ + return vreinterpretq_m128i_u64( + vmovl_u32(vget_low_u32(vreinterpretq_u32_m128i(a)))); +} + +// Converts the two signed 32-bit integers in the lower 64 bits to two signed +// 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepi32_epi64(__m128i a) +{ + return vreinterpretq_m128i_s64( + vmovl_s32(vget_low_s32(vreinterpretq_s32_m128i(a)))); +} + +// Converts the four single-precision, floating-point values of a to signed +// 32-bit integer values. +// +// r0 := (int) a0 +// r1 := (int) a1 +// r2 := (int) a2 +// r3 := (int) a3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/xdc42k5e(v=vs.100).aspx +// *NOTE*. The default rounding mode on SSE is 'round to even', which ARMv7-A +// does not support! It is supported on ARMv8-A however. +FORCE_INLINE __m128i _mm_cvtps_epi32(__m128 a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s32(vcvtnq_s32_f32(a)); +#else + uint32x4_t signmask = vdupq_n_u32(0x80000000); + float32x4_t half = vbslq_f32(signmask, vreinterpretq_f32_m128(a), + vdupq_n_f32(0.5f)); /* +/- 0.5 */ + int32x4_t r_normal = vcvtq_s32_f32(vaddq_f32( + vreinterpretq_f32_m128(a), half)); /* round to integer: [a + 0.5]*/ + int32x4_t r_trunc = + vcvtq_s32_f32(vreinterpretq_f32_m128(a)); /* truncate to integer: [a] */ + int32x4_t plusone = vreinterpretq_s32_u32(vshrq_n_u32( + vreinterpretq_u32_s32(vnegq_s32(r_trunc)), 31)); /* 1 or 0 */ + int32x4_t r_even = vbicq_s32(vaddq_s32(r_trunc, plusone), + vdupq_n_s32(1)); /* ([a] + {0,1}) & ~1 */ + float32x4_t delta = vsubq_f32( + vreinterpretq_f32_m128(a), + vcvtq_f32_s32(r_trunc)); /* compute delta: delta = (a - [a]) */ + uint32x4_t is_delta_half = vceqq_f32(delta, half); /* delta == +/- 0.5 */ + return vreinterpretq_m128i_s32(vbslq_s32(is_delta_half, r_even, r_normal)); +#endif +} + +// Copy the lower 32-bit integer in a to dst. +// +// dst[31:0] := a[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si32 +FORCE_INLINE int _mm_cvtsi128_si32(__m128i a) +{ + return vgetq_lane_s32(vreinterpretq_s32_m128i(a), 0); +} + +// Copy the lower 64-bit integer in a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64 +FORCE_INLINE int64_t _mm_cvtsi128_si64(__m128i a) +{ + return vgetq_lane_s64(vreinterpretq_s64_m128i(a), 0); +} + +// Copy the lower 64-bit integer in a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64x +#define _mm_cvtsi128_si64x(a) _mm_cvtsi128_si64(a) + +// Moves 32-bit integer a to the least significant 32 bits of an __m128 object, +// zero extending the upper bits. +// +// r0 := a +// r1 := 0x0 +// r2 := 0x0 +// r3 := 0x0 +// +// https://msdn.microsoft.com/en-us/library/ct3539ha%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_cvtsi32_si128(int a) +{ + return vreinterpretq_m128i_s32(vsetq_lane_s32(a, vdupq_n_s32(0), 0)); +} + +// Moves 64-bit integer a to the least significant 64 bits of an __m128 object, +// zero extending the upper bits. +// +// r0 := a +// r1 := 0x0 +FORCE_INLINE __m128i _mm_cvtsi64_si128(int64_t a) +{ + return vreinterpretq_m128i_s64(vsetq_lane_s64(a, vdupq_n_s64(0), 0)); +} + +// Cast vector of type __m128 to type __m128d. This intrinsic is only used for +// compilation and does not generate any instructions, thus it has zero latency. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castps_pd +FORCE_INLINE __m128d _mm_castps_pd(__m128 a) +{ + return vreinterpretq_m128d_s32(vreinterpretq_s32_m128(a)); +} + +// Applies a type cast to reinterpret four 32-bit floating point values passed +// in as a 128-bit parameter as packed 32-bit integers. +// https://msdn.microsoft.com/en-us/library/bb514099.aspx +FORCE_INLINE __m128i _mm_castps_si128(__m128 a) +{ + return vreinterpretq_m128i_s32(vreinterpretq_s32_m128(a)); +} + +// Applies a type cast to reinterpret four 32-bit integers passed in as a +// 128-bit parameter as packed 32-bit floating point values. +// https://msdn.microsoft.com/en-us/library/bb514029.aspx +FORCE_INLINE __m128 _mm_castsi128_ps(__m128i a) +{ + return vreinterpretq_m128_s32(vreinterpretq_s32_m128i(a)); +} + +// Loads 128-bit value. : +// https://msdn.microsoft.com/en-us/library/atzzad1h(v=vs.80).aspx +FORCE_INLINE __m128i _mm_load_si128(const __m128i *p) +{ + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p)); +} + +// Load a double-precision (64-bit) floating-point element from memory into both +// elements of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load1_pd +FORCE_INLINE __m128d _mm_load1_pd(const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vld1q_dup_f64(p)); +#else + return vreinterpretq_m128d_s64(vdupq_n_s64(*(const int64_t *) p)); +#endif +} + +// Load a double-precision (64-bit) floating-point element from memory into the +// upper element of dst, and copy the lower element from a to dst. mem_addr does +// not need to be aligned on any particular boundary. +// +// dst[63:0] := a[63:0] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadh_pd +FORCE_INLINE __m128d _mm_loadh_pd(__m128d a, const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcombine_f64(vget_low_f64(vreinterpretq_f64_m128d(a)), vld1_f64(p))); +#else + return vreinterpretq_m128d_f32(vcombine_f32( + vget_low_f32(vreinterpretq_f32_m128d(a)), vld1_f32((const float *) p))); +#endif +} + +// Load a double-precision (64-bit) floating-point element from memory into both +// elements of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd1 +#define _mm_load_pd1 _mm_load1_pd + +// Load a double-precision (64-bit) floating-point element from memory into both +// elements of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loaddup_pd +#define _mm_loaddup_pd _mm_load1_pd + +// Loads 128-bit value. : +// https://msdn.microsoft.com/zh-cn/library/f4k12ae8(v=vs.90).aspx +FORCE_INLINE __m128i _mm_loadu_si128(const __m128i *p) +{ + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p)); +} + +// Load unaligned 32-bit integer from memory into the first element of dst. +// +// dst[31:0] := MEM[mem_addr+31:mem_addr] +// dst[MAX:32] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si32 +FORCE_INLINE __m128i _mm_loadu_si32(const void *p) +{ + return vreinterpretq_m128i_s32( + vsetq_lane_s32(*(const int32_t *) p, vdupq_n_s32(0), 0)); +} + +// Convert packed double-precision (64-bit) floating-point elements in a to +// packed single-precision (32-bit) floating-point elements, and store the +// results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// k := 64*j +// dst[i+31:i] := Convert_FP64_To_FP32(a[k+64:k]) +// ENDFOR +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_ps +FORCE_INLINE __m128 _mm_cvtpd_ps(__m128d a) +{ +#if defined(__aarch64__) + float32x2_t tmp = vcvt_f32_f64(vreinterpretq_f64_m128d(a)); + return vreinterpretq_m128_f32(vcombine_f32(tmp, vdup_n_f32(0))); +#else + float a0 = (float) ((double *) &a)[0]; + float a1 = (float) ((double *) &a)[1]; + return _mm_set_ps(0, 0, a1, a0); +#endif +} + +// Copy the lower double-precision (64-bit) floating-point element of a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_f64 +FORCE_INLINE double _mm_cvtsd_f64(__m128d a) +{ +#if defined(__aarch64__) + return (double) vgetq_lane_f64(vreinterpretq_f64_m128d(a), 0); +#else + return ((double *) &a)[0]; +#endif +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed double-precision (64-bit) floating-point elements, and store the +// results in dst. +// +// FOR j := 0 to 1 +// i := 64*j +// k := 32*j +// dst[i+63:i] := Convert_FP32_To_FP64(a[k+31:k]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pd +FORCE_INLINE __m128d _mm_cvtps_pd(__m128 a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcvt_f64_f32(vget_low_f32(vreinterpretq_f32_m128(a)))); +#else + double a0 = (double) vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + double a1 = (double) vgetq_lane_f32(vreinterpretq_f32_m128(a), 1); + return _mm_set_pd(a1, a0); +#endif +} + +// Cast vector of type __m128d to type __m128i. This intrinsic is only used for +// compilation and does not generate any instructions, thus it has zero latency. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castpd_si128 +FORCE_INLINE __m128i _mm_castpd_si128(__m128d a) +{ + return vreinterpretq_m128i_s64(vreinterpretq_s64_m128d(a)); +} + +// Blend packed single-precision (32-bit) floating-point elements from a and b +// using mask, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blendv_ps +FORCE_INLINE __m128 _mm_blendv_ps(__m128 a, __m128 b, __m128 mask) +{ + return vreinterpretq_m128_f32(vbslq_f32(vreinterpretq_u32_m128(mask), + vreinterpretq_f32_m128(b), + vreinterpretq_f32_m128(a))); +} + +// Round the packed single-precision (32-bit) floating-point elements in a using +// the rounding parameter, and store the results as packed single-precision +// floating-point elements in dst. +// software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_ps +FORCE_INLINE __m128 _mm_round_ps(__m128 a, int rounding) +{ +#if defined(__aarch64__) + switch (rounding) { + case (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC): + return vreinterpretq_m128_f32(vrndnq_f32(vreinterpretq_f32_m128(a))); + case (_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC): + return vreinterpretq_m128_f32(vrndmq_f32(vreinterpretq_f32_m128(a))); + case (_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC): + return vreinterpretq_m128_f32(vrndpq_f32(vreinterpretq_f32_m128(a))); + case (_MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC): + return vreinterpretq_m128_f32(vrndq_f32(vreinterpretq_f32_m128(a))); + default: //_MM_FROUND_CUR_DIRECTION + return vreinterpretq_m128_f32(vrndiq_f32(vreinterpretq_f32_m128(a))); + } +#else + float *v_float = (float *) &a; + __m128 zero, neg_inf, pos_inf; + + switch (rounding) { + case (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC): + return _mm_cvtepi32_ps(_mm_cvtps_epi32(a)); + case (_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC): + return (__m128){floorf(v_float[0]), floorf(v_float[1]), + floorf(v_float[2]), floorf(v_float[3])}; + case (_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC): + return (__m128){ceilf(v_float[0]), ceilf(v_float[1]), ceilf(v_float[2]), + ceilf(v_float[3])}; + case (_MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC): + zero = _mm_set_ps(0.0f, 0.0f, 0.0f, 0.0f); + neg_inf = _mm_set_ps(floorf(v_float[0]), floorf(v_float[1]), + floorf(v_float[2]), floorf(v_float[3])); + pos_inf = _mm_set_ps(ceilf(v_float[0]), ceilf(v_float[1]), + ceilf(v_float[2]), ceilf(v_float[3])); + return _mm_blendv_ps(pos_inf, neg_inf, _mm_cmple_ps(a, zero)); + default: //_MM_FROUND_CUR_DIRECTION + return (__m128){roundf(v_float[0]), roundf(v_float[1]), + roundf(v_float[2]), roundf(v_float[3])}; + } +#endif +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 32-bit integers, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// dst[i+31:i] := Convert_FP32_To_Int32(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ps2pi +FORCE_INLINE __m64 _mm_cvt_ps2pi(__m128 a) +{ +#if defined(__aarch64__) + return vreinterpret_m64_s32( + vget_low_s32(vcvtnq_s32_f32(vreinterpretq_f32_m128(a)))); +#else + return vreinterpret_m64_s32( + vcvt_s32_f32(vget_low_f32(vreinterpretq_f32_m128( + _mm_round_ps(a, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC))))); +#endif +} + +// Round the packed single-precision (32-bit) floating-point elements in a up to +// an integer value, and store the results as packed single-precision +// floating-point elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_ps +FORCE_INLINE __m128 _mm_ceil_ps(__m128 a) +{ + return _mm_round_ps(a, _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC); +} + +// Round the packed single-precision (32-bit) floating-point elements in a down +// to an integer value, and store the results as packed single-precision +// floating-point elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_ps +FORCE_INLINE __m128 _mm_floor_ps(__m128 a) +{ + return _mm_round_ps(a, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC); +} + + +// Load 128-bits of integer data from unaligned memory into dst. This intrinsic +// may perform better than _mm_loadu_si128 when the data crosses a cache line +// boundary. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_lddqu_si128 +#define _mm_lddqu_si128 _mm_loadu_si128 + +/* Miscellaneous Operations */ + +// Shifts the 8 signed 16-bit integers in a right by count bits while shifting +// in the sign bit. +// +// r0 := a0 >> count +// r1 := a1 >> count +// ... +// r7 := a7 >> count +// +// https://msdn.microsoft.com/en-us/library/3c9997dk(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_sra_epi16(__m128i a, __m128i count) +{ + int64_t c = (int64_t) vget_low_s64((int64x2_t) count); + if (c > 15) + return _mm_cmplt_epi16(a, _mm_setzero_si128()); + return vreinterpretq_m128i_s16(vshlq_s16((int16x8_t) a, vdupq_n_s16(-c))); +} + +// Shifts the 4 signed 32-bit integers in a right by count bits while shifting +// in the sign bit. +// +// r0 := a0 >> count +// r1 := a1 >> count +// r2 := a2 >> count +// r3 := a3 >> count +// +// https://msdn.microsoft.com/en-us/library/ce40009e(v%3dvs.100).aspx +FORCE_INLINE __m128i _mm_sra_epi32(__m128i a, __m128i count) +{ + int64_t c = (int64_t) vget_low_s64((int64x2_t) count); + if (c > 31) + return _mm_cmplt_epi32(a, _mm_setzero_si128()); + return vreinterpretq_m128i_s32(vshlq_s32((int32x4_t) a, vdupq_n_s32(-c))); +} + +// Packs the 16 signed 16-bit integers from a and b into 8-bit integers and +// saturates. +// https://msdn.microsoft.com/en-us/library/k4y4f7w5%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_packs_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vcombine_s8(vqmovn_s16(vreinterpretq_s16_m128i(a)), + vqmovn_s16(vreinterpretq_s16_m128i(b)))); +} + +// Packs the 16 signed 16 - bit integers from a and b into 8 - bit unsigned +// integers and saturates. +// +// r0 := UnsignedSaturate(a0) +// r1 := UnsignedSaturate(a1) +// ... +// r7 := UnsignedSaturate(a7) +// r8 := UnsignedSaturate(b0) +// r9 := UnsignedSaturate(b1) +// ... +// r15 := UnsignedSaturate(b7) +// +// https://msdn.microsoft.com/en-us/library/07ad1wx4(v=vs.100).aspx +FORCE_INLINE __m128i _mm_packus_epi16(const __m128i a, const __m128i b) +{ + return vreinterpretq_m128i_u8( + vcombine_u8(vqmovun_s16(vreinterpretq_s16_m128i(a)), + vqmovun_s16(vreinterpretq_s16_m128i(b)))); +} + +// Packs the 8 signed 32-bit integers from a and b into signed 16-bit integers +// and saturates. +// +// r0 := SignedSaturate(a0) +// r1 := SignedSaturate(a1) +// r2 := SignedSaturate(a2) +// r3 := SignedSaturate(a3) +// r4 := SignedSaturate(b0) +// r5 := SignedSaturate(b1) +// r6 := SignedSaturate(b2) +// r7 := SignedSaturate(b3) +// +// https://msdn.microsoft.com/en-us/library/393t56f9%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_packs_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vcombine_s16(vqmovn_s32(vreinterpretq_s32_m128i(a)), + vqmovn_s32(vreinterpretq_s32_m128i(b)))); +} + +// Packs the 8 unsigned 32-bit integers from a and b into unsigned 16-bit +// integers and saturates. +// +// r0 := UnsignedSaturate(a0) +// r1 := UnsignedSaturate(a1) +// r2 := UnsignedSaturate(a2) +// r3 := UnsignedSaturate(a3) +// r4 := UnsignedSaturate(b0) +// r5 := UnsignedSaturate(b1) +// r6 := UnsignedSaturate(b2) +// r7 := UnsignedSaturate(b3) +FORCE_INLINE __m128i _mm_packus_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vcombine_u16(vqmovun_s32(vreinterpretq_s32_m128i(a)), + vqmovun_s32(vreinterpretq_s32_m128i(b)))); +} + +// Interleaves the lower 8 signed or unsigned 8-bit integers in a with the lower +// 8 signed or unsigned 8-bit integers in b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// ... +// r14 := a7 +// r15 := b7 +// +// https://msdn.microsoft.com/en-us/library/xf7k860c%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_unpacklo_epi8(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s8( + vzip1q_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +#else + int8x8_t a1 = vreinterpret_s8_s16(vget_low_s16(vreinterpretq_s16_m128i(a))); + int8x8_t b1 = vreinterpret_s8_s16(vget_low_s16(vreinterpretq_s16_m128i(b))); + int8x8x2_t result = vzip_s8(a1, b1); + return vreinterpretq_m128i_s8(vcombine_s8(result.val[0], result.val[1])); +#endif +} + +// Interleaves the lower 4 signed or unsigned 16-bit integers in a with the +// lower 4 signed or unsigned 16-bit integers in b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// r4 := a2 +// r5 := b2 +// r6 := a3 +// r7 := b3 +// +// https://msdn.microsoft.com/en-us/library/btxb17bw%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_unpacklo_epi16(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s16( + vzip1q_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +#else + int16x4_t a1 = vget_low_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b1 = vget_low_s16(vreinterpretq_s16_m128i(b)); + int16x4x2_t result = vzip_s16(a1, b1); + return vreinterpretq_m128i_s16(vcombine_s16(result.val[0], result.val[1])); +#endif +} + +// Interleaves the lower 2 signed or unsigned 32 - bit integers in a with the +// lower 2 signed or unsigned 32 - bit integers in b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// +// https://msdn.microsoft.com/en-us/library/x8atst9d(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpacklo_epi32(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s32( + vzip1q_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +#else + int32x2_t a1 = vget_low_s32(vreinterpretq_s32_m128i(a)); + int32x2_t b1 = vget_low_s32(vreinterpretq_s32_m128i(b)); + int32x2x2_t result = vzip_s32(a1, b1); + return vreinterpretq_m128i_s32(vcombine_s32(result.val[0], result.val[1])); +#endif +} + +FORCE_INLINE __m128i _mm_unpacklo_epi64(__m128i a, __m128i b) +{ + int64x1_t a_l = vget_low_s64(vreinterpretq_s64_m128i(a)); + int64x1_t b_l = vget_low_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vcombine_s64(a_l, b_l)); +} + +// Selects and interleaves the lower two single-precision, floating-point values +// from a and b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// +// https://msdn.microsoft.com/en-us/library/25st103b%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_unpacklo_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vzip1q_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x2_t a1 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t b1 = vget_low_f32(vreinterpretq_f32_m128(b)); + float32x2x2_t result = vzip_f32(a1, b1); + return vreinterpretq_m128_f32(vcombine_f32(result.val[0], result.val[1])); +#endif +} + +// Selects and interleaves the upper two single-precision, floating-point values +// from a and b. +// +// r0 := a2 +// r1 := b2 +// r2 := a3 +// r3 := b3 +// +// https://msdn.microsoft.com/en-us/library/skccxx7d%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_unpackhi_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vzip2q_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x2_t a1 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32x2_t b1 = vget_high_f32(vreinterpretq_f32_m128(b)); + float32x2x2_t result = vzip_f32(a1, b1); + return vreinterpretq_m128_f32(vcombine_f32(result.val[0], result.val[1])); +#endif +} + +// Interleaves the upper 8 signed or unsigned 8-bit integers in a with the upper +// 8 signed or unsigned 8-bit integers in b. +// +// r0 := a8 +// r1 := b8 +// r2 := a9 +// r3 := b9 +// ... +// r14 := a15 +// r15 := b15 +// +// https://msdn.microsoft.com/en-us/library/t5h7783k(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpackhi_epi8(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s8( + vzip2q_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +#else + int8x8_t a1 = + vreinterpret_s8_s16(vget_high_s16(vreinterpretq_s16_m128i(a))); + int8x8_t b1 = + vreinterpret_s8_s16(vget_high_s16(vreinterpretq_s16_m128i(b))); + int8x8x2_t result = vzip_s8(a1, b1); + return vreinterpretq_m128i_s8(vcombine_s8(result.val[0], result.val[1])); +#endif +} + +// Interleaves the upper 4 signed or unsigned 16-bit integers in a with the +// upper 4 signed or unsigned 16-bit integers in b. +// +// r0 := a4 +// r1 := b4 +// r2 := a5 +// r3 := b5 +// r4 := a6 +// r5 := b6 +// r6 := a7 +// r7 := b7 +// +// https://msdn.microsoft.com/en-us/library/03196cz7(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpackhi_epi16(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s16( + vzip2q_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +#else + int16x4_t a1 = vget_high_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b1 = vget_high_s16(vreinterpretq_s16_m128i(b)); + int16x4x2_t result = vzip_s16(a1, b1); + return vreinterpretq_m128i_s16(vcombine_s16(result.val[0], result.val[1])); +#endif +} + +// Interleaves the upper 2 signed or unsigned 32-bit integers in a with the +// upper 2 signed or unsigned 32-bit integers in b. +// https://msdn.microsoft.com/en-us/library/65sa7cbs(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpackhi_epi32(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s32( + vzip2q_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +#else + int32x2_t a1 = vget_high_s32(vreinterpretq_s32_m128i(a)); + int32x2_t b1 = vget_high_s32(vreinterpretq_s32_m128i(b)); + int32x2x2_t result = vzip_s32(a1, b1); + return vreinterpretq_m128i_s32(vcombine_s32(result.val[0], result.val[1])); +#endif +} + +// Interleaves the upper signed or unsigned 64-bit integer in a with the +// upper signed or unsigned 64-bit integer in b. +// +// r0 := a1 +// r1 := b1 +FORCE_INLINE __m128i _mm_unpackhi_epi64(__m128i a, __m128i b) +{ + int64x1_t a_h = vget_high_s64(vreinterpretq_s64_m128i(a)); + int64x1_t b_h = vget_high_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vcombine_s64(a_h, b_h)); +} + +// Horizontally compute the minimum amongst the packed unsigned 16-bit integers +// in a, store the minimum and index in dst, and zero the remaining bits in dst. +// +// index[2:0] := 0 +// min[15:0] := a[15:0] +// FOR j := 0 to 7 +// i := j*16 +// IF a[i+15:i] < min[15:0] +// index[2:0] := j +// min[15:0] := a[i+15:i] +// FI +// ENDFOR +// dst[15:0] := min[15:0] +// dst[18:16] := index[2:0] +// dst[127:19] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_minpos_epu16 +FORCE_INLINE __m128i _mm_minpos_epu16(__m128i a) +{ + __m128i dst; + uint16_t min, idx = 0; + // Find the minimum value +#if defined(__aarch64__) + min = vminvq_u16(vreinterpretq_u16_m128i(a)); +#else + __m64 tmp; + tmp = vreinterpret_m64_u16( + vmin_u16(vget_low_u16(vreinterpretq_u16_m128i(a)), + vget_high_u16(vreinterpretq_u16_m128i(a)))); + tmp = vreinterpret_m64_u16( + vpmin_u16(vreinterpret_u16_m64(tmp), vreinterpret_u16_m64(tmp))); + tmp = vreinterpret_m64_u16( + vpmin_u16(vreinterpret_u16_m64(tmp), vreinterpret_u16_m64(tmp))); + min = vget_lane_u16(vreinterpret_u16_m64(tmp), 0); +#endif + // Get the index of the minimum value + int i; + for (i = 0; i < 8; i++) { + if (min == vgetq_lane_u16(vreinterpretq_u16_m128i(a), 0)) { + idx = (uint16_t) i; + break; + } + a = _mm_srli_si128(a, 2); + } + // Generate result + dst = _mm_setzero_si128(); + dst = vreinterpretq_m128i_u16( + vsetq_lane_u16(min, vreinterpretq_u16_m128i(dst), 0)); + dst = vreinterpretq_m128i_u16( + vsetq_lane_u16(idx, vreinterpretq_u16_m128i(dst), 1)); + return dst; +} + +// shift to right +// https://msdn.microsoft.com/en-us/library/bb514041(v=vs.120).aspx +// http://blog.csdn.net/hemmingway/article/details/44828303 +// Clang requires a macro here, as it is extremely picky about c being a +// literal. +#define _mm_alignr_epi8(a, b, c) \ + ((__m128i) vextq_s8((int8x16_t)(b), (int8x16_t)(a), (c))) + +// Compute the bitwise AND of 128 bits (representing integer data) in a and b, +// and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the +// bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, +// otherwise set CF to 0. Return the CF value. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testc_si128 +FORCE_INLINE int _mm_testc_si128(__m128i a, __m128i b) +{ + int64x2_t s64 = + vandq_s64(vreinterpretq_s64_s32(vmvnq_s32(vreinterpretq_s32_m128i(a))), + vreinterpretq_s64_m128i(b)); + return !(vgetq_lane_s64(s64, 0) | vgetq_lane_s64(s64, 1)); +} + +// Compute the bitwise AND of 128 bits (representing integer data) in a and b, +// and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the +// bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, +// otherwise set CF to 0. Return the ZF value. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testz_si128 +FORCE_INLINE int _mm_testz_si128(__m128i a, __m128i b) +{ + int64x2_t s64 = + vandq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b)); + return !(vgetq_lane_s64(s64, 0) | vgetq_lane_s64(s64, 1)); +} + +// Extracts the selected signed or unsigned 8-bit integer from a and zero +// extends. +// FORCE_INLINE int _mm_extract_epi8(__m128i a, __constrange(0,16) int imm) +#define _mm_extract_epi8(a, imm) vgetq_lane_u8(vreinterpretq_u8_m128i(a), (imm)) + +// Inserts the least significant 8 bits of b into the selected 8-bit integer +// of a. +// FORCE_INLINE __m128i _mm_insert_epi8(__m128i a, int b, +// __constrange(0,16) int imm) +#define _mm_insert_epi8(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s8( \ + vsetq_lane_s8((b), vreinterpretq_s8_m128i(a), (imm))); \ + }) + +// Extracts the selected signed or unsigned 16-bit integer from a and zero +// extends. +// https://msdn.microsoft.com/en-us/library/6dceta0c(v=vs.100).aspx +// FORCE_INLINE int _mm_extract_epi16(__m128i a, __constrange(0,8) int imm) +#define _mm_extract_epi16(a, imm) \ + vgetq_lane_u16(vreinterpretq_u16_m128i(a), (imm)) + +// Inserts the least significant 16 bits of b into the selected 16-bit integer +// of a. +// https://msdn.microsoft.com/en-us/library/kaze8hz1%28v=vs.100%29.aspx +// FORCE_INLINE __m128i _mm_insert_epi16(__m128i a, int b, +// __constrange(0,8) int imm) +#define _mm_insert_epi16(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s16( \ + vsetq_lane_s16((b), vreinterpretq_s16_m128i(a), (imm))); \ + }) + +// Extracts the selected signed or unsigned 32-bit integer from a and zero +// extends. +// FORCE_INLINE int _mm_extract_epi32(__m128i a, __constrange(0,4) int imm) +#define _mm_extract_epi32(a, imm) \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm)) + +// Extracts the selected single-precision (32-bit) floating-point from a. +// FORCE_INLINE int _mm_extract_ps(__m128 a, __constrange(0,4) int imm) +#define _mm_extract_ps(a, imm) vgetq_lane_s32(vreinterpretq_s32_m128(a), (imm)) + +// Inserts the least significant 32 bits of b into the selected 32-bit integer +// of a. +// FORCE_INLINE __m128i _mm_insert_epi32(__m128i a, int b, +// __constrange(0,4) int imm) +#define _mm_insert_epi32(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s32( \ + vsetq_lane_s32((b), vreinterpretq_s32_m128i(a), (imm))); \ + }) + +// Extracts the selected signed or unsigned 64-bit integer from a and zero +// extends. +// FORCE_INLINE __int64 _mm_extract_epi64(__m128i a, __constrange(0,2) int imm) +#define _mm_extract_epi64(a, imm) \ + vgetq_lane_s64(vreinterpretq_s64_m128i(a), (imm)) + +// Inserts the least significant 64 bits of b into the selected 64-bit integer +// of a. +// FORCE_INLINE __m128i _mm_insert_epi64(__m128i a, __int64 b, +// __constrange(0,2) int imm) +#define _mm_insert_epi64(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s64( \ + vsetq_lane_s64((b), vreinterpretq_s64_m128i(a), (imm))); \ + }) + +// Count the number of bits set to 1 in unsigned 32-bit integer a, and +// return that count in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u32 +FORCE_INLINE int _mm_popcnt_u32(unsigned int a) +{ +#if defined(__aarch64__) +#if __has_builtin(__builtin_popcount) + return __builtin_popcount(a); +#else + return (int) vaddlv_u8(vcnt_u8(vcreate_u8((uint64_t) a))); +#endif +#else + uint32_t count = 0; + uint8x8_t input_val, count8x8_val; + uint16x4_t count16x4_val; + uint32x2_t count32x2_val; + + input_val = vld1_u8((uint8_t *) &a); + count8x8_val = vcnt_u8(input_val); + count16x4_val = vpaddl_u8(count8x8_val); + count32x2_val = vpaddl_u16(count16x4_val); + + vst1_u32(&count, count32x2_val); + return count; +#endif +} + +// Count the number of bits set to 1 in unsigned 64-bit integer a, and +// return that count in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u64 +FORCE_INLINE int64_t _mm_popcnt_u64(uint64_t a) +{ +#if defined(__aarch64__) +#if __has_builtin(__builtin_popcountll) + return __builtin_popcountll(a); +#else + return (int64_t) vaddlv_u8(vcnt_u8(vcreate_u8(a))); +#endif +#else + uint64_t count = 0; + uint8x8_t input_val, count8x8_val; + uint16x4_t count16x4_val; + uint32x2_t count32x2_val; + uint64x1_t count64x1_val; + + input_val = vld1_u8((uint8_t *) &a); + count8x8_val = vcnt_u8(input_val); + count16x4_val = vpaddl_u8(count8x8_val); + count32x2_val = vpaddl_u16(count16x4_val); + count64x1_val = vpaddl_u32(count32x2_val); + vst1_u64(&count, count64x1_val); + return count; +#endif +} + +// Macro: Transpose the 4x4 matrix formed by the 4 rows of single-precision +// (32-bit) floating-point elements in row0, row1, row2, and row3, and store the +// transposed matrix in these vectors (row0 now contains column 0, etc.). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=MM_TRANSPOSE4_PS +#define _MM_TRANSPOSE4_PS(row0, row1, row2, row3) \ + do { \ + float32x4x2_t ROW01 = vtrnq_f32(row0, row1); \ + float32x4x2_t ROW23 = vtrnq_f32(row2, row3); \ + row0 = vcombine_f32(vget_low_f32(ROW01.val[0]), \ + vget_low_f32(ROW23.val[0])); \ + row1 = vcombine_f32(vget_low_f32(ROW01.val[1]), \ + vget_low_f32(ROW23.val[1])); \ + row2 = vcombine_f32(vget_high_f32(ROW01.val[0]), \ + vget_high_f32(ROW23.val[0])); \ + row3 = vcombine_f32(vget_high_f32(ROW01.val[1]), \ + vget_high_f32(ROW23.val[1])); \ + } while (0) + +/* Crypto Extensions */ + +#if defined(__ARM_FEATURE_CRYPTO) +// Wraps vmull_p64 +FORCE_INLINE uint64x2_t _sse2neon_vmull_p64(uint64x1_t _a, uint64x1_t _b) +{ + poly64_t a = vget_lane_p64(vreinterpret_p64_u64(_a), 0); + poly64_t b = vget_lane_p64(vreinterpret_p64_u64(_b), 0); + return vreinterpretq_u64_p128(vmull_p64(a, b)); +} +#else // ARMv7 polyfill +// ARMv7/some A64 lacks vmull_p64, but it has vmull_p8. +// +// vmull_p8 calculates 8 8-bit->16-bit polynomial multiplies, but we need a +// 64-bit->128-bit polynomial multiply. +// +// It needs some work and is somewhat slow, but it is still faster than all +// known scalar methods. +// +// Algorithm adapted to C from +// https://www.workofard.com/2017/07/ghash-for-low-end-cores/, which is adapted +// from "Fast Software Polynomial Multiplication on ARM Processors Using the +// NEON Engine" by Danilo Camara, Conrado Gouvea, Julio Lopez and Ricardo Dahab +// (https://hal.inria.fr/hal-01506572) +static uint64x2_t _sse2neon_vmull_p64(uint64x1_t _a, uint64x1_t _b) +{ + poly8x8_t a = vreinterpret_p8_u64(_a); + poly8x8_t b = vreinterpret_p8_u64(_b); + + // Masks + uint8x16_t k48_32 = vcombine_u8(vcreate_u8(0x0000ffffffffffff), + vcreate_u8(0x00000000ffffffff)); + uint8x16_t k16_00 = vcombine_u8(vcreate_u8(0x000000000000ffff), + vcreate_u8(0x0000000000000000)); + + // Do the multiplies, rotating with vext to get all combinations + uint8x16_t d = vreinterpretq_u8_p16(vmull_p8(a, b)); // D = A0 * B0 + uint8x16_t e = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 1))); // E = A0 * B1 + uint8x16_t f = + vreinterpretq_u8_p16(vmull_p8(vext_p8(a, a, 1), b)); // F = A1 * B0 + uint8x16_t g = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 2))); // G = A0 * B2 + uint8x16_t h = + vreinterpretq_u8_p16(vmull_p8(vext_p8(a, a, 2), b)); // H = A2 * B0 + uint8x16_t i = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 3))); // I = A0 * B3 + uint8x16_t j = + vreinterpretq_u8_p16(vmull_p8(vext_p8(a, a, 3), b)); // J = A3 * B0 + uint8x16_t k = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 4))); // L = A0 * B4 + + // Add cross products + uint8x16_t l = veorq_u8(e, f); // L = E + F + uint8x16_t m = veorq_u8(g, h); // M = G + H + uint8x16_t n = veorq_u8(i, j); // N = I + J + + // Interleave. Using vzip1 and vzip2 prevents Clang from emitting TBL + // instructions. +#if defined(__aarch64__) + uint8x16_t lm_p0 = vreinterpretq_u8_u64( + vzip1q_u64(vreinterpretq_u64_u8(l), vreinterpretq_u64_u8(m))); + uint8x16_t lm_p1 = vreinterpretq_u8_u64( + vzip2q_u64(vreinterpretq_u64_u8(l), vreinterpretq_u64_u8(m))); + uint8x16_t nk_p0 = vreinterpretq_u8_u64( + vzip1q_u64(vreinterpretq_u64_u8(n), vreinterpretq_u64_u8(k))); + uint8x16_t nk_p1 = vreinterpretq_u8_u64( + vzip2q_u64(vreinterpretq_u64_u8(n), vreinterpretq_u64_u8(k))); +#else + uint8x16_t lm_p0 = vcombine_u8(vget_low_u8(l), vget_low_u8(m)); + uint8x16_t lm_p1 = vcombine_u8(vget_high_u8(l), vget_high_u8(m)); + uint8x16_t nk_p0 = vcombine_u8(vget_low_u8(n), vget_low_u8(k)); + uint8x16_t nk_p1 = vcombine_u8(vget_high_u8(n), vget_high_u8(k)); +#endif + // t0 = (L) (P0 + P1) << 8 + // t1 = (M) (P2 + P3) << 16 + uint8x16_t t0t1_tmp = veorq_u8(lm_p0, lm_p1); + uint8x16_t t0t1_h = vandq_u8(lm_p1, k48_32); + uint8x16_t t0t1_l = veorq_u8(t0t1_tmp, t0t1_h); + + // t2 = (N) (P4 + P5) << 24 + // t3 = (K) (P6 + P7) << 32 + uint8x16_t t2t3_tmp = veorq_u8(nk_p0, nk_p1); + uint8x16_t t2t3_h = vandq_u8(nk_p1, k16_00); + uint8x16_t t2t3_l = veorq_u8(t2t3_tmp, t2t3_h); + + // De-interleave +#if defined(__aarch64__) + uint8x16_t t0 = vreinterpretq_u8_u64( + vuzp1q_u64(vreinterpretq_u64_u8(t0t1_l), vreinterpretq_u64_u8(t0t1_h))); + uint8x16_t t1 = vreinterpretq_u8_u64( + vuzp2q_u64(vreinterpretq_u64_u8(t0t1_l), vreinterpretq_u64_u8(t0t1_h))); + uint8x16_t t2 = vreinterpretq_u8_u64( + vuzp1q_u64(vreinterpretq_u64_u8(t2t3_l), vreinterpretq_u64_u8(t2t3_h))); + uint8x16_t t3 = vreinterpretq_u8_u64( + vuzp2q_u64(vreinterpretq_u64_u8(t2t3_l), vreinterpretq_u64_u8(t2t3_h))); +#else + uint8x16_t t1 = vcombine_u8(vget_high_u8(t0t1_l), vget_high_u8(t0t1_h)); + uint8x16_t t0 = vcombine_u8(vget_low_u8(t0t1_l), vget_low_u8(t0t1_h)); + uint8x16_t t3 = vcombine_u8(vget_high_u8(t2t3_l), vget_high_u8(t2t3_h)); + uint8x16_t t2 = vcombine_u8(vget_low_u8(t2t3_l), vget_low_u8(t2t3_h)); +#endif + // Shift the cross products + uint8x16_t t0_shift = vextq_u8(t0, t0, 15); // t0 << 8 + uint8x16_t t1_shift = vextq_u8(t1, t1, 14); // t1 << 16 + uint8x16_t t2_shift = vextq_u8(t2, t2, 13); // t2 << 24 + uint8x16_t t3_shift = vextq_u8(t3, t3, 12); // t3 << 32 + + // Accumulate the products + uint8x16_t cross1 = veorq_u8(t0_shift, t1_shift); + uint8x16_t cross2 = veorq_u8(t2_shift, t3_shift); + uint8x16_t mix = veorq_u8(d, cross1); + uint8x16_t r = veorq_u8(mix, cross2); + return vreinterpretq_u64_u8(r); +} +#endif // ARMv7 polyfill + +FORCE_INLINE __m128i _mm_clmulepi64_si128(__m128i _a, __m128i _b, const int imm) +{ + uint64x2_t a = vreinterpretq_u64_m128i(_a); + uint64x2_t b = vreinterpretq_u64_m128i(_b); + switch (imm & 0x11) { + case 0x00: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_low_u64(a), vget_low_u64(b))); + case 0x01: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_high_u64(a), vget_low_u64(b))); + case 0x10: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_low_u64(a), vget_high_u64(b))); + case 0x11: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_high_u64(a), vget_high_u64(b))); + default: + abort(); + } +} + +#if !defined(__ARM_FEATURE_CRYPTO) +/* clang-format off */ +#define SSE2NEON_AES_DATA(w) \ + { \ + w(0x63), w(0x7c), w(0x77), w(0x7b), w(0xf2), w(0x6b), w(0x6f), \ + w(0xc5), w(0x30), w(0x01), w(0x67), w(0x2b), w(0xfe), w(0xd7), \ + w(0xab), w(0x76), w(0xca), w(0x82), w(0xc9), w(0x7d), w(0xfa), \ + w(0x59), w(0x47), w(0xf0), w(0xad), w(0xd4), w(0xa2), w(0xaf), \ + w(0x9c), w(0xa4), w(0x72), w(0xc0), w(0xb7), w(0xfd), w(0x93), \ + w(0x26), w(0x36), w(0x3f), w(0xf7), w(0xcc), w(0x34), w(0xa5), \ + w(0xe5), w(0xf1), w(0x71), w(0xd8), w(0x31), w(0x15), w(0x04), \ + w(0xc7), w(0x23), w(0xc3), w(0x18), w(0x96), w(0x05), w(0x9a), \ + w(0x07), w(0x12), w(0x80), w(0xe2), w(0xeb), w(0x27), w(0xb2), \ + w(0x75), w(0x09), w(0x83), w(0x2c), w(0x1a), w(0x1b), w(0x6e), \ + w(0x5a), w(0xa0), w(0x52), w(0x3b), w(0xd6), w(0xb3), w(0x29), \ + w(0xe3), w(0x2f), w(0x84), w(0x53), w(0xd1), w(0x00), w(0xed), \ + w(0x20), w(0xfc), w(0xb1), w(0x5b), w(0x6a), w(0xcb), w(0xbe), \ + w(0x39), w(0x4a), w(0x4c), w(0x58), w(0xcf), w(0xd0), w(0xef), \ + w(0xaa), w(0xfb), w(0x43), w(0x4d), w(0x33), w(0x85), w(0x45), \ + w(0xf9), w(0x02), w(0x7f), w(0x50), w(0x3c), w(0x9f), w(0xa8), \ + w(0x51), w(0xa3), w(0x40), w(0x8f), w(0x92), w(0x9d), w(0x38), \ + w(0xf5), w(0xbc), w(0xb6), w(0xda), w(0x21), w(0x10), w(0xff), \ + w(0xf3), w(0xd2), w(0xcd), w(0x0c), w(0x13), w(0xec), w(0x5f), \ + w(0x97), w(0x44), w(0x17), w(0xc4), w(0xa7), w(0x7e), w(0x3d), \ + w(0x64), w(0x5d), w(0x19), w(0x73), w(0x60), w(0x81), w(0x4f), \ + w(0xdc), w(0x22), w(0x2a), w(0x90), w(0x88), w(0x46), w(0xee), \ + w(0xb8), w(0x14), w(0xde), w(0x5e), w(0x0b), w(0xdb), w(0xe0), \ + w(0x32), w(0x3a), w(0x0a), w(0x49), w(0x06), w(0x24), w(0x5c), \ + w(0xc2), w(0xd3), w(0xac), w(0x62), w(0x91), w(0x95), w(0xe4), \ + w(0x79), w(0xe7), w(0xc8), w(0x37), w(0x6d), w(0x8d), w(0xd5), \ + w(0x4e), w(0xa9), w(0x6c), w(0x56), w(0xf4), w(0xea), w(0x65), \ + w(0x7a), w(0xae), w(0x08), w(0xba), w(0x78), w(0x25), w(0x2e), \ + w(0x1c), w(0xa6), w(0xb4), w(0xc6), w(0xe8), w(0xdd), w(0x74), \ + w(0x1f), w(0x4b), w(0xbd), w(0x8b), w(0x8a), w(0x70), w(0x3e), \ + w(0xb5), w(0x66), w(0x48), w(0x03), w(0xf6), w(0x0e), w(0x61), \ + w(0x35), w(0x57), w(0xb9), w(0x86), w(0xc1), w(0x1d), w(0x9e), \ + w(0xe1), w(0xf8), w(0x98), w(0x11), w(0x69), w(0xd9), w(0x8e), \ + w(0x94), w(0x9b), w(0x1e), w(0x87), w(0xe9), w(0xce), w(0x55), \ + w(0x28), w(0xdf), w(0x8c), w(0xa1), w(0x89), w(0x0d), w(0xbf), \ + w(0xe6), w(0x42), w(0x68), w(0x41), w(0x99), w(0x2d), w(0x0f), \ + w(0xb0), w(0x54), w(0xbb), w(0x16) \ + } +/* clang-format on */ + +/* X Macro trick. See https://en.wikipedia.org/wiki/X_Macro */ +#define SSE2NEON_AES_H0(x) (x) +static const uint8_t SSE2NEON_sbox[256] = SSE2NEON_AES_DATA(SSE2NEON_AES_H0); +#undef SSE2NEON_AES_H0 + +// In the absence of crypto extensions, implement aesenc using regular neon +// intrinsics instead. See: +// https://www.workofard.com/2017/01/accelerated-aes-for-the-arm64-linux-kernel/ +// https://www.workofard.com/2017/07/ghash-for-low-end-cores/ and +// https://github.com/ColinIanKing/linux-next-mirror/blob/b5f466091e130caaf0735976648f72bd5e09aa84/crypto/aegis128-neon-inner.c#L52 +// for more information Reproduced with permission of the author. +FORCE_INLINE __m128i _mm_aesenc_si128(__m128i EncBlock, __m128i RoundKey) +{ +#if defined(__aarch64__) + static const uint8_t shift_rows[] = {0x0, 0x5, 0xa, 0xf, 0x4, 0x9, + 0xe, 0x3, 0x8, 0xd, 0x2, 0x7, + 0xc, 0x1, 0x6, 0xb}; + static const uint8_t ror32by8[] = {0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4, + 0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc}; + + uint8x16_t v; + uint8x16_t w = vreinterpretq_u8_m128i(EncBlock); + + // shift rows + w = vqtbl1q_u8(w, vld1q_u8(shift_rows)); + + // sub bytes + v = vqtbl4q_u8(vld1q_u8_x4(SSE2NEON_sbox), w); + v = vqtbx4q_u8(v, vld1q_u8_x4(SSE2NEON_sbox + 0x40), w - 0x40); + v = vqtbx4q_u8(v, vld1q_u8_x4(SSE2NEON_sbox + 0x80), w - 0x80); + v = vqtbx4q_u8(v, vld1q_u8_x4(SSE2NEON_sbox + 0xc0), w - 0xc0); + + // mix columns + w = (v << 1) ^ (uint8x16_t)(((int8x16_t) v >> 7) & 0x1b); + w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v); + w ^= vqtbl1q_u8(v ^ w, vld1q_u8(ror32by8)); + + // add round key + return vreinterpretq_m128i_u8(w) ^ RoundKey; + +#else /* ARMv7-A NEON implementation */ +#define SSE2NEON_AES_B2W(b0, b1, b2, b3) \ + (((uint32_t)(b3) << 24) | ((uint32_t)(b2) << 16) | ((uint32_t)(b1) << 8) | \ + (b0)) +#define SSE2NEON_AES_F2(x) ((x << 1) ^ (((x >> 7) & 1) * 0x011b /* WPOLY */)) +#define SSE2NEON_AES_F3(x) (SSE2NEON_AES_F2(x) ^ x) +#define SSE2NEON_AES_U0(p) \ + SSE2NEON_AES_B2W(SSE2NEON_AES_F2(p), p, p, SSE2NEON_AES_F3(p)) +#define SSE2NEON_AES_U1(p) \ + SSE2NEON_AES_B2W(SSE2NEON_AES_F3(p), SSE2NEON_AES_F2(p), p, p) +#define SSE2NEON_AES_U2(p) \ + SSE2NEON_AES_B2W(p, SSE2NEON_AES_F3(p), SSE2NEON_AES_F2(p), p) +#define SSE2NEON_AES_U3(p) \ + SSE2NEON_AES_B2W(p, p, SSE2NEON_AES_F3(p), SSE2NEON_AES_F2(p)) + static const uint32_t ALIGN_STRUCT(16) aes_table[4][256] = { + SSE2NEON_AES_DATA(SSE2NEON_AES_U0), + SSE2NEON_AES_DATA(SSE2NEON_AES_U1), + SSE2NEON_AES_DATA(SSE2NEON_AES_U2), + SSE2NEON_AES_DATA(SSE2NEON_AES_U3), + }; +#undef SSE2NEON_AES_B2W +#undef SSE2NEON_AES_F2 +#undef SSE2NEON_AES_F3 +#undef SSE2NEON_AES_U0 +#undef SSE2NEON_AES_U1 +#undef SSE2NEON_AES_U2 +#undef SSE2NEON_AES_U3 + + uint32_t x0 = _mm_cvtsi128_si32(EncBlock); + uint32_t x1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(EncBlock, 0x55)); + uint32_t x2 = _mm_cvtsi128_si32(_mm_shuffle_epi32(EncBlock, 0xAA)); + uint32_t x3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(EncBlock, 0xFF)); + + __m128i out = _mm_set_epi32( + (aes_table[0][x3 & 0xff] ^ aes_table[1][(x0 >> 8) & 0xff] ^ + aes_table[2][(x1 >> 16) & 0xff] ^ aes_table[3][x2 >> 24]), + (aes_table[0][x2 & 0xff] ^ aes_table[1][(x3 >> 8) & 0xff] ^ + aes_table[2][(x0 >> 16) & 0xff] ^ aes_table[3][x1 >> 24]), + (aes_table[0][x1 & 0xff] ^ aes_table[1][(x2 >> 8) & 0xff] ^ + aes_table[2][(x3 >> 16) & 0xff] ^ aes_table[3][x0 >> 24]), + (aes_table[0][x0 & 0xff] ^ aes_table[1][(x1 >> 8) & 0xff] ^ + aes_table[2][(x2 >> 16) & 0xff] ^ aes_table[3][x3 >> 24])); + + return _mm_xor_si128(out, RoundKey); +#endif +} + +FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) +{ + /* FIXME: optimized for NEON */ + uint8_t v[4][4] = { + [0] = {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 0)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 5)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 10)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 15)]}, + [1] = {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 4)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 9)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 14)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 3)]}, + [2] = {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 8)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 13)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 2)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 7)]}, + [3] = {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 12)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 1)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 6)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 11)]}, + }; + for (int i = 0; i < 16; i++) + vreinterpretq_nth_u8_m128i(a, i) = + v[i / 4][i % 4] ^ vreinterpretq_nth_u8_m128i(RoundKey, i); + return a; +} + +// Emits the Advanced Encryption Standard (AES) instruction aeskeygenassist. +// This instruction generates a round key for AES encryption. See +// https://kazakov.life/2017/11/01/cryptocurrency-mining-on-ios-devices/ +// for details. +// +// https://msdn.microsoft.com/en-us/library/cc714138(v=vs.120).aspx +FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i key, const int rcon) +{ + uint32_t X1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0x55)); + uint32_t X3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0xFF)); + for (int i = 0; i < 4; ++i) { + ((uint8_t *) &X1)[i] = SSE2NEON_sbox[((uint8_t *) &X1)[i]]; + ((uint8_t *) &X3)[i] = SSE2NEON_sbox[((uint8_t *) &X3)[i]]; + } + return _mm_set_epi32(((X3 >> 8) | (X3 << 24)) ^ rcon, X3, + ((X1 >> 8) | (X1 << 24)) ^ rcon, X1); +} +#undef SSE2NEON_AES_DATA + +#else /* __ARM_FEATURE_CRYPTO */ +// Implements equivalent of 'aesenc' by combining AESE (with an empty key) and +// AESMC and then manually applying the real key as an xor operation. This +// unfortunately means an additional xor op; the compiler should be able to +// optimize this away for repeated calls however. See +// https://blog.michaelbrase.com/2018/05/08/emulating-x86-aes-intrinsics-on-armv8-a +// for more details. +FORCE_INLINE __m128i _mm_aesenc_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vaesmcq_u8(vaeseq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0))) ^ + vreinterpretq_u8_m128i(b)); +} + +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_aesenclast_si128 +FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) +{ + return _mm_xor_si128(vreinterpretq_m128i_u8(vaeseq_u8( + vreinterpretq_u8_m128i(a), vdupq_n_u8(0))), + RoundKey); +} + +FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i a, const int rcon) +{ + // AESE does ShiftRows and SubBytes on A + uint8x16_t u8 = vaeseq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0)); + + uint8x16_t dest = { + // Undo ShiftRows step from AESE and extract X1 and X3 + u8[0x4], u8[0x1], u8[0xE], u8[0xB], // SubBytes(X1) + u8[0x1], u8[0xE], u8[0xB], u8[0x4], // ROT(SubBytes(X1)) + u8[0xC], u8[0x9], u8[0x6], u8[0x3], // SubBytes(X3) + u8[0x9], u8[0x6], u8[0x3], u8[0xC], // ROT(SubBytes(X3)) + }; + uint32x4_t r = {0, (unsigned) rcon, 0, (unsigned) rcon}; + return vreinterpretq_m128i_u8(dest) ^ vreinterpretq_m128i_u32(r); +} +#endif + +/* Streaming Extensions */ + +// Guarantees that every preceding store is globally visible before any +// subsequent store. +// https://msdn.microsoft.com/en-us/library/5h2w73d1%28v=vs.90%29.aspx +FORCE_INLINE void _mm_sfence(void) +{ + __sync_synchronize(); +} + +// Store 128-bits (composed of 4 packed single-precision (32-bit) floating- +// point elements) from a into memory using a non-temporal memory hint. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_ps +FORCE_INLINE void _mm_stream_ps(float *p, __m128 a) +{ +#if __has_builtin(__builtin_nontemporal_store) + __builtin_nontemporal_store(a, (float32x4_t *) p); +#else + vst1q_f32(p, vreinterpretq_f32_m128(a)); +#endif +} + +// Stores the data in a to the address p without polluting the caches. If the +// cache line containing address p is already in the cache, the cache will be +// updated. +// https://msdn.microsoft.com/en-us/library/ba08y07y%28v=vs.90%29.aspx +FORCE_INLINE void _mm_stream_si128(__m128i *p, __m128i a) +{ +#if __has_builtin(__builtin_nontemporal_store) + __builtin_nontemporal_store(a, p); +#else + vst1q_s64((int64_t *) p, vreinterpretq_s64_m128i(a)); +#endif +} + +// Load 128-bits of integer data from memory into dst using a non-temporal +// memory hint. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_load_si128 +FORCE_INLINE __m128i _mm_stream_load_si128(__m128i *p) +{ +#if __has_builtin(__builtin_nontemporal_store) + return __builtin_nontemporal_load(p); +#else + return vreinterpretq_m128i_s64(vld1q_s64((int64_t *) p)); +#endif +} + +// Cache line containing p is flushed and invalidated from all caches in the +// coherency domain. : +// https://msdn.microsoft.com/en-us/library/ba08y07y(v=vs.100).aspx +FORCE_INLINE void _mm_clflush(void const *p) +{ + (void) p; + // no corollary for Neon? +} + +// Allocate aligned blocks of memory. +// https://software.intel.com/en-us/ +// cpp-compiler-developer-guide-and-reference-allocating-and-freeing-aligned-memory-blocks +FORCE_INLINE void *_mm_malloc(size_t size, size_t align) +{ + void *ptr; + if (align == 1) + return malloc(size); + if (align == 2 || (sizeof(void *) == 8 && align == 4)) + align = sizeof(void *); + if (!posix_memalign(&ptr, align, size)) + return ptr; + return NULL; +} + +FORCE_INLINE void _mm_free(void *addr) +{ + free(addr); +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 8-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb514036(v=vs.100) +FORCE_INLINE uint32_t _mm_crc32_u8(uint32_t crc, uint8_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32cb %w[c], %w[c], %w[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc ^= v; + for (int bit = 0; bit < 8; bit++) { + if (crc & 1) + crc = (crc >> 1) ^ UINT32_C(0x82f63b78); + else + crc = (crc >> 1); + } +#endif + return crc; +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 16-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb531411(v=vs.100) +FORCE_INLINE uint32_t _mm_crc32_u16(uint32_t crc, uint16_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32ch %w[c], %w[c], %w[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc = _mm_crc32_u8(crc, v & 0xff); + crc = _mm_crc32_u8(crc, (v >> 8) & 0xff); +#endif + return crc; +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 32-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb531394(v=vs.100) +FORCE_INLINE uint32_t _mm_crc32_u32(uint32_t crc, uint32_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32cw %w[c], %w[c], %w[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc = _mm_crc32_u16(crc, v & 0xffff); + crc = _mm_crc32_u16(crc, (v >> 16) & 0xffff); +#endif + return crc; +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 64-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb514033(v=vs.100) +FORCE_INLINE uint64_t _mm_crc32_u64(uint64_t crc, uint64_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32cx %w[c], %w[c], %x[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc = _mm_crc32_u32((uint32_t)(crc), v & 0xffffffff); + crc = _mm_crc32_u32((uint32_t)(crc), (v >> 32) & 0xffffffff); +#endif + return crc; +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma pop_macro("ALIGN_STRUCT") +#pragma pop_macro("FORCE_INLINE") +#endif + +#if defined(__GNUC__) +#pragma GCC pop_options +#endif + +#endif diff --git a/.venv/include/site/python3.8/pygame/mask.h b/.venv/include/site/python3.8/pygame/mask.h new file mode 100644 index 0000000..45ad8c5 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/mask.h @@ -0,0 +1,7 @@ +#ifndef PGMASK_INTERNAL_H +#define PGMASK_INTERNAL_H + +#include "include/pygame_mask.h" +#define PYGAMEAPI_MASK_NUMSLOTS 1 + +#endif /* ~PGMASK_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/mixer.h b/.venv/include/site/python3.8/pygame/mixer.h new file mode 100644 index 0000000..97f5a0f --- /dev/null +++ b/.venv/include/site/python3.8/pygame/mixer.h @@ -0,0 +1,14 @@ +#ifndef MIXER_INTERNAL_H +#define MIXER_INTERNAL_H + +#include + +/* test mixer initializations */ +#define MIXER_INIT_CHECK() \ + if (!SDL_WasInit(SDL_INIT_AUDIO)) \ + return RAISE(pgExc_SDLError, "mixer not initialized") + +#define PYGAMEAPI_MIXER_NUMSLOTS 5 +#include "include/pygame_mixer.h" + +#endif /* ~MIXER_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/palette.h b/.venv/include/site/python3.8/pygame/palette.h new file mode 100644 index 0000000..1ae4cf6 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/palette.h @@ -0,0 +1,123 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +#ifndef PALETTE_H +#define PALETTE_H + +#include + +/* SDL 2 does not assign a default palette color scheme to a new 8 bit + * surface. Instead, the palette is set all white. This defines the SDL 1.2 + * default palette. + */ +static const SDL_Color default_palette_colors[] = { + {0, 0, 0, 255}, {0, 0, 85, 255}, {0, 0, 170, 255}, + {0, 0, 255, 255}, {0, 36, 0, 255}, {0, 36, 85, 255}, + {0, 36, 170, 255}, {0, 36, 255, 255}, {0, 73, 0, 255}, + {0, 73, 85, 255}, {0, 73, 170, 255}, {0, 73, 255, 255}, + {0, 109, 0, 255}, {0, 109, 85, 255}, {0, 109, 170, 255}, + {0, 109, 255, 255}, {0, 146, 0, 255}, {0, 146, 85, 255}, + {0, 146, 170, 255}, {0, 146, 255, 255}, {0, 182, 0, 255}, + {0, 182, 85, 255}, {0, 182, 170, 255}, {0, 182, 255, 255}, + {0, 219, 0, 255}, {0, 219, 85, 255}, {0, 219, 170, 255}, + {0, 219, 255, 255}, {0, 255, 0, 255}, {0, 255, 85, 255}, + {0, 255, 170, 255}, {0, 255, 255, 255}, {85, 0, 0, 255}, + {85, 0, 85, 255}, {85, 0, 170, 255}, {85, 0, 255, 255}, + {85, 36, 0, 255}, {85, 36, 85, 255}, {85, 36, 170, 255}, + {85, 36, 255, 255}, {85, 73, 0, 255}, {85, 73, 85, 255}, + {85, 73, 170, 255}, {85, 73, 255, 255}, {85, 109, 0, 255}, + {85, 109, 85, 255}, {85, 109, 170, 255}, {85, 109, 255, 255}, + {85, 146, 0, 255}, {85, 146, 85, 255}, {85, 146, 170, 255}, + {85, 146, 255, 255}, {85, 182, 0, 255}, {85, 182, 85, 255}, + {85, 182, 170, 255}, {85, 182, 255, 255}, {85, 219, 0, 255}, + {85, 219, 85, 255}, {85, 219, 170, 255}, {85, 219, 255, 255}, + {85, 255, 0, 255}, {85, 255, 85, 255}, {85, 255, 170, 255}, + {85, 255, 255, 255}, {170, 0, 0, 255}, {170, 0, 85, 255}, + {170, 0, 170, 255}, {170, 0, 255, 255}, {170, 36, 0, 255}, + {170, 36, 85, 255}, {170, 36, 170, 255}, {170, 36, 255, 255}, + {170, 73, 0, 255}, {170, 73, 85, 255}, {170, 73, 170, 255}, + {170, 73, 255, 255}, {170, 109, 0, 255}, {170, 109, 85, 255}, + {170, 109, 170, 255}, {170, 109, 255, 255}, {170, 146, 0, 255}, + {170, 146, 85, 255}, {170, 146, 170, 255}, {170, 146, 255, 255}, + {170, 182, 0, 255}, {170, 182, 85, 255}, {170, 182, 170, 255}, + {170, 182, 255, 255}, {170, 219, 0, 255}, {170, 219, 85, 255}, + {170, 219, 170, 255}, {170, 219, 255, 255}, {170, 255, 0, 255}, + {170, 255, 85, 255}, {170, 255, 170, 255}, {170, 255, 255, 255}, + {255, 0, 0, 255}, {255, 0, 85, 255}, {255, 0, 170, 255}, + {255, 0, 255, 255}, {255, 36, 0, 255}, {255, 36, 85, 255}, + {255, 36, 170, 255}, {255, 36, 255, 255}, {255, 73, 0, 255}, + {255, 73, 85, 255}, {255, 73, 170, 255}, {255, 73, 255, 255}, + {255, 109, 0, 255}, {255, 109, 85, 255}, {255, 109, 170, 255}, + {255, 109, 255, 255}, {255, 146, 0, 255}, {255, 146, 85, 255}, + {255, 146, 170, 255}, {255, 146, 255, 255}, {255, 182, 0, 255}, + {255, 182, 85, 255}, {255, 182, 170, 255}, {255, 182, 255, 255}, + {255, 219, 0, 255}, {255, 219, 85, 255}, {255, 219, 170, 255}, + {255, 219, 255, 255}, {255, 255, 0, 255}, {255, 255, 85, 255}, + {255, 255, 170, 255}, {255, 255, 255, 255}, {0, 0, 0, 255}, + {0, 0, 85, 255}, {0, 0, 170, 255}, {0, 0, 255, 255}, + {0, 36, 0, 255}, {0, 36, 85, 255}, {0, 36, 170, 255}, + {0, 36, 255, 255}, {0, 73, 0, 255}, {0, 73, 85, 255}, + {0, 73, 170, 255}, {0, 73, 255, 255}, {0, 109, 0, 255}, + {0, 109, 85, 255}, {0, 109, 170, 255}, {0, 109, 255, 255}, + {0, 146, 0, 255}, {0, 146, 85, 255}, {0, 146, 170, 255}, + {0, 146, 255, 255}, {0, 182, 0, 255}, {0, 182, 85, 255}, + {0, 182, 170, 255}, {0, 182, 255, 255}, {0, 219, 0, 255}, + {0, 219, 85, 255}, {0, 219, 170, 255}, {0, 219, 255, 255}, + {0, 255, 0, 255}, {0, 255, 85, 255}, {0, 255, 170, 255}, + {0, 255, 255, 255}, {85, 0, 0, 255}, {85, 0, 85, 255}, + {85, 0, 170, 255}, {85, 0, 255, 255}, {85, 36, 0, 255}, + {85, 36, 85, 255}, {85, 36, 170, 255}, {85, 36, 255, 255}, + {85, 73, 0, 255}, {85, 73, 85, 255}, {85, 73, 170, 255}, + {85, 73, 255, 255}, {85, 109, 0, 255}, {85, 109, 85, 255}, + {85, 109, 170, 255}, {85, 109, 255, 255}, {85, 146, 0, 255}, + {85, 146, 85, 255}, {85, 146, 170, 255}, {85, 146, 255, 255}, + {85, 182, 0, 255}, {85, 182, 85, 255}, {85, 182, 170, 255}, + {85, 182, 255, 255}, {85, 219, 0, 255}, {85, 219, 85, 255}, + {85, 219, 170, 255}, {85, 219, 255, 255}, {85, 255, 0, 255}, + {85, 255, 85, 255}, {85, 255, 170, 255}, {85, 255, 255, 255}, + {170, 0, 0, 255}, {170, 0, 85, 255}, {170, 0, 170, 255}, + {170, 0, 255, 255}, {170, 36, 0, 255}, {170, 36, 85, 255}, + {170, 36, 170, 255}, {170, 36, 255, 255}, {170, 73, 0, 255}, + {170, 73, 85, 255}, {170, 73, 170, 255}, {170, 73, 255, 255}, + {170, 109, 0, 255}, {170, 109, 85, 255}, {170, 109, 170, 255}, + {170, 109, 255, 255}, {170, 146, 0, 255}, {170, 146, 85, 255}, + {170, 146, 170, 255}, {170, 146, 255, 255}, {170, 182, 0, 255}, + {170, 182, 85, 255}, {170, 182, 170, 255}, {170, 182, 255, 255}, + {170, 219, 0, 255}, {170, 219, 85, 255}, {170, 219, 170, 255}, + {170, 219, 255, 255}, {170, 255, 0, 255}, {170, 255, 85, 255}, + {170, 255, 170, 255}, {170, 255, 255, 255}, {255, 0, 0, 255}, + {255, 0, 85, 255}, {255, 0, 170, 255}, {255, 0, 255, 255}, + {255, 36, 0, 255}, {255, 36, 85, 255}, {255, 36, 170, 255}, + {255, 36, 255, 255}, {255, 73, 0, 255}, {255, 73, 85, 255}, + {255, 73, 170, 255}, {255, 73, 255, 255}, {255, 109, 0, 255}, + {255, 109, 85, 255}, {255, 109, 170, 255}, {255, 109, 255, 255}, + {255, 146, 0, 255}, {255, 146, 85, 255}, {255, 146, 170, 255}, + {255, 146, 255, 255}, {255, 182, 0, 255}, {255, 182, 85, 255}, + {255, 182, 170, 255}, {255, 182, 255, 255}, {255, 219, 0, 255}, + {255, 219, 85, 255}, {255, 219, 170, 255}, {255, 219, 255, 255}, + {255, 255, 0, 255}, {255, 255, 85, 255}, {255, 255, 170, 255}, + {255, 255, 255, 255}}; + +static const int default_palette_size = + (int)(sizeof(default_palette_colors) / sizeof(SDL_Color)); + +#endif diff --git a/.venv/include/site/python3.8/pygame/pgarrinter.h b/.venv/include/site/python3.8/pygame/pgarrinter.h new file mode 100644 index 0000000..5ba096b --- /dev/null +++ b/.venv/include/site/python3.8/pygame/pgarrinter.h @@ -0,0 +1,26 @@ +/* array structure interface version 3 declarations */ + +#if !defined(PG_ARRAYINTER_HEADER) +#define PG_ARRAYINTER_HEADER + +static const int PAI_CONTIGUOUS = 0x01; +static const int PAI_FORTRAN = 0x02; +static const int PAI_ALIGNED = 0x100; +static const int PAI_NOTSWAPPED = 0x200; +static const int PAI_WRITEABLE = 0x400; +static const int PAI_ARR_HAS_DESCR = 0x800; + +typedef struct { + int two; /* contains the integer 2 -- simple sanity check */ + int nd; /* number of dimensions */ + char typekind; /* kind in array -- character code of typestr */ + int itemsize; /* size of each element */ + int flags; /* flags indicating how the data should be */ + /* interpreted */ + Py_intptr_t *shape; /* A length-nd array of shape information */ + Py_intptr_t *strides; /* A length-nd array of stride information */ + void *data; /* A pointer to the first element of the array */ + PyObject *descr; /* NULL or a data-description */ +} PyArrayInterface; + +#endif diff --git a/.venv/include/site/python3.8/pygame/pgbufferproxy.h b/.venv/include/site/python3.8/pygame/pgbufferproxy.h new file mode 100644 index 0000000..1507608 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/pgbufferproxy.h @@ -0,0 +1,7 @@ +#ifndef PG_BUFPROXY_INTERNAL_H +#define PG_BUFPROXY_INTERNAL_H + +#include "include/pygame_bufferproxy.h" +#define PYGAMEAPI_BUFPROXY_NUMSLOTS 4 + +#endif /* ~PG_BUFPROXY_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/pgcompat.h b/.venv/include/site/python3.8/pygame/pgcompat.h new file mode 100644 index 0000000..602043d --- /dev/null +++ b/.venv/include/site/python3.8/pygame/pgcompat.h @@ -0,0 +1,57 @@ +/* Python 2.x/3.x compatibility tools (internal) + */ +#ifndef PGCOMPAT_INTERNAL_H +#define PGCOMPAT_INTERNAL_H + +#include "include/pgcompat.h" + +/* Module init function returns new module instance. */ +#define MODINIT_DEFINE(mod_name) PyMODINIT_FUNC PyInit_##mod_name(void) + +/* Defaults for unicode file path encoding */ +#if defined(MS_WIN32) +#define UNICODE_DEF_FS_ERROR "replace" +#else +#define UNICODE_DEF_FS_ERROR "surrogateescape" +#endif + +#define RELATIVE_MODULE(m) ("." m) + +#ifndef Py_TPFLAGS_HAVE_NEWBUFFER +#define Py_TPFLAGS_HAVE_NEWBUFFER 0 +#endif + +#ifndef Py_TPFLAGS_HAVE_CLASS +#define Py_TPFLAGS_HAVE_CLASS 0 +#endif + +#ifndef Py_TPFLAGS_CHECKTYPES +#define Py_TPFLAGS_CHECKTYPES 0 +#endif + +#define Slice_GET_INDICES_EX(slice, length, start, stop, step, slicelength) \ + PySlice_GetIndicesEx(slice, length, start, stop, step, slicelength) + +#if defined(SDL_VERSION_ATLEAST) +#if !(SDL_VERSION_ATLEAST(2, 0, 5)) +/* These functions require SDL 2.0.5 or greater. + + https://wiki.libsdl.org/SDL_SetWindowResizable +*/ +void +SDL_SetWindowResizable(SDL_Window *window, SDL_bool resizable); +int +SDL_GetWindowOpacity(SDL_Window *window, float *opacity); +int +SDL_SetWindowOpacity(SDL_Window *window, float opacity); +int +SDL_SetWindowModalFor(SDL_Window *modal_window, SDL_Window *parent_window); +int +SDL_SetWindowInputFocus(SDL_Window *window); +SDL_Surface * +SDL_CreateRGBSurfaceWithFormat(Uint32 flags, int width, int height, int depth, + Uint32 format); +#endif /* !(SDL_VERSION_ATLEAST(2, 0, 5)) */ +#endif /* defined(SDL_VERSION_ATLEAST) */ + +#endif /* ~PGCOMPAT_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/pgopengl.h b/.venv/include/site/python3.8/pygame/pgopengl.h new file mode 100644 index 0000000..a845cbf --- /dev/null +++ b/.venv/include/site/python3.8/pygame/pgopengl.h @@ -0,0 +1,20 @@ +#if !defined(PGOPENGL_H) +#define PGOPENGL_H + +/** This header includes definitions of Opengl functions as pointer types for + ** use with the SDL function SDL_GL_GetProcAddress. + **/ + +#if defined(_WIN32) +#define GL_APIENTRY __stdcall +#else +#define GL_APIENTRY +#endif + +typedef void(GL_APIENTRY *GL_glReadPixels_Func)(int, int, int, int, + unsigned int, unsigned int, + void *); + +typedef void(GL_APIENTRY *GL_glViewport_Func)(int, int, unsigned int, + unsigned int); +#endif diff --git a/.venv/include/site/python3.8/pygame/pgplatform.h b/.venv/include/site/python3.8/pygame/pgplatform.h new file mode 100644 index 0000000..1c6c285 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/pgplatform.h @@ -0,0 +1,39 @@ +/* platform/compiler adjustments (internal) */ +#ifndef PG_PLATFORM_INTERNAL_H +#define PG_PLATFORM_INTERNAL_H + +/* This must be before all else */ +#if defined(__SYMBIAN32__) && defined(OPENC) +#include +#if defined(__WINS__) +void * +_alloca(size_t size); +#define alloca _alloca +#endif /* __WINS__ */ +#endif /* defined(__SYMBIAN32__) && defined(OPENC) */ + +#include "include/pgplatform.h" + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef ABS +#define ABS(a) (((a) < 0) ? -(a) : (a)) +#endif + +#if defined(macintosh) && defined(__MWERKS__) || defined(__SYMBIAN32__) +#define PYGAME_EXPORT __declspec(export) +#else +#define PYGAME_EXPORT +#endif + +/* warnings */ +#define PG_STRINGIZE_HELPER(x) #x +#define PG_STRINGIZE(x) PG_STRINGIZE_HELPER(x) +#define PG_WARN(desc) \ + message(__FILE__ "(" PG_STRINGIZE(__LINE__) "): WARNING: " #desc) + +#endif /* ~PG_PLATFORM_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/pygame.h b/.venv/include/site/python3.8/pygame/pygame.h new file mode 100644 index 0000000..d7eaf73 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/pygame.h @@ -0,0 +1,32 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +/* This will use PYGAMEAPI_DEFINE_SLOTS instead + * of PYGAMEAPI_EXTERN_SLOTS for base modules. + */ +#ifndef PYGAME_INTERNAL_H +#define PYGAME_INTERNAL_H + +#define PYGAME_H +#include "_pygame.h" + +#endif /* ~PYGAME_INTERNAL_H */ diff --git a/.venv/include/site/python3.8/pygame/scrap.h b/.venv/include/site/python3.8/pygame/scrap.h new file mode 100644 index 0000000..b3265a3 --- /dev/null +++ b/.venv/include/site/python3.8/pygame/scrap.h @@ -0,0 +1,147 @@ +/* + pygame - Python Game Library + Copyright (C) 2006, 2007 Rene Dudfield, Marcus von Appen + + Originally put in the public domain by Sam Lantinga. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef SCRAP_H +#define SCRAP_H + +/* This is unconditionally defined in Python.h */ +#if defined(_POSIX_C_SOURCE) +#undef _POSIX_C_SOURCE +#endif + +#include + +/* Handle clipboard text and data in arbitrary formats */ + +/** + * Predefined supported pygame scrap types. + */ +#define PYGAME_SCRAP_TEXT "text/plain" +#define PYGAME_SCRAP_BMP "image/bmp" +#define PYGAME_SCRAP_PPM "image/ppm" +#define PYGAME_SCRAP_PBM "image/pbm" + +/** + * The supported scrap clipboard types. + * + * This is only relevant in a X11 environment, which supports mouse + * selections as well. For Win32 and MacOS environments the default + * clipboard is used, no matter what value is passed. + */ +typedef enum { + SCRAP_CLIPBOARD, + SCRAP_SELECTION /* only supported in X11 environments. */ +} ScrapClipType; + +/** + * Macro for initialization checks. + */ +#define PYGAME_SCRAP_INIT_CHECK() \ + if (!pygame_scrap_initialized()) \ + return (PyErr_SetString(pgExc_SDLError, "scrap system not initialized."), \ + NULL) + +/** + * \brief Checks, whether the pygame scrap module was initialized. + * + * \return 1 if the modules was initialized, 0 otherwise. + */ +extern int +pygame_scrap_initialized(void); + +/** + * \brief Initializes the pygame scrap module internals. Call this before any + * other method. + * + * \return 1 on successful initialization, 0 otherwise. + */ +extern int +pygame_scrap_init(void); + +/** + * \brief Checks, whether the pygame window lost the clipboard focus or not. + * + * \return 1 if the window lost the focus, 0 otherwise. + */ +extern int +pygame_scrap_lost(void); + +/** + * \brief Places content of a specific type into the clipboard. + * + * \note For X11 the following notes are important: The following types + * are reserved for internal usage and thus will throw an error on + * setting them: "TIMESTAMP", "TARGETS", "SDL_SELECTION". + * Setting PYGAME_SCRAP_TEXT ("text/plain") will also automatically + * set the X11 types "STRING" (XA_STRING), "TEXT" and "UTF8_STRING". + * + * For Win32 the following notes are important: Setting + * PYGAME_SCRAP_TEXT ("text/plain") will also automatically set + * the Win32 type "TEXT" (CF_TEXT). + * + * For QNX the following notes are important: Setting + * PYGAME_SCRAP_TEXT ("text/plain") will also automatically set + * the QNX type "TEXT" (Ph_CL_TEXT). + * + * \param type The type of the content. + * \param srclen The length of the content. + * \param src The NULL terminated content. + * \return 1, if the content could be successfully pasted into the clipboard, + * 0 otherwise. + */ +extern int +pygame_scrap_put(char *type, int srclen, char *src); + +/** + * \brief Gets the current content from the clipboard. + * + * \note The received content does not need to be the content previously + * placed in the clipboard using pygame_put_scrap(). See the + * pygame_put_scrap() notes for more details. + * + * \param type The type of the content to receive. + * \param count The size of the returned content. + * \return The content or NULL in case of an error or if no content of the + * specified type was available. + */ +extern char * +pygame_scrap_get(char *type, unsigned long *count); + +/** + * \brief Gets the currently available content types from the clipboard. + * + * \return The different available content types or NULL in case of an + * error or if no content type is available. + */ +extern char ** +pygame_scrap_get_types(void); + +/** + * \brief Checks whether content for the specified scrap type is currently + * available in the clipboard. + * + * \param type The type to check for. + * \return 1, if there is content and 0 otherwise. + */ +extern int +pygame_scrap_contains(char *type); + +#endif /* SCRAP_H */ diff --git a/.venv/include/site/python3.8/pygame/surface.h b/.venv/include/site/python3.8/pygame/surface.h new file mode 100644 index 0000000..eb9bbed --- /dev/null +++ b/.venv/include/site/python3.8/pygame/surface.h @@ -0,0 +1,355 @@ +/* + pygame - Python Game Library + Copyright (C) 2000-2001 Pete Shinners + Copyright (C) 2007 Marcus von Appen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Pete Shinners + pete@shinners.org +*/ + +#ifndef SURFACE_H +#define SURFACE_H + +/* This is defined in SDL.h */ +#if defined(_POSIX_C_SOURCE) +#undef _POSIX_C_SOURCE +#endif + +#include +#include "pygame.h" + +/* Blend modes */ +#define PYGAME_BLEND_ADD 0x1 +#define PYGAME_BLEND_SUB 0x2 +#define PYGAME_BLEND_MULT 0x3 +#define PYGAME_BLEND_MIN 0x4 +#define PYGAME_BLEND_MAX 0x5 + +#define PYGAME_BLEND_RGB_ADD 0x1 +#define PYGAME_BLEND_RGB_SUB 0x2 +#define PYGAME_BLEND_RGB_MULT 0x3 +#define PYGAME_BLEND_RGB_MIN 0x4 +#define PYGAME_BLEND_RGB_MAX 0x5 + +#define PYGAME_BLEND_RGBA_ADD 0x6 +#define PYGAME_BLEND_RGBA_SUB 0x7 +#define PYGAME_BLEND_RGBA_MULT 0x8 +#define PYGAME_BLEND_RGBA_MIN 0x9 +#define PYGAME_BLEND_RGBA_MAX 0x10 +#define PYGAME_BLEND_PREMULTIPLIED 0x11 +#define PYGAME_BLEND_ALPHA_SDL2 0x12 + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define GET_PIXEL_24(b) (b[0] + (b[1] << 8) + (b[2] << 16)) +#else +#define GET_PIXEL_24(b) (b[2] + (b[1] << 8) + (b[0] << 16)) +#endif + +#define GET_PIXEL(pxl, bpp, source) \ + switch (bpp) { \ + case 2: \ + pxl = *((Uint16 *)(source)); \ + break; \ + case 4: \ + pxl = *((Uint32 *)(source)); \ + break; \ + default: { \ + Uint8 *b = (Uint8 *)source; \ + pxl = GET_PIXEL_24(b); \ + } break; \ + } + +#define GET_PIXELVALS(_sR, _sG, _sB, _sA, px, fmt, ppa) \ + SDL_GetRGBA(px, fmt, &(_sR), &(_sG), &(_sB), &(_sA)); \ + if (!ppa) { \ + _sA = 255; \ + } + +#define GET_PIXELVALS_1(sr, sg, sb, sa, _src, _fmt) \ + sr = _fmt->palette->colors[*((Uint8 *)(_src))].r; \ + sg = _fmt->palette->colors[*((Uint8 *)(_src))].g; \ + sb = _fmt->palette->colors[*((Uint8 *)(_src))].b; \ + sa = 255; + +/* For 1 byte palette pixels */ +#define SET_PIXELVAL(px, fmt, _dR, _dG, _dB, _dA) \ + *(px) = (Uint8)SDL_MapRGBA(fmt, _dR, _dG, _dB, _dA) + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define SET_OFFSETS_24(or, og, ob, fmt) \ + { \ + or = (fmt->Rshift == 0 ? 0 : fmt->Rshift == 8 ? 1 : 2); \ + og = (fmt->Gshift == 0 ? 0 : fmt->Gshift == 8 ? 1 : 2); \ + ob = (fmt->Bshift == 0 ? 0 : fmt->Bshift == 8 ? 1 : 2); \ + } + +#define SET_OFFSETS_32(or, og, ob, fmt) \ + { \ + or = (fmt->Rshift == 0 ? 0 \ + : fmt->Rshift == 8 ? 1 \ + : fmt->Rshift == 16 ? 2 \ + : 3); \ + og = (fmt->Gshift == 0 ? 0 \ + : fmt->Gshift == 8 ? 1 \ + : fmt->Gshift == 16 ? 2 \ + : 3); \ + ob = (fmt->Bshift == 0 ? 0 \ + : fmt->Bshift == 8 ? 1 \ + : fmt->Bshift == 16 ? 2 \ + : 3); \ + } +#else +#define SET_OFFSETS_24(or, og, ob, fmt) \ + { \ + or = (fmt->Rshift == 0 ? 2 : fmt->Rshift == 8 ? 1 : 0); \ + og = (fmt->Gshift == 0 ? 2 : fmt->Gshift == 8 ? 1 : 0); \ + ob = (fmt->Bshift == 0 ? 2 : fmt->Bshift == 8 ? 1 : 0); \ + } + +#define SET_OFFSETS_32(or, og, ob, fmt) \ + { \ + or = (fmt->Rshift == 0 ? 3 \ + : fmt->Rshift == 8 ? 2 \ + : fmt->Rshift == 16 ? 1 \ + : 0); \ + og = (fmt->Gshift == 0 ? 3 \ + : fmt->Gshift == 8 ? 2 \ + : fmt->Gshift == 16 ? 1 \ + : 0); \ + ob = (fmt->Bshift == 0 ? 3 \ + : fmt->Bshift == 8 ? 2 \ + : fmt->Bshift == 16 ? 1 \ + : 0); \ + } +#endif + +#define CREATE_PIXEL(buf, r, g, b, a, bp, ft) \ + switch (bp) { \ + case 2: \ + *((Uint16 *)(buf)) = ((r >> ft->Rloss) << ft->Rshift) | \ + ((g >> ft->Gloss) << ft->Gshift) | \ + ((b >> ft->Bloss) << ft->Bshift) | \ + ((a >> ft->Aloss) << ft->Ashift); \ + break; \ + case 4: \ + *((Uint32 *)(buf)) = ((r >> ft->Rloss) << ft->Rshift) | \ + ((g >> ft->Gloss) << ft->Gshift) | \ + ((b >> ft->Bloss) << ft->Bshift) | \ + ((a >> ft->Aloss) << ft->Ashift); \ + break; \ + } + +/* Pretty good idea from Tom Duff :-). */ +#define LOOP_UNROLLED4(code, n, width) \ + n = (width + 3) / 4; \ + switch (width & 3) { \ + case 0: \ + do { \ + code; \ + case 3: \ + code; \ + case 2: \ + code; \ + case 1: \ + code; \ + } while (--n > 0); \ + } + +/* Used in the srcbpp == dstbpp == 1 blend functions */ +#define REPEAT_3(code) \ + code; \ + code; \ + code; + +#define REPEAT_4(code) \ + code; \ + code; \ + code; \ + code; + +#define BLEND_ADD(tmp, sR, sG, sB, sA, dR, dG, dB, dA) \ + tmp = dR + sR; \ + dR = (tmp <= 255 ? tmp : 255); \ + tmp = dG + sG; \ + dG = (tmp <= 255 ? tmp : 255); \ + tmp = dB + sB; \ + dB = (tmp <= 255 ? tmp : 255); + +#define BLEND_SUB(tmp, sR, sG, sB, sA, dR, dG, dB, dA) \ + tmp = dR - sR; \ + dR = (tmp >= 0 ? tmp : 0); \ + tmp = dG - sG; \ + dG = (tmp >= 0 ? tmp : 0); \ + tmp = dB - sB; \ + dB = (tmp >= 0 ? tmp : 0); + +#define BLEND_MULT(sR, sG, sB, sA, dR, dG, dB, dA) \ + dR = (dR && sR) ? (dR * sR) >> 8 : 0; \ + dG = (dG && sG) ? (dG * sG) >> 8 : 0; \ + dB = (dB && sB) ? (dB * sB) >> 8 : 0; + +#define BLEND_MIN(sR, sG, sB, sA, dR, dG, dB, dA) \ + if (sR < dR) { \ + dR = sR; \ + } \ + if (sG < dG) { \ + dG = sG; \ + } \ + if (sB < dB) { \ + dB = sB; \ + } + +#define BLEND_MAX(sR, sG, sB, sA, dR, dG, dB, dA) \ + if (sR > dR) { \ + dR = sR; \ + } \ + if (sG > dG) { \ + dG = sG; \ + } \ + if (sB > dB) { \ + dB = sB; \ + } + +#define BLEND_RGBA_ADD(tmp, sR, sG, sB, sA, dR, dG, dB, dA) \ + tmp = dR + sR; \ + dR = (tmp <= 255 ? tmp : 255); \ + tmp = dG + sG; \ + dG = (tmp <= 255 ? tmp : 255); \ + tmp = dB + sB; \ + dB = (tmp <= 255 ? tmp : 255); \ + tmp = dA + sA; \ + dA = (tmp <= 255 ? tmp : 255); + +#define BLEND_RGBA_SUB(tmp, sR, sG, sB, sA, dR, dG, dB, dA) \ + tmp = dR - sR; \ + dR = (tmp >= 0 ? tmp : 0); \ + tmp = dG - sG; \ + dG = (tmp >= 0 ? tmp : 0); \ + tmp = dB - sB; \ + dB = (tmp >= 0 ? tmp : 0); \ + tmp = dA - sA; \ + dA = (tmp >= 0 ? tmp : 0); + +#define BLEND_RGBA_MULT(sR, sG, sB, sA, dR, dG, dB, dA) \ + dR = (dR && sR) ? (dR * sR) >> 8 : 0; \ + dG = (dG && sG) ? (dG * sG) >> 8 : 0; \ + dB = (dB && sB) ? (dB * sB) >> 8 : 0; \ + dA = (dA && sA) ? (dA * sA) >> 8 : 0; + +#define BLEND_RGBA_MIN(sR, sG, sB, sA, dR, dG, dB, dA) \ + if (sR < dR) { \ + dR = sR; \ + } \ + if (sG < dG) { \ + dG = sG; \ + } \ + if (sB < dB) { \ + dB = sB; \ + } \ + if (sA < dA) { \ + dA = sA; \ + } + +#define BLEND_RGBA_MAX(sR, sG, sB, sA, dR, dG, dB, dA) \ + if (sR > dR) { \ + dR = sR; \ + } \ + if (sG > dG) { \ + dG = sG; \ + } \ + if (sB > dB) { \ + dB = sB; \ + } \ + if (sA > dA) { \ + dA = sA; \ + } + +#if 1 +/* Choose an alpha blend equation. If the sign is preserved on a right shift + * then use a specialized, faster, equation. Otherwise a more general form, + * where all additions are done before the shift, is needed. + */ +#if (-1 >> 1) < 0 +#define ALPHA_BLEND_COMP(sC, dC, sA) ((((sC - dC) * sA + sC) >> 8) + dC) +#else +#define ALPHA_BLEND_COMP(sC, dC, sA) (((dC << 8) + (sC - dC) * sA + sC) >> 8) +#endif + +#define ALPHA_BLEND(sR, sG, sB, sA, dR, dG, dB, dA) \ + do { \ + if (dA) { \ + dR = ALPHA_BLEND_COMP(sR, dR, sA); \ + dG = ALPHA_BLEND_COMP(sG, dG, sA); \ + dB = ALPHA_BLEND_COMP(sB, dB, sA); \ + dA = sA + dA - ((sA * dA) / 255); \ + } \ + else { \ + dR = sR; \ + dG = sG; \ + dB = sB; \ + dA = sA; \ + } \ + } while (0) + +#define ALPHA_BLEND_PREMULTIPLIED_COMP(sC, dC, sA) \ + (sC + dC - ((dC + 1) * sA >> 8)) + +#define ALPHA_BLEND_PREMULTIPLIED(tmp, sR, sG, sB, sA, dR, dG, dB, dA) \ + do { \ + dR = ALPHA_BLEND_PREMULTIPLIED_COMP(sR, dR, sA); \ + dG = ALPHA_BLEND_PREMULTIPLIED_COMP(sG, dG, sA); \ + dB = ALPHA_BLEND_PREMULTIPLIED_COMP(sB, dB, sA); \ + dA = ALPHA_BLEND_PREMULTIPLIED_COMP(sA, dA, sA); \ + } while (0) +#elif 0 + +#define ALPHA_BLEND(sR, sG, sB, sA, dR, dG, dB, dA) \ + do { \ + if (sA) { \ + if (dA && sA < 255) { \ + int dContrib = dA * (255 - sA) / 255; \ + dA = sA + dA - ((sA * dA) / 255); \ + dR = (dR * dContrib + sR * sA) / dA; \ + dG = (dG * dContrib + sG * sA) / dA; \ + dB = (dB * dContrib + sB * sA) / dA; \ + } \ + else { \ + dR = sR; \ + dG = sG; \ + dB = sB; \ + dA = sA; \ + } \ + } \ + } while (0) +#endif + +int +surface_fill_blend(SDL_Surface *surface, SDL_Rect *rect, Uint32 color, + int blendargs); + +void +surface_respect_clip_rect(SDL_Surface *surface, SDL_Rect *rect); + +int +pygame_AlphaBlit(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, + SDL_Rect *dstrect, int the_args); + +int +pygame_Blit(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, + SDL_Rect *dstrect, int the_args); + +#endif /* SURFACE_H */ diff --git a/.venv/lib/python3.8/site-packages/2dd510b5c3364608e57a__mypyc.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/2dd510b5c3364608e57a__mypyc.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..5ae6dbe Binary files /dev/null and b/.venv/lib/python3.8/site-packages/2dd510b5c3364608e57a__mypyc.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/IPython/__init__.py b/.venv/lib/python3.8/site-packages/IPython/__init__.py new file mode 100644 index 0000000..e12da90 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/__init__.py @@ -0,0 +1,155 @@ +""" +IPython: tools for interactive and parallel computing in Python. + +https://ipython.org +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2008-2011, IPython Development Team. +# Copyright (c) 2001-2007, Fernando Perez +# Copyright (c) 2001, Janko Hauser +# Copyright (c) 2001, Nathaniel Gray +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import sys + +#----------------------------------------------------------------------------- +# Setup everything +#----------------------------------------------------------------------------- + +# Don't forget to also update setup.py when this changes! +if sys.version_info < (3, 8): + raise ImportError( +""" +IPython 8+ supports Python 3.8 and above, following NEP 29. +When using Python 2.7, please install IPython 5.x LTS Long Term Support version. +Python 3.3 and 3.4 were supported up to IPython 6.x. +Python 3.5 was supported with IPython 7.0 to 7.9. +Python 3.6 was supported with IPython up to 7.16. +Python 3.7 was still supported with the 7.x branch. + +See IPython `README.rst` file for more information: + + https://github.com/ipython/ipython/blob/master/README.rst + +""") + +#----------------------------------------------------------------------------- +# Setup the top level names +#----------------------------------------------------------------------------- + +from .core.getipython import get_ipython +from .core import release +from .core.application import Application +from .terminal.embed import embed + +from .core.interactiveshell import InteractiveShell +from .utils.sysinfo import sys_info +from .utils.frame import extract_module_locals + +# Release data +__author__ = '%s <%s>' % (release.author, release.author_email) +__license__ = release.license +__version__ = release.version +version_info = release.version_info +# list of CVEs that should have been patched in this release. +# this is informational and should not be relied upon. +__patched_cves__ = {"CVE-2022-21699"} + + +def embed_kernel(module=None, local_ns=None, **kwargs): + """Embed and start an IPython kernel in a given scope. + + If you don't want the kernel to initialize the namespace + from the scope of the surrounding function, + and/or you want to load full IPython configuration, + you probably want `IPython.start_kernel()` instead. + + Parameters + ---------- + module : types.ModuleType, optional + The module to load into IPython globals (default: caller) + local_ns : dict, optional + The namespace to load into IPython user namespace (default: caller) + **kwargs : various, optional + Further keyword args are relayed to the IPKernelApp constructor, + allowing configuration of the Kernel. Will only have an effect + on the first embed_kernel call for a given process. + """ + + (caller_module, caller_locals) = extract_module_locals(1) + if module is None: + module = caller_module + if local_ns is None: + local_ns = caller_locals + + # Only import .zmq when we really need it + from ipykernel.embed import embed_kernel as real_embed_kernel + real_embed_kernel(module=module, local_ns=local_ns, **kwargs) + +def start_ipython(argv=None, **kwargs): + """Launch a normal IPython instance (as opposed to embedded) + + `IPython.embed()` puts a shell in a particular calling scope, + such as a function or method for debugging purposes, + which is often not desirable. + + `start_ipython()` does full, regular IPython initialization, + including loading startup files, configuration, etc. + much of which is skipped by `embed()`. + + This is a public API method, and will survive implementation changes. + + Parameters + ---------- + argv : list or None, optional + If unspecified or None, IPython will parse command-line options from sys.argv. + To prevent any command-line parsing, pass an empty list: `argv=[]`. + user_ns : dict, optional + specify this dictionary to initialize the IPython user namespace with particular values. + **kwargs : various, optional + Any other kwargs will be passed to the Application constructor, + such as `config`. + """ + from IPython.terminal.ipapp import launch_new_instance + return launch_new_instance(argv=argv, **kwargs) + +def start_kernel(argv=None, **kwargs): + """Launch a normal IPython kernel instance (as opposed to embedded) + + `IPython.embed_kernel()` puts a shell in a particular calling scope, + such as a function or method for debugging purposes, + which is often not desirable. + + `start_kernel()` does full, regular IPython initialization, + including loading startup files, configuration, etc. + much of which is skipped by `embed()`. + + Parameters + ---------- + argv : list or None, optional + If unspecified or None, IPython will parse command-line options from sys.argv. + To prevent any command-line parsing, pass an empty list: `argv=[]`. + user_ns : dict, optional + specify this dictionary to initialize the IPython user namespace with particular values. + **kwargs : various, optional + Any other kwargs will be passed to the Application constructor, + such as `config`. + """ + import warnings + + warnings.warn( + "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`", + DeprecationWarning, + stacklevel=2, + ) + from ipykernel.kernelapp import launch_new_instance + return launch_new_instance(argv=argv, **kwargs) diff --git a/.venv/lib/python3.8/site-packages/IPython/__main__.py b/.venv/lib/python3.8/site-packages/IPython/__main__.py new file mode 100644 index 0000000..d5123f3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/__main__.py @@ -0,0 +1,14 @@ +# encoding: utf-8 +"""Terminal-based IPython entry point. +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2012, IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +from IPython import start_ipython + +start_ipython() diff --git a/.venv/lib/python3.8/site-packages/IPython/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ff82c78 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..19962b8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/__pycache__/conftest.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/__pycache__/conftest.cpython-38.pyc new file mode 100644 index 0000000..c7137f8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/__pycache__/conftest.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/__pycache__/consoleapp.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/__pycache__/consoleapp.cpython-38.pyc new file mode 100644 index 0000000..d931984 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/__pycache__/consoleapp.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/__pycache__/display.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/__pycache__/display.cpython-38.pyc new file mode 100644 index 0000000..fee8799 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/__pycache__/display.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/__pycache__/paths.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/__pycache__/paths.cpython-38.pyc new file mode 100644 index 0000000..92b5fbf Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/__pycache__/paths.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/conftest.py b/.venv/lib/python3.8/site-packages/IPython/conftest.py new file mode 100644 index 0000000..abf6131 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/conftest.py @@ -0,0 +1,87 @@ +import builtins +import inspect +import os +import pathlib +import shutil +import sys +import types + +import pytest + +# Must register before it gets imported +pytest.register_assert_rewrite("IPython.testing.tools") + +from .testing import tools + + +def pytest_collection_modifyitems(items): + """This function is automatically run by pytest passing all collected test + functions. + + We use it to add asyncio marker to all async tests and assert we don't use + test functions that are async generators which wouldn't make sense. + """ + for item in items: + if inspect.iscoroutinefunction(item.obj): + item.add_marker("asyncio") + assert not inspect.isasyncgenfunction(item.obj) + + +def get_ipython(): + from .terminal.interactiveshell import TerminalInteractiveShell + if TerminalInteractiveShell._instance: + return TerminalInteractiveShell.instance() + + config = tools.default_config() + config.TerminalInteractiveShell.simple_prompt = True + + # Create and initialize our test-friendly IPython instance. + shell = TerminalInteractiveShell.instance(config=config) + return shell + + +@pytest.fixture(scope='session', autouse=True) +def work_path(): + path = pathlib.Path("./tmp-ipython-pytest-profiledir") + os.environ["IPYTHONDIR"] = str(path.absolute()) + if path.exists(): + raise ValueError('IPython dir temporary path already exists ! Did previous test run exit successfully ?') + path.mkdir() + yield + shutil.rmtree(str(path.resolve())) + + +def nopage(strng, start=0, screen_lines=0, pager_cmd=None): + if isinstance(strng, dict): + strng = strng.get("text/plain", "") + print(strng) + + +def xsys(self, cmd): + """Replace the default system call with a capturing one for doctest. + """ + # We use getoutput, but we need to strip it because pexpect captures + # the trailing newline differently from commands.getoutput + print(self.getoutput(cmd, split=False, depth=1).rstrip(), end="", file=sys.stdout) + sys.stdout.flush() + + +# for things to work correctly we would need this as a session fixture; +# unfortunately this will fail on some test that get executed as _collection_ +# time (before the fixture run), in particular parametrized test that contain +# yields. so for now execute at import time. +#@pytest.fixture(autouse=True, scope='session') +def inject(): + + builtins.get_ipython = get_ipython + builtins._ip = get_ipython() + builtins.ip = get_ipython() + builtins.ip.system = types.MethodType(xsys, ip) + builtins.ip.builtin_trap.activate() + from .core import page + + page.pager_page = nopage + # yield + + +inject() diff --git a/.venv/lib/python3.8/site-packages/IPython/consoleapp.py b/.venv/lib/python3.8/site-packages/IPython/consoleapp.py new file mode 100644 index 0000000..c2bbe18 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/consoleapp.py @@ -0,0 +1,12 @@ +""" +Shim to maintain backwards compatibility with old IPython.consoleapp imports. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from warnings import warn + +warn("The `IPython.consoleapp` package has been deprecated since IPython 4.0." + "You should import from jupyter_client.consoleapp instead.", stacklevel=2) + +from jupyter_client.consoleapp import * diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__init__.py b/.venv/lib/python3.8/site-packages/IPython/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..7f55471 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/alias.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/alias.cpython-38.pyc new file mode 100644 index 0000000..5b5d39e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/alias.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/application.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/application.cpython-38.pyc new file mode 100644 index 0000000..ae01eff Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/application.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/async_helpers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/async_helpers.cpython-38.pyc new file mode 100644 index 0000000..bb9dfba Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/async_helpers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/autocall.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/autocall.cpython-38.pyc new file mode 100644 index 0000000..653776a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/autocall.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/builtin_trap.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/builtin_trap.cpython-38.pyc new file mode 100644 index 0000000..3216030 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/builtin_trap.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/compilerop.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/compilerop.cpython-38.pyc new file mode 100644 index 0000000..6d96dfb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/compilerop.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/completer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/completer.cpython-38.pyc new file mode 100644 index 0000000..909d6cb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/completer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/completerlib.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/completerlib.cpython-38.pyc new file mode 100644 index 0000000..7625589 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/completerlib.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/crashhandler.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/crashhandler.cpython-38.pyc new file mode 100644 index 0000000..5e6cd7d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/crashhandler.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/debugger.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/debugger.cpython-38.pyc new file mode 100644 index 0000000..43304d1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/debugger.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display.cpython-38.pyc new file mode 100644 index 0000000..920eb2d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display_functions.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display_functions.cpython-38.pyc new file mode 100644 index 0000000..310e9e0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display_functions.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display_trap.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display_trap.cpython-38.pyc new file mode 100644 index 0000000..606f3c1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/display_trap.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/displayhook.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/displayhook.cpython-38.pyc new file mode 100644 index 0000000..93fa022 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/displayhook.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/displaypub.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/displaypub.cpython-38.pyc new file mode 100644 index 0000000..30f920f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/displaypub.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/error.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/error.cpython-38.pyc new file mode 100644 index 0000000..3c16efc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/error.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/events.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/events.cpython-38.pyc new file mode 100644 index 0000000..f77cd23 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/events.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/excolors.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/excolors.cpython-38.pyc new file mode 100644 index 0000000..16fae13 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/excolors.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/extensions.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/extensions.cpython-38.pyc new file mode 100644 index 0000000..c2baf55 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/extensions.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/formatters.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/formatters.cpython-38.pyc new file mode 100644 index 0000000..1a7f2be Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/formatters.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/getipython.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/getipython.cpython-38.pyc new file mode 100644 index 0000000..764c944 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/getipython.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/history.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/history.cpython-38.pyc new file mode 100644 index 0000000..a1c5d67 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/history.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/historyapp.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/historyapp.cpython-38.pyc new file mode 100644 index 0000000..df039fa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/historyapp.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/hooks.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/hooks.cpython-38.pyc new file mode 100644 index 0000000..9a7eec3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/hooks.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputsplitter.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputsplitter.cpython-38.pyc new file mode 100644 index 0000000..51ddbc3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputsplitter.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputtransformer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputtransformer.cpython-38.pyc new file mode 100644 index 0000000..559816e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputtransformer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputtransformer2.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputtransformer2.cpython-38.pyc new file mode 100644 index 0000000..f68a26a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/inputtransformer2.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/interactiveshell.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/interactiveshell.cpython-38.pyc new file mode 100644 index 0000000..bde7f93 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/interactiveshell.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/latex_symbols.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/latex_symbols.cpython-38.pyc new file mode 100644 index 0000000..2c412c0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/latex_symbols.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/logger.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/logger.cpython-38.pyc new file mode 100644 index 0000000..4c6124e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/logger.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/macro.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/macro.cpython-38.pyc new file mode 100644 index 0000000..6592067 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/macro.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/magic.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/magic.cpython-38.pyc new file mode 100644 index 0000000..6c31050 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/magic.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/magic_arguments.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/magic_arguments.cpython-38.pyc new file mode 100644 index 0000000..0763160 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/magic_arguments.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/oinspect.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/oinspect.cpython-38.pyc new file mode 100644 index 0000000..47efa56 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/oinspect.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/page.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/page.cpython-38.pyc new file mode 100644 index 0000000..4d2e1e6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/page.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/payload.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/payload.cpython-38.pyc new file mode 100644 index 0000000..0c99166 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/payload.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/payloadpage.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/payloadpage.cpython-38.pyc new file mode 100644 index 0000000..e2239bf Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/payloadpage.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/prefilter.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/prefilter.cpython-38.pyc new file mode 100644 index 0000000..3278f53 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/prefilter.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/profileapp.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/profileapp.cpython-38.pyc new file mode 100644 index 0000000..9922b24 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/profileapp.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/profiledir.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/profiledir.cpython-38.pyc new file mode 100644 index 0000000..2992ee0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/profiledir.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/prompts.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/prompts.cpython-38.pyc new file mode 100644 index 0000000..d968e65 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/prompts.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/pylabtools.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/pylabtools.cpython-38.pyc new file mode 100644 index 0000000..78efdf2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/pylabtools.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/release.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/release.cpython-38.pyc new file mode 100644 index 0000000..200506a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/release.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/shellapp.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/shellapp.cpython-38.pyc new file mode 100644 index 0000000..d02cbb8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/shellapp.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/splitinput.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/splitinput.cpython-38.pyc new file mode 100644 index 0000000..df35152 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/splitinput.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/ultratb.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/ultratb.cpython-38.pyc new file mode 100644 index 0000000..867809a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/ultratb.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/usage.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/usage.cpython-38.pyc new file mode 100644 index 0000000..873eb37 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/core/__pycache__/usage.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/core/alias.py b/.venv/lib/python3.8/site-packages/IPython/core/alias.py new file mode 100644 index 0000000..2ad9902 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/alias.py @@ -0,0 +1,258 @@ +# encoding: utf-8 +""" +System command aliases. + +Authors: + +* Fernando Perez +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import re +import sys + +from traitlets.config.configurable import Configurable +from .error import UsageError + +from traitlets import List, Instance +from logging import error + +#----------------------------------------------------------------------------- +# Utilities +#----------------------------------------------------------------------------- + +# This is used as the pattern for calls to split_user_input. +shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)') + +def default_aliases(): + """Return list of shell aliases to auto-define. + """ + # Note: the aliases defined here should be safe to use on a kernel + # regardless of what frontend it is attached to. Frontends that use a + # kernel in-process can define additional aliases that will only work in + # their case. For example, things like 'less' or 'clear' that manipulate + # the terminal should NOT be declared here, as they will only work if the + # kernel is running inside a true terminal, and not over the network. + + if os.name == 'posix': + default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'), + ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'), + ('cat', 'cat'), + ] + # Useful set of ls aliases. The GNU and BSD options are a little + # different, so we make aliases that provide as similar as possible + # behavior in ipython, by passing the right flags for each platform + if sys.platform.startswith('linux'): + ls_aliases = [('ls', 'ls -F --color'), + # long ls + ('ll', 'ls -F -o --color'), + # ls normal files only + ('lf', 'ls -F -o --color %l | grep ^-'), + # ls symbolic links + ('lk', 'ls -F -o --color %l | grep ^l'), + # directories or links to directories, + ('ldir', 'ls -F -o --color %l | grep /$'), + # things which are executable + ('lx', 'ls -F -o --color %l | grep ^-..x'), + ] + elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'): + # OpenBSD, NetBSD. The ls implementation on these platforms do not support + # the -G switch and lack the ability to use colorized output. + ls_aliases = [('ls', 'ls -F'), + # long ls + ('ll', 'ls -F -l'), + # ls normal files only + ('lf', 'ls -F -l %l | grep ^-'), + # ls symbolic links + ('lk', 'ls -F -l %l | grep ^l'), + # directories or links to directories, + ('ldir', 'ls -F -l %l | grep /$'), + # things which are executable + ('lx', 'ls -F -l %l | grep ^-..x'), + ] + else: + # BSD, OSX, etc. + ls_aliases = [('ls', 'ls -F -G'), + # long ls + ('ll', 'ls -F -l -G'), + # ls normal files only + ('lf', 'ls -F -l -G %l | grep ^-'), + # ls symbolic links + ('lk', 'ls -F -l -G %l | grep ^l'), + # directories or links to directories, + ('ldir', 'ls -F -G -l %l | grep /$'), + # things which are executable + ('lx', 'ls -F -l -G %l | grep ^-..x'), + ] + default_aliases = default_aliases + ls_aliases + elif os.name in ['nt', 'dos']: + default_aliases = [('ls', 'dir /on'), + ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'), + ('mkdir', 'mkdir'), ('rmdir', 'rmdir'), + ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'), + ] + else: + default_aliases = [] + + return default_aliases + + +class AliasError(Exception): + pass + + +class InvalidAliasError(AliasError): + pass + +class Alias(object): + """Callable object storing the details of one alias. + + Instances are registered as magic functions to allow use of aliases. + """ + + # Prepare blacklist + blacklist = {'cd','popd','pushd','dhist','alias','unalias'} + + def __init__(self, shell, name, cmd): + self.shell = shell + self.name = name + self.cmd = cmd + self.__doc__ = "Alias for `!{}`".format(cmd) + self.nargs = self.validate() + + def validate(self): + """Validate the alias, and return the number of arguments.""" + if self.name in self.blacklist: + raise InvalidAliasError("The name %s can't be aliased " + "because it is a keyword or builtin." % self.name) + try: + caller = self.shell.magics_manager.magics['line'][self.name] + except KeyError: + pass + else: + if not isinstance(caller, Alias): + raise InvalidAliasError("The name %s can't be aliased " + "because it is another magic command." % self.name) + + if not (isinstance(self.cmd, str)): + raise InvalidAliasError("An alias command must be a string, " + "got: %r" % self.cmd) + + nargs = self.cmd.count('%s') - self.cmd.count('%%s') + + if (nargs > 0) and (self.cmd.find('%l') >= 0): + raise InvalidAliasError('The %s and %l specifiers are mutually ' + 'exclusive in alias definitions.') + + return nargs + + def __repr__(self): + return "".format(self.name, self.cmd) + + def __call__(self, rest=''): + cmd = self.cmd + nargs = self.nargs + # Expand the %l special to be the user's input line + if cmd.find('%l') >= 0: + cmd = cmd.replace('%l', rest) + rest = '' + + if nargs==0: + if cmd.find('%%s') >= 1: + cmd = cmd.replace('%%s', '%s') + # Simple, argument-less aliases + cmd = '%s %s' % (cmd, rest) + else: + # Handle aliases with positional arguments + args = rest.split(None, nargs) + if len(args) < nargs: + raise UsageError('Alias <%s> requires %s arguments, %s given.' % + (self.name, nargs, len(args))) + cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:])) + + self.shell.system(cmd) + +#----------------------------------------------------------------------------- +# Main AliasManager class +#----------------------------------------------------------------------------- + +class AliasManager(Configurable): + + default_aliases = List(default_aliases()).tag(config=True) + user_aliases = List(default_value=[]).tag(config=True) + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) + + def __init__(self, shell=None, **kwargs): + super(AliasManager, self).__init__(shell=shell, **kwargs) + # For convenient access + self.linemagics = self.shell.magics_manager.magics['line'] + self.init_aliases() + + def init_aliases(self): + # Load default & user aliases + for name, cmd in self.default_aliases + self.user_aliases: + if cmd.startswith('ls ') and self.shell.colors == 'NoColor': + cmd = cmd.replace(' --color', '') + self.soft_define_alias(name, cmd) + + @property + def aliases(self): + return [(n, func.cmd) for (n, func) in self.linemagics.items() + if isinstance(func, Alias)] + + def soft_define_alias(self, name, cmd): + """Define an alias, but don't raise on an AliasError.""" + try: + self.define_alias(name, cmd) + except AliasError as e: + error("Invalid alias: %s" % e) + + def define_alias(self, name, cmd): + """Define a new alias after validating it. + + This will raise an :exc:`AliasError` if there are validation + problems. + """ + caller = Alias(shell=self.shell, name=name, cmd=cmd) + self.shell.magics_manager.register_function(caller, magic_kind='line', + magic_name=name) + + def get_alias(self, name): + """Return an alias, or None if no alias by that name exists.""" + aname = self.linemagics.get(name, None) + return aname if isinstance(aname, Alias) else None + + def is_alias(self, name): + """Return whether or not a given name has been defined as an alias""" + return self.get_alias(name) is not None + + def undefine_alias(self, name): + if self.is_alias(name): + del self.linemagics[name] + else: + raise ValueError('%s is not an alias' % name) + + def clear_aliases(self): + for name, cmd in self.aliases: + self.undefine_alias(name) + + def retrieve_alias(self, name): + """Retrieve the command to which an alias expands.""" + caller = self.get_alias(name) + if caller: + return caller.cmd + else: + raise ValueError('%s is not an alias' % name) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/application.py b/.venv/lib/python3.8/site-packages/IPython/core/application.py new file mode 100644 index 0000000..0cdea5c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/application.py @@ -0,0 +1,490 @@ +# encoding: utf-8 +""" +An application for IPython. + +All top-level applications should use the classes in this module for +handling configuration and creating configurables. + +The job of an :class:`Application` is to create the master configuration +object and then create the configurable objects, passing the config to them. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import atexit +from copy import deepcopy +import glob +import logging +import os +import shutil +import sys + +from pathlib import Path + +from traitlets.config.application import Application, catch_config_error +from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader +from IPython.core import release, crashhandler +from IPython.core.profiledir import ProfileDir, ProfileDirError +from IPython.paths import get_ipython_dir, get_ipython_package_dir +from IPython.utils.path import ensure_dir_exists +from traitlets import ( + List, Unicode, Type, Bool, Set, Instance, Undefined, + default, observe, +) + +if os.name == "nt": + programdata = os.environ.get("PROGRAMDATA", None) + if programdata is not None: + SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] + else: # PROGRAMDATA is not defined by default on XP. + SYSTEM_CONFIG_DIRS = [] +else: + SYSTEM_CONFIG_DIRS = [ + "/usr/local/etc/ipython", + "/etc/ipython", + ] + + +ENV_CONFIG_DIRS = [] +_env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') +if _env_config_dir not in SYSTEM_CONFIG_DIRS: + # only add ENV_CONFIG if sys.prefix is not already included + ENV_CONFIG_DIRS.append(_env_config_dir) + + +_envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') +if _envvar in {None, ''}: + IPYTHON_SUPPRESS_CONFIG_ERRORS = None +else: + if _envvar.lower() in {'1','true'}: + IPYTHON_SUPPRESS_CONFIG_ERRORS = True + elif _envvar.lower() in {'0','false'} : + IPYTHON_SUPPRESS_CONFIG_ERRORS = False + else: + sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar ) + +# aliases and flags + +base_aliases = {} +if isinstance(Application.aliases, dict): + # traitlets 5 + base_aliases.update(Application.aliases) +base_aliases.update( + { + "profile-dir": "ProfileDir.location", + "profile": "BaseIPythonApplication.profile", + "ipython-dir": "BaseIPythonApplication.ipython_dir", + "log-level": "Application.log_level", + "config": "BaseIPythonApplication.extra_config_file", + } +) + +base_flags = dict() +if isinstance(Application.flags, dict): + # traitlets 5 + base_flags.update(Application.flags) +base_flags.update( + dict( + debug=( + {"Application": {"log_level": logging.DEBUG}}, + "set log level to logging.DEBUG (maximize logging output)", + ), + quiet=( + {"Application": {"log_level": logging.CRITICAL}}, + "set log level to logging.CRITICAL (minimize logging output)", + ), + init=( + { + "BaseIPythonApplication": { + "copy_config_files": True, + "auto_create": True, + } + }, + """Initialize profile with default config files. This is equivalent + to running `ipython profile create ` prior to startup. + """, + ), + ) +) + + +class ProfileAwareConfigLoader(PyFileConfigLoader): + """A Python file config loader that is aware of IPython profiles.""" + def load_subconfig(self, fname, path=None, profile=None): + if profile is not None: + try: + profile_dir = ProfileDir.find_profile_dir_by_name( + get_ipython_dir(), + profile, + ) + except ProfileDirError: + return + path = profile_dir.location + return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) + +class BaseIPythonApplication(Application): + + name = u'ipython' + description = Unicode(u'IPython: an enhanced interactive Python shell.') + version = Unicode(release.version) + + aliases = base_aliases + flags = base_flags + classes = List([ProfileDir]) + + # enable `load_subconfig('cfg.py', profile='name')` + python_config_loader_class = ProfileAwareConfigLoader + + # Track whether the config_file has changed, + # because some logic happens only if we aren't using the default. + config_file_specified = Set() + + config_file_name = Unicode() + @default('config_file_name') + def _config_file_name_default(self): + return self.name.replace('-','_') + u'_config.py' + @observe('config_file_name') + def _config_file_name_changed(self, change): + if change['new'] != change['old']: + self.config_file_specified.add(change['new']) + + # The directory that contains IPython's builtin profiles. + builtin_profile_dir = Unicode( + os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') + ) + + config_file_paths = List(Unicode()) + @default('config_file_paths') + def _config_file_paths_default(self): + return [] + + extra_config_file = Unicode( + help="""Path to an extra config file to load. + + If specified, load this config file in addition to any other IPython config. + """).tag(config=True) + @observe('extra_config_file') + def _extra_config_file_changed(self, change): + old = change['old'] + new = change['new'] + try: + self.config_files.remove(old) + except ValueError: + pass + self.config_file_specified.add(new) + self.config_files.append(new) + + profile = Unicode(u'default', + help="""The IPython profile to use.""" + ).tag(config=True) + + @observe('profile') + def _profile_changed(self, change): + self.builtin_profile_dir = os.path.join( + get_ipython_package_dir(), u'config', u'profile', change['new'] + ) + + add_ipython_dir_to_sys_path = Bool( + False, + """Should the IPython profile directory be added to sys path ? + + This option was non-existing before IPython 8.0, and ipython_dir was added to + sys path to allow import of extensions present there. This was historical + baggage from when pip did not exist. This now default to false, + but can be set to true for legacy reasons. + """, + ).tag(config=True) + + ipython_dir = Unicode( + help=""" + The name of the IPython directory. This directory is used for logging + configuration (through profiles), history storage, etc. The default + is usually $HOME/.ipython. This option can also be specified through + the environment variable IPYTHONDIR. + """ + ).tag(config=True) + @default('ipython_dir') + def _ipython_dir_default(self): + d = get_ipython_dir() + self._ipython_dir_changed({ + 'name': 'ipython_dir', + 'old': d, + 'new': d, + }) + return d + + _in_init_profile_dir = False + profile_dir = Instance(ProfileDir, allow_none=True) + @default('profile_dir') + def _profile_dir_default(self): + # avoid recursion + if self._in_init_profile_dir: + return + # profile_dir requested early, force initialization + self.init_profile_dir() + return self.profile_dir + + overwrite = Bool(False, + help="""Whether to overwrite existing config files when copying""" + ).tag(config=True) + auto_create = Bool(False, + help="""Whether to create profile dir if it doesn't exist""" + ).tag(config=True) + + config_files = List(Unicode()) + @default('config_files') + def _config_files_default(self): + return [self.config_file_name] + + copy_config_files = Bool(False, + help="""Whether to install the default config files into the profile dir. + If a new profile is being created, and IPython contains config files for that + profile, then they will be staged into the new directory. Otherwise, + default config files will be automatically generated. + """).tag(config=True) + + verbose_crash = Bool(False, + help="""Create a massive crash report when IPython encounters what may be an + internal error. The default is to append a short message to the + usual traceback""").tag(config=True) + + # The class to use as the crash handler. + crash_handler_class = Type(crashhandler.CrashHandler) + + @catch_config_error + def __init__(self, **kwargs): + super(BaseIPythonApplication, self).__init__(**kwargs) + # ensure current working directory exists + try: + os.getcwd() + except: + # exit if cwd doesn't exist + self.log.error("Current working directory doesn't exist.") + self.exit(1) + + #------------------------------------------------------------------------- + # Various stages of Application creation + #------------------------------------------------------------------------- + + def init_crash_handler(self): + """Create a crash handler, typically setting sys.excepthook to it.""" + self.crash_handler = self.crash_handler_class(self) + sys.excepthook = self.excepthook + def unset_crashhandler(): + sys.excepthook = sys.__excepthook__ + atexit.register(unset_crashhandler) + + def excepthook(self, etype, evalue, tb): + """this is sys.excepthook after init_crashhandler + + set self.verbose_crash=True to use our full crashhandler, instead of + a regular traceback with a short message (crash_handler_lite) + """ + + if self.verbose_crash: + return self.crash_handler(etype, evalue, tb) + else: + return crashhandler.crash_handler_lite(etype, evalue, tb) + + @observe('ipython_dir') + def _ipython_dir_changed(self, change): + old = change['old'] + new = change['new'] + if old is not Undefined: + str_old = os.path.abspath(old) + if str_old in sys.path: + sys.path.remove(str_old) + if self.add_ipython_dir_to_sys_path: + str_path = os.path.abspath(new) + sys.path.append(str_path) + ensure_dir_exists(new) + readme = os.path.join(new, "README") + readme_src = os.path.join( + get_ipython_package_dir(), "config", "profile", "README" + ) + if not os.path.exists(readme) and os.path.exists(readme_src): + shutil.copy(readme_src, readme) + for d in ("extensions", "nbextensions"): + path = os.path.join(new, d) + try: + ensure_dir_exists(path) + except OSError as e: + # this will not be EEXIST + self.log.error("couldn't create path %s: %s", path, e) + self.log.debug("IPYTHONDIR set to: %s" % new) + + def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): + """Load the config file. + + By default, errors in loading config are handled, and a warning + printed on screen. For testing, the suppress_errors option is set + to False, so errors will make tests fail. + + `suppress_errors` default value is to be `None` in which case the + behavior default to the one of `traitlets.Application`. + + The default value can be set : + - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). + - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). + - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. + + Any other value are invalid, and will make IPython exit with a non-zero return code. + """ + + + self.log.debug("Searching path %s for config files", self.config_file_paths) + base_config = 'ipython_config.py' + self.log.debug("Attempting to load config file: %s" % + base_config) + try: + if suppress_errors is not None: + old_value = Application.raise_config_file_errors + Application.raise_config_file_errors = not suppress_errors; + Application.load_config_file( + self, + base_config, + path=self.config_file_paths + ) + except ConfigFileNotFound: + # ignore errors loading parent + self.log.debug("Config file %s not found", base_config) + pass + if suppress_errors is not None: + Application.raise_config_file_errors = old_value + + for config_file_name in self.config_files: + if not config_file_name or config_file_name == base_config: + continue + self.log.debug("Attempting to load config file: %s" % + self.config_file_name) + try: + Application.load_config_file( + self, + config_file_name, + path=self.config_file_paths + ) + except ConfigFileNotFound: + # Only warn if the default config file was NOT being used. + if config_file_name in self.config_file_specified: + msg = self.log.warning + else: + msg = self.log.debug + msg("Config file not found, skipping: %s", config_file_name) + except Exception: + # For testing purposes. + if not suppress_errors: + raise + self.log.warning("Error loading config file: %s" % + self.config_file_name, exc_info=True) + + def init_profile_dir(self): + """initialize the profile dir""" + self._in_init_profile_dir = True + if self.profile_dir is not None: + # already ran + return + if 'ProfileDir.location' not in self.config: + # location not specified, find by profile name + try: + p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) + except ProfileDirError: + # not found, maybe create it (always create default profile) + if self.auto_create or self.profile == 'default': + try: + p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) + except ProfileDirError: + self.log.fatal("Could not create profile: %r"%self.profile) + self.exit(1) + else: + self.log.info("Created profile dir: %r"%p.location) + else: + self.log.fatal("Profile %r not found."%self.profile) + self.exit(1) + else: + self.log.debug(f"Using existing profile dir: {p.location!r}") + else: + location = self.config.ProfileDir.location + # location is fully specified + try: + p = ProfileDir.find_profile_dir(location, self.config) + except ProfileDirError: + # not found, maybe create it + if self.auto_create: + try: + p = ProfileDir.create_profile_dir(location, self.config) + except ProfileDirError: + self.log.fatal("Could not create profile directory: %r"%location) + self.exit(1) + else: + self.log.debug("Creating new profile dir: %r"%location) + else: + self.log.fatal("Profile directory %r not found."%location) + self.exit(1) + else: + self.log.debug(f"Using existing profile dir: {p.location!r}") + # if profile_dir is specified explicitly, set profile name + dir_name = os.path.basename(p.location) + if dir_name.startswith('profile_'): + self.profile = dir_name[8:] + + self.profile_dir = p + self.config_file_paths.append(p.location) + self._in_init_profile_dir = False + + def init_config_files(self): + """[optionally] copy default config files into profile dir.""" + self.config_file_paths.extend(ENV_CONFIG_DIRS) + self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) + # copy config files + path = Path(self.builtin_profile_dir) + if self.copy_config_files: + src = self.profile + + cfg = self.config_file_name + if path and (path / cfg).exists(): + self.log.warning( + "Staging %r from %s into %r [overwrite=%s]" + % (cfg, src, self.profile_dir.location, self.overwrite) + ) + self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) + else: + self.stage_default_config_file() + else: + # Still stage *bundled* config files, but not generated ones + # This is necessary for `ipython profile=sympy` to load the profile + # on the first go + files = path.glob("*.py") + for fullpath in files: + cfg = fullpath.name + if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): + # file was copied + self.log.warning("Staging bundled %s from %s into %r"%( + cfg, self.profile, self.profile_dir.location) + ) + + + def stage_default_config_file(self): + """auto generate default config file, and stage it into the profile.""" + s = self.generate_config_file() + config_file = Path(self.profile_dir.location) / self.config_file_name + if self.overwrite or not config_file.exists(): + self.log.warning("Generating default config file: %r" % (config_file)) + config_file.write_text(s, encoding="utf-8") + + @catch_config_error + def initialize(self, argv=None): + # don't hook up crash handler before parsing command-line + self.parse_command_line(argv) + self.init_crash_handler() + if self.subapp is not None: + # stop here if subapp is taking over + return + # save a copy of CLI config to re-load after config files + # so that it has highest priority + cl_config = deepcopy(self.config) + self.init_profile_dir() + self.init_config_files() + self.load_config_file() + # enforce cl-opts override configfile opts: + self.update_config(cl_config) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/async_helpers.py b/.venv/lib/python3.8/site-packages/IPython/core/async_helpers.py new file mode 100644 index 0000000..0e7db0b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/async_helpers.py @@ -0,0 +1,156 @@ +""" +Async helper function that are invalid syntax on Python 3.5 and below. + +This code is best effort, and may have edge cases not behaving as expected. In +particular it contain a number of heuristics to detect whether code is +effectively async and need to run in an event loop or not. + +Some constructs (like top-level `return`, or `yield`) are taken care of +explicitly to actually raise a SyntaxError and stay as close as possible to +Python semantics. +""" + + +import ast +import asyncio +import inspect +from functools import wraps + +_asyncio_event_loop = None + + +def get_asyncio_loop(): + """asyncio has deprecated get_event_loop + + Replicate it here, with our desired semantics: + + - always returns a valid, not-closed loop + - not thread-local like asyncio's, + because we only want one loop for IPython + - if called from inside a coroutine (e.g. in ipykernel), + return the running loop + + .. versionadded:: 8.0 + """ + try: + return asyncio.get_running_loop() + except RuntimeError: + # not inside a coroutine, + # track our own global + pass + + # not thread-local like asyncio's, + # because we only track one event loop to run for IPython itself, + # always in the main thread. + global _asyncio_event_loop + if _asyncio_event_loop is None or _asyncio_event_loop.is_closed(): + _asyncio_event_loop = asyncio.new_event_loop() + return _asyncio_event_loop + + +class _AsyncIORunner: + def __call__(self, coro): + """ + Handler for asyncio autoawait + """ + return get_asyncio_loop().run_until_complete(coro) + + def __str__(self): + return "asyncio" + + +_asyncio_runner = _AsyncIORunner() + + +class _AsyncIOProxy: + """Proxy-object for an asyncio + + Any coroutine methods will be wrapped in event_loop.run_ + """ + + def __init__(self, obj, event_loop): + self._obj = obj + self._event_loop = event_loop + + def __repr__(self): + return f"<_AsyncIOProxy({self._obj!r})>" + + def __getattr__(self, key): + attr = getattr(self._obj, key) + if inspect.iscoroutinefunction(attr): + # if it's a coroutine method, + # return a threadsafe wrapper onto the _current_ asyncio loop + @wraps(attr) + def _wrapped(*args, **kwargs): + concurrent_future = asyncio.run_coroutine_threadsafe( + attr(*args, **kwargs), self._event_loop + ) + return asyncio.wrap_future(concurrent_future) + + return _wrapped + else: + return attr + + def __dir__(self): + return dir(self._obj) + + +def _curio_runner(coroutine): + """ + handler for curio autoawait + """ + import curio + + return curio.run(coroutine) + + +def _trio_runner(async_fn): + import trio + + async def loc(coro): + """ + We need the dummy no-op async def to protect from + trio's internal. See https://github.com/python-trio/trio/issues/89 + """ + return await coro + + return trio.run(loc, async_fn) + + +def _pseudo_sync_runner(coro): + """ + A runner that does not really allow async execution, and just advance the coroutine. + + See discussion in https://github.com/python-trio/trio/issues/608, + + Credit to Nathaniel Smith + """ + try: + coro.send(None) + except StopIteration as exc: + return exc.value + else: + # TODO: do not raise but return an execution result with the right info. + raise RuntimeError( + "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__) + ) + + +def _should_be_async(cell: str) -> bool: + """Detect if a block of code need to be wrapped in an `async def` + + Attempt to parse the block of code, it it compile we're fine. + Otherwise we wrap if and try to compile. + + If it works, assume it should be async. Otherwise Return False. + + Not handled yet: If the block of code has a return statement as the top + level, it will be seen as async. This is a know limitation. + """ + try: + code = compile( + cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) + ) + return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE + except (SyntaxError, MemoryError): + return False diff --git a/.venv/lib/python3.8/site-packages/IPython/core/autocall.py b/.venv/lib/python3.8/site-packages/IPython/core/autocall.py new file mode 100644 index 0000000..5f7720b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/autocall.py @@ -0,0 +1,70 @@ +# encoding: utf-8 +""" +Autocall capabilities for IPython.core. + +Authors: + +* Brian Granger +* Fernando Perez +* Thomas Kluyver + +Notes +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +class IPyAutocall(object): + """ Instances of this class are always autocalled + + This happens regardless of 'autocall' variable state. Use this to + develop macro-like mechanisms. + """ + _ip = None + rewrite = True + def __init__(self, ip=None): + self._ip = ip + + def set_ip(self, ip): + """ Will be used to set _ip point to current ipython instance b/f call + + Override this method if you don't want this to happen. + + """ + self._ip = ip + + +class ExitAutocall(IPyAutocall): + """An autocallable object which will be added to the user namespace so that + exit, exit(), quit or quit() are all valid ways to close the shell.""" + rewrite = False + + def __call__(self): + self._ip.ask_exit() + +class ZMQExitAutocall(ExitAutocall): + """Exit IPython. Autocallable, so it needn't be explicitly called. + + Parameters + ---------- + keep_kernel : bool + If True, leave the kernel alive. Otherwise, tell the kernel to exit too + (default). + """ + def __call__(self, keep_kernel=False): + self._ip.keepkernel_on_exit = keep_kernel + self._ip.ask_exit() diff --git a/.venv/lib/python3.8/site-packages/IPython/core/builtin_trap.py b/.venv/lib/python3.8/site-packages/IPython/core/builtin_trap.py new file mode 100644 index 0000000..a8ea4ab --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/builtin_trap.py @@ -0,0 +1,86 @@ +""" +A context manager for managing things injected into :mod:`builtins`. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +import builtins as builtin_mod + +from traitlets.config.configurable import Configurable + +from traitlets import Instance + + +class __BuiltinUndefined(object): pass +BuiltinUndefined = __BuiltinUndefined() + +class __HideBuiltin(object): pass +HideBuiltin = __HideBuiltin() + + +class BuiltinTrap(Configurable): + + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', + allow_none=True) + + def __init__(self, shell=None): + super(BuiltinTrap, self).__init__(shell=shell, config=None) + self._orig_builtins = {} + # We define this to track if a single BuiltinTrap is nested. + # Only turn off the trap when the outermost call to __exit__ is made. + self._nested_level = 0 + self.shell = shell + # builtins we always add - if set to HideBuiltin, they will just + # be removed instead of being replaced by something else + self.auto_builtins = {'exit': HideBuiltin, + 'quit': HideBuiltin, + 'get_ipython': self.shell.get_ipython, + } + + def __enter__(self): + if self._nested_level == 0: + self.activate() + self._nested_level += 1 + # I return self, so callers can use add_builtin in a with clause. + return self + + def __exit__(self, type, value, traceback): + if self._nested_level == 1: + self.deactivate() + self._nested_level -= 1 + # Returning False will cause exceptions to propagate + return False + + def add_builtin(self, key, value): + """Add a builtin and save the original.""" + bdict = builtin_mod.__dict__ + orig = bdict.get(key, BuiltinUndefined) + if value is HideBuiltin: + if orig is not BuiltinUndefined: #same as 'key in bdict' + self._orig_builtins[key] = orig + del bdict[key] + else: + self._orig_builtins[key] = orig + bdict[key] = value + + def remove_builtin(self, key, orig): + """Remove an added builtin and re-set the original.""" + if orig is BuiltinUndefined: + del builtin_mod.__dict__[key] + else: + builtin_mod.__dict__[key] = orig + + def activate(self): + """Store ipython references in the __builtin__ namespace.""" + + add_builtin = self.add_builtin + for name, func in self.auto_builtins.items(): + add_builtin(name, func) + + def deactivate(self): + """Remove any builtins which might have been added by add_builtins, or + restore overwritten ones to their previous values.""" + remove_builtin = self.remove_builtin + for key, val in self._orig_builtins.items(): + remove_builtin(key, val) + self._orig_builtins.clear() + self._builtins_added = False diff --git a/.venv/lib/python3.8/site-packages/IPython/core/compilerop.py b/.venv/lib/python3.8/site-packages/IPython/core/compilerop.py new file mode 100644 index 0000000..b43e570 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/compilerop.py @@ -0,0 +1,196 @@ +"""Compiler tools with improved interactive support. + +Provides compilation machinery similar to codeop, but with caching support so +we can provide interactive tracebacks. + +Authors +------- +* Robert Kern +* Fernando Perez +* Thomas Kluyver +""" + +# Note: though it might be more natural to name this module 'compiler', that +# name is in the stdlib and name collisions with the stdlib tend to produce +# weird problems (often with third-party tools). + +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib imports +import __future__ +from ast import PyCF_ONLY_AST +import codeop +import functools +import hashlib +import linecache +import operator +import time +from contextlib import contextmanager + +#----------------------------------------------------------------------------- +# Constants +#----------------------------------------------------------------------------- + +# Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, +# this is used as a bitmask to extract future-related code flags. +PyCF_MASK = functools.reduce(operator.or_, + (getattr(__future__, fname).compiler_flag + for fname in __future__.all_feature_names)) + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + +def code_name(code, number=0): + """ Compute a (probably) unique name for code for caching. + + This now expects code to be unicode. + """ + hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() + # Include the number and 12 characters of the hash in the name. It's + # pretty much impossible that in a single session we'll have collisions + # even with truncated hashes, and the full one makes tracebacks too long + return ''.format(number, hash_digest[:12]) + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +class CachingCompiler(codeop.Compile): + """A compiler that caches code compiled from interactive statements. + """ + + def __init__(self): + codeop.Compile.__init__(self) + + # This is ugly, but it must be done this way to allow multiple + # simultaneous ipython instances to coexist. Since Python itself + # directly accesses the data structures in the linecache module, and + # the cache therein is global, we must work with that data structure. + # We must hold a reference to the original checkcache routine and call + # that in our own check_cache() below, but the special IPython cache + # must also be shared by all IPython instances. If we were to hold + # separate caches (one in each CachingCompiler instance), any call made + # by Python itself to linecache.checkcache() would obliterate the + # cached data from the other IPython instances. + if not hasattr(linecache, '_ipython_cache'): + linecache._ipython_cache = {} + if not hasattr(linecache, '_checkcache_ori'): + linecache._checkcache_ori = linecache.checkcache + # Now, we must monkeypatch the linecache directly so that parts of the + # stdlib that call it outside our control go through our codepath + # (otherwise we'd lose our tracebacks). + linecache.checkcache = check_linecache_ipython + + # Caching a dictionary { filename: execution_count } for nicely + # rendered tracebacks. The filename corresponds to the filename + # argument used for the builtins.compile function. + self._filename_map = {} + + def ast_parse(self, source, filename='', symbol='exec'): + """Parse code to an AST with the current compiler flags active. + + Arguments are exactly the same as ast.parse (in the standard library), + and are passed to the built-in compile function.""" + return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) + + def reset_compiler_flags(self): + """Reset compiler flags to default state.""" + # This value is copied from codeop.Compile.__init__, so if that ever + # changes, it will need to be updated. + self.flags = codeop.PyCF_DONT_IMPLY_DEDENT + + @property + def compiler_flags(self): + """Flags currently active in the compilation process. + """ + return self.flags + + def get_code_name(self, raw_code, transformed_code, number): + """Compute filename given the code, and the cell number. + + Parameters + ---------- + raw_code : str + The raw cell code. + transformed_code : str + The executable Python source code to cache and compile. + number : int + A number which forms part of the code's name. Used for the execution + counter. + + Returns + ------- + The computed filename. + """ + return code_name(transformed_code, number) + + def cache(self, transformed_code, number=0, raw_code=None): + """Make a name for a block of code, and cache the code. + + Parameters + ---------- + transformed_code : str + The executable Python source code to cache and compile. + number : int + A number which forms part of the code's name. Used for the execution + counter. + raw_code : str + The raw code before transformation, if None, set to `transformed_code`. + + Returns + ------- + The name of the cached code (as a string). Pass this as the filename + argument to compilation, so that tracebacks are correctly hooked up. + """ + if raw_code is None: + raw_code = transformed_code + + name = self.get_code_name(raw_code, transformed_code, number) + + # Save the execution count + self._filename_map[name] = number + + entry = ( + len(transformed_code), + time.time(), + [line + "\n" for line in transformed_code.splitlines()], + name, + ) + linecache.cache[name] = entry + linecache._ipython_cache[name] = entry + return name + + @contextmanager + def extra_flags(self, flags): + ## bits that we'll set to 1 + turn_on_bits = ~self.flags & flags + + + self.flags = self.flags | flags + try: + yield + finally: + # turn off only the bits we turned on so that something like + # __future__ that set flags stays. + self.flags &= ~turn_on_bits + + +def check_linecache_ipython(*args): + """Call linecache.checkcache() safely protecting our cached values. + """ + # First call the original checkcache as intended + linecache._checkcache_ori(*args) + # Then, update back the cache with our data, so that tracebacks related + # to our compiled codes can be produced. + linecache.cache.update(linecache._ipython_cache) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/completer.py b/.venv/lib/python3.8/site-packages/IPython/core/completer.py new file mode 100644 index 0000000..0579e68 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/completer.py @@ -0,0 +1,2272 @@ +"""Completion for IPython. + +This module started as fork of the rlcompleter module in the Python standard +library. The original enhancements made to rlcompleter have been sent +upstream and were accepted as of Python 2.3, + +This module now support a wide variety of completion mechanism both available +for normal classic Python code, as well as completer for IPython specific +Syntax like magics. + +Latex and Unicode completion +============================ + +IPython and compatible frontends not only can complete your code, but can help +you to input a wide range of characters. In particular we allow you to insert +a unicode character using the tab completion mechanism. + +Forward latex/unicode completion +-------------------------------- + +Forward completion allows you to easily type a unicode character using its latex +name, or unicode long description. To do so type a backslash follow by the +relevant name and press tab: + + +Using latex completion: + +.. code:: + + \\alpha + α + +or using unicode completion: + + +.. code:: + + \\GREEK SMALL LETTER ALPHA + α + + +Only valid Python identifiers will complete. Combining characters (like arrow or +dots) are also available, unlike latex they need to be put after the their +counterpart that is to say, `F\\\\vec` is correct, not `\\\\vecF`. + +Some browsers are known to display combining characters incorrectly. + +Backward latex completion +------------------------- + +It is sometime challenging to know how to type a character, if you are using +IPython, or any compatible frontend you can prepend backslash to the character +and press `` to expand it to its latex form. + +.. code:: + + \\α + \\alpha + + +Both forward and backward completions can be deactivated by setting the +``Completer.backslash_combining_completions`` option to ``False``. + + +Experimental +============ + +Starting with IPython 6.0, this module can make use of the Jedi library to +generate completions both using static analysis of the code, and dynamically +inspecting multiple namespaces. Jedi is an autocompletion and static analysis +for Python. The APIs attached to this new mechanism is unstable and will +raise unless use in an :any:`provisionalcompleter` context manager. + +You will find that the following are experimental: + + - :any:`provisionalcompleter` + - :any:`IPCompleter.completions` + - :any:`Completion` + - :any:`rectify_completions` + +.. note:: + + better name for :any:`rectify_completions` ? + +We welcome any feedback on these new API, and we also encourage you to try this +module in debug mode (start IPython with ``--Completer.debug=True``) in order +to have extra logging information if :any:`jedi` is crashing, or if current +IPython completer pending deprecations are returning results not yet handled +by :any:`jedi` + +Using Jedi for tab completion allow snippets like the following to work without +having to execute any code: + + >>> myvar = ['hello', 42] + ... myvar[1].bi + +Tab completion will be able to infer that ``myvar[1]`` is a real number without +executing any code unlike the previously available ``IPCompleter.greedy`` +option. + +Be sure to update :any:`jedi` to the latest stable version or to try the +current development version to get better completions. +""" + + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +# +# Some of this code originated from rlcompleter in the Python standard library +# Copyright (C) 2001 Python Software Foundation, www.python.org + + +import builtins as builtin_mod +import glob +import inspect +import itertools +import keyword +import os +import re +import string +import sys +import time +import unicodedata +import uuid +import warnings +from contextlib import contextmanager +from importlib import import_module +from types import SimpleNamespace +from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional + +from IPython.core.error import TryNext +from IPython.core.inputtransformer2 import ESC_MAGIC +from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol +from IPython.core.oinspect import InspectColors +from IPython.testing.skipdoctest import skip_doctest +from IPython.utils import generics +from IPython.utils.dir2 import dir2, get_real_method +from IPython.utils.path import ensure_dir_exists +from IPython.utils.process import arg_split +from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe +from traitlets.config.configurable import Configurable + +import __main__ + +# skip module docstests +__skip_doctest__ = True + +try: + import jedi + jedi.settings.case_insensitive_completion = False + import jedi.api.helpers + import jedi.api.classes + JEDI_INSTALLED = True +except ImportError: + JEDI_INSTALLED = False +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +# ranges where we have most of the valid unicode names. We could be more finer +# grained but is it worth it for performance While unicode have character in the +# range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I +# write this). With below range we cover them all, with a density of ~67% +# biggest next gap we consider only adds up about 1% density and there are 600 +# gaps that would need hard coding. +_UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] + +# Public API +__all__ = ['Completer','IPCompleter'] + +if sys.platform == 'win32': + PROTECTABLES = ' ' +else: + PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&' + +# Protect against returning an enormous number of completions which the frontend +# may have trouble processing. +MATCHES_LIMIT = 500 + + +class ProvisionalCompleterWarning(FutureWarning): + """ + Exception raise by an experimental feature in this module. + + Wrap code in :any:`provisionalcompleter` context manager if you + are certain you want to use an unstable feature. + """ + pass + +warnings.filterwarnings('error', category=ProvisionalCompleterWarning) + + +@skip_doctest +@contextmanager +def provisionalcompleter(action='ignore'): + """ + This context manager has to be used in any place where unstable completer + behavior and API may be called. + + >>> with provisionalcompleter(): + ... completer.do_experimental_things() # works + + >>> completer.do_experimental_things() # raises. + + .. note:: + + Unstable + + By using this context manager you agree that the API in use may change + without warning, and that you won't complain if they do so. + + You also understand that, if the API is not to your liking, you should report + a bug to explain your use case upstream. + + We'll be happy to get your feedback, feature requests, and improvements on + any of the unstable APIs! + """ + with warnings.catch_warnings(): + warnings.filterwarnings(action, category=ProvisionalCompleterWarning) + yield + + +def has_open_quotes(s): + """Return whether a string has open quotes. + + This simply counts whether the number of quote characters of either type in + the string is odd. + + Returns + ------- + If there is an open quote, the quote character is returned. Else, return + False. + """ + # We check " first, then ', so complex cases with nested quotes will get + # the " to take precedence. + if s.count('"') % 2: + return '"' + elif s.count("'") % 2: + return "'" + else: + return False + + +def protect_filename(s, protectables=PROTECTABLES): + """Escape a string to protect certain characters.""" + if set(s) & set(protectables): + if sys.platform == "win32": + return '"' + s + '"' + else: + return "".join(("\\" + c if c in protectables else c) for c in s) + else: + return s + + +def expand_user(path:str) -> Tuple[str, bool, str]: + """Expand ``~``-style usernames in strings. + + This is similar to :func:`os.path.expanduser`, but it computes and returns + extra information that will be useful if the input was being used in + computing completions, and you wish to return the completions with the + original '~' instead of its expanded value. + + Parameters + ---------- + path : str + String to be expanded. If no ~ is present, the output is the same as the + input. + + Returns + ------- + newpath : str + Result of ~ expansion in the input path. + tilde_expand : bool + Whether any expansion was performed or not. + tilde_val : str + The value that ~ was replaced with. + """ + # Default values + tilde_expand = False + tilde_val = '' + newpath = path + + if path.startswith('~'): + tilde_expand = True + rest = len(path)-1 + newpath = os.path.expanduser(path) + if rest: + tilde_val = newpath[:-rest] + else: + tilde_val = newpath + + return newpath, tilde_expand, tilde_val + + +def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str: + """Does the opposite of expand_user, with its outputs. + """ + if tilde_expand: + return path.replace(tilde_val, '~') + else: + return path + + +def completions_sorting_key(word): + """key for sorting completions + + This does several things: + + - Demote any completions starting with underscores to the end + - Insert any %magic and %%cellmagic completions in the alphabetical order + by their name + """ + prio1, prio2 = 0, 0 + + if word.startswith('__'): + prio1 = 2 + elif word.startswith('_'): + prio1 = 1 + + if word.endswith('='): + prio1 = -1 + + if word.startswith('%%'): + # If there's another % in there, this is something else, so leave it alone + if not "%" in word[2:]: + word = word[2:] + prio2 = 2 + elif word.startswith('%'): + if not "%" in word[1:]: + word = word[1:] + prio2 = 1 + + return prio1, word, prio2 + + +class _FakeJediCompletion: + """ + This is a workaround to communicate to the UI that Jedi has crashed and to + report a bug. Will be used only id :any:`IPCompleter.debug` is set to true. + + Added in IPython 6.0 so should likely be removed for 7.0 + + """ + + def __init__(self, name): + + self.name = name + self.complete = name + self.type = 'crashed' + self.name_with_symbols = name + self.signature = '' + self._origin = 'fake' + + def __repr__(self): + return '' + + +class Completion: + """ + Completion object used and return by IPython completers. + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + It will also raise unless use in proper context manager. + + This act as a middle ground :any:`Completion` object between the + :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion + object. While Jedi need a lot of information about evaluator and how the + code should be ran/inspected, PromptToolkit (and other frontend) mostly + need user facing information. + + - Which range should be replaced replaced by what. + - Some metadata (like completion type), or meta information to displayed to + the use user. + + For debugging purpose we can also store the origin of the completion (``jedi``, + ``IPython.python_matches``, ``IPython.magics_matches``...). + """ + + __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] + + def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None: + warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). " + "It may change without warnings. " + "Use in corresponding context manager.", + category=ProvisionalCompleterWarning, stacklevel=2) + + self.start = start + self.end = end + self.text = text + self.type = type + self.signature = signature + self._origin = _origin + + def __repr__(self): + return '' % \ + (self.start, self.end, self.text, self.type or '?', self.signature or '?') + + def __eq__(self, other)->Bool: + """ + Equality and hash do not hash the type (as some completer may not be + able to infer the type), but are use to (partially) de-duplicate + completion. + + Completely de-duplicating completion is a bit tricker that just + comparing as it depends on surrounding text, which Completions are not + aware of. + """ + return self.start == other.start and \ + self.end == other.end and \ + self.text == other.text + + def __hash__(self): + return hash((self.start, self.end, self.text)) + + +_IC = Iterable[Completion] + + +def _deduplicate_completions(text: str, completions: _IC)-> _IC: + """ + Deduplicate a set of completions. + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + + Parameters + ---------- + text : str + text that should be completed. + completions : Iterator[Completion] + iterator over the completions to deduplicate + + Yields + ------ + `Completions` objects + Completions coming from multiple sources, may be different but end up having + the same effect when applied to ``text``. If this is the case, this will + consider completions as equal and only emit the first encountered. + Not folded in `completions()` yet for debugging purpose, and to detect when + the IPython completer does return things that Jedi does not, but should be + at some point. + """ + completions = list(completions) + if not completions: + return + + new_start = min(c.start for c in completions) + new_end = max(c.end for c in completions) + + seen = set() + for c in completions: + new_text = text[new_start:c.start] + c.text + text[c.end:new_end] + if new_text not in seen: + yield c + seen.add(new_text) + + +def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC: + """ + Rectify a set of completions to all have the same ``start`` and ``end`` + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + It will also raise unless use in proper context manager. + + Parameters + ---------- + text : str + text that should be completed. + completions : Iterator[Completion] + iterator over the completions to rectify + _debug : bool + Log failed completion + + Notes + ----- + :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though + the Jupyter Protocol requires them to behave like so. This will readjust + the completion to have the same ``start`` and ``end`` by padding both + extremities with surrounding text. + + During stabilisation should support a ``_debug`` option to log which + completion are return by the IPython completer and not found in Jedi in + order to make upstream bug report. + """ + warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). " + "It may change without warnings. " + "Use in corresponding context manager.", + category=ProvisionalCompleterWarning, stacklevel=2) + + completions = list(completions) + if not completions: + return + starts = (c.start for c in completions) + ends = (c.end for c in completions) + + new_start = min(starts) + new_end = max(ends) + + seen_jedi = set() + seen_python_matches = set() + for c in completions: + new_text = text[new_start:c.start] + c.text + text[c.end:new_end] + if c._origin == 'jedi': + seen_jedi.add(new_text) + elif c._origin == 'IPCompleter.python_matches': + seen_python_matches.add(new_text) + yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) + diff = seen_python_matches.difference(seen_jedi) + if diff and _debug: + print('IPython.python matches have extras:', diff) + + +if sys.platform == 'win32': + DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?' +else: + DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' + +GREEDY_DELIMS = ' =\r\n' + + +class CompletionSplitter(object): + """An object to split an input line in a manner similar to readline. + + By having our own implementation, we can expose readline-like completion in + a uniform manner to all frontends. This object only needs to be given the + line of text to be split and the cursor position on said line, and it + returns the 'word' to be completed on at the cursor after splitting the + entire line. + + What characters are used as splitting delimiters can be controlled by + setting the ``delims`` attribute (this is a property that internally + automatically builds the necessary regular expression)""" + + # Private interface + + # A string of delimiter characters. The default value makes sense for + # IPython's most typical usage patterns. + _delims = DELIMS + + # The expression (a normal string) to be compiled into a regular expression + # for actual splitting. We store it as an attribute mostly for ease of + # debugging, since this type of code can be so tricky to debug. + _delim_expr = None + + # The regular expression that does the actual splitting + _delim_re = None + + def __init__(self, delims=None): + delims = CompletionSplitter._delims if delims is None else delims + self.delims = delims + + @property + def delims(self): + """Return the string of delimiter characters.""" + return self._delims + + @delims.setter + def delims(self, delims): + """Set the delimiters for line splitting.""" + expr = '[' + ''.join('\\'+ c for c in delims) + ']' + self._delim_re = re.compile(expr) + self._delims = delims + self._delim_expr = expr + + def split_line(self, line, cursor_pos=None): + """Split a line of text with a cursor at the given position. + """ + l = line if cursor_pos is None else line[:cursor_pos] + return self._delim_re.split(l)[-1] + + + +class Completer(Configurable): + + greedy = Bool(False, + help="""Activate greedy completion + PENDING DEPRECATION. this is now mostly taken care of with Jedi. + + This will enable completion on elements of lists, results of function calls, etc., + but can be unsafe because the code is actually evaluated on TAB. + """ + ).tag(config=True) + + use_jedi = Bool(default_value=JEDI_INSTALLED, + help="Experimental: Use Jedi to generate autocompletions. " + "Default to True if jedi is installed.").tag(config=True) + + jedi_compute_type_timeout = Int(default_value=400, + help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types. + Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt + performance by preventing jedi to build its cache. + """).tag(config=True) + + debug = Bool(default_value=False, + help='Enable debug for the Completer. Mostly print extra ' + 'information for experimental jedi integration.')\ + .tag(config=True) + + backslash_combining_completions = Bool(True, + help="Enable unicode completions, e.g. \\alpha . " + "Includes completion of latex commands, unicode names, and expanding " + "unicode characters back to latex commands.").tag(config=True) + + def __init__(self, namespace=None, global_namespace=None, **kwargs): + """Create a new completer for the command line. + + Completer(namespace=ns, global_namespace=ns2) -> completer instance. + + If unspecified, the default namespace where completions are performed + is __main__ (technically, __main__.__dict__). Namespaces should be + given as dictionaries. + + An optional second namespace can be given. This allows the completer + to handle cases where both the local and global scopes need to be + distinguished. + """ + + # Don't bind to namespace quite yet, but flag whether the user wants a + # specific namespace or to use __main__.__dict__. This will allow us + # to bind to __main__.__dict__ at completion time, not now. + if namespace is None: + self.use_main_ns = True + else: + self.use_main_ns = False + self.namespace = namespace + + # The global namespace, if given, can be bound directly + if global_namespace is None: + self.global_namespace = {} + else: + self.global_namespace = global_namespace + + self.custom_matchers = [] + + super(Completer, self).__init__(**kwargs) + + def complete(self, text, state): + """Return the next possible completion for 'text'. + + This is called successively with state == 0, 1, 2, ... until it + returns None. The completion should begin with 'text'. + + """ + if self.use_main_ns: + self.namespace = __main__.__dict__ + + if state == 0: + if "." in text: + self.matches = self.attr_matches(text) + else: + self.matches = self.global_matches(text) + try: + return self.matches[state] + except IndexError: + return None + + def global_matches(self, text): + """Compute matches when text is a simple name. + + Return a list of all keywords, built-in functions and names currently + defined in self.namespace or self.global_namespace that match. + + """ + matches = [] + match_append = matches.append + n = len(text) + for lst in [keyword.kwlist, + builtin_mod.__dict__.keys(), + self.namespace.keys(), + self.global_namespace.keys()]: + for word in lst: + if word[:n] == text and word != "__builtins__": + match_append(word) + + snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") + for lst in [self.namespace.keys(), + self.global_namespace.keys()]: + shortened = {"_".join([sub[0] for sub in word.split('_')]) : word + for word in lst if snake_case_re.match(word)} + for word in shortened.keys(): + if word[:n] == text and word != "__builtins__": + match_append(shortened[word]) + return matches + + def attr_matches(self, text): + """Compute matches when text contains a dot. + + Assuming the text is of the form NAME.NAME....[NAME], and is + evaluatable in self.namespace or self.global_namespace, it will be + evaluated and its attributes (as revealed by dir()) are used as + possible completions. (For class instances, class members are + also considered.) + + WARNING: this can still invoke arbitrary C code, if an object + with a __getattr__ hook is evaluated. + + """ + + # Another option, seems to work great. Catches things like ''. + m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) + + if m: + expr, attr = m.group(1, 3) + elif self.greedy: + m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) + if not m2: + return [] + expr, attr = m2.group(1,2) + else: + return [] + + try: + obj = eval(expr, self.namespace) + except: + try: + obj = eval(expr, self.global_namespace) + except: + return [] + + if self.limit_to__all__ and hasattr(obj, '__all__'): + words = get__all__entries(obj) + else: + words = dir2(obj) + + try: + words = generics.complete_object(obj, words) + except TryNext: + pass + except AssertionError: + raise + except Exception: + # Silence errors from completion function + #raise # dbg + pass + # Build match list to return + n = len(attr) + return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ] + + +def get__all__entries(obj): + """returns the strings in the __all__ attribute""" + try: + words = getattr(obj, '__all__') + except: + return [] + + return [w for w in words if isinstance(w, str)] + + +def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str, + extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]: + """Used by dict_key_matches, matching the prefix to a list of keys + + Parameters + ---------- + keys + list of keys in dictionary currently being completed. + prefix + Part of the text already typed by the user. E.g. `mydict[b'fo` + delims + String of delimiters to consider when finding the current key. + extra_prefix : optional + Part of the text already typed in multi-key index cases. E.g. for + `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. + + Returns + ------- + A tuple of three elements: ``quote``, ``token_start``, ``matched``, with + ``quote`` being the quote that need to be used to close current string. + ``token_start`` the position where the replacement should start occurring, + ``matches`` a list of replacement/completion + + """ + prefix_tuple = extra_prefix if extra_prefix else () + Nprefix = len(prefix_tuple) + def filter_prefix_tuple(key): + # Reject too short keys + if len(key) <= Nprefix: + return False + # Reject keys with non str/bytes in it + for k in key: + if not isinstance(k, (str, bytes)): + return False + # Reject keys that do not match the prefix + for k, pt in zip(key, prefix_tuple): + if k != pt: + return False + # All checks passed! + return True + + filtered_keys:List[Union[str,bytes]] = [] + def _add_to_filtered_keys(key): + if isinstance(key, (str, bytes)): + filtered_keys.append(key) + + for k in keys: + if isinstance(k, tuple): + if filter_prefix_tuple(k): + _add_to_filtered_keys(k[Nprefix]) + else: + _add_to_filtered_keys(k) + + if not prefix: + return '', 0, [repr(k) for k in filtered_keys] + quote_match = re.search('["\']', prefix) + assert quote_match is not None # silence mypy + quote = quote_match.group() + try: + prefix_str = eval(prefix + quote, {}) + except Exception: + return '', 0, [] + + pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' + token_match = re.search(pattern, prefix, re.UNICODE) + assert token_match is not None # silence mypy + token_start = token_match.start() + token_prefix = token_match.group() + + matched:List[str] = [] + for key in filtered_keys: + try: + if not key.startswith(prefix_str): + continue + except (AttributeError, TypeError, UnicodeError): + # Python 3+ TypeError on b'a'.startswith('a') or vice-versa + continue + + # reformat remainder of key to begin with prefix + rem = key[len(prefix_str):] + # force repr wrapped in ' + rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') + rem_repr = rem_repr[1 + rem_repr.index("'"):-2] + if quote == '"': + # The entered prefix is quoted with ", + # but the match is quoted with '. + # A contained " hence needs escaping for comparison: + rem_repr = rem_repr.replace('"', '\\"') + + # then reinsert prefix from start of token + matched.append('%s%s' % (token_prefix, rem_repr)) + return quote, token_start, matched + + +def cursor_to_position(text:str, line:int, column:int)->int: + """ + Convert the (line,column) position of the cursor in text to an offset in a + string. + + Parameters + ---------- + text : str + The text in which to calculate the cursor offset + line : int + Line of the cursor; 0-indexed + column : int + Column of the cursor 0-indexed + + Returns + ------- + Position of the cursor in ``text``, 0-indexed. + + See Also + -------- + position_to_cursor : reciprocal of this function + + """ + lines = text.split('\n') + assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines))) + + return sum(len(l) + 1 for l in lines[:line]) + column + +def position_to_cursor(text:str, offset:int)->Tuple[int, int]: + """ + Convert the position of the cursor in text (0 indexed) to a line + number(0-indexed) and a column number (0-indexed) pair + + Position should be a valid position in ``text``. + + Parameters + ---------- + text : str + The text in which to calculate the cursor offset + offset : int + Position of the cursor in ``text``, 0-indexed. + + Returns + ------- + (line, column) : (int, int) + Line of the cursor; 0-indexed, column of the cursor 0-indexed + + See Also + -------- + cursor_to_position : reciprocal of this function + + """ + + assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) + + before = text[:offset] + blines = before.split('\n') # ! splitnes trim trailing \n + line = before.count('\n') + col = len(blines[-1]) + return line, col + + +def _safe_isinstance(obj, module, class_name): + """Checks if obj is an instance of module.class_name if loaded + """ + return (module in sys.modules and + isinstance(obj, getattr(import_module(module), class_name))) + +def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: + """Match Unicode characters back to Unicode name + + This does ``☃`` -> ``\\snowman`` + + Note that snowman is not a valid python3 combining character but will be expanded. + Though it will not recombine back to the snowman character by the completion machinery. + + This will not either back-complete standard sequences like \\n, \\b ... + + Returns + ======= + + Return a tuple with two elements: + + - The Unicode character that was matched (preceded with a backslash), or + empty string, + - a sequence (of 1), name for the match Unicode character, preceded by + backslash, or empty if no match. + + """ + if len(text)<2: + return '', () + maybe_slash = text[-2] + if maybe_slash != '\\': + return '', () + + char = text[-1] + # no expand on quote for completion in strings. + # nor backcomplete standard ascii keys + if char in string.ascii_letters or char in ('"',"'"): + return '', () + try : + unic = unicodedata.name(char) + return '\\'+char,('\\'+unic,) + except KeyError: + pass + return '', () + +def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] : + """Match latex characters back to unicode name + + This does ``\\ℵ`` -> ``\\aleph`` + + """ + if len(text)<2: + return '', () + maybe_slash = text[-2] + if maybe_slash != '\\': + return '', () + + + char = text[-1] + # no expand on quote for completion in strings. + # nor backcomplete standard ascii keys + if char in string.ascii_letters or char in ('"',"'"): + return '', () + try : + latex = reverse_latex_symbol[char] + # '\\' replace the \ as well + return '\\'+char,[latex] + except KeyError: + pass + return '', () + + +def _formatparamchildren(parameter) -> str: + """ + Get parameter name and value from Jedi Private API + + Jedi does not expose a simple way to get `param=value` from its API. + + Parameters + ---------- + parameter + Jedi's function `Param` + + Returns + ------- + A string like 'a', 'b=1', '*args', '**kwargs' + + """ + description = parameter.description + if not description.startswith('param '): + raise ValueError('Jedi function parameter description have change format.' + 'Expected "param ...", found %r".' % description) + return description[6:] + +def _make_signature(completion)-> str: + """ + Make the signature from a jedi completion + + Parameters + ---------- + completion : jedi.Completion + object does not complete a function type + + Returns + ------- + a string consisting of the function signature, with the parenthesis but + without the function name. example: + `(a, *args, b=1, **kwargs)` + + """ + + # it looks like this might work on jedi 0.17 + if hasattr(completion, 'get_signatures'): + signatures = completion.get_signatures() + if not signatures: + return '(?)' + + c0 = completion.get_signatures()[0] + return '('+c0.to_string().split('(', maxsplit=1)[1] + + return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() + for p in signature.defined_names()) if f]) + + +class _CompleteResult(NamedTuple): + matched_text : str + matches: Sequence[str] + matches_origin: Sequence[str] + jedi_matches: Any + + +class IPCompleter(Completer): + """Extension of the completer class with IPython-specific features""" + + __dict_key_regexps: Optional[Dict[bool,Pattern]] = None + + @observe('greedy') + def _greedy_changed(self, change): + """update the splitter and readline delims when greedy is changed""" + if change['new']: + self.splitter.delims = GREEDY_DELIMS + else: + self.splitter.delims = DELIMS + + dict_keys_only = Bool(False, + help="""Whether to show dict key matches only""") + + merge_completions = Bool(True, + help="""Whether to merge completion results into a single list + + If False, only the completion results from the first non-empty + completer will be returned. + """ + ).tag(config=True) + omit__names = Enum((0,1,2), default_value=2, + help="""Instruct the completer to omit private method names + + Specifically, when completing on ``object.``. + + When 2 [default]: all names that start with '_' will be excluded. + + When 1: all 'magic' names (``__foo__``) will be excluded. + + When 0: nothing will be excluded. + """ + ).tag(config=True) + limit_to__all__ = Bool(False, + help=""" + DEPRECATED as of version 5.0. + + Instruct the completer to use __all__ for the completion + + Specifically, when completing on ``object.``. + + When True: only those names in obj.__all__ will be included. + + When False [default]: the __all__ attribute is ignored + """, + ).tag(config=True) + + profile_completions = Bool( + default_value=False, + help="If True, emit profiling data for completion subsystem using cProfile." + ).tag(config=True) + + profiler_output_dir = Unicode( + default_value=".completion_profiles", + help="Template for path at which to output profile data for completions." + ).tag(config=True) + + @observe('limit_to__all__') + def _limit_to_all_changed(self, change): + warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration ' + 'value has been deprecated since IPython 5.0, will be made to have ' + 'no effects and then removed in future version of IPython.', + UserWarning) + + def __init__( + self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs + ): + """IPCompleter() -> completer + + Return a completer object. + + Parameters + ---------- + shell + a pointer to the ipython shell itself. This is needed + because this completer knows about magic functions, and those can + only be accessed via the ipython instance. + namespace : dict, optional + an optional dict where completions are performed. + global_namespace : dict, optional + secondary optional dict for completions, to + handle cases (such as IPython embedded inside functions) where + both Python scopes are visible. + config : Config + traitlet's config object + **kwargs + passed to super class unmodified. + """ + + self.magic_escape = ESC_MAGIC + self.splitter = CompletionSplitter() + + # _greedy_changed() depends on splitter and readline being defined: + super().__init__( + namespace=namespace, + global_namespace=global_namespace, + config=config, + **kwargs + ) + + # List where completion matches will be stored + self.matches = [] + self.shell = shell + # Regexp to split filenames with spaces in them + self.space_name_re = re.compile(r'([^\\] )') + # Hold a local ref. to glob.glob for speed + self.glob = glob.glob + + # Determine if we are running on 'dumb' terminals, like (X)Emacs + # buffers, to avoid completion problems. + term = os.environ.get('TERM','xterm') + self.dumb_terminal = term in ['dumb','emacs'] + + # Special handling of backslashes needed in win32 platforms + if sys.platform == "win32": + self.clean_glob = self._clean_glob_win32 + else: + self.clean_glob = self._clean_glob + + #regexp to parse docstring for function signature + self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') + self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') + #use this if positional argument name is also needed + #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') + + self.magic_arg_matchers = [ + self.magic_config_matches, + self.magic_color_matches, + ] + + # This is set externally by InteractiveShell + self.custom_completers = None + + # This is a list of names of unicode characters that can be completed + # into their corresponding unicode value. The list is large, so we + # lazily initialize it on first use. Consuming code should access this + # attribute through the `@unicode_names` property. + self._unicode_names = None + + @property + def matchers(self) -> List[Any]: + """All active matcher routines for completion""" + if self.dict_keys_only: + return [self.dict_key_matches] + + if self.use_jedi: + return [ + *self.custom_matchers, + self.dict_key_matches, + self.file_matches, + self.magic_matches, + ] + else: + return [ + *self.custom_matchers, + self.dict_key_matches, + self.python_matches, + self.file_matches, + self.magic_matches, + self.python_func_kw_matches, + ] + + def all_completions(self, text:str) -> List[str]: + """ + Wrapper around the completion methods for the benefit of emacs. + """ + prefix = text.rpartition('.')[0] + with provisionalcompleter(): + return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text + for c in self.completions(text, len(text))] + + return self.complete(text)[1] + + def _clean_glob(self, text:str): + return self.glob("%s*" % text) + + def _clean_glob_win32(self, text:str): + return [f.replace("\\","/") + for f in self.glob("%s*" % text)] + + def file_matches(self, text:str)->List[str]: + """Match filenames, expanding ~USER type strings. + + Most of the seemingly convoluted logic in this completer is an + attempt to handle filenames with spaces in them. And yet it's not + quite perfect, because Python's readline doesn't expose all of the + GNU readline details needed for this to be done correctly. + + For a filename with a space in it, the printed completions will be + only the parts after what's already been typed (instead of the + full completions, as is normally done). I don't think with the + current (as of Python 2.3) Python readline it's possible to do + better.""" + + # chars that require escaping with backslash - i.e. chars + # that readline treats incorrectly as delimiters, but we + # don't want to treat as delimiters in filename matching + # when escaped with backslash + if text.startswith('!'): + text = text[1:] + text_prefix = u'!' + else: + text_prefix = u'' + + text_until_cursor = self.text_until_cursor + # track strings with open quotes + open_quotes = has_open_quotes(text_until_cursor) + + if '(' in text_until_cursor or '[' in text_until_cursor: + lsplit = text + else: + try: + # arg_split ~ shlex.split, but with unicode bugs fixed by us + lsplit = arg_split(text_until_cursor)[-1] + except ValueError: + # typically an unmatched ", or backslash without escaped char. + if open_quotes: + lsplit = text_until_cursor.split(open_quotes)[-1] + else: + return [] + except IndexError: + # tab pressed on empty line + lsplit = "" + + if not open_quotes and lsplit != protect_filename(lsplit): + # if protectables are found, do matching on the whole escaped name + has_protectables = True + text0,text = text,lsplit + else: + has_protectables = False + text = os.path.expanduser(text) + + if text == "": + return [text_prefix + protect_filename(f) for f in self.glob("*")] + + # Compute the matches from the filesystem + if sys.platform == 'win32': + m0 = self.clean_glob(text) + else: + m0 = self.clean_glob(text.replace('\\', '')) + + if has_protectables: + # If we had protectables, we need to revert our changes to the + # beginning of filename so that we don't double-write the part + # of the filename we have so far + len_lsplit = len(lsplit) + matches = [text_prefix + text0 + + protect_filename(f[len_lsplit:]) for f in m0] + else: + if open_quotes: + # if we have a string with an open quote, we don't need to + # protect the names beyond the quote (and we _shouldn't_, as + # it would cause bugs when the filesystem call is made). + matches = m0 if sys.platform == "win32" else\ + [protect_filename(f, open_quotes) for f in m0] + else: + matches = [text_prefix + + protect_filename(f) for f in m0] + + # Mark directories in input list by appending '/' to their names. + return [x+'/' if os.path.isdir(x) else x for x in matches] + + def magic_matches(self, text:str): + """Match magics""" + # Get all shell magics now rather than statically, so magics loaded at + # runtime show up too. + lsm = self.shell.magics_manager.lsmagic() + line_magics = lsm['line'] + cell_magics = lsm['cell'] + pre = self.magic_escape + pre2 = pre+pre + + explicit_magic = text.startswith(pre) + + # Completion logic: + # - user gives %%: only do cell magics + # - user gives %: do both line and cell magics + # - no prefix: do both + # In other words, line magics are skipped if the user gives %% explicitly + # + # We also exclude magics that match any currently visible names: + # https://github.com/ipython/ipython/issues/4877, unless the user has + # typed a %: + # https://github.com/ipython/ipython/issues/10754 + bare_text = text.lstrip(pre) + global_matches = self.global_matches(bare_text) + if not explicit_magic: + def matches(magic): + """ + Filter magics, in particular remove magics that match + a name present in global namespace. + """ + return ( magic.startswith(bare_text) and + magic not in global_matches ) + else: + def matches(magic): + return magic.startswith(bare_text) + + comp = [ pre2+m for m in cell_magics if matches(m)] + if not text.startswith(pre2): + comp += [ pre+m for m in line_magics if matches(m)] + + return comp + + def magic_config_matches(self, text:str) -> List[str]: + """ Match class names and attributes for %config magic """ + texts = text.strip().split() + + if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): + # get all configuration classes + classes = sorted(set([ c for c in self.shell.configurables + if c.__class__.class_traits(config=True) + ]), key=lambda x: x.__class__.__name__) + classnames = [ c.__class__.__name__ for c in classes ] + + # return all classnames if config or %config is given + if len(texts) == 1: + return classnames + + # match classname + classname_texts = texts[1].split('.') + classname = classname_texts[0] + classname_matches = [ c for c in classnames + if c.startswith(classname) ] + + # return matched classes or the matched class with attributes + if texts[1].find('.') < 0: + return classname_matches + elif len(classname_matches) == 1 and \ + classname_matches[0] == classname: + cls = classes[classnames.index(classname)].__class__ + help = cls.class_get_help() + # strip leading '--' from cl-args: + help = re.sub(re.compile(r'^--', re.MULTILINE), '', help) + return [ attr.split('=')[0] + for attr in help.strip().splitlines() + if attr.startswith(texts[1]) ] + return [] + + def magic_color_matches(self, text:str) -> List[str] : + """ Match color schemes for %colors magic""" + texts = text.split() + if text.endswith(' '): + # .split() strips off the trailing whitespace. Add '' back + # so that: '%colors ' -> ['%colors', ''] + texts.append('') + + if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'): + prefix = texts[1] + return [ color for color in InspectColors.keys() + if color.startswith(prefix) ] + return [] + + def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]: + """ + Return a list of :any:`jedi.api.Completions` object from a ``text`` and + cursor position. + + Parameters + ---------- + cursor_column : int + column position of the cursor in ``text``, 0-indexed. + cursor_line : int + line position of the cursor in ``text``, 0-indexed + text : str + text to complete + + Notes + ----- + If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` + object containing a string with the Jedi debug information attached. + """ + namespaces = [self.namespace] + if self.global_namespace is not None: + namespaces.append(self.global_namespace) + + completion_filter = lambda x:x + offset = cursor_to_position(text, cursor_line, cursor_column) + # filter output if we are completing for object members + if offset: + pre = text[offset-1] + if pre == '.': + if self.omit__names == 2: + completion_filter = lambda c:not c.name.startswith('_') + elif self.omit__names == 1: + completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__')) + elif self.omit__names == 0: + completion_filter = lambda x:x + else: + raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names)) + + interpreter = jedi.Interpreter(text[:offset], namespaces) + try_jedi = True + + try: + # find the first token in the current tree -- if it is a ' or " then we are in a string + completing_string = False + try: + first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value')) + except StopIteration: + pass + else: + # note the value may be ', ", or it may also be ''' or """, or + # in some cases, """what/you/typed..., but all of these are + # strings. + completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'} + + # if we are in a string jedi is likely not the right candidate for + # now. Skip it. + try_jedi = not completing_string + except Exception as e: + # many of things can go wrong, we are using private API just don't crash. + if self.debug: + print("Error detecting if completing a non-finished string :", e, '|') + + if not try_jedi: + return [] + try: + return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1)) + except Exception as e: + if self.debug: + return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))] + else: + return [] + + def python_matches(self, text:str)->List[str]: + """Match attributes or global python names""" + if "." in text: + try: + matches = self.attr_matches(text) + if text.endswith('.') and self.omit__names: + if self.omit__names == 1: + # true if txt is _not_ a __ name, false otherwise: + no__name = (lambda txt: + re.match(r'.*\.__.*?__',txt) is None) + else: + # true if txt is _not_ a _ name, false otherwise: + no__name = (lambda txt: + re.match(r'\._.*?',txt[txt.rindex('.'):]) is None) + matches = filter(no__name, matches) + except NameError: + # catches . + matches = [] + else: + matches = self.global_matches(text) + return matches + + def _default_arguments_from_docstring(self, doc): + """Parse the first line of docstring for call signature. + + Docstring should be of the form 'min(iterable[, key=func])\n'. + It can also parse cython docstring of the form + 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'. + """ + if doc is None: + return [] + + #care only the firstline + line = doc.lstrip().splitlines()[0] + + #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') + #'min(iterable[, key=func])\n' -> 'iterable[, key=func]' + sig = self.docstring_sig_re.search(line) + if sig is None: + return [] + # iterable[, key=func]' -> ['iterable[' ,' key=func]'] + sig = sig.groups()[0].split(',') + ret = [] + for s in sig: + #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') + ret += self.docstring_kwd_re.findall(s) + return ret + + def _default_arguments(self, obj): + """Return the list of default arguments of obj if it is callable, + or empty list otherwise.""" + call_obj = obj + ret = [] + if inspect.isbuiltin(obj): + pass + elif not (inspect.isfunction(obj) or inspect.ismethod(obj)): + if inspect.isclass(obj): + #for cython embedsignature=True the constructor docstring + #belongs to the object itself not __init__ + ret += self._default_arguments_from_docstring( + getattr(obj, '__doc__', '')) + # for classes, check for __init__,__new__ + call_obj = (getattr(obj, '__init__', None) or + getattr(obj, '__new__', None)) + # for all others, check if they are __call__able + elif hasattr(obj, '__call__'): + call_obj = obj.__call__ + ret += self._default_arguments_from_docstring( + getattr(call_obj, '__doc__', '')) + + _keeps = (inspect.Parameter.KEYWORD_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD) + + try: + sig = inspect.signature(obj) + ret.extend(k for k, v in sig.parameters.items() if + v.kind in _keeps) + except ValueError: + pass + + return list(set(ret)) + + def python_func_kw_matches(self, text): + """Match named parameters (kwargs) of the last open function""" + + if "." in text: # a parameter cannot be dotted + return [] + try: regexp = self.__funcParamsRegex + except AttributeError: + regexp = self.__funcParamsRegex = re.compile(r''' + '.*?(?,a=1)", the candidate is "foo" + tokens = regexp.findall(self.text_until_cursor) + iterTokens = reversed(tokens); openPar = 0 + + for token in iterTokens: + if token == ')': + openPar -= 1 + elif token == '(': + openPar += 1 + if openPar > 0: + # found the last unclosed parenthesis + break + else: + return [] + # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" ) + ids = [] + isId = re.compile(r'\w+$').match + + while True: + try: + ids.append(next(iterTokens)) + if not isId(ids[-1]): + ids.pop(); break + if not next(iterTokens) == '.': + break + except StopIteration: + break + + # Find all named arguments already assigned to, as to avoid suggesting + # them again + usedNamedArgs = set() + par_level = -1 + for token, next_token in zip(tokens, tokens[1:]): + if token == '(': + par_level += 1 + elif token == ')': + par_level -= 1 + + if par_level != 0: + continue + + if next_token != '=': + continue + + usedNamedArgs.add(token) + + argMatches = [] + try: + callableObj = '.'.join(ids[::-1]) + namedArgs = self._default_arguments(eval(callableObj, + self.namespace)) + + # Remove used named arguments from the list, no need to show twice + for namedArg in set(namedArgs) - usedNamedArgs: + if namedArg.startswith(text): + argMatches.append("%s=" %namedArg) + except: + pass + + return argMatches + + @staticmethod + def _get_keys(obj: Any) -> List[Any]: + # Objects can define their own completions by defining an + # _ipy_key_completions_() method. + method = get_real_method(obj, '_ipython_key_completions_') + if method is not None: + return method() + + # Special case some common in-memory dict-like types + if isinstance(obj, dict) or\ + _safe_isinstance(obj, 'pandas', 'DataFrame'): + try: + return list(obj.keys()) + except Exception: + return [] + elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ + _safe_isinstance(obj, 'numpy', 'void'): + return obj.dtype.names or [] + return [] + + def dict_key_matches(self, text:str) -> List[str]: + "Match string keys in a dictionary, after e.g. 'foo[' " + + + if self.__dict_key_regexps is not None: + regexps = self.__dict_key_regexps + else: + dict_key_re_fmt = r'''(?x) + ( # match dict-referring expression wrt greedy setting + %s + ) + \[ # open bracket + \s* # and optional whitespace + # Capture any number of str-like objects (e.g. "a", "b", 'c') + ((?:[uUbB]? # string prefix (r not handled) + (?: + '(?:[^']|(? key_start: + leading = '' + else: + leading = text[text_start:completion_start] + + # the index of the `[` character + bracket_idx = match.end(1) + + # append closing quote and bracket as appropriate + # this is *not* appropriate if the opening quote or bracket is outside + # the text given to this method + suf = '' + continuation = self.line_buffer[len(self.text_until_cursor):] + if key_start > text_start and closing_quote: + # quotes were opened inside text, maybe close them + if continuation.startswith(closing_quote): + continuation = continuation[len(closing_quote):] + else: + suf += closing_quote + if bracket_idx > text_start: + # brackets were opened inside text, maybe close them + if not continuation.startswith(']'): + suf += ']' + + return [leading + k + suf for k in matches] + + @staticmethod + def unicode_name_matches(text:str) -> Tuple[str, List[str]] : + """Match Latex-like syntax for unicode characters base + on the name of the character. + + This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` + + Works only on valid python 3 identifier, or on combining characters that + will combine to form a valid identifier. + """ + slashpos = text.rfind('\\') + if slashpos > -1: + s = text[slashpos+1:] + try : + unic = unicodedata.lookup(s) + # allow combining chars + if ('a'+unic).isidentifier(): + return '\\'+s,[unic] + except KeyError: + pass + return '', [] + + + def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: + """Match Latex syntax for unicode characters. + + This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` + """ + slashpos = text.rfind('\\') + if slashpos > -1: + s = text[slashpos:] + if s in latex_symbols: + # Try to complete a full latex symbol to unicode + # \\alpha -> α + return s, [latex_symbols[s]] + else: + # If a user has partially typed a latex symbol, give them + # a full list of options \al -> [\aleph, \alpha] + matches = [k for k in latex_symbols if k.startswith(s)] + if matches: + return s, matches + return '', () + + def dispatch_custom_completer(self, text): + if not self.custom_completers: + return + + line = self.line_buffer + if not line.strip(): + return None + + # Create a little structure to pass all the relevant information about + # the current completion to any custom completer. + event = SimpleNamespace() + event.line = line + event.symbol = text + cmd = line.split(None,1)[0] + event.command = cmd + event.text_until_cursor = self.text_until_cursor + + # for foo etc, try also to find completer for %foo + if not cmd.startswith(self.magic_escape): + try_magic = self.custom_completers.s_matches( + self.magic_escape + cmd) + else: + try_magic = [] + + for c in itertools.chain(self.custom_completers.s_matches(cmd), + try_magic, + self.custom_completers.flat_matches(self.text_until_cursor)): + try: + res = c(event) + if res: + # first, try case sensitive match + withcase = [r for r in res if r.startswith(text)] + if withcase: + return withcase + # if none, then case insensitive ones are ok too + text_low = text.lower() + return [r for r in res if r.lower().startswith(text_low)] + except TryNext: + pass + except KeyboardInterrupt: + """ + If custom completer take too long, + let keyboard interrupt abort and return nothing. + """ + break + + return None + + def completions(self, text: str, offset: int)->Iterator[Completion]: + """ + Returns an iterator over the possible completions + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + It will also raise unless use in proper context manager. + + Parameters + ---------- + text : str + Full text of the current input, multi line string. + offset : int + Integer representing the position of the cursor in ``text``. Offset + is 0-based indexed. + + Yields + ------ + Completion + + Notes + ----- + The cursor on a text can either be seen as being "in between" + characters or "On" a character depending on the interface visible to + the user. For consistency the cursor being on "in between" characters X + and Y is equivalent to the cursor being "on" character Y, that is to say + the character the cursor is on is considered as being after the cursor. + + Combining characters may span more that one position in the + text. + + .. note:: + + If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--`` + fake Completion token to distinguish completion returned by Jedi + and usual IPython completion. + + .. note:: + + Completions are not completely deduplicated yet. If identical + completions are coming from different sources this function does not + ensure that each completion object will only be present once. + """ + warnings.warn("_complete is a provisional API (as of IPython 6.0). " + "It may change without warnings. " + "Use in corresponding context manager.", + category=ProvisionalCompleterWarning, stacklevel=2) + + seen = set() + profiler:Optional[cProfile.Profile] + try: + if self.profile_completions: + import cProfile + profiler = cProfile.Profile() + profiler.enable() + else: + profiler = None + + for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): + if c and (c in seen): + continue + yield c + seen.add(c) + except KeyboardInterrupt: + """if completions take too long and users send keyboard interrupt, + do not crash and return ASAP. """ + pass + finally: + if profiler is not None: + profiler.disable() + ensure_dir_exists(self.profiler_output_dir) + output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4())) + print("Writing profiler output to", output_path) + profiler.dump_stats(output_path) + + def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: + """ + Core completion module.Same signature as :any:`completions`, with the + extra `timeout` parameter (in seconds). + + Computing jedi's completion ``.type`` can be quite expensive (it is a + lazy property) and can require some warm-up, more warm up than just + computing the ``name`` of a completion. The warm-up can be : + + - Long warm-up the first time a module is encountered after + install/update: actually build parse/inference tree. + + - first time the module is encountered in a session: load tree from + disk. + + We don't want to block completions for tens of seconds so we give the + completer a "budget" of ``_timeout`` seconds per invocation to compute + completions types, the completions that have not yet been computed will + be marked as "unknown" an will have a chance to be computed next round + are things get cached. + + Keep in mind that Jedi is not the only thing treating the completion so + keep the timeout short-ish as if we take more than 0.3 second we still + have lots of processing to do. + + """ + deadline = time.monotonic() + _timeout + + + before = full_text[:offset] + cursor_line, cursor_column = position_to_cursor(full_text, offset) + + matched_text, matches, matches_origin, jedi_matches = self._complete( + full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column) + + iter_jm = iter(jedi_matches) + if _timeout: + for jm in iter_jm: + try: + type_ = jm.type + except Exception: + if self.debug: + print("Error in Jedi getting type of ", jm) + type_ = None + delta = len(jm.name_with_symbols) - len(jm.complete) + if type_ == 'function': + signature = _make_signature(jm) + else: + signature = '' + yield Completion(start=offset - delta, + end=offset, + text=jm.name_with_symbols, + type=type_, + signature=signature, + _origin='jedi') + + if time.monotonic() > deadline: + break + + for jm in iter_jm: + delta = len(jm.name_with_symbols) - len(jm.complete) + yield Completion(start=offset - delta, + end=offset, + text=jm.name_with_symbols, + type='', # don't compute type for speed + _origin='jedi', + signature='') + + + start_offset = before.rfind(matched_text) + + # TODO: + # Suppress this, right now just for debug. + if jedi_matches and matches and self.debug: + yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', + _origin='debug', type='none', signature='') + + # I'm unsure if this is always true, so let's assert and see if it + # crash + assert before.endswith(matched_text) + for m, t in zip(matches, matches_origin): + yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='') + + + def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: + """Find completions for the given text and line context. + + Note that both the text and the line_buffer are optional, but at least + one of them must be given. + + Parameters + ---------- + text : string, optional + Text to perform the completion on. If not given, the line buffer + is split using the instance's CompletionSplitter object. + line_buffer : string, optional + If not given, the completer attempts to obtain the current line + buffer via readline. This keyword allows clients which are + requesting for text completions in non-readline contexts to inform + the completer of the entire text. + cursor_pos : int, optional + Index of the cursor in the full line buffer. Should be provided by + remote frontends where kernel has no access to frontend state. + + Returns + ------- + Tuple of two items: + text : str + Text that was actually used in the completion. + matches : list + A list of completion matches. + + Notes + ----- + This API is likely to be deprecated and replaced by + :any:`IPCompleter.completions` in the future. + + """ + warnings.warn('`Completer.complete` is pending deprecation since ' + 'IPython 6.0 and will be replaced by `Completer.completions`.', + PendingDeprecationWarning) + # potential todo, FOLD the 3rd throw away argument of _complete + # into the first 2 one. + return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2] + + def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, + full_text=None) -> _CompleteResult: + """ + Like complete but can also returns raw jedi completions as well as the + origin of the completion text. This could (and should) be made much + cleaner but that will be simpler once we drop the old (and stateful) + :any:`complete` API. + + With current provisional API, cursor_pos act both (depending on the + caller) as the offset in the ``text`` or ``line_buffer``, or as the + ``column`` when passing multiline strings this could/should be renamed + but would add extra noise. + + Parameters + ---------- + cursor_line + Index of the line the cursor is on. 0 indexed. + cursor_pos + Position of the cursor in the current line/line_buffer/text. 0 + indexed. + line_buffer : optional, str + The current line the cursor is in, this is mostly due to legacy + reason that readline could only give a us the single current line. + Prefer `full_text`. + text : str + The current "token" the cursor is in, mostly also for historical + reasons. as the completer would trigger only after the current line + was parsed. + full_text : str + Full text of the current cell. + + Returns + ------- + A tuple of N elements which are (likely): + matched_text: ? the text that the complete matched + matches: list of completions ? + matches_origin: ? list same length as matches, and where each completion came from + jedi_matches: list of Jedi matches, have it's own structure. + """ + + + # if the cursor position isn't given, the only sane assumption we can + # make is that it's at the end of the line (the common case) + if cursor_pos is None: + cursor_pos = len(line_buffer) if text is None else len(text) + + if self.use_main_ns: + self.namespace = __main__.__dict__ + + # if text is either None or an empty string, rely on the line buffer + if (not line_buffer) and full_text: + line_buffer = full_text.split('\n')[cursor_line] + if not text: # issue #11508: check line_buffer before calling split_line + text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else '' + + if self.backslash_combining_completions: + # allow deactivation of these on windows. + base_text = text if not line_buffer else line_buffer[:cursor_pos] + + for meth in (self.latex_matches, + self.unicode_name_matches, + back_latex_name_matches, + back_unicode_name_matches, + self.fwd_unicode_match): + name_text, name_matches = meth(base_text) + if name_text: + return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \ + [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ()) + + + # If no line buffer is given, assume the input text is all there was + if line_buffer is None: + line_buffer = text + + self.line_buffer = line_buffer + self.text_until_cursor = self.line_buffer[:cursor_pos] + + # Do magic arg matches + for matcher in self.magic_arg_matchers: + matches = list(matcher(line_buffer))[:MATCHES_LIMIT] + if matches: + origins = [matcher.__qualname__] * len(matches) + return _CompleteResult(text, matches, origins, ()) + + # Start with a clean slate of completions + matches = [] + + # FIXME: we should extend our api to return a dict with completions for + # different types of objects. The rlcomplete() method could then + # simply collapse the dict into a list for readline, but we'd have + # richer completion semantics in other environments. + is_magic_prefix = len(text) > 0 and text[0] == "%" + completions: Iterable[Any] = [] + if self.use_jedi and not is_magic_prefix: + if not full_text: + full_text = line_buffer + completions = self._jedi_matches( + cursor_pos, cursor_line, full_text) + + if self.merge_completions: + matches = [] + for matcher in self.matchers: + try: + matches.extend([(m, matcher.__qualname__) + for m in matcher(text)]) + except: + # Show the ugly traceback if the matcher causes an + # exception, but do NOT crash the kernel! + sys.excepthook(*sys.exc_info()) + else: + for matcher in self.matchers: + matches = [(m, matcher.__qualname__) + for m in matcher(text)] + if matches: + break + + seen = set() + filtered_matches = set() + for m in matches: + t, c = m + if t not in seen: + filtered_matches.add(m) + seen.add(t) + + _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0])) + + custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []] + + _filtered_matches = custom_res or _filtered_matches + + _filtered_matches = _filtered_matches[:MATCHES_LIMIT] + _matches = [m[0] for m in _filtered_matches] + origins = [m[1] for m in _filtered_matches] + + self.matches = _matches + + return _CompleteResult(text, _matches, origins, completions) + + def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: + """ + Forward match a string starting with a backslash with a list of + potential Unicode completions. + + Will compute list list of Unicode character names on first call and cache it. + + Returns + ------- + At tuple with: + - matched text (empty if no matches) + - list of potential completions, empty tuple otherwise) + """ + # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. + # We could do a faster match using a Trie. + + # Using pygtrie the following seem to work: + + # s = PrefixSet() + + # for c in range(0,0x10FFFF + 1): + # try: + # s.add(unicodedata.name(chr(c))) + # except ValueError: + # pass + # [''.join(k) for k in s.iter(prefix)] + + # But need to be timed and adds an extra dependency. + + slashpos = text.rfind('\\') + # if text starts with slash + if slashpos > -1: + # PERF: It's important that we don't access self._unicode_names + # until we're inside this if-block. _unicode_names is lazily + # initialized, and it takes a user-noticeable amount of time to + # initialize it, so we don't want to initialize it unless we're + # actually going to use it. + s = text[slashpos + 1 :] + sup = s.upper() + candidates = [x for x in self.unicode_names if x.startswith(sup)] + if candidates: + return s, candidates + candidates = [x for x in self.unicode_names if sup in x] + if candidates: + return s, candidates + splitsup = sup.split(" ") + candidates = [ + x for x in self.unicode_names if all(u in x for u in splitsup) + ] + if candidates: + return s, candidates + + return "", () + + # if text does not start with slash + else: + return '', () + + @property + def unicode_names(self) -> List[str]: + """List of names of unicode code points that can be completed. + + The list is lazily initialized on first access. + """ + if self._unicode_names is None: + names = [] + for c in range(0,0x10FFFF + 1): + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) + + return self._unicode_names + +def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]: + names = [] + for start,stop in ranges: + for c in range(start, stop) : + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + return names diff --git a/.venv/lib/python3.8/site-packages/IPython/core/completerlib.py b/.venv/lib/python3.8/site-packages/IPython/core/completerlib.py new file mode 100644 index 0000000..0ca97e7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/completerlib.py @@ -0,0 +1,370 @@ +# encoding: utf-8 +"""Implementations for various useful completers. + +These are all loaded by default by IPython. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib imports +import glob +import inspect +import os +import re +import sys +from importlib import import_module +from importlib.machinery import all_suffixes + + +# Third-party imports +from time import time +from zipimport import zipimporter + +# Our own imports +from .completer import expand_user, compress_user +from .error import TryNext +from ..utils._process_common import arg_split + +# FIXME: this should be pulled in with the right call via the component system +from IPython import get_ipython + +from typing import List + +#----------------------------------------------------------------------------- +# Globals and constants +#----------------------------------------------------------------------------- +_suffixes = all_suffixes() + +# Time in seconds after which the rootmodules will be stored permanently in the +# ipython ip.db database (kept in the user's .ipython dir). +TIMEOUT_STORAGE = 2 + +# Time in seconds after which we give up +TIMEOUT_GIVEUP = 20 + +# Regular expression for the python import statement +import_re = re.compile(r'(?P[^\W\d]\w*?)' + r'(?P[/\\]__init__)?' + r'(?P%s)$' % + r'|'.join(re.escape(s) for s in _suffixes)) + +# RE for the ipython %run command (python + ipython scripts) +magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$') + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + +def module_list(path): + """ + Return the list containing the names of the modules available in the given + folder. + """ + # sys.path has the cwd as an empty string, but isdir/listdir need it as '.' + if path == '': + path = '.' + + # A few local constants to be used in loops below + pjoin = os.path.join + + if os.path.isdir(path): + # Build a list of all files in the directory and all files + # in its subdirectories. For performance reasons, do not + # recurse more than one level into subdirectories. + files = [] + for root, dirs, nondirs in os.walk(path, followlinks=True): + subdir = root[len(path)+1:] + if subdir: + files.extend(pjoin(subdir, f) for f in nondirs) + dirs[:] = [] # Do not recurse into additional subdirectories. + else: + files.extend(nondirs) + + else: + try: + files = list(zipimporter(path)._files.keys()) + except: + files = [] + + # Build a list of modules which match the import_re regex. + modules = [] + for f in files: + m = import_re.match(f) + if m: + modules.append(m.group('name')) + return list(set(modules)) + + +def get_root_modules(): + """ + Returns a list containing the names of all the modules available in the + folders of the pythonpath. + + ip.db['rootmodules_cache'] maps sys.path entries to list of modules. + """ + ip = get_ipython() + if ip is None: + # No global shell instance to store cached list of modules. + # Don't try to scan for modules every time. + return list(sys.builtin_module_names) + + rootmodules_cache = ip.db.get('rootmodules_cache', {}) + rootmodules = list(sys.builtin_module_names) + start_time = time() + store = False + for path in sys.path: + try: + modules = rootmodules_cache[path] + except KeyError: + modules = module_list(path) + try: + modules.remove('__init__') + except ValueError: + pass + if path not in ('', '.'): # cwd modules should not be cached + rootmodules_cache[path] = modules + if time() - start_time > TIMEOUT_STORAGE and not store: + store = True + print("\nCaching the list of root modules, please wait!") + print("(This will only be done once - type '%rehashx' to " + "reset cache!)\n") + sys.stdout.flush() + if time() - start_time > TIMEOUT_GIVEUP: + print("This is taking too long, we give up.\n") + return [] + rootmodules.extend(modules) + if store: + ip.db['rootmodules_cache'] = rootmodules_cache + rootmodules = list(set(rootmodules)) + return rootmodules + + +def is_importable(module, attr, only_modules): + if only_modules: + return inspect.ismodule(getattr(module, attr)) + else: + return not(attr[:2] == '__' and attr[-2:] == '__') + +def is_possible_submodule(module, attr): + try: + obj = getattr(module, attr) + except AttributeError: + # Is possilby an unimported submodule + return True + except TypeError: + # https://github.com/ipython/ipython/issues/9678 + return False + return inspect.ismodule(obj) + + +def try_import(mod: str, only_modules=False) -> List[str]: + """ + Try to import given module and return list of potential completions. + """ + mod = mod.rstrip('.') + try: + m = import_module(mod) + except: + return [] + + m_is_init = '__init__' in (getattr(m, '__file__', '') or '') + + completions = [] + if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init: + completions.extend( [attr for attr in dir(m) if + is_importable(m, attr, only_modules)]) + + m_all = getattr(m, "__all__", []) + if only_modules: + completions.extend(attr for attr in m_all if is_possible_submodule(m, attr)) + else: + completions.extend(m_all) + + if m_is_init: + completions.extend(module_list(os.path.dirname(m.__file__))) + completions_set = {c for c in completions if isinstance(c, str)} + completions_set.discard('__init__') + return list(completions_set) + + +#----------------------------------------------------------------------------- +# Completion-related functions. +#----------------------------------------------------------------------------- + +def quick_completer(cmd, completions): + r""" Easily create a trivial completer for a command. + + Takes either a list of completions, or all completions in string (that will + be split on whitespace). + + Example:: + + [d:\ipython]|1> import ipy_completers + [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) + [d:\ipython]|3> foo b + bar baz + [d:\ipython]|3> foo ba + """ + + if isinstance(completions, str): + completions = completions.split() + + def do_complete(self, event): + return completions + + get_ipython().set_hook('complete_command',do_complete, str_key = cmd) + +def module_completion(line): + """ + Returns a list containing the completion possibilities for an import line. + + The line looks like this : + 'import xml.d' + 'from xml.dom import' + """ + + words = line.split(' ') + nwords = len(words) + + # from whatever -> 'import ' + if nwords == 3 and words[0] == 'from': + return ['import '] + + # 'from xy' or 'import xy' + if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) : + if nwords == 1: + return get_root_modules() + mod = words[1].split('.') + if len(mod) < 2: + return get_root_modules() + completion_list = try_import('.'.join(mod[:-1]), True) + return ['.'.join(mod[:-1] + [el]) for el in completion_list] + + # 'from xyz import abc' + if nwords >= 3 and words[0] == 'from': + mod = words[1] + return try_import(mod) + +#----------------------------------------------------------------------------- +# Completers +#----------------------------------------------------------------------------- +# These all have the func(self, event) signature to be used as custom +# completers + +def module_completer(self,event): + """Give completions after user has typed 'import ...' or 'from ...'""" + + # This works in all versions of python. While 2.5 has + # pkgutil.walk_packages(), that particular routine is fairly dangerous, + # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full + # of possibly problematic side effects. + # This search the folders in the sys.path for available modules. + + return module_completion(event.line) + +# FIXME: there's a lot of logic common to the run, cd and builtin file +# completers, that is currently reimplemented in each. + +def magic_run_completer(self, event): + """Complete files that end in .py or .ipy or .ipynb for the %run command. + """ + comps = arg_split(event.line, strict=False) + # relpath should be the current token that we need to complete. + if (len(comps) > 1) and (not event.line.endswith(' ')): + relpath = comps[-1].strip("'\"") + else: + relpath = '' + + #print("\nev=", event) # dbg + #print("rp=", relpath) # dbg + #print('comps=', comps) # dbg + + lglob = glob.glob + isdir = os.path.isdir + relpath, tilde_expand, tilde_val = expand_user(relpath) + + # Find if the user has already typed the first filename, after which we + # should complete on all files, since after the first one other files may + # be arguments to the input script. + + if any(magic_run_re.match(c) for c in comps): + matches = [f.replace('\\','/') + ('/' if isdir(f) else '') + for f in lglob(relpath+'*')] + else: + dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)] + pys = [f.replace('\\','/') + for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + + lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')] + + matches = dirs + pys + + #print('run comp:', dirs+pys) # dbg + return [compress_user(p, tilde_expand, tilde_val) for p in matches] + + +def cd_completer(self, event): + """Completer function for cd, which only returns directories.""" + ip = get_ipython() + relpath = event.symbol + + #print(event) # dbg + if event.line.endswith('-b') or ' -b ' in event.line: + # return only bookmark completions + bkms = self.db.get('bookmarks', None) + if bkms: + return bkms.keys() + else: + return [] + + if event.symbol == '-': + width_dh = str(len(str(len(ip.user_ns['_dh']) + 1))) + # jump in directory history by number + fmt = '-%0' + width_dh +'d [%s]' + ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] + if len(ents) > 1: + return ents + return [] + + if event.symbol.startswith('--'): + return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']] + + # Expand ~ in path and normalize directory separators. + relpath, tilde_expand, tilde_val = expand_user(relpath) + relpath = relpath.replace('\\','/') + + found = [] + for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') + if os.path.isdir(f)]: + if ' ' in d: + # we don't want to deal with any of that, complex code + # for this is elsewhere + raise TryNext + + found.append(d) + + if not found: + if os.path.isdir(relpath): + return [compress_user(relpath, tilde_expand, tilde_val)] + + # if no completions so far, try bookmarks + bks = self.db.get('bookmarks',{}) + bkmatches = [s for s in bks if s.startswith(event.symbol)] + if bkmatches: + return bkmatches + + raise TryNext + + return [compress_user(p, tilde_expand, tilde_val) for p in found] + +def reset_completer(self, event): + "A completer for %reset magic" + return '-f -s in out array dhist'.split() diff --git a/.venv/lib/python3.8/site-packages/IPython/core/crashhandler.py b/.venv/lib/python3.8/site-packages/IPython/core/crashhandler.py new file mode 100644 index 0000000..4af3936 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/crashhandler.py @@ -0,0 +1,237 @@ +# encoding: utf-8 +"""sys.excepthook for IPython itself, leaves a detailed report on disk. + +Authors: + +* Fernando Perez +* Brian E. Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2001-2007 Fernando Perez. +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import sys +import traceback +from pprint import pformat +from pathlib import Path + +from IPython.core import ultratb +from IPython.core.release import author_email +from IPython.utils.sysinfo import sys_info +from IPython.utils.py3compat import input + +from IPython.core.release import __version__ as version + +from typing import Optional + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +# Template for the user message. +_default_message_template = """\ +Oops, {app_name} crashed. We do our best to make it stable, but... + +A crash report was automatically generated with the following information: + - A verbatim copy of the crash traceback. + - A copy of your input history during this session. + - Data on your current {app_name} configuration. + +It was left in the file named: +\t'{crash_report_fname}' +If you can email this file to the developers, the information in it will help +them in understanding and correcting the problem. + +You can mail it to: {contact_name} at {contact_email} +with the subject '{app_name} Crash Report'. + +If you want to do it now, the following command will work (under Unix): +mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} + +In your email, please also include information about: +- The operating system under which the crash happened: Linux, macOS, Windows, + other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2, + Windows 10 Pro), and whether it is 32-bit or 64-bit; +- How {app_name} was installed: using pip or conda, from GitHub, as part of + a Docker container, or other, providing more detail if possible; +- How to reproduce the crash: what exact sequence of instructions can one + input to get the same crash? Ideally, find a minimal yet complete sequence + of instructions that yields the crash. + +To ensure accurate tracking of this issue, please file a report about it at: +{bug_tracker} +""" + +_lite_message_template = """ +If you suspect this is an IPython {version} bug, please report it at: + https://github.com/ipython/ipython/issues +or send an email to the mailing list at {email} + +You can print a more detailed traceback right now with "%tb", or use "%debug" +to interactively debug it. + +Extra-detailed tracebacks for bug-reporting purposes can be enabled via: + {config}Application.verbose_crash=True +""" + + +class CrashHandler(object): + """Customizable crash handlers for IPython applications. + + Instances of this class provide a :meth:`__call__` method which can be + used as a ``sys.excepthook``. The :meth:`__call__` signature is:: + + def __call__(self, etype, evalue, etb) + """ + + message_template = _default_message_template + section_sep = '\n\n'+'*'*75+'\n\n' + + def __init__( + self, + app, + contact_name: Optional[str] = None, + contact_email: Optional[str] = None, + bug_tracker: Optional[str] = None, + show_crash_traceback: bool = True, + call_pdb: bool = False, + ): + """Create a new crash handler + + Parameters + ---------- + app : Application + A running :class:`Application` instance, which will be queried at + crash time for internal information. + contact_name : str + A string with the name of the person to contact. + contact_email : str + A string with the email address of the contact. + bug_tracker : str + A string with the URL for your project's bug tracker. + show_crash_traceback : bool + If false, don't print the crash traceback on stderr, only generate + the on-disk report + call_pdb + Whether to call pdb on crash + + Attributes + ---------- + These instances contain some non-argument attributes which allow for + further customization of the crash handler's behavior. Please see the + source for further details. + + """ + self.crash_report_fname = "Crash_report_%s.txt" % app.name + self.app = app + self.call_pdb = call_pdb + #self.call_pdb = True # dbg + self.show_crash_traceback = show_crash_traceback + self.info = dict(app_name = app.name, + contact_name = contact_name, + contact_email = contact_email, + bug_tracker = bug_tracker, + crash_report_fname = self.crash_report_fname) + + + def __call__(self, etype, evalue, etb): + """Handle an exception, call for compatible with sys.excepthook""" + + # do not allow the crash handler to be called twice without reinstalling it + # this prevents unlikely errors in the crash handling from entering an + # infinite loop. + sys.excepthook = sys.__excepthook__ + + # Report tracebacks shouldn't use color in general (safer for users) + color_scheme = 'NoColor' + + # Use this ONLY for developer debugging (keep commented out for release) + #color_scheme = 'Linux' # dbg + try: + rptdir = self.app.ipython_dir + except: + rptdir = Path.cwd() + if rptdir is None or not Path.is_dir(rptdir): + rptdir = Path.cwd() + report_name = rptdir / self.crash_report_fname + # write the report filename into the instance dict so it can get + # properly expanded out in the user message template + self.crash_report_fname = report_name + self.info['crash_report_fname'] = report_name + TBhandler = ultratb.VerboseTB( + color_scheme=color_scheme, + long_header=1, + call_pdb=self.call_pdb, + ) + if self.call_pdb: + TBhandler(etype,evalue,etb) + return + else: + traceback = TBhandler.text(etype,evalue,etb,context=31) + + # print traceback to screen + if self.show_crash_traceback: + print(traceback, file=sys.stderr) + + # and generate a complete report on disk + try: + report = open(report_name, "w", encoding="utf-8") + except: + print('Could not create crash report on disk.', file=sys.stderr) + return + + with report: + # Inform user on stderr of what happened + print('\n'+'*'*70+'\n', file=sys.stderr) + print(self.message_template.format(**self.info), file=sys.stderr) + + # Construct report on disk + report.write(self.make_report(traceback)) + + input("Hit to quit (your terminal may close):") + + def make_report(self,traceback): + """Return a string containing a crash report.""" + + sec_sep = self.section_sep + + report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] + rpt_add = report.append + rpt_add(sys_info()) + + try: + config = pformat(self.app.config) + rpt_add(sec_sep) + rpt_add('Application name: %s\n\n' % self.app_name) + rpt_add('Current user configuration structure:\n\n') + rpt_add(config) + except: + pass + rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) + + return ''.join(report) + + +def crash_handler_lite(etype, evalue, tb): + """a light excepthook, adding a small message to the usual traceback""" + traceback.print_exception(etype, evalue, tb) + + from IPython.core.interactiveshell import InteractiveShell + if InteractiveShell.initialized(): + # we are in a Shell environment, give %magic example + config = "%config " + else: + # we are not in a shell, show generic config + config = "c." + print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) + diff --git a/.venv/lib/python3.8/site-packages/IPython/core/debugger.py b/.venv/lib/python3.8/site-packages/IPython/core/debugger.py new file mode 100644 index 0000000..8e3dd96 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/debugger.py @@ -0,0 +1,1000 @@ +# -*- coding: utf-8 -*- +""" +Pdb debugger class. + + +This is an extension to PDB which adds a number of new features. +Note that there is also the `IPython.terminal.debugger` class which provides UI +improvements. + +We also strongly recommend to use this via the `ipdb` package, which provides +extra configuration options. + +Among other things, this subclass of PDB: + - supports many IPython magics like pdef/psource + - hide frames in tracebacks based on `__tracebackhide__` + - allows to skip frames based on `__debuggerskip__` + +The skipping and hiding frames are configurable via the `skip_predicates` +command. + +By default, frames from readonly files will be hidden, frames containing +``__tracebackhide__=True`` will be hidden. + +Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent +frames value of ``__debuggerskip__`` is ``True`` will be skipped. + + >>> def helpers_helper(): + ... pass + ... + ... def helper_1(): + ... print("don't step in me") + ... helpers_helpers() # will be stepped over unless breakpoint set. + ... + ... + ... def helper_2(): + ... print("in me neither") + ... + +One can define a decorator that wraps a function between the two helpers: + + >>> def pdb_skipped_decorator(function): + ... + ... + ... def wrapped_fn(*args, **kwargs): + ... __debuggerskip__ = True + ... helper_1() + ... __debuggerskip__ = False + ... result = function(*args, **kwargs) + ... __debuggerskip__ = True + ... helper_2() + ... # setting __debuggerskip__ to False again is not necessary + ... return result + ... + ... return wrapped_fn + +When decorating a function, ipdb will directly step into ``bar()`` by +default: + + >>> @foo_decorator + ... def bar(x, y): + ... return x * y + + +You can toggle the behavior with + + ipdb> skip_predicates debuggerskip false + +or configure it in your ``.pdbrc`` + + + +License +------- + +Modified from the standard pdb.Pdb class to avoid including readline, so that +the command line completion of other programs which include this isn't +damaged. + +In the future, this class will be expanded with improvements over the standard +pdb. + +The original code in this file is mainly lifted out of cmd.py in Python 2.2, +with minor changes. Licensing should therefore be under the standard Python +terms. For details on the PSF (Python Software Foundation) standard license, +see: + +https://docs.python.org/2/license.html + + +All the changes since then are under the same license as IPython. + +""" + +#***************************************************************************** +# +# This file is licensed under the PSF license. +# +# Copyright (C) 2001 Python Software Foundation, www.python.org +# Copyright (C) 2005-2006 Fernando Perez. +# +# +#***************************************************************************** + +import bdb +import inspect +import linecache +import sys +import warnings +import re +import os + +from IPython import get_ipython +from IPython.utils import PyColorize +from IPython.utils import coloransi, py3compat +from IPython.core.excolors import exception_colors + +# skip module docstests +__skip_doctest__ = True + +prompt = 'ipdb> ' + +# We have to check this directly from sys.argv, config struct not yet available +from pdb import Pdb as OldPdb + +# Allow the set_trace code to operate outside of an ipython instance, even if +# it does so with some limitations. The rest of this support is implemented in +# the Tracer constructor. + +DEBUGGERSKIP = "__debuggerskip__" + + +def make_arrow(pad): + """generate the leading arrow in front of traceback or debugger""" + if pad >= 2: + return '-'*(pad-2) + '> ' + elif pad == 1: + return '>' + return '' + + +def BdbQuit_excepthook(et, ev, tb, excepthook=None): + """Exception hook which handles `BdbQuit` exceptions. + + All other exceptions are processed using the `excepthook` + parameter. + """ + raise ValueError( + "`BdbQuit_excepthook` is deprecated since version 5.1", + ) + + +def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None): + raise ValueError( + "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", + DeprecationWarning, stacklevel=2) + + +RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') + + +def strip_indentation(multiline_string): + return RGX_EXTRA_INDENT.sub('', multiline_string) + + +def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): + """Make new_fn have old_fn's doc string. This is particularly useful + for the ``do_...`` commands that hook into the help system. + Adapted from from a comp.lang.python posting + by Duncan Booth.""" + def wrapper(*args, **kw): + return new_fn(*args, **kw) + if old_fn.__doc__: + wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text + return wrapper + + +class Pdb(OldPdb): + """Modified Pdb class, does not load readline. + + for a standalone version that uses prompt_toolkit, see + `IPython.terminal.debugger.TerminalPdb` and + `IPython.terminal.debugger.set_trace()` + + + This debugger can hide and skip frames that are tagged according to some predicates. + See the `skip_predicates` commands. + + """ + + default_predicates = { + "tbhide": True, + "readonly": False, + "ipython_internal": True, + "debuggerskip": True, + } + + def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs): + """Create a new IPython debugger. + + Parameters + ---------- + completekey : default None + Passed to pdb.Pdb. + stdin : default None + Passed to pdb.Pdb. + stdout : default None + Passed to pdb.Pdb. + context : int + Number of lines of source code context to show when + displaying stacktrace information. + **kwargs + Passed to pdb.Pdb. + + Notes + ----- + The possibilities are python version dependent, see the python + docs for more info. + """ + + # Parent constructor: + try: + self.context = int(context) + if self.context <= 0: + raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e + + # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`. + OldPdb.__init__(self, completekey, stdin, stdout, **kwargs) + + # IPython changes... + self.shell = get_ipython() + + if self.shell is None: + save_main = sys.modules['__main__'] + # No IPython instance running, we must create one + from IPython.terminal.interactiveshell import \ + TerminalInteractiveShell + self.shell = TerminalInteractiveShell.instance() + # needed by any code which calls __import__("__main__") after + # the debugger was entered. See also #9941. + sys.modules["__main__"] = save_main + + + color_scheme = self.shell.colors + + self.aliases = {} + + # Create color table: we copy the default one from the traceback + # module and add a few attributes needed for debugging + self.color_scheme_table = exception_colors() + + # shorthands + C = coloransi.TermColors + cst = self.color_scheme_table + + cst['NoColor'].colors.prompt = C.NoColor + cst['NoColor'].colors.breakpoint_enabled = C.NoColor + cst['NoColor'].colors.breakpoint_disabled = C.NoColor + + cst['Linux'].colors.prompt = C.Green + cst['Linux'].colors.breakpoint_enabled = C.LightRed + cst['Linux'].colors.breakpoint_disabled = C.Red + + cst['LightBG'].colors.prompt = C.Blue + cst['LightBG'].colors.breakpoint_enabled = C.LightRed + cst['LightBG'].colors.breakpoint_disabled = C.Red + + cst['Neutral'].colors.prompt = C.Blue + cst['Neutral'].colors.breakpoint_enabled = C.LightRed + cst['Neutral'].colors.breakpoint_disabled = C.Red + + # Add a python parser so we can syntax highlight source while + # debugging. + self.parser = PyColorize.Parser(style=color_scheme) + self.set_colors(color_scheme) + + # Set the prompt - the default prompt is '(Pdb)' + self.prompt = prompt + self.skip_hidden = True + self.report_skipped = True + + # list of predicates we use to skip frames + self._predicates = self.default_predicates + + # + def set_colors(self, scheme): + """Shorthand access to the color table scheme selector method.""" + self.color_scheme_table.set_active_scheme(scheme) + self.parser.style = scheme + + def set_trace(self, frame=None): + if frame is None: + frame = sys._getframe().f_back + self.initial_frame = frame + return super().set_trace(frame) + + def _hidden_predicate(self, frame): + """ + Given a frame return whether it it should be hidden or not by IPython. + """ + + if self._predicates["readonly"]: + fname = frame.f_code.co_filename + # we need to check for file existence and interactively define + # function would otherwise appear as RO. + if os.path.isfile(fname) and not os.access(fname, os.W_OK): + return True + + if self._predicates["tbhide"]: + if frame in (self.curframe, getattr(self, "initial_frame", None)): + return False + frame_locals = self._get_frame_locals(frame) + if "__tracebackhide__" not in frame_locals: + return False + return frame_locals["__tracebackhide__"] + return False + + def hidden_frames(self, stack): + """ + Given an index in the stack return whether it should be skipped. + + This is used in up/down and where to skip frames. + """ + # The f_locals dictionary is updated from the actual frame + # locals whenever the .f_locals accessor is called, so we + # avoid calling it here to preserve self.curframe_locals. + # Furthermore, there is no good reason to hide the current frame. + ip_hide = [self._hidden_predicate(s[0]) for s in stack] + ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] + if ip_start and self._predicates["ipython_internal"]: + ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] + return ip_hide + + def interaction(self, frame, traceback): + try: + OldPdb.interaction(self, frame, traceback) + except KeyboardInterrupt: + self.stdout.write("\n" + self.shell.get_exception_only()) + + def precmd(self, line): + """Perform useful escapes on the command before it is executed.""" + + if line.endswith("??"): + line = "pinfo2 " + line[:-2] + elif line.endswith("?"): + line = "pinfo " + line[:-1] + + line = super().precmd(line) + + return line + + def new_do_frame(self, arg): + OldPdb.do_frame(self, arg) + + def new_do_quit(self, arg): + + if hasattr(self, 'old_all_completions'): + self.shell.Completer.all_completions = self.old_all_completions + + return OldPdb.do_quit(self, arg) + + do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit) + + def new_do_restart(self, arg): + """Restart command. In the context of ipython this is exactly the same + thing as 'quit'.""" + self.msg("Restart doesn't make sense here. Using 'quit' instead.") + return self.do_quit(arg) + + def print_stack_trace(self, context=None): + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + if context is None: + context = self.context + try: + context = int(context) + if context <= 0: + raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e + try: + skipped = 0 + for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): + if hidden and self.skip_hidden: + skipped += 1 + continue + if skipped: + print( + f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + skipped = 0 + self.print_stack_entry(frame_lineno, context=context) + if skipped: + print( + f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + except KeyboardInterrupt: + pass + + def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ', + context=None): + if context is None: + context = self.context + try: + context = int(context) + if context <= 0: + raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e + print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout) + + # vds: >> + frame, lineno = frame_lineno + filename = frame.f_code.co_filename + self.shell.hooks.synchronize_with_editor(filename, lineno, 0) + # vds: << + + def _get_frame_locals(self, frame): + """ " + Accessing f_local of current frame reset the namespace, so we want to avoid + that or the following can happen + + ipdb> foo + "old" + ipdb> foo = "new" + ipdb> foo + "new" + ipdb> where + ipdb> foo + "old" + + So if frame is self.current_frame we instead return self.curframe_locals + + """ + if frame is self.curframe: + return self.curframe_locals + else: + return frame.f_locals + + def format_stack_entry(self, frame_lineno, lprefix=': ', context=None): + if context is None: + context = self.context + try: + context = int(context) + if context <= 0: + print("Context must be a positive integer", file=self.stdout) + except (TypeError, ValueError): + print("Context must be a positive integer", file=self.stdout) + + import reprlib + + ret = [] + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal) + tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal) + tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal) + tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal) + + frame, lineno = frame_lineno + + return_value = '' + loc_frame = self._get_frame_locals(frame) + if "__return__" in loc_frame: + rv = loc_frame["__return__"] + # return_value += '->' + return_value += reprlib.repr(rv) + "\n" + ret.append(return_value) + + #s = filename + '(' + `lineno` + ')' + filename = self.canonic(frame.f_code.co_filename) + link = tpl_link % py3compat.cast_unicode(filename) + + if frame.f_code.co_name: + func = frame.f_code.co_name + else: + func = "" + + call = "" + if func != "?": + if "__args__" in loc_frame: + args = reprlib.repr(loc_frame["__args__"]) + else: + args = '()' + call = tpl_call % (func, args) + + # The level info should be generated in the same format pdb uses, to + # avoid breaking the pdbtrack functionality of python-mode in *emacs. + if frame is self.curframe: + ret.append('> ') + else: + ret.append(" ") + ret.append("%s(%s)%s\n" % (link, lineno, call)) + + start = lineno - 1 - context//2 + lines = linecache.getlines(filename) + start = min(start, len(lines) - context) + start = max(start, 0) + lines = lines[start : start + context] + + for i, line in enumerate(lines): + show_arrow = start + 1 + i == lineno + linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line + ret.append( + self.__format_line( + linetpl, filename, start + 1 + i, line, arrow=show_arrow + ) + ) + return "".join(ret) + + def __format_line(self, tpl_line, filename, lineno, line, arrow=False): + bp_mark = "" + bp_mark_color = "" + + new_line, err = self.parser.format2(line, 'str') + if not err: + line = new_line + + bp = None + if lineno in self.get_file_breaks(filename): + bps = self.get_breaks(filename, lineno) + bp = bps[-1] + + if bp: + Colors = self.color_scheme_table.active_colors + bp_mark = str(bp.number) + bp_mark_color = Colors.breakpoint_enabled + if not bp.enabled: + bp_mark_color = Colors.breakpoint_disabled + + numbers_width = 7 + if arrow: + # This is the line with the error + pad = numbers_width - len(str(lineno)) - len(bp_mark) + num = '%s%s' % (make_arrow(pad), str(lineno)) + else: + num = '%*s' % (numbers_width - len(bp_mark), str(lineno)) + + return tpl_line % (bp_mark_color + bp_mark, num, line) + + def print_list_lines(self, filename, first, last): + """The printing (as opposed to the parsing part of a 'list' + command.""" + try: + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) + tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal) + src = [] + if filename == "" and hasattr(self, "_exec_filename"): + filename = self._exec_filename + + for lineno in range(first, last+1): + line = linecache.getline(filename, lineno) + if not line: + break + + if lineno == self.curframe.f_lineno: + line = self.__format_line( + tpl_line_em, filename, lineno, line, arrow=True + ) + else: + line = self.__format_line( + tpl_line, filename, lineno, line, arrow=False + ) + + src.append(line) + self.lineno = lineno + + print(''.join(src), file=self.stdout) + + except KeyboardInterrupt: + pass + + def do_skip_predicates(self, args): + """ + Turn on/off individual predicates as to whether a frame should be hidden/skip. + + The global option to skip (or not) hidden frames is set with skip_hidden + + To change the value of a predicate + + skip_predicates key [true|false] + + Call without arguments to see the current values. + + To permanently change the value of an option add the corresponding + command to your ``~/.pdbrc`` file. If you are programmatically using the + Pdb instance you can also change the ``default_predicates`` class + attribute. + """ + if not args.strip(): + print("current predicates:") + for (p, v) in self._predicates.items(): + print(" ", p, ":", v) + return + type_value = args.strip().split(" ") + if len(type_value) != 2: + print( + f"Usage: skip_predicates , with one of {set(self._predicates.keys())}" + ) + return + + type_, value = type_value + if type_ not in self._predicates: + print(f"{type_!r} not in {set(self._predicates.keys())}") + return + if value.lower() not in ("true", "yes", "1", "no", "false", "0"): + print( + f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')" + ) + return + + self._predicates[type_] = value.lower() in ("true", "yes", "1") + if not any(self._predicates.values()): + print( + "Warning, all predicates set to False, skip_hidden may not have any effects." + ) + + def do_skip_hidden(self, arg): + """ + Change whether or not we should skip frames with the + __tracebackhide__ attribute. + """ + if not arg.strip(): + print( + f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change." + ) + elif arg.strip().lower() in ("true", "yes"): + self.skip_hidden = True + elif arg.strip().lower() in ("false", "no"): + self.skip_hidden = False + if not any(self._predicates.values()): + print( + "Warning, all predicates set to False, skip_hidden may not have any effects." + ) + + def do_list(self, arg): + """Print lines of code from the current stack frame + """ + self.lastcmd = 'list' + last = None + if arg: + try: + x = eval(arg, {}, {}) + if type(x) == type(()): + first, last = x + first = int(first) + last = int(last) + if last < first: + # Assume it's a count + last = first + last + else: + first = max(1, int(x) - 5) + except: + print('*** Error in argument:', repr(arg), file=self.stdout) + return + elif self.lineno is None: + first = max(1, self.curframe.f_lineno - 5) + else: + first = self.lineno + 1 + if last is None: + last = first + 10 + self.print_list_lines(self.curframe.f_code.co_filename, first, last) + + # vds: >> + lineno = first + filename = self.curframe.f_code.co_filename + self.shell.hooks.synchronize_with_editor(filename, lineno, 0) + # vds: << + + do_l = do_list + + def getsourcelines(self, obj): + lines, lineno = inspect.findsource(obj) + if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj): + # must be a module frame: do not try to cut a block out of it + return lines, 1 + elif inspect.ismodule(obj): + return lines, 1 + return inspect.getblock(lines[lineno:]), lineno+1 + + def do_longlist(self, arg): + """Print lines of code from the current stack frame. + + Shows more lines than 'list' does. + """ + self.lastcmd = 'longlist' + try: + lines, lineno = self.getsourcelines(self.curframe) + except OSError as err: + self.error(err) + return + last = lineno + len(lines) + self.print_list_lines(self.curframe.f_code.co_filename, lineno, last) + do_ll = do_longlist + + def do_debug(self, arg): + """debug code + Enter a recursive debugger that steps through the code + argument (which is an arbitrary expression or statement to be + executed in the current environment). + """ + trace_function = sys.gettrace() + sys.settrace(None) + globals = self.curframe.f_globals + locals = self.curframe_locals + p = self.__class__(completekey=self.completekey, + stdin=self.stdin, stdout=self.stdout) + p.use_rawinput = self.use_rawinput + p.prompt = "(%s) " % self.prompt.strip() + self.message("ENTERING RECURSIVE DEBUGGER") + sys.call_tracing(p.run, (arg, globals, locals)) + self.message("LEAVING RECURSIVE DEBUGGER") + sys.settrace(trace_function) + self.lastcmd = p.lastcmd + + def do_pdef(self, arg): + """Print the call signature for any callable object. + + The debugger interface to %pdef""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pdef")(arg, namespaces=namespaces) + + def do_pdoc(self, arg): + """Print the docstring for an object. + + The debugger interface to %pdoc.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces) + + def do_pfile(self, arg): + """Print (or run through pager) the file where an object is defined. + + The debugger interface to %pfile. + """ + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pfile")(arg, namespaces=namespaces) + + def do_pinfo(self, arg): + """Provide detailed information about an object. + + The debugger interface to %pinfo, i.e., obj?.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces) + + def do_pinfo2(self, arg): + """Provide extra detailed information about an object. + + The debugger interface to %pinfo2, i.e., obj??.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces) + + def do_psource(self, arg): + """Print (or run through pager) the source code for an object.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("psource")(arg, namespaces=namespaces) + + def do_where(self, arg): + """w(here) + Print a stack trace, with the most recent frame at the bottom. + An arrow indicates the "current frame", which determines the + context of most commands. 'bt' is an alias for this command. + + Take a number as argument as an (optional) number of context line to + print""" + if arg: + try: + context = int(arg) + except ValueError as err: + self.error(err) + return + self.print_stack_trace(context) + else: + self.print_stack_trace() + + do_w = do_where + + def break_anywhere(self, frame): + """ + _stop_in_decorator_internals is overly restrictive, as we may still want + to trace function calls, so we need to also update break_anywhere so + that is we don't `stop_here`, because of debugger skip, we may still + stop at any point inside the function + + """ + + sup = super().break_anywhere(frame) + if sup: + return sup + if self._predicates["debuggerskip"]: + if DEBUGGERSKIP in frame.f_code.co_varnames: + return True + if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): + return True + return False + + def _is_in_decorator_internal_and_should_skip(self, frame): + """ + Utility to tell us whether we are in a decorator internal and should stop. + + """ + + # if we are disabled don't skip + if not self._predicates["debuggerskip"]: + return False + + # if frame is tagged, skip by default. + if DEBUGGERSKIP in frame.f_code.co_varnames: + return True + + # if one of the parent frame value set to True skip as well. + + cframe = frame + while getattr(cframe, "f_back", None): + cframe = cframe.f_back + if self._get_frame_locals(cframe).get(DEBUGGERSKIP): + return True + + return False + + def stop_here(self, frame): + + if self._is_in_decorator_internal_and_should_skip(frame) is True: + return False + + hidden = False + if self.skip_hidden: + hidden = self._hidden_predicate(frame) + if hidden: + if self.report_skipped: + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + print( + f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n" + ) + return super().stop_here(frame) + + def do_up(self, arg): + """u(p) [count] + Move the current frame count (default one) levels up in the + stack trace (to an older frame). + + Will skip hidden frames. + """ + # modified version of upstream that skips + # frames with __tracebackhide__ + if self.curindex == 0: + self.error("Oldest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + skipped = 0 + if count < 0: + _newframe = 0 + else: + counter = 0 + hidden_frames = self.hidden_frames(self.stack) + for i in range(self.curindex - 1, -1, -1): + if hidden_frames[i] and self.skip_hidden: + skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + # if no break occurred. + self.error( + "all frames above hidden, use `skip_hidden False` to get get into those." + ) + return + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + _newframe = i + self._select_frame(_newframe) + if skipped: + print( + f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + + def do_down(self, arg): + """d(own) [count] + Move the current frame count (default one) levels down in the + stack trace (to a newer frame). + + Will skip hidden frames. + """ + if self.curindex + 1 == len(self.stack): + self.error("Newest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + if count < 0: + _newframe = len(self.stack) - 1 + else: + counter = 0 + skipped = 0 + hidden_frames = self.hidden_frames(self.stack) + for i in range(self.curindex + 1, len(self.stack)): + if hidden_frames[i] and self.skip_hidden: + skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + self.error( + "all frames below hidden, use `skip_hidden False` to get get into those." + ) + return + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + if skipped: + print( + f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + _newframe = i + + self._select_frame(_newframe) + + do_d = do_down + do_u = do_up + + def do_context(self, context): + """context number_of_lines + Set the number of lines of source code to show when displaying + stacktrace information. + """ + try: + new_context = int(context) + if new_context <= 0: + raise ValueError() + self.context = new_context + except ValueError: + self.error("The 'context' command requires a positive integer argument.") + + +class InterruptiblePdb(Pdb): + """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" + + def cmdloop(self, intro=None): + """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" + try: + return OldPdb.cmdloop(self, intro=intro) + except KeyboardInterrupt: + self.stop_here = lambda frame: False + self.do_quit("") + sys.settrace(None) + self.quitting = False + raise + + def _cmdloop(self): + while True: + try: + # keyboard interrupts allow for an easy way to cancel + # the current command, so allow them during interactive input + self.allow_kbdint = True + self.cmdloop() + self.allow_kbdint = False + break + except KeyboardInterrupt: + self.message('--KeyboardInterrupt--') + raise + + +def set_trace(frame=None): + """ + Start debugging from `frame`. + + If frame is not specified, debugging starts from caller's frame. + """ + Pdb().set_trace(frame or sys._getframe().f_back) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/display.py b/.venv/lib/python3.8/site-packages/IPython/core/display.py new file mode 100644 index 0000000..933295a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/display.py @@ -0,0 +1,1277 @@ +# -*- coding: utf-8 -*- +"""Top-level display functions for displaying object in different formats.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from binascii import b2a_base64, hexlify +import html +import json +import mimetypes +import os +import struct +import warnings +from copy import deepcopy +from os.path import splitext +from pathlib import Path, PurePath + +from IPython.utils.py3compat import cast_unicode +from IPython.testing.skipdoctest import skip_doctest +from . import display_functions + + +__all__ = ['display_pretty', 'display_html', 'display_markdown', + 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', + 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', + 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON', + 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats', + 'set_matplotlib_close', + 'Video'] + +_deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"] + +__all__ = __all__ + _deprecated_names + + +# ----- warn to import from IPython.display ----- + +from warnings import warn + + +def __getattr__(name): + if name in _deprecated_names: + warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2) + return getattr(display_functions, name) + + if name in globals().keys(): + return globals()[name] + else: + raise AttributeError(f"module {__name__} has no attribute {name}") + + +#----------------------------------------------------------------------------- +# utility functions +#----------------------------------------------------------------------------- + +def _safe_exists(path): + """Check path, but don't let exceptions raise""" + try: + return os.path.exists(path) + except Exception: + return False + + +def _display_mimetype(mimetype, objs, raw=False, metadata=None): + """internal implementation of all display_foo methods + + Parameters + ---------- + mimetype : str + The mimetype to be published (e.g. 'image/png') + *objs : object + The Python objects to display, or if raw=True raw text data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + if metadata: + metadata = {mimetype: metadata} + if raw: + # turn list of pngdata into list of { 'image/png': pngdata } + objs = [ {mimetype: obj} for obj in objs ] + display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype]) + +#----------------------------------------------------------------------------- +# Main functions +#----------------------------------------------------------------------------- + + +def display_pretty(*objs, **kwargs): + """Display the pretty (default) representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw text data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('text/plain', objs, **kwargs) + + +def display_html(*objs, **kwargs): + """Display the HTML representation of an object. + + Note: If raw=False and the object does not have a HTML + representation, no HTML will be shown. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw HTML data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('text/html', objs, **kwargs) + + +def display_markdown(*objs, **kwargs): + """Displays the Markdown representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw markdown data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + + _display_mimetype('text/markdown', objs, **kwargs) + + +def display_svg(*objs, **kwargs): + """Display the SVG representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw svg data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('image/svg+xml', objs, **kwargs) + + +def display_png(*objs, **kwargs): + """Display the PNG representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw png data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('image/png', objs, **kwargs) + + +def display_jpeg(*objs, **kwargs): + """Display the JPEG representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw JPEG data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('image/jpeg', objs, **kwargs) + + +def display_latex(*objs, **kwargs): + """Display the LaTeX representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw latex data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('text/latex', objs, **kwargs) + + +def display_json(*objs, **kwargs): + """Display the JSON representation of an object. + + Note that not many frontends support displaying JSON. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw json data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('application/json', objs, **kwargs) + + +def display_javascript(*objs, **kwargs): + """Display the Javascript representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw javascript data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('application/javascript', objs, **kwargs) + + +def display_pdf(*objs, **kwargs): + """Display the PDF representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw javascript data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('application/pdf', objs, **kwargs) + + +#----------------------------------------------------------------------------- +# Smart classes +#----------------------------------------------------------------------------- + + +class DisplayObject(object): + """An object that wraps data to be displayed.""" + + _read_flags = 'r' + _show_mem_addr = False + metadata = None + + def __init__(self, data=None, url=None, filename=None, metadata=None): + """Create a display object given raw data. + + When this object is returned by an expression or passed to the + display function, it will result in the data being displayed + in the frontend. The MIME type of the data should match the + subclasses used, so the Png subclass should be used for 'image/png' + data. If the data is a URL, the data will first be downloaded + and then displayed. If + + Parameters + ---------- + data : unicode, str or bytes + The raw data or a URL or file to load the data from + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + metadata : dict + Dict of metadata associated to be the object when displayed + """ + if isinstance(data, (Path, PurePath)): + data = str(data) + + if data is not None and isinstance(data, str): + if data.startswith('http') and url is None: + url = data + filename = None + data = None + elif _safe_exists(data) and filename is None: + url = None + filename = data + data = None + + self.url = url + self.filename = filename + # because of @data.setter methods in + # subclasses ensure url and filename are set + # before assigning to self.data + self.data = data + + if metadata is not None: + self.metadata = metadata + elif self.metadata is None: + self.metadata = {} + + self.reload() + self._check_data() + + def __repr__(self): + if not self._show_mem_addr: + cls = self.__class__ + r = "<%s.%s object>" % (cls.__module__, cls.__name__) + else: + r = super(DisplayObject, self).__repr__() + return r + + def _check_data(self): + """Override in subclasses if there's something to check.""" + pass + + def _data_and_metadata(self): + """shortcut for returning metadata with shape information, if defined""" + if self.metadata: + return self.data, deepcopy(self.metadata) + else: + return self.data + + def reload(self): + """Reload the raw data from file or URL.""" + if self.filename is not None: + encoding = None if "b" in self._read_flags else "utf-8" + with open(self.filename, self._read_flags, encoding=encoding) as f: + self.data = f.read() + elif self.url is not None: + # Deferred import + from urllib.request import urlopen + response = urlopen(self.url) + data = response.read() + # extract encoding from header, if there is one: + encoding = None + if 'content-type' in response.headers: + for sub in response.headers['content-type'].split(';'): + sub = sub.strip() + if sub.startswith('charset'): + encoding = sub.split('=')[-1].strip() + break + if 'content-encoding' in response.headers: + # TODO: do deflate? + if 'gzip' in response.headers['content-encoding']: + import gzip + from io import BytesIO + + # assume utf-8 if encoding is not specified + with gzip.open( + BytesIO(data), "rt", encoding=encoding or "utf-8" + ) as fp: + encoding = None + data = fp.read() + + # decode data, if an encoding was specified + # We only touch self.data once since + # subclasses such as SVG have @data.setter methods + # that transform self.data into ... well svg. + if encoding: + self.data = data.decode(encoding, 'replace') + else: + self.data = data + + +class TextDisplayObject(DisplayObject): + """Validate that display data is text""" + def _check_data(self): + if self.data is not None and not isinstance(self.data, str): + raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) + +class Pretty(TextDisplayObject): + + def _repr_pretty_(self, pp, cycle): + return pp.text(self.data) + + +class HTML(TextDisplayObject): + + def __init__(self, data=None, url=None, filename=None, metadata=None): + def warn(): + if not data: + return False + + # + # Avoid calling lower() on the entire data, because it could be a + # long string and we're only interested in its beginning and end. + # + prefix = data[:10].lower() + suffix = data[-10:].lower() + return prefix.startswith("') + m_warn.assert_not_called() + + display.HTML('') + m_warn.assert_called_with('Consider using IPython.display.IFrame instead') + + m_warn.reset_mock() + display.HTML('') + m_warn.assert_called_with('Consider using IPython.display.IFrame instead') + +def test_progress(): + p = display.ProgressBar(10) + assert "0/10" in repr(p) + p.html_width = "100%" + p.progress = 5 + assert ( + p._repr_html_() == "" + ) + + +def test_progress_iter(): + with capture_output(display=False) as captured: + for i in display.ProgressBar(5): + out = captured.stdout + assert "{0}/5".format(i) in out + out = captured.stdout + assert "5/5" in out + + +def test_json(): + d = {'a': 5} + lis = [d] + metadata = [ + {'expanded': False, 'root': 'root'}, + {'expanded': True, 'root': 'root'}, + {'expanded': False, 'root': 'custom'}, + {'expanded': True, 'root': 'custom'}, + ] + json_objs = [ + display.JSON(d), + display.JSON(d, expanded=True), + display.JSON(d, root='custom'), + display.JSON(d, expanded=True, root='custom'), + ] + for j, md in zip(json_objs, metadata): + assert j._repr_json_() == (d, md) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + j = display.JSON(json.dumps(d)) + assert len(w) == 1 + assert j._repr_json_() == (d, metadata[0]) + + json_objs = [ + display.JSON(lis), + display.JSON(lis, expanded=True), + display.JSON(lis, root='custom'), + display.JSON(lis, expanded=True, root='custom'), + ] + for j, md in zip(json_objs, metadata): + assert j._repr_json_() == (lis, md) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + j = display.JSON(json.dumps(lis)) + assert len(w) == 1 + assert j._repr_json_() == (lis, metadata[0]) + + +def test_video_embedding(): + """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash""" + v = display.Video("http://ignored") + assert not v.embed + html = v._repr_html_() + assert 'src="data:' not in html + assert 'src="http://ignored"' in html + + with pytest.raises(ValueError): + v = display.Video(b'abc') + + with NamedFileInTemporaryDirectory('test.mp4') as f: + f.write(b'abc') + f.close() + + v = display.Video(f.name) + assert not v.embed + html = v._repr_html_() + assert 'src="data:' not in html + + v = display.Video(f.name, embed=True) + html = v._repr_html_() + assert 'src="data:video/mp4;base64,YWJj"' in html + + v = display.Video(f.name, embed=True, mimetype='video/other') + html = v._repr_html_() + assert 'src="data:video/other;base64,YWJj"' in html + + v = display.Video(b'abc', embed=True, mimetype='video/mp4') + html = v._repr_html_() + assert 'src="data:video/mp4;base64,YWJj"' in html + + v = display.Video(u'YWJj', embed=True, mimetype='video/xyz') + html = v._repr_html_() + assert 'src="data:video/xyz;base64,YWJj"' in html + +def test_html_metadata(): + s = "

Test

" + h = display.HTML(s, metadata={"isolated": True}) + assert h._repr_html_() == (s, {"isolated": True}) + + +def test_display_id(): + ip = get_ipython() + with mock.patch.object(ip.display_pub, 'publish') as pub: + handle = display.display('x') + assert handle is None + handle = display.display('y', display_id='secret') + assert isinstance(handle, display.DisplayHandle) + handle2 = display.display('z', display_id=True) + assert isinstance(handle2, display.DisplayHandle) + assert handle.display_id != handle2.display_id + + assert pub.call_count == 3 + args, kwargs = pub.call_args_list[0] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('x') + }, + 'metadata': {}, + } + args, kwargs = pub.call_args_list[1] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('y') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle.display_id, + }, + } + args, kwargs = pub.call_args_list[2] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('z') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle2.display_id, + }, + } + + +def test_update_display(): + ip = get_ipython() + with mock.patch.object(ip.display_pub, 'publish') as pub: + with pytest.raises(TypeError): + display.update_display('x') + display.update_display('x', display_id='1') + display.update_display('y', display_id='2') + args, kwargs = pub.call_args_list[0] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('x') + }, + 'metadata': {}, + 'transient': { + 'display_id': '1', + }, + 'update': True, + } + args, kwargs = pub.call_args_list[1] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('y') + }, + 'metadata': {}, + 'transient': { + 'display_id': '2', + }, + 'update': True, + } + + +def test_display_handle(): + ip = get_ipython() + handle = display.DisplayHandle() + assert isinstance(handle.display_id, str) + handle = display.DisplayHandle("my-id") + assert handle.display_id == "my-id" + with mock.patch.object(ip.display_pub, "publish") as pub: + handle.display("x") + handle.update("y") + + args, kwargs = pub.call_args_list[0] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('x') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle.display_id, + } + } + args, kwargs = pub.call_args_list[1] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('y') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle.display_id, + }, + 'update': True, + } + + +def test_image_alt_tag(): + """Simple test for display.Image(args, alt=x,)""" + thisurl = "http://example.com/image.png" + img = display.Image(url=thisurl, alt="an image") + assert 'an image' % (thisurl) == img._repr_html_() + img = display.Image(url=thisurl, unconfined=True, alt="an image") + assert ( + 'an image' % (thisurl) + == img._repr_html_() + ) + img = display.Image(url=thisurl, alt='>"& <') + assert '>"& <' % (thisurl) == img._repr_html_() + + img = display.Image(url=thisurl, metadata={"alt": "an image"}) + assert img.alt == "an image" + here = os.path.dirname(__file__) + img = display.Image(os.path.join(here, "2x2.png"), alt="an image") + assert img.alt == "an image" + _, md = img._repr_png_() + assert md["alt"] == "an image" + + +def test_image_bad_filename_raises_proper_exception(): + with pytest.raises(FileNotFoundError): + display.Image("/this/file/does/not/exist/")._repr_png_() diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_displayhook.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_displayhook.py new file mode 100644 index 0000000..6ad8979 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_displayhook.py @@ -0,0 +1,112 @@ +import sys +from IPython.testing.tools import AssertPrints, AssertNotPrints +from IPython.core.displayhook import CapturingDisplayHook +from IPython.utils.capture import CapturedIO + +def test_output_displayed(): + """Checking to make sure that output is displayed""" + + with AssertPrints('2'): + ip.run_cell('1+1', store_history=True) + + with AssertPrints('2'): + ip.run_cell('1+1 # comment with a semicolon;', store_history=True) + + with AssertPrints('2'): + ip.run_cell('1+1\n#commented_out_function();', store_history=True) + + +def test_output_quiet(): + """Checking to make sure that output is quiet""" + + with AssertNotPrints('2'): + ip.run_cell('1+1;', store_history=True) + + with AssertNotPrints('2'): + ip.run_cell('1+1; # comment with a semicolon', store_history=True) + + with AssertNotPrints('2'): + ip.run_cell('1+1;\n#commented_out_function()', store_history=True) + +def test_underscore_no_overrite_user(): + ip.run_cell('_ = 42', store_history=True) + ip.run_cell('1+1', store_history=True) + + with AssertPrints('42'): + ip.run_cell('print(_)', store_history=True) + + ip.run_cell('del _', store_history=True) + ip.run_cell('6+6', store_history=True) + with AssertPrints('12'): + ip.run_cell('_', store_history=True) + + +def test_underscore_no_overrite_builtins(): + ip.run_cell("import gettext ; gettext.install('foo')", store_history=True) + ip.run_cell('3+3', store_history=True) + + with AssertPrints('gettext'): + ip.run_cell('print(_)', store_history=True) + + ip.run_cell('_ = "userset"', store_history=True) + + with AssertPrints('userset'): + ip.run_cell('print(_)', store_history=True) + ip.run_cell('import builtins; del builtins._') + + +def test_interactivehooks_ast_modes(): + """ + Test that ast nodes can be triggered with different modes + """ + saved_mode = ip.ast_node_interactivity + ip.ast_node_interactivity = 'last_expr_or_assign' + + try: + with AssertPrints('2'): + ip.run_cell('a = 1+1', store_history=True) + + with AssertPrints('9'): + ip.run_cell('b = 1+8 # comment with a semicolon;', store_history=False) + + with AssertPrints('7'): + ip.run_cell('c = 1+6\n#commented_out_function();', store_history=True) + + ip.run_cell('d = 11', store_history=True) + with AssertPrints('12'): + ip.run_cell('d += 1', store_history=True) + + with AssertNotPrints('42'): + ip.run_cell('(u,v) = (41+1, 43-1)') + + finally: + ip.ast_node_interactivity = saved_mode + +def test_interactivehooks_ast_modes_semi_suppress(): + """ + Test that ast nodes can be triggered with different modes and suppressed + by semicolon + """ + saved_mode = ip.ast_node_interactivity + ip.ast_node_interactivity = 'last_expr_or_assign' + + try: + with AssertNotPrints('2'): + ip.run_cell('x = 1+1;', store_history=True) + + with AssertNotPrints('7'): + ip.run_cell('y = 1+6; # comment with a semicolon', store_history=True) + + with AssertNotPrints('9'): + ip.run_cell('z = 1+8;\n#commented_out_function()', store_history=True) + + finally: + ip.ast_node_interactivity = saved_mode + +def test_capture_display_hook_format(): + """Tests that the capture display hook conforms to the CapturedIO output format""" + hook = CapturingDisplayHook(ip) + hook({"foo": "bar"}) + captured = CapturedIO(sys.stdout, sys.stderr, hook.outputs) + # Should not raise with RichOutput transformation error + captured.outputs diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_events.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_events.py new file mode 100644 index 0000000..cc9bf40 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_events.py @@ -0,0 +1,91 @@ +import unittest +from unittest.mock import Mock + +from IPython.core import events +import IPython.testing.tools as tt + + +@events._define_event +def ping_received(): + pass + + +@events._define_event +def event_with_argument(argument): + pass + + +class CallbackTests(unittest.TestCase): + def setUp(self): + self.em = events.EventManager(get_ipython(), + {'ping_received': ping_received, + 'event_with_argument': event_with_argument}) + + def test_register_unregister(self): + cb = Mock() + + self.em.register('ping_received', cb) + self.em.trigger('ping_received') + self.assertEqual(cb.call_count, 1) + + self.em.unregister('ping_received', cb) + self.em.trigger('ping_received') + self.assertEqual(cb.call_count, 1) + + def test_bare_function_missed_unregister(self): + def cb1(): + ... + + def cb2(): + ... + + self.em.register("ping_received", cb1) + self.assertRaises(ValueError, self.em.unregister, "ping_received", cb2) + self.em.unregister("ping_received", cb1) + + def test_cb_error(self): + cb = Mock(side_effect=ValueError) + self.em.register('ping_received', cb) + with tt.AssertPrints("Error in callback"): + self.em.trigger('ping_received') + + def test_cb_keyboard_interrupt(self): + cb = Mock(side_effect=KeyboardInterrupt) + self.em.register('ping_received', cb) + with tt.AssertPrints("Error in callback"): + self.em.trigger('ping_received') + + def test_unregister_during_callback(self): + invoked = [False] * 3 + + def func1(*_): + invoked[0] = True + self.em.unregister('ping_received', func1) + self.em.register('ping_received', func3) + + def func2(*_): + invoked[1] = True + self.em.unregister('ping_received', func2) + + def func3(*_): + invoked[2] = True + + self.em.register('ping_received', func1) + self.em.register('ping_received', func2) + + self.em.trigger('ping_received') + self.assertEqual([True, True, False], invoked) + self.assertEqual([func3], self.em.callbacks['ping_received']) + + def test_ignore_event_arguments_if_no_argument_required(self): + call_count = [0] + def event_with_no_argument(): + call_count[0] += 1 + + self.em.register('event_with_argument', event_with_no_argument) + self.em.trigger('event_with_argument', 'the argument') + self.assertEqual(call_count[0], 1) + + self.em.unregister('event_with_argument', event_with_no_argument) + self.em.trigger('ping_received') + self.assertEqual(call_count[0], 1) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_extension.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_extension.py new file mode 100644 index 0000000..24ecf7e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_extension.py @@ -0,0 +1,95 @@ +import os.path + +from tempfile import TemporaryDirectory + +import IPython.testing.tools as tt +from IPython.utils.syspathcontext import prepended_to_syspath + +ext1_content = """ +def load_ipython_extension(ip): + print("Running ext1 load") + +def unload_ipython_extension(ip): + print("Running ext1 unload") +""" + +ext2_content = """ +def load_ipython_extension(ip): + print("Running ext2 load") +""" + +ext3_content = """ +def load_ipython_extension(ip): + ip2 = get_ipython() + print(ip is ip2) +""" + +def test_extension_loading(): + em = get_ipython().extension_manager + with TemporaryDirectory() as td: + ext1 = os.path.join(td, "ext1.py") + with open(ext1, "w", encoding="utf-8") as f: + f.write(ext1_content) + + ext2 = os.path.join(td, "ext2.py") + with open(ext2, "w", encoding="utf-8") as f: + f.write(ext2_content) + + with prepended_to_syspath(td): + assert 'ext1' not in em.loaded + assert 'ext2' not in em.loaded + + # Load extension + with tt.AssertPrints("Running ext1 load"): + assert em.load_extension('ext1') is None + assert 'ext1' in em.loaded + + # Should refuse to load it again + with tt.AssertNotPrints("Running ext1 load"): + assert em.load_extension('ext1') == 'already loaded' + + # Reload + with tt.AssertPrints("Running ext1 unload"): + with tt.AssertPrints("Running ext1 load", suppress=False): + em.reload_extension('ext1') + + # Unload + with tt.AssertPrints("Running ext1 unload"): + assert em.unload_extension('ext1') is None + + # Can't unload again + with tt.AssertNotPrints("Running ext1 unload"): + assert em.unload_extension('ext1') == 'not loaded' + assert em.unload_extension('ext2') == 'not loaded' + + # Load extension 2 + with tt.AssertPrints("Running ext2 load"): + assert em.load_extension('ext2') is None + + # Can't unload this + assert em.unload_extension('ext2') == 'no unload function' + + # But can reload it + with tt.AssertPrints("Running ext2 load"): + em.reload_extension('ext2') + + +def test_extension_builtins(): + em = get_ipython().extension_manager + with TemporaryDirectory() as td: + ext3 = os.path.join(td, "ext3.py") + with open(ext3, "w", encoding="utf-8") as f: + f.write(ext3_content) + + assert 'ext3' not in em.loaded + + with prepended_to_syspath(td): + # Load extension + with tt.AssertPrints("True"): + assert em.load_extension('ext3') is None + assert 'ext3' in em.loaded + + +def test_non_extension(): + em = get_ipython().extension_manager + assert em.load_extension("sys") == "no load function" diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_formatters.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_formatters.py new file mode 100644 index 0000000..26d3583 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_formatters.py @@ -0,0 +1,532 @@ +"""Tests for the Formatters.""" + +import warnings +from math import pi + +try: + import numpy +except: + numpy = None +import pytest + +from IPython import get_ipython +from traitlets.config import Config +from IPython.core.formatters import ( + PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key, + DisplayFormatter, JSONFormatter, +) +from IPython.utils.io import capture_output + +class A(object): + def __repr__(self): + return 'A()' + +class B(A): + def __repr__(self): + return 'B()' + +class C: + pass + +class BadRepr(object): + def __repr__(self): + raise ValueError("bad repr") + +class BadPretty(object): + _repr_pretty_ = None + +class GoodPretty(object): + def _repr_pretty_(self, pp, cycle): + pp.text('foo') + + def __repr__(self): + return 'GoodPretty()' + +def foo_printer(obj, pp, cycle): + pp.text('foo') + +def test_pretty(): + f = PlainTextFormatter() + f.for_type(A, foo_printer) + assert f(A()) == "foo" + assert f(B()) == "B()" + assert f(GoodPretty()) == "foo" + # Just don't raise an exception for the following: + f(BadPretty()) + + f.pprint = False + assert f(A()) == "A()" + assert f(B()) == "B()" + assert f(GoodPretty()) == "GoodPretty()" + + +def test_deferred(): + f = PlainTextFormatter() + +def test_precision(): + """test various values for float_precision.""" + f = PlainTextFormatter() + assert f(pi) == repr(pi) + f.float_precision = 0 + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 0 + assert f(pi) == "3" + f.float_precision = 2 + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 2 + assert f(pi) == "3.14" + f.float_precision = "%g" + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 2 + assert f(pi) == "3.14159" + f.float_precision = "%e" + assert f(pi) == "3.141593e+00" + f.float_precision = "" + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 8 + assert f(pi) == repr(pi) + + +def test_bad_precision(): + """test various invalid values for float_precision.""" + f = PlainTextFormatter() + def set_fp(p): + f.float_precision = p + + pytest.raises(ValueError, set_fp, "%") + pytest.raises(ValueError, set_fp, "%.3f%i") + pytest.raises(ValueError, set_fp, "foo") + pytest.raises(ValueError, set_fp, -1) + +def test_for_type(): + f = PlainTextFormatter() + + # initial return, None + assert f.for_type(C, foo_printer) is None + # no func queries + assert f.for_type(C) is foo_printer + # shouldn't change anything + assert f.for_type(C) is foo_printer + # None should do the same + assert f.for_type(C, None) is foo_printer + assert f.for_type(C, None) is foo_printer + +def test_for_type_string(): + f = PlainTextFormatter() + + type_str = '%s.%s' % (C.__module__, 'C') + + # initial return, None + assert f.for_type(type_str, foo_printer) is None + # no func queries + assert f.for_type(type_str) is foo_printer + assert _mod_name_key(C) in f.deferred_printers + assert f.for_type(C) is foo_printer + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers + +def test_for_type_by_name(): + f = PlainTextFormatter() + + mod = C.__module__ + + # initial return, None + assert f.for_type_by_name(mod, "C", foo_printer) is None + # no func queries + assert f.for_type_by_name(mod, "C") is foo_printer + # shouldn't change anything + assert f.for_type_by_name(mod, "C") is foo_printer + # None should do the same + assert f.for_type_by_name(mod, "C", None) is foo_printer + assert f.for_type_by_name(mod, "C", None) is foo_printer + + +def test_lookup(): + f = PlainTextFormatter() + + f.for_type(C, foo_printer) + assert f.lookup(C()) is foo_printer + with pytest.raises(KeyError): + f.lookup(A()) + +def test_lookup_string(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + + f.for_type(type_str, foo_printer) + assert f.lookup(C()) is foo_printer + # should move from deferred to imported dict + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers + +def test_lookup_by_type(): + f = PlainTextFormatter() + f.for_type(C, foo_printer) + assert f.lookup_by_type(C) is foo_printer + with pytest.raises(KeyError): + f.lookup_by_type(A) + +def test_lookup_by_type_string(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + f.for_type(type_str, foo_printer) + + # verify insertion + assert _mod_name_key(C) in f.deferred_printers + assert C not in f.type_printers + + assert f.lookup_by_type(type_str) is foo_printer + # lookup by string doesn't cause import + assert _mod_name_key(C) in f.deferred_printers + assert C not in f.type_printers + + assert f.lookup_by_type(C) is foo_printer + # should move from deferred to imported dict + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers + +def test_in_formatter(): + f = PlainTextFormatter() + f.for_type(C, foo_printer) + type_str = '%s.%s' % (C.__module__, 'C') + assert C in f + assert type_str in f + +def test_string_in_formatter(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + f.for_type(type_str, foo_printer) + assert type_str in f + assert C in f + +def test_pop(): + f = PlainTextFormatter() + f.for_type(C, foo_printer) + assert f.lookup_by_type(C) is foo_printer + assert f.pop(C, None) is foo_printer + f.for_type(C, foo_printer) + assert f.pop(C) is foo_printer + with pytest.raises(KeyError): + f.lookup_by_type(C) + with pytest.raises(KeyError): + f.pop(C) + with pytest.raises(KeyError): + f.pop(A) + assert f.pop(A, None) is None + +def test_pop_string(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + + with pytest.raises(KeyError): + f.pop(type_str) + + f.for_type(type_str, foo_printer) + f.pop(type_str) + with pytest.raises(KeyError): + f.lookup_by_type(C) + with pytest.raises(KeyError): + f.pop(type_str) + + f.for_type(C, foo_printer) + assert f.pop(type_str, None) is foo_printer + with pytest.raises(KeyError): + f.lookup_by_type(C) + with pytest.raises(KeyError): + f.pop(type_str) + assert f.pop(type_str, None) is None + + +def test_error_method(): + f = HTMLFormatter() + class BadHTML(object): + def _repr_html_(self): + raise ValueError("Bad HTML") + bad = BadHTML() + with capture_output() as captured: + result = f(bad) + assert result is None + assert "Traceback" in captured.stdout + assert "Bad HTML" in captured.stdout + assert "_repr_html_" in captured.stdout + +def test_nowarn_notimplemented(): + f = HTMLFormatter() + class HTMLNotImplemented(object): + def _repr_html_(self): + raise NotImplementedError + h = HTMLNotImplemented() + with capture_output() as captured: + result = f(h) + assert result is None + assert "" == captured.stderr + assert "" == captured.stdout + + +def test_warn_error_for_type(): + f = HTMLFormatter() + f.for_type(int, lambda i: name_error) + with capture_output() as captured: + result = f(5) + assert result is None + assert "Traceback" in captured.stdout + assert "NameError" in captured.stdout + assert "name_error" in captured.stdout + +def test_error_pretty_method(): + f = PlainTextFormatter() + class BadPretty(object): + def _repr_pretty_(self): + return "hello" + bad = BadPretty() + with capture_output() as captured: + result = f(bad) + assert result is None + assert "Traceback" in captured.stdout + assert "_repr_pretty_" in captured.stdout + assert "given" in captured.stdout + assert "argument" in captured.stdout + + +def test_bad_repr_traceback(): + f = PlainTextFormatter() + bad = BadRepr() + with capture_output() as captured: + result = f(bad) + # catches error, returns None + assert result is None + assert "Traceback" in captured.stdout + assert "__repr__" in captured.stdout + assert "ValueError" in captured.stdout + + +class MakePDF(object): + def _repr_pdf_(self): + return 'PDF' + +def test_pdf_formatter(): + pdf = MakePDF() + f = PDFFormatter() + assert f(pdf) == "PDF" + + +def test_print_method_bound(): + f = HTMLFormatter() + class MyHTML(object): + def _repr_html_(self): + return "hello" + with capture_output() as captured: + result = f(MyHTML) + assert result is None + assert "FormatterWarning" not in captured.stderr + + with capture_output() as captured: + result = f(MyHTML()) + assert result == "hello" + assert captured.stderr == "" + + +def test_print_method_weird(): + + class TextMagicHat(object): + def __getattr__(self, key): + return key + + f = HTMLFormatter() + + text_hat = TextMagicHat() + assert text_hat._repr_html_ == "_repr_html_" + with capture_output() as captured: + result = f(text_hat) + + assert result is None + assert "FormatterWarning" not in captured.stderr + + class CallableMagicHat(object): + def __getattr__(self, key): + return lambda : key + + call_hat = CallableMagicHat() + with capture_output() as captured: + result = f(call_hat) + + assert result is None + + class BadReprArgs(object): + def _repr_html_(self, extra, args): + return "html" + + bad = BadReprArgs() + with capture_output() as captured: + result = f(bad) + + assert result is None + assert "FormatterWarning" not in captured.stderr + + +def test_format_config(): + """config objects don't pretend to support fancy reprs with lazy attrs""" + f = HTMLFormatter() + cfg = Config() + with capture_output() as captured: + result = f(cfg) + assert result is None + assert captured.stderr == "" + + with capture_output() as captured: + result = f(Config) + assert result is None + assert captured.stderr == "" + + +def test_pretty_max_seq_length(): + f = PlainTextFormatter(max_seq_length=1) + lis = list(range(3)) + text = f(lis) + assert text == "[0, ...]" + f.max_seq_length = 0 + text = f(lis) + assert text == "[0, 1, 2]" + text = f(list(range(1024))) + lines = text.splitlines() + assert len(lines) == 1024 + + +def test_ipython_display_formatter(): + """Objects with _ipython_display_ defined bypass other formatters""" + f = get_ipython().display_formatter + catcher = [] + class SelfDisplaying(object): + def _ipython_display_(self): + catcher.append(self) + + class NotSelfDisplaying(object): + def __repr__(self): + return "NotSelfDisplaying" + + def _ipython_display_(self): + raise NotImplementedError + + save_enabled = f.ipython_display_formatter.enabled + f.ipython_display_formatter.enabled = True + + yes = SelfDisplaying() + no = NotSelfDisplaying() + + d, md = f.format(no) + assert d == {"text/plain": repr(no)} + assert md == {} + assert catcher == [] + + d, md = f.format(yes) + assert d == {} + assert md == {} + assert catcher == [yes] + + f.ipython_display_formatter.enabled = save_enabled + + +def test_repr_mime(): + class HasReprMime(object): + def _repr_mimebundle_(self, include=None, exclude=None): + return { + 'application/json+test.v2': { + 'x': 'y' + }, + 'plain/text' : '', + 'image/png' : 'i-overwrite' + } + + def _repr_png_(self): + return 'should-be-overwritten' + def _repr_html_(self): + return 'hi!' + + f = get_ipython().display_formatter + html_f = f.formatters['text/html'] + save_enabled = html_f.enabled + html_f.enabled = True + obj = HasReprMime() + d, md = f.format(obj) + html_f.enabled = save_enabled + + assert sorted(d) == [ + "application/json+test.v2", + "image/png", + "plain/text", + "text/html", + "text/plain", + ] + assert md == {} + + d, md = f.format(obj, include={"image/png"}) + assert list(d.keys()) == [ + "image/png" + ], "Include should filter out even things from repr_mimebundle" + + assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence" + + +def test_pass_correct_include_exclude(): + class Tester(object): + + def __init__(self, include=None, exclude=None): + self.include = include + self.exclude = exclude + + def _repr_mimebundle_(self, include, exclude, **kwargs): + if include and (include != self.include): + raise ValueError('include got modified: display() may be broken.') + if exclude and (exclude != self.exclude): + raise ValueError('exclude got modified: display() may be broken.') + + return None + + include = {'a', 'b', 'c'} + exclude = {'c', 'e' , 'f'} + + f = get_ipython().display_formatter + f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude) + f.format(Tester(exclude=exclude), exclude=exclude) + f.format(Tester(include=include), include=include) + + +def test_repr_mime_meta(): + class HasReprMimeMeta(object): + def _repr_mimebundle_(self, include=None, exclude=None): + data = { + 'image/png': 'base64-image-data', + } + metadata = { + 'image/png': { + 'width': 5, + 'height': 10, + } + } + return (data, metadata) + + f = get_ipython().display_formatter + obj = HasReprMimeMeta() + d, md = f.format(obj) + assert sorted(d) == ["image/png", "text/plain"] + assert md == { + "image/png": { + "width": 5, + "height": 10, + } + } + + +def test_repr_mime_failure(): + class BadReprMime(object): + def _repr_mimebundle_(self, include=None, exclude=None): + raise RuntimeError + + f = get_ipython().display_formatter + obj = BadReprMime() + d, md = f.format(obj) + assert "text/plain" in d diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_handlers.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_handlers.py new file mode 100644 index 0000000..604dade --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_handlers.py @@ -0,0 +1,97 @@ +"""Tests for input handlers. +""" +#----------------------------------------------------------------------------- +# Module imports +#----------------------------------------------------------------------------- + +# our own packages +from IPython.core import autocall +from IPython.testing import tools as tt + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +# Get the public instance of IPython + +failures = [] +num_tests = 0 + +#----------------------------------------------------------------------------- +# Test functions +#----------------------------------------------------------------------------- + +class CallableIndexable(object): + def __getitem__(self, idx): return True + def __call__(self, *args, **kws): return True + + +class Autocallable(autocall.IPyAutocall): + def __call__(self): + return "called" + + +def run(tests): + """Loop through a list of (pre, post) inputs, where pre is the string + handed to ipython, and post is how that string looks after it's been + transformed (i.e. ipython's notion of _i)""" + tt.check_pairs(ip.prefilter_manager.prefilter_lines, tests) + + +def test_handlers(): + call_idx = CallableIndexable() + ip.user_ns['call_idx'] = call_idx + + # For many of the below, we're also checking that leading whitespace + # turns off the esc char, which it should unless there is a continuation + # line. + run( + [('"no change"', '"no change"'), # normal + (u"lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic + #("a = b # PYTHON-MODE", '_i'), # emacs -- avoids _in cache + ]) + + # Objects which are instances of IPyAutocall are *always* autocalled + autocallable = Autocallable() + ip.user_ns['autocallable'] = autocallable + + # auto + ip.run_line_magic("autocall", "0") + # Only explicit escapes or instances of IPyAutocallable should get + # expanded + run( + [ + ('len "abc"', 'len "abc"'), + ("autocallable", "autocallable()"), + # Don't add extra brackets (gh-1117) + ("autocallable()", "autocallable()"), + ] + ) + ip.run_line_magic("autocall", "1") + run( + [ + ('len "abc"', 'len("abc")'), + ('len "abc";', 'len("abc");'), # ; is special -- moves out of parens + # Autocall is turned off if first arg is [] and the object + # is both callable and indexable. Like so: + ("len [1,2]", "len([1,2])"), # len doesn't support __getitem__... + ("call_idx [1]", "call_idx [1]"), # call_idx *does*.. + ("call_idx 1", "call_idx(1)"), + ("len", "len"), # only at 2 does it auto-call on single args + ] + ) + ip.run_line_magic("autocall", "2") + run( + [ + ('len "abc"', 'len("abc")'), + ('len "abc";', 'len("abc");'), + ("len [1,2]", "len([1,2])"), + ("call_idx [1]", "call_idx [1]"), + ("call_idx 1", "call_idx(1)"), + # This is what's different: + ("len", "len()"), # only at 2 does it auto-call on single args + ] + ) + ip.run_line_magic("autocall", "1") + + assert failures == [] diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_history.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_history.py new file mode 100644 index 0000000..73d50c8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_history.py @@ -0,0 +1,229 @@ +# coding: utf-8 +"""Tests for the IPython tab-completion machinery. +""" +#----------------------------------------------------------------------------- +# Module imports +#----------------------------------------------------------------------------- + +# stdlib +import io +import sqlite3 +import sys +import tempfile +from datetime import datetime +from pathlib import Path + +from tempfile import TemporaryDirectory +# our own packages +from traitlets.config.loader import Config + +from IPython.core.history import HistoryManager, extract_hist_ranges + + +def test_proper_default_encoding(): + assert sys.getdefaultencoding() == "utf-8" + +def test_history(): + ip = get_ipython() + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + hist_manager_ori = ip.history_manager + hist_file = tmp_path / "history.sqlite" + try: + ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file) + hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"] + for i, h in enumerate(hist, start=1): + ip.history_manager.store_inputs(i, h) + + ip.history_manager.db_log_output = True + # Doesn't match the input, but we'll just check it's stored. + ip.history_manager.output_hist_reprs[3] = "spam" + ip.history_manager.store_output(3) + + assert ip.history_manager.input_hist_raw == [""] + hist + + # Detailed tests for _get_range_session + grs = ip.history_manager._get_range_session + assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1])) + assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:])) + assert list(grs(output=True)) == list( + zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"])) + ) + + # Check whether specifying a range beyond the end of the current + # session results in an error (gh-804) + ip.run_line_magic("hist", "2-500") + + # Check that we can write non-ascii characters to a file + ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1")) + ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2")) + ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3")) + ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4")) + + # New session + ip.history_manager.reset() + newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"] + for i, cmd in enumerate(newcmds, start=1): + ip.history_manager.store_inputs(i, cmd) + gothist = ip.history_manager.get_range(start=1, stop=4) + assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds)) + # Previous session: + gothist = ip.history_manager.get_range(-1, 1, 4) + assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist)) + + newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)] + + # Check get_hist_tail + gothist = ip.history_manager.get_tail(5, output=True, + include_latest=True) + expected = [(1, 3, (hist[-1], "spam"))] \ + + [(s, n, (c, None)) for (s, n, c) in newhist] + assert list(gothist) == expected + + gothist = ip.history_manager.get_tail(2) + expected = newhist[-3:-1] + assert list(gothist) == expected + + # Check get_hist_search + + gothist = ip.history_manager.search("*test*") + assert list(gothist) == [(1, 2, hist[1])] + + gothist = ip.history_manager.search("*=*") + assert list(gothist) == [ + (1, 1, hist[0]), + (1, 2, hist[1]), + (1, 3, hist[2]), + newhist[0], + newhist[2], + newhist[3], + ] + + gothist = ip.history_manager.search("*=*", n=4) + assert list(gothist) == [ + (1, 3, hist[2]), + newhist[0], + newhist[2], + newhist[3], + ] + + gothist = ip.history_manager.search("*=*", unique=True) + assert list(gothist) == [ + (1, 1, hist[0]), + (1, 2, hist[1]), + (1, 3, hist[2]), + newhist[2], + newhist[3], + ] + + gothist = ip.history_manager.search("*=*", unique=True, n=3) + assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]] + + gothist = ip.history_manager.search("b*", output=True) + assert list(gothist) == [(1, 3, (hist[2], "spam"))] + + # Cross testing: check that magic %save can get previous session. + testfilename = (tmp_path / "test.py").resolve() + ip.run_line_magic("save", str(testfilename) + " ~1/1-3") + with io.open(testfilename, encoding="utf-8") as testfile: + assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n" + + # Duplicate line numbers - check that it doesn't crash, and + # gets a new session + ip.history_manager.store_inputs(1, "rogue") + ip.history_manager.writeout_cache() + assert ip.history_manager.session_number == 3 + + # Check that session and line values are not just max values + sessid, lineno, entry = newhist[-1] + assert lineno > 1 + ip.history_manager.reset() + lineno = 1 + ip.history_manager.store_inputs(lineno, entry) + gothist = ip.history_manager.search("*=*", unique=True) + hist = list(gothist)[-1] + assert sessid < hist[0] + assert hist[1:] == (lineno, entry) + finally: + # Ensure saving thread is shut down before we try to clean up the files + ip.history_manager.save_thread.stop() + # Forcibly close database rather than relying on garbage collection + ip.history_manager.db.close() + # Restore history manager + ip.history_manager = hist_manager_ori + + +def test_extract_hist_ranges(): + instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/" + expected = [(0, 1, 2), # 0 == current session + (2, 3, 4), + (-4, 5, 7), + (-4, 7, 10), + (-9, 2, None), # None == to end + (-8, 1, None), + (-7, 1, 6), + (-10, 1, None)] + actual = list(extract_hist_ranges(instr)) + assert actual == expected + + +def test_extract_hist_ranges_empty_str(): + instr = "" + expected = [(0, 1, None)] # 0 == current session, None == to end + actual = list(extract_hist_ranges(instr)) + assert actual == expected + + +def test_magic_rerun(): + """Simple test for %rerun (no args -> rerun last line)""" + ip = get_ipython() + ip.run_cell("a = 10", store_history=True) + ip.run_cell("a += 1", store_history=True) + assert ip.user_ns["a"] == 11 + ip.run_cell("%rerun", store_history=True) + assert ip.user_ns["a"] == 12 + +def test_timestamp_type(): + ip = get_ipython() + info = ip.history_manager.get_session_info() + assert isinstance(info[1], datetime) + +def test_hist_file_config(): + cfg = Config() + tfile = tempfile.NamedTemporaryFile(delete=False) + cfg.HistoryManager.hist_file = Path(tfile.name) + try: + hm = HistoryManager(shell=get_ipython(), config=cfg) + assert hm.hist_file == cfg.HistoryManager.hist_file + finally: + try: + Path(tfile.name).unlink() + except OSError: + # same catch as in testing.tools.TempFileMixin + # On Windows, even though we close the file, we still can't + # delete it. I have no clue why + pass + +def test_histmanager_disabled(): + """Ensure that disabling the history manager doesn't create a database.""" + cfg = Config() + cfg.HistoryAccessor.enabled = False + + ip = get_ipython() + with TemporaryDirectory() as tmpdir: + hist_manager_ori = ip.history_manager + hist_file = Path(tmpdir) / "history.sqlite" + cfg.HistoryManager.hist_file = hist_file + try: + ip.history_manager = HistoryManager(shell=ip, config=cfg) + hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"] + for i, h in enumerate(hist, start=1): + ip.history_manager.store_inputs(i, h) + assert ip.history_manager.input_hist_raw == [""] + hist + ip.history_manager.reset() + ip.history_manager.end_session() + finally: + ip.history_manager = hist_manager_ori + + # hist_file should not be created + assert hist_file.exists() is False diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_hooks.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_hooks.py new file mode 100644 index 0000000..6e0b1c1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_hooks.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +"""Tests for CommandChainDispatcher.""" + + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import pytest +from IPython.core.error import TryNext +from IPython.core.hooks import CommandChainDispatcher + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + +# Define two classes, one which succeeds and one which raises TryNext. Each +# sets the attribute `called` to True when it is called. +class Okay(object): + def __init__(self, message): + self.message = message + self.called = False + def __call__(self): + self.called = True + return self.message + +class Fail(object): + def __init__(self, message): + self.message = message + self.called = False + def __call__(self): + self.called = True + raise TryNext(self.message) + +#----------------------------------------------------------------------------- +# Test functions +#----------------------------------------------------------------------------- + +def test_command_chain_dispatcher_ff(): + """Test two failing hooks""" + fail1 = Fail("fail1") + fail2 = Fail("fail2") + dp = CommandChainDispatcher([(0, fail1), (10, fail2)]) + + with pytest.raises(TryNext) as e: + dp() + assert str(e.value) == "fail2" + + assert fail1.called is True + assert fail2.called is True + +def test_command_chain_dispatcher_fofo(): + """Test a mixture of failing and succeeding hooks.""" + fail1 = Fail("fail1") + fail2 = Fail("fail2") + okay1 = Okay("okay1") + okay2 = Okay("okay2") + + dp = CommandChainDispatcher([(0, fail1), + # (5, okay1), # add this later + (10, fail2), + (15, okay2)]) + dp.add(okay1, 5) + + assert dp() == "okay1" + + assert fail1.called is True + assert okay1.called is True + assert fail2.called is False + assert okay2.called is False + +def test_command_chain_dispatcher_eq_priority(): + okay1 = Okay(u'okay1') + okay2 = Okay(u'okay2') + dp = CommandChainDispatcher([(1, okay1)]) + dp.add(okay2, 1) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_imports.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_imports.py new file mode 100644 index 0000000..7aa278f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_imports.py @@ -0,0 +1,52 @@ +# encoding: utf-8 + +def test_import_completer(): + from IPython.core import completer + +def test_import_crashhandler(): + from IPython.core import crashhandler + +def test_import_debugger(): + from IPython.core import debugger + +def test_import_excolors(): + from IPython.core import excolors + +def test_import_history(): + from IPython.core import history + +def test_import_hooks(): + from IPython.core import hooks + +def test_import_getipython(): + from IPython.core import getipython + +def test_import_interactiveshell(): + from IPython.core import interactiveshell + +def test_import_logger(): + from IPython.core import logger + +def test_import_macro(): + from IPython.core import macro + +def test_import_magic(): + from IPython.core import magic + +def test_import_oinspect(): + from IPython.core import oinspect + +def test_import_prefilter(): + from IPython.core import prefilter + +def test_import_prompts(): + from IPython.core import prompts + +def test_import_release(): + from IPython.core import release + +def test_import_ultratb(): + from IPython.core import ultratb + +def test_import_usage(): + from IPython.core import usage diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputsplitter.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputsplitter.py new file mode 100644 index 0000000..5a22895 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputsplitter.py @@ -0,0 +1,642 @@ +# -*- coding: utf-8 -*- +"""Tests for the inputsplitter module.""" + + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import unittest +import pytest +import sys + +with pytest.warns(DeprecationWarning, match="inputsplitter"): + from IPython.core import inputsplitter as isp +from IPython.core.inputtransformer import InputTransformer +from IPython.core.tests.test_inputtransformer import syntax, syntax_ml +from IPython.testing import tools as tt + +#----------------------------------------------------------------------------- +# Semi-complete examples (also used as tests) +#----------------------------------------------------------------------------- + +# Note: at the bottom, there's a slightly more complete version of this that +# can be useful during development of code here. + +def mini_interactive_loop(input_func): + """Minimal example of the logic of an interactive interpreter loop. + + This serves as an example, and it is used by the test system with a fake + raw_input that simulates interactive input.""" + + from IPython.core.inputsplitter import InputSplitter + + isp = InputSplitter() + # In practice, this input loop would be wrapped in an outside loop to read + # input indefinitely, until some exit/quit command was issued. Here we + # only illustrate the basic inner loop. + while isp.push_accepts_more(): + indent = ' '*isp.get_indent_spaces() + prompt = '>>> ' + indent + line = indent + input_func(prompt) + isp.push(line) + + # Here we just return input so we can use it in a test suite, but a real + # interpreter would instead send it for execution somewhere. + src = isp.source_reset() + #print 'Input source was:\n', src # dbg + return src + +#----------------------------------------------------------------------------- +# Test utilities, just for local use +#----------------------------------------------------------------------------- + + +def pseudo_input(lines): + """Return a function that acts like raw_input but feeds the input list.""" + ilines = iter(lines) + def raw_in(prompt): + try: + return next(ilines) + except StopIteration: + return '' + return raw_in + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- +def test_spaces(): + tests = [('', 0), + (' ', 1), + ('\n', 0), + (' \n', 1), + ('x', 0), + (' x', 1), + (' x',2), + (' x',4), + # Note: tabs are counted as a single whitespace! + ('\tx', 1), + ('\t x', 2), + ] + tt.check_pairs(isp.num_ini_spaces, tests) + + +def test_remove_comments(): + tests = [('text', 'text'), + ('text # comment', 'text '), + ('text # comment\n', 'text \n'), + ('text # comment \n', 'text \n'), + ('line # c \nline\n','line \nline\n'), + ('line # c \nline#c2 \nline\nline #c\n\n', + 'line \nline\nline\nline \n\n'), + ] + tt.check_pairs(isp.remove_comments, tests) + + +def test_get_input_encoding(): + encoding = isp.get_input_encoding() + assert isinstance(encoding, str) + # simple-minded check that at least encoding a simple string works with the + # encoding we got. + assert "test".encode(encoding) == b"test" + + +class NoInputEncodingTestCase(unittest.TestCase): + def setUp(self): + self.old_stdin = sys.stdin + class X: pass + fake_stdin = X() + sys.stdin = fake_stdin + + def test(self): + # Verify that if sys.stdin has no 'encoding' attribute we do the right + # thing + enc = isp.get_input_encoding() + self.assertEqual(enc, 'ascii') + + def tearDown(self): + sys.stdin = self.old_stdin + + +class InputSplitterTestCase(unittest.TestCase): + def setUp(self): + self.isp = isp.InputSplitter() + + def test_reset(self): + isp = self.isp + isp.push('x=1') + isp.reset() + self.assertEqual(isp._buffer, []) + self.assertEqual(isp.get_indent_spaces(), 0) + self.assertEqual(isp.source, '') + self.assertEqual(isp.code, None) + self.assertEqual(isp._is_complete, False) + + def test_source(self): + self.isp._store('1') + self.isp._store('2') + self.assertEqual(self.isp.source, '1\n2\n') + self.assertEqual(len(self.isp._buffer)>0, True) + self.assertEqual(self.isp.source_reset(), '1\n2\n') + self.assertEqual(self.isp._buffer, []) + self.assertEqual(self.isp.source, '') + + def test_indent(self): + isp = self.isp # shorthand + isp.push('x=1') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('y=2\n') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_indent2(self): + isp = self.isp + isp.push('if 1:') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push(' x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + # Blank lines shouldn't change the indent level + isp.push(' '*2) + self.assertEqual(isp.get_indent_spaces(), 4) + + def test_indent3(self): + isp = self.isp + # When a multiline statement contains parens or multiline strings, we + # shouldn't get confused. + isp.push("if 1:") + isp.push(" x = (1+\n 2)") + self.assertEqual(isp.get_indent_spaces(), 4) + + def test_indent4(self): + isp = self.isp + # whitespace after ':' should not screw up indent level + isp.push('if 1: \n x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('y=2\n') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\t\n x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('y=2\n') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_pass(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('if 1:\n passes = 5') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('if 1:\n pass') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n pass ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_break(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('while 1:\n breaks = 5') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('while 1:\n break') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('while 1:\n break ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_continue(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('while 1:\n continues = 5') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('while 1:\n continue') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('while 1:\n continue ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_raise(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('if 1:\n raised = 4') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('if 1:\n raise TypeError()') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n raise') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n raise ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_return(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('if 1:\n returning = 4') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('if 1:\n return 5 + 493') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n return') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n return ') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n return(0)') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_push(self): + isp = self.isp + self.assertEqual(isp.push('x=1'), True) + + def test_push2(self): + isp = self.isp + self.assertEqual(isp.push('if 1:'), False) + for line in [' x=1', '# a comment', ' y=2']: + print(line) + self.assertEqual(isp.push(line), True) + + def test_push3(self): + isp = self.isp + isp.push('if True:') + isp.push(' a = 1') + self.assertEqual(isp.push('b = [1,'), False) + + def test_push_accepts_more(self): + isp = self.isp + isp.push('x=1') + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more2(self): + isp = self.isp + isp.push('if 1:') + self.assertEqual(isp.push_accepts_more(), True) + isp.push(' x=1') + self.assertEqual(isp.push_accepts_more(), True) + isp.push('') + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more3(self): + isp = self.isp + isp.push("x = (2+\n3)") + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more4(self): + isp = self.isp + # When a multiline statement contains parens or multiline strings, we + # shouldn't get confused. + # FIXME: we should be able to better handle de-dents in statements like + # multiline strings and multiline expressions (continued with \ or + # parens). Right now we aren't handling the indentation tracking quite + # correctly with this, though in practice it may not be too much of a + # problem. We'll need to see. + isp.push("if 1:") + isp.push(" x = (2+") + isp.push(" 3)") + self.assertEqual(isp.push_accepts_more(), True) + isp.push(" y = 3") + self.assertEqual(isp.push_accepts_more(), True) + isp.push('') + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more5(self): + isp = self.isp + isp.push('try:') + isp.push(' a = 5') + isp.push('except:') + isp.push(' raise') + # We want to be able to add an else: block at this point, so it should + # wait for a blank line. + self.assertEqual(isp.push_accepts_more(), True) + + def test_continuation(self): + isp = self.isp + isp.push("import os, \\") + self.assertEqual(isp.push_accepts_more(), True) + isp.push("sys") + self.assertEqual(isp.push_accepts_more(), False) + + def test_syntax_error(self): + isp = self.isp + # Syntax errors immediately produce a 'ready' block, so the invalid + # Python can be sent to the kernel for evaluation with possible ipython + # special-syntax conversion. + isp.push('run foo') + self.assertEqual(isp.push_accepts_more(), False) + + def test_unicode(self): + self.isp.push(u"Pérez") + self.isp.push(u'\xc3\xa9') + self.isp.push(u"u'\xc3\xa9'") + + @pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, + ) + def test_line_continuation(self): + """ Test issue #2108.""" + isp = self.isp + # A blank line after a line continuation should not accept more + isp.push("1 \\\n\n") + self.assertEqual(isp.push_accepts_more(), False) + # Whitespace after a \ is a SyntaxError. The only way to test that + # here is to test that push doesn't accept more (as with + # test_syntax_error() above). + isp.push(r"1 \ ") + self.assertEqual(isp.push_accepts_more(), False) + # Even if the line is continuable (c.f. the regular Python + # interpreter) + isp.push(r"(1 \ ") + self.assertEqual(isp.push_accepts_more(), False) + + def test_check_complete(self): + isp = self.isp + self.assertEqual(isp.check_complete("a = 1"), ('complete', None)) + self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4)) + self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None)) + self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0)) + self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None)) + +class InteractiveLoopTestCase(unittest.TestCase): + """Tests for an interactive loop like a python shell. + """ + def check_ns(self, lines, ns): + """Validate that the given input lines produce the resulting namespace. + + Note: the input lines are given exactly as they would be typed in an + auto-indenting environment, as mini_interactive_loop above already does + auto-indenting and prepends spaces to the input. + """ + src = mini_interactive_loop(pseudo_input(lines)) + test_ns = {} + exec(src, test_ns) + # We can't check that the provided ns is identical to the test_ns, + # because Python fills test_ns with extra keys (copyright, etc). But + # we can check that the given dict is *contained* in test_ns + for k,v in ns.items(): + self.assertEqual(test_ns[k], v) + + def test_simple(self): + self.check_ns(['x=1'], dict(x=1)) + + def test_simple2(self): + self.check_ns(['if 1:', 'x=2'], dict(x=2)) + + def test_xy(self): + self.check_ns(['x=1; y=2'], dict(x=1, y=2)) + + def test_abc(self): + self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3)) + + def test_multi(self): + self.check_ns(['x =(1+','1+','2)'], dict(x=4)) + + +class IPythonInputTestCase(InputSplitterTestCase): + """By just creating a new class whose .isp is a different instance, we + re-run the same test battery on the new input splitter. + + In addition, this runs the tests over the syntax and syntax_ml dicts that + were tested by individual functions, as part of the OO interface. + + It also makes some checks on the raw buffer storage. + """ + + def setUp(self): + self.isp = isp.IPythonInputSplitter() + + def test_syntax(self): + """Call all single-line syntax tests from the main object""" + isp = self.isp + for example in syntax.values(): + for raw, out_t in example: + if raw.startswith(' '): + continue + + isp.push(raw+'\n') + out_raw = isp.source_raw + out = isp.source_reset() + self.assertEqual(out.rstrip(), out_t, + tt.pair_fail_msg.format("inputsplitter",raw, out_t, out)) + self.assertEqual(out_raw.rstrip(), raw.rstrip()) + + def test_syntax_multiline(self): + isp = self.isp + for example in syntax_ml.values(): + for line_pairs in example: + out_t_parts = [] + raw_parts = [] + for lraw, out_t_part in line_pairs: + if out_t_part is not None: + out_t_parts.append(out_t_part) + + if lraw is not None: + isp.push(lraw) + raw_parts.append(lraw) + + out_raw = isp.source_raw + out = isp.source_reset() + out_t = '\n'.join(out_t_parts).rstrip() + raw = '\n'.join(raw_parts).rstrip() + self.assertEqual(out.rstrip(), out_t) + self.assertEqual(out_raw.rstrip(), raw) + + def test_syntax_multiline_cell(self): + isp = self.isp + for example in syntax_ml.values(): + + out_t_parts = [] + for line_pairs in example: + raw = '\n'.join(r for r, _ in line_pairs if r is not None) + out_t = '\n'.join(t for _,t in line_pairs if t is not None) + out = isp.transform_cell(raw) + # Match ignoring trailing whitespace + self.assertEqual(out.rstrip(), out_t.rstrip()) + + def test_cellmagic_preempt(self): + isp = self.isp + for raw, name, line, cell in [ + ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'), + ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'), + (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'), + ("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'), + ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'), + ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'), + ]: + expected = "get_ipython().run_cell_magic(%r, %r, %r)" % ( + name, line, cell + ) + out = isp.transform_cell(raw) + self.assertEqual(out.rstrip(), expected.rstrip()) + + def test_multiline_passthrough(self): + isp = self.isp + class CommentTransformer(InputTransformer): + def __init__(self): + self._lines = [] + + def push(self, line): + self._lines.append(line + '#') + + def reset(self): + text = '\n'.join(self._lines) + self._lines = [] + return text + + isp.physical_line_transforms.insert(0, CommentTransformer()) + + for raw, expected in [ + ("a=5", "a=5#"), + ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')), + ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % ( + u'ls foo#', u'ls', u'bar#' + )), + ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')), + ]: + out = isp.transform_cell(raw) + self.assertEqual(out.rstrip(), expected.rstrip()) + +#----------------------------------------------------------------------------- +# Main - use as a script, mostly for developer experiments +#----------------------------------------------------------------------------- + +if __name__ == '__main__': + # A simple demo for interactive experimentation. This code will not get + # picked up by any test suite. + from IPython.core.inputsplitter import IPythonInputSplitter + + # configure here the syntax to use, prompt and whether to autoindent + #isp, start_prompt = InputSplitter(), '>>> ' + isp, start_prompt = IPythonInputSplitter(), 'In> ' + + autoindent = True + #autoindent = False + + try: + while True: + prompt = start_prompt + while isp.push_accepts_more(): + indent = ' '*isp.get_indent_spaces() + if autoindent: + line = indent + input(prompt+indent) + else: + line = input(prompt) + isp.push(line) + prompt = '... ' + + # Here we just return input so we can use it in a test suite, but a + # real interpreter would instead send it for execution somewhere. + #src = isp.source; raise EOFError # dbg + raw = isp.source_raw + src = isp.source_reset() + print('Input source was:\n', src) + print('Raw source was:\n', raw) + except EOFError: + print('Bye') + +# Tests for cell magics support + +def test_last_blank(): + assert isp.last_blank("") is False + assert isp.last_blank("abc") is False + assert isp.last_blank("abc\n") is False + assert isp.last_blank("abc\na") is False + + assert isp.last_blank("\n") is True + assert isp.last_blank("\n ") is True + assert isp.last_blank("abc\n ") is True + assert isp.last_blank("abc\n\n") is True + assert isp.last_blank("abc\nd\n\n") is True + assert isp.last_blank("abc\nd\ne\n\n") is True + assert isp.last_blank("abc \n \n \n\n") is True + + +def test_last_two_blanks(): + assert isp.last_two_blanks("") is False + assert isp.last_two_blanks("abc") is False + assert isp.last_two_blanks("abc\n") is False + assert isp.last_two_blanks("abc\n\na") is False + assert isp.last_two_blanks("abc\n \n") is False + assert isp.last_two_blanks("abc\n\n") is False + + assert isp.last_two_blanks("\n\n") is True + assert isp.last_two_blanks("\n\n ") is True + assert isp.last_two_blanks("\n \n") is True + assert isp.last_two_blanks("abc\n\n ") is True + assert isp.last_two_blanks("abc\n\n\n") is True + assert isp.last_two_blanks("abc\n\n \n") is True + assert isp.last_two_blanks("abc\n\n \n ") is True + assert isp.last_two_blanks("abc\n\n \n \n") is True + assert isp.last_two_blanks("abc\nd\n\n\n") is True + assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True + + +class CellMagicsCommon(object): + + def test_whole_cell(self): + src = "%%cellm line\nbody\n" + out = self.sp.transform_cell(src) + ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n" + assert out == ref + + def test_cellmagic_help(self): + self.sp.push('%%cellm?') + assert self.sp.push_accepts_more() is False + + def tearDown(self): + self.sp.reset() + + +class CellModeCellMagics(CellMagicsCommon, unittest.TestCase): + sp = isp.IPythonInputSplitter(line_input_checker=False) + + def test_incremental(self): + sp = self.sp + sp.push("%%cellm firstline\n") + assert sp.push_accepts_more() is True # 1 + sp.push("line2\n") + assert sp.push_accepts_more() is True # 2 + sp.push("\n") + # This should accept a blank line and carry on until the cell is reset + assert sp.push_accepts_more() is True # 3 + + def test_no_strip_coding(self): + src = '\n'.join([ + '%%writefile foo.py', + '# coding: utf-8', + 'print(u"üñîçø∂é")', + ]) + out = self.sp.transform_cell(src) + assert "# coding: utf-8" in out + + +class LineModeCellMagics(CellMagicsCommon, unittest.TestCase): + sp = isp.IPythonInputSplitter(line_input_checker=True) + + def test_incremental(self): + sp = self.sp + sp.push("%%cellm line2\n") + assert sp.push_accepts_more() is True # 1 + sp.push("\n") + # In this case, a blank line should end the cell magic + assert sp.push_accepts_more() is False # 2 + + +indentation_samples = [ + ('a = 1', 0), + ('for a in b:', 4), + ('def f():', 4), + ('def f(): #comment', 4), + ('a = ":#not a comment"', 0), + ('def f():\n a = 1', 4), + ('def f():\n return 1', 0), + ('for a in b:\n' + ' if a < 0:' + ' continue', 3), + ('a = {', 4), + ('a = {\n' + ' 1,', 5), + ('b = """123', 0), + ('', 0), + ('def f():\n pass', 0), + ('class Bar:\n def f():\n pass', 4), + ('class Bar:\n def f():\n raise', 4), +] + +def test_find_next_indent(): + for code, exp in indentation_samples: + res = isp.find_next_indent(code) + msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code) + assert res == exp, msg diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer.py new file mode 100644 index 0000000..4de97b8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer.py @@ -0,0 +1,484 @@ +import tokenize + +from IPython.testing import tools as tt + +from IPython.core import inputtransformer as ipt + +def transform_and_reset(transformer): + transformer = transformer() + def transform(inp): + try: + return transformer.push(inp) + finally: + transformer.reset() + + return transform + +# Transformer tests +def transform_checker(tests, transformer, **kwargs): + """Utility to loop over test inputs""" + transformer = transformer(**kwargs) + try: + for inp, tr in tests: + if inp is None: + out = transformer.reset() + else: + out = transformer.push(inp) + assert out == tr + finally: + transformer.reset() + +# Data for all the syntax tests in the form of lists of pairs of +# raw/transformed input. We store it here as a global dict so that we can use +# it both within single-function tests and also to validate the behavior of the +# larger objects + +syntax = \ + dict(assign_system = + [('a =! ls', "a = get_ipython().getoutput('ls')"), + ('b = !ls', "b = get_ipython().getoutput('ls')"), + ('c= !ls', "c = get_ipython().getoutput('ls')"), + ('d == !ls', 'd == !ls'), # Invalid syntax, but we leave == alone. + ('x=1', 'x=1'), # normal input is unmodified + (' ',' '), # blank lines are kept intact + # Tuple unpacking + ("a, b = !echo 'a\\nb'", "a, b = get_ipython().getoutput(\"echo 'a\\\\nb'\")"), + ("a,= !echo 'a'", "a, = get_ipython().getoutput(\"echo 'a'\")"), + ("a, *bc = !echo 'a\\nb\\nc'", "a, *bc = get_ipython().getoutput(\"echo 'a\\\\nb\\\\nc'\")"), + # Tuple unpacking with regular Python expressions, not our syntax. + ("a, b = range(2)", "a, b = range(2)"), + ("a, = range(1)", "a, = range(1)"), + ("a, *bc = range(3)", "a, *bc = range(3)"), + ], + + assign_magic = + [('a =% who', "a = get_ipython().run_line_magic('who', '')"), + ('b = %who', "b = get_ipython().run_line_magic('who', '')"), + ('c= %ls', "c = get_ipython().run_line_magic('ls', '')"), + ('d == %ls', 'd == %ls'), # Invalid syntax, but we leave == alone. + ('x=1', 'x=1'), # normal input is unmodified + (' ',' '), # blank lines are kept intact + ("a, b = %foo", "a, b = get_ipython().run_line_magic('foo', '')"), + ], + + classic_prompt = + [('>>> x=1', 'x=1'), + ('x=1', 'x=1'), # normal input is unmodified + (' ', ' '), # blank lines are kept intact + ], + + ipy_prompt = + [('In [1]: x=1', 'x=1'), + ('x=1', 'x=1'), # normal input is unmodified + (' ',' '), # blank lines are kept intact + ], + + # Tests for the escape transformer to leave normal code alone + escaped_noesc = + [ (' ', ' '), + ('x=1', 'x=1'), + ], + + # System calls + escaped_shell = + [ ('!ls', "get_ipython().system('ls')"), + # Double-escape shell, this means to capture the output of the + # subprocess and return it + ('!!ls', "get_ipython().getoutput('ls')"), + ], + + # Help/object info + escaped_help = + [ ('?', 'get_ipython().show_usage()'), + ('?x1', "get_ipython().run_line_magic('pinfo', 'x1')"), + ('??x2', "get_ipython().run_line_magic('pinfo2', 'x2')"), + ('?a.*s', "get_ipython().run_line_magic('psearch', 'a.*s')"), + ('?%hist1', "get_ipython().run_line_magic('pinfo', '%hist1')"), + ('?%%hist2', "get_ipython().run_line_magic('pinfo', '%%hist2')"), + ('?abc = qwe', "get_ipython().run_line_magic('pinfo', 'abc')"), + ], + + end_help = + [ ('x3?', "get_ipython().run_line_magic('pinfo', 'x3')"), + ('x4??', "get_ipython().run_line_magic('pinfo2', 'x4')"), + ('%hist1?', "get_ipython().run_line_magic('pinfo', '%hist1')"), + ('%hist2??', "get_ipython().run_line_magic('pinfo2', '%hist2')"), + ('%%hist3?', "get_ipython().run_line_magic('pinfo', '%%hist3')"), + ('%%hist4??', "get_ipython().run_line_magic('pinfo2', '%%hist4')"), + ('π.foo?', "get_ipython().run_line_magic('pinfo', 'π.foo')"), + ('f*?', "get_ipython().run_line_magic('psearch', 'f*')"), + ('ax.*aspe*?', "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"), + ('a = abc?', "get_ipython().set_next_input('a = abc');" + "get_ipython().run_line_magic('pinfo', 'abc')"), + ('a = abc.qe??', "get_ipython().set_next_input('a = abc.qe');" + "get_ipython().run_line_magic('pinfo2', 'abc.qe')"), + ('a = *.items?', "get_ipython().set_next_input('a = *.items');" + "get_ipython().run_line_magic('psearch', '*.items')"), + ('plot(a?', "get_ipython().set_next_input('plot(a');" + "get_ipython().run_line_magic('pinfo', 'a')"), + ('a*2 #comment?', 'a*2 #comment?'), + ], + + # Explicit magic calls + escaped_magic = + [ ('%cd', "get_ipython().run_line_magic('cd', '')"), + ('%cd /home', "get_ipython().run_line_magic('cd', '/home')"), + # Backslashes need to be escaped. + ('%cd C:\\User', "get_ipython().run_line_magic('cd', 'C:\\\\User')"), + (' %magic', " get_ipython().run_line_magic('magic', '')"), + ], + + # Quoting with separate arguments + escaped_quote = + [ (',f', 'f("")'), + (',f x', 'f("x")'), + (' ,f y', ' f("y")'), + (',f a b', 'f("a", "b")'), + ], + + # Quoting with single argument + escaped_quote2 = + [ (';f', 'f("")'), + (';f x', 'f("x")'), + (' ;f y', ' f("y")'), + (';f a b', 'f("a b")'), + ], + + # Simply apply parens + escaped_paren = + [ ('/f', 'f()'), + ('/f x', 'f(x)'), + (' /f y', ' f(y)'), + ('/f a b', 'f(a, b)'), + ], + + # Check that we transform prompts before other transforms + mixed = + [ ('In [1]: %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"), + ('>>> %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"), + ('In [2]: !ls', "get_ipython().system('ls')"), + ('In [3]: abs?', "get_ipython().run_line_magic('pinfo', 'abs')"), + ('In [4]: b = %who', "b = get_ipython().run_line_magic('who', '')"), + ], + ) + +# multiline syntax examples. Each of these should be a list of lists, with +# each entry itself having pairs of raw/transformed input. The union (with +# '\n'.join() of the transformed inputs is what the splitter should produce +# when fed the raw lines one at a time via push. +syntax_ml = \ + dict(classic_prompt = + [ [('>>> for i in range(10):','for i in range(10):'), + ('... print i',' print i'), + ('... ', ''), + ], + [('>>> a="""','a="""'), + ('... 123"""','123"""'), + ], + [('a="""','a="""'), + ('... 123','123'), + ('... 456"""','456"""'), + ], + [('a="""','a="""'), + ('>>> 123','123'), + ('... 456"""','456"""'), + ], + [('a="""','a="""'), + ('123','123'), + ('... 456"""','... 456"""'), + ], + [('....__class__','....__class__'), + ], + [('a=5', 'a=5'), + ('...', ''), + ], + [('>>> def f(x):', 'def f(x):'), + ('...', ''), + ('... return x', ' return x'), + ], + [('board = """....', 'board = """....'), + ('....', '....'), + ('...."""', '...."""'), + ], + ], + + ipy_prompt = + [ [('In [24]: for i in range(10):','for i in range(10):'), + (' ....: print i',' print i'), + (' ....: ', ''), + ], + [('In [24]: for i in range(10):','for i in range(10):'), + # Qt console prompts expand with spaces, not dots + (' ...: print i',' print i'), + (' ...: ', ''), + ], + [('In [24]: for i in range(10):','for i in range(10):'), + # Sometimes whitespace preceding '...' has been removed + ('...: print i',' print i'), + ('...: ', ''), + ], + [('In [24]: for i in range(10):','for i in range(10):'), + # Space after last continuation prompt has been removed (issue #6674) + ('...: print i',' print i'), + ('...:', ''), + ], + [('In [2]: a="""','a="""'), + (' ...: 123"""','123"""'), + ], + [('a="""','a="""'), + (' ...: 123','123'), + (' ...: 456"""','456"""'), + ], + [('a="""','a="""'), + ('In [1]: 123','123'), + (' ...: 456"""','456"""'), + ], + [('a="""','a="""'), + ('123','123'), + (' ...: 456"""',' ...: 456"""'), + ], + ], + + multiline_datastructure_prompt = + [ [('>>> a = [1,','a = [1,'), + ('... 2]','2]'), + ], + ], + + multiline_datastructure = + [ [('b = ("%s"', None), + ('# comment', None), + ('%foo )', 'b = ("%s"\n# comment\n%foo )'), + ], + ], + + multiline_string = + [ [("'''foo?", None), + ("bar'''", "'''foo?\nbar'''"), + ], + ], + + leading_indent = + [ [(' print "hi"','print "hi"'), + ], + [(' for a in range(5):','for a in range(5):'), + (' a*2',' a*2'), + ], + [(' a="""','a="""'), + (' 123"""','123"""'), + ], + [('a="""','a="""'), + (' 123"""',' 123"""'), + ], + ], + + cellmagic = + [ [('%%foo a', None), + (None, "get_ipython().run_cell_magic('foo', 'a', '')"), + ], + [('%%bar 123', None), + ('hello', None), + (None , "get_ipython().run_cell_magic('bar', '123', 'hello')"), + ], + [('a=5', 'a=5'), + ('%%cellmagic', '%%cellmagic'), + ], + ], + + escaped = + [ [('%abc def \\', None), + ('ghi', "get_ipython().run_line_magic('abc', 'def ghi')"), + ], + [('%abc def \\', None), + ('ghi\\', None), + (None, "get_ipython().run_line_magic('abc', 'def ghi')"), + ], + ], + + assign_magic = + [ [('a = %bc de \\', None), + ('fg', "a = get_ipython().run_line_magic('bc', 'de fg')"), + ], + [('a = %bc de \\', None), + ('fg\\', None), + (None, "a = get_ipython().run_line_magic('bc', 'de fg')"), + ], + ], + + assign_system = + [ [('a = !bc de \\', None), + ('fg', "a = get_ipython().getoutput('bc de fg')"), + ], + [('a = !bc de \\', None), + ('fg\\', None), + (None, "a = get_ipython().getoutput('bc de fg')"), + ], + ], + ) + + +def test_assign_system(): + tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system']) + +def test_assign_magic(): + tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic']) + +def test_classic_prompt(): + tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt']) + for example in syntax_ml['classic_prompt']: + transform_checker(example, ipt.classic_prompt) + for example in syntax_ml['multiline_datastructure_prompt']: + transform_checker(example, ipt.classic_prompt) + + # Check that we don't transform the second line if the first is obviously + # IPython syntax + transform_checker([ + ('%foo', '%foo'), + ('>>> bar', '>>> bar'), + ], ipt.classic_prompt) + + +def test_ipy_prompt(): + tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt']) + for example in syntax_ml['ipy_prompt']: + transform_checker(example, ipt.ipy_prompt) + + # Check that we don't transform the second line if we're inside a cell magic + transform_checker([ + ('%%foo', '%%foo'), + ('In [1]: bar', 'In [1]: bar'), + ], ipt.ipy_prompt) + +def test_assemble_logical_lines(): + tests = \ + [ [("a = \\", None), + ("123", "a = 123"), + ], + [("a = \\", None), # Test resetting when within a multi-line string + ("12 *\\", None), + (None, "a = 12 *"), + ], + [("# foo\\", "# foo\\"), # Comments can't be continued like this + ], + ] + for example in tests: + transform_checker(example, ipt.assemble_logical_lines) + +def test_assemble_python_lines(): + tests = \ + [ [("a = '''", None), + ("abc'''", "a = '''\nabc'''"), + ], + [("a = '''", None), # Test resetting when within a multi-line string + ("def", None), + (None, "a = '''\ndef"), + ], + [("a = [1,", None), + ("2]", "a = [1,\n2]"), + ], + [("a = [1,", None), # Test resetting when within a multi-line string + ("2,", None), + (None, "a = [1,\n2,"), + ], + [("a = '''", None), # Test line continuation within a multi-line string + ("abc\\", None), + ("def", None), + ("'''", "a = '''\nabc\\\ndef\n'''"), + ], + ] + syntax_ml['multiline_datastructure'] + for example in tests: + transform_checker(example, ipt.assemble_python_lines) + + +def test_help_end(): + tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help']) + +def test_escaped_noesc(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc']) + + +def test_escaped_shell(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell']) + + +def test_escaped_help(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help']) + + +def test_escaped_magic(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic']) + + +def test_escaped_quote(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote']) + + +def test_escaped_quote2(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2']) + + +def test_escaped_paren(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren']) + + +def test_cellmagic(): + for example in syntax_ml['cellmagic']: + transform_checker(example, ipt.cellmagic) + + line_example = [('%%bar 123', None), + ('hello', None), + ('' , "get_ipython().run_cell_magic('bar', '123', 'hello')"), + ] + transform_checker(line_example, ipt.cellmagic, end_on_blank_line=True) + +def test_has_comment(): + tests = [('text', False), + ('text #comment', True), + ('text #comment\n', True), + ('#comment', True), + ('#comment\n', True), + ('a = "#string"', False), + ('a = "#string" # comment', True), + ('a #comment not "string"', True), + ] + tt.check_pairs(ipt.has_comment, tests) + +@ipt.TokenInputTransformer.wrap +def decistmt(tokens): + """Substitute Decimals for floats in a string of statements. + + Based on an example from the tokenize module docs. + """ + result = [] + for toknum, tokval, _, _, _ in tokens: + if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens + yield from [ + (tokenize.NAME, 'Decimal'), + (tokenize.OP, '('), + (tokenize.STRING, repr(tokval)), + (tokenize.OP, ')') + ] + else: + yield (toknum, tokval) + + + +def test_token_input_transformer(): + tests = [('1.2', "Decimal ('1.2')"), + ('"1.2"', '"1.2"'), + ] + tt.check_pairs(transform_and_reset(decistmt), tests) + ml_tests = \ + [ [("a = 1.2; b = '''x", None), + ("y'''", "a =Decimal ('1.2');b ='''x\ny'''"), + ], + [("a = [1.2,", None), + ("3]", "a =[Decimal ('1.2'),\n3 ]"), + ], + [("a = '''foo", None), # Test resetting when within a multi-line string + ("bar", None), + (None, "a = '''foo\nbar"), + ], + ] + for example in ml_tests: + transform_checker(example, decistmt) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer2.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer2.py new file mode 100644 index 0000000..abc6303 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer2.py @@ -0,0 +1,405 @@ +"""Tests for the token-based transformers in IPython.core.inputtransformer2 + +Line-based transformers are the simpler ones; token-based transformers are +more complex. See test_inputtransformer2_line for tests for line-based +transformations. +""" +import platform +import string +import sys +from textwrap import dedent + +import pytest + +from IPython.core import inputtransformer2 as ipt2 +from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line + +MULTILINE_MAGIC = ("""\ +a = f() +%foo \\ +bar +g() +""".splitlines(keepends=True), (2, 0), """\ +a = f() +get_ipython().run_line_magic('foo', ' bar') +g() +""".splitlines(keepends=True)) + +INDENTED_MAGIC = ("""\ +for a in range(5): + %ls +""".splitlines(keepends=True), (2, 4), """\ +for a in range(5): + get_ipython().run_line_magic('ls', '') +""".splitlines(keepends=True)) + +CRLF_MAGIC = ([ + "a = f()\n", + "%ls\r\n", + "g()\n" +], (2, 0), [ + "a = f()\n", + "get_ipython().run_line_magic('ls', '')\n", + "g()\n" +]) + +MULTILINE_MAGIC_ASSIGN = ("""\ +a = f() +b = %foo \\ + bar +g() +""".splitlines(keepends=True), (2, 4), """\ +a = f() +b = get_ipython().run_line_magic('foo', ' bar') +g() +""".splitlines(keepends=True)) + +MULTILINE_SYSTEM_ASSIGN = ("""\ +a = f() +b = !foo \\ + bar +g() +""".splitlines(keepends=True), (2, 4), """\ +a = f() +b = get_ipython().getoutput('foo bar') +g() +""".splitlines(keepends=True)) + +##### + +MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\ +def test(): + for i in range(1): + print(i) + res =! ls +""".splitlines(keepends=True), (4, 7), '''\ +def test(): + for i in range(1): + print(i) + res =get_ipython().getoutput(\' ls\') +'''.splitlines(keepends=True)) + +###### + +AUTOCALL_QUOTE = ( + [",f 1 2 3\n"], (1, 0), + ['f("1", "2", "3")\n'] +) + +AUTOCALL_QUOTE2 = ( + [";f 1 2 3\n"], (1, 0), + ['f("1 2 3")\n'] +) + +AUTOCALL_PAREN = ( + ["/f 1 2 3\n"], (1, 0), + ['f(1, 2, 3)\n'] +) + +SIMPLE_HELP = ( + ["foo?\n"], (1, 0), + ["get_ipython().run_line_magic('pinfo', 'foo')\n"] +) + +DETAILED_HELP = ( + ["foo??\n"], (1, 0), + ["get_ipython().run_line_magic('pinfo2', 'foo')\n"] +) + +MAGIC_HELP = ( + ["%foo?\n"], (1, 0), + ["get_ipython().run_line_magic('pinfo', '%foo')\n"] +) + +HELP_IN_EXPR = ( + ["a = b + c?\n"], (1, 0), + ["get_ipython().set_next_input('a = b + c');" + "get_ipython().run_line_magic('pinfo', 'c')\n"] +) + +HELP_CONTINUED_LINE = ("""\ +a = \\ +zip? +""".splitlines(keepends=True), (1, 0), +[r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] +) + +HELP_MULTILINE = ("""\ +(a, +b) = zip? +""".splitlines(keepends=True), (1, 0), +[r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] +) + +HELP_UNICODE = ( + ["π.foo?\n"], (1, 0), + ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"] +) + + +def null_cleanup_transformer(lines): + """ + A cleanup transform that returns an empty list. + """ + return [] + + +def test_check_make_token_by_line_never_ends_empty(): + """ + Check that not sequence of single or double characters ends up leading to en empty list of tokens + """ + from string import printable + for c in printable: + assert make_tokens_by_line(c)[-1] != [] + for k in printable: + assert make_tokens_by_line(c + k)[-1] != [] + + +def check_find(transformer, case, match=True): + sample, expected_start, _ = case + tbl = make_tokens_by_line(sample) + res = transformer.find(tbl) + if match: + # start_line is stored 0-indexed, expected values are 1-indexed + assert (res.start_line + 1, res.start_col) == expected_start + return res + else: + assert res is None + +def check_transform(transformer_cls, case): + lines, start, expected = case + transformer = transformer_cls(start) + assert transformer.transform(lines) == expected + +def test_continued_line(): + lines = MULTILINE_MAGIC_ASSIGN[0] + assert ipt2.find_end_of_continued_line(lines, 1) == 2 + + assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar" + +def test_find_assign_magic(): + check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) + check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False) + check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False) + +def test_transform_assign_magic(): + check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) + +def test_find_assign_system(): + check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) + check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) + check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None)) + check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None)) + check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False) + +def test_transform_assign_system(): + check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) + check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) + +def test_find_magic_escape(): + check_find(ipt2.EscapedCommand, MULTILINE_MAGIC) + check_find(ipt2.EscapedCommand, INDENTED_MAGIC) + check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False) + +def test_transform_magic_escape(): + check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC) + check_transform(ipt2.EscapedCommand, INDENTED_MAGIC) + check_transform(ipt2.EscapedCommand, CRLF_MAGIC) + +def test_find_autocalls(): + for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]: + print("Testing %r" % case[0]) + check_find(ipt2.EscapedCommand, case) + +def test_transform_autocall(): + for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]: + print("Testing %r" % case[0]) + check_transform(ipt2.EscapedCommand, case) + +def test_find_help(): + for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]: + check_find(ipt2.HelpEnd, case) + + tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE) + assert tf.q_line == 1 + assert tf.q_col == 3 + + tf = check_find(ipt2.HelpEnd, HELP_MULTILINE) + assert tf.q_line == 1 + assert tf.q_col == 8 + + # ? in a comment does not trigger help + check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False) + # Nor in a string + check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False) + +def test_transform_help(): + tf = ipt2.HelpEnd((1, 0), (1, 9)) + assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2] + + tf = ipt2.HelpEnd((1, 0), (2, 3)) + assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2] + + tf = ipt2.HelpEnd((1, 0), (2, 8)) + assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2] + + tf = ipt2.HelpEnd((1, 0), (1, 0)) + assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2] + +def test_find_assign_op_dedent(): + """ + be careful that empty token like dedent are not counted as parens + """ + class Tk: + def __init__(self, s): + self.string = s + + assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2 + assert ( + _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6 + ) + + +examples = [ + pytest.param("a = 1", "complete", None), + pytest.param("for a in range(5):", "incomplete", 4), + pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8), + pytest.param("raise = 2", "invalid", None), + pytest.param("a = [1,\n2,", "incomplete", 0), + pytest.param("(\n))", "incomplete", 0), + pytest.param("\\\r\n", "incomplete", 0), + pytest.param("a = '''\n hi", "incomplete", 3), + pytest.param("def a():\n x=1\n global x", "invalid", None), + pytest.param( + "a \\ ", + "invalid", + None, + marks=pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info + in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, + ), + ), # Nothing allowed after backslash, + pytest.param("1\\\n+2", "complete", None), +] + + +@pytest.mark.parametrize("code, expected, number", examples) +def test_check_complete_param(code, expected, number): + cc = ipt2.TransformerManager().check_complete + assert cc(code) == (expected, number) + + +@pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy") +@pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, +) +def test_check_complete(): + cc = ipt2.TransformerManager().check_complete + + example = dedent(""" + if True: + a=1""" ) + + assert cc(example) == ("incomplete", 4) + assert cc(example + "\n") == ("complete", None) + assert cc(example + "\n ") == ("complete", None) + + # no need to loop on all the letters/numbers. + short = '12abAB'+string.printable[62:] + for c in short: + # test does not raise: + cc(c) + for k in short: + cc(c+k) + + assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2) + + +@pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy") +@pytest.mark.parametrize( + "value, expected", + [ + ('''def foo():\n """''', ("incomplete", 4)), + ("""async with example:\n pass""", ("incomplete", 4)), + ("""async with example:\n pass\n """, ("complete", None)), + ], +) +def test_check_complete_II(value, expected): + """ + Test that multiple line strings are properly handled. + + Separate test function for convenience + + """ + cc = ipt2.TransformerManager().check_complete + assert cc(value) == expected + + +@pytest.mark.parametrize( + "value, expected", + [ + (")", ("invalid", None)), + ("]", ("invalid", None)), + ("}", ("invalid", None)), + (")(", ("invalid", None)), + ("][", ("invalid", None)), + ("}{", ("invalid", None)), + ("]()(", ("invalid", None)), + ("())(", ("invalid", None)), + (")[](", ("invalid", None)), + ("()](", ("invalid", None)), + ], +) +def test_check_complete_invalidates_sunken_brackets(value, expected): + """ + Test that a single line with more closing brackets than the opening ones is + interpreted as invalid + """ + cc = ipt2.TransformerManager().check_complete + assert cc(value) == expected + + +def test_null_cleanup_transformer(): + manager = ipt2.TransformerManager() + manager.cleanup_transforms.insert(0, null_cleanup_transformer) + assert manager.transform_cell("") == "" + + + + +def test_side_effects_I(): + count = 0 + def counter(lines): + nonlocal count + count += 1 + return lines + + counter.has_side_effects = True + + manager = ipt2.TransformerManager() + manager.cleanup_transforms.insert(0, counter) + assert manager.check_complete("a=1\n") == ('complete', None) + assert count == 0 + + + + +def test_side_effects_II(): + count = 0 + def counter(lines): + nonlocal count + count += 1 + return lines + + counter.has_side_effects = True + + manager = ipt2.TransformerManager() + manager.line_transforms.insert(0, counter) + assert manager.check_complete("b=1\n") == ('complete', None) + assert count == 0 diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer2_line.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer2_line.py new file mode 100644 index 0000000..30558fd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_inputtransformer2_line.py @@ -0,0 +1,168 @@ +"""Tests for the line-based transformers in IPython.core.inputtransformer2 + +Line-based transformers are the simpler ones; token-based transformers are +more complex. See test_inputtransformer2 for tests for token-based transformers. +""" +import pytest + +from IPython.core import inputtransformer2 as ipt2 + +CELL_MAGIC = ("""\ +%%foo arg +body 1 +body 2 +""", """\ +get_ipython().run_cell_magic('foo', 'arg', 'body 1\\nbody 2\\n') +""") + +def test_cell_magic(): + for sample, expected in [CELL_MAGIC]: + assert ipt2.cell_magic(sample.splitlines(keepends=True)) == expected.splitlines( + keepends=True + ) + +CLASSIC_PROMPT = ("""\ +>>> for a in range(5): +... print(a) +""", """\ +for a in range(5): + print(a) +""") + +CLASSIC_PROMPT_L2 = ("""\ +for a in range(5): +... print(a) +... print(a ** 2) +""", """\ +for a in range(5): + print(a) + print(a ** 2) +""") + +def test_classic_prompt(): + for sample, expected in [CLASSIC_PROMPT, CLASSIC_PROMPT_L2]: + assert ipt2.classic_prompt( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + +IPYTHON_PROMPT = ("""\ +In [1]: for a in range(5): + ...: print(a) +""", """\ +for a in range(5): + print(a) +""") + +IPYTHON_PROMPT_L2 = ("""\ +for a in range(5): + ...: print(a) + ...: print(a ** 2) +""", """\ +for a in range(5): + print(a) + print(a ** 2) +""") + + +IPYTHON_PROMPT_VI_INS = ( + """\ +[ins] In [11]: def a(): + ...: 123 + ...: + ...: 123 +""", + """\ +def a(): + 123 + +123 +""", +) + +IPYTHON_PROMPT_VI_NAV = ( + """\ +[nav] In [11]: def a(): + ...: 123 + ...: + ...: 123 +""", + """\ +def a(): + 123 + +123 +""", +) + + +def test_ipython_prompt(): + for sample, expected in [ + IPYTHON_PROMPT, + IPYTHON_PROMPT_L2, + IPYTHON_PROMPT_VI_INS, + IPYTHON_PROMPT_VI_NAV, + ]: + assert ipt2.ipython_prompt( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + + +INDENT_SPACES = ("""\ + if True: + a = 3 +""", """\ +if True: + a = 3 +""") + +INDENT_TABS = ("""\ +\tif True: +\t\tb = 4 +""", """\ +if True: +\tb = 4 +""") + +def test_leading_indent(): + for sample, expected in [INDENT_SPACES, INDENT_TABS]: + assert ipt2.leading_indent( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + +LEADING_EMPTY_LINES = ("""\ + \t + +if True: + a = 3 + +b = 4 +""", """\ +if True: + a = 3 + +b = 4 +""") + +ONLY_EMPTY_LINES = ("""\ + \t + +""", """\ + \t + +""") + +def test_leading_empty_lines(): + for sample, expected in [LEADING_EMPTY_LINES, ONLY_EMPTY_LINES]: + assert ipt2.leading_empty_lines( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + +CRLF_MAGIC = ([ + "%%ls\r\n" +], [ + "get_ipython().run_cell_magic('ls', '', '')\n" +]) + +def test_crlf_magic(): + for sample, expected in [CRLF_MAGIC]: + assert ipt2.cell_magic(sample) == expected diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_interactiveshell.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_interactiveshell.py new file mode 100644 index 0000000..10cce1f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_interactiveshell.py @@ -0,0 +1,1100 @@ +# -*- coding: utf-8 -*- +"""Tests for the key interactiveshell module. + +Historically the main classes in interactiveshell have been under-tested. This +module should grow as many single-method tests as possible to trap many of the +recurring bugs we seem to encounter with high-level interaction. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import asyncio +import ast +import os +import signal +import shutil +import sys +import tempfile +import unittest +from unittest import mock + +from os.path import join + +from IPython.core.error import InputRejected +from IPython.core.inputtransformer import InputTransformer +from IPython.core import interactiveshell +from IPython.testing.decorators import ( + skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist, +) +from IPython.testing import tools as tt +from IPython.utils.process import find_cmd + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- +# This is used by every single test, no point repeating it ad nauseam + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +class DerivedInterrupt(KeyboardInterrupt): + pass + +class InteractiveShellTestCase(unittest.TestCase): + def test_naked_string_cells(self): + """Test that cells with only naked strings are fully executed""" + # First, single-line inputs + ip.run_cell('"a"\n') + self.assertEqual(ip.user_ns['_'], 'a') + # And also multi-line cells + ip.run_cell('"""a\nb"""\n') + self.assertEqual(ip.user_ns['_'], 'a\nb') + + def test_run_empty_cell(self): + """Just make sure we don't get a horrible error with a blank + cell of input. Yes, I did overlook that.""" + old_xc = ip.execution_count + res = ip.run_cell('') + self.assertEqual(ip.execution_count, old_xc) + self.assertEqual(res.execution_count, None) + + def test_run_cell_multiline(self): + """Multi-block, multi-line cells must execute correctly. + """ + src = '\n'.join(["x=1", + "y=2", + "if 1:", + " x += 1", + " y += 1",]) + res = ip.run_cell(src) + self.assertEqual(ip.user_ns['x'], 2) + self.assertEqual(ip.user_ns['y'], 3) + self.assertEqual(res.success, True) + self.assertEqual(res.result, None) + + def test_multiline_string_cells(self): + "Code sprinkled with multiline strings should execute (GH-306)" + ip.run_cell('tmp=0') + self.assertEqual(ip.user_ns['tmp'], 0) + res = ip.run_cell('tmp=1;"""a\nb"""\n') + self.assertEqual(ip.user_ns['tmp'], 1) + self.assertEqual(res.success, True) + self.assertEqual(res.result, "a\nb") + + def test_dont_cache_with_semicolon(self): + "Ending a line with semicolon should not cache the returned object (GH-307)" + oldlen = len(ip.user_ns['Out']) + for cell in ['1;', '1;1;']: + res = ip.run_cell(cell, store_history=True) + newlen = len(ip.user_ns['Out']) + self.assertEqual(oldlen, newlen) + self.assertIsNone(res.result) + i = 0 + #also test the default caching behavior + for cell in ['1', '1;1']: + ip.run_cell(cell, store_history=True) + newlen = len(ip.user_ns['Out']) + i += 1 + self.assertEqual(oldlen+i, newlen) + + def test_syntax_error(self): + res = ip.run_cell("raise = 3") + self.assertIsInstance(res.error_before_exec, SyntaxError) + + def test_In_variable(self): + "Verify that In variable grows with user input (GH-284)" + oldlen = len(ip.user_ns['In']) + ip.run_cell('1;', store_history=True) + newlen = len(ip.user_ns['In']) + self.assertEqual(oldlen+1, newlen) + self.assertEqual(ip.user_ns['In'][-1],'1;') + + def test_magic_names_in_string(self): + ip.run_cell('a = """\n%exit\n"""') + self.assertEqual(ip.user_ns['a'], '\n%exit\n') + + def test_trailing_newline(self): + """test that running !(command) does not raise a SyntaxError""" + ip.run_cell('!(true)\n', False) + ip.run_cell('!(true)\n\n\n', False) + + def test_gh_597(self): + """Pretty-printing lists of objects with non-ascii reprs may cause + problems.""" + class Spam(object): + def __repr__(self): + return "\xe9"*50 + import IPython.core.formatters + f = IPython.core.formatters.PlainTextFormatter() + f([Spam(),Spam()]) + + + def test_future_flags(self): + """Check that future flags are used for parsing code (gh-777)""" + ip.run_cell('from __future__ import barry_as_FLUFL') + try: + ip.run_cell('prfunc_return_val = 1 <> 2') + assert 'prfunc_return_val' in ip.user_ns + finally: + # Reset compiler flags so we don't mess up other tests. + ip.compile.reset_compiler_flags() + + def test_can_pickle(self): + "Can we pickle objects defined interactively (GH-29)" + ip = get_ipython() + ip.reset() + ip.run_cell(("class Mylist(list):\n" + " def __init__(self,x=[]):\n" + " list.__init__(self,x)")) + ip.run_cell("w=Mylist([1,2,3])") + + from pickle import dumps + + # We need to swap in our main module - this is only necessary + # inside the test framework, because IPython puts the interactive module + # in place (but the test framework undoes this). + _main = sys.modules['__main__'] + sys.modules['__main__'] = ip.user_module + try: + res = dumps(ip.user_ns["w"]) + finally: + sys.modules['__main__'] = _main + self.assertTrue(isinstance(res, bytes)) + + def test_global_ns(self): + "Code in functions must be able to access variables outside them." + ip = get_ipython() + ip.run_cell("a = 10") + ip.run_cell(("def f(x):\n" + " return x + a")) + ip.run_cell("b = f(12)") + self.assertEqual(ip.user_ns["b"], 22) + + def test_bad_custom_tb(self): + """Check that InteractiveShell is protected from bad custom exception handlers""" + ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0) + self.assertEqual(ip.custom_exceptions, (IOError,)) + with tt.AssertPrints("Custom TB Handler failed", channel='stderr'): + ip.run_cell(u'raise IOError("foo")') + self.assertEqual(ip.custom_exceptions, ()) + + def test_bad_custom_tb_return(self): + """Check that InteractiveShell is protected from bad return types in custom exception handlers""" + ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1) + self.assertEqual(ip.custom_exceptions, (NameError,)) + with tt.AssertPrints("Custom TB Handler failed", channel='stderr'): + ip.run_cell(u'a=abracadabra') + self.assertEqual(ip.custom_exceptions, ()) + + def test_drop_by_id(self): + myvars = {"a":object(), "b":object(), "c": object()} + ip.push(myvars, interactive=False) + for name in myvars: + assert name in ip.user_ns, name + assert name in ip.user_ns_hidden, name + ip.user_ns['b'] = 12 + ip.drop_by_id(myvars) + for name in ["a", "c"]: + assert name not in ip.user_ns, name + assert name not in ip.user_ns_hidden, name + assert ip.user_ns['b'] == 12 + ip.reset() + + def test_var_expand(self): + ip.user_ns['f'] = u'Ca\xf1o' + self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o') + self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o') + self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1') + self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2') + + self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'") + + ip.user_ns['f'] = b'Ca\xc3\xb1o' + # This should not raise any exception: + ip.var_expand(u'echo $f') + + def test_var_expand_local(self): + """Test local variable expansion in !system and %magic calls""" + # !system + ip.run_cell( + "def test():\n" + ' lvar = "ttt"\n' + " ret = !echo {lvar}\n" + " return ret[0]\n" + ) + res = ip.user_ns["test"]() + self.assertIn("ttt", res) + + # %magic + ip.run_cell( + "def makemacro():\n" + ' macroname = "macro_var_expand_locals"\n' + " %macro {macroname} codestr\n" + ) + ip.user_ns["codestr"] = "str(12)" + ip.run_cell("makemacro()") + self.assertIn("macro_var_expand_locals", ip.user_ns) + + def test_var_expand_self(self): + """Test variable expansion with the name 'self', which was failing. + + See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218 + """ + ip.run_cell( + "class cTest:\n" + ' classvar="see me"\n' + " def test(self):\n" + " res = !echo Variable: {self.classvar}\n" + " return res[0]\n" + ) + self.assertIn("see me", ip.user_ns["cTest"]().test()) + + def test_bad_var_expand(self): + """var_expand on invalid formats shouldn't raise""" + # SyntaxError + self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}") + # NameError + self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}") + # ZeroDivisionError + self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") + + def test_silent_postexec(self): + """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks""" + pre_explicit = mock.Mock() + pre_always = mock.Mock() + post_explicit = mock.Mock() + post_always = mock.Mock() + all_mocks = [pre_explicit, pre_always, post_explicit, post_always] + + ip.events.register('pre_run_cell', pre_explicit) + ip.events.register('pre_execute', pre_always) + ip.events.register('post_run_cell', post_explicit) + ip.events.register('post_execute', post_always) + + try: + ip.run_cell("1", silent=True) + assert pre_always.called + assert not pre_explicit.called + assert post_always.called + assert not post_explicit.called + # double-check that non-silent exec did what we expected + # silent to avoid + ip.run_cell("1") + assert pre_explicit.called + assert post_explicit.called + info, = pre_explicit.call_args[0] + result, = post_explicit.call_args[0] + self.assertEqual(info, result.info) + # check that post hooks are always called + [m.reset_mock() for m in all_mocks] + ip.run_cell("syntax error") + assert pre_always.called + assert pre_explicit.called + assert post_always.called + assert post_explicit.called + info, = pre_explicit.call_args[0] + result, = post_explicit.call_args[0] + self.assertEqual(info, result.info) + finally: + # remove post-exec + ip.events.unregister('pre_run_cell', pre_explicit) + ip.events.unregister('pre_execute', pre_always) + ip.events.unregister('post_run_cell', post_explicit) + ip.events.unregister('post_execute', post_always) + + def test_silent_noadvance(self): + """run_cell(silent=True) doesn't advance execution_count""" + ec = ip.execution_count + # silent should force store_history=False + ip.run_cell("1", store_history=True, silent=True) + + self.assertEqual(ec, ip.execution_count) + # double-check that non-silent exec did what we expected + # silent to avoid + ip.run_cell("1", store_history=True) + self.assertEqual(ec+1, ip.execution_count) + + def test_silent_nodisplayhook(self): + """run_cell(silent=True) doesn't trigger displayhook""" + d = dict(called=False) + + trap = ip.display_trap + save_hook = trap.hook + + def failing_hook(*args, **kwargs): + d['called'] = True + + try: + trap.hook = failing_hook + res = ip.run_cell("1", silent=True) + self.assertFalse(d['called']) + self.assertIsNone(res.result) + # double-check that non-silent exec did what we expected + # silent to avoid + ip.run_cell("1") + self.assertTrue(d['called']) + finally: + trap.hook = save_hook + + def test_ofind_line_magic(self): + from IPython.core.magic import register_line_magic + + @register_line_magic + def lmagic(line): + "A line magic" + + # Get info on line magic + lfind = ip._ofind("lmagic") + info = dict( + found=True, + isalias=False, + ismagic=True, + namespace="IPython internal", + obj=lmagic, + parent=None, + ) + self.assertEqual(lfind, info) + + def test_ofind_cell_magic(self): + from IPython.core.magic import register_cell_magic + + @register_cell_magic + def cmagic(line, cell): + "A cell magic" + + # Get info on cell magic + find = ip._ofind("cmagic") + info = dict( + found=True, + isalias=False, + ismagic=True, + namespace="IPython internal", + obj=cmagic, + parent=None, + ) + self.assertEqual(find, info) + + def test_ofind_property_with_error(self): + class A(object): + @property + def foo(self): + raise NotImplementedError() # pragma: no cover + + a = A() + + found = ip._ofind('a.foo', [('locals', locals())]) + info = dict(found=True, isalias=False, ismagic=False, + namespace='locals', obj=A.foo, parent=a) + self.assertEqual(found, info) + + def test_ofind_multiple_attribute_lookups(self): + class A(object): + @property + def foo(self): + raise NotImplementedError() # pragma: no cover + + a = A() + a.a = A() + a.a.a = A() + + found = ip._ofind('a.a.a.foo', [('locals', locals())]) + info = dict(found=True, isalias=False, ismagic=False, + namespace='locals', obj=A.foo, parent=a.a.a) + self.assertEqual(found, info) + + def test_ofind_slotted_attributes(self): + class A(object): + __slots__ = ['foo'] + def __init__(self): + self.foo = 'bar' + + a = A() + found = ip._ofind('a.foo', [('locals', locals())]) + info = dict(found=True, isalias=False, ismagic=False, + namespace='locals', obj=a.foo, parent=a) + self.assertEqual(found, info) + + found = ip._ofind('a.bar', [('locals', locals())]) + info = dict(found=False, isalias=False, ismagic=False, + namespace=None, obj=None, parent=a) + self.assertEqual(found, info) + + def test_ofind_prefers_property_to_instance_level_attribute(self): + class A(object): + @property + def foo(self): + return 'bar' + a = A() + a.__dict__["foo"] = "baz" + self.assertEqual(a.foo, "bar") + found = ip._ofind("a.foo", [("locals", locals())]) + self.assertIs(found["obj"], A.foo) + + def test_custom_syntaxerror_exception(self): + called = [] + def my_handler(shell, etype, value, tb, tb_offset=None): + called.append(etype) + shell.showtraceback((etype, value, tb), tb_offset=tb_offset) + + ip.set_custom_exc((SyntaxError,), my_handler) + try: + ip.run_cell("1f") + # Check that this was called, and only once. + self.assertEqual(called, [SyntaxError]) + finally: + # Reset the custom exception hook + ip.set_custom_exc((), None) + + def test_custom_exception(self): + called = [] + def my_handler(shell, etype, value, tb, tb_offset=None): + called.append(etype) + shell.showtraceback((etype, value, tb), tb_offset=tb_offset) + + ip.set_custom_exc((ValueError,), my_handler) + try: + res = ip.run_cell("raise ValueError('test')") + # Check that this was called, and only once. + self.assertEqual(called, [ValueError]) + # Check that the error is on the result object + self.assertIsInstance(res.error_in_exec, ValueError) + finally: + # Reset the custom exception hook + ip.set_custom_exc((), None) + + @mock.patch("builtins.print") + def test_showtraceback_with_surrogates(self, mocked_print): + values = [] + + def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False): + values.append(value) + if value == chr(0xD8FF): + raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "") + + # mock builtins.print + mocked_print.side_effect = mock_print_func + + # ip._showtraceback() is replaced in globalipapp.py. + # Call original method to test. + interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF)) + + self.assertEqual(mocked_print.call_count, 2) + self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"]) + + def test_mktempfile(self): + filename = ip.mktempfile() + # Check that we can open the file again on Windows + with open(filename, "w", encoding="utf-8") as f: + f.write("abc") + + filename = ip.mktempfile(data="blah") + with open(filename, "r", encoding="utf-8") as f: + self.assertEqual(f.read(), "blah") + + def test_new_main_mod(self): + # Smoketest to check that this accepts a unicode module name + name = u'jiefmw' + mod = ip.new_main_mod(u'%s.py' % name, name) + self.assertEqual(mod.__name__, name) + + def test_get_exception_only(self): + try: + raise KeyboardInterrupt + except KeyboardInterrupt: + msg = ip.get_exception_only() + self.assertEqual(msg, 'KeyboardInterrupt\n') + + try: + raise DerivedInterrupt("foo") + except KeyboardInterrupt: + msg = ip.get_exception_only() + self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n') + + def test_inspect_text(self): + ip.run_cell('a = 5') + text = ip.object_inspect_text('a') + self.assertIsInstance(text, str) + + def test_last_execution_result(self): + """ Check that last execution result gets set correctly (GH-10702) """ + result = ip.run_cell('a = 5; a') + self.assertTrue(ip.last_execution_succeeded) + self.assertEqual(ip.last_execution_result.result, 5) + + result = ip.run_cell('a = x_invalid_id_x') + self.assertFalse(ip.last_execution_succeeded) + self.assertFalse(ip.last_execution_result.success) + self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError) + + def test_reset_aliasing(self): + """ Check that standard posix aliases work after %reset. """ + if os.name != 'posix': + return + + ip.reset() + for cmd in ('clear', 'more', 'less', 'man'): + res = ip.run_cell('%' + cmd) + self.assertEqual(res.success, True) + + +class TestSafeExecfileNonAsciiPath(unittest.TestCase): + + @onlyif_unicode_paths + def setUp(self): + self.BASETESTDIR = tempfile.mkdtemp() + self.TESTDIR = join(self.BASETESTDIR, u"åäö") + os.mkdir(self.TESTDIR) + with open( + join(self.TESTDIR, u"åäötestscript.py"), "w", encoding="utf-8" + ) as sfile: + sfile.write("pass\n") + self.oldpath = os.getcwd() + os.chdir(self.TESTDIR) + self.fname = u"åäötestscript.py" + + def tearDown(self): + os.chdir(self.oldpath) + shutil.rmtree(self.BASETESTDIR) + + @onlyif_unicode_paths + def test_1(self): + """Test safe_execfile with non-ascii path + """ + ip.safe_execfile(self.fname, {}, raise_exceptions=True) + +class ExitCodeChecks(tt.TempFileMixin): + + def setUp(self): + self.system = ip.system_raw + + def test_exit_code_ok(self): + self.system('exit 0') + self.assertEqual(ip.user_ns['_exit_code'], 0) + + def test_exit_code_error(self): + self.system('exit 1') + self.assertEqual(ip.user_ns['_exit_code'], 1) + + @skipif(not hasattr(signal, 'SIGALRM')) + def test_exit_code_signal(self): + self.mktmp("import signal, time\n" + "signal.setitimer(signal.ITIMER_REAL, 0.1)\n" + "time.sleep(1)\n") + self.system("%s %s" % (sys.executable, self.fname)) + self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM) + + @onlyif_cmds_exist("csh") + def test_exit_code_signal_csh(self): # pragma: no cover + SHELL = os.environ.get("SHELL", None) + os.environ["SHELL"] = find_cmd("csh") + try: + self.test_exit_code_signal() + finally: + if SHELL is not None: + os.environ['SHELL'] = SHELL + else: + del os.environ['SHELL'] + + +class TestSystemRaw(ExitCodeChecks): + + def setUp(self): + super().setUp() + self.system = ip.system_raw + + @onlyif_unicode_paths + def test_1(self): + """Test system_raw with non-ascii cmd + """ + cmd = u'''python -c "'åäö'" ''' + ip.system_raw(cmd) + + @mock.patch('subprocess.call', side_effect=KeyboardInterrupt) + @mock.patch('os.system', side_effect=KeyboardInterrupt) + def test_control_c(self, *mocks): + try: + self.system("sleep 1 # wont happen") + except KeyboardInterrupt: # pragma: no cove + self.fail( + "system call should intercept " + "keyboard interrupt from subprocess.call" + ) + self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT) + + def test_magic_warnings(self): + for magic_cmd in ("pip", "conda", "cd"): + with self.assertWarnsRegex(Warning, "You executed the system command"): + ip.system_raw(magic_cmd) + +# TODO: Exit codes are currently ignored on Windows. +class TestSystemPipedExitCode(ExitCodeChecks): + + def setUp(self): + super().setUp() + self.system = ip.system_piped + + @skip_win32 + def test_exit_code_ok(self): + ExitCodeChecks.test_exit_code_ok(self) + + @skip_win32 + def test_exit_code_error(self): + ExitCodeChecks.test_exit_code_error(self) + + @skip_win32 + def test_exit_code_signal(self): + ExitCodeChecks.test_exit_code_signal(self) + +class TestModules(tt.TempFileMixin): + def test_extraneous_loads(self): + """Test we're not loading modules on startup that we shouldn't. + """ + self.mktmp("import sys\n" + "print('numpy' in sys.modules)\n" + "print('ipyparallel' in sys.modules)\n" + "print('ipykernel' in sys.modules)\n" + ) + out = "False\nFalse\nFalse\n" + tt.ipexec_validate(self.fname, out) + +class Negator(ast.NodeTransformer): + """Negates all number literals in an AST.""" + + # for python 3.7 and earlier + def visit_Num(self, node): + node.n = -node.n + return node + + # for python 3.8+ + def visit_Constant(self, node): + if isinstance(node.value, int): + return self.visit_Num(node) + return node + +class TestAstTransform(unittest.TestCase): + def setUp(self): + self.negator = Negator() + ip.ast_transformers.append(self.negator) + + def tearDown(self): + ip.ast_transformers.remove(self.negator) + + def test_non_int_const(self): + with tt.AssertPrints("hello"): + ip.run_cell('print("hello")') + + def test_run_cell(self): + with tt.AssertPrints("-34"): + ip.run_cell("print(12 + 22)") + + # A named reference to a number shouldn't be transformed. + ip.user_ns["n"] = 55 + with tt.AssertNotPrints("-55"): + ip.run_cell("print(n)") + + def test_timeit(self): + called = set() + def f(x): + called.add(x) + ip.push({'f':f}) + + with tt.AssertPrints("std. dev. of"): + ip.run_line_magic("timeit", "-n1 f(1)") + self.assertEqual(called, {-1}) + called.clear() + + with tt.AssertPrints("std. dev. of"): + ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") + self.assertEqual(called, {-2, -3}) + + def test_time(self): + called = [] + def f(x): + called.append(x) + ip.push({'f':f}) + + # Test with an expression + with tt.AssertPrints("Wall time: "): + ip.run_line_magic("time", "f(5+9)") + self.assertEqual(called, [-14]) + called[:] = [] + + # Test with a statement (different code path) + with tt.AssertPrints("Wall time: "): + ip.run_line_magic("time", "a = f(-3 + -2)") + self.assertEqual(called, [5]) + + def test_macro(self): + ip.push({'a':10}) + # The AST transformation makes this do a+=-1 + ip.define_macro("amacro", "a+=1\nprint(a)") + + with tt.AssertPrints("9"): + ip.run_cell("amacro") + with tt.AssertPrints("8"): + ip.run_cell("amacro") + +class TestMiscTransform(unittest.TestCase): + + + def test_transform_only_once(self): + cleanup = 0 + line_t = 0 + def count_cleanup(lines): + nonlocal cleanup + cleanup += 1 + return lines + + def count_line_t(lines): + nonlocal line_t + line_t += 1 + return lines + + ip.input_transformer_manager.cleanup_transforms.append(count_cleanup) + ip.input_transformer_manager.line_transforms.append(count_line_t) + + ip.run_cell('1') + + assert cleanup == 1 + assert line_t == 1 + +class IntegerWrapper(ast.NodeTransformer): + """Wraps all integers in a call to Integer()""" + + # for Python 3.7 and earlier + + # for Python 3.7 and earlier + def visit_Num(self, node): + if isinstance(node.n, int): + return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()), + args=[node], keywords=[]) + return node + + # For Python 3.8+ + def visit_Constant(self, node): + if isinstance(node.value, int): + return self.visit_Num(node) + return node + + +class TestAstTransform2(unittest.TestCase): + def setUp(self): + self.intwrapper = IntegerWrapper() + ip.ast_transformers.append(self.intwrapper) + + self.calls = [] + def Integer(*args): + self.calls.append(args) + return args + ip.push({"Integer": Integer}) + + def tearDown(self): + ip.ast_transformers.remove(self.intwrapper) + del ip.user_ns['Integer'] + + def test_run_cell(self): + ip.run_cell("n = 2") + self.assertEqual(self.calls, [(2,)]) + + # This shouldn't throw an error + ip.run_cell("o = 2.0") + self.assertEqual(ip.user_ns['o'], 2.0) + + def test_run_cell_non_int(self): + ip.run_cell("n = 'a'") + assert self.calls == [] + + def test_timeit(self): + called = set() + def f(x): + called.add(x) + ip.push({'f':f}) + + with tt.AssertPrints("std. dev. of"): + ip.run_line_magic("timeit", "-n1 f(1)") + self.assertEqual(called, {(1,)}) + called.clear() + + with tt.AssertPrints("std. dev. of"): + ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") + self.assertEqual(called, {(2,), (3,)}) + +class ErrorTransformer(ast.NodeTransformer): + """Throws an error when it sees a number.""" + + def visit_Constant(self, node): + if isinstance(node.value, int): + raise ValueError("test") + return node + + +class TestAstTransformError(unittest.TestCase): + def test_unregistering(self): + err_transformer = ErrorTransformer() + ip.ast_transformers.append(err_transformer) + + with self.assertWarnsRegex(UserWarning, "It will be unregistered"): + ip.run_cell("1 + 2") + + # This should have been removed. + self.assertNotIn(err_transformer, ip.ast_transformers) + + +class StringRejector(ast.NodeTransformer): + """Throws an InputRejected when it sees a string literal. + + Used to verify that NodeTransformers can signal that a piece of code should + not be executed by throwing an InputRejected. + """ + + # 3.8 only + def visit_Constant(self, node): + if isinstance(node.value, str): + raise InputRejected("test") + return node + + +class TestAstTransformInputRejection(unittest.TestCase): + + def setUp(self): + self.transformer = StringRejector() + ip.ast_transformers.append(self.transformer) + + def tearDown(self): + ip.ast_transformers.remove(self.transformer) + + def test_input_rejection(self): + """Check that NodeTransformers can reject input.""" + + expect_exception_tb = tt.AssertPrints("InputRejected: test") + expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False) + + # Run the same check twice to verify that the transformer is not + # disabled after raising. + with expect_exception_tb, expect_no_cell_output: + ip.run_cell("'unsafe'") + + with expect_exception_tb, expect_no_cell_output: + res = ip.run_cell("'unsafe'") + + self.assertIsInstance(res.error_before_exec, InputRejected) + +def test__IPYTHON__(): + # This shouldn't raise a NameError, that's all + __IPYTHON__ + + +class DummyRepr(object): + def __repr__(self): + return "DummyRepr" + + def _repr_html_(self): + return "dummy" + + def _repr_javascript_(self): + return "console.log('hi');", {'key': 'value'} + + +def test_user_variables(): + # enable all formatters + ip.display_formatter.active_types = ip.display_formatter.format_types + + ip.user_ns['dummy'] = d = DummyRepr() + keys = {'dummy', 'doesnotexist'} + r = ip.user_expressions({ key:key for key in keys}) + + assert keys == set(r.keys()) + dummy = r["dummy"] + assert {"status", "data", "metadata"} == set(dummy.keys()) + assert dummy["status"] == "ok" + data = dummy["data"] + metadata = dummy["metadata"] + assert data.get("text/html") == d._repr_html_() + js, jsmd = d._repr_javascript_() + assert data.get("application/javascript") == js + assert metadata.get("application/javascript") == jsmd + + dne = r["doesnotexist"] + assert dne["status"] == "error" + assert dne["ename"] == "NameError" + + # back to text only + ip.display_formatter.active_types = ['text/plain'] + +def test_user_expression(): + # enable all formatters + ip.display_formatter.active_types = ip.display_formatter.format_types + query = { + 'a' : '1 + 2', + 'b' : '1/0', + } + r = ip.user_expressions(query) + import pprint + pprint.pprint(r) + assert set(r.keys()) == set(query.keys()) + a = r["a"] + assert {"status", "data", "metadata"} == set(a.keys()) + assert a["status"] == "ok" + data = a["data"] + metadata = a["metadata"] + assert data.get("text/plain") == "3" + + b = r["b"] + assert b["status"] == "error" + assert b["ename"] == "ZeroDivisionError" + + # back to text only + ip.display_formatter.active_types = ['text/plain'] + + +class TestSyntaxErrorTransformer(unittest.TestCase): + """Check that SyntaxError raised by an input transformer is handled by run_cell()""" + + @staticmethod + def transformer(lines): + for line in lines: + pos = line.find('syntaxerror') + if pos >= 0: + e = SyntaxError('input contains "syntaxerror"') + e.text = line + e.offset = pos + 1 + raise e + return lines + + def setUp(self): + ip.input_transformers_post.append(self.transformer) + + def tearDown(self): + ip.input_transformers_post.remove(self.transformer) + + def test_syntaxerror_input_transformer(self): + with tt.AssertPrints('1234'): + ip.run_cell('1234') + with tt.AssertPrints('SyntaxError: invalid syntax'): + ip.run_cell('1 2 3') # plain python syntax error + with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'): + ip.run_cell('2345 # syntaxerror') # input transformer syntax error + with tt.AssertPrints('3456'): + ip.run_cell('3456') + + +class TestWarningSuppression(unittest.TestCase): + def test_warning_suppression(self): + ip.run_cell("import warnings") + try: + with self.assertWarnsRegex(UserWarning, "asdf"): + ip.run_cell("warnings.warn('asdf')") + # Here's the real test -- if we run that again, we should get the + # warning again. Traditionally, each warning was only issued once per + # IPython session (approximately), even if the user typed in new and + # different code that should have also triggered the warning, leading + # to much confusion. + with self.assertWarnsRegex(UserWarning, "asdf"): + ip.run_cell("warnings.warn('asdf')") + finally: + ip.run_cell("del warnings") + + + def test_deprecation_warning(self): + ip.run_cell(""" +import warnings +def wrn(): + warnings.warn( + "I AM A WARNING", + DeprecationWarning + ) + """) + try: + with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"): + ip.run_cell("wrn()") + finally: + ip.run_cell("del warnings") + ip.run_cell("del wrn") + + +class TestImportNoDeprecate(tt.TempFileMixin): + + def setUp(self): + """Make a valid python temp file.""" + self.mktmp(""" +import warnings +def wrn(): + warnings.warn( + "I AM A WARNING", + DeprecationWarning + ) +""") + super().setUp() + + def test_no_dep(self): + """ + No deprecation warning should be raised from imported functions + """ + ip.run_cell("from {} import wrn".format(self.fname)) + + with tt.AssertNotPrints("I AM A WARNING"): + ip.run_cell("wrn()") + ip.run_cell("del wrn") + + +def test_custom_exc_count(): + hook = mock.Mock(return_value=None) + ip.set_custom_exc((SyntaxError,), hook) + before = ip.execution_count + ip.run_cell("def foo()", store_history=True) + # restore default excepthook + ip.set_custom_exc((), None) + assert hook.call_count == 1 + assert ip.execution_count == before + 1 + + +def test_run_cell_async(): + ip.run_cell("import asyncio") + coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5") + assert asyncio.iscoroutine(coro) + loop = asyncio.new_event_loop() + result = loop.run_until_complete(coro) + assert isinstance(result, interactiveshell.ExecutionResult) + assert result.result == 5 + + +def test_run_cell_await(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 10") + assert ip.user_ns["_"] == 10 + + +def test_run_cell_asyncio_run(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 1") + assert ip.user_ns["_"] == 1 + result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2") + assert ip.user_ns["_"] == 2 + result = ip.run_cell("await asyncio.sleep(0.01); 3") + assert ip.user_ns["_"] == 3 + + +def test_should_run_async(): + assert not ip.should_run_async("a = 5") + assert ip.should_run_async("await x") + assert ip.should_run_async("import asyncio; await asyncio.sleep(1)") + + +def test_set_custom_completer(): + num_completers = len(ip.Completer.matchers) + + def foo(*args, **kwargs): + return "I'm a completer!" + + ip.set_custom_completer(foo, 0) + + # check that we've really added a new completer + assert len(ip.Completer.matchers) == num_completers + 1 + + # check that the first completer is the function we defined + assert ip.Completer.matchers[0]() == "I'm a completer!" + + # clean up + ip.Completer.custom_matchers.pop() diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_iplib.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_iplib.py new file mode 100644 index 0000000..94ce518 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_iplib.py @@ -0,0 +1,249 @@ +"""Tests for the key interactiveshell module, where the main ipython class is defined. +""" +#----------------------------------------------------------------------------- +# Module imports +#----------------------------------------------------------------------------- + +# third party +import pytest + +# our own packages + +#----------------------------------------------------------------------------- +# Test functions +#----------------------------------------------------------------------------- + +def test_reset(): + """reset must clear most namespaces.""" + + # Check that reset runs without error + ip.reset() + + # Once we've reset it (to clear of any junk that might have been there from + # other tests, we can count how many variables are in the user's namespace + nvars_user_ns = len(ip.user_ns) + nvars_hidden = len(ip.user_ns_hidden) + + # Now add a few variables to user_ns, and check that reset clears them + ip.user_ns['x'] = 1 + ip.user_ns['y'] = 1 + ip.reset() + + # Finally, check that all namespaces have only as many variables as we + # expect to find in them: + assert len(ip.user_ns) == nvars_user_ns + assert len(ip.user_ns_hidden) == nvars_hidden + + +# Tests for reporting of exceptions in various modes, handling of SystemExit, +# and %tb functionality. This is really a mix of testing ultraTB and interactiveshell. + +def doctest_tb_plain(): + """ + In [18]: xmode plain + Exception reporting mode: Plain + + In [19]: run simpleerr.py + Traceback (most recent call last): + File ...:... in + bar(mode) + File ...:... in bar + div0() + File ...:... in div0 + x/y + ZeroDivisionError: ... + """ + + +def doctest_tb_context(): + """ + In [3]: xmode context + Exception reporting mode: Context + + In [4]: run simpleerr.py + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + + ... in + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + + ... in bar(mode) + 15 "bar" + 16 if mode=='div': + ---> 17 div0() + 18 elif mode=='exit': + 19 try: + + ... in div0() + 6 x = 1 + 7 y = 0 + ----> 8 x/y + + ZeroDivisionError: ...""" + + +def doctest_tb_verbose(): + """ + In [5]: xmode verbose + Exception reporting mode: Verbose + + In [6]: run simpleerr.py + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + + ... in + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + mode = 'div' + + ... in bar(mode='div') + 15 "bar" + 16 if mode=='div': + ---> 17 div0() + 18 elif mode=='exit': + 19 try: + + ... in div0() + 6 x = 1 + 7 y = 0 + ----> 8 x/y + x = 1 + y = 0 + + ZeroDivisionError: ... + """ + + +def doctest_tb_sysexit(): + """ + In [17]: %xmode plain + Exception reporting mode: Plain + + In [18]: %run simpleerr.py exit + An exception has occurred, use %tb to see the full traceback. + SystemExit: (1, 'Mode = exit') + + In [19]: %run simpleerr.py exit 2 + An exception has occurred, use %tb to see the full traceback. + SystemExit: (2, 'Mode = exit') + + In [20]: %tb + Traceback (most recent call last): + File ...:... in execfile + exec(compiler(f.read(), fname, "exec"), glob, loc) + File ...:... in + bar(mode) + File ...:... in bar + sysexit(stat, mode) + File ...:... in sysexit + raise SystemExit(stat, f"Mode = {mode}") + SystemExit: (2, 'Mode = exit') + + In [21]: %xmode context + Exception reporting mode: Context + + In [22]: %tb + --------------------------------------------------------------------------- + SystemExit Traceback (most recent call last) + File ..., in execfile(fname, glob, loc, compiler) + ... with open(fname, "rb") as f: + ... compiler = compiler or compile + ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc) + ... + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + + ...bar(mode) + 21 except: + 22 stat = 1 + ---> 23 sysexit(stat, mode) + 24 else: + 25 raise ValueError('Unknown mode') + + ...sysexit(stat, mode) + 10 def sysexit(stat, mode): + ---> 11 raise SystemExit(stat, f"Mode = {mode}") + + SystemExit: (2, 'Mode = exit') + """ + + +def doctest_tb_sysexit_verbose(): + """ + In [18]: %run simpleerr.py exit + An exception has occurred, use %tb to see the full traceback. + SystemExit: (1, 'Mode = exit') + + In [19]: %run simpleerr.py exit 2 + An exception has occurred, use %tb to see the full traceback. + SystemExit: (2, 'Mode = exit') + + In [23]: %xmode verbose + Exception reporting mode: Verbose + + In [24]: %tb + --------------------------------------------------------------------------- + SystemExit Traceback (most recent call last) + + ... in + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + mode = 'exit' + + ... in bar(mode='exit') + ... except: + ... stat = 1 + ---> ... sysexit(stat, mode) + mode = 'exit' + stat = 2 + ... else: + ... raise ValueError('Unknown mode') + + ... in sysexit(stat=2, mode='exit') + 10 def sysexit(stat, mode): + ---> 11 raise SystemExit(stat, f"Mode = {mode}") + stat = 2 + + SystemExit: (2, 'Mode = exit') + """ + + +def test_run_cell(): + import textwrap + + ip.run_cell("a = 10\na+=1") + ip.run_cell("assert a == 11\nassert 1") + + assert ip.user_ns["a"] == 11 + complex = textwrap.dedent( + """ + if 1: + print "hello" + if 1: + print "world" + + if 2: + print "foo" + + if 3: + print "bar" + + if 4: + print "bar" + + """) + # Simply verifies that this kind of input is run + ip.run_cell(complex) + + +def test_db(): + """Test the internal database used for variable persistence.""" + ip.db["__unittest_"] = 12 + assert ip.db["__unittest_"] == 12 + del ip.db["__unittest_"] + assert "__unittest_" not in ip.db diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_logger.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_logger.py new file mode 100644 index 0000000..10e4620 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_logger.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +"""Test IPython.core.logger""" + +import os.path + +import pytest +from tempfile import TemporaryDirectory + + +def test_logstart_inaccessible_file(): + with pytest.raises(IOError): + _ip.logger.logstart(logfname="/") # Opening that filename will fail. + + try: + _ip.run_cell("a=1") # Check it doesn't try to log this + finally: + _ip.logger.log_active = False # If this fails, don't let later tests fail + +def test_logstart_unicode(): + with TemporaryDirectory() as tdir: + logfname = os.path.join(tdir, "test_unicode.log") + _ip.run_cell("'abc€'") + try: + _ip.magic("logstart -to %s" % logfname) + _ip.run_cell("'abc€'") + finally: + _ip.logger.logstop() diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic.py new file mode 100644 index 0000000..b46a9e8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic.py @@ -0,0 +1,1450 @@ +# -*- coding: utf-8 -*- +"""Tests for various magic functions.""" + +import asyncio +import gc +import io +import os +import re +import shlex +import sys +import warnings +from importlib import invalidate_caches +from io import StringIO +from pathlib import Path +from textwrap import dedent +from unittest import TestCase, mock + +import pytest + +from IPython import get_ipython +from IPython.core import magic +from IPython.core.error import UsageError +from IPython.core.magic import ( + Magics, + cell_magic, + line_magic, + magics_class, + register_cell_magic, + register_line_magic, +) +from IPython.core.magics import code, execution, logging, osm, script +from IPython.testing import decorators as dec +from IPython.testing import tools as tt +from IPython.utils.io import capture_output +from IPython.utils.process import find_cmd +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from IPython.utils.syspathcontext import prepended_to_syspath + +from .test_debugger import PdbTestInput + +from tempfile import NamedTemporaryFile + +@magic.magics_class +class DummyMagics(magic.Magics): pass + +def test_extract_code_ranges(): + instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" + expected = [ + (0, 1), + (2, 3), + (4, 6), + (6, 9), + (9, 14), + (16, None), + (None, 9), + (9, None), + (None, 13), + (None, None), + ] + actual = list(code.extract_code_ranges(instr)) + assert actual == expected + +def test_extract_symbols(): + source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" + symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] + expected = [([], ['a']), + (["def b():\n return 42\n"], []), + (["class A: pass\n"], []), + (["class A: pass\n", "def b():\n return 42\n"], []), + (["class A: pass\n"], ['a']), + ([], ['z'])] + for symbols, exp in zip(symbols_args, expected): + assert code.extract_symbols(source, symbols) == exp + + +def test_extract_symbols_raises_exception_with_non_python_code(): + source = ("=begin A Ruby program :)=end\n" + "def hello\n" + "puts 'Hello world'\n" + "end") + with pytest.raises(SyntaxError): + code.extract_symbols(source, "hello") + + +def test_magic_not_found(): + # magic not found raises UsageError + with pytest.raises(UsageError): + _ip.magic('doesntexist') + + # ensure result isn't success when a magic isn't found + result = _ip.run_cell('%doesntexist') + assert isinstance(result.error_in_exec, UsageError) + + +def test_cell_magic_not_found(): + # magic not found raises UsageError + with pytest.raises(UsageError): + _ip.run_cell_magic('doesntexist', 'line', 'cell') + + # ensure result isn't success when a magic isn't found + result = _ip.run_cell('%%doesntexist') + assert isinstance(result.error_in_exec, UsageError) + + +def test_magic_error_status(): + def fail(shell): + 1/0 + _ip.register_magic_function(fail) + result = _ip.run_cell('%fail') + assert isinstance(result.error_in_exec, ZeroDivisionError) + + +def test_config(): + """ test that config magic does not raise + can happen if Configurable init is moved too early into + Magics.__init__ as then a Config object will be registered as a + magic. + """ + ## should not raise. + _ip.magic('config') + +def test_config_available_configs(): + """ test that config magic prints available configs in unique and + sorted order. """ + with capture_output() as captured: + _ip.magic('config') + + stdout = captured.stdout + config_classes = stdout.strip().split('\n')[1:] + assert config_classes == sorted(set(config_classes)) + +def test_config_print_class(): + """ test that config with a classname prints the class's options. """ + with capture_output() as captured: + _ip.magic('config TerminalInteractiveShell') + + stdout = captured.stdout + assert re.match( + "TerminalInteractiveShell.* options", stdout.splitlines()[0] + ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'" + + +def test_rehashx(): + # clear up everything + _ip.alias_manager.clear_aliases() + del _ip.db['syscmdlist'] + + _ip.magic('rehashx') + # Practically ALL ipython development systems will have more than 10 aliases + + assert len(_ip.alias_manager.aliases) > 10 + for name, cmd in _ip.alias_manager.aliases: + # we must strip dots from alias names + assert "." not in name + + # rehashx must fill up syscmdlist + scoms = _ip.db['syscmdlist'] + assert len(scoms) > 10 + + +def test_magic_parse_options(): + """Test that we don't mangle paths when parsing magic options.""" + ip = get_ipython() + path = 'c:\\x' + m = DummyMagics(ip) + opts = m.parse_options('-f %s' % path,'f:')[0] + # argv splitting is os-dependent + if os.name == 'posix': + expected = 'c:x' + else: + expected = path + assert opts["f"] == expected + + +def test_magic_parse_long_options(): + """Magic.parse_options can handle --foo=bar long options""" + ip = get_ipython() + m = DummyMagics(ip) + opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=") + assert "foo" in opts + assert "bar" in opts + assert opts["bar"] == "bubble" + + +def doctest_hist_f(): + """Test %hist -f with temporary filename. + + In [9]: import tempfile + + In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') + + In [11]: %hist -nl -f $tfile 3 + + In [13]: import os; os.unlink(tfile) + """ + + +def doctest_hist_op(): + """Test %hist -op + + In [1]: class b(float): + ...: pass + ...: + + In [2]: class s(object): + ...: def __str__(self): + ...: return 's' + ...: + + In [3]: + + In [4]: class r(b): + ...: def __repr__(self): + ...: return 'r' + ...: + + In [5]: class sr(s,r): pass + ...: + + In [6]: + + In [7]: bb=b() + + In [8]: ss=s() + + In [9]: rr=r() + + In [10]: ssrr=sr() + + In [11]: 4.5 + Out[11]: 4.5 + + In [12]: str(ss) + Out[12]: 's' + + In [13]: + + In [14]: %hist -op + >>> class b: + ... pass + ... + >>> class s(b): + ... def __str__(self): + ... return 's' + ... + >>> + >>> class r(b): + ... def __repr__(self): + ... return 'r' + ... + >>> class sr(s,r): pass + >>> + >>> bb=b() + >>> ss=s() + >>> rr=r() + >>> ssrr=sr() + >>> 4.5 + 4.5 + >>> str(ss) + 's' + >>> + """ + +def test_hist_pof(): + ip = get_ipython() + ip.run_cell("1+2", store_history=True) + #raise Exception(ip.history_manager.session_number) + #raise Exception(list(ip.history_manager._get_range_session())) + with TemporaryDirectory() as td: + tf = os.path.join(td, 'hist.py') + ip.run_line_magic('history', '-pof %s' % tf) + assert os.path.isfile(tf) + + +def test_macro(): + ip = get_ipython() + ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] + for i, cmd in enumerate(cmds, start=1): + ip.history_manager.store_inputs(i, cmd) + ip.magic("macro test 1-3") + assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" + + # List macros + assert "test" in ip.magic("macro") + + +def test_macro_run(): + """Test that we can run a multi-line macro successfully.""" + ip = get_ipython() + ip.history_manager.reset() + cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"] + for cmd in cmds: + ip.run_cell(cmd, store_history=True) + assert ip.user_ns["test"].value == "a+=1\nprint(a)\n" + with tt.AssertPrints("12"): + ip.run_cell("test") + with tt.AssertPrints("13"): + ip.run_cell("test") + + +def test_magic_magic(): + """Test %magic""" + ip = get_ipython() + with capture_output() as captured: + ip.magic("magic") + + stdout = captured.stdout + assert "%magic" in stdout + assert "IPython" in stdout + assert "Available" in stdout + + +@dec.skipif_not_numpy +def test_numpy_reset_array_undec(): + "Test '%reset array' functionality" + _ip.ex("import numpy as np") + _ip.ex("a = np.empty(2)") + assert "a" in _ip.user_ns + _ip.magic("reset -f array") + assert "a" not in _ip.user_ns + + +def test_reset_out(): + "Test '%reset out' magic" + _ip.run_cell("parrot = 'dead'", store_history=True) + # test '%reset -f out', make an Out prompt + _ip.run_cell("parrot", store_history=True) + assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")] + _ip.magic("reset -f out") + assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] + assert len(_ip.user_ns["Out"]) == 0 + + +def test_reset_in(): + "Test '%reset in' magic" + # test '%reset -f in' + _ip.run_cell("parrot", store_history=True) + assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] + _ip.magic("%reset -f in") + assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] + assert len(set(_ip.user_ns["In"])) == 1 + + +def test_reset_dhist(): + "Test '%reset dhist' magic" + _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing + _ip.magic("cd " + os.path.dirname(pytest.__file__)) + _ip.magic("cd -") + assert len(_ip.user_ns["_dh"]) > 0 + _ip.magic("reset -f dhist") + assert len(_ip.user_ns["_dh"]) == 0 + _ip.run_cell("_dh = [d for d in tmp]") # restore + + +def test_reset_in_length(): + "Test that '%reset in' preserves In[] length" + _ip.run_cell("print 'foo'") + _ip.run_cell("reset -f in") + assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1 + + +class TestResetErrors(TestCase): + + def test_reset_redefine(self): + + @magics_class + class KernelMagics(Magics): + @line_magic + def less(self, shell): pass + + _ip.register_magics(KernelMagics) + + with self.assertLogs() as cm: + # hack, we want to just capture logs, but assertLogs fails if not + # logs get produce. + # so log one things we ignore. + import logging as log_mod + log = log_mod.getLogger() + log.info('Nothing') + # end hack. + _ip.run_cell("reset -f") + + assert len(cm.output) == 1 + for out in cm.output: + assert "Invalid alias" not in out + +def test_tb_syntaxerror(): + """test %tb after a SyntaxError""" + ip = get_ipython() + ip.run_cell("for") + + # trap and validate stdout + save_stdout = sys.stdout + try: + sys.stdout = StringIO() + ip.run_cell("%tb") + out = sys.stdout.getvalue() + finally: + sys.stdout = save_stdout + # trim output, and only check the last line + last_line = out.rstrip().splitlines()[-1].strip() + assert last_line == "SyntaxError: invalid syntax" + + +def test_time(): + ip = get_ipython() + + with tt.AssertPrints("Wall time: "): + ip.run_cell("%time None") + + ip.run_cell("def f(kmjy):\n" + " %time print (2*kmjy)") + + with tt.AssertPrints("Wall time: "): + with tt.AssertPrints("hihi", suppress=False): + ip.run_cell("f('hi')") + +def test_time_last_not_expression(): + ip.run_cell("%%time\n" + "var_1 = 1\n" + "var_2 = 2\n") + assert ip.user_ns['var_1'] == 1 + del ip.user_ns['var_1'] + assert ip.user_ns['var_2'] == 2 + del ip.user_ns['var_2'] + + +@dec.skip_win32 +def test_time2(): + ip = get_ipython() + + with tt.AssertPrints("CPU times: user "): + ip.run_cell("%time None") + +def test_time3(): + """Erroneous magic function calls, issue gh-3334""" + ip = get_ipython() + ip.user_ns.pop('run', None) + + with tt.AssertNotPrints("not found", channel='stderr'): + ip.run_cell("%%time\n" + "run = 0\n" + "run += 1") + +def test_multiline_time(): + """Make sure last statement from time return a value.""" + ip = get_ipython() + ip.user_ns.pop('run', None) + + ip.run_cell(dedent("""\ + %%time + a = "ho" + b = "hey" + a+b + """ + ) + ) + assert ip.user_ns_hidden["_"] == "hohey" + + +def test_time_local_ns(): + """ + Test that local_ns is actually global_ns when running a cell magic + """ + ip = get_ipython() + ip.run_cell("%%time\n" "myvar = 1") + assert ip.user_ns["myvar"] == 1 + del ip.user_ns["myvar"] + + +def test_doctest_mode(): + "Toggle doctest_mode twice, it should be a no-op and run without error" + _ip.magic('doctest_mode') + _ip.magic('doctest_mode') + + +def test_parse_options(): + """Tests for basic options parsing in magics.""" + # These are only the most minimal of tests, more should be added later. At + # the very least we check that basic text/unicode calls work OK. + m = DummyMagics(_ip) + assert m.parse_options("foo", "")[1] == "foo" + assert m.parse_options("foo", "")[1] == "foo" + + +def test_parse_options_preserve_non_option_string(): + """Test to assert preservation of non-option part of magic-block, while parsing magic options.""" + m = DummyMagics(_ip) + opts, stmt = m.parse_options( + " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True + ) + assert opts == {"n": "1", "r": "13"} + assert stmt == "_ = 314 + foo" + + +def test_run_magic_preserve_code_block(): + """Test to assert preservation of non-option part of magic-block, while running magic.""" + _ip.user_ns["spaces"] = [] + _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])") + assert _ip.user_ns["spaces"] == [[0]] + + +def test_dirops(): + """Test various directory handling operations.""" + # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/') + curpath = os.getcwd + startdir = os.getcwd() + ipdir = os.path.realpath(_ip.ipython_dir) + try: + _ip.magic('cd "%s"' % ipdir) + assert curpath() == ipdir + _ip.magic('cd -') + assert curpath() == startdir + _ip.magic('pushd "%s"' % ipdir) + assert curpath() == ipdir + _ip.magic('popd') + assert curpath() == startdir + finally: + os.chdir(startdir) + + +def test_cd_force_quiet(): + """Test OSMagics.cd_force_quiet option""" + _ip.config.OSMagics.cd_force_quiet = True + osmagics = osm.OSMagics(shell=_ip) + + startdir = os.getcwd() + ipdir = os.path.realpath(_ip.ipython_dir) + + try: + with tt.AssertNotPrints(ipdir): + osmagics.cd('"%s"' % ipdir) + with tt.AssertNotPrints(startdir): + osmagics.cd('-') + finally: + os.chdir(startdir) + + +def test_xmode(): + # Calling xmode three times should be a no-op + xmode = _ip.InteractiveTB.mode + for i in range(4): + _ip.magic("xmode") + assert _ip.InteractiveTB.mode == xmode + +def test_reset_hard(): + monitor = [] + class A(object): + def __del__(self): + monitor.append(1) + def __repr__(self): + return "
" + + _ip.user_ns["a"] = A() + _ip.run_cell("a") + + assert monitor == [] + _ip.magic("reset -f") + assert monitor == [1] + +class TestXdel(tt.TempFileMixin): + def test_xdel(self): + """Test that references from %run are cleared by xdel.""" + src = ("class A(object):\n" + " monitor = []\n" + " def __del__(self):\n" + " self.monitor.append(1)\n" + "a = A()\n") + self.mktmp(src) + # %run creates some hidden references... + _ip.magic("run %s" % self.fname) + # ... as does the displayhook. + _ip.run_cell("a") + + monitor = _ip.user_ns["A"].monitor + assert monitor == [] + + _ip.magic("xdel a") + + # Check that a's __del__ method has been called. + gc.collect(0) + assert monitor == [1] + +def doctest_who(): + """doctest for %who + + In [1]: %reset -sf + + In [2]: alpha = 123 + + In [3]: beta = 'beta' + + In [4]: %who int + alpha + + In [5]: %who str + beta + + In [6]: %whos + Variable Type Data/Info + ---------------------------- + alpha int 123 + beta str beta + + In [7]: %who_ls + Out[7]: ['alpha', 'beta'] + """ + +def test_whos(): + """Check that whos is protected against objects where repr() fails.""" + class A(object): + def __repr__(self): + raise Exception() + _ip.user_ns['a'] = A() + _ip.magic("whos") + +def doctest_precision(): + """doctest for %precision + + In [1]: f = get_ipython().display_formatter.formatters['text/plain'] + + In [2]: %precision 5 + Out[2]: '%.5f' + + In [3]: f.float_format + Out[3]: '%.5f' + + In [4]: %precision %e + Out[4]: '%e' + + In [5]: f(3.1415927) + Out[5]: '3.141593e+00' + """ + +def test_debug_magic(): + """Test debugging a small code with %debug + + In [1]: with PdbTestInput(['c']): + ...: %debug print("a b") #doctest: +ELLIPSIS + ...: + ... + ipdb> c + a b + In [2]: + """ + +def test_psearch(): + with tt.AssertPrints("dict.fromkeys"): + _ip.run_cell("dict.fr*?") + with tt.AssertPrints("π.is_integer"): + _ip.run_cell("π = 3.14;\nπ.is_integ*?") + +def test_timeit_shlex(): + """test shlex issues with timeit (#1109)""" + _ip.ex("def f(*a,**kw): pass") + _ip.magic('timeit -n1 "this is a bug".count(" ")') + _ip.magic('timeit -r1 -n1 f(" ", 1)') + _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")') + _ip.magic('timeit -r1 -n1 ("a " + "b")') + _ip.magic('timeit -r1 -n1 f("a " + "b")') + _ip.magic('timeit -r1 -n1 f("a " + "b ")') + + +def test_timeit_special_syntax(): + "Test %%timeit with IPython special syntax" + @register_line_magic + def lmagic(line): + ip = get_ipython() + ip.user_ns['lmagic_out'] = line + + # line mode test + _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line") + assert _ip.user_ns["lmagic_out"] == "my line" + # cell mode test + _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2") + assert _ip.user_ns["lmagic_out"] == "my line2" + + +def test_timeit_return(): + """ + test whether timeit -o return object + """ + + res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') + assert(res is not None) + +def test_timeit_quiet(): + """ + test quiet option of timeit magic + """ + with tt.AssertNotPrints("loops"): + _ip.run_cell("%timeit -n1 -r1 -q 1") + +def test_timeit_return_quiet(): + with tt.AssertNotPrints("loops"): + res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1') + assert (res is not None) + +def test_timeit_invalid_return(): + with pytest.raises(SyntaxError): + _ip.run_line_magic('timeit', 'return') + +@dec.skipif(execution.profile is None) +def test_prun_special_syntax(): + "Test %%prun with IPython special syntax" + @register_line_magic + def lmagic(line): + ip = get_ipython() + ip.user_ns['lmagic_out'] = line + + # line mode test + _ip.run_line_magic("prun", "-q %lmagic my line") + assert _ip.user_ns["lmagic_out"] == "my line" + # cell mode test + _ip.run_cell_magic("prun", "-q", "%lmagic my line2") + assert _ip.user_ns["lmagic_out"] == "my line2" + + +@dec.skipif(execution.profile is None) +def test_prun_quotes(): + "Test that prun does not clobber string escapes (GH #1302)" + _ip.magic(r"prun -q x = '\t'") + assert _ip.user_ns["x"] == "\t" + + +def test_extension(): + # Debugging information for failures of this test + print('sys.path:') + for p in sys.path: + print(' ', p) + print('CWD', os.getcwd()) + + pytest.raises(ImportError, _ip.magic, "load_ext daft_extension") + daft_path = os.path.join(os.path.dirname(__file__), "daft_extension") + sys.path.insert(0, daft_path) + try: + _ip.user_ns.pop('arq', None) + invalidate_caches() # Clear import caches + _ip.magic("load_ext daft_extension") + assert _ip.user_ns["arq"] == 185 + _ip.magic("unload_ext daft_extension") + assert 'arq' not in _ip.user_ns + finally: + sys.path.remove(daft_path) + + +def test_notebook_export_json(): + pytest.importorskip("nbformat") + _ip = get_ipython() + _ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"] + for i, cmd in enumerate(cmds, start=1): + _ip.history_manager.store_inputs(i, cmd) + with TemporaryDirectory() as td: + outfile = os.path.join(td, "nb.ipynb") + _ip.magic("notebook %s" % outfile) + + +class TestEnv(TestCase): + + def test_env(self): + env = _ip.magic("env") + self.assertTrue(isinstance(env, dict)) + + def test_env_secret(self): + env = _ip.magic("env") + hidden = "" + with mock.patch.dict( + os.environ, + { + "API_KEY": "abc123", + "SECRET_THING": "ssshhh", + "JUPYTER_TOKEN": "", + "VAR": "abc" + } + ): + env = _ip.magic("env") + assert env["API_KEY"] == hidden + assert env["SECRET_THING"] == hidden + assert env["JUPYTER_TOKEN"] == hidden + assert env["VAR"] == "abc" + + def test_env_get_set_simple(self): + env = _ip.magic("env var val1") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val1') + self.assertEqual(_ip.magic("env var"), 'val1') + env = _ip.magic("env var=val2") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val2') + + def test_env_get_set_complex(self): + env = _ip.magic("env var 'val1 '' 'val2") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], "'val1 '' 'val2") + self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2") + env = _ip.magic('env var=val2 val3="val4') + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val2 val3="val4') + + def test_env_set_bad_input(self): + self.assertRaises(UsageError, lambda: _ip.magic("set_env var")) + + def test_env_set_whitespace(self): + self.assertRaises(UsageError, lambda: _ip.magic("env var A=B")) + + +class CellMagicTestCase(TestCase): + + def check_ident(self, magic): + # Manually called, we get the result + out = _ip.run_cell_magic(magic, "a", "b") + assert out == ("a", "b") + # Via run_cell, it goes into the user's namespace via displayhook + _ip.run_cell("%%" + magic + " c\nd\n") + assert _ip.user_ns["_"] == ("c", "d\n") + + def test_cell_magic_func_deco(self): + "Cell magic using simple decorator" + @register_cell_magic + def cellm(line, cell): + return line, cell + + self.check_ident('cellm') + + def test_cell_magic_reg(self): + "Cell magic manually registered" + def cellm(line, cell): + return line, cell + + _ip.register_magic_function(cellm, 'cell', 'cellm2') + self.check_ident('cellm2') + + def test_cell_magic_class(self): + "Cell magics declared via a class" + @magics_class + class MyMagics(Magics): + + @cell_magic + def cellm3(self, line, cell): + return line, cell + + _ip.register_magics(MyMagics) + self.check_ident('cellm3') + + def test_cell_magic_class2(self): + "Cell magics declared via a class, #2" + @magics_class + class MyMagics2(Magics): + + @cell_magic('cellm4') + def cellm33(self, line, cell): + return line, cell + + _ip.register_magics(MyMagics2) + self.check_ident('cellm4') + # Check that nothing is registered as 'cellm33' + c33 = _ip.find_cell_magic('cellm33') + assert c33 == None + +def test_file(): + """Basic %%writefile""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "file1") + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +@dec.skip_win32 +def test_file_single_quote(): + """Basic %%writefile with embedded single quotes""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "'file1'") + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +@dec.skip_win32 +def test_file_double_quote(): + """Basic %%writefile with embedded double quotes""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, '"file1"') + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +def test_file_var_expand(): + """%%writefile $filename""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "file1") + ip.user_ns["filename"] = fname + ip.run_cell_magic( + "writefile", + "$filename", + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +def test_file_unicode(): + """%%writefile with unicode cell""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, 'file1') + ip.run_cell_magic("writefile", fname, u'\n'.join([ + u'liné1', + u'liné2', + ])) + with io.open(fname, encoding='utf-8') as f: + s = f.read() + assert "liné1\n" in s + assert "liné2" in s + + +def test_file_amend(): + """%%writefile -a amends files""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "file2") + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + ip.run_cell_magic( + "writefile", + "-a %s" % fname, + "\n".join( + [ + "line3", + "line4", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line3\n" in s + + +def test_file_spaces(): + """%%file with spaces in filename""" + ip = get_ipython() + with TemporaryWorkingDirectory() as td: + fname = "file name" + ip.run_cell_magic( + "file", + '"%s"' % fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +def test_script_config(): + ip = get_ipython() + ip.config.ScriptMagics.script_magics = ['whoda'] + sm = script.ScriptMagics(shell=ip) + assert "whoda" in sm.magics["cell"] + + +def test_script_out(): + ip = get_ipython() + ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')") + assert ip.user_ns["output"].strip() == "hi" + + +def test_script_err(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--err error {sys.executable}", + "import sys; print('hello', file=sys.stderr)", + ) + assert ip.user_ns["error"].strip() == "hello" + + +def test_script_out_err(): + + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--out output --err error {sys.executable}", + "\n".join( + [ + "import sys", + "print('hi')", + "print('hello', file=sys.stderr)", + ] + ), + ) + assert ip.user_ns["output"].strip() == "hi" + assert ip.user_ns["error"].strip() == "hello" + + +async def test_script_bg_out(): + ip = get_ipython() + ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')") + assert (await ip.user_ns["output"].read()).strip() == b"hi" + assert ip.user_ns["output"].at_eof() + + +async def test_script_bg_err(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--bg --err error {sys.executable}", + "import sys; print('hello', file=sys.stderr)", + ) + assert (await ip.user_ns["error"].read()).strip() == b"hello" + assert ip.user_ns["error"].at_eof() + + +async def test_script_bg_out_err(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--bg --out output --err error {sys.executable}", + "\n".join( + [ + "import sys", + "print('hi')", + "print('hello', file=sys.stderr)", + ] + ), + ) + assert (await ip.user_ns["output"].read()).strip() == b"hi" + assert (await ip.user_ns["error"].read()).strip() == b"hello" + assert ip.user_ns["output"].at_eof() + assert ip.user_ns["error"].at_eof() + + +async def test_script_bg_proc(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--bg --out output --proc p {sys.executable}", + "\n".join( + [ + "import sys", + "print('hi')", + "print('hello', file=sys.stderr)", + ] + ), + ) + p = ip.user_ns["p"] + await p.wait() + assert p.returncode == 0 + assert (await p.stdout.read()).strip() == b"hi" + # not captured, so empty + assert (await p.stderr.read()) == b"" + assert p.stdout.at_eof() + assert p.stderr.at_eof() + + +def test_script_defaults(): + ip = get_ipython() + for cmd in ['sh', 'bash', 'perl', 'ruby']: + try: + find_cmd(cmd) + except Exception: + pass + else: + assert cmd in ip.magics_manager.magics["cell"] + + +@magics_class +class FooFoo(Magics): + """class with both %foo and %%foo magics""" + @line_magic('foo') + def line_foo(self, line): + "I am line foo" + pass + + @cell_magic("foo") + def cell_foo(self, line, cell): + "I am cell foo, not line foo" + pass + +def test_line_cell_info(): + """%%foo and %foo magics are distinguishable to inspect""" + ip = get_ipython() + ip.magics_manager.register(FooFoo) + oinfo = ip.object_inspect("foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + + oinfo = ip.object_inspect("%%foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + assert oinfo["docstring"] == FooFoo.cell_foo.__doc__ + + oinfo = ip.object_inspect("%foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + assert oinfo["docstring"] == FooFoo.line_foo.__doc__ + + +def test_multiple_magics(): + ip = get_ipython() + foo1 = FooFoo(ip) + foo2 = FooFoo(ip) + mm = ip.magics_manager + mm.register(foo1) + assert mm.magics["line"]["foo"].__self__ is foo1 + mm.register(foo2) + assert mm.magics["line"]["foo"].__self__ is foo2 + + +def test_alias_magic(): + """Test %alias_magic.""" + ip = get_ipython() + mm = ip.magics_manager + + # Basic operation: both cell and line magics are created, if possible. + ip.run_line_magic("alias_magic", "timeit_alias timeit") + assert "timeit_alias" in mm.magics["line"] + assert "timeit_alias" in mm.magics["cell"] + + # --cell is specified, line magic not created. + ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit") + assert "timeit_cell_alias" not in mm.magics["line"] + assert "timeit_cell_alias" in mm.magics["cell"] + + # Test that line alias is created successfully. + ip.run_line_magic("alias_magic", "--line env_alias env") + assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "") + + # Test that line alias with parameters passed in is created successfully. + ip.run_line_magic( + "alias_magic", "--line history_alias history --params " + shlex.quote("3") + ) + assert "history_alias" in mm.magics["line"] + + +def test_save(): + """Test %save.""" + ip = get_ipython() + ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print(a, b())"] + for i, cmd in enumerate(cmds, start=1): + ip.history_manager.store_inputs(i, cmd) + with TemporaryDirectory() as tmpdir: + file = os.path.join(tmpdir, "testsave.py") + ip.run_line_magic("save", "%s 1-10" % file) + content = Path(file).read_text(encoding="utf-8") + assert content.count(cmds[0]) == 1 + assert "coding: utf-8" in content + ip.run_line_magic("save", "-a %s 1-10" % file) + content = Path(file).read_text(encoding="utf-8") + assert content.count(cmds[0]) == 2 + assert "coding: utf-8" in content + + +def test_save_with_no_args(): + ip = get_ipython() + ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"] + for i, cmd in enumerate(cmds, start=1): + ip.history_manager.store_inputs(i, cmd) + + with TemporaryDirectory() as tmpdir: + path = os.path.join(tmpdir, "testsave.py") + ip.run_line_magic("save", path) + content = Path(path).read_text(encoding="utf-8") + expected_content = dedent( + """\ + # coding: utf-8 + a=1 + def b(): + return a**2 + print(a, b()) + """ + ) + assert content == expected_content + + +def test_store(): + """Test %store.""" + ip = get_ipython() + ip.run_line_magic('load_ext', 'storemagic') + + # make sure the storage is empty + ip.run_line_magic("store", "-z") + ip.user_ns["var"] = 42 + ip.run_line_magic("store", "var") + ip.user_ns["var"] = 39 + ip.run_line_magic("store", "-r") + assert ip.user_ns["var"] == 42 + + ip.run_line_magic("store", "-d var") + ip.user_ns["var"] = 39 + ip.run_line_magic("store", "-r") + assert ip.user_ns["var"] == 39 + + +def _run_edit_test(arg_s, exp_filename=None, + exp_lineno=-1, + exp_contents=None, + exp_is_temp=None): + ip = get_ipython() + M = code.CodeMagics(ip) + last_call = ['',''] + opts,args = M.parse_options(arg_s,'prxn:') + filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) + + if exp_filename is not None: + assert exp_filename == filename + if exp_contents is not None: + with io.open(filename, 'r', encoding='utf-8') as f: + contents = f.read() + assert exp_contents == contents + if exp_lineno != -1: + assert exp_lineno == lineno + if exp_is_temp is not None: + assert exp_is_temp == is_temp + + +def test_edit_interactive(): + """%edit on interactively defined objects""" + ip = get_ipython() + n = ip.execution_count + ip.run_cell("def foo(): return 1", store_history=True) + + with pytest.raises(code.InteractivelyDefined) as e: + _run_edit_test("foo") + assert e.value.index == n + + +def test_edit_cell(): + """%edit [cell id]""" + ip = get_ipython() + + ip.run_cell("def foo(): return 1", store_history=True) + + # test + _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) + +def test_edit_fname(): + """%edit file""" + # test + _run_edit_test("test file.py", exp_filename="test file.py") + +def test_bookmark(): + ip = get_ipython() + ip.run_line_magic('bookmark', 'bmname') + with tt.AssertPrints('bmname'): + ip.run_line_magic('bookmark', '-l') + ip.run_line_magic('bookmark', '-d bmname') + +def test_ls_magic(): + ip = get_ipython() + json_formatter = ip.display_formatter.formatters['application/json'] + json_formatter.enabled = True + lsmagic = ip.magic('lsmagic') + with warnings.catch_warnings(record=True) as w: + j = json_formatter(lsmagic) + assert sorted(j) == ["cell", "line"] + assert w == [] # no warnings + + +def test_strip_initial_indent(): + def sii(s): + lines = s.splitlines() + return '\n'.join(code.strip_initial_indent(lines)) + + assert sii(" a = 1\nb = 2") == "a = 1\nb = 2" + assert sii(" a\n b\nc") == "a\n b\nc" + assert sii("a\n b") == "a\n b" + +def test_logging_magic_quiet_from_arg(): + _ip.config.LoggingMagics.quiet = False + lm = logging.LoggingMagics(shell=_ip) + with TemporaryDirectory() as td: + try: + with tt.AssertNotPrints(re.compile("Activating.*")): + lm.logstart('-q {}'.format( + os.path.join(td, "quiet_from_arg.log"))) + finally: + _ip.logger.logstop() + +def test_logging_magic_quiet_from_config(): + _ip.config.LoggingMagics.quiet = True + lm = logging.LoggingMagics(shell=_ip) + with TemporaryDirectory() as td: + try: + with tt.AssertNotPrints(re.compile("Activating.*")): + lm.logstart(os.path.join(td, "quiet_from_config.log")) + finally: + _ip.logger.logstop() + + +def test_logging_magic_not_quiet(): + _ip.config.LoggingMagics.quiet = False + lm = logging.LoggingMagics(shell=_ip) + with TemporaryDirectory() as td: + try: + with tt.AssertPrints(re.compile("Activating.*")): + lm.logstart(os.path.join(td, "not_quiet.log")) + finally: + _ip.logger.logstop() + + +def test_time_no_var_expand(): + _ip.user_ns['a'] = 5 + _ip.user_ns['b'] = [] + _ip.magic('time b.append("{a}")') + assert _ip.user_ns['b'] == ['{a}'] + + +# this is slow, put at the end for local testing. +def test_timeit_arguments(): + "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" + _ip.magic("timeit -n1 -r1 a=('#')") + + +MINIMAL_LAZY_MAGIC = """ +from IPython.core.magic import ( + Magics, + magics_class, + line_magic, + cell_magic, +) + + +@magics_class +class LazyMagics(Magics): + @line_magic + def lazy_line(self, line): + print("Lazy Line") + + @cell_magic + def lazy_cell(self, line, cell): + print("Lazy Cell") + + +def load_ipython_extension(ipython): + ipython.register_magics(LazyMagics) +""" + + +def test_lazy_magics(): + with pytest.raises(UsageError): + ip.run_line_magic("lazy_line", "") + + startdir = os.getcwd() + + with TemporaryDirectory() as tmpdir: + with prepended_to_syspath(tmpdir): + ptempdir = Path(tmpdir) + tf = ptempdir / "lazy_magic_module.py" + tf.write_text(MINIMAL_LAZY_MAGIC) + ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3]) + with tt.AssertPrints("Lazy Line"): + ip.run_line_magic("lazy_line", "") + + +TEST_MODULE = """ +print('Loaded my_tmp') +if __name__ == "__main__": + print('I just ran a script') +""" + +def test_run_module_from_import_hook(): + "Test that a module can be loaded via an import hook" + with TemporaryDirectory() as tmpdir: + fullpath = os.path.join(tmpdir, "my_tmp.py") + Path(fullpath).write_text(TEST_MODULE, encoding="utf-8") + + import importlib.abc + import importlib.util + + class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader): + def find_spec(self, fullname, path, target=None): + if fullname == "my_tmp": + return importlib.util.spec_from_loader(fullname, self) + + def get_filename(self, fullname): + assert fullname == "my_tmp" + return fullpath + + def get_data(self, path): + assert Path(path).samefile(fullpath) + return Path(fullpath).read_text(encoding="utf-8") + + sys.meta_path.insert(0, MyTempImporter()) + + with capture_output() as captured: + _ip.magic("run -m my_tmp") + _ip.run_cell("import my_tmp") + + output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" + assert output == captured.stdout + + sys.meta_path.pop(0) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic_arguments.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic_arguments.py new file mode 100644 index 0000000..8b263b2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic_arguments.py @@ -0,0 +1,140 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011, IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +import argparse +import sys + +from IPython.core.magic_arguments import (argument, argument_group, kwds, + magic_arguments, parse_argstring, real_name) + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +def magic_foo1(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo1, args) + + +@magic_arguments() +def magic_foo2(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo2, args) + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +@argument_group('Group') +@argument('-b', '--bar', help="a grouped argument") +@argument_group('Second Group') +@argument('-z', '--baz', help="another grouped argument") +def magic_foo3(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo3, args) + + +@magic_arguments() +@kwds(argument_default=argparse.SUPPRESS) +@argument('-f', '--foo', help="an argument") +def magic_foo4(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo4, args) + + +@magic_arguments('frobnicate') +@argument('-f', '--foo', help="an argument") +def magic_foo5(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo5, args) + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +def magic_magic_foo(self, args): + """ A docstring. + """ + return parse_argstring(magic_magic_foo, args) + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +def foo(self, args): + """ A docstring. + """ + return parse_argstring(foo, args) + + +def test_magic_arguments(): + # “optional arguments†was replaced with “options†in argparse help + # https://docs.python.org/3/whatsnew/3.10.html#argparse + # https://bugs.python.org/issue9694 + options = "optional arguments" if sys.version_info < (3, 10) else "options" + + assert ( + magic_foo1.__doc__ + == f"::\n\n %foo1 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_foo1, "argcmd_name", None) == None + assert real_name(magic_foo1) == "foo1" + assert magic_foo1(None, "") == argparse.Namespace(foo=None) + assert hasattr(magic_foo1, "has_arguments") + + assert magic_foo2.__doc__ == "::\n\n %foo2\n\n A docstring.\n" + assert getattr(magic_foo2, "argcmd_name", None) == None + assert real_name(magic_foo2) == "foo2" + assert magic_foo2(None, "") == argparse.Namespace() + assert hasattr(magic_foo2, "has_arguments") + + assert ( + magic_foo3.__doc__ + == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n" + ) + assert getattr(magic_foo3, "argcmd_name", None) == None + assert real_name(magic_foo3) == "foo3" + assert magic_foo3(None, "") == argparse.Namespace(bar=None, baz=None, foo=None) + assert hasattr(magic_foo3, "has_arguments") + + assert ( + magic_foo4.__doc__ + == f"::\n\n %foo4 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_foo4, "argcmd_name", None) == None + assert real_name(magic_foo4) == "foo4" + assert magic_foo4(None, "") == argparse.Namespace() + assert hasattr(magic_foo4, "has_arguments") + + assert ( + magic_foo5.__doc__ + == f"::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate" + assert real_name(magic_foo5) == "frobnicate" + assert magic_foo5(None, "") == argparse.Namespace(foo=None) + assert hasattr(magic_foo5, "has_arguments") + + assert ( + magic_magic_foo.__doc__ + == f"::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_magic_foo, "argcmd_name", None) == None + assert real_name(magic_magic_foo) == "magic_foo" + assert magic_magic_foo(None, "") == argparse.Namespace(foo=None) + assert hasattr(magic_magic_foo, "has_arguments") + + assert ( + foo.__doc__ + == f"::\n\n %foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(foo, "argcmd_name", None) == None + assert real_name(foo) == "foo" + assert foo(None, "") == argparse.Namespace(foo=None) + assert hasattr(foo, "has_arguments") diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic_terminal.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic_terminal.py new file mode 100644 index 0000000..f090147 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_magic_terminal.py @@ -0,0 +1,212 @@ +"""Tests for various magic functions specific to the terminal frontend.""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys +from io import StringIO +from unittest import TestCase + +from IPython.testing import tools as tt +#----------------------------------------------------------------------------- +# Test functions begin +#----------------------------------------------------------------------------- + + +MINIMAL_LAZY_MAGIC = """ +from IPython.core.magic import ( + Magics, + magics_class, + line_magic, + cell_magic, +) + + +@magics_class +class LazyMagics(Magics): + @line_magic + def lazy_line(self, line): + print("Lazy Line") + + @cell_magic + def lazy_cell(self, line, cell): + print("Lazy Cell") + + +def load_ipython_extension(ipython): + ipython.register_magics(LazyMagics) +""" + +def check_cpaste(code, should_fail=False): + """Execute code via 'cpaste' and ensure it was executed, unless + should_fail is set. + """ + ip.user_ns['code_ran'] = False + + src = StringIO() + src.write(code) + src.write('\n--\n') + src.seek(0) + + stdin_save = sys.stdin + sys.stdin = src + + try: + context = tt.AssertPrints if should_fail else tt.AssertNotPrints + with context("Traceback (most recent call last)"): + ip.run_line_magic("cpaste", "") + + if not should_fail: + assert ip.user_ns['code_ran'], "%r failed" % code + finally: + sys.stdin = stdin_save + +def test_cpaste(): + """Test cpaste magic""" + + def runf(): + """Marker function: sets a flag when executed. + """ + ip.user_ns['code_ran'] = True + return 'runf' # return string so '+ runf()' doesn't result in success + + tests = {'pass': ["runf()", + "In [1]: runf()", + "In [1]: if 1:\n ...: runf()", + "> > > runf()", + ">>> runf()", + " >>> runf()", + ], + + 'fail': ["1 + runf()", + "++ runf()", + ]} + + ip.user_ns['runf'] = runf + + for code in tests['pass']: + check_cpaste(code) + + for code in tests['fail']: + check_cpaste(code, should_fail=True) + + + +class PasteTestCase(TestCase): + """Multiple tests for clipboard pasting""" + + def paste(self, txt, flags='-q'): + """Paste input text, by default in quiet mode""" + ip.hooks.clipboard_get = lambda: txt + ip.run_line_magic("paste", flags) + + def setUp(self): + # Inject fake clipboard hook but save original so we can restore it later + self.original_clip = ip.hooks.clipboard_get + + def tearDown(self): + # Restore original hook + ip.hooks.clipboard_get = self.original_clip + + def test_paste(self): + ip.user_ns.pop("x", None) + self.paste("x = 1") + self.assertEqual(ip.user_ns["x"], 1) + ip.user_ns.pop("x") + + def test_paste_pyprompt(self): + ip.user_ns.pop("x", None) + self.paste(">>> x=2") + self.assertEqual(ip.user_ns["x"], 2) + ip.user_ns.pop("x") + + def test_paste_py_multi(self): + self.paste(""" + >>> x = [1,2,3] + >>> y = [] + >>> for i in x: + ... y.append(i**2) + ... + """ + ) + self.assertEqual(ip.user_ns["x"], [1, 2, 3]) + self.assertEqual(ip.user_ns["y"], [1, 4, 9]) + + def test_paste_py_multi_r(self): + "Now, test that self.paste -r works" + self.test_paste_py_multi() + self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3]) + self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9]) + self.assertFalse("x" in ip.user_ns) + ip.run_line_magic("paste", "-r") + self.assertEqual(ip.user_ns["x"], [1, 2, 3]) + self.assertEqual(ip.user_ns["y"], [1, 4, 9]) + + def test_paste_email(self): + "Test pasting of email-quoted contents" + self.paste("""\ + >> def foo(x): + >> return x + 1 + >> xx = foo(1.1)""" + ) + self.assertEqual(ip.user_ns["xx"], 2.1) + + def test_paste_email2(self): + "Email again; some programs add a space also at each quoting level" + self.paste("""\ + > > def foo(x): + > > return x + 1 + > > yy = foo(2.1) """ + ) + self.assertEqual(ip.user_ns["yy"], 3.1) + + def test_paste_email_py(self): + "Email quoting of interactive input" + self.paste("""\ + >> >>> def f(x): + >> ... return x+1 + >> ... + >> >>> zz = f(2.5) """ + ) + self.assertEqual(ip.user_ns["zz"], 3.5) + + def test_paste_echo(self): + "Also test self.paste echoing, by temporarily faking the writer" + w = StringIO() + old_write = sys.stdout.write + sys.stdout.write = w.write + code = """ + a = 100 + b = 200""" + try: + self.paste(code,'') + out = w.getvalue() + finally: + sys.stdout.write = old_write + self.assertEqual(ip.user_ns["a"], 100) + self.assertEqual(ip.user_ns["b"], 200) + assert out == code + "\n## -- End pasted text --\n" + + def test_paste_leading_commas(self): + "Test multiline strings with leading commas" + tm = ip.magics_manager.registry['TerminalMagics'] + s = '''\ +a = """ +,1,2,3 +"""''' + ip.user_ns.pop("foo", None) + tm.store_or_execute(s, "foo") + self.assertIn("foo", ip.user_ns) + + def test_paste_trailing_question(self): + "Test pasting sources with trailing question marks" + tm = ip.magics_manager.registry['TerminalMagics'] + s = '''\ +def funcfoo(): + if True: #am i true? + return 'fooresult' +''' + ip.user_ns.pop('funcfoo', None) + self.paste(s) + self.assertEqual(ip.user_ns["funcfoo"](), "fooresult") diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_oinspect.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_oinspect.py new file mode 100644 index 0000000..94deb35 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_oinspect.py @@ -0,0 +1,476 @@ +"""Tests for the object inspection functionality. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from inspect import signature, Signature, Parameter +import inspect +import os +import pytest +import re +import sys + +from .. import oinspect + +from decorator import decorator + +from IPython.testing.tools import AssertPrints, AssertNotPrints +from IPython.utils.path import compress_user + + +#----------------------------------------------------------------------------- +# Globals and constants +#----------------------------------------------------------------------------- + +inspector = None + +def setup_module(): + global inspector + inspector = oinspect.Inspector() + + +class SourceModuleMainTest: + __module__ = "__main__" + + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + +# WARNING: since this test checks the line number where a function is +# defined, if any code is inserted above, the following line will need to be +# updated. Do NOT insert any whitespace between the next line and the function +# definition below. +THIS_LINE_NUMBER = 46 # Put here the actual number of this line + + +def test_find_source_lines(): + assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3 + assert oinspect.find_source_lines(type) is None + assert oinspect.find_source_lines(SourceModuleMainTest) is None + assert oinspect.find_source_lines(SourceModuleMainTest()) is None + + +def test_getsource(): + assert oinspect.getsource(type) is None + assert oinspect.getsource(SourceModuleMainTest) is None + assert oinspect.getsource(SourceModuleMainTest()) is None + + +def test_inspect_getfile_raises_exception(): + """Check oinspect.find_file/getsource/find_source_lines expectations""" + with pytest.raises(TypeError): + inspect.getfile(type) + with pytest.raises(OSError if sys.version_info >= (3, 10) else TypeError): + inspect.getfile(SourceModuleMainTest) + + +# A couple of utilities to ensure these tests work the same from a source or a +# binary install +def pyfile(fname): + return os.path.normcase(re.sub('.py[co]$', '.py', fname)) + + +def match_pyfiles(f1, f2): + assert pyfile(f1) == pyfile(f2) + + +def test_find_file(): + match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__)) + assert oinspect.find_file(type) is None + assert oinspect.find_file(SourceModuleMainTest) is None + assert oinspect.find_file(SourceModuleMainTest()) is None + + +def test_find_file_decorated1(): + + @decorator + def noop1(f): + def wrapper(*a, **kw): + return f(*a, **kw) + return wrapper + + @noop1 + def f(x): + "My docstring" + + match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) + assert f.__doc__ == "My docstring" + + +def test_find_file_decorated2(): + + @decorator + def noop2(f, *a, **kw): + return f(*a, **kw) + + @noop2 + @noop2 + @noop2 + def f(x): + "My docstring 2" + + match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) + assert f.__doc__ == "My docstring 2" + + +def test_find_file_magic(): + run = ip.find_line_magic('run') + assert oinspect.find_file(run) is not None + + +# A few generic objects we can then inspect in the tests below + +class Call(object): + """This is the class docstring.""" + + def __init__(self, x, y=1): + """This is the constructor docstring.""" + + def __call__(self, *a, **kw): + """This is the call docstring.""" + + def method(self, x, z=2): + """Some method's docstring""" + +class HasSignature(object): + """This is the class docstring.""" + __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)]) + + def __init__(self, *args): + """This is the init docstring""" + + +class SimpleClass(object): + def method(self, x, z=2): + """Some method's docstring""" + + +class Awkward(object): + def __getattr__(self, name): + raise Exception(name) + +class NoBoolCall: + """ + callable with `__bool__` raising should still be inspect-able. + """ + + def __call__(self): + """does nothing""" + pass + + def __bool__(self): + """just raise NotImplemented""" + raise NotImplementedError('Must be implemented') + + +class SerialLiar(object): + """Attribute accesses always get another copy of the same class. + + unittest.mock.call does something similar, but it's not ideal for testing + as the failure mode is to eat all your RAM. This gives up after 10k levels. + """ + def __init__(self, max_fibbing_twig, lies_told=0): + if lies_told > 10000: + raise RuntimeError('Nose too long, honesty is the best policy') + self.max_fibbing_twig = max_fibbing_twig + self.lies_told = lies_told + max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told) + + def __getattr__(self, item): + return SerialLiar(self.max_fibbing_twig, self.lies_told + 1) + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +def test_info(): + "Check that Inspector.info fills out various fields as expected." + i = inspector.info(Call, oname="Call") + assert i["type_name"] == "type" + expected_class = str(type(type)) # (Python 3) or + assert i["base_class"] == expected_class + assert re.search( + "", + i["string_form"], + ) + fname = __file__ + if fname.endswith(".pyc"): + fname = fname[:-1] + # case-insensitive comparison needed on some filesystems + # e.g. Windows: + assert i["file"].lower() == compress_user(fname).lower() + assert i["definition"] == None + assert i["docstring"] == Call.__doc__ + assert i["source"] == None + assert i["isclass"] is True + assert i["init_definition"] == "Call(x, y=1)" + assert i["init_docstring"] == Call.__init__.__doc__ + + i = inspector.info(Call, detail_level=1) + assert i["source"] is not None + assert i["docstring"] == None + + c = Call(1) + c.__doc__ = "Modified instance docstring" + i = inspector.info(c) + assert i["type_name"] == "Call" + assert i["docstring"] == "Modified instance docstring" + assert i["class_docstring"] == Call.__doc__ + assert i["init_docstring"] == Call.__init__.__doc__ + assert i["call_docstring"] == Call.__call__.__doc__ + + +def test_class_signature(): + info = inspector.info(HasSignature, "HasSignature") + assert info["init_definition"] == "HasSignature(test)" + assert info["init_docstring"] == HasSignature.__init__.__doc__ + + +def test_info_awkward(): + # Just test that this doesn't throw an error. + inspector.info(Awkward()) + +def test_bool_raise(): + inspector.info(NoBoolCall()) + +def test_info_serialliar(): + fib_tracker = [0] + inspector.info(SerialLiar(fib_tracker)) + + # Nested attribute access should be cut off at 100 levels deep to avoid + # infinite loops: https://github.com/ipython/ipython/issues/9122 + assert fib_tracker[0] < 9000 + +def support_function_one(x, y=2, *a, **kw): + """A simple function.""" + +def test_calldef_none(): + # We should ignore __call__ for all of these. + for obj in [support_function_one, SimpleClass().method, any, str.upper]: + i = inspector.info(obj) + assert i["call_def"] is None + + +def f_kwarg(pos, *, kwonly): + pass + +def test_definition_kwonlyargs(): + i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore + assert i["definition"] == "f_kwarg(pos, *, kwonly)" + + +def test_getdoc(): + class A(object): + """standard docstring""" + pass + + class B(object): + """standard docstring""" + def getdoc(self): + return "custom docstring" + + class C(object): + """standard docstring""" + def getdoc(self): + return None + + a = A() + b = B() + c = C() + + assert oinspect.getdoc(a) == "standard docstring" + assert oinspect.getdoc(b) == "custom docstring" + assert oinspect.getdoc(c) == "standard docstring" + + +def test_empty_property_has_no_source(): + i = inspector.info(property(), detail_level=1) + assert i["source"] is None + + +def test_property_sources(): + # A simple adder whose source and signature stays + # the same across Python distributions + def simple_add(a, b): + "Adds two numbers" + return a + b + + class A(object): + @property + def foo(self): + return 'bar' + + foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) + + dname = property(oinspect.getdoc) + adder = property(simple_add) + + i = inspector.info(A.foo, detail_level=1) + assert "def foo(self):" in i["source"] + assert "lambda self, v:" in i["source"] + + i = inspector.info(A.dname, detail_level=1) + assert "def getdoc(obj)" in i["source"] + + i = inspector.info(A.adder, detail_level=1) + assert "def simple_add(a, b)" in i["source"] + + +def test_property_docstring_is_in_info_for_detail_level_0(): + class A(object): + @property + def foobar(self): + """This is `foobar` property.""" + pass + + ip.user_ns["a_obj"] = A() + assert ( + "This is `foobar` property." + == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"] + ) + + ip.user_ns["a_cls"] = A + assert ( + "This is `foobar` property." + == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"] + ) + + +def test_pdef(): + # See gh-1914 + def foo(): pass + inspector.pdef(foo, 'foo') + + +def test_pinfo_nonascii(): + # See gh-1177 + from . import nonascii2 + ip.user_ns['nonascii2'] = nonascii2 + ip._inspect('pinfo', 'nonascii2', detail_level=1) + +def test_pinfo_type(): + """ + type can fail in various edge case, for example `type.__subclass__()` + """ + ip._inspect('pinfo', 'type') + + +def test_pinfo_docstring_no_source(): + """Docstring should be included with detail_level=1 if there is no source""" + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'str.format', detail_level=0) + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'str.format', detail_level=1) + + +def test_pinfo_no_docstring_if_source(): + """Docstring should not be included with detail_level=1 if source is found""" + def foo(): + """foo has a docstring""" + + ip.user_ns['foo'] = foo + + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=0) + with AssertPrints('Source:'): + ip._inspect('pinfo', 'foo', detail_level=1) + with AssertNotPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=1) + + +def test_pinfo_docstring_if_detail_and_no_source(): + """ Docstring should be displayed if source info not available """ + obj_def = '''class Foo(object): + """ This is a docstring for Foo """ + def bar(self): + """ This is a docstring for Foo.bar """ + pass + ''' + + ip.run_cell(obj_def) + ip.run_cell('foo = Foo()') + + with AssertNotPrints("Source:"): + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=0) + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=1) + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo.bar', detail_level=0) + + with AssertNotPrints('Docstring:'): + with AssertPrints('Source:'): + ip._inspect('pinfo', 'foo.bar', detail_level=1) + + +def test_pinfo_magic(): + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'lsmagic', detail_level=0) + + with AssertPrints('Source:'): + ip._inspect('pinfo', 'lsmagic', detail_level=1) + + +def test_init_colors(): + # ensure colors are not present in signature info + info = inspector.info(HasSignature) + init_def = info["init_definition"] + assert "[0m" not in init_def + + +def test_builtin_init(): + info = inspector.info(list) + init_def = info['init_definition'] + assert init_def is not None + + +def test_render_signature_short(): + def short_fun(a=1): pass + sig = oinspect._render_signature( + signature(short_fun), + short_fun.__name__, + ) + assert sig == "short_fun(a=1)" + + +def test_render_signature_long(): + from typing import Optional + + def long_function( + a_really_long_parameter: int, + and_another_long_one: bool = False, + let_us_make_sure_this_is_looong: Optional[str] = None, + ) -> bool: pass + + sig = oinspect._render_signature( + signature(long_function), + long_function.__name__, + ) + assert sig in [ + # Python >=3.9 + '''\ +long_function( + a_really_long_parameter: int, + and_another_long_one: bool = False, + let_us_make_sure_this_is_looong: Optional[str] = None, +) -> bool\ +''', + # Python >=3.7 + '''\ +long_function( + a_really_long_parameter: int, + and_another_long_one: bool = False, + let_us_make_sure_this_is_looong: Union[str, NoneType] = None, +) -> bool\ +''', # Python <=3.6 + '''\ +long_function( + a_really_long_parameter:int, + and_another_long_one:bool=False, + let_us_make_sure_this_is_looong:Union[str, NoneType]=None, +) -> bool\ +''', + ] diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_page.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_page.py new file mode 100644 index 0000000..9f6a374 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_page.py @@ -0,0 +1,20 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- +import io + +# N.B. For the test suite, page.page is overridden (see IPython.testing.globalipapp) +from IPython.core import page + +def test_detect_screen_size(): + """Simple smoketest for page._detect_screen_size.""" + try: + page._detect_screen_size(True, 25) + except (TypeError, io.UnsupportedOperation): + # This can happen in the test suite, because stdout may not have a + # fileno. + pass diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_paths.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_paths.py new file mode 100644 index 0000000..eb754b8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_paths.py @@ -0,0 +1,201 @@ +import errno +import os +import shutil +import sys +import tempfile +import warnings +from unittest.mock import patch + +from tempfile import TemporaryDirectory +from testpath import assert_isdir, assert_isfile, modified_env + +from IPython import paths +from IPython.testing.decorators import skip_win32 + +TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp()) +HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir") +XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir") +XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir") +IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython') + +def setup_module(): + """Setup testenvironment for the module: + + - Adds dummy home dir tree + """ + # Do not mask exceptions here. In particular, catching WindowsError is a + # problem because that exception is only defined on Windows... + os.makedirs(IP_TEST_DIR) + os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython')) + os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython')) + + +def teardown_module(): + """Teardown testenvironment for the module: + + - Remove dummy home dir tree + """ + # Note: we remove the parent test dir, which is the root of all test + # subdirs we may have created. Use shutil instead of os.removedirs, so + # that non-empty directories are all recursively removed. + shutil.rmtree(TMP_TEST_DIR) + +def patch_get_home_dir(dirpath): + return patch.object(paths, 'get_home_dir', return_value=dirpath) + + +def test_get_ipython_dir_1(): + """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions.""" + env_ipdir = os.path.join("someplace", ".ipython") + with patch.object(paths, '_writable_dir', return_value=True), \ + modified_env({'IPYTHONDIR': env_ipdir}): + ipdir = paths.get_ipython_dir() + + assert ipdir == env_ipdir + +def test_get_ipython_dir_2(): + """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions.""" + with patch_get_home_dir('someplace'), \ + patch.object(paths, 'get_xdg_dir', return_value=None), \ + patch.object(paths, '_writable_dir', return_value=True), \ + patch('os.name', "posix"), \ + modified_env({'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': None + }): + ipdir = paths.get_ipython_dir() + + assert ipdir == os.path.join("someplace", ".ipython") + +def test_get_ipython_dir_3(): + """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist.""" + tmphome = TemporaryDirectory() + try: + with patch_get_home_dir(tmphome.name), \ + patch('os.name', 'posix'), \ + modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': XDG_TEST_DIR, + }), warnings.catch_warnings(record=True) as w: + ipdir = paths.get_ipython_dir() + + assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython") + assert len(w) == 0 + finally: + tmphome.cleanup() + +def test_get_ipython_dir_4(): + """test_get_ipython_dir_4, warn if XDG and home both exist.""" + with patch_get_home_dir(HOME_TEST_DIR), \ + patch('os.name', 'posix'): + try: + os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython')) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + + with modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': XDG_TEST_DIR, + }), warnings.catch_warnings(record=True) as w: + ipdir = paths.get_ipython_dir() + + assert len(w) == 1 + assert "Ignoring" in str(w[0]) + + +def test_get_ipython_dir_5(): + """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist.""" + with patch_get_home_dir(HOME_TEST_DIR), \ + patch('os.name', 'posix'): + try: + os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython')) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + with modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': XDG_TEST_DIR, + }): + ipdir = paths.get_ipython_dir() + + assert ipdir == IP_TEST_DIR + +def test_get_ipython_dir_6(): + """test_get_ipython_dir_6, use home over XDG if defined and neither exist.""" + xdg = os.path.join(HOME_TEST_DIR, 'somexdg') + os.mkdir(xdg) + shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython')) + print(paths._writable_dir) + with patch_get_home_dir(HOME_TEST_DIR), \ + patch.object(paths, 'get_xdg_dir', return_value=xdg), \ + patch('os.name', 'posix'), \ + modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': None, + }), warnings.catch_warnings(record=True) as w: + ipdir = paths.get_ipython_dir() + + assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython") + assert len(w) == 0 + +def test_get_ipython_dir_7(): + """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR""" + home_dir = os.path.normpath(os.path.expanduser('~')) + with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \ + patch.object(paths, '_writable_dir', return_value=True): + ipdir = paths.get_ipython_dir() + assert ipdir == os.path.join(home_dir, "somewhere") + + +@skip_win32 +def test_get_ipython_dir_8(): + """test_get_ipython_dir_8, test / home directory""" + if not os.access("/", os.W_OK): + # test only when HOME directory actually writable + return + + with patch.object(paths, "_writable_dir", lambda path: bool(path)), patch.object( + paths, "get_xdg_dir", return_value=None + ), modified_env( + { + "IPYTHON_DIR": None, + "IPYTHONDIR": None, + "HOME": "/", + } + ): + assert paths.get_ipython_dir() == "/.ipython" + + +def test_get_ipython_cache_dir(): + with modified_env({'HOME': HOME_TEST_DIR}): + if os.name == "posix": + # test default + os.makedirs(os.path.join(HOME_TEST_DIR, ".cache")) + with modified_env({'XDG_CACHE_HOME': None}): + ipdir = paths.get_ipython_cache_dir() + assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir + assert_isdir(ipdir) + + # test env override + with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}): + ipdir = paths.get_ipython_cache_dir() + assert_isdir(ipdir) + assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython") + else: + assert paths.get_ipython_cache_dir() == paths.get_ipython_dir() + +def test_get_ipython_package_dir(): + ipdir = paths.get_ipython_package_dir() + assert_isdir(ipdir) + + +def test_get_ipython_module_path(): + ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp') + assert_isfile(ipapp_path) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_prefilter.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_prefilter.py new file mode 100644 index 0000000..91c3c86 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_prefilter.py @@ -0,0 +1,127 @@ +"""Tests for input manipulation machinery.""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import pytest + +from IPython.core.prefilter import AutocallChecker + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +def test_prefilter(): + """Test user input conversions""" + + # pairs of (raw, expected correct) input + pairs = [ ('2+2','2+2'), + ] + + for raw, correct in pairs: + assert ip.prefilter(raw) == correct + +def test_prefilter_shadowed(): + def dummy_magic(line): pass + + prev_automagic_state = ip.automagic + ip.automagic = True + ip.autocall = 0 + + try: + # These should not be transformed - they are shadowed by other names + for name in ['if', 'zip', 'get_ipython']: # keyword, builtin, global + ip.register_magic_function(dummy_magic, magic_name=name) + res = ip.prefilter(name + " foo") + assert res == name + " foo" + del ip.magics_manager.magics["line"][name] + + # These should be transformed + for name in ['fi', 'piz', 'nohtypi_teg']: + ip.register_magic_function(dummy_magic, magic_name=name) + res = ip.prefilter(name + " foo") + assert res != name + " foo" + del ip.magics_manager.magics["line"][name] + + finally: + ip.automagic = prev_automagic_state + +def test_autocall_binops(): + """See https://github.com/ipython/ipython/issues/81""" + ip.magic('autocall 2') + f = lambda x: x + ip.user_ns['f'] = f + try: + assert ip.prefilter("f 1") == "f(1)" + for t in ["f +1", "f -1"]: + assert ip.prefilter(t) == t + + # Run tests again with a more permissive exclude_regexp, which will + # allow transformation of binary operations ('f -1' -> 'f(-1)'). + pm = ip.prefilter_manager + ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, + config=pm.config) + try: + ac.priority = 1 + ac.exclude_regexp = r'^[,&^\|\*/]|^is |^not |^in |^and |^or ' + pm.sort_checkers() + + assert ip.prefilter("f -1") == "f(-1)" + assert ip.prefilter("f +1") == "f(+1)" + finally: + pm.unregister_checker(ac) + finally: + ip.magic('autocall 0') + del ip.user_ns['f'] + + +def test_issue_114(): + """Check that multiline string literals don't expand as magic + see http://github.com/ipython/ipython/issues/114""" + + template = '"""\n%s\n"""' + # Store the current value of multi_line_specials and turn it off before + # running test, since it could be true (case in which the test doesn't make + # sense, as multiline string literals *will* expand as magic in that case). + msp = ip.prefilter_manager.multi_line_specials + ip.prefilter_manager.multi_line_specials = False + try: + for mgk in ip.magics_manager.lsmagic()['line']: + raw = template % mgk + assert ip.prefilter(raw) == raw + finally: + ip.prefilter_manager.multi_line_specials = msp + + +def test_prefilter_attribute_errors(): + """Capture exceptions thrown by user objects on attribute access. + + See http://github.com/ipython/ipython/issues/988.""" + + class X(object): + def __getattr__(self, k): + raise ValueError('broken object') + def __call__(self, x): + return x + + # Create a callable broken object + ip.user_ns['x'] = X() + ip.magic('autocall 2') + try: + # Even if x throws an attribute error when looking at its rewrite + # attribute, we should not crash. So the test here is simply making + # the prefilter call and not having an exception. + ip.prefilter('x 1') + finally: + del ip.user_ns['x'] + ip.magic('autocall 0') + + +def test_autocall_should_support_unicode(): + ip.magic('autocall 2') + ip.user_ns['π'] = lambda x: x + try: + assert ip.prefilter("π 3") == "π(3)" + finally: + ip.magic('autocall 0') + del ip.user_ns['π'] diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_profile.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_profile.py new file mode 100644 index 0000000..a0de2f2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_profile.py @@ -0,0 +1,155 @@ +# coding: utf-8 +"""Tests for profile-related functions. + +Currently only the startup-dir functionality is tested, but more tests should +be added for: + + * ipython profile create + * ipython profile list + * ipython profile create --parallel + * security dir permissions + +Authors +------- + +* MinRK + +""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import shutil +import sys +import tempfile +from pathlib import Path +from unittest import TestCase + +from tempfile import TemporaryDirectory + +from IPython.core.profileapp import list_bundled_profiles, list_profiles_in +from IPython.core.profiledir import ProfileDir +from IPython.testing import decorators as dec +from IPython.testing import tools as tt +from IPython.utils.process import getoutput + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- +TMP_TEST_DIR = Path(tempfile.mkdtemp()) +HOME_TEST_DIR = TMP_TEST_DIR / "home_test_dir" +IP_TEST_DIR = HOME_TEST_DIR / ".ipython" + +# +# Setup/teardown functions/decorators +# + +def setup_module(): + """Setup test environment for the module: + + - Adds dummy home dir tree + """ + # Do not mask exceptions here. In particular, catching WindowsError is a + # problem because that exception is only defined on Windows... + (Path.cwd() / IP_TEST_DIR).mkdir(parents=True) + + +def teardown_module(): + """Teardown test environment for the module: + + - Remove dummy home dir tree + """ + # Note: we remove the parent test dir, which is the root of all test + # subdirs we may have created. Use shutil instead of os.removedirs, so + # that non-empty directories are all recursively removed. + shutil.rmtree(TMP_TEST_DIR) + + +#----------------------------------------------------------------------------- +# Test functions +#----------------------------------------------------------------------------- +class ProfileStartupTest(TestCase): + def setUp(self): + # create profile dir + self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, "test") + self.options = ["--ipython-dir", IP_TEST_DIR, "--profile", "test"] + self.fname = TMP_TEST_DIR / "test.py" + + def tearDown(self): + # We must remove this profile right away so its presence doesn't + # confuse other tests. + shutil.rmtree(self.pd.location) + + def init(self, startup_file, startup, test): + # write startup python file + with open(Path(self.pd.startup_dir) / startup_file, "w", encoding="utf-8") as f: + f.write(startup) + # write simple test file, to check that the startup file was run + with open(self.fname, "w", encoding="utf-8") as f: + f.write(test) + + def validate(self, output): + tt.ipexec_validate(self.fname, output, "", options=self.options) + + def test_startup_py(self): + self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n') + self.validate('123') + + def test_startup_ipy(self): + self.init('00-start.ipy', '%xmode plain\n', '') + self.validate('Exception reporting mode: Plain') + + +def test_list_profiles_in(): + # No need to remove these directories and files, as they will get nuked in + # the module-level teardown. + td = Path(tempfile.mkdtemp(dir=TMP_TEST_DIR)) + for name in ("profile_foo", "profile_hello", "not_a_profile"): + Path(td / name).mkdir(parents=True) + if dec.unicode_paths: + Path(td / u"profile_ünicode").mkdir(parents=True) + + with open(td / "profile_file", "w", encoding="utf-8") as f: + f.write("I am not a profile directory") + profiles = list_profiles_in(td) + + # unicode normalization can turn u'ünicode' into u'u\0308nicode', + # so only check for *nicode, and that creating a ProfileDir from the + # name remains valid + found_unicode = False + for p in list(profiles): + if p.endswith('nicode'): + pd = ProfileDir.find_profile_dir_by_name(td, p) + profiles.remove(p) + found_unicode = True + break + if dec.unicode_paths: + assert found_unicode is True + assert set(profiles) == {"foo", "hello"} + + +def test_list_bundled_profiles(): + # This variable will need to be updated when a new profile gets bundled + bundled = sorted(list_bundled_profiles()) + assert bundled == [] + + +def test_profile_create_ipython_dir(): + """ipython profile create respects --ipython-dir""" + with TemporaryDirectory() as td: + getoutput( + [ + sys.executable, + "-m", + "IPython", + "profile", + "create", + "foo", + "--ipython-dir=%s" % td, + ] + ) + profile_dir = Path(td) / "profile_foo" + assert Path(profile_dir).exists() + ipython_config = profile_dir / "ipython_config.py" + assert Path(ipython_config).exists() diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_prompts.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_prompts.py new file mode 100644 index 0000000..95e6163 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_prompts.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 +"""Tests for prompt generation.""" + +import unittest + +from IPython.core.prompts import LazyEvaluate + +class PromptTests(unittest.TestCase): + def test_lazy_eval_unicode(self): + u = u'ünicødé' + lz = LazyEvaluate(lambda : u) + self.assertEqual(str(lz), u) + self.assertEqual(format(lz), u) + + def test_lazy_eval_nonascii_bytes(self): + u = u'ünicødé' + b = u.encode('utf8') + lz = LazyEvaluate(lambda : b) + # unicode(lz) would fail + self.assertEqual(str(lz), str(b)) + self.assertEqual(format(lz), str(b)) + + def test_lazy_eval_float(self): + f = 0.503 + lz = LazyEvaluate(lambda : f) + + self.assertEqual(str(lz), str(f)) + self.assertEqual(format(lz), str(f)) + self.assertEqual(format(lz, '.1'), '0.5') + diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_pylabtools.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_pylabtools.py new file mode 100644 index 0000000..7888637 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_pylabtools.py @@ -0,0 +1,274 @@ +"""Tests for pylab tools module. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from binascii import a2b_base64 +from io import BytesIO + +import pytest + +matplotlib = pytest.importorskip("matplotlib") +matplotlib.use('Agg') +from matplotlib.figure import Figure + +from matplotlib import pyplot as plt +from matplotlib_inline import backend_inline +import numpy as np + +from IPython.core.getipython import get_ipython +from IPython.core.interactiveshell import InteractiveShell +from IPython.core.display import _PNG, _JPEG +from .. import pylabtools as pt + +from IPython.testing import decorators as dec + + +def test_figure_to_svg(): + # simple empty-figure test + fig = plt.figure() + assert pt.print_figure(fig, "svg") is None + + plt.close('all') + + # simple check for at least svg-looking output + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + svg = pt.print_figure(fig, "svg")[:100].lower() + assert "doctype svg" in svg + + +def _check_pil_jpeg_bytes(): + """Skip if PIL can't write JPEGs to BytesIO objects""" + # PIL's JPEG plugin can't write to BytesIO objects + # Pillow fixes this + from PIL import Image + buf = BytesIO() + img = Image.new("RGB", (4,4)) + try: + img.save(buf, 'jpeg') + except Exception as e: + ename = e.__class__.__name__ + raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e + +@dec.skip_without("PIL.Image") +def test_figure_to_jpeg(): + _check_pil_jpeg_bytes() + # simple check for at least jpeg-looking output + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower() + assert jpeg.startswith(_JPEG) + +def test_retina_figure(): + # simple empty-figure test + fig = plt.figure() + assert pt.retina_figure(fig) == None + plt.close('all') + + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + png, md = pt.retina_figure(fig) + assert png.startswith(_PNG) + assert "width" in md + assert "height" in md + + +_fmt_mime_map = { + 'png': 'image/png', + 'jpeg': 'image/jpeg', + 'pdf': 'application/pdf', + 'retina': 'image/png', + 'svg': 'image/svg+xml', +} + +def test_select_figure_formats_str(): + ip = get_ipython() + for fmt, active_mime in _fmt_mime_map.items(): + pt.select_figure_formats(ip, fmt) + for mime, f in ip.display_formatter.formatters.items(): + if mime == active_mime: + assert Figure in f + else: + assert Figure not in f + +def test_select_figure_formats_kwargs(): + ip = get_ipython() + kwargs = dict(bbox_inches="tight") + pt.select_figure_formats(ip, "png", **kwargs) + formatter = ip.display_formatter.formatters["image/png"] + f = formatter.lookup_by_type(Figure) + cell = f.keywords + expected = kwargs + expected["base64"] = True + expected["fmt"] = "png" + assert cell == expected + + # check that the formatter doesn't raise + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + formatter.enabled = True + png = formatter(fig) + assert isinstance(png, str) + png_bytes = a2b_base64(png) + assert png_bytes.startswith(_PNG) + +def test_select_figure_formats_set(): + ip = get_ipython() + for fmts in [ + {'png', 'svg'}, + ['png'], + ('jpeg', 'pdf', 'retina'), + {'svg'}, + ]: + active_mimes = {_fmt_mime_map[fmt] for fmt in fmts} + pt.select_figure_formats(ip, fmts) + for mime, f in ip.display_formatter.formatters.items(): + if mime in active_mimes: + assert Figure in f + else: + assert Figure not in f + +def test_select_figure_formats_bad(): + ip = get_ipython() + with pytest.raises(ValueError): + pt.select_figure_formats(ip, 'foo') + with pytest.raises(ValueError): + pt.select_figure_formats(ip, {'png', 'foo'}) + with pytest.raises(ValueError): + pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad']) + +def test_import_pylab(): + ns = {} + pt.import_pylab(ns, import_all=False) + assert "plt" in ns + assert ns["np"] == np + + +from traitlets.config import Config + + +class TestPylabSwitch(object): + class Shell(InteractiveShell): + def init_history(self): + """Sets up the command history, and starts regular autosaves.""" + self.config.HistoryManager.hist_file = ":memory:" + super().init_history() + + def enable_gui(self, gui): + pass + + def setup(self): + import matplotlib + def act_mpl(backend): + matplotlib.rcParams['backend'] = backend + + # Save rcParams since they get modified + self._saved_rcParams = matplotlib.rcParams + self._saved_rcParamsOrig = matplotlib.rcParamsOrig + matplotlib.rcParams = dict(backend='Qt4Agg') + matplotlib.rcParamsOrig = dict(backend='Qt4Agg') + + # Mock out functions + self._save_am = pt.activate_matplotlib + pt.activate_matplotlib = act_mpl + self._save_ip = pt.import_pylab + pt.import_pylab = lambda *a,**kw:None + self._save_cis = backend_inline.configure_inline_support + backend_inline.configure_inline_support = lambda *a, **kw: None + + def teardown(self): + pt.activate_matplotlib = self._save_am + pt.import_pylab = self._save_ip + backend_inline.configure_inline_support = self._save_cis + import matplotlib + matplotlib.rcParams = self._saved_rcParams + matplotlib.rcParamsOrig = self._saved_rcParamsOrig + + def test_qt(self): + + s = self.Shell() + gui, backend = s.enable_matplotlib(None) + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib() + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + def test_inline(self): + s = self.Shell() + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == None + + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == None + + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + def test_inline_twice(self): + "Using '%matplotlib inline' twice should not reset formatters" + + ip = self.Shell() + gui, backend = ip.enable_matplotlib("inline") + assert gui == "inline" + + fmts = {'png'} + active_mimes = {_fmt_mime_map[fmt] for fmt in fmts} + pt.select_figure_formats(ip, fmts) + + gui, backend = ip.enable_matplotlib("inline") + assert gui == "inline" + + for mime, f in ip.display_formatter.formatters.items(): + if mime in active_mimes: + assert Figure in f + else: + assert Figure not in f + + def test_qt_gtk(self): + s = self.Shell() + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("gtk") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + +def test_no_gui_backends(): + for k in ['agg', 'svg', 'pdf', 'ps']: + assert k not in pt.backend2gui + + +def test_figure_no_canvas(): + fig = Figure() + fig.canvas = None + pt.print_figure(fig) diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_run.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_run.py new file mode 100644 index 0000000..ae20ce6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_run.py @@ -0,0 +1,622 @@ +# encoding: utf-8 +"""Tests for code execution (%run and related), which is particularly tricky. + +Because of how %run manages namespaces, and the fact that we are trying here to +verify subtle object deletion and reference counting issues, the %run tests +will be kept in this separate file. This makes it easier to aggregate in one +place the tricks needed to handle it; most other magics are much easier to test +and we do so in a common test_magic file. + +Note that any test using `run -i` should make sure to do a `reset` afterwards, +as otherwise it may influence later tests. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + + +import functools +import os +import platform +import random +import string +import sys +import textwrap +import unittest +from os.path import join as pjoin +from unittest.mock import patch + +import pytest +from tempfile import TemporaryDirectory + +from IPython.core import debugger +from IPython.testing import decorators as dec +from IPython.testing import tools as tt +from IPython.utils.io import capture_output + + +def doctest_refbug(): + """Very nasty problem with references held by multiple runs of a script. + See: https://github.com/ipython/ipython/issues/141 + + In [1]: _ip.clear_main_mod_cache() + # random + + In [2]: %run refbug + + In [3]: call_f() + lowercased: hello + + In [4]: %run refbug + + In [5]: call_f() + lowercased: hello + lowercased: hello + """ + + +def doctest_run_builtins(): + r"""Check that %run doesn't damage __builtins__. + + In [1]: import tempfile + + In [2]: bid1 = id(__builtins__) + + In [3]: fname = tempfile.mkstemp('.py')[1] + + In [3]: f = open(fname, 'w', encoding='utf-8') + + In [4]: dummy= f.write('pass\n') + + In [5]: f.flush() + + In [6]: t1 = type(__builtins__) + + In [7]: %run $fname + + In [7]: f.close() + + In [8]: bid2 = id(__builtins__) + + In [9]: t2 = type(__builtins__) + + In [10]: t1 == t2 + Out[10]: True + + In [10]: bid1 == bid2 + Out[10]: True + + In [12]: try: + ....: os.unlink(fname) + ....: except: + ....: pass + ....: + """ + + +def doctest_run_option_parser(): + r"""Test option parser in %run. + + In [1]: %run print_argv.py + [] + + In [2]: %run print_argv.py print*.py + ['print_argv.py'] + + In [3]: %run -G print_argv.py print*.py + ['print*.py'] + + """ + + +@dec.skip_win32 +def doctest_run_option_parser_for_posix(): + r"""Test option parser in %run (Linux/OSX specific). + + You need double quote to escape glob in POSIX systems: + + In [1]: %run print_argv.py print\\*.py + ['print*.py'] + + You can't use quote to escape glob in POSIX systems: + + In [2]: %run print_argv.py 'print*.py' + ['print_argv.py'] + + """ + + +doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32" + + +@dec.skip_if_not_win32 +def doctest_run_option_parser_for_windows(): + r"""Test option parser in %run (Windows specific). + + In Windows, you can't escape ``*` `by backslash: + + In [1]: %run print_argv.py print\\*.py + ['print\\\\*.py'] + + You can use quote to escape glob: + + In [2]: %run print_argv.py 'print*.py' + ["'print*.py'"] + + """ + + +doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32" + + +def doctest_reset_del(): + """Test that resetting doesn't cause errors in __del__ methods. + + In [2]: class A(object): + ...: def __del__(self): + ...: print(str("Hi")) + ...: + + In [3]: a = A() + + In [4]: get_ipython().reset(); import gc; x = gc.collect(0) + Hi + + In [5]: 1+1 + Out[5]: 2 + """ + +# For some tests, it will be handy to organize them in a class with a common +# setup that makes a temp file + +class TestMagicRunPass(tt.TempFileMixin): + + def setUp(self): + content = "a = [1,2,3]\nb = 1" + self.mktmp(content) + + def run_tmpfile(self): + _ip = get_ipython() + # This fails on Windows if self.tmpfile.name has spaces or "~" in it. + # See below and ticket https://bugs.launchpad.net/bugs/366353 + _ip.magic('run %s' % self.fname) + + def run_tmpfile_p(self): + _ip = get_ipython() + # This fails on Windows if self.tmpfile.name has spaces or "~" in it. + # See below and ticket https://bugs.launchpad.net/bugs/366353 + _ip.magic('run -p %s' % self.fname) + + def test_builtins_id(self): + """Check that %run doesn't damage __builtins__ """ + _ip = get_ipython() + # Test that the id of __builtins__ is not modified by %run + bid1 = id(_ip.user_ns['__builtins__']) + self.run_tmpfile() + bid2 = id(_ip.user_ns['__builtins__']) + assert bid1 == bid2 + + def test_builtins_type(self): + """Check that the type of __builtins__ doesn't change with %run. + + However, the above could pass if __builtins__ was already modified to + be a dict (it should be a module) by a previous use of %run. So we + also check explicitly that it really is a module: + """ + _ip = get_ipython() + self.run_tmpfile() + assert type(_ip.user_ns["__builtins__"]) == type(sys) + + def test_run_profile(self): + """Test that the option -p, which invokes the profiler, do not + crash by invoking execfile""" + self.run_tmpfile_p() + + def test_run_debug_twice(self): + # https://github.com/ipython/ipython/issues/10028 + _ip = get_ipython() + with tt.fake_input(['c']): + _ip.magic('run -d %s' % self.fname) + with tt.fake_input(['c']): + _ip.magic('run -d %s' % self.fname) + + def test_run_debug_twice_with_breakpoint(self): + """Make a valid python temp file.""" + _ip = get_ipython() + with tt.fake_input(['b 2', 'c', 'c']): + _ip.magic('run -d %s' % self.fname) + + with tt.fake_input(['c']): + with tt.AssertNotPrints('KeyError'): + _ip.magic('run -d %s' % self.fname) + + +class TestMagicRunSimple(tt.TempFileMixin): + + def test_simpledef(self): + """Test that simple class definitions work.""" + src = ("class foo: pass\n" + "def f(): return foo()") + self.mktmp(src) + _ip.magic("run %s" % self.fname) + _ip.run_cell("t = isinstance(f(), foo)") + assert _ip.user_ns["t"] is True + + @pytest.mark.xfail( + platform.python_implementation() == "PyPy", + reason="expecting __del__ call on exit is unreliable and doesn't happen on PyPy", + ) + def test_obj_del(self): + """Test that object's __del__ methods are called on exit.""" + src = ("class A(object):\n" + " def __del__(self):\n" + " print('object A deleted')\n" + "a = A()\n") + self.mktmp(src) + err = None + tt.ipexec_validate(self.fname, 'object A deleted', err) + + def test_aggressive_namespace_cleanup(self): + """Test that namespace cleanup is not too aggressive GH-238 + + Returning from another run magic deletes the namespace""" + # see ticket https://github.com/ipython/ipython/issues/238 + + with tt.TempFileMixin() as empty: + empty.mktmp("") + # On Windows, the filename will have \users in it, so we need to use the + # repr so that the \u becomes \\u. + src = ( + "ip = get_ipython()\n" + "for i in range(5):\n" + " try:\n" + " ip.magic(%r)\n" + " except NameError as e:\n" + " print(i)\n" + " break\n" % ("run " + empty.fname) + ) + self.mktmp(src) + _ip.magic("run %s" % self.fname) + _ip.run_cell("ip == get_ipython()") + assert _ip.user_ns["i"] == 4 + + def test_run_second(self): + """Test that running a second file doesn't clobber the first, gh-3547""" + self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n") + + with tt.TempFileMixin() as empty: + empty.mktmp("") + + _ip.magic("run %s" % self.fname) + _ip.magic("run %s" % empty.fname) + assert _ip.user_ns["afunc"]() == 1 + + def test_tclass(self): + mydir = os.path.dirname(__file__) + tc = os.path.join(mydir, "tclass") + src = f"""\ +import gc +%run "{tc}" C-first +gc.collect(0) +%run "{tc}" C-second +gc.collect(0) +%run "{tc}" C-third +gc.collect(0) +%reset -f +""" + self.mktmp(src, ".ipy") + out = """\ +ARGV 1-: ['C-first'] +ARGV 1-: ['C-second'] +tclass.py: deleting object: C-first +ARGV 1-: ['C-third'] +tclass.py: deleting object: C-second +tclass.py: deleting object: C-third +""" + err = None + tt.ipexec_validate(self.fname, out, err) + + def test_run_i_after_reset(self): + """Check that %run -i still works after %reset (gh-693)""" + src = "yy = zz\n" + self.mktmp(src) + _ip.run_cell("zz = 23") + try: + _ip.magic("run -i %s" % self.fname) + assert _ip.user_ns["yy"] == 23 + finally: + _ip.magic('reset -f') + + _ip.run_cell("zz = 23") + try: + _ip.magic("run -i %s" % self.fname) + assert _ip.user_ns["yy"] == 23 + finally: + _ip.magic('reset -f') + + def test_unicode(self): + """Check that files in odd encodings are accepted.""" + mydir = os.path.dirname(__file__) + na = os.path.join(mydir, 'nonascii.py') + _ip.magic('run "%s"' % na) + assert _ip.user_ns["u"] == "Ўт№Ф" + + def test_run_py_file_attribute(self): + """Test handling of `__file__` attribute in `%run .py`.""" + src = "t = __file__\n" + self.mktmp(src) + _missing = object() + file1 = _ip.user_ns.get('__file__', _missing) + _ip.magic('run %s' % self.fname) + file2 = _ip.user_ns.get('__file__', _missing) + + # Check that __file__ was equal to the filename in the script's + # namespace. + assert _ip.user_ns["t"] == self.fname + + # Check that __file__ was not leaked back into user_ns. + assert file1 == file2 + + def test_run_ipy_file_attribute(self): + """Test handling of `__file__` attribute in `%run `.""" + src = "t = __file__\n" + self.mktmp(src, ext='.ipy') + _missing = object() + file1 = _ip.user_ns.get('__file__', _missing) + _ip.magic('run %s' % self.fname) + file2 = _ip.user_ns.get('__file__', _missing) + + # Check that __file__ was equal to the filename in the script's + # namespace. + assert _ip.user_ns["t"] == self.fname + + # Check that __file__ was not leaked back into user_ns. + assert file1 == file2 + + def test_run_formatting(self): + """ Test that %run -t -N does not raise a TypeError for N > 1.""" + src = "pass" + self.mktmp(src) + _ip.magic('run -t -N 1 %s' % self.fname) + _ip.magic('run -t -N 10 %s' % self.fname) + + def test_ignore_sys_exit(self): + """Test the -e option to ignore sys.exit()""" + src = "import sys; sys.exit(1)" + self.mktmp(src) + with tt.AssertPrints('SystemExit'): + _ip.magic('run %s' % self.fname) + + with tt.AssertNotPrints('SystemExit'): + _ip.magic('run -e %s' % self.fname) + + def test_run_nb(self): + """Test %run notebook.ipynb""" + pytest.importorskip("nbformat") + from nbformat import v4, writes + nb = v4.new_notebook( + cells=[ + v4.new_markdown_cell("The Ultimate Question of Everything"), + v4.new_code_cell("answer=42") + ] + ) + src = writes(nb, version=4) + self.mktmp(src, ext='.ipynb') + + _ip.magic("run %s" % self.fname) + + assert _ip.user_ns["answer"] == 42 + + def test_run_nb_error(self): + """Test %run notebook.ipynb error""" + pytest.importorskip("nbformat") + from nbformat import v4, writes + + # %run when a file name isn't provided + pytest.raises(Exception, _ip.magic, "run") + + # %run when a file doesn't exist + pytest.raises(Exception, _ip.magic, "run foobar.ipynb") + + # %run on a notebook with an error + nb = v4.new_notebook( + cells=[ + v4.new_code_cell("0/0") + ] + ) + src = writes(nb, version=4) + self.mktmp(src, ext='.ipynb') + pytest.raises(Exception, _ip.magic, "run %s" % self.fname) + + def test_file_options(self): + src = ('import sys\n' + 'a = " ".join(sys.argv[1:])\n') + self.mktmp(src) + test_opts = "-x 3 --verbose" + _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts)) + assert _ip.user_ns["a"] == test_opts + + +class TestMagicRunWithPackage(unittest.TestCase): + + def writefile(self, name, content): + path = os.path.join(self.tempdir.name, name) + d = os.path.dirname(path) + if not os.path.isdir(d): + os.makedirs(d) + with open(path, "w", encoding="utf-8") as f: + f.write(textwrap.dedent(content)) + + def setUp(self): + self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)])) + """Temporary (probably) valid python package name.""" + + self.value = int(random.random() * 10000) + + self.tempdir = TemporaryDirectory() + self.__orig_cwd = os.getcwd() + sys.path.insert(0, self.tempdir.name) + + self.writefile(os.path.join(package, '__init__.py'), '') + self.writefile(os.path.join(package, 'sub.py'), """ + x = {0!r} + """.format(self.value)) + self.writefile(os.path.join(package, 'relative.py'), """ + from .sub import x + """) + self.writefile(os.path.join(package, 'absolute.py'), """ + from {0}.sub import x + """.format(package)) + self.writefile(os.path.join(package, 'args.py'), """ + import sys + a = " ".join(sys.argv[1:]) + """.format(package)) + + def tearDown(self): + os.chdir(self.__orig_cwd) + sys.path[:] = [p for p in sys.path if p != self.tempdir.name] + self.tempdir.cleanup() + + def check_run_submodule(self, submodule, opts=''): + _ip.user_ns.pop('x', None) + _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts)) + self.assertEqual(_ip.user_ns['x'], self.value, + 'Variable `x` is not loaded from module `{0}`.' + .format(submodule)) + + def test_run_submodule_with_absolute_import(self): + self.check_run_submodule('absolute') + + def test_run_submodule_with_relative_import(self): + """Run submodule that has a relative import statement (#2727).""" + self.check_run_submodule('relative') + + def test_prun_submodule_with_absolute_import(self): + self.check_run_submodule('absolute', '-p') + + def test_prun_submodule_with_relative_import(self): + self.check_run_submodule('relative', '-p') + + def with_fake_debugger(func): + @functools.wraps(func) + def wrapper(*args, **kwds): + with patch.object(debugger.Pdb, 'run', staticmethod(eval)): + return func(*args, **kwds) + return wrapper + + @with_fake_debugger + def test_debug_run_submodule_with_absolute_import(self): + self.check_run_submodule('absolute', '-d') + + @with_fake_debugger + def test_debug_run_submodule_with_relative_import(self): + self.check_run_submodule('relative', '-d') + + def test_module_options(self): + _ip.user_ns.pop("a", None) + test_opts = "-x abc -m test" + _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts)) + assert _ip.user_ns["a"] == test_opts + + def test_module_options_with_separator(self): + _ip.user_ns.pop("a", None) + test_opts = "-x abc -m test" + _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts)) + assert _ip.user_ns["a"] == test_opts + + +def test_run__name__(): + with TemporaryDirectory() as td: + path = pjoin(td, "foo.py") + with open(path, "w", encoding="utf-8") as f: + f.write("q = __name__") + + _ip.user_ns.pop("q", None) + _ip.magic("run {}".format(path)) + assert _ip.user_ns.pop("q") == "__main__" + + _ip.magic("run -n {}".format(path)) + assert _ip.user_ns.pop("q") == "foo" + + try: + _ip.magic("run -i -n {}".format(path)) + assert _ip.user_ns.pop("q") == "foo" + finally: + _ip.magic('reset -f') + + +def test_run_tb(): + """Test traceback offset in %run""" + with TemporaryDirectory() as td: + path = pjoin(td, "foo.py") + with open(path, "w", encoding="utf-8") as f: + f.write( + "\n".join( + [ + "def foo():", + " return bar()", + "def bar():", + " raise RuntimeError('hello!')", + "foo()", + ] + ) + ) + with capture_output() as io: + _ip.magic('run {}'.format(path)) + out = io.stdout + assert "execfile" not in out + assert "RuntimeError" in out + assert out.count("---->") == 3 + del ip.user_ns['bar'] + del ip.user_ns['foo'] + + +def test_multiprocessing_run(): + """Set we can run mutiprocesgin without messing up up main namespace + + Note that import `nose.tools as nt` mdify the value s + sys.module['__mp_main__'] so we need to temporarily set it to None to test + the issue. + """ + with TemporaryDirectory() as td: + mpm = sys.modules.get('__mp_main__') + sys.modules['__mp_main__'] = None + try: + path = pjoin(td, "test.py") + with open(path, "w", encoding="utf-8") as f: + f.write("import multiprocessing\nprint('hoy')") + with capture_output() as io: + _ip.run_line_magic('run', path) + _ip.run_cell("i_m_undefined") + out = io.stdout + assert "hoy" in out + assert "AttributeError" not in out + assert "NameError" in out + assert out.count("---->") == 1 + except: + raise + finally: + sys.modules['__mp_main__'] = mpm + + +def test_script_tb(): + """Test traceback offset in `ipython script.py`""" + with TemporaryDirectory() as td: + path = pjoin(td, "foo.py") + with open(path, "w", encoding="utf-8") as f: + f.write( + "\n".join( + [ + "def foo():", + " return bar()", + "def bar():", + " raise RuntimeError('hello!')", + "foo()", + ] + ) + ) + out, err = tt.ipexec(path) + assert "execfile" not in out + assert "RuntimeError" in out + assert out.count("---->") == 3 diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_shellapp.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_shellapp.py new file mode 100644 index 0000000..9f4f87b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_shellapp.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +"""Tests for shellapp module. + +Authors +------- +* Bradley Froehle +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import unittest + +from IPython.testing import decorators as dec +from IPython.testing import tools as tt + + +class TestFileToRun(tt.TempFileMixin, unittest.TestCase): + """Test the behavior of the file_to_run parameter.""" + + def test_py_script_file_attribute(self): + """Test that `__file__` is set when running `ipython file.py`""" + src = "print(__file__)\n" + self.mktmp(src) + + err = None + tt.ipexec_validate(self.fname, self.fname, err) + + def test_ipy_script_file_attribute(self): + """Test that `__file__` is set when running `ipython file.ipy`""" + src = "print(__file__)\n" + self.mktmp(src, ext='.ipy') + + err = None + tt.ipexec_validate(self.fname, self.fname, err) + + # The commands option to ipexec_validate doesn't work on Windows, and it + # doesn't seem worth fixing + @dec.skip_win32 + def test_py_script_file_attribute_interactively(self): + """Test that `__file__` is not set after `ipython -i file.py`""" + src = "True\n" + self.mktmp(src) + + out, err = tt.ipexec( + self.fname, + options=["-i"], + commands=['"__file__" in globals()', "print(123)", "exit()"], + ) + assert "False" in out, f"Subprocess stderr:\n{err}\n-----" diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_splitinput.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_splitinput.py new file mode 100644 index 0000000..8969da2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_splitinput.py @@ -0,0 +1,38 @@ +# coding: utf-8 + +from IPython.core.splitinput import split_user_input, LineInfo +from IPython.testing import tools as tt + +tests = [ + ("x=1", ("", "", "x", "=1")), + ("?", ("", "?", "", "")), + ("??", ("", "??", "", "")), + (" ?", (" ", "?", "", "")), + (" ??", (" ", "??", "", "")), + ("??x", ("", "??", "x", "")), + ("?x=1", ("", "?", "x", "=1")), + ("!ls", ("", "!", "ls", "")), + (" !ls", (" ", "!", "ls", "")), + ("!!ls", ("", "!!", "ls", "")), + (" !!ls", (" ", "!!", "ls", "")), + (",ls", ("", ",", "ls", "")), + (";ls", ("", ";", "ls", "")), + (" ;ls", (" ", ";", "ls", "")), + ("f.g(x)", ("", "", "f.g", "(x)")), + ("f.g (x)", ("", "", "f.g", "(x)")), + ("?%hist1", ("", "?", "%hist1", "")), + ("?%%hist2", ("", "?", "%%hist2", "")), + ("??%hist3", ("", "??", "%hist3", "")), + ("??%%hist4", ("", "??", "%%hist4", "")), + ("?x*", ("", "?", "x*", "")), +] +tests.append(("Pérez Fernando", ("", "", "Pérez", "Fernando"))) + + +def test_split_user_input(): + return tt.check_pairs(split_user_input, tests) + +def test_LineInfo(): + """Simple test for LineInfo construction and str()""" + linfo = LineInfo(" %cd /home") + assert str(linfo) == "LineInfo [ |%|cd|/home]" diff --git a/.venv/lib/python3.8/site-packages/IPython/core/tests/test_ultratb.py b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_ultratb.py new file mode 100644 index 0000000..1f49603 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/tests/test_ultratb.py @@ -0,0 +1,409 @@ +# encoding: utf-8 +"""Tests for IPython.core.ultratb +""" +import io +import logging +import os.path +import platform +import re +import sys +import traceback +import unittest +from textwrap import dedent + +from tempfile import TemporaryDirectory + +from IPython.core.ultratb import ColorTB, VerboseTB +from IPython.testing import tools as tt +from IPython.testing.decorators import onlyif_unicode_paths +from IPython.utils.syspathcontext import prepended_to_syspath + +file_1 = """1 +2 +3 +def f(): + 1/0 +""" + +file_2 = """def f(): + 1/0 +""" + + +def recursionlimit(frames): + """ + decorator to set the recursion limit temporarily + """ + + def inner(test_function): + def wrapper(*args, **kwargs): + rl = sys.getrecursionlimit() + sys.setrecursionlimit(frames) + try: + return test_function(*args, **kwargs) + finally: + sys.setrecursionlimit(rl) + + return wrapper + + return inner + + +class ChangedPyFileTest(unittest.TestCase): + def test_changing_py_file(self): + """Traceback produced if the line where the error occurred is missing? + + https://github.com/ipython/ipython/issues/1456 + """ + with TemporaryDirectory() as td: + fname = os.path.join(td, "foo.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(file_1) + + with prepended_to_syspath(td): + ip.run_cell("import foo") + + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + + # Make the file shorter, so the line of the error is missing. + with open(fname, "w", encoding="utf-8") as f: + f.write(file_2) + + # For some reason, this was failing on the *second* call after + # changing the file, so we call f() twice. + with tt.AssertNotPrints("Internal Python error", channel='stderr'): + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + +iso_8859_5_file = u'''# coding: iso-8859-5 + +def fail(): + """дбИЖ""" + 1/0 # дбИЖ +''' + +class NonAsciiTest(unittest.TestCase): + @onlyif_unicode_paths + def test_nonascii_path(self): + # Non-ascii directory name as well. + with TemporaryDirectory(suffix=u'é') as td: + fname = os.path.join(td, u"fooé.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(file_1) + + with prepended_to_syspath(td): + ip.run_cell("import foo") + + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + + def test_iso8859_5(self): + with TemporaryDirectory() as td: + fname = os.path.join(td, 'dfghjkl.py') + + with io.open(fname, 'w', encoding='iso-8859-5') as f: + f.write(iso_8859_5_file) + + with prepended_to_syspath(td): + ip.run_cell("from dfghjkl import fail") + + with tt.AssertPrints("ZeroDivisionError"): + with tt.AssertPrints(u'дбИЖ', suppress=False): + ip.run_cell('fail()') + + def test_nonascii_msg(self): + cell = u"raise Exception('é')" + expected = u"Exception('é')" + ip.run_cell("%xmode plain") + with tt.AssertPrints(expected): + ip.run_cell(cell) + + ip.run_cell("%xmode verbose") + with tt.AssertPrints(expected): + ip.run_cell(cell) + + ip.run_cell("%xmode context") + with tt.AssertPrints(expected): + ip.run_cell(cell) + + ip.run_cell("%xmode minimal") + with tt.AssertPrints(u"Exception: é"): + ip.run_cell(cell) + + # Put this back into Context mode for later tests. + ip.run_cell("%xmode context") + +class NestedGenExprTestCase(unittest.TestCase): + """ + Regression test for the following issues: + https://github.com/ipython/ipython/issues/8293 + https://github.com/ipython/ipython/issues/8205 + """ + def test_nested_genexpr(self): + code = dedent( + """\ + class SpecificException(Exception): + pass + + def foo(x): + raise SpecificException("Success!") + + sum(sum(foo(x) for _ in [0]) for x in [0]) + """ + ) + with tt.AssertPrints('SpecificException: Success!', suppress=False): + ip.run_cell(code) + + +indentationerror_file = """if True: +zoon() +""" + +class IndentationErrorTest(unittest.TestCase): + def test_indentationerror_shows_line(self): + # See issue gh-2398 + with tt.AssertPrints("IndentationError"): + with tt.AssertPrints("zoon()", suppress=False): + ip.run_cell(indentationerror_file) + + with TemporaryDirectory() as td: + fname = os.path.join(td, "foo.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(indentationerror_file) + + with tt.AssertPrints("IndentationError"): + with tt.AssertPrints("zoon()", suppress=False): + ip.magic('run %s' % fname) + +se_file_1 = """1 +2 +7/ +""" + +se_file_2 = """7/ +""" + +class SyntaxErrorTest(unittest.TestCase): + + def test_syntaxerror_no_stacktrace_at_compile_time(self): + syntax_error_at_compile_time = """ +def foo(): + .. +""" + with tt.AssertPrints("SyntaxError"): + ip.run_cell(syntax_error_at_compile_time) + + with tt.AssertNotPrints("foo()"): + ip.run_cell(syntax_error_at_compile_time) + + def test_syntaxerror_stacktrace_when_running_compiled_code(self): + syntax_error_at_runtime = """ +def foo(): + eval("..") + +def bar(): + foo() + +bar() +""" + with tt.AssertPrints("SyntaxError"): + ip.run_cell(syntax_error_at_runtime) + # Assert syntax error during runtime generate stacktrace + with tt.AssertPrints(["foo()", "bar()"]): + ip.run_cell(syntax_error_at_runtime) + del ip.user_ns['bar'] + del ip.user_ns['foo'] + + def test_changing_py_file(self): + with TemporaryDirectory() as td: + fname = os.path.join(td, "foo.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(se_file_1) + + with tt.AssertPrints(["7/", "SyntaxError"]): + ip.magic("run " + fname) + + # Modify the file + with open(fname, "w", encoding="utf-8") as f: + f.write(se_file_2) + + # The SyntaxError should point to the correct line + with tt.AssertPrints(["7/", "SyntaxError"]): + ip.magic("run " + fname) + + def test_non_syntaxerror(self): + # SyntaxTB may be called with an error other than a SyntaxError + # See e.g. gh-4361 + try: + raise ValueError('QWERTY') + except ValueError: + with tt.AssertPrints('QWERTY'): + ip.showsyntaxerror() + +import sys + +if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy": + """ + New 3.9 Pgen Parser does not raise Memory error, except on failed malloc. + """ + class MemoryErrorTest(unittest.TestCase): + def test_memoryerror(self): + memoryerror_code = "(" * 200 + ")" * 200 + with tt.AssertPrints("MemoryError"): + ip.run_cell(memoryerror_code) + + +class Python3ChainedExceptionsTest(unittest.TestCase): + DIRECT_CAUSE_ERROR_CODE = """ +try: + x = 1 + 2 + print(not_defined_here) +except Exception as e: + x += 55 + x - 1 + y = {} + raise KeyError('uh') from e + """ + + EXCEPTION_DURING_HANDLING_CODE = """ +try: + x = 1 + 2 + print(not_defined_here) +except Exception as e: + x += 55 + x - 1 + y = {} + raise KeyError('uh') + """ + + SUPPRESS_CHAINING_CODE = """ +try: + 1/0 +except Exception: + raise ValueError("Yikes") from None + """ + + def test_direct_cause_error(self): + with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): + ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) + + def test_exception_during_handling_error(self): + with tt.AssertPrints(["KeyError", "NameError", "During handling"]): + ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) + + def test_suppress_exception_chaining(self): + with tt.AssertNotPrints("ZeroDivisionError"), \ + tt.AssertPrints("ValueError", suppress=False): + ip.run_cell(self.SUPPRESS_CHAINING_CODE) + + def test_plain_direct_cause_error(self): + with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): + ip.run_cell("%xmode Plain") + ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) + ip.run_cell("%xmode Verbose") + + def test_plain_exception_during_handling_error(self): + with tt.AssertPrints(["KeyError", "NameError", "During handling"]): + ip.run_cell("%xmode Plain") + ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) + ip.run_cell("%xmode Verbose") + + def test_plain_suppress_exception_chaining(self): + with tt.AssertNotPrints("ZeroDivisionError"), \ + tt.AssertPrints("ValueError", suppress=False): + ip.run_cell("%xmode Plain") + ip.run_cell(self.SUPPRESS_CHAINING_CODE) + ip.run_cell("%xmode Verbose") + + +class RecursionTest(unittest.TestCase): + DEFINITIONS = """ +def non_recurs(): + 1/0 + +def r1(): + r1() + +def r3a(): + r3b() + +def r3b(): + r3c() + +def r3c(): + r3a() + +def r3o1(): + r3a() + +def r3o2(): + r3o1() +""" + def setUp(self): + ip.run_cell(self.DEFINITIONS) + + def test_no_recursion(self): + with tt.AssertNotPrints("skipping similar frames"): + ip.run_cell("non_recurs()") + + @recursionlimit(200) + def test_recursion_one_frame(self): + with tt.AssertPrints(re.compile( + r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]") + ): + ip.run_cell("r1()") + + @recursionlimit(160) + def test_recursion_three_frames(self): + with tt.AssertPrints("[... skipping similar frames: "), \ + tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \ + tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \ + tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False): + ip.run_cell("r3o2()") + + +#---------------------------------------------------------------------------- + +# module testing (minimal) +def test_handlers(): + def spam(c, d_e): + (d, e) = d_e + x = c + d + y = c * d + foo(x, y) + + def foo(a, b, bar=1): + eggs(a, b + bar) + + def eggs(f, g, z=globals()): + h = f + g + i = f - g + return h / i + + buff = io.StringIO() + + buff.write('') + buff.write('*** Before ***') + try: + buff.write(spam(1, (2, 3))) + except: + traceback.print_exc(file=buff) + + handler = ColorTB(ostream=buff) + buff.write('*** ColorTB ***') + try: + buff.write(spam(1, (2, 3))) + except: + handler(*sys.exc_info()) + buff.write('') + + handler = VerboseTB(ostream=buff) + buff.write('*** VerboseTB ***') + try: + buff.write(spam(1, (2, 3))) + except: + handler(*sys.exc_info()) + buff.write('') diff --git a/.venv/lib/python3.8/site-packages/IPython/core/ultratb.py b/.venv/lib/python3.8/site-packages/IPython/core/ultratb.py new file mode 100644 index 0000000..8569791 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/ultratb.py @@ -0,0 +1,1207 @@ +# -*- coding: utf-8 -*- +""" +Verbose and colourful traceback formatting. + +**ColorTB** + +I've always found it a bit hard to visually parse tracebacks in Python. The +ColorTB class is a solution to that problem. It colors the different parts of a +traceback in a manner similar to what you would expect from a syntax-highlighting +text editor. + +Installation instructions for ColorTB:: + + import sys,ultratb + sys.excepthook = ultratb.ColorTB() + +**VerboseTB** + +I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds +of useful info when a traceback occurs. Ping originally had it spit out HTML +and intended it for CGI programmers, but why should they have all the fun? I +altered it to spit out colored text to the terminal. It's a bit overwhelming, +but kind of neat, and maybe useful for long-running programs that you believe +are bug-free. If a crash *does* occur in that type of program you want details. +Give it a shot--you'll love it or you'll hate it. + +.. note:: + + The Verbose mode prints the variables currently visible where the exception + happened (shortening their strings if too long). This can potentially be + very slow, if you happen to have a huge data structure whose string + representation is complex to compute. Your computer may appear to freeze for + a while with cpu usage at 100%. If this occurs, you can cancel the traceback + with Ctrl-C (maybe hitting it more than once). + + If you encounter this kind of situation often, you may want to use the + Verbose_novars mode instead of the regular Verbose, which avoids formatting + variables (but otherwise includes the information and context given by + Verbose). + +.. note:: + + The verbose mode print all variables in the stack, which means it can + potentially leak sensitive information like access keys, or unencrypted + password. + +Installation instructions for VerboseTB:: + + import sys,ultratb + sys.excepthook = ultratb.VerboseTB() + +Note: Much of the code in this module was lifted verbatim from the standard +library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'. + +Color schemes +------------- + +The colors are defined in the class TBTools through the use of the +ColorSchemeTable class. Currently the following exist: + + - NoColor: allows all of this module to be used in any terminal (the color + escapes are just dummy blank strings). + + - Linux: is meant to look good in a terminal like the Linux console (black + or very dark background). + + - LightBG: similar to Linux but swaps dark/light colors to be more readable + in light background terminals. + + - Neutral: a neutral color scheme that should be readable on both light and + dark background + +You can implement other color schemes easily, the syntax is fairly +self-explanatory. Please send back new schemes you develop to the author for +possible inclusion in future releases. + +Inheritance diagram: + +.. inheritance-diagram:: IPython.core.ultratb + :parts: 3 +""" + +#***************************************************************************** +# Copyright (C) 2001 Nathaniel Gray +# Copyright (C) 2001-2004 Fernando Perez +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#***************************************************************************** + + +import inspect +import linecache +import pydoc +import sys +import time +import traceback +from types import TracebackType +from typing import Tuple, List, Any, Optional + +import stack_data +from pygments.formatters.terminal256 import Terminal256Formatter +from pygments.styles import get_style_by_name + +# IPython's own modules +from IPython import get_ipython +from IPython.core import debugger +from IPython.core.display_trap import DisplayTrap +from IPython.core.excolors import exception_colors +from IPython.utils import path as util_path +from IPython.utils import py3compat +from IPython.utils.terminal import get_terminal_size + +import IPython.utils.colorable as colorable + +# Globals +# amount of space to put line numbers before verbose tracebacks +INDENT_SIZE = 8 + +# Default color scheme. This is used, for example, by the traceback +# formatter. When running in an actual IPython instance, the user's rc.colors +# value is used, but having a module global makes this functionality available +# to users of ultratb who are NOT running inside ipython. +DEFAULT_SCHEME = 'NoColor' + +# --------------------------------------------------------------------------- +# Code begins + +# Helper function -- largely belongs to VerboseTB, but we need the same +# functionality to produce a pseudo verbose TB for SyntaxErrors, so that they +# can be recognized properly by ipython.el's py-traceback-line-re +# (SyntaxErrors have to be treated specially because they have no traceback) + + +def _format_traceback_lines(lines, Colors, has_colors: bool, lvals): + """ + Format tracebacks lines with pointing arrow, leading numbers... + + Parameters + ---------- + lines : list[Line] + Colors + ColorScheme used. + lvals : str + Values of local variables, already colored, to inject just after the error line. + """ + numbers_width = INDENT_SIZE - 1 + res = [] + + for stack_line in lines: + if stack_line is stack_data.LINE_GAP: + res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal)) + continue + + line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n' + lineno = stack_line.lineno + if stack_line.is_current: + # This is the line with the error + pad = numbers_width - len(str(lineno)) + num = '%s%s' % (debugger.make_arrow(pad), str(lineno)) + start_color = Colors.linenoEm + else: + num = '%*s' % (numbers_width, lineno) + start_color = Colors.lineno + + line = '%s%s%s %s' % (start_color, num, Colors.Normal, line) + + res.append(line) + if lvals and stack_line.is_current: + res.append(lvals + '\n') + return res + + +def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None): + """ + Format filename lines with `In [n]` if it's the nth code cell or `File *.py` if it's a module. + + Parameters + ---------- + file : str + ColorFilename + ColorScheme's filename coloring to be used. + ColorNormal + ColorScheme's normal coloring to be used. + """ + ipinst = get_ipython() + + if ipinst is not None and file in ipinst.compile._filename_map: + file = "[%s]" % ipinst.compile._filename_map[file] + tpl_link = f"Input {ColorFilename}In {{file}}{ColorNormal}" + else: + file = util_path.compress_user( + py3compat.cast_unicode(file, util_path.fs_encoding) + ) + if lineno is None: + tpl_link = f"File {ColorFilename}{{file}}{ColorNormal}" + else: + tpl_link = f"File {ColorFilename}{{file}}:{{lineno}}{ColorNormal}" + + return tpl_link.format(file=file, lineno=lineno) + +#--------------------------------------------------------------------------- +# Module classes +class TBTools(colorable.Colorable): + """Basic tools used by all traceback printer classes.""" + + # Number of frames to skip when reporting tracebacks + tb_offset = 0 + + def __init__( + self, + color_scheme="NoColor", + call_pdb=False, + ostream=None, + parent=None, + config=None, + *, + debugger_cls=None, + ): + # Whether to call the interactive pdb debugger after printing + # tracebacks or not + super(TBTools, self).__init__(parent=parent, config=config) + self.call_pdb = call_pdb + + # Output stream to write to. Note that we store the original value in + # a private attribute and then make the public ostream a property, so + # that we can delay accessing sys.stdout until runtime. The way + # things are written now, the sys.stdout object is dynamically managed + # so a reference to it should NEVER be stored statically. This + # property approach confines this detail to a single location, and all + # subclasses can simply access self.ostream for writing. + self._ostream = ostream + + # Create color table + self.color_scheme_table = exception_colors() + + self.set_colors(color_scheme) + self.old_scheme = color_scheme # save initial value for toggles + self.debugger_cls = debugger_cls or debugger.Pdb + + if call_pdb: + self.pdb = debugger_cls() + else: + self.pdb = None + + def _get_ostream(self): + """Output stream that exceptions are written to. + + Valid values are: + + - None: the default, which means that IPython will dynamically resolve + to sys.stdout. This ensures compatibility with most tools, including + Windows (where plain stdout doesn't recognize ANSI escapes). + + - Any object with 'write' and 'flush' attributes. + """ + return sys.stdout if self._ostream is None else self._ostream + + def _set_ostream(self, val): + assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush')) + self._ostream = val + + ostream = property(_get_ostream, _set_ostream) + + @staticmethod + def _get_chained_exception(exception_value): + cause = getattr(exception_value, "__cause__", None) + if cause: + return cause + if getattr(exception_value, "__suppress_context__", False): + return None + return getattr(exception_value, "__context__", None) + + def get_parts_of_chained_exception( + self, evalue + ) -> Optional[Tuple[type, BaseException, TracebackType]]: + + chained_evalue = self._get_chained_exception(evalue) + + if chained_evalue: + return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ + return None + + def prepare_chained_exception_message(self, cause) -> List[Any]: + direct_cause = "\nThe above exception was the direct cause of the following exception:\n" + exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" + + if cause: + message = [[direct_cause]] + else: + message = [[exception_during_handling]] + return message + + @property + def has_colors(self) -> bool: + return self.color_scheme_table.active_scheme_name.lower() != "nocolor" + + def set_colors(self, *args, **kw): + """Shorthand access to the color table scheme selector method.""" + + # Set own color table + self.color_scheme_table.set_active_scheme(*args, **kw) + # for convenience, set Colors to the active scheme + self.Colors = self.color_scheme_table.active_colors + # Also set colors of debugger + if hasattr(self, 'pdb') and self.pdb is not None: + self.pdb.set_colors(*args, **kw) + + def color_toggle(self): + """Toggle between the currently active color scheme and NoColor.""" + + if self.color_scheme_table.active_scheme_name == 'NoColor': + self.color_scheme_table.set_active_scheme(self.old_scheme) + self.Colors = self.color_scheme_table.active_colors + else: + self.old_scheme = self.color_scheme_table.active_scheme_name + self.color_scheme_table.set_active_scheme('NoColor') + self.Colors = self.color_scheme_table.active_colors + + def stb2text(self, stb): + """Convert a structured traceback (a list) to a string.""" + return '\n'.join(stb) + + def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5): + """Return formatted traceback. + + Subclasses may override this if they add extra arguments. + """ + tb_list = self.structured_traceback(etype, value, tb, + tb_offset, context) + return self.stb2text(tb_list) + + def structured_traceback( + self, etype, evalue, tb, tb_offset: Optional[int] = None, context=5, mode=None + ): + """Return a list of traceback frames. + + Must be implemented by each class. + """ + raise NotImplementedError() + + +#--------------------------------------------------------------------------- +class ListTB(TBTools): + """Print traceback information from a traceback list, with optional color. + + Calling requires 3 arguments: (etype, evalue, elist) + as would be obtained by:: + + etype, evalue, tb = sys.exc_info() + if tb: + elist = traceback.extract_tb(tb) + else: + elist = None + + It can thus be used by programs which need to process the traceback before + printing (such as console replacements based on the code module from the + standard library). + + Because they are meant to be called without a full traceback (only a + list), instances of this class can't call the interactive pdb debugger.""" + + + def __call__(self, etype, value, elist): + self.ostream.flush() + self.ostream.write(self.text(etype, value, elist)) + self.ostream.write('\n') + + def _extract_tb(self, tb): + if tb: + return traceback.extract_tb(tb) + else: + return None + + def structured_traceback( + self, + etype: type, + evalue: BaseException, + etb: Optional[TracebackType] = None, + tb_offset: Optional[int] = None, + context=5, + ): + """Return a color formatted string with the traceback info. + + Parameters + ---------- + etype : exception type + Type of the exception raised. + evalue : object + Data stored in the exception + etb : list | TracebackType | None + If list: List of frames, see class docstring for details. + If Traceback: Traceback of the exception. + tb_offset : int, optional + Number of frames in the traceback to skip. If not given, the + instance evalue is used (set in constructor). + context : int, optional + Number of lines of context information to print. + + Returns + ------- + String with formatted exception. + """ + # This is a workaround to get chained_exc_ids in recursive calls + # etb should not be a tuple if structured_traceback is not recursive + if isinstance(etb, tuple): + etb, chained_exc_ids = etb + else: + chained_exc_ids = set() + + if isinstance(etb, list): + elist = etb + elif etb is not None: + elist = self._extract_tb(etb) + else: + elist = [] + tb_offset = self.tb_offset if tb_offset is None else tb_offset + assert isinstance(tb_offset, int) + Colors = self.Colors + out_list = [] + if elist: + + if tb_offset and len(elist) > tb_offset: + elist = elist[tb_offset:] + + out_list.append('Traceback %s(most recent call last)%s:' % + (Colors.normalEm, Colors.Normal) + '\n') + out_list.extend(self._format_list(elist)) + # The exception info should be a single entry in the list. + lines = ''.join(self._format_exception_only(etype, evalue)) + out_list.append(lines) + + exception = self.get_parts_of_chained_exception(evalue) + + if exception and not id(exception[1]) in chained_exc_ids: + chained_exception_message = self.prepare_chained_exception_message( + evalue.__cause__)[0] + etype, evalue, etb = exception + # Trace exception to avoid infinite 'cause' loop + chained_exc_ids.add(id(exception[1])) + chained_exceptions_tb_offset = 0 + out_list = ( + self.structured_traceback( + etype, evalue, (etb, chained_exc_ids), + chained_exceptions_tb_offset, context) + + chained_exception_message + + out_list) + + return out_list + + def _format_list(self, extracted_list): + """Format a list of traceback entry tuples for printing. + + Given a list of tuples as returned by extract_tb() or + extract_stack(), return a list of strings ready for printing. + Each string in the resulting list corresponds to the item with the + same index in the argument list. Each string ends in a newline; + the strings may contain internal newlines as well, for those items + whose source text line is not None. + + Lifted almost verbatim from traceback.py + """ + + Colors = self.Colors + list = [] + for filename, lineno, name, line in extracted_list[:-1]: + item = " %s in %s%s%s\n" % ( + _format_filename( + filename, Colors.filename, Colors.Normal, lineno=lineno + ), + Colors.name, + name, + Colors.Normal, + ) + if line: + item += ' %s\n' % line.strip() + list.append(item) + # Emphasize the last entry + filename, lineno, name, line = extracted_list[-1] + item = "%s %s in %s%s%s%s\n" % ( + Colors.normalEm, + _format_filename( + filename, Colors.filenameEm, Colors.normalEm, lineno=lineno + ), + Colors.nameEm, + name, + Colors.normalEm, + Colors.Normal, + ) + if line: + item += '%s %s%s\n' % (Colors.line, line.strip(), + Colors.Normal) + list.append(item) + return list + + def _format_exception_only(self, etype, value): + """Format the exception part of a traceback. + + The arguments are the exception type and value such as given by + sys.exc_info()[:2]. The return value is a list of strings, each ending + in a newline. Normally, the list contains a single string; however, + for SyntaxError exceptions, it contains several lines that (when + printed) display detailed information about where the syntax error + occurred. The message indicating which exception occurred is the + always last string in the list. + + Also lifted nearly verbatim from traceback.py + """ + have_filedata = False + Colors = self.Colors + list = [] + stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal) + if value is None: + # Not sure if this can still happen in Python 2.6 and above + list.append(stype + '\n') + else: + if issubclass(etype, SyntaxError): + have_filedata = True + if not value.filename: value.filename = "" + if value.lineno: + lineno = value.lineno + textline = linecache.getline(value.filename, value.lineno) + else: + lineno = "unknown" + textline = "" + list.append( + "%s %s%s\n" + % ( + Colors.normalEm, + _format_filename( + value.filename, + Colors.filenameEm, + Colors.normalEm, + lineno=(None if lineno == "unknown" else lineno), + ), + Colors.Normal, + ) + ) + if textline == "": + textline = py3compat.cast_unicode(value.text, "utf-8") + + if textline is not None: + i = 0 + while i < len(textline) and textline[i].isspace(): + i += 1 + list.append('%s %s%s\n' % (Colors.line, + textline.strip(), + Colors.Normal)) + if value.offset is not None: + s = ' ' + for c in textline[i:value.offset - 1]: + if c.isspace(): + s += c + else: + s += ' ' + list.append('%s%s^%s\n' % (Colors.caret, s, + Colors.Normal)) + + try: + s = value.msg + except Exception: + s = self._some_str(value) + if s: + list.append('%s%s:%s %s\n' % (stype, Colors.excName, + Colors.Normal, s)) + else: + list.append('%s\n' % stype) + + # sync with user hooks + if have_filedata: + ipinst = get_ipython() + if ipinst is not None: + ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0) + + return list + + def get_exception_only(self, etype, value): + """Only print the exception type and message, without a traceback. + + Parameters + ---------- + etype : exception type + value : exception value + """ + return ListTB.structured_traceback(self, etype, value) + + def show_exception_only(self, etype, evalue): + """Only print the exception type and message, without a traceback. + + Parameters + ---------- + etype : exception type + evalue : exception value + """ + # This method needs to use __call__ from *this* class, not the one from + # a subclass whose signature or behavior may be different + ostream = self.ostream + ostream.flush() + ostream.write('\n'.join(self.get_exception_only(etype, evalue))) + ostream.flush() + + def _some_str(self, value): + # Lifted from traceback.py + try: + return py3compat.cast_unicode(str(value)) + except: + return u'' % type(value).__name__ + + +#---------------------------------------------------------------------------- +class VerboseTB(TBTools): + """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead + of HTML. Requires inspect and pydoc. Crazy, man. + + Modified version which optionally strips the topmost entries from the + traceback, to be used with alternate interpreters (because their own code + would appear in the traceback).""" + + def __init__( + self, + color_scheme: str = "Linux", + call_pdb: bool = False, + ostream=None, + tb_offset: int = 0, + long_header: bool = False, + include_vars: bool = True, + check_cache=None, + debugger_cls=None, + parent=None, + config=None, + ): + """Specify traceback offset, headers and color scheme. + + Define how many frames to drop from the tracebacks. Calling it with + tb_offset=1 allows use of this handler in interpreters which will have + their own code at the top of the traceback (VerboseTB will first + remove that frame before printing the traceback info).""" + TBTools.__init__( + self, + color_scheme=color_scheme, + call_pdb=call_pdb, + ostream=ostream, + parent=parent, + config=config, + debugger_cls=debugger_cls, + ) + self.tb_offset = tb_offset + self.long_header = long_header + self.include_vars = include_vars + # By default we use linecache.checkcache, but the user can provide a + # different check_cache implementation. This is used by the IPython + # kernel to provide tracebacks for interactive code that is cached, + # by a compiler instance that flushes the linecache but preserves its + # own code cache. + if check_cache is None: + check_cache = linecache.checkcache + self.check_cache = check_cache + + self.skip_hidden = True + + def format_record(self, frame_info): + """Format a single stack frame""" + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + + if isinstance(frame_info, stack_data.RepeatedFrames): + return ' %s[... skipping similar frames: %s]%s\n' % ( + Colors.excName, frame_info.description, ColorsNormal) + + indent = " " * INDENT_SIZE + em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal) + tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}" + tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % ( + Colors.vName, + Colors.valEm, + ColorsNormal, + ) + tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal) + + link = _format_filename( + frame_info.filename, + Colors.filenameEm, + ColorsNormal, + lineno=frame_info.lineno, + ) + args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame) + + func = frame_info.executing.code_qualname() + if func == "": + call = tpl_call.format(file=func, scope="") + else: + # Decide whether to include variable details or not + var_repr = eqrepr if self.include_vars else nullrepr + try: + scope = inspect.formatargvalues( + args, varargs, varkw, locals_, formatvalue=var_repr + ) + call = tpl_call.format(file=func, scope=scope) + except KeyError: + # This happens in situations like errors inside generator + # expressions, where local variables are listed in the + # line, but can't be extracted from the frame. I'm not + # 100% sure this isn't actually a bug in inspect itself, + # but since there's no info for us to compute with, the + # best we can do is report the failure and move on. Here + # we must *not* call any traceback construction again, + # because that would mess up use of %debug later on. So we + # simply report the failure and move on. The only + # limitation will be that this frame won't have locals + # listed in the call signature. Quite subtle problem... + # I can't think of a good way to validate this in a unit + # test, but running a script consisting of: + # dict( (k,v.strip()) for (k,v) in range(10) ) + # will illustrate the error, if this exception catch is + # disabled. + call = tpl_call_fail % func + + lvals = '' + lvals_list = [] + if self.include_vars: + try: + # we likely want to fix stackdata at some point, but + # still need a workaround. + fibp = frame_info.variables_in_executing_piece + for var in fibp: + lvals_list.append(tpl_name_val % (var.name, repr(var.value))) + except Exception: + lvals_list.append( + "Exception trying to inspect frame. No more locals available." + ) + if lvals_list: + lvals = '%s%s' % (indent, em_normal.join(lvals_list)) + + result = "%s, %s\n" % (link, call) + + result += ''.join(_format_traceback_lines(frame_info.lines, Colors, self.has_colors, lvals)) + return result + + def prepare_header(self, etype, long_version=False): + colors = self.Colors # just a shorthand + quicker name lookup + colorsnormal = colors.Normal # used a lot + exc = '%s%s%s' % (colors.excName, etype, colorsnormal) + width = min(75, get_terminal_size()[0]) + if long_version: + # Header with the exception type, python version, and date + pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable + date = time.ctime(time.time()) + + head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * width, colorsnormal, + exc, ' ' * (width - len(str(etype)) - len(pyver)), + pyver, date.rjust(width) ) + head += "\nA problem occurred executing Python code. Here is the sequence of function" \ + "\ncalls leading up to the error, with the most recent (innermost) call last." + else: + # Simplified header + head = '%s%s' % (exc, 'Traceback (most recent call last)'. \ + rjust(width - len(str(etype))) ) + + return head + + def format_exception(self, etype, evalue): + colors = self.Colors # just a shorthand + quicker name lookup + colorsnormal = colors.Normal # used a lot + # Get (safely) a string form of the exception info + try: + etype_str, evalue_str = map(str, (etype, evalue)) + except: + # User exception is improperly defined. + etype, evalue = str, sys.exc_info()[:2] + etype_str, evalue_str = map(str, (etype, evalue)) + # ... and format it + return ['%s%s%s: %s' % (colors.excName, etype_str, + colorsnormal, py3compat.cast_unicode(evalue_str))] + + def format_exception_as_a_whole( + self, + etype: type, + evalue: BaseException, + etb: Optional[TracebackType], + number_of_lines_of_context, + tb_offset: Optional[int], + ): + """Formats the header, traceback and exception message for a single exception. + + This may be called multiple times by Python 3 exception chaining + (PEP 3134). + """ + # some locals + orig_etype = etype + try: + etype = etype.__name__ + except AttributeError: + pass + + tb_offset = self.tb_offset if tb_offset is None else tb_offset + assert isinstance(tb_offset, int) + head = self.prepare_header(etype, self.long_header) + records = ( + self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else [] + ) + + frames = [] + skipped = 0 + lastrecord = len(records) - 1 + for i, r in enumerate(records): + if not isinstance(r, stack_data.RepeatedFrames) and self.skip_hidden: + if r.frame.f_locals.get("__tracebackhide__", 0) and i != lastrecord: + skipped += 1 + continue + if skipped: + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + frames.append( + " %s[... skipping hidden %s frame]%s\n" + % (Colors.excName, skipped, ColorsNormal) + ) + skipped = 0 + frames.append(self.format_record(r)) + if skipped: + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + frames.append( + " %s[... skipping hidden %s frame]%s\n" + % (Colors.excName, skipped, ColorsNormal) + ) + + formatted_exception = self.format_exception(etype, evalue) + if records: + frame_info = records[-1] + ipinst = get_ipython() + if ipinst is not None: + ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0) + + return [[head] + frames + [''.join(formatted_exception[0])]] + + def get_records( + self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int + ): + assert etb is not None + context = number_of_lines_of_context - 1 + after = context // 2 + before = context - after + if self.has_colors: + style = get_style_by_name("default") + style = stack_data.style_with_executing_node(style, "bg:ansiyellow") + formatter = Terminal256Formatter(style=style) + else: + formatter = None + options = stack_data.Options( + before=before, + after=after, + pygments_formatter=formatter, + ) + return list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] + + def structured_traceback( + self, + etype: type, + evalue: Optional[BaseException], + etb: Optional[TracebackType], + tb_offset: Optional[int] = None, + number_of_lines_of_context: int = 5, + ): + """Return a nice text document describing the traceback.""" + formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context, + tb_offset) + + colors = self.Colors # just a shorthand + quicker name lookup + colorsnormal = colors.Normal # used a lot + head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal) + structured_traceback_parts = [head] + chained_exceptions_tb_offset = 0 + lines_of_context = 3 + formatted_exceptions = formatted_exception + exception = self.get_parts_of_chained_exception(evalue) + if exception: + assert evalue is not None + formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) + etype, evalue, etb = exception + else: + evalue = None + chained_exc_ids = set() + while evalue: + formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context, + chained_exceptions_tb_offset) + exception = self.get_parts_of_chained_exception(evalue) + + if exception and not id(exception[1]) in chained_exc_ids: + chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop + formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) + etype, evalue, etb = exception + else: + evalue = None + + # we want to see exceptions in a reversed order: + # the first exception should be on top + for formatted_exception in reversed(formatted_exceptions): + structured_traceback_parts += formatted_exception + + return structured_traceback_parts + + def debugger(self, force: bool = False): + """Call up the pdb debugger if desired, always clean up the tb + reference. + + Keywords: + + - force(False): by default, this routine checks the instance call_pdb + flag and does not actually invoke the debugger if the flag is false. + The 'force' option forces the debugger to activate even if the flag + is false. + + If the call_pdb flag is set, the pdb interactive debugger is + invoked. In all cases, the self.tb reference to the current traceback + is deleted to prevent lingering references which hamper memory + management. + + Note that each call to pdb() does an 'import readline', so if your app + requires a special setup for the readline completers, you'll have to + fix that by hand after invoking the exception handler.""" + + if force or self.call_pdb: + if self.pdb is None: + self.pdb = self.debugger_cls() + # the system displayhook may have changed, restore the original + # for pdb + display_trap = DisplayTrap(hook=sys.__displayhook__) + with display_trap: + self.pdb.reset() + # Find the right frame so we don't pop up inside ipython itself + if hasattr(self, 'tb') and self.tb is not None: + etb = self.tb + else: + etb = self.tb = sys.last_traceback + while self.tb is not None and self.tb.tb_next is not None: + assert self.tb.tb_next is not None + self.tb = self.tb.tb_next + if etb and etb.tb_next: + etb = etb.tb_next + self.pdb.botframe = etb.tb_frame + self.pdb.interaction(None, etb) + + if hasattr(self, 'tb'): + del self.tb + + def handler(self, info=None): + (etype, evalue, etb) = info or sys.exc_info() + self.tb = etb + ostream = self.ostream + ostream.flush() + ostream.write(self.text(etype, evalue, etb)) + ostream.write('\n') + ostream.flush() + + # Changed so an instance can just be called as VerboseTB_inst() and print + # out the right info on its own. + def __call__(self, etype=None, evalue=None, etb=None): + """This hook can replace sys.excepthook (for Python 2.1 or higher).""" + if etb is None: + self.handler() + else: + self.handler((etype, evalue, etb)) + try: + self.debugger() + except KeyboardInterrupt: + print("\nKeyboardInterrupt") + + +#---------------------------------------------------------------------------- +class FormattedTB(VerboseTB, ListTB): + """Subclass ListTB but allow calling with a traceback. + + It can thus be used as a sys.excepthook for Python > 2.1. + + Also adds 'Context' and 'Verbose' modes, not available in ListTB. + + Allows a tb_offset to be specified. This is useful for situations where + one needs to remove a number of topmost frames from the traceback (such as + occurs with python programs that themselves execute other python code, + like Python shells). """ + + mode: str + + def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False, + ostream=None, + tb_offset=0, long_header=False, include_vars=False, + check_cache=None, debugger_cls=None, + parent=None, config=None): + + # NEVER change the order of this list. Put new modes at the end: + self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal'] + self.verbose_modes = self.valid_modes[1:3] + + VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, + ostream=ostream, tb_offset=tb_offset, + long_header=long_header, include_vars=include_vars, + check_cache=check_cache, debugger_cls=debugger_cls, + parent=parent, config=config) + + # Different types of tracebacks are joined with different separators to + # form a single string. They are taken from this dict + self._join_chars = dict(Plain='', Context='\n', Verbose='\n', + Minimal='') + # set_mode also sets the tb_join_char attribute + self.set_mode(mode) + + def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5): + tb_offset = self.tb_offset if tb_offset is None else tb_offset + mode = self.mode + if mode in self.verbose_modes: + # Verbose modes need a full traceback + return VerboseTB.structured_traceback( + self, etype, value, tb, tb_offset, number_of_lines_of_context + ) + elif mode == 'Minimal': + return ListTB.get_exception_only(self, etype, value) + else: + # We must check the source cache because otherwise we can print + # out-of-date source code. + self.check_cache() + # Now we can extract and format the exception + return ListTB.structured_traceback( + self, etype, value, tb, tb_offset, number_of_lines_of_context + ) + + def stb2text(self, stb): + """Convert a structured traceback (a list) to a string.""" + return self.tb_join_char.join(stb) + + def set_mode(self, mode: Optional[str] = None): + """Switch to the desired mode. + + If mode is not specified, cycles through the available modes.""" + + if not mode: + new_idx = (self.valid_modes.index(self.mode) + 1 ) % \ + len(self.valid_modes) + self.mode = self.valid_modes[new_idx] + elif mode not in self.valid_modes: + raise ValueError( + "Unrecognized mode in FormattedTB: <" + mode + ">\n" + "Valid modes: " + str(self.valid_modes) + ) + else: + assert isinstance(mode, str) + self.mode = mode + # include variable details only in 'Verbose' mode + self.include_vars = (self.mode == self.valid_modes[2]) + # Set the join character for generating text tracebacks + self.tb_join_char = self._join_chars[self.mode] + + # some convenient shortcuts + def plain(self): + self.set_mode(self.valid_modes[0]) + + def context(self): + self.set_mode(self.valid_modes[1]) + + def verbose(self): + self.set_mode(self.valid_modes[2]) + + def minimal(self): + self.set_mode(self.valid_modes[3]) + + +#---------------------------------------------------------------------------- +class AutoFormattedTB(FormattedTB): + """A traceback printer which can be called on the fly. + + It will find out about exceptions by itself. + + A brief example:: + + AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux') + try: + ... + except: + AutoTB() # or AutoTB(out=logfile) where logfile is an open file object + """ + + def __call__(self, etype=None, evalue=None, etb=None, + out=None, tb_offset=None): + """Print out a formatted exception traceback. + + Optional arguments: + - out: an open file-like object to direct output to. + + - tb_offset: the number of frames to skip over in the stack, on a + per-call basis (this overrides temporarily the instance's tb_offset + given at initialization time.""" + + if out is None: + out = self.ostream + out.flush() + out.write(self.text(etype, evalue, etb, tb_offset)) + out.write('\n') + out.flush() + # FIXME: we should remove the auto pdb behavior from here and leave + # that to the clients. + try: + self.debugger() + except KeyboardInterrupt: + print("\nKeyboardInterrupt") + + def structured_traceback(self, etype=None, value=None, tb=None, + tb_offset=None, number_of_lines_of_context=5): + + etype: type + value: BaseException + # tb: TracebackType or tupleof tb types ? + if etype is None: + etype, value, tb = sys.exc_info() + if isinstance(tb, tuple): + # tb is a tuple if this is a chained exception. + self.tb = tb[0] + else: + self.tb = tb + return FormattedTB.structured_traceback( + self, etype, value, tb, tb_offset, number_of_lines_of_context) + + +#--------------------------------------------------------------------------- + +# A simple class to preserve Nathan's original functionality. +class ColorTB(FormattedTB): + """Shorthand to initialize a FormattedTB in Linux colors mode.""" + + def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs): + FormattedTB.__init__(self, color_scheme=color_scheme, + call_pdb=call_pdb, **kwargs) + + +class SyntaxTB(ListTB): + """Extension which holds some state: the last exception value""" + + def __init__(self, color_scheme='NoColor', parent=None, config=None): + ListTB.__init__(self, color_scheme, parent=parent, config=config) + self.last_syntax_error = None + + def __call__(self, etype, value, elist): + self.last_syntax_error = value + + ListTB.__call__(self, etype, value, elist) + + def structured_traceback(self, etype, value, elist, tb_offset=None, + context=5): + # If the source file has been edited, the line in the syntax error can + # be wrong (retrieved from an outdated cache). This replaces it with + # the current value. + if isinstance(value, SyntaxError) \ + and isinstance(value.filename, str) \ + and isinstance(value.lineno, int): + linecache.checkcache(value.filename) + newtext = linecache.getline(value.filename, value.lineno) + if newtext: + value.text = newtext + self.last_syntax_error = value + return super(SyntaxTB, self).structured_traceback(etype, value, elist, + tb_offset=tb_offset, context=context) + + def clear_err_state(self): + """Return the current error state and clear it""" + e = self.last_syntax_error + self.last_syntax_error = None + return e + + def stb2text(self, stb): + """Convert a structured traceback (a list) to a string.""" + return ''.join(stb) + + +# some internal-use functions +def text_repr(value): + """Hopefully pretty robust repr equivalent.""" + # this is pretty horrible but should always return *something* + try: + return pydoc.text.repr(value) + except KeyboardInterrupt: + raise + except: + try: + return repr(value) + except KeyboardInterrupt: + raise + except: + try: + # all still in an except block so we catch + # getattr raising + name = getattr(value, '__name__', None) + if name: + # ick, recursion + return text_repr(name) + klass = getattr(value, '__class__', None) + if klass: + return '%s instance' % text_repr(klass) + except KeyboardInterrupt: + raise + except: + return 'UNRECOVERABLE REPR FAILURE' + + +def eqrepr(value, repr=text_repr): + return '=%s' % repr(value) + + +def nullrepr(value, repr=text_repr): + return '' diff --git a/.venv/lib/python3.8/site-packages/IPython/core/usage.py b/.venv/lib/python3.8/site-packages/IPython/core/usage.py new file mode 100644 index 0000000..53219bc --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/core/usage.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +"""Usage information for the main IPython applications. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez. +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +import sys +from IPython.core import release + +cl_usage = """\ +========= + IPython +========= + +Tools for Interactive Computing in Python +========================================= + + A Python shell with automatic history (input and output), dynamic object + introspection, easier configuration, command completion, access to the + system shell and more. IPython can also be embedded in running programs. + + +Usage + + ipython [subcommand] [options] [-c cmd | -m mod | file] [--] [arg] ... + + If invoked with no options, it executes the file and exits, passing the + remaining arguments to the script, just as if you had specified the same + command with python. You may need to specify `--` before args to be passed + to the script, to prevent IPython from attempting to parse them. If you + specify the option `-i` before the filename, it will enter an interactive + IPython session after running the script, rather than exiting. Files ending + in .py will be treated as normal Python, but files ending in .ipy can + contain special IPython syntax (magic commands, shell expansions, etc.). + + Almost all configuration in IPython is available via the command-line. Do + `ipython --help-all` to see all available options. For persistent + configuration, look into your `ipython_config.py` configuration file for + details. + + This file is typically installed in the `IPYTHONDIR` directory, and there + is a separate configuration directory for each profile. The default profile + directory will be located in $IPYTHONDIR/profile_default. IPYTHONDIR + defaults to to `$HOME/.ipython`. For Windows users, $HOME resolves to + C:\\Users\\YourUserName in most instances. + + To initialize a profile with the default configuration file, do:: + + $> ipython profile create + + and start editing `IPYTHONDIR/profile_default/ipython_config.py` + + In IPython's documentation, we will refer to this directory as + `IPYTHONDIR`, you can change its default location by creating an + environment variable with this name and setting it to the desired path. + + For more information, see the manual available in HTML and PDF in your + installation, or online at https://ipython.org/documentation.html. +""" + +interactive_usage = """ +IPython -- An enhanced Interactive Python +========================================= + +IPython offers a fully compatible replacement for the standard Python +interpreter, with convenient shell features, special commands, command +history mechanism and output results caching. + +At your system command line, type 'ipython -h' to see the command line +options available. This document only describes interactive features. + +GETTING HELP +------------ + +Within IPython you have various way to access help: + + ? -> Introduction and overview of IPython's features (this screen). + object? -> Details about 'object'. + object?? -> More detailed, verbose information about 'object'. + %quickref -> Quick reference of all IPython specific syntax and magics. + help -> Access Python's own help system. + +If you are in terminal IPython you can quit this screen by pressing `q`. + + +MAIN FEATURES +------------- + +* Access to the standard Python help with object docstrings and the Python + manuals. Simply type 'help' (no quotes) to invoke it. + +* Magic commands: type %magic for information on the magic subsystem. + +* System command aliases, via the %alias command or the configuration file(s). + +* Dynamic object information: + + Typing ?word or word? prints detailed information about an object. Certain + long strings (code, etc.) get snipped in the center for brevity. + + Typing ??word or word?? gives access to the full information without + snipping long strings. Strings that are longer than the screen are printed + through the less pager. + + The ?/?? system gives access to the full source code for any object (if + available), shows function prototypes and other useful information. + + If you just want to see an object's docstring, type '%pdoc object' (without + quotes, and without % if you have automagic on). + +* Tab completion in the local namespace: + + At any time, hitting tab will complete any available python commands or + variable names, and show you a list of the possible completions if there's + no unambiguous one. It will also complete filenames in the current directory. + +* Search previous command history in multiple ways: + + - Start typing, and then use arrow keys up/down or (Ctrl-p/Ctrl-n) to search + through the history items that match what you've typed so far. + + - Hit Ctrl-r: opens a search prompt. Begin typing and the system searches + your history for lines that match what you've typed so far, completing as + much as it can. + + - %hist: search history by index. + +* Persistent command history across sessions. + +* Logging of input with the ability to save and restore a working session. + +* System shell with !. Typing !ls will run 'ls' in the current directory. + +* The reload command does a 'deep' reload of a module: changes made to the + module since you imported will actually be available without having to exit. + +* Verbose and colored exception traceback printouts. See the magic xmode and + xcolor functions for details (just type %magic). + +* Input caching system: + + IPython offers numbered prompts (In/Out) with input and output caching. All + input is saved and can be retrieved as variables (besides the usual arrow + key recall). + + The following GLOBAL variables always exist (so don't overwrite them!): + _i: stores previous input. + _ii: next previous. + _iii: next-next previous. + _ih : a list of all input _ih[n] is the input from line n. + + Additionally, global variables named _i are dynamically created ( + being the prompt counter), such that _i == _ih[] + + For example, what you typed at prompt 14 is available as _i14 and _ih[14]. + + You can create macros which contain multiple input lines from this history, + for later re-execution, with the %macro function. + + The history function %hist allows you to see any part of your input history + by printing a range of the _i variables. Note that inputs which contain + magic functions (%) appear in the history with a prepended comment. This is + because they aren't really valid Python code, so you can't exec them. + +* Output caching system: + + For output that is returned from actions, a system similar to the input + cache exists but using _ instead of _i. Only actions that produce a result + (NOT assignments, for example) are cached. If you are familiar with + Mathematica, IPython's _ variables behave exactly like Mathematica's % + variables. + + The following GLOBAL variables always exist (so don't overwrite them!): + _ (one underscore): previous output. + __ (two underscores): next previous. + ___ (three underscores): next-next previous. + + Global variables named _ are dynamically created ( being the prompt + counter), such that the result of output is always available as _. + + Finally, a global dictionary named _oh exists with entries for all lines + which generated output. + +* Directory history: + + Your history of visited directories is kept in the global list _dh, and the + magic %cd command can be used to go to any entry in that list. + +* Auto-parentheses and auto-quotes (adapted from Nathan Gray's LazyPython) + + 1. Auto-parentheses + + Callable objects (i.e. functions, methods, etc) can be invoked like + this (notice the commas between the arguments):: + + In [1]: callable_ob arg1, arg2, arg3 + + and the input will be translated to this:: + + callable_ob(arg1, arg2, arg3) + + This feature is off by default (in rare cases it can produce + undesirable side-effects), but you can activate it at the command-line + by starting IPython with `--autocall 1`, set it permanently in your + configuration file, or turn on at runtime with `%autocall 1`. + + You can force auto-parentheses by using '/' as the first character + of a line. For example:: + + In [1]: /globals # becomes 'globals()' + + Note that the '/' MUST be the first character on the line! This + won't work:: + + In [2]: print /globals # syntax error + + In most cases the automatic algorithm should work, so you should + rarely need to explicitly invoke /. One notable exception is if you + are trying to call a function with a list of tuples as arguments (the + parenthesis will confuse IPython):: + + In [1]: zip (1,2,3),(4,5,6) # won't work + + but this will work:: + + In [2]: /zip (1,2,3),(4,5,6) + ------> zip ((1,2,3),(4,5,6)) + Out[2]= [(1, 4), (2, 5), (3, 6)] + + IPython tells you that it has altered your command line by + displaying the new command line preceded by -->. e.g.:: + + In [18]: callable list + -------> callable (list) + + 2. Auto-Quoting + + You can force auto-quoting of a function's arguments by using ',' as + the first character of a line. For example:: + + In [1]: ,my_function /home/me # becomes my_function("/home/me") + + If you use ';' instead, the whole argument is quoted as a single + string (while ',' splits on whitespace):: + + In [2]: ,my_function a b c # becomes my_function("a","b","c") + In [3]: ;my_function a b c # becomes my_function("a b c") + + Note that the ',' MUST be the first character on the line! This + won't work:: + + In [4]: x = ,my_function /home/me # syntax error +""" + +interactive_usage_min = """\ +An enhanced console for Python. +Some of its features are: +- Tab completion in the local namespace. +- Logging of input, see command-line options. +- System shell escape via ! , eg !ls. +- Magic commands, starting with a % (like %ls, %pwd, %cd, etc.) +- Keeps track of locally defined variables via %who, %whos. +- Show object information with a ? eg ?x or x? (use ?? for more info). +""" + +quick_reference = r""" +IPython -- An enhanced Interactive Python - Quick Reference Card +================================================================ + +obj?, obj?? : Get help, or more help for object (also works as + ?obj, ??obj). +?foo.*abc* : List names in 'foo' containing 'abc' in them. +%magic : Information about IPython's 'magic' % functions. + +Magic functions are prefixed by % or %%, and typically take their arguments +without parentheses, quotes or even commas for convenience. Line magics take a +single % and cell magics are prefixed with two %%. + +Example magic function calls: + +%alias d ls -F : 'd' is now an alias for 'ls -F' +alias d ls -F : Works if 'alias' not a python name +alist = %alias : Get list of aliases to 'alist' +cd /usr/share : Obvious. cd - to choose from visited dirs. +%cd?? : See help AND source for magic %cd +%timeit x=10 : time the 'x=10' statement with high precision. +%%timeit x=2**100 +x**100 : time 'x**100' with a setup of 'x=2**100'; setup code is not + counted. This is an example of a cell magic. + +System commands: + +!cp a.txt b/ : System command escape, calls os.system() +cp a.txt b/ : after %rehashx, most system commands work without ! +cp ${f}.txt $bar : Variable expansion in magics and system commands +files = !ls /usr : Capture system command output +files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc' + +History: + +_i, _ii, _iii : Previous, next previous, next next previous input +_i4, _ih[2:5] : Input history line 4, lines 2-4 +exec(_i81) : Execute input history line #81 again +%rep 81 : Edit input history line #81 +_, __, ___ : previous, next previous, next next previous output +_dh : Directory history +_oh : Output history +%hist : Command history of current session. +%hist -g foo : Search command history of (almost) all sessions for 'foo'. +%hist -g : Command history of (almost) all sessions. +%hist 1/2-8 : Command history containing lines 2-8 of session 1. +%hist 1/ ~2/ : Command history of session 1 and 2 sessions before current. +%hist ~8/1-~6/5 : Command history from line 1 of 8 sessions ago to + line 5 of 6 sessions ago. +%edit 0/ : Open editor to execute code with history of current session. + +Autocall: + +f 1,2 : f(1,2) # Off by default, enable with %autocall magic. +/f 1,2 : f(1,2) (forced autoparen) +,f 1 2 : f("1","2") +;f 1 2 : f("1 2") + +Remember: TAB completion works in many contexts, not just file names +or python names. + +The following magic functions are currently available: + +""" + +default_banner_parts = ["Python %s\n"%sys.version.split("\n")[0], + "Type 'copyright', 'credits' or 'license' for more information\n" , + "IPython {version} -- An enhanced Interactive Python. Type '?' for help.\n".format(version=release.version), +] + +default_banner = ''.join(default_banner_parts) diff --git a/.venv/lib/python3.8/site-packages/IPython/display.py b/.venv/lib/python3.8/site-packages/IPython/display.py new file mode 100644 index 0000000..b7f64f2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/display.py @@ -0,0 +1,44 @@ +"""Public API for display tools in IPython. +""" + +# ----------------------------------------------------------------------------- +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +from IPython.core.display_functions import * +from IPython.core.display import ( + display_pretty, + display_html, + display_markdown, + display_svg, + display_png, + display_jpeg, + display_latex, + display_json, + display_javascript, + display_pdf, + DisplayObject, + TextDisplayObject, + Pretty, + HTML, + Markdown, + Math, + Latex, + SVG, + ProgressBar, + JSON, + GeoJSON, + Javascript, + Image, + set_matplotlib_formats, + set_matplotlib_close, + Video, +) +from IPython.lib.display import * diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/__init__.py b/.venv/lib/python3.8/site-packages/IPython/extensions/__init__.py new file mode 100644 index 0000000..db7f79f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/extensions/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +"""This directory is meant for IPython extensions.""" diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..276849c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/autoreload.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/autoreload.cpython-38.pyc new file mode 100644 index 0000000..8b6def9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/autoreload.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/storemagic.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/storemagic.cpython-38.pyc new file mode 100644 index 0000000..b67a607 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/extensions/__pycache__/storemagic.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/autoreload.py b/.venv/lib/python3.8/site-packages/IPython/extensions/autoreload.py new file mode 100644 index 0000000..816d2f3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/extensions/autoreload.py @@ -0,0 +1,627 @@ +"""IPython extension to reload modules before executing user code. + +``autoreload`` reloads modules automatically before entering the execution of +code typed at the IPython prompt. + +This makes for example the following workflow possible: + +.. sourcecode:: ipython + + In [1]: %load_ext autoreload + + In [2]: %autoreload 2 + + In [3]: from foo import some_function + + In [4]: some_function() + Out[4]: 42 + + In [5]: # open foo.py in an editor and change some_function to return 43 + + In [6]: some_function() + Out[6]: 43 + +The module was reloaded without reloading it explicitly, and the object +imported with ``from foo import ...`` was also updated. + +Usage +===== + +The following magic commands are provided: + +``%autoreload`` + + Reload all modules (except those excluded by ``%aimport``) + automatically now. + +``%autoreload 0`` + + Disable automatic reloading. + +``%autoreload 1`` + + Reload all modules imported with ``%aimport`` every time before + executing the Python code typed. + +``%autoreload 2`` + + Reload all modules (except those excluded by ``%aimport``) every + time before executing the Python code typed. + +``%autoreload 3`` + + Reload all modules AND autoload newly added objects + every time before executing the Python code typed. + +``%aimport`` + + List modules which are to be automatically imported or not to be imported. + +``%aimport foo`` + + Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1`` + +``%aimport foo, bar`` + + Import modules 'foo', 'bar' and mark them to be autoreloaded for ``%autoreload 1`` + +``%aimport -foo`` + + Mark module 'foo' to not be autoreloaded. + +Caveats +======= + +Reloading Python modules in a reliable way is in general difficult, +and unexpected things may occur. ``%autoreload`` tries to work around +common pitfalls by replacing function code objects and parts of +classes previously in the module with new versions. This makes the +following things to work: + +- Functions and classes imported via 'from xxx import foo' are upgraded + to new versions when 'xxx' is reloaded. + +- Methods and properties of classes are upgraded on reload, so that + calling 'c.foo()' on an object 'c' created before the reload causes + the new code for 'foo' to be executed. + +Some of the known remaining caveats are: + +- Replacing code objects does not always succeed: changing a @property + in a class to an ordinary method or a method to a member variable + can cause problems (but in old objects only). + +- Functions that are removed (eg. via monkey-patching) from a module + before it is reloaded are not upgraded. + +- C extension modules cannot be reloaded, and so cannot be autoreloaded. +""" + +__skip_doctest__ = True + +# ----------------------------------------------------------------------------- +# Copyright (C) 2000 Thomas Heller +# Copyright (C) 2008 Pauli Virtanen +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# ----------------------------------------------------------------------------- +# +# This IPython module is written by Pauli Virtanen, based on the autoreload +# code by Thomas Heller. + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +import os +import sys +import traceback +import types +import weakref +import gc +from importlib import import_module, reload +from importlib.util import source_from_cache + +# ------------------------------------------------------------------------------ +# Autoreload functionality +# ------------------------------------------------------------------------------ + + +class ModuleReloader: + enabled = False + """Whether this reloader is enabled""" + + check_all = True + """Autoreload all modules, not just those listed in 'modules'""" + + autoload_obj = False + """Autoreload all modules AND autoload all new objects""" + + def __init__(self, shell=None): + # Modules that failed to reload: {module: mtime-on-failed-reload, ...} + self.failed = {} + # Modules specially marked as autoreloadable. + self.modules = {} + # Modules specially marked as not autoreloadable. + self.skip_modules = {} + # (module-name, name) -> weakref, for replacing old code objects + self.old_objects = {} + # Module modification timestamps + self.modules_mtimes = {} + self.shell = shell + + # Cache module modification times + self.check(check_all=True, do_reload=False) + + def mark_module_skipped(self, module_name): + """Skip reloading the named module in the future""" + try: + del self.modules[module_name] + except KeyError: + pass + self.skip_modules[module_name] = True + + def mark_module_reloadable(self, module_name): + """Reload the named module in the future (if it is imported)""" + try: + del self.skip_modules[module_name] + except KeyError: + pass + self.modules[module_name] = True + + def aimport_module(self, module_name): + """Import a module, and mark it reloadable + + Returns + ------- + top_module : module + The imported module if it is top-level, or the top-level + top_name : module + Name of top_module + + """ + self.mark_module_reloadable(module_name) + + import_module(module_name) + top_name = module_name.split(".")[0] + top_module = sys.modules[top_name] + return top_module, top_name + + def filename_and_mtime(self, module): + if not hasattr(module, "__file__") or module.__file__ is None: + return None, None + + if getattr(module, "__name__", None) in [None, "__mp_main__", "__main__"]: + # we cannot reload(__main__) or reload(__mp_main__) + return None, None + + filename = module.__file__ + path, ext = os.path.splitext(filename) + + if ext.lower() == ".py": + py_filename = filename + else: + try: + py_filename = source_from_cache(filename) + except ValueError: + return None, None + + try: + pymtime = os.stat(py_filename).st_mtime + except OSError: + return None, None + + return py_filename, pymtime + + def check(self, check_all=False, do_reload=True): + """Check whether some modules need to be reloaded.""" + + if not self.enabled and not check_all: + return + + if check_all or self.check_all: + modules = list(sys.modules.keys()) + else: + modules = list(self.modules.keys()) + + for modname in modules: + m = sys.modules.get(modname, None) + + if modname in self.skip_modules: + continue + + py_filename, pymtime = self.filename_and_mtime(m) + if py_filename is None: + continue + + try: + if pymtime <= self.modules_mtimes[modname]: + continue + except KeyError: + self.modules_mtimes[modname] = pymtime + continue + else: + if self.failed.get(py_filename, None) == pymtime: + continue + + self.modules_mtimes[modname] = pymtime + + # If we've reached this point, we should try to reload the module + if do_reload: + try: + if self.autoload_obj: + superreload(m, reload, self.old_objects, self.shell) + else: + superreload(m, reload, self.old_objects) + if py_filename in self.failed: + del self.failed[py_filename] + except: + print( + "[autoreload of {} failed: {}]".format( + modname, traceback.format_exc(10) + ), + file=sys.stderr, + ) + self.failed[py_filename] = pymtime + + +# ------------------------------------------------------------------------------ +# superreload +# ------------------------------------------------------------------------------ + + +func_attrs = [ + "__code__", + "__defaults__", + "__doc__", + "__closure__", + "__globals__", + "__dict__", +] + + +def update_function(old, new): + """Upgrade the code object of a function""" + for name in func_attrs: + try: + setattr(old, name, getattr(new, name)) + except (AttributeError, TypeError): + pass + + +def update_instances(old, new): + """Use garbage collector to find all instances that refer to the old + class definition and update their __class__ to point to the new class + definition""" + + refs = gc.get_referrers(old) + + for ref in refs: + if type(ref) is old: + ref.__class__ = new + + +def update_class(old, new): + """Replace stuff in the __dict__ of a class, and upgrade + method code objects, and add new methods, if any""" + for key in list(old.__dict__.keys()): + old_obj = getattr(old, key) + try: + new_obj = getattr(new, key) + # explicitly checking that comparison returns True to handle + # cases where `==` doesn't return a boolean. + if (old_obj == new_obj) is True: + continue + except AttributeError: + # obsolete attribute: remove it + try: + delattr(old, key) + except (AttributeError, TypeError): + pass + continue + except ValueError: + # can't compare nested structures containing + # numpy arrays using `==` + pass + + if update_generic(old_obj, new_obj): + continue + + try: + setattr(old, key, getattr(new, key)) + except (AttributeError, TypeError): + pass # skip non-writable attributes + + for key in list(new.__dict__.keys()): + if key not in list(old.__dict__.keys()): + try: + setattr(old, key, getattr(new, key)) + except (AttributeError, TypeError): + pass # skip non-writable attributes + + # update all instances of class + update_instances(old, new) + + +def update_property(old, new): + """Replace get/set/del functions of a property""" + update_generic(old.fdel, new.fdel) + update_generic(old.fget, new.fget) + update_generic(old.fset, new.fset) + + +def isinstance2(a, b, typ): + return isinstance(a, typ) and isinstance(b, typ) + + +UPDATE_RULES = [ + (lambda a, b: isinstance2(a, b, type), update_class), + (lambda a, b: isinstance2(a, b, types.FunctionType), update_function), + (lambda a, b: isinstance2(a, b, property), update_property), +] +UPDATE_RULES.extend( + [ + ( + lambda a, b: isinstance2(a, b, types.MethodType), + lambda a, b: update_function(a.__func__, b.__func__), + ), + ] +) + + +def update_generic(a, b): + for type_check, update in UPDATE_RULES: + if type_check(a, b): + update(a, b) + return True + return False + + +class StrongRef: + def __init__(self, obj): + self.obj = obj + + def __call__(self): + return self.obj + + +mod_attrs = [ + "__name__", + "__doc__", + "__package__", + "__loader__", + "__spec__", + "__file__", + "__cached__", + "__builtins__", +] + + +def append_obj(module, d, name, obj, autoload=False): + in_module = hasattr(obj, "__module__") and obj.__module__ == module.__name__ + if autoload: + # check needed for module global built-ins + if not in_module and name in mod_attrs: + return False + else: + if not in_module: + return False + + key = (module.__name__, name) + try: + d.setdefault(key, []).append(weakref.ref(obj)) + except TypeError: + pass + return True + + +def superreload(module, reload=reload, old_objects=None, shell=None): + """Enhanced version of the builtin reload function. + + superreload remembers objects previously in the module, and + + - upgrades the class dictionary of every old class in the module + - upgrades the code object of every old function and method + - clears the module's namespace before reloading + + """ + if old_objects is None: + old_objects = {} + + # collect old objects in the module + for name, obj in list(module.__dict__.items()): + if not append_obj(module, old_objects, name, obj): + continue + key = (module.__name__, name) + try: + old_objects.setdefault(key, []).append(weakref.ref(obj)) + except TypeError: + pass + + # reload module + try: + # clear namespace first from old cruft + old_dict = module.__dict__.copy() + old_name = module.__name__ + module.__dict__.clear() + module.__dict__["__name__"] = old_name + module.__dict__["__loader__"] = old_dict["__loader__"] + except (TypeError, AttributeError, KeyError): + pass + + try: + module = reload(module) + except: + # restore module dictionary on failed reload + module.__dict__.update(old_dict) + raise + + # iterate over all objects and update functions & classes + for name, new_obj in list(module.__dict__.items()): + key = (module.__name__, name) + if key not in old_objects: + # here 'shell' acts both as a flag and as an output var + if ( + shell is None + or name == "Enum" + or not append_obj(module, old_objects, name, new_obj, True) + ): + continue + shell.user_ns[name] = new_obj + + new_refs = [] + for old_ref in old_objects[key]: + old_obj = old_ref() + if old_obj is None: + continue + new_refs.append(old_ref) + update_generic(old_obj, new_obj) + + if new_refs: + old_objects[key] = new_refs + else: + del old_objects[key] + + return module + + +# ------------------------------------------------------------------------------ +# IPython connectivity +# ------------------------------------------------------------------------------ + +from IPython.core.magic import Magics, magics_class, line_magic + + +@magics_class +class AutoreloadMagics(Magics): + def __init__(self, *a, **kw): + super().__init__(*a, **kw) + self._reloader = ModuleReloader(self.shell) + self._reloader.check_all = False + self._reloader.autoload_obj = False + self.loaded_modules = set(sys.modules) + + @line_magic + def autoreload(self, parameter_s=""): + r"""%autoreload => Reload modules automatically + + %autoreload + Reload all modules (except those excluded by %aimport) automatically + now. + + %autoreload 0 + Disable automatic reloading. + + %autoreload 1 + Reload all modules imported with %aimport every time before executing + the Python code typed. + + %autoreload 2 + Reload all modules (except those excluded by %aimport) every time + before executing the Python code typed. + + Reloading Python modules in a reliable way is in general + difficult, and unexpected things may occur. %autoreload tries to + work around common pitfalls by replacing function code objects and + parts of classes previously in the module with new versions. This + makes the following things to work: + + - Functions and classes imported via 'from xxx import foo' are upgraded + to new versions when 'xxx' is reloaded. + + - Methods and properties of classes are upgraded on reload, so that + calling 'c.foo()' on an object 'c' created before the reload causes + the new code for 'foo' to be executed. + + Some of the known remaining caveats are: + + - Replacing code objects does not always succeed: changing a @property + in a class to an ordinary method or a method to a member variable + can cause problems (but in old objects only). + + - Functions that are removed (eg. via monkey-patching) from a module + before it is reloaded are not upgraded. + + - C extension modules cannot be reloaded, and so cannot be + autoreloaded. + + """ + if parameter_s == "": + self._reloader.check(True) + elif parameter_s == "0": + self._reloader.enabled = False + elif parameter_s == "1": + self._reloader.check_all = False + self._reloader.enabled = True + elif parameter_s == "2": + self._reloader.check_all = True + self._reloader.enabled = True + self._reloader.enabled = True + elif parameter_s == "3": + self._reloader.check_all = True + self._reloader.enabled = True + self._reloader.autoload_obj = True + + @line_magic + def aimport(self, parameter_s="", stream=None): + """%aimport => Import modules for automatic reloading. + + %aimport + List modules to automatically import and not to import. + + %aimport foo + Import module 'foo' and mark it to be autoreloaded for %autoreload 1 + + %aimport foo, bar + Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload 1 + + %aimport -foo + Mark module 'foo' to not be autoreloaded for %autoreload 1 + """ + modname = parameter_s + if not modname: + to_reload = sorted(self._reloader.modules.keys()) + to_skip = sorted(self._reloader.skip_modules.keys()) + if stream is None: + stream = sys.stdout + if self._reloader.check_all: + stream.write("Modules to reload:\nall-except-skipped\n") + else: + stream.write("Modules to reload:\n%s\n" % " ".join(to_reload)) + stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip)) + elif modname.startswith("-"): + modname = modname[1:] + self._reloader.mark_module_skipped(modname) + else: + for _module in [_.strip() for _ in modname.split(",")]: + top_module, top_name = self._reloader.aimport_module(_module) + + # Inject module to user namespace + self.shell.push({top_name: top_module}) + + def pre_run_cell(self): + if self._reloader.enabled: + try: + self._reloader.check() + except: + pass + + def post_execute_hook(self): + """Cache the modification times of any modules imported in this execution""" + newly_loaded_modules = set(sys.modules) - self.loaded_modules + for modname in newly_loaded_modules: + _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname]) + if pymtime is not None: + self._reloader.modules_mtimes[modname] = pymtime + + self.loaded_modules.update(newly_loaded_modules) + + +def load_ipython_extension(ip): + """Load the extension in IPython.""" + auto_reload = AutoreloadMagics(ip) + ip.register_magics(auto_reload) + ip.events.register("pre_run_cell", auto_reload.pre_run_cell) + ip.events.register("post_execute", auto_reload.post_execute_hook) diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/storemagic.py b/.venv/lib/python3.8/site-packages/IPython/extensions/storemagic.py new file mode 100644 index 0000000..d9d00f1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/extensions/storemagic.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +""" +%store magic for lightweight persistence. + +Stores variables, aliases and macros in IPython's database. + +To automatically restore stored variables at startup, add this to your +:file:`ipython_config.py` file:: + + c.StoreMagics.autorestore = True +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import inspect, os, sys, textwrap + +from IPython.core.error import UsageError +from IPython.core.magic import Magics, magics_class, line_magic +from IPython.testing.skipdoctest import skip_doctest +from traitlets import Bool + + +def restore_aliases(ip, alias=None): + staliases = ip.db.get('stored_aliases', {}) + if alias is None: + for k,v in staliases.items(): + #print "restore alias",k,v # dbg + #self.alias_table[k] = v + ip.alias_manager.define_alias(k,v) + else: + ip.alias_manager.define_alias(alias, staliases[alias]) + + +def refresh_variables(ip): + db = ip.db + for key in db.keys('autorestore/*'): + # strip autorestore + justkey = os.path.basename(key) + try: + obj = db[key] + except KeyError: + print("Unable to restore variable '%s', ignoring (use %%store -d to forget!)" % justkey) + print("The error was:", sys.exc_info()[0]) + else: + #print "restored",justkey,"=",obj #dbg + ip.user_ns[justkey] = obj + + +def restore_dhist(ip): + ip.user_ns['_dh'] = ip.db.get('dhist',[]) + + +def restore_data(ip): + refresh_variables(ip) + restore_aliases(ip) + restore_dhist(ip) + + +@magics_class +class StoreMagics(Magics): + """Lightweight persistence for python variables. + + Provides the %store magic.""" + + autorestore = Bool(False, help= + """If True, any %store-d variables will be automatically restored + when IPython starts. + """ + ).tag(config=True) + + def __init__(self, shell): + super(StoreMagics, self).__init__(shell=shell) + self.shell.configurables.append(self) + if self.autorestore: + restore_data(self.shell) + + @skip_doctest + @line_magic + def store(self, parameter_s=''): + """Lightweight persistence for python variables. + + Example:: + + In [1]: l = ['hello',10,'world'] + In [2]: %store l + Stored 'l' (list) + In [3]: exit + + (IPython session is closed and started again...) + + ville@badger:~$ ipython + In [1]: l + NameError: name 'l' is not defined + In [2]: %store -r + In [3]: l + Out[3]: ['hello', 10, 'world'] + + Usage: + + * ``%store`` - Show list of all variables and their current + values + * ``%store spam bar`` - Store the *current* value of the variables spam + and bar to disk + * ``%store -d spam`` - Remove the variable and its value from storage + * ``%store -z`` - Remove all variables from storage + * ``%store -r`` - Refresh all variables, aliases and directory history + from store (overwrite current vals) + * ``%store -r spam bar`` - Refresh specified variables and aliases from store + (delete current val) + * ``%store foo >a.txt`` - Store value of foo to new file a.txt + * ``%store foo >>a.txt`` - Append value of foo to file a.txt + + It should be noted that if you change the value of a variable, you + need to %store it again if you want to persist the new value. + + Note also that the variables will need to be pickleable; most basic + python types can be safely %store'd. + + Also aliases can be %store'd across sessions. + To remove an alias from the storage, use the %unalias magic. + """ + + opts,argsl = self.parse_options(parameter_s,'drz',mode='string') + args = argsl.split() + ip = self.shell + db = ip.db + # delete + if 'd' in opts: + try: + todel = args[0] + except IndexError as e: + raise UsageError('You must provide the variable to forget') from e + else: + try: + del db['autorestore/' + todel] + except BaseException as e: + raise UsageError("Can't delete variable '%s'" % todel) from e + # reset + elif 'z' in opts: + for k in db.keys('autorestore/*'): + del db[k] + + elif 'r' in opts: + if args: + for arg in args: + try: + obj = db['autorestore/' + arg] + except KeyError: + try: + restore_aliases(ip, alias=arg) + except KeyError: + print("no stored variable or alias %s" % arg) + else: + ip.user_ns[arg] = obj + else: + restore_data(ip) + + # run without arguments -> list variables & values + elif not args: + vars = db.keys('autorestore/*') + vars.sort() + if vars: + size = max(map(len, vars)) + else: + size = 0 + + print('Stored variables and their in-db values:') + fmt = '%-'+str(size)+'s -> %s' + get = db.get + for var in vars: + justkey = os.path.basename(var) + # print 30 first characters from every var + print(fmt % (justkey, repr(get(var, ''))[:50])) + + # default action - store the variable + else: + # %store foo >file.txt or >>file.txt + if len(args) > 1 and args[1].startswith(">"): + fnam = os.path.expanduser(args[1].lstrip(">").lstrip()) + if args[1].startswith(">>"): + fil = open(fnam, "a", encoding="utf-8") + else: + fil = open(fnam, "w", encoding="utf-8") + with fil: + obj = ip.ev(args[0]) + print("Writing '%s' (%s) to file '%s'." % (args[0], + obj.__class__.__name__, fnam)) + + if not isinstance (obj, str): + from pprint import pprint + pprint(obj, fil) + else: + fil.write(obj) + if not obj.endswith('\n'): + fil.write('\n') + + return + + # %store foo + for arg in args: + try: + obj = ip.user_ns[arg] + except KeyError: + # it might be an alias + name = arg + try: + cmd = ip.alias_manager.retrieve_alias(name) + except ValueError as e: + raise UsageError("Unknown variable '%s'" % name) from e + + staliases = db.get('stored_aliases',{}) + staliases[name] = cmd + db['stored_aliases'] = staliases + print("Alias stored: %s (%s)" % (name, cmd)) + return + + else: + modname = getattr(inspect.getmodule(obj), '__name__', '') + if modname == '__main__': + print(textwrap.dedent("""\ + Warning:%s is %s + Proper storage of interactively declared classes (or instances + of those classes) is not possible! Only instances + of classes in real modules on file system can be %%store'd. + """ % (arg, obj) )) + return + #pickled = pickle.dumps(obj) + db[ 'autorestore/' + arg ] = obj + print("Stored '%s' (%s)" % (arg, obj.__class__.__name__)) + + +def load_ipython_extension(ip): + """Load the extension in IPython.""" + ip.register_magics(StoreMagics) + diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__init__.py b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..e5396e8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/test_autoreload.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/test_autoreload.cpython-38.pyc new file mode 100644 index 0000000..1e15119 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/test_autoreload.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/test_storemagic.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/test_storemagic.cpython-38.pyc new file mode 100644 index 0000000..c86a5b7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/__pycache__/test_storemagic.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/tests/test_autoreload.py b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/test_autoreload.py new file mode 100644 index 0000000..88637fb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/test_autoreload.py @@ -0,0 +1,597 @@ +"""Tests for autoreload extension. +""" +# ----------------------------------------------------------------------------- +# Copyright (c) 2012 IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +import os +import platform +import pytest +import sys +import tempfile +import textwrap +import shutil +import random +import time +from io import StringIO + +import IPython.testing.tools as tt + +from unittest import TestCase + +from IPython.extensions.autoreload import AutoreloadMagics +from IPython.core.events import EventManager, pre_run_cell +from IPython.testing.decorators import skipif_not_numpy + +if platform.python_implementation() == "PyPy": + pytest.skip( + "Current autoreload implementation is extremely slow on PyPy", + allow_module_level=True, + ) + +# ----------------------------------------------------------------------------- +# Test fixture +# ----------------------------------------------------------------------------- + +noop = lambda *a, **kw: None + + +class FakeShell: + def __init__(self): + self.ns = {} + self.user_ns = self.ns + self.user_ns_hidden = {} + self.events = EventManager(self, {"pre_run_cell", pre_run_cell}) + self.auto_magics = AutoreloadMagics(shell=self) + self.events.register("pre_run_cell", self.auto_magics.pre_run_cell) + + register_magics = set_hook = noop + + def run_code(self, code): + self.events.trigger("pre_run_cell") + exec(code, self.user_ns) + self.auto_magics.post_execute_hook() + + def push(self, items): + self.ns.update(items) + + def magic_autoreload(self, parameter): + self.auto_magics.autoreload(parameter) + + def magic_aimport(self, parameter, stream=None): + self.auto_magics.aimport(parameter, stream=stream) + self.auto_magics.post_execute_hook() + + +class Fixture(TestCase): + """Fixture for creating test module files""" + + test_dir = None + old_sys_path = None + filename_chars = "abcdefghijklmopqrstuvwxyz0123456789" + + def setUp(self): + self.test_dir = tempfile.mkdtemp() + self.old_sys_path = list(sys.path) + sys.path.insert(0, self.test_dir) + self.shell = FakeShell() + + def tearDown(self): + shutil.rmtree(self.test_dir) + sys.path = self.old_sys_path + + self.test_dir = None + self.old_sys_path = None + self.shell = None + + def get_module(self): + module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20)) + if module_name in sys.modules: + del sys.modules[module_name] + file_name = os.path.join(self.test_dir, module_name + ".py") + return module_name, file_name + + def write_file(self, filename, content): + """ + Write a file, and force a timestamp difference of at least one second + + Notes + ----- + Python's .pyc files record the timestamp of their compilation + with a time resolution of one second. + + Therefore, we need to force a timestamp difference between .py + and .pyc, without having the .py file be timestamped in the + future, and without changing the timestamp of the .pyc file + (because that is stored in the file). The only reliable way + to achieve this seems to be to sleep. + """ + content = textwrap.dedent(content) + # Sleep one second + eps + time.sleep(1.05) + + # Write + with open(filename, "w", encoding="utf-8") as f: + f.write(content) + + def new_module(self, code): + code = textwrap.dedent(code) + mod_name, mod_fn = self.get_module() + with open(mod_fn, "w", encoding="utf-8") as f: + f.write(code) + return mod_name, mod_fn + + +# ----------------------------------------------------------------------------- +# Test automatic reloading +# ----------------------------------------------------------------------------- + + +def pickle_get_current_class(obj): + """ + Original issue comes from pickle; hence the name. + """ + name = obj.__class__.__name__ + module_name = getattr(obj, "__module__", None) + obj2 = sys.modules[module_name] + for subpath in name.split("."): + obj2 = getattr(obj2, subpath) + return obj2 + + +class TestAutoreload(Fixture): + def test_reload_enums(self): + mod_name, mod_fn = self.new_module( + textwrap.dedent( + """ + from enum import Enum + class MyEnum(Enum): + A = 'A' + B = 'B' + """ + ) + ) + self.shell.magic_autoreload("2") + self.shell.magic_aimport(mod_name) + self.write_file( + mod_fn, + textwrap.dedent( + """ + from enum import Enum + class MyEnum(Enum): + A = 'A' + B = 'B' + C = 'C' + """ + ), + ) + with tt.AssertNotPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger another reload + + def test_reload_class_type(self): + self.shell.magic_autoreload("2") + mod_name, mod_fn = self.new_module( + """ + class Test(): + def meth(self): + return "old" + """ + ) + assert "test" not in self.shell.ns + assert "result" not in self.shell.ns + + self.shell.run_code("from %s import Test" % mod_name) + self.shell.run_code("test = Test()") + + self.write_file( + mod_fn, + """ + class Test(): + def meth(self): + return "new" + """, + ) + + test_object = self.shell.ns["test"] + + # important to trigger autoreload logic ! + self.shell.run_code("pass") + + test_class = pickle_get_current_class(test_object) + assert isinstance(test_object, test_class) + + # extra check. + self.shell.run_code("import pickle") + self.shell.run_code("p = pickle.dumps(test)") + + def test_reload_class_attributes(self): + self.shell.magic_autoreload("2") + mod_name, mod_fn = self.new_module( + textwrap.dedent( + """ + class MyClass: + + def __init__(self, a=10): + self.a = a + self.b = 22 + # self.toto = 33 + + def square(self): + print('compute square') + return self.a*self.a + """ + ) + ) + self.shell.run_code("from %s import MyClass" % mod_name) + self.shell.run_code("first = MyClass(5)") + self.shell.run_code("first.square()") + with self.assertRaises(AttributeError): + self.shell.run_code("first.cube()") + with self.assertRaises(AttributeError): + self.shell.run_code("first.power(5)") + self.shell.run_code("first.b") + with self.assertRaises(AttributeError): + self.shell.run_code("first.toto") + + # remove square, add power + + self.write_file( + mod_fn, + textwrap.dedent( + """ + class MyClass: + + def __init__(self, a=10): + self.a = a + self.b = 11 + + def power(self, p): + print('compute power '+str(p)) + return self.a**p + """ + ), + ) + + self.shell.run_code("second = MyClass(5)") + + for object_name in {"first", "second"}: + self.shell.run_code(f"{object_name}.power(5)") + with self.assertRaises(AttributeError): + self.shell.run_code(f"{object_name}.cube()") + with self.assertRaises(AttributeError): + self.shell.run_code(f"{object_name}.square()") + self.shell.run_code(f"{object_name}.b") + self.shell.run_code(f"{object_name}.a") + with self.assertRaises(AttributeError): + self.shell.run_code(f"{object_name}.toto") + + @skipif_not_numpy + def test_comparing_numpy_structures(self): + self.shell.magic_autoreload("2") + mod_name, mod_fn = self.new_module( + textwrap.dedent( + """ + import numpy as np + class MyClass: + a = (np.array((.1, .2)), + np.array((.2, .3))) + """ + ) + ) + self.shell.run_code("from %s import MyClass" % mod_name) + self.shell.run_code("first = MyClass()") + + # change property `a` + self.write_file( + mod_fn, + textwrap.dedent( + """ + import numpy as np + class MyClass: + a = (np.array((.3, .4)), + np.array((.5, .6))) + """ + ), + ) + + with tt.AssertNotPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger another reload + + def test_autoload_newly_added_objects(self): + self.shell.magic_autoreload("3") + mod_code = """ + def func1(): pass + """ + mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code)) + self.shell.run_code(f"from {mod_name} import *") + self.shell.run_code("func1()") + with self.assertRaises(NameError): + self.shell.run_code("func2()") + with self.assertRaises(NameError): + self.shell.run_code("t = Test()") + with self.assertRaises(NameError): + self.shell.run_code("number") + + # ----------- TEST NEW OBJ LOADED -------------------------- + + new_code = """ + def func1(): pass + def func2(): pass + class Test: pass + number = 0 + from enum import Enum + class TestEnum(Enum): + A = 'a' + """ + self.write_file(mod_fn, textwrap.dedent(new_code)) + + # test function now exists in shell's namespace namespace + self.shell.run_code("func2()") + # test function now exists in module's dict + self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()") + # test class now exists + self.shell.run_code("t = Test()") + # test global built-in var now exists + self.shell.run_code("number") + # test the enumerations gets loaded successfully + self.shell.run_code("TestEnum.A") + + # ----------- TEST NEW OBJ CAN BE CHANGED -------------------- + + new_code = """ + def func1(): return 'changed' + def func2(): return 'changed' + class Test: + def new_func(self): + return 'changed' + number = 1 + from enum import Enum + class TestEnum(Enum): + A = 'a' + B = 'added' + """ + self.write_file(mod_fn, textwrap.dedent(new_code)) + self.shell.run_code("assert func1() == 'changed'") + self.shell.run_code("assert func2() == 'changed'") + self.shell.run_code("t = Test(); assert t.new_func() == 'changed'") + self.shell.run_code("assert number == 1") + self.shell.run_code("assert TestEnum.B.value == 'added'") + + # ----------- TEST IMPORT FROM MODULE -------------------------- + + new_mod_code = """ + from enum import Enum + class Ext(Enum): + A = 'ext' + def ext_func(): + return 'ext' + class ExtTest: + def meth(self): + return 'ext' + ext_int = 2 + """ + new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code)) + current_mod_code = f""" + from {new_mod_name} import * + """ + self.write_file(mod_fn, textwrap.dedent(current_mod_code)) + self.shell.run_code("assert Ext.A.value == 'ext'") + self.shell.run_code("assert ext_func() == 'ext'") + self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'") + self.shell.run_code("assert ext_int == 2") + + def _check_smoketest(self, use_aimport=True): + """ + Functional test for the automatic reloader using either + '%autoreload 1' or '%autoreload 2' + """ + + mod_name, mod_fn = self.new_module( + """ +x = 9 + +z = 123 # this item will be deleted + +def foo(y): + return y + 3 + +class Baz(object): + def __init__(self, x): + self.x = x + def bar(self, y): + return self.x + y + @property + def quux(self): + return 42 + def zzz(self): + '''This method will be deleted below''' + return 99 + +class Bar: # old-style class: weakref doesn't work for it on Python < 2.7 + def foo(self): + return 1 +""" + ) + + # + # Import module, and mark for reloading + # + if use_aimport: + self.shell.magic_autoreload("1") + self.shell.magic_aimport(mod_name) + stream = StringIO() + self.shell.magic_aimport("", stream=stream) + self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue()) + + with self.assertRaises(ImportError): + self.shell.magic_aimport("tmpmod_as318989e89ds") + else: + self.shell.magic_autoreload("2") + self.shell.run_code("import %s" % mod_name) + stream = StringIO() + self.shell.magic_aimport("", stream=stream) + self.assertTrue( + "Modules to reload:\nall-except-skipped" in stream.getvalue() + ) + self.assertIn(mod_name, self.shell.ns) + + mod = sys.modules[mod_name] + + # + # Test module contents + # + old_foo = mod.foo + old_obj = mod.Baz(9) + old_obj2 = mod.Bar() + + def check_module_contents(): + self.assertEqual(mod.x, 9) + self.assertEqual(mod.z, 123) + + self.assertEqual(old_foo(0), 3) + self.assertEqual(mod.foo(0), 3) + + obj = mod.Baz(9) + self.assertEqual(old_obj.bar(1), 10) + self.assertEqual(obj.bar(1), 10) + self.assertEqual(obj.quux, 42) + self.assertEqual(obj.zzz(), 99) + + obj2 = mod.Bar() + self.assertEqual(old_obj2.foo(), 1) + self.assertEqual(obj2.foo(), 1) + + check_module_contents() + + # + # Simulate a failed reload: no reload should occur and exactly + # one error message should be printed + # + self.write_file( + mod_fn, + """ +a syntax error +""", + ) + + with tt.AssertPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger reload + with tt.AssertNotPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger another reload + check_module_contents() + + # + # Rewrite module (this time reload should succeed) + # + self.write_file( + mod_fn, + """ +x = 10 + +def foo(y): + return y + 4 + +class Baz(object): + def __init__(self, x): + self.x = x + def bar(self, y): + return self.x + y + 1 + @property + def quux(self): + return 43 + +class Bar: # old-style class + def foo(self): + return 2 +""", + ) + + def check_module_contents(): + self.assertEqual(mod.x, 10) + self.assertFalse(hasattr(mod, "z")) + + self.assertEqual(old_foo(0), 4) # superreload magic! + self.assertEqual(mod.foo(0), 4) + + obj = mod.Baz(9) + self.assertEqual(old_obj.bar(1), 11) # superreload magic! + self.assertEqual(obj.bar(1), 11) + + self.assertEqual(old_obj.quux, 43) + self.assertEqual(obj.quux, 43) + + self.assertFalse(hasattr(old_obj, "zzz")) + self.assertFalse(hasattr(obj, "zzz")) + + obj2 = mod.Bar() + self.assertEqual(old_obj2.foo(), 2) + self.assertEqual(obj2.foo(), 2) + + self.shell.run_code("pass") # trigger reload + check_module_contents() + + # + # Another failure case: deleted file (shouldn't reload) + # + os.unlink(mod_fn) + + self.shell.run_code("pass") # trigger reload + check_module_contents() + + # + # Disable autoreload and rewrite module: no reload should occur + # + if use_aimport: + self.shell.magic_aimport("-" + mod_name) + stream = StringIO() + self.shell.magic_aimport("", stream=stream) + self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue()) + + # This should succeed, although no such module exists + self.shell.magic_aimport("-tmpmod_as318989e89ds") + else: + self.shell.magic_autoreload("0") + + self.write_file( + mod_fn, + """ +x = -99 +""", + ) + + self.shell.run_code("pass") # trigger reload + self.shell.run_code("pass") + check_module_contents() + + # + # Re-enable autoreload: reload should now occur + # + if use_aimport: + self.shell.magic_aimport(mod_name) + else: + self.shell.magic_autoreload("") + + self.shell.run_code("pass") # trigger reload + self.assertEqual(mod.x, -99) + + def test_smoketest_aimport(self): + self._check_smoketest(use_aimport=True) + + def test_smoketest_autoreload(self): + self._check_smoketest(use_aimport=False) diff --git a/.venv/lib/python3.8/site-packages/IPython/extensions/tests/test_storemagic.py b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/test_storemagic.py new file mode 100644 index 0000000..3ac306b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/extensions/tests/test_storemagic.py @@ -0,0 +1,66 @@ +import tempfile, os +from pathlib import Path + +from traitlets.config.loader import Config + + +def setup_module(): + ip.magic('load_ext storemagic') + +def test_store_restore(): + assert 'bar' not in ip.user_ns, "Error: some other test leaked `bar` in user_ns" + assert 'foo' not in ip.user_ns, "Error: some other test leaked `foo` in user_ns" + assert 'foobar' not in ip.user_ns, "Error: some other test leaked `foobar` in user_ns" + assert 'foobaz' not in ip.user_ns, "Error: some other test leaked `foobaz` in user_ns" + ip.user_ns['foo'] = 78 + ip.magic('alias bar echo "hello"') + ip.user_ns['foobar'] = 79 + ip.user_ns['foobaz'] = '80' + tmpd = tempfile.mkdtemp() + ip.magic('cd ' + tmpd) + ip.magic('store foo') + ip.magic('store bar') + ip.magic('store foobar foobaz') + + # Check storing + assert ip.db["autorestore/foo"] == 78 + assert "bar" in ip.db["stored_aliases"] + assert ip.db["autorestore/foobar"] == 79 + assert ip.db["autorestore/foobaz"] == "80" + + # Remove those items + ip.user_ns.pop('foo', None) + ip.user_ns.pop('foobar', None) + ip.user_ns.pop('foobaz', None) + ip.alias_manager.undefine_alias('bar') + ip.magic('cd -') + ip.user_ns['_dh'][:] = [] + + # Check restoring + ip.magic("store -r foo bar foobar foobaz") + assert ip.user_ns["foo"] == 78 + assert ip.alias_manager.is_alias("bar") + assert ip.user_ns["foobar"] == 79 + assert ip.user_ns["foobaz"] == "80" + + ip.magic("store -r") # restores _dh too + assert any(Path(tmpd).samefile(p) for p in ip.user_ns["_dh"]) + + os.rmdir(tmpd) + +def test_autorestore(): + ip.user_ns['foo'] = 95 + ip.magic('store foo') + del ip.user_ns['foo'] + c = Config() + c.StoreMagics.autorestore = False + orig_config = ip.config + try: + ip.config = c + ip.extension_manager.reload_extension("storemagic") + assert "foo" not in ip.user_ns + c.StoreMagics.autorestore = True + ip.extension_manager.reload_extension("storemagic") + assert ip.user_ns["foo"] == 95 + finally: + ip.config = orig_config diff --git a/.venv/lib/python3.8/site-packages/IPython/external/__init__.py b/.venv/lib/python3.8/site-packages/IPython/external/__init__.py new file mode 100644 index 0000000..eedc338 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/external/__init__.py @@ -0,0 +1,7 @@ +""" +This package contains all third-party modules bundled with IPython. +""" + +from typing import List + +__all__: List[str] = [] diff --git a/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..0af4cd8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/qt_for_kernel.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/qt_for_kernel.cpython-38.pyc new file mode 100644 index 0000000..a291210 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/qt_for_kernel.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/qt_loaders.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/qt_loaders.cpython-38.pyc new file mode 100644 index 0000000..ab98bed Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/external/__pycache__/qt_loaders.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/external/qt_for_kernel.py b/.venv/lib/python3.8/site-packages/IPython/external/qt_for_kernel.py new file mode 100644 index 0000000..b3168f6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/external/qt_for_kernel.py @@ -0,0 +1,128 @@ +""" Import Qt in a manner suitable for an IPython kernel. + +This is the import used for the `gui=qt` or `matplotlib=qt` initialization. + +Import Priority: + +if Qt has been imported anywhere else: + use that + +if matplotlib has been imported and doesn't support v2 (<= 1.0.1): + use PyQt4 @v1 + +Next, ask QT_API env variable + +if QT_API not set: + ask matplotlib what it's using. If Qt4Agg or Qt5Agg, then use the + version matplotlib is configured with + + else: (matplotlib said nothing) + # this is the default path - nobody told us anything + try in this order: + PyQt default version, PySide, PyQt5 +else: + use what QT_API says + +""" +# NOTE: This is no longer an external, third-party module, and should be +# considered part of IPython. For compatibility however, it is being kept in +# IPython/external. + +import os +import sys + +from IPython.external.qt_loaders import ( + load_qt, + loaded_api, + enum_factory, + # QT6 + QT_API_PYQT6, + QT_API_PYSIDE6, + # QT5 + QT_API_PYQT5, + QT_API_PYSIDE2, + # QT4 + QT_API_PYQTv1, + QT_API_PYQT, + QT_API_PYSIDE, + # default + QT_API_PYQT_DEFAULT, +) + +_qt_apis = ( + # QT6 + QT_API_PYQT6, + QT_API_PYSIDE6, + # QT5 + QT_API_PYQT5, + QT_API_PYSIDE2, + # QT4 + QT_API_PYQTv1, + QT_API_PYQT, + QT_API_PYSIDE, + # default + QT_API_PYQT_DEFAULT, +) + + +def matplotlib_options(mpl): + """Constraints placed on an imported matplotlib.""" + if mpl is None: + return + backend = mpl.rcParams.get('backend', None) + if backend == 'Qt4Agg': + mpqt = mpl.rcParams.get('backend.qt4', None) + if mpqt is None: + return None + if mpqt.lower() == 'pyside': + return [QT_API_PYSIDE] + elif mpqt.lower() == 'pyqt4': + return [QT_API_PYQT_DEFAULT] + elif mpqt.lower() == 'pyqt4v2': + return [QT_API_PYQT] + raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" % + mpqt) + elif backend == 'Qt5Agg': + mpqt = mpl.rcParams.get('backend.qt5', None) + if mpqt is None: + return None + if mpqt.lower() == 'pyqt5': + return [QT_API_PYQT5] + raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" % + mpqt) + +def get_options(): + """Return a list of acceptable QT APIs, in decreasing order of preference.""" + #already imported Qt somewhere. Use that + loaded = loaded_api() + if loaded is not None: + return [loaded] + + mpl = sys.modules.get('matplotlib', None) + + if mpl is not None and tuple(mpl.__version__.split(".")) < ("1", "0", "2"): + # 1.0.1 only supports PyQt4 v1 + return [QT_API_PYQT_DEFAULT] + + qt_api = os.environ.get('QT_API', None) + if qt_api is None: + #no ETS variable. Ask mpl, then use default fallback path + return matplotlib_options(mpl) or [ + QT_API_PYQT_DEFAULT, + QT_API_PYQT6, + QT_API_PYSIDE6, + QT_API_PYQT5, + QT_API_PYSIDE2, + QT_API_PYQT, + QT_API_PYSIDE, + ] + elif qt_api not in _qt_apis: + raise RuntimeError("Invalid Qt API %r, valid values are: %r" % + (qt_api, ', '.join(_qt_apis))) + else: + return [qt_api] + + +api_opts = get_options() +QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts) +enum_helper = enum_factory(QT_API, QtCore) diff --git a/.venv/lib/python3.8/site-packages/IPython/external/qt_loaders.py b/.venv/lib/python3.8/site-packages/IPython/external/qt_loaders.py new file mode 100644 index 0000000..975855c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/external/qt_loaders.py @@ -0,0 +1,397 @@ +""" +This module contains factory functions that attempt +to return Qt submodules from the various python Qt bindings. + +It also protects against double-importing Qt with different +bindings, which is unstable and likely to crash + +This is used primarily by qt and qt_for_kernel, and shouldn't +be accessed directly from the outside +""" +import importlib.abc +import sys +import types +from functools import partial, lru_cache +import operator + +# ### Available APIs. +# Qt6 +QT_API_PYQT6 = "pyqt6" +QT_API_PYSIDE6 = "pyside6" + +# Qt5 +QT_API_PYQT5 = 'pyqt5' +QT_API_PYSIDE2 = 'pyside2' + +# Qt4 +QT_API_PYQT = "pyqt" # Force version 2 +QT_API_PYQTv1 = "pyqtv1" # Force version 2 +QT_API_PYSIDE = "pyside" + +QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2 + +api_to_module = { + # Qt6 + QT_API_PYQT6: "PyQt6", + QT_API_PYSIDE6: "PySide6", + # Qt5 + QT_API_PYQT5: "PyQt5", + QT_API_PYSIDE2: "PySide2", + # Qt4 + QT_API_PYSIDE: "PySide", + QT_API_PYQT: "PyQt4", + QT_API_PYQTv1: "PyQt4", + # default + QT_API_PYQT_DEFAULT: "PyQt6", +} + + +class ImportDenier(importlib.abc.MetaPathFinder): + """Import Hook that will guard against bad Qt imports + once IPython commits to a specific binding + """ + + def __init__(self): + self.__forbidden = set() + + def forbid(self, module_name): + sys.modules.pop(module_name, None) + self.__forbidden.add(module_name) + + def find_spec(self, fullname, path, target=None): + if path: + return + if fullname in self.__forbidden: + raise ImportError( + """ + Importing %s disabled by IPython, which has + already imported an Incompatible QT Binding: %s + """ % (fullname, loaded_api())) + + +ID = ImportDenier() +sys.meta_path.insert(0, ID) + + +def commit_api(api): + """Commit to a particular API, and trigger ImportErrors on subsequent + dangerous imports""" + modules = set(api_to_module.values()) + + modules.remove(api_to_module[api]) + for mod in modules: + ID.forbid(mod) + + +def loaded_api(): + """Return which API is loaded, if any + + If this returns anything besides None, + importing any other Qt binding is unsafe. + + Returns + ------- + None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1' + """ + if sys.modules.get("PyQt6.QtCore"): + return QT_API_PYQT6 + elif sys.modules.get("PySide6.QtCore"): + return QT_API_PYSIDE6 + elif sys.modules.get("PyQt5.QtCore"): + return QT_API_PYQT5 + elif sys.modules.get("PySide2.QtCore"): + return QT_API_PYSIDE2 + elif sys.modules.get("PyQt4.QtCore"): + if qtapi_version() == 2: + return QT_API_PYQT + else: + return QT_API_PYQTv1 + elif sys.modules.get("PySide.QtCore"): + return QT_API_PYSIDE + + return None + + +def has_binding(api): + """Safely check for PyQt4/5, PySide or PySide2, without importing submodules + + Parameters + ---------- + api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault'] + Which module to check for + + Returns + ------- + True if the relevant module appears to be importable + """ + module_name = api_to_module[api] + from importlib.util import find_spec + + required = ['QtCore', 'QtGui', 'QtSvg'] + if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6): + # QT5 requires QtWidgets too + required.append('QtWidgets') + + for submod in required: + try: + spec = find_spec('%s.%s' % (module_name, submod)) + except ImportError: + # Package (e.g. PyQt5) not found + return False + else: + if spec is None: + # Submodule (e.g. PyQt5.QtCore) not found + return False + + if api == QT_API_PYSIDE: + # We can also safely check PySide version + import PySide + + return PySide.__version_info__ >= (1, 0, 3) + + return True + + +def qtapi_version(): + """Return which QString API has been set, if any + + Returns + ------- + The QString API version (1 or 2), or None if not set + """ + try: + import sip + except ImportError: + # as of PyQt5 5.11, sip is no longer available as a top-level + # module and needs to be imported from the PyQt5 namespace + try: + from PyQt5 import sip + except ImportError: + return + try: + return sip.getapi('QString') + except ValueError: + return + + +def can_import(api): + """Safely query whether an API is importable, without importing it""" + if not has_binding(api): + return False + + current = loaded_api() + if api == QT_API_PYQT_DEFAULT: + return current in [QT_API_PYQT6, None] + else: + return current in [api, None] + + +def import_pyqt4(version=2): + """ + Import PyQt4 + + Parameters + ---------- + version : 1, 2, or None + Which QString/QVariant API to use. Set to None to use the system + default + ImportErrors raised within this function are non-recoverable + """ + # The new-style string API (version=2) automatically + # converts QStrings to Unicode Python strings. Also, automatically unpacks + # QVariants to their underlying objects. + import sip + + if version is not None: + sip.setapi('QString', version) + sip.setapi('QVariant', version) + + from PyQt4 import QtGui, QtCore, QtSvg + + if QtCore.PYQT_VERSION < 0x040700: + raise ImportError("IPython requires PyQt4 >= 4.7, found %s" % + QtCore.PYQT_VERSION_STR) + + # Alias PyQt-specific functions for PySide compatibility. + QtCore.Signal = QtCore.pyqtSignal + QtCore.Slot = QtCore.pyqtSlot + + # query for the API version (in case version == None) + version = sip.getapi('QString') + api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT + return QtCore, QtGui, QtSvg, api + + +def import_pyqt5(): + """ + Import PyQt5 + + ImportErrors raised within this function are non-recoverable + """ + + from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui + + # Alias PyQt-specific functions for PySide compatibility. + QtCore.Signal = QtCore.pyqtSignal + QtCore.Slot = QtCore.pyqtSlot + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType('QtGuiCompat') + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + + api = QT_API_PYQT5 + return QtCore, QtGuiCompat, QtSvg, api + + +def import_pyqt6(): + """ + Import PyQt6 + + ImportErrors raised within this function are non-recoverable + """ + + from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui + + # Alias PyQt-specific functions for PySide compatibility. + QtCore.Signal = QtCore.pyqtSignal + QtCore.Slot = QtCore.pyqtSlot + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType("QtGuiCompat") + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + + api = QT_API_PYQT6 + return QtCore, QtGuiCompat, QtSvg, api + + +def import_pyside(): + """ + Import PySide + + ImportErrors raised within this function are non-recoverable + """ + from PySide import QtGui, QtCore, QtSvg + return QtCore, QtGui, QtSvg, QT_API_PYSIDE + +def import_pyside2(): + """ + Import PySide2 + + ImportErrors raised within this function are non-recoverable + """ + from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType('QtGuiCompat') + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) + + return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2 + + +def import_pyside6(): + """ + Import PySide6 + + ImportErrors raised within this function are non-recoverable + """ + from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType("QtGuiCompat") + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) + + return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6 + + +def load_qt(api_options): + """ + Attempt to import Qt, given a preference list + of permissible bindings + + It is safe to call this function multiple times. + + Parameters + ---------- + api_options : List of strings + The order of APIs to try. Valid items are 'pyside', 'pyside2', + 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault' + + Returns + ------- + A tuple of QtCore, QtGui, QtSvg, QT_API + The first three are the Qt modules. The last is the + string indicating which module was loaded. + + Raises + ------ + ImportError, if it isn't possible to import any requested + bindings (either because they aren't installed, or because + an incompatible library has already been installed) + """ + loaders = { + # Qt6 + QT_API_PYQT6: import_pyqt6, + QT_API_PYSIDE6: import_pyside6, + # Qt5 + QT_API_PYQT5: import_pyqt5, + QT_API_PYSIDE2: import_pyside2, + # Qt4 + QT_API_PYSIDE: import_pyside, + QT_API_PYQT: import_pyqt4, + QT_API_PYQTv1: partial(import_pyqt4, version=1), + # default + QT_API_PYQT_DEFAULT: import_pyqt6, + } + + for api in api_options: + + if api not in loaders: + raise RuntimeError( + "Invalid Qt API %r, valid values are: %s" % + (api, ", ".join(["%r" % k for k in loaders.keys()]))) + + if not can_import(api): + continue + + #cannot safely recover from an ImportError during this + result = loaders[api]() + api = result[-1] # changed if api = QT_API_PYQT_DEFAULT + commit_api(api) + return result + else: + raise ImportError(""" + Could not load requested Qt binding. Please ensure that + PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available, + and only one is imported per session. + + Currently-imported Qt library: %r + PyQt4 available (requires QtCore, QtGui, QtSvg): %s + PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s + PySide >= 1.0.3 installed: %s + PySide2 installed: %s + Tried to load: %r + """ % (loaded_api(), + has_binding(QT_API_PYQT), + has_binding(QT_API_PYQT5), + has_binding(QT_API_PYSIDE), + has_binding(QT_API_PYSIDE2), + api_options)) + + +def enum_factory(QT_API, QtCore): + """Construct an enum helper to account for PyQt5 <-> PyQt6 changes.""" + + @lru_cache(None) + def _enum(name): + # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6). + return operator.attrgetter( + name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0] + )(sys.modules[QtCore.__package__]) + + return _enum diff --git a/.venv/lib/python3.8/site-packages/IPython/external/tests/__init__.py b/.venv/lib/python3.8/site-packages/IPython/external/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/IPython/external/tests/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/external/tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..bcaecd6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/external/tests/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/external/tests/__pycache__/test_qt_loaders.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/external/tests/__pycache__/test_qt_loaders.cpython-38.pyc new file mode 100644 index 0000000..f05db5d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/external/tests/__pycache__/test_qt_loaders.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/external/tests/test_qt_loaders.py b/.venv/lib/python3.8/site-packages/IPython/external/tests/test_qt_loaders.py new file mode 100644 index 0000000..7bc9ccf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/external/tests/test_qt_loaders.py @@ -0,0 +1,11 @@ +import importlib +import pytest +from IPython.external.qt_loaders import ID + + +def test_import_denier(): + ID.forbid("ipython_denied_module") + with pytest.raises(ImportError, match="disabled by IPython"): + import ipython_denied_module + with pytest.raises(ImportError, match="disabled by IPython"): + importlib.import_module("ipython_denied_module") diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__init__.py b/.venv/lib/python3.8/site-packages/IPython/lib/__init__.py new file mode 100644 index 0000000..94b8ade --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/__init__.py @@ -0,0 +1,11 @@ +# encoding: utf-8 +""" +Extra capabilities for IPython +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..af1c4a5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/backgroundjobs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/backgroundjobs.cpython-38.pyc new file mode 100644 index 0000000..85118b4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/backgroundjobs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/clipboard.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/clipboard.cpython-38.pyc new file mode 100644 index 0000000..47b5bc3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/clipboard.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/deepreload.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/deepreload.cpython-38.pyc new file mode 100644 index 0000000..8ea91b2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/deepreload.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/demo.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/demo.cpython-38.pyc new file mode 100644 index 0000000..8aeb7b3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/demo.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/display.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/display.cpython-38.pyc new file mode 100644 index 0000000..30eb430 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/display.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/editorhooks.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/editorhooks.cpython-38.pyc new file mode 100644 index 0000000..b69bb74 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/editorhooks.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/guisupport.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/guisupport.cpython-38.pyc new file mode 100644 index 0000000..5fd099d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/guisupport.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/latextools.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/latextools.cpython-38.pyc new file mode 100644 index 0000000..9639a16 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/latextools.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/lexers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/lexers.cpython-38.pyc new file mode 100644 index 0000000..eef5fbd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/lexers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/pretty.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/pretty.cpython-38.pyc new file mode 100644 index 0000000..5617f69 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/__pycache__/pretty.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/backgroundjobs.py b/.venv/lib/python3.8/site-packages/IPython/lib/backgroundjobs.py new file mode 100644 index 0000000..e7ad51e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/backgroundjobs.py @@ -0,0 +1,491 @@ +# -*- coding: utf-8 -*- +"""Manage background (threaded) jobs conveniently from an interactive shell. + +This module provides a BackgroundJobManager class. This is the main class +meant for public usage, it implements an object which can create and manage +new background jobs. + +It also provides the actual job classes managed by these BackgroundJobManager +objects, see their docstrings below. + + +This system was inspired by discussions with B. Granger and the +BackgroundCommand class described in the book Python Scripting for +Computational Science, by H. P. Langtangen: + +http://folk.uio.no/hpl/scripting + +(although ultimately no code from this text was used, as IPython's system is a +separate implementation). + +An example notebook is provided in our documentation illustrating interactive +use of the system. +""" + +#***************************************************************************** +# Copyright (C) 2005-2006 Fernando Perez +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#***************************************************************************** + +# Code begins +import sys +import threading + +from IPython import get_ipython +from IPython.core.ultratb import AutoFormattedTB +from logging import error, debug + + +class BackgroundJobManager(object): + """Class to manage a pool of backgrounded threaded jobs. + + Below, we assume that 'jobs' is a BackgroundJobManager instance. + + Usage summary (see the method docstrings for details): + + jobs.new(...) -> start a new job + + jobs() or jobs.status() -> print status summary of all jobs + + jobs[N] -> returns job number N. + + foo = jobs[N].result -> assign to variable foo the result of job N + + jobs[N].traceback() -> print the traceback of dead job N + + jobs.remove(N) -> remove (finished) job N + + jobs.flush() -> remove all finished jobs + + As a convenience feature, BackgroundJobManager instances provide the + utility result and traceback methods which retrieve the corresponding + information from the jobs list: + + jobs.result(N) <--> jobs[N].result + jobs.traceback(N) <--> jobs[N].traceback() + + While this appears minor, it allows you to use tab completion + interactively on the job manager instance. + """ + + def __init__(self): + # Lists for job management, accessed via a property to ensure they're + # up to date.x + self._running = [] + self._completed = [] + self._dead = [] + # A dict of all jobs, so users can easily access any of them + self.all = {} + # For reporting + self._comp_report = [] + self._dead_report = [] + # Store status codes locally for fast lookups + self._s_created = BackgroundJobBase.stat_created_c + self._s_running = BackgroundJobBase.stat_running_c + self._s_completed = BackgroundJobBase.stat_completed_c + self._s_dead = BackgroundJobBase.stat_dead_c + self._current_job_id = 0 + + @property + def running(self): + self._update_status() + return self._running + + @property + def dead(self): + self._update_status() + return self._dead + + @property + def completed(self): + self._update_status() + return self._completed + + def new(self, func_or_exp, *args, **kwargs): + """Add a new background job and start it in a separate thread. + + There are two types of jobs which can be created: + + 1. Jobs based on expressions which can be passed to an eval() call. + The expression must be given as a string. For example: + + job_manager.new('myfunc(x,y,z=1)'[,glob[,loc]]) + + The given expression is passed to eval(), along with the optional + global/local dicts provided. If no dicts are given, they are + extracted automatically from the caller's frame. + + A Python statement is NOT a valid eval() expression. Basically, you + can only use as an eval() argument something which can go on the right + of an '=' sign and be assigned to a variable. + + For example,"print 'hello'" is not valid, but '2+3' is. + + 2. Jobs given a function object, optionally passing additional + positional arguments: + + job_manager.new(myfunc, x, y) + + The function is called with the given arguments. + + If you need to pass keyword arguments to your function, you must + supply them as a dict named kw: + + job_manager.new(myfunc, x, y, kw=dict(z=1)) + + The reason for this asymmetry is that the new() method needs to + maintain access to its own keywords, and this prevents name collisions + between arguments to new() and arguments to your own functions. + + In both cases, the result is stored in the job.result field of the + background job object. + + You can set `daemon` attribute of the thread by giving the keyword + argument `daemon`. + + Notes and caveats: + + 1. All threads running share the same standard output. Thus, if your + background jobs generate output, it will come out on top of whatever + you are currently writing. For this reason, background jobs are best + used with silent functions which simply return their output. + + 2. Threads also all work within the same global namespace, and this + system does not lock interactive variables. So if you send job to the + background which operates on a mutable object for a long time, and + start modifying that same mutable object interactively (or in another + backgrounded job), all sorts of bizarre behaviour will occur. + + 3. If a background job is spending a lot of time inside a C extension + module which does not release the Python Global Interpreter Lock + (GIL), this will block the IPython prompt. This is simply because the + Python interpreter can only switch between threads at Python + bytecodes. While the execution is inside C code, the interpreter must + simply wait unless the extension module releases the GIL. + + 4. There is no way, due to limitations in the Python threads library, + to kill a thread once it has started.""" + + if callable(func_or_exp): + kw = kwargs.get('kw',{}) + job = BackgroundJobFunc(func_or_exp,*args,**kw) + elif isinstance(func_or_exp, str): + if not args: + frame = sys._getframe(1) + glob, loc = frame.f_globals, frame.f_locals + elif len(args)==1: + glob = loc = args[0] + elif len(args)==2: + glob,loc = args + else: + raise ValueError( + 'Expression jobs take at most 2 args (globals,locals)') + job = BackgroundJobExpr(func_or_exp, glob, loc) + else: + raise TypeError('invalid args for new job') + + if kwargs.get('daemon', False): + job.daemon = True + job.num = self._current_job_id + self._current_job_id += 1 + self.running.append(job) + self.all[job.num] = job + debug('Starting job # %s in a separate thread.' % job.num) + job.start() + return job + + def __getitem__(self, job_key): + num = job_key if isinstance(job_key, int) else job_key.num + return self.all[num] + + def __call__(self): + """An alias to self.status(), + + This allows you to simply call a job manager instance much like the + Unix `jobs` shell command.""" + + return self.status() + + def _update_status(self): + """Update the status of the job lists. + + This method moves finished jobs to one of two lists: + - self.completed: jobs which completed successfully + - self.dead: jobs which finished but died. + + It also copies those jobs to corresponding _report lists. These lists + are used to report jobs completed/dead since the last update, and are + then cleared by the reporting function after each call.""" + + # Status codes + srun, scomp, sdead = self._s_running, self._s_completed, self._s_dead + # State lists, use the actual lists b/c the public names are properties + # that call this very function on access + running, completed, dead = self._running, self._completed, self._dead + + # Now, update all state lists + for num, job in enumerate(running): + stat = job.stat_code + if stat == srun: + continue + elif stat == scomp: + completed.append(job) + self._comp_report.append(job) + running[num] = False + elif stat == sdead: + dead.append(job) + self._dead_report.append(job) + running[num] = False + # Remove dead/completed jobs from running list + running[:] = filter(None, running) + + def _group_report(self,group,name): + """Report summary for a given job group. + + Return True if the group had any elements.""" + + if group: + print('%s jobs:' % name) + for job in group: + print('%s : %s' % (job.num,job)) + print() + return True + + def _group_flush(self,group,name): + """Flush a given job group + + Return True if the group had any elements.""" + + njobs = len(group) + if njobs: + plural = {1:''}.setdefault(njobs,'s') + print('Flushing %s %s job%s.' % (njobs,name,plural)) + group[:] = [] + return True + + def _status_new(self): + """Print the status of newly finished jobs. + + Return True if any new jobs are reported. + + This call resets its own state every time, so it only reports jobs + which have finished since the last time it was called.""" + + self._update_status() + new_comp = self._group_report(self._comp_report, 'Completed') + new_dead = self._group_report(self._dead_report, + 'Dead, call jobs.traceback() for details') + self._comp_report[:] = [] + self._dead_report[:] = [] + return new_comp or new_dead + + def status(self,verbose=0): + """Print a status of all jobs currently being managed.""" + + self._update_status() + self._group_report(self.running,'Running') + self._group_report(self.completed,'Completed') + self._group_report(self.dead,'Dead') + # Also flush the report queues + self._comp_report[:] = [] + self._dead_report[:] = [] + + def remove(self,num): + """Remove a finished (completed or dead) job.""" + + try: + job = self.all[num] + except KeyError: + error('Job #%s not found' % num) + else: + stat_code = job.stat_code + if stat_code == self._s_running: + error('Job #%s is still running, it can not be removed.' % num) + return + elif stat_code == self._s_completed: + self.completed.remove(job) + elif stat_code == self._s_dead: + self.dead.remove(job) + + def flush(self): + """Flush all finished jobs (completed and dead) from lists. + + Running jobs are never flushed. + + It first calls _status_new(), to update info. If any jobs have + completed since the last _status_new() call, the flush operation + aborts.""" + + # Remove the finished jobs from the master dict + alljobs = self.all + for job in self.completed+self.dead: + del(alljobs[job.num]) + + # Now flush these lists completely + fl_comp = self._group_flush(self.completed, 'Completed') + fl_dead = self._group_flush(self.dead, 'Dead') + if not (fl_comp or fl_dead): + print('No jobs to flush.') + + def result(self,num): + """result(N) -> return the result of job N.""" + try: + return self.all[num].result + except KeyError: + error('Job #%s not found' % num) + + def _traceback(self, job): + num = job if isinstance(job, int) else job.num + try: + self.all[num].traceback() + except KeyError: + error('Job #%s not found' % num) + + def traceback(self, job=None): + if job is None: + self._update_status() + for deadjob in self.dead: + print("Traceback for: %r" % deadjob) + self._traceback(deadjob) + print() + else: + self._traceback(job) + + +class BackgroundJobBase(threading.Thread): + """Base class to build BackgroundJob classes. + + The derived classes must implement: + + - Their own __init__, since the one here raises NotImplementedError. The + derived constructor must call self._init() at the end, to provide common + initialization. + + - A strform attribute used in calls to __str__. + + - A call() method, which will make the actual execution call and must + return a value to be held in the 'result' field of the job object. + """ + + # Class constants for status, in string and as numerical codes (when + # updating jobs lists, we don't want to do string comparisons). This will + # be done at every user prompt, so it has to be as fast as possible + stat_created = 'Created'; stat_created_c = 0 + stat_running = 'Running'; stat_running_c = 1 + stat_completed = 'Completed'; stat_completed_c = 2 + stat_dead = 'Dead (Exception), call jobs.traceback() for details' + stat_dead_c = -1 + + def __init__(self): + """Must be implemented in subclasses. + + Subclasses must call :meth:`_init` for standard initialisation. + """ + raise NotImplementedError("This class can not be instantiated directly.") + + def _init(self): + """Common initialization for all BackgroundJob objects""" + + for attr in ['call','strform']: + assert hasattr(self,attr), "Missing attribute <%s>" % attr + + # The num tag can be set by an external job manager + self.num = None + + self.status = BackgroundJobBase.stat_created + self.stat_code = BackgroundJobBase.stat_created_c + self.finished = False + self.result = '' + + # reuse the ipython traceback handler if we can get to it, otherwise + # make a new one + try: + make_tb = get_ipython().InteractiveTB.text + except: + make_tb = AutoFormattedTB(mode = 'Context', + color_scheme='NoColor', + tb_offset = 1).text + # Note that the actual API for text() requires the three args to be + # passed in, so we wrap it in a simple lambda. + self._make_tb = lambda : make_tb(None, None, None) + + # Hold a formatted traceback if one is generated. + self._tb = None + + threading.Thread.__init__(self) + + def __str__(self): + return self.strform + + def __repr__(self): + return '' % (self.num, self.strform) + + def traceback(self): + print(self._tb) + + def run(self): + try: + self.status = BackgroundJobBase.stat_running + self.stat_code = BackgroundJobBase.stat_running_c + self.result = self.call() + except: + self.status = BackgroundJobBase.stat_dead + self.stat_code = BackgroundJobBase.stat_dead_c + self.finished = None + self.result = ('') + self._tb = self._make_tb() + else: + self.status = BackgroundJobBase.stat_completed + self.stat_code = BackgroundJobBase.stat_completed_c + self.finished = True + + +class BackgroundJobExpr(BackgroundJobBase): + """Evaluate an expression as a background job (uses a separate thread).""" + + def __init__(self, expression, glob=None, loc=None): + """Create a new job from a string which can be fed to eval(). + + global/locals dicts can be provided, which will be passed to the eval + call.""" + + # fail immediately if the given expression can't be compiled + self.code = compile(expression,'','eval') + + glob = {} if glob is None else glob + loc = {} if loc is None else loc + self.expression = self.strform = expression + self.glob = glob + self.loc = loc + self._init() + + def call(self): + return eval(self.code,self.glob,self.loc) + + +class BackgroundJobFunc(BackgroundJobBase): + """Run a function call as a background job (uses a separate thread).""" + + def __init__(self, func, *args, **kwargs): + """Create a new job from a callable object. + + Any positional arguments and keyword args given to this constructor + after the initial callable are passed directly to it.""" + + if not callable(func): + raise TypeError( + 'first argument to BackgroundJobFunc must be callable') + + self.func = func + self.args = args + self.kwargs = kwargs + # The string form will only include the function passed, because + # generating string representations of the arguments is a potentially + # _very_ expensive operation (e.g. with large arrays). + self.strform = str(func) + self._init() + + def call(self): + return self.func(*self.args, **self.kwargs) diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/clipboard.py b/.venv/lib/python3.8/site-packages/IPython/lib/clipboard.py new file mode 100644 index 0000000..95a6b0a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/clipboard.py @@ -0,0 +1,69 @@ +""" Utilities for accessing the platform's clipboard. +""" + +import subprocess + +from IPython.core.error import TryNext +import IPython.utils.py3compat as py3compat + +class ClipboardEmpty(ValueError): + pass + +def win32_clipboard_get(): + """ Get the current clipboard's text on Windows. + + Requires Mark Hammond's pywin32 extensions. + """ + try: + import win32clipboard + except ImportError as e: + raise TryNext("Getting text from the clipboard requires the pywin32 " + "extensions: http://sourceforge.net/projects/pywin32/") from e + win32clipboard.OpenClipboard() + try: + text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT) + except (TypeError, win32clipboard.error): + try: + text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT) + text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING) + except (TypeError, win32clipboard.error) as e: + raise ClipboardEmpty from e + finally: + win32clipboard.CloseClipboard() + return text + +def osx_clipboard_get() -> str: + """ Get the clipboard's text on OS X. + """ + p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'], + stdout=subprocess.PIPE) + bytes_, stderr = p.communicate() + # Text comes in with old Mac \r line endings. Change them to \n. + bytes_ = bytes_.replace(b'\r', b'\n') + text = py3compat.decode(bytes_) + return text + +def tkinter_clipboard_get(): + """ Get the clipboard's text using Tkinter. + + This is the default on systems that are not Windows or OS X. It may + interfere with other UI toolkits and should be replaced with an + implementation that uses that toolkit. + """ + try: + from tkinter import Tk, TclError + except ImportError as e: + raise TryNext("Getting text from the clipboard on this platform requires tkinter.") from e + + root = Tk() + root.withdraw() + try: + text = root.clipboard_get() + except TclError as e: + raise ClipboardEmpty from e + finally: + root.destroy() + text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING) + return text + + diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/deepreload.py b/.venv/lib/python3.8/site-packages/IPython/lib/deepreload.py new file mode 100644 index 0000000..aaedab2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/deepreload.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +""" +Provides a reload() function that acts recursively. + +Python's normal :func:`python:reload` function only reloads the module that it's +passed. The :func:`reload` function in this module also reloads everything +imported from that module, which is useful when you're changing files deep +inside a package. + +To use this as your default reload function, type this:: + + import builtins + from IPython.lib import deepreload + builtins.reload = deepreload.reload + +A reference to the original :func:`python:reload` is stored in this module as +:data:`original_reload`, so you can restore it later. + +This code is almost entirely based on knee.py, which is a Python +re-implementation of hierarchical module import. +""" +#***************************************************************************** +# Copyright (C) 2001 Nathaniel Gray +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#***************************************************************************** + +import builtins as builtin_mod +from contextlib import contextmanager +import importlib +import sys + +from types import ModuleType +from warnings import warn +import types + +original_import = builtin_mod.__import__ + +@contextmanager +def replace_import_hook(new_import): + saved_import = builtin_mod.__import__ + builtin_mod.__import__ = new_import + try: + yield + finally: + builtin_mod.__import__ = saved_import + +def get_parent(globals, level): + """ + parent, name = get_parent(globals, level) + + Return the package that an import is being performed in. If globals comes + from the module foo.bar.bat (not itself a package), this returns the + sys.modules entry for foo.bar. If globals is from a package's __init__.py, + the package's entry in sys.modules is returned. + + If globals doesn't come from a package or a module in a package, or a + corresponding entry is not found in sys.modules, None is returned. + """ + orig_level = level + + if not level or not isinstance(globals, dict): + return None, '' + + pkgname = globals.get('__package__', None) + + if pkgname is not None: + # __package__ is set, so use it + if not hasattr(pkgname, 'rindex'): + raise ValueError('__package__ set to non-string') + if len(pkgname) == 0: + if level > 0: + raise ValueError('Attempted relative import in non-package') + return None, '' + name = pkgname + else: + # __package__ not set, so figure it out and set it + if '__name__' not in globals: + return None, '' + modname = globals['__name__'] + + if '__path__' in globals: + # __path__ is set, so modname is already the package name + globals['__package__'] = name = modname + else: + # Normal module, so work out the package name if any + lastdot = modname.rfind('.') + if lastdot < 0 < level: + raise ValueError("Attempted relative import in non-package") + if lastdot < 0: + globals['__package__'] = None + return None, '' + globals['__package__'] = name = modname[:lastdot] + + dot = len(name) + for x in range(level, 1, -1): + try: + dot = name.rindex('.', 0, dot) + except ValueError as e: + raise ValueError("attempted relative import beyond top-level " + "package") from e + name = name[:dot] + + try: + parent = sys.modules[name] + except BaseException as e: + if orig_level < 1: + warn("Parent module '%.200s' not found while handling absolute " + "import" % name) + parent = None + else: + raise SystemError("Parent module '%.200s' not loaded, cannot " + "perform relative import" % name) from e + + # We expect, but can't guarantee, if parent != None, that: + # - parent.__name__ == name + # - parent.__dict__ is globals + # If this is violated... Who cares? + return parent, name + +def load_next(mod, altmod, name, buf): + """ + mod, name, buf = load_next(mod, altmod, name, buf) + + altmod is either None or same as mod + """ + + if len(name) == 0: + # completely empty module name should only happen in + # 'from . import' (or '__import__("")') + return mod, None, buf + + dot = name.find('.') + if dot == 0: + raise ValueError('Empty module name') + + if dot < 0: + subname = name + next = None + else: + subname = name[:dot] + next = name[dot+1:] + + if buf != '': + buf += '.' + buf += subname + + result = import_submodule(mod, subname, buf) + if result is None and mod != altmod: + result = import_submodule(altmod, subname, subname) + if result is not None: + buf = subname + + if result is None: + raise ImportError("No module named %.200s" % name) + + return result, next, buf + + +# Need to keep track of what we've already reloaded to prevent cyclic evil +found_now = {} + +def import_submodule(mod, subname, fullname): + """m = import_submodule(mod, subname, fullname)""" + # Require: + # if mod == None: subname == fullname + # else: mod.__name__ + "." + subname == fullname + + global found_now + if fullname in found_now and fullname in sys.modules: + m = sys.modules[fullname] + else: + print('Reloading', fullname) + found_now[fullname] = 1 + oldm = sys.modules.get(fullname, None) + try: + if oldm is not None: + m = importlib.reload(oldm) + else: + m = importlib.import_module(subname, mod) + except: + # load_module probably removed name from modules because of + # the error. Put back the original module object. + if oldm: + sys.modules[fullname] = oldm + raise + + add_submodule(mod, m, fullname, subname) + + return m + +def add_submodule(mod, submod, fullname, subname): + """mod.{subname} = submod""" + if mod is None: + return #Nothing to do here. + + if submod is None: + submod = sys.modules[fullname] + + setattr(mod, subname, submod) + + return + +def ensure_fromlist(mod, fromlist, buf, recursive): + """Handle 'from module import a, b, c' imports.""" + if not hasattr(mod, '__path__'): + return + for item in fromlist: + if not hasattr(item, 'rindex'): + raise TypeError("Item in ``from list'' not a string") + if item == '*': + if recursive: + continue # avoid endless recursion + try: + all = mod.__all__ + except AttributeError: + pass + else: + ret = ensure_fromlist(mod, all, buf, 1) + if not ret: + return 0 + elif not hasattr(mod, item): + import_submodule(mod, item, buf + '.' + item) + +def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1): + """Replacement for __import__()""" + parent, buf = get_parent(globals, level) + + head, name, buf = load_next(parent, None if level < 0 else parent, name, buf) + + tail = head + while name: + tail, name, buf = load_next(tail, tail, name, buf) + + # If tail is None, both get_parent and load_next found + # an empty module name: someone called __import__("") or + # doctored faulty bytecode + if tail is None: + raise ValueError('Empty module name') + + if not fromlist: + return head + + ensure_fromlist(tail, fromlist, buf, 0) + return tail + +modules_reloading = {} + +def deep_reload_hook(m): + """Replacement for reload().""" + # Hardcode this one as it would raise a NotImplementedError from the + # bowels of Python and screw up the import machinery after. + # unlike other imports the `exclude` list already in place is not enough. + + if m is types: + return m + if not isinstance(m, ModuleType): + raise TypeError("reload() argument must be module") + + name = m.__name__ + + if name not in sys.modules: + raise ImportError("reload(): module %.200s not in sys.modules" % name) + + global modules_reloading + try: + return modules_reloading[name] + except: + modules_reloading[name] = m + + try: + newm = importlib.reload(m) + except: + sys.modules[name] = m + raise + finally: + modules_reloading.clear() + return newm + +# Save the original hooks +original_reload = importlib.reload + +# Replacement for reload() +def reload( + module, + exclude=( + *sys.builtin_module_names, + "sys", + "os.path", + "builtins", + "__main__", + "numpy", + "numpy._globals", + ), +): + """Recursively reload all modules used in the given module. Optionally + takes a list of modules to exclude from reloading. The default exclude + list contains modules listed in sys.builtin_module_names with additional + sys, os.path, builtins and __main__, to prevent, e.g., resetting + display, exception, and io hooks. + """ + global found_now + for i in exclude: + found_now[i] = 1 + try: + with replace_import_hook(deep_import_hook): + return deep_reload_hook(module) + finally: + found_now = {} diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/demo.py b/.venv/lib/python3.8/site-packages/IPython/lib/demo.py new file mode 100644 index 0000000..8c9ae90 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/demo.py @@ -0,0 +1,672 @@ +"""Module for interactive demos using IPython. + +This module implements a few classes for running Python scripts interactively +in IPython for demonstrations. With very simple markup (a few tags in +comments), you can control points where the script stops executing and returns +control to IPython. + + +Provided classes +---------------- + +The classes are (see their docstrings for further details): + + - Demo: pure python demos + + - IPythonDemo: demos with input to be processed by IPython as if it had been + typed interactively (so magics work, as well as any other special syntax you + may have added via input prefilters). + + - LineDemo: single-line version of the Demo class. These demos are executed + one line at a time, and require no markup. + + - IPythonLineDemo: IPython version of the LineDemo class (the demo is + executed a line at a time, but processed via IPython). + + - ClearMixin: mixin to make Demo classes with less visual clutter. It + declares an empty marquee and a pre_cmd that clears the screen before each + block (see Subclassing below). + + - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo + classes. + +Inheritance diagram: + +.. inheritance-diagram:: IPython.lib.demo + :parts: 3 + +Subclassing +----------- + +The classes here all include a few methods meant to make customization by +subclassing more convenient. Their docstrings below have some more details: + + - highlight(): format every block and optionally highlight comments and + docstring content. + + - marquee(): generates a marquee to provide visible on-screen markers at each + block start and end. + + - pre_cmd(): run right before the execution of each block. + + - post_cmd(): run right after the execution of each block. If the block + raises an exception, this is NOT called. + + +Operation +--------- + +The file is run in its own empty namespace (though you can pass it a string of +arguments as if in a command line environment, and it will see those as +sys.argv). But at each stop, the global IPython namespace is updated with the +current internal demo namespace, so you can work interactively with the data +accumulated so far. + +By default, each block of code is printed (with syntax highlighting) before +executing it and you have to confirm execution. This is intended to show the +code to an audience first so you can discuss it, and only proceed with +execution once you agree. There are a few tags which allow you to modify this +behavior. + +The supported tags are: + +# stop + + Defines block boundaries, the points where IPython stops execution of the + file and returns to the interactive prompt. + + You can optionally mark the stop tag with extra dashes before and after the + word 'stop', to help visually distinguish the blocks in a text editor: + + # --- stop --- + + +# silent + + Make a block execute silently (and hence automatically). Typically used in + cases where you have some boilerplate or initialization code which you need + executed but do not want to be seen in the demo. + +# auto + + Make a block execute automatically, but still being printed. Useful for + simple code which does not warrant discussion, since it avoids the extra + manual confirmation. + +# auto_all + + This tag can _only_ be in the first block, and if given it overrides the + individual auto tags to make the whole demo fully automatic (no block asks + for confirmation). It can also be given at creation time (or the attribute + set later) to override what's in the file. + +While _any_ python file can be run as a Demo instance, if there are no stop +tags the whole file will run in a single block (no different that calling +first %pycat and then %run). The minimal markup to make this useful is to +place a set of stop tags; the other tags are only there to let you fine-tune +the execution. + +This is probably best explained with the simple example file below. You can +copy this into a file named ex_demo.py, and try running it via:: + + from IPython.lib.demo import Demo + d = Demo('ex_demo.py') + d() + +Each time you call the demo object, it runs the next block. The demo object +has a few useful methods for navigation, like again(), edit(), jump(), seek() +and back(). It can be reset for a new run via reset() or reloaded from disk +(in case you've edited the source) via reload(). See their docstrings below. + +Note: To make this simpler to explore, a file called "demo-exercizer.py" has +been added to the "docs/examples/core" directory. Just cd to this directory in +an IPython session, and type:: + + %run demo-exercizer.py + +and then follow the directions. + +Example +------- + +The following is a very simple example of a valid demo file. + +:: + + #################### EXAMPLE DEMO ############################### + '''A simple interactive demo to illustrate the use of IPython's Demo class.''' + + print 'Hello, welcome to an interactive IPython demo.' + + # The mark below defines a block boundary, which is a point where IPython will + # stop execution and return to the interactive prompt. The dashes are actually + # optional and used only as a visual aid to clearly separate blocks while + # editing the demo code. + # stop + + x = 1 + y = 2 + + # stop + + # the mark below makes this block as silent + # silent + + print 'This is a silent block, which gets executed but not printed.' + + # stop + # auto + print 'This is an automatic block.' + print 'It is executed without asking for confirmation, but printed.' + z = x+y + + print 'z=',x + + # stop + # This is just another normal block. + print 'z is now:', z + + print 'bye!' + ################### END EXAMPLE DEMO ############################ +""" + + +#***************************************************************************** +# Copyright (C) 2005-2006 Fernando Perez. +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# +#***************************************************************************** + +import os +import re +import shlex +import sys +import pygments +from pathlib import Path + +from IPython.utils.text import marquee +from IPython.utils import openpy +from IPython.utils import py3compat +__all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError'] + +class DemoError(Exception): pass + +def re_mark(mark): + return re.compile(r'^\s*#\s+\s+%s\s*$' % mark,re.MULTILINE) + +class Demo(object): + + re_stop = re_mark(r'-*\s?stop\s?-*') + re_silent = re_mark('silent') + re_auto = re_mark('auto') + re_auto_all = re_mark('auto_all') + + def __init__(self,src,title='',arg_str='',auto_all=None, format_rst=False, + formatter='terminal', style='default'): + """Make a new demo object. To run the demo, simply call the object. + + See the module docstring for full details and an example (you can use + IPython.Demo? in IPython to see it). + + Inputs: + + - src is either a file, or file-like object, or a + string that can be resolved to a filename. + + Optional inputs: + + - title: a string to use as the demo name. Of most use when the demo + you are making comes from an object that has no filename, or if you + want an alternate denotation distinct from the filename. + + - arg_str(''): a string of arguments, internally converted to a list + just like sys.argv, so the demo script can see a similar + environment. + + - auto_all(None): global flag to run all blocks automatically without + confirmation. This attribute overrides the block-level tags and + applies to the whole demo. It is an attribute of the object, and + can be changed at runtime simply by reassigning it to a boolean + value. + + - format_rst(False): a bool to enable comments and doc strings + formatting with pygments rst lexer + + - formatter('terminal'): a string of pygments formatter name to be + used. Useful values for terminals: terminal, terminal256, + terminal16m + + - style('default'): a string of pygments style name to be used. + """ + if hasattr(src, "read"): + # It seems to be a file or a file-like object + self.fname = "from a file-like object" + if title == '': + self.title = "from a file-like object" + else: + self.title = title + else: + # Assume it's a string or something that can be converted to one + self.fname = src + if title == '': + (filepath, filename) = os.path.split(src) + self.title = filename + else: + self.title = title + self.sys_argv = [src] + shlex.split(arg_str) + self.auto_all = auto_all + self.src = src + + try: + ip = get_ipython() # this is in builtins whenever IPython is running + self.inside_ipython = True + except NameError: + self.inside_ipython = False + + if self.inside_ipython: + # get a few things from ipython. While it's a bit ugly design-wise, + # it ensures that things like color scheme and the like are always in + # sync with the ipython mode being used. This class is only meant to + # be used inside ipython anyways, so it's OK. + self.ip_ns = ip.user_ns + self.ip_colorize = ip.pycolorize + self.ip_showtb = ip.showtraceback + self.ip_run_cell = ip.run_cell + self.shell = ip + + self.formatter = pygments.formatters.get_formatter_by_name(formatter, + style=style) + self.python_lexer = pygments.lexers.get_lexer_by_name("py3") + self.format_rst = format_rst + if format_rst: + self.rst_lexer = pygments.lexers.get_lexer_by_name("rst") + + # load user data and initialize data structures + self.reload() + + def fload(self): + """Load file object.""" + # read data and parse into blocks + if hasattr(self, 'fobj') and self.fobj is not None: + self.fobj.close() + if hasattr(self.src, "read"): + # It seems to be a file or a file-like object + self.fobj = self.src + else: + # Assume it's a string or something that can be converted to one + self.fobj = openpy.open(self.fname) + + def reload(self): + """Reload source from disk and initialize state.""" + self.fload() + + self.src = "".join(openpy.strip_encoding_cookie(self.fobj)) + src_b = [b.strip() for b in self.re_stop.split(self.src) if b] + self._silent = [bool(self.re_silent.findall(b)) for b in src_b] + self._auto = [bool(self.re_auto.findall(b)) for b in src_b] + + # if auto_all is not given (def. None), we read it from the file + if self.auto_all is None: + self.auto_all = bool(self.re_auto_all.findall(src_b[0])) + else: + self.auto_all = bool(self.auto_all) + + # Clean the sources from all markup so it doesn't get displayed when + # running the demo + src_blocks = [] + auto_strip = lambda s: self.re_auto.sub('',s) + for i,b in enumerate(src_b): + if self._auto[i]: + src_blocks.append(auto_strip(b)) + else: + src_blocks.append(b) + # remove the auto_all marker + src_blocks[0] = self.re_auto_all.sub('',src_blocks[0]) + + self.nblocks = len(src_blocks) + self.src_blocks = src_blocks + + # also build syntax-highlighted source + self.src_blocks_colored = list(map(self.highlight,self.src_blocks)) + + # ensure clean namespace and seek offset + self.reset() + + def reset(self): + """Reset the namespace and seek pointer to restart the demo""" + self.user_ns = {} + self.finished = False + self.block_index = 0 + + def _validate_index(self,index): + if index<0 or index>=self.nblocks: + raise ValueError('invalid block index %s' % index) + + def _get_index(self,index): + """Get the current block index, validating and checking status. + + Returns None if the demo is finished""" + + if index is None: + if self.finished: + print('Demo finished. Use .reset() if you want to rerun it.') + return None + index = self.block_index + else: + self._validate_index(index) + return index + + def seek(self,index): + """Move the current seek pointer to the given block. + + You can use negative indices to seek from the end, with identical + semantics to those of Python lists.""" + if index<0: + index = self.nblocks + index + self._validate_index(index) + self.block_index = index + self.finished = False + + def back(self,num=1): + """Move the seek pointer back num blocks (default is 1).""" + self.seek(self.block_index-num) + + def jump(self,num=1): + """Jump a given number of blocks relative to the current one. + + The offset can be positive or negative, defaults to 1.""" + self.seek(self.block_index+num) + + def again(self): + """Move the seek pointer back one block and re-execute.""" + self.back(1) + self() + + def edit(self,index=None): + """Edit a block. + + If no number is given, use the last block executed. + + This edits the in-memory copy of the demo, it does NOT modify the + original source file. If you want to do that, simply open the file in + an editor and use reload() when you make changes to the file. This + method is meant to let you change a block during a demonstration for + explanatory purposes, without damaging your original script.""" + + index = self._get_index(index) + if index is None: + return + # decrease the index by one (unless we're at the very beginning), so + # that the default demo.edit() call opens up the sblock we've last run + if index>0: + index -= 1 + + filename = self.shell.mktempfile(self.src_blocks[index]) + self.shell.hooks.editor(filename, 1) + with open(Path(filename), "r", encoding="utf-8") as f: + new_block = f.read() + # update the source and colored block + self.src_blocks[index] = new_block + self.src_blocks_colored[index] = self.highlight(new_block) + self.block_index = index + # call to run with the newly edited index + self() + + def show(self,index=None): + """Show a single block on screen""" + + index = self._get_index(index) + if index is None: + return + + print(self.marquee('<%s> block # %s (%s remaining)' % + (self.title,index,self.nblocks-index-1))) + print(self.src_blocks_colored[index]) + sys.stdout.flush() + + def show_all(self): + """Show entire demo on screen, block by block""" + + fname = self.title + title = self.title + nblocks = self.nblocks + silent = self._silent + marquee = self.marquee + for index,block in enumerate(self.src_blocks_colored): + if silent[index]: + print(marquee('<%s> SILENT block # %s (%s remaining)' % + (title,index,nblocks-index-1))) + else: + print(marquee('<%s> block # %s (%s remaining)' % + (title,index,nblocks-index-1))) + print(block, end=' ') + sys.stdout.flush() + + def run_cell(self,source): + """Execute a string with one or more lines of code""" + + exec(source, self.user_ns) + + def __call__(self,index=None): + """run a block of the demo. + + If index is given, it should be an integer >=1 and <= nblocks. This + means that the calling convention is one off from typical Python + lists. The reason for the inconsistency is that the demo always + prints 'Block n/N, and N is the total, so it would be very odd to use + zero-indexing here.""" + + index = self._get_index(index) + if index is None: + return + try: + marquee = self.marquee + next_block = self.src_blocks[index] + self.block_index += 1 + if self._silent[index]: + print(marquee('Executing silent block # %s (%s remaining)' % + (index,self.nblocks-index-1))) + else: + self.pre_cmd() + self.show(index) + if self.auto_all or self._auto[index]: + print(marquee('output:')) + else: + print(marquee('Press to quit, to execute...'), end=' ') + ans = py3compat.input().strip() + if ans: + print(marquee('Block NOT executed')) + return + try: + save_argv = sys.argv + sys.argv = self.sys_argv + self.run_cell(next_block) + self.post_cmd() + finally: + sys.argv = save_argv + + except: + if self.inside_ipython: + self.ip_showtb(filename=self.fname) + else: + if self.inside_ipython: + self.ip_ns.update(self.user_ns) + + if self.block_index == self.nblocks: + mq1 = self.marquee('END OF DEMO') + if mq1: + # avoid spurious print if empty marquees are used + print() + print(mq1) + print(self.marquee('Use .reset() if you want to rerun it.')) + self.finished = True + + # These methods are meant to be overridden by subclasses who may wish to + # customize the behavior of of their demos. + def marquee(self,txt='',width=78,mark='*'): + """Return the input string centered in a 'marquee'.""" + return marquee(txt,width,mark) + + def pre_cmd(self): + """Method called before executing each block.""" + pass + + def post_cmd(self): + """Method called after executing each block.""" + pass + + def highlight(self, block): + """Method called on each block to highlight it content""" + tokens = pygments.lex(block, self.python_lexer) + if self.format_rst: + from pygments.token import Token + toks = [] + for token in tokens: + if token[0] == Token.String.Doc and len(token[1]) > 6: + toks += pygments.lex(token[1][:3], self.python_lexer) + # parse doc string content by rst lexer + toks += pygments.lex(token[1][3:-3], self.rst_lexer) + toks += pygments.lex(token[1][-3:], self.python_lexer) + elif token[0] == Token.Comment.Single: + toks.append((Token.Comment.Single, token[1][0])) + # parse comment content by rst lexer + # remove the extra newline added by rst lexer + toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1] + else: + toks.append(token) + tokens = toks + return pygments.format(tokens, self.formatter) + + +class IPythonDemo(Demo): + """Class for interactive demos with IPython's input processing applied. + + This subclasses Demo, but instead of executing each block by the Python + interpreter (via exec), it actually calls IPython on it, so that any input + filters which may be in place are applied to the input block. + + If you have an interactive environment which exposes special input + processing, you can use this class instead to write demo scripts which + operate exactly as if you had typed them interactively. The default Demo + class requires the input to be valid, pure Python code. + """ + + def run_cell(self,source): + """Execute a string with one or more lines of code""" + + self.shell.run_cell(source) + +class LineDemo(Demo): + """Demo where each line is executed as a separate block. + + The input script should be valid Python code. + + This class doesn't require any markup at all, and it's meant for simple + scripts (with no nesting or any kind of indentation) which consist of + multiple lines of input to be executed, one at a time, as if they had been + typed in the interactive prompt. + + Note: the input can not have *any* indentation, which means that only + single-lines of input are accepted, not even function definitions are + valid.""" + + def reload(self): + """Reload source from disk and initialize state.""" + # read data and parse into blocks + self.fload() + lines = self.fobj.readlines() + src_b = [l for l in lines if l.strip()] + nblocks = len(src_b) + self.src = ''.join(lines) + self._silent = [False]*nblocks + self._auto = [True]*nblocks + self.auto_all = True + self.nblocks = nblocks + self.src_blocks = src_b + + # also build syntax-highlighted source + self.src_blocks_colored = list(map(self.highlight,self.src_blocks)) + + # ensure clean namespace and seek offset + self.reset() + + +class IPythonLineDemo(IPythonDemo,LineDemo): + """Variant of the LineDemo class whose input is processed by IPython.""" + pass + + +class ClearMixin(object): + """Use this mixin to make Demo classes with less visual clutter. + + Demos using this mixin will clear the screen before every block and use + blank marquees. + + Note that in order for the methods defined here to actually override those + of the classes it's mixed with, it must go /first/ in the inheritance + tree. For example: + + class ClearIPDemo(ClearMixin,IPythonDemo): pass + + will provide an IPythonDemo class with the mixin's features. + """ + + def marquee(self,txt='',width=78,mark='*'): + """Blank marquee that returns '' no matter what the input.""" + return '' + + def pre_cmd(self): + """Method called before executing each block. + + This one simply clears the screen.""" + from IPython.utils.terminal import _term_clear + _term_clear() + +class ClearDemo(ClearMixin,Demo): + pass + + +class ClearIPDemo(ClearMixin,IPythonDemo): + pass + + +def slide(file_path, noclear=False, format_rst=True, formatter="terminal", + style="native", auto_all=False, delimiter='...'): + if noclear: + demo_class = Demo + else: + demo_class = ClearDemo + demo = demo_class(file_path, format_rst=format_rst, formatter=formatter, + style=style, auto_all=auto_all) + while not demo.finished: + demo() + try: + py3compat.input('\n' + delimiter) + except KeyboardInterrupt: + exit(1) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Run python demos') + parser.add_argument('--noclear', '-C', action='store_true', + help='Do not clear terminal on each slide') + parser.add_argument('--rst', '-r', action='store_true', + help='Highlight comments and dostrings as rst') + parser.add_argument('--formatter', '-f', default='terminal', + help='pygments formatter name could be: terminal, ' + 'terminal256, terminal16m') + parser.add_argument('--style', '-s', default='default', + help='pygments style name') + parser.add_argument('--auto', '-a', action='store_true', + help='Run all blocks automatically without' + 'confirmation') + parser.add_argument('--delimiter', '-d', default='...', + help='slides delimiter added after each slide run') + parser.add_argument('file', nargs=1, + help='python demo file') + args = parser.parse_args() + slide(args.file[0], noclear=args.noclear, format_rst=args.rst, + formatter=args.formatter, style=args.style, auto_all=args.auto, + delimiter=args.delimiter) diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/display.py b/.venv/lib/python3.8/site-packages/IPython/lib/display.py new file mode 100644 index 0000000..52060da --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/display.py @@ -0,0 +1,677 @@ +"""Various display related classes. + +Authors : MinRK, gregcaporaso, dannystaple +""" +from html import escape as html_escape +from os.path import exists, isfile, splitext, abspath, join, isdir +from os import walk, sep, fsdecode + +from IPython.core.display import DisplayObject, TextDisplayObject + +from typing import Tuple, Iterable + +__all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument', + 'FileLink', 'FileLinks', 'Code'] + + +class Audio(DisplayObject): + """Create an audio object. + + When this object is returned by an input cell or passed to the + display function, it will result in Audio controls being displayed + in the frontend (only works in the notebook). + + Parameters + ---------- + data : numpy array, list, unicode, str or bytes + Can be one of + + * Numpy 1d array containing the desired waveform (mono) + * Numpy 2d array containing waveforms for each channel. + Shape=(NCHAN, NSAMPLES). For the standard channel order, see + http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx + * List of float or integer representing the waveform (mono) + * String containing the filename + * Bytestring containing raw PCM data or + * URL pointing to a file on the web. + + If the array option is used, the waveform will be normalized. + + If a filename or url is used, the format support will be browser + dependent. + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + embed : boolean + Should the audio data be embedded using a data URI (True) or should + the original source be referenced. Set this to True if you want the + audio to playable later with no internet connection in the notebook. + + Default is `True`, unless the keyword argument `url` is set, then + default value is `False`. + rate : integer + The sampling rate of the raw data. + Only required when data parameter is being used as an array + autoplay : bool + Set to True if the audio should immediately start playing. + Default is `False`. + normalize : bool + Whether audio should be normalized (rescaled) to the maximum possible + range. Default is `True`. When set to `False`, `data` must be between + -1 and 1 (inclusive), otherwise an error is raised. + Applies only when `data` is a list or array of samples; other types of + audio are never normalized. + + Examples + -------- + + >>> import pytest + >>> np = pytest.importorskip("numpy") + + Generate a sound + + >>> import numpy as np + >>> framerate = 44100 + >>> t = np.linspace(0,5,framerate*5) + >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t) + >>> Audio(data, rate=framerate) + + + Can also do stereo or more channels + + >>> dataleft = np.sin(2*np.pi*220*t) + >>> dataright = np.sin(2*np.pi*224*t) + >>> Audio([dataleft, dataright], rate=framerate) + + + From URL: + + >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP + >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP + + From a File: + + >>> Audio('/path/to/sound.wav') # doctest: +SKIP + >>> Audio(filename='/path/to/sound.ogg') # doctest: +SKIP + + From Bytes: + + >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP + >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP + + See Also + -------- + ipywidgets.Audio + + AUdio widget with more more flexibility and options. + + """ + _read_flags = 'rb' + + def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *, + element_id=None): + if filename is None and url is None and data is None: + raise ValueError("No audio data found. Expecting filename, url, or data.") + if embed is False and url is None: + raise ValueError("No url found. Expecting url when embed=False") + + if url is not None and embed is not True: + self.embed = False + else: + self.embed = True + self.autoplay = autoplay + self.element_id = element_id + super(Audio, self).__init__(data=data, url=url, filename=filename) + + if self.data is not None and not isinstance(self.data, bytes): + if rate is None: + raise ValueError("rate must be specified when data is a numpy array or list of audio samples.") + self.data = Audio._make_wav(data, rate, normalize) + + def reload(self): + """Reload the raw data from file or URL.""" + import mimetypes + if self.embed: + super(Audio, self).reload() + + if self.filename is not None: + self.mimetype = mimetypes.guess_type(self.filename)[0] + elif self.url is not None: + self.mimetype = mimetypes.guess_type(self.url)[0] + else: + self.mimetype = "audio/wav" + + @staticmethod + def _make_wav(data, rate, normalize): + """ Transform a numpy array to a PCM bytestring """ + from io import BytesIO + import wave + + try: + scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize) + except ImportError: + scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize) + + fp = BytesIO() + waveobj = wave.open(fp,mode='wb') + waveobj.setnchannels(nchan) + waveobj.setframerate(rate) + waveobj.setsampwidth(2) + waveobj.setcomptype('NONE','NONE') + waveobj.writeframes(scaled) + val = fp.getvalue() + waveobj.close() + + return val + + @staticmethod + def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]: + import numpy as np + + data = np.array(data, dtype=float) + if len(data.shape) == 1: + nchan = 1 + elif len(data.shape) == 2: + # In wave files,channels are interleaved. E.g., + # "L1R1L2R2..." for stereo. See + # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx + # for channel ordering + nchan = data.shape[0] + data = data.T.ravel() + else: + raise ValueError('Array audio input must be a 1D or 2D array') + + max_abs_value = np.max(np.abs(data)) + normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) + scaled = data / normalization_factor * 32767 + return scaled.astype(" 1: + raise ValueError('Audio data must be between -1 and 1 when normalize=False.') + return max_abs_value if normalize else 1 + + def _data_and_metadata(self): + """shortcut for returning metadata with url information, if defined""" + md = {} + if self.url: + md['url'] = self.url + if md: + return self.data, md + else: + return self.data + + def _repr_html_(self): + src = """ + + """ + return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(), + element_id=self.element_id_attr()) + + def src_attr(self): + import base64 + if self.embed and (self.data is not None): + data = base64=base64.b64encode(self.data).decode('ascii') + return """data:{type};base64,{base64}""".format(type=self.mimetype, + base64=data) + elif self.url is not None: + return self.url + else: + return "" + + def autoplay_attr(self): + if(self.autoplay): + return 'autoplay="autoplay"' + else: + return '' + + def element_id_attr(self): + if (self.element_id): + return 'id="{element_id}"'.format(element_id=self.element_id) + else: + return '' + +class IFrame(object): + """ + Generic class to embed an iframe in an IPython notebook + """ + + iframe = """ + + """ + + def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs): + if extras is None: + extras = [] + + self.src = src + self.width = width + self.height = height + self.extras = extras + self.params = kwargs + + def _repr_html_(self): + """return the embed iframe""" + if self.params: + from urllib.parse import urlencode + params = "?" + urlencode(self.params) + else: + params = "" + return self.iframe.format( + src=self.src, + width=self.width, + height=self.height, + params=params, + extras=" ".join(self.extras), + ) + + +class YouTubeVideo(IFrame): + """Class for embedding a YouTube Video in an IPython session, based on its video id. + + e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would + do:: + + vid = YouTubeVideo("foo") + display(vid) + + To start from 30 seconds:: + + vid = YouTubeVideo("abc", start=30) + display(vid) + + To calculate seconds from time as hours, minutes, seconds use + :class:`datetime.timedelta`:: + + start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds()) + + Other parameters can be provided as documented at + https://developers.google.com/youtube/player_parameters#Parameters + + When converting the notebook using nbconvert, a jpeg representation of the video + will be inserted in the document. + """ + + def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): + self.id=id + src = "https://www.youtube.com/embed/{0}".format(id) + if allow_autoplay: + extras = list(kwargs.get("extras", [])) + ['allow="autoplay"'] + kwargs.update(autoplay=1, extras=extras) + super(YouTubeVideo, self).__init__(src, width, height, **kwargs) + + def _repr_jpeg_(self): + # Deferred import + from urllib.request import urlopen + + try: + return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read() + except IOError: + return None + +class VimeoVideo(IFrame): + """ + Class for embedding a Vimeo video in an IPython session, based on its video id. + """ + + def __init__(self, id, width=400, height=300, **kwargs): + src="https://player.vimeo.com/video/{0}".format(id) + super(VimeoVideo, self).__init__(src, width, height, **kwargs) + +class ScribdDocument(IFrame): + """ + Class for embedding a Scribd document in an IPython session + + Use the start_page params to specify a starting point in the document + Use the view_mode params to specify display type one off scroll | slideshow | book + + e.g to Display Wes' foundational paper about PANDAS in book mode from page 3 + + ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book") + """ + + def __init__(self, id, width=400, height=300, **kwargs): + src="https://www.scribd.com/embeds/{0}/content".format(id) + super(ScribdDocument, self).__init__(src, width, height, **kwargs) + +class FileLink(object): + """Class for embedding a local file link in an IPython session, based on path + + e.g. to embed a link that was generated in the IPython notebook as my/data.txt + + you would do:: + + local_file = FileLink("my/data.txt") + display(local_file) + + or in the HTML notebook, just:: + + FileLink("my/data.txt") + """ + + html_link_str = "%s" + + def __init__(self, + path, + url_prefix='', + result_html_prefix='', + result_html_suffix='
'): + """ + Parameters + ---------- + path : str + path to the file or directory that should be formatted + url_prefix : str + prefix to be prepended to all files to form a working link [default: + ''] + result_html_prefix : str + text to append to beginning to link [default: ''] + result_html_suffix : str + text to append at the end of link [default: '
'] + """ + if isdir(path): + raise ValueError("Cannot display a directory using FileLink. " + "Use FileLinks to display '%s'." % path) + self.path = fsdecode(path) + self.url_prefix = url_prefix + self.result_html_prefix = result_html_prefix + self.result_html_suffix = result_html_suffix + + def _format_path(self): + fp = ''.join([self.url_prefix, html_escape(self.path)]) + return ''.join([self.result_html_prefix, + self.html_link_str % \ + (fp, html_escape(self.path, quote=False)), + self.result_html_suffix]) + + def _repr_html_(self): + """return html link to file + """ + if not exists(self.path): + return ("Path (%s) doesn't exist. " + "It may still be in the process of " + "being generated, or you may have the " + "incorrect path." % self.path) + + return self._format_path() + + def __repr__(self): + """return absolute path to file + """ + return abspath(self.path) + +class FileLinks(FileLink): + """Class for embedding local file links in an IPython session, based on path + + e.g. to embed links to files that were generated in the IPython notebook + under ``my/data``, you would do:: + + local_files = FileLinks("my/data") + display(local_files) + + or in the HTML notebook, just:: + + FileLinks("my/data") + """ + def __init__(self, + path, + url_prefix='', + included_suffixes=None, + result_html_prefix='', + result_html_suffix='
', + notebook_display_formatter=None, + terminal_display_formatter=None, + recursive=True): + """ + See :class:`FileLink` for the ``path``, ``url_prefix``, + ``result_html_prefix`` and ``result_html_suffix`` parameters. + + included_suffixes : list + Filename suffixes to include when formatting output [default: include + all files] + + notebook_display_formatter : function + Used to format links for display in the notebook. See discussion of + formatter functions below. + + terminal_display_formatter : function + Used to format links for display in the terminal. See discussion of + formatter functions below. + + Formatter functions must be of the form:: + + f(dirname, fnames, included_suffixes) + + dirname : str + The name of a directory + fnames : list + The files in that directory + included_suffixes : list + The file suffixes that should be included in the output (passing None + meansto include all suffixes in the output in the built-in formatters) + recursive : boolean + Whether to recurse into subdirectories. Default is True. + + The function should return a list of lines that will be printed in the + notebook (if passing notebook_display_formatter) or the terminal (if + passing terminal_display_formatter). This function is iterated over for + each directory in self.path. Default formatters are in place, can be + passed here to support alternative formatting. + + """ + if isfile(path): + raise ValueError("Cannot display a file using FileLinks. " + "Use FileLink to display '%s'." % path) + self.included_suffixes = included_suffixes + # remove trailing slashes for more consistent output formatting + path = path.rstrip('/') + + self.path = path + self.url_prefix = url_prefix + self.result_html_prefix = result_html_prefix + self.result_html_suffix = result_html_suffix + + self.notebook_display_formatter = \ + notebook_display_formatter or self._get_notebook_display_formatter() + self.terminal_display_formatter = \ + terminal_display_formatter or self._get_terminal_display_formatter() + + self.recursive = recursive + + def _get_display_formatter(self, + dirname_output_format, + fname_output_format, + fp_format, + fp_cleaner=None): + """ generate built-in formatter function + + this is used to define both the notebook and terminal built-in + formatters as they only differ by some wrapper text for each entry + + dirname_output_format: string to use for formatting directory + names, dirname will be substituted for a single "%s" which + must appear in this string + fname_output_format: string to use for formatting file names, + if a single "%s" appears in the string, fname will be substituted + if two "%s" appear in the string, the path to fname will be + substituted for the first and fname will be substituted for the + second + fp_format: string to use for formatting filepaths, must contain + exactly two "%s" and the dirname will be substituted for the first + and fname will be substituted for the second + """ + def f(dirname, fnames, included_suffixes=None): + result = [] + # begin by figuring out which filenames, if any, + # are going to be displayed + display_fnames = [] + for fname in fnames: + if (isfile(join(dirname,fname)) and + (included_suffixes is None or + splitext(fname)[1] in included_suffixes)): + display_fnames.append(fname) + + if len(display_fnames) == 0: + # if there are no filenames to display, don't print anything + # (not even the directory name) + pass + else: + # otherwise print the formatted directory name followed by + # the formatted filenames + dirname_output_line = dirname_output_format % dirname + result.append(dirname_output_line) + for fname in display_fnames: + fp = fp_format % (dirname,fname) + if fp_cleaner is not None: + fp = fp_cleaner(fp) + try: + # output can include both a filepath and a filename... + fname_output_line = fname_output_format % (fp, fname) + except TypeError: + # ... or just a single filepath + fname_output_line = fname_output_format % fname + result.append(fname_output_line) + return result + return f + + def _get_notebook_display_formatter(self, + spacer="  "): + """ generate function to use for notebook formatting + """ + dirname_output_format = \ + self.result_html_prefix + "%s/" + self.result_html_suffix + fname_output_format = \ + self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix + fp_format = self.url_prefix + '%s/%s' + if sep == "\\": + # Working on a platform where the path separator is "\", so + # must convert these to "/" for generating a URI + def fp_cleaner(fp): + # Replace all occurrences of backslash ("\") with a forward + # slash ("/") - this is necessary on windows when a path is + # provided as input, but we must link to a URI + return fp.replace('\\','/') + else: + fp_cleaner = None + + return self._get_display_formatter(dirname_output_format, + fname_output_format, + fp_format, + fp_cleaner) + + def _get_terminal_display_formatter(self, + spacer=" "): + """ generate function to use for terminal formatting + """ + dirname_output_format = "%s/" + fname_output_format = spacer + "%s" + fp_format = '%s/%s' + + return self._get_display_formatter(dirname_output_format, + fname_output_format, + fp_format) + + def _format_path(self): + result_lines = [] + if self.recursive: + walked_dir = list(walk(self.path)) + else: + walked_dir = [next(walk(self.path))] + walked_dir.sort() + for dirname, subdirs, fnames in walked_dir: + result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes) + return '\n'.join(result_lines) + + def __repr__(self): + """return newline-separated absolute paths + """ + result_lines = [] + if self.recursive: + walked_dir = list(walk(self.path)) + else: + walked_dir = [next(walk(self.path))] + walked_dir.sort() + for dirname, subdirs, fnames in walked_dir: + result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes) + return '\n'.join(result_lines) + + +class Code(TextDisplayObject): + """Display syntax-highlighted source code. + + This uses Pygments to highlight the code for HTML and Latex output. + + Parameters + ---------- + data : str + The code as a string + url : str + A URL to fetch the code from + filename : str + A local filename to load the code from + language : str + The short name of a Pygments lexer to use for highlighting. + If not specified, it will guess the lexer based on the filename + or the code. Available lexers: http://pygments.org/docs/lexers/ + """ + def __init__(self, data=None, url=None, filename=None, language=None): + self.language = language + super().__init__(data=data, url=url, filename=filename) + + def _get_lexer(self): + if self.language: + from pygments.lexers import get_lexer_by_name + return get_lexer_by_name(self.language) + elif self.filename: + from pygments.lexers import get_lexer_for_filename + return get_lexer_for_filename(self.filename) + else: + from pygments.lexers import guess_lexer + return guess_lexer(self.data) + + def __repr__(self): + return self.data + + def _repr_html_(self): + from pygments import highlight + from pygments.formatters import HtmlFormatter + fmt = HtmlFormatter() + style = ''.format(fmt.get_style_defs('.output_html')) + return style + highlight(self.data, self._get_lexer(), fmt) + + def _repr_latex_(self): + from pygments import highlight + from pygments.formatters import LatexFormatter + return highlight(self.data, self._get_lexer(), LatexFormatter()) diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/editorhooks.py b/.venv/lib/python3.8/site-packages/IPython/lib/editorhooks.py new file mode 100644 index 0000000..d8bd6ac --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/editorhooks.py @@ -0,0 +1,127 @@ +""" 'editor' hooks for common editors that work well with ipython + +They should honor the line number argument, at least. + +Contributions are *very* welcome. +""" + +import os +import shlex +import subprocess +import sys + +from IPython import get_ipython +from IPython.core.error import TryNext +from IPython.utils import py3compat + + +def install_editor(template, wait=False): + """Installs the editor that is called by IPython for the %edit magic. + + This overrides the default editor, which is generally set by your EDITOR + environment variable or is notepad (windows) or vi (linux). By supplying a + template string `run_template`, you can control how the editor is invoked + by IPython -- (e.g. the format in which it accepts command line options) + + Parameters + ---------- + template : basestring + run_template acts as a template for how your editor is invoked by + the shell. It should contain '{filename}', which will be replaced on + invocation with the file name, and '{line}', $line by line number + (or 0) to invoke the file with. + wait : bool + If `wait` is true, wait until the user presses enter before returning, + to facilitate non-blocking editors that exit immediately after + the call. + """ + + # not all editors support $line, so we'll leave out this check + # for substitution in ['$file', '$line']: + # if not substitution in run_template: + # raise ValueError(('run_template should contain %s' + # ' for string substitution. You supplied "%s"' % (substitution, + # run_template))) + + def call_editor(self, filename, line=0): + if line is None: + line = 0 + cmd = template.format(filename=shlex.quote(filename), line=line) + print(">", cmd) + # shlex.quote doesn't work right on Windows, but it does after splitting + if sys.platform.startswith('win'): + cmd = shlex.split(cmd) + proc = subprocess.Popen(cmd, shell=True) + if proc.wait() != 0: + raise TryNext() + if wait: + py3compat.input("Press Enter when done editing:") + + get_ipython().set_hook('editor', call_editor) + get_ipython().editor = template + + +# in these, exe is always the path/name of the executable. Useful +# if you don't have the editor directory in your path +def komodo(exe=u'komodo'): + """ Activestate Komodo [Edit] """ + install_editor(exe + u' -l {line} {filename}', wait=True) + + +def scite(exe=u"scite"): + """ SciTE or Sc1 """ + install_editor(exe + u' {filename} -goto:{line}') + + +def notepadplusplus(exe=u'notepad++'): + """ Notepad++ http://notepad-plus.sourceforge.net """ + install_editor(exe + u' -n{line} {filename}') + + +def jed(exe=u'jed'): + """ JED, the lightweight emacsish editor """ + install_editor(exe + u' +{line} {filename}') + + +def idle(exe=u'idle'): + """ Idle, the editor bundled with python + + Parameters + ---------- + exe : str, None + If none, should be pretty smart about finding the executable. + """ + if exe is None: + import idlelib + p = os.path.dirname(idlelib.__filename__) + # i'm not sure if this actually works. Is this idle.py script + # guaranteed to be executable? + exe = os.path.join(p, 'idle.py') + install_editor(exe + u' {filename}') + + +def mate(exe=u'mate'): + """ TextMate, the missing editor""" + # wait=True is not required since we're using the -w flag to mate + install_editor(exe + u' -w -l {line} {filename}') + + +# ########################################## +# these are untested, report any problems +# ########################################## + + +def emacs(exe=u'emacs'): + install_editor(exe + u' +{line} {filename}') + + +def gnuclient(exe=u'gnuclient'): + install_editor(exe + u' -nw +{line} {filename}') + + +def crimson_editor(exe=u'cedt.exe'): + install_editor(exe + u' /L:{line} {filename}') + + +def kate(exe=u'kate'): + install_editor(exe + u' -u -l {line} {filename}') diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/guisupport.py b/.venv/lib/python3.8/site-packages/IPython/lib/guisupport.py new file mode 100644 index 0000000..cfd325e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/guisupport.py @@ -0,0 +1,155 @@ +# coding: utf-8 +""" +Support for creating GUI apps and starting event loops. + +IPython's GUI integration allows interactive plotting and GUI usage in IPython +session. IPython has two different types of GUI integration: + +1. The terminal based IPython supports GUI event loops through Python's + PyOS_InputHook. PyOS_InputHook is a hook that Python calls periodically + whenever raw_input is waiting for a user to type code. We implement GUI + support in the terminal by setting PyOS_InputHook to a function that + iterates the event loop for a short while. It is important to note that + in this situation, the real GUI event loop is NOT run in the normal + manner, so you can't use the normal means to detect that it is running. +2. In the two process IPython kernel/frontend, the GUI event loop is run in + the kernel. In this case, the event loop is run in the normal manner by + calling the function or method of the GUI toolkit that starts the event + loop. + +In addition to starting the GUI event loops in one of these two ways, IPython +will *always* create an appropriate GUI application object when GUi +integration is enabled. + +If you want your GUI apps to run in IPython you need to do two things: + +1. Test to see if there is already an existing main application object. If + there is, you should use it. If there is not an existing application object + you should create one. +2. Test to see if the GUI event loop is running. If it is, you should not + start it. If the event loop is not running you may start it. + +This module contains functions for each toolkit that perform these things +in a consistent manner. Because of how PyOS_InputHook runs the event loop +you cannot detect if the event loop is running using the traditional calls +(such as ``wx.GetApp.IsMainLoopRunning()`` in wxPython). If PyOS_InputHook is +set These methods will return a false negative. That is, they will say the +event loop is not running, when is actually is. To work around this limitation +we proposed the following informal protocol: + +* Whenever someone starts the event loop, they *must* set the ``_in_event_loop`` + attribute of the main application object to ``True``. This should be done + regardless of how the event loop is actually run. +* Whenever someone stops the event loop, they *must* set the ``_in_event_loop`` + attribute of the main application object to ``False``. +* If you want to see if the event loop is running, you *must* use ``hasattr`` + to see if ``_in_event_loop`` attribute has been set. If it is set, you + *must* use its value. If it has not been set, you can query the toolkit + in the normal manner. +* If you want GUI support and no one else has created an application or + started the event loop you *must* do this. We don't want projects to + attempt to defer these things to someone else if they themselves need it. + +The functions below implement this logic for each GUI toolkit. If you need +to create custom application subclasses, you will likely have to modify this +code for your own purposes. This code can be copied into your own project +so you don't have to depend on IPython. + +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from IPython.core.getipython import get_ipython + +#----------------------------------------------------------------------------- +# wx +#----------------------------------------------------------------------------- + +def get_app_wx(*args, **kwargs): + """Create a new wx app or return an exiting one.""" + import wx + app = wx.GetApp() + if app is None: + if 'redirect' not in kwargs: + kwargs['redirect'] = False + app = wx.PySimpleApp(*args, **kwargs) + return app + +def is_event_loop_running_wx(app=None): + """Is the wx event loop running.""" + # New way: check attribute on shell instance + ip = get_ipython() + if ip is not None: + if ip.active_eventloop and ip.active_eventloop == 'wx': + return True + # Fall through to checking the application, because Wx has a native way + # to check if the event loop is running, unlike Qt. + + # Old way: check Wx application + if app is None: + app = get_app_wx() + if hasattr(app, '_in_event_loop'): + return app._in_event_loop + else: + return app.IsMainLoopRunning() + +def start_event_loop_wx(app=None): + """Start the wx event loop in a consistent manner.""" + if app is None: + app = get_app_wx() + if not is_event_loop_running_wx(app): + app._in_event_loop = True + app.MainLoop() + app._in_event_loop = False + else: + app._in_event_loop = True + +#----------------------------------------------------------------------------- +# qt4 +#----------------------------------------------------------------------------- + +def get_app_qt4(*args, **kwargs): + """Create a new qt4 app or return an existing one.""" + from IPython.external.qt_for_kernel import QtGui + app = QtGui.QApplication.instance() + if app is None: + if not args: + args = ([''],) + app = QtGui.QApplication(*args, **kwargs) + return app + +def is_event_loop_running_qt4(app=None): + """Is the qt4 event loop running.""" + # New way: check attribute on shell instance + ip = get_ipython() + if ip is not None: + return ip.active_eventloop and ip.active_eventloop.startswith('qt') + + # Old way: check attribute on QApplication singleton + if app is None: + app = get_app_qt4(['']) + if hasattr(app, '_in_event_loop'): + return app._in_event_loop + else: + # Does qt4 provide a other way to detect this? + return False + +def start_event_loop_qt4(app=None): + """Start the qt4 event loop in a consistent manner.""" + if app is None: + app = get_app_qt4(['']) + if not is_event_loop_running_qt4(app): + app._in_event_loop = True + app.exec_() + app._in_event_loop = False + else: + app._in_event_loop = True + +#----------------------------------------------------------------------------- +# Tk +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# gtk +#----------------------------------------------------------------------------- diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/latextools.py b/.venv/lib/python3.8/site-packages/IPython/lib/latextools.py new file mode 100644 index 0000000..27aeef5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/latextools.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +"""Tools for handling LaTeX.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from io import BytesIO, open +import os +import tempfile +import shutil +import subprocess +from base64 import encodebytes +import textwrap + +from pathlib import Path, PurePath + +from IPython.utils.process import find_cmd, FindCmdError +from traitlets.config import get_config +from traitlets.config.configurable import SingletonConfigurable +from traitlets import List, Bool, Unicode +from IPython.utils.py3compat import cast_unicode + + +class LaTeXTool(SingletonConfigurable): + """An object to store configuration of the LaTeX tool.""" + def _config_default(self): + return get_config() + + backends = List( + Unicode(), ["matplotlib", "dvipng"], + help="Preferred backend to draw LaTeX math equations. " + "Backends in the list are checked one by one and the first " + "usable one is used. Note that `matplotlib` backend " + "is usable only for inline style equations. To draw " + "display style equations, `dvipng` backend must be specified. ", + # It is a List instead of Enum, to make configuration more + # flexible. For example, to use matplotlib mainly but dvipng + # for display style, the default ["matplotlib", "dvipng"] can + # be used. To NOT use dvipng so that other repr such as + # unicode pretty printing is used, you can use ["matplotlib"]. + ).tag(config=True) + + use_breqn = Bool( + True, + help="Use breqn.sty to automatically break long equations. " + "This configuration takes effect only for dvipng backend.", + ).tag(config=True) + + packages = List( + ['amsmath', 'amsthm', 'amssymb', 'bm'], + help="A list of packages to use for dvipng backend. " + "'breqn' will be automatically appended when use_breqn=True.", + ).tag(config=True) + + preamble = Unicode( + help="Additional preamble to use when generating LaTeX source " + "for dvipng backend.", + ).tag(config=True) + + +def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black', + scale=1.0): + """Render a LaTeX string to PNG. + + Parameters + ---------- + s : str + The raw string containing valid inline LaTeX. + encode : bool, optional + Should the PNG data base64 encoded to make it JSON'able. + backend : {matplotlib, dvipng} + Backend for producing PNG data. + wrap : bool + If true, Automatically wrap `s` as a LaTeX equation. + color : string + Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB + format, e.g. '#AA20FA'. + scale : float + Scale factor for the resulting PNG. + None is returned when the backend cannot be used. + + """ + s = cast_unicode(s) + allowed_backends = LaTeXTool.instance().backends + if backend is None: + backend = allowed_backends[0] + if backend not in allowed_backends: + return None + if backend == 'matplotlib': + f = latex_to_png_mpl + elif backend == 'dvipng': + f = latex_to_png_dvipng + if color.startswith('#'): + # Convert hex RGB color to LaTeX RGB color. + if len(color) == 7: + try: + color = "RGB {}".format(" ".join([str(int(x, 16)) for x in + textwrap.wrap(color[1:], 2)])) + except ValueError as e: + raise ValueError('Invalid color specification {}.'.format(color)) from e + else: + raise ValueError('Invalid color specification {}.'.format(color)) + else: + raise ValueError('No such backend {0}'.format(backend)) + bin_data = f(s, wrap, color, scale) + if encode and bin_data: + bin_data = encodebytes(bin_data) + return bin_data + + +def latex_to_png_mpl(s, wrap, color='Black', scale=1.0): + try: + from matplotlib import figure, font_manager, mathtext + from matplotlib.backends import backend_agg + from pyparsing import ParseFatalException + except ImportError: + return None + + # mpl mathtext doesn't support display math, force inline + s = s.replace('$$', '$') + if wrap: + s = u'${0}$'.format(s) + + try: + prop = font_manager.FontProperties(size=12) + dpi = 120 * scale + buffer = BytesIO() + + # Adapted from mathtext.math_to_image + parser = mathtext.MathTextParser("path") + width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop) + fig = figure.Figure(figsize=(width / 72, height / 72)) + fig.text(0, depth / height, s, fontproperties=prop, color=color) + backend_agg.FigureCanvasAgg(fig) + fig.savefig(buffer, dpi=dpi, format="png", transparent=True) + return buffer.getvalue() + except (ValueError, RuntimeError, ParseFatalException): + return None + + +def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0): + try: + find_cmd('latex') + find_cmd('dvipng') + except FindCmdError: + return None + try: + workdir = Path(tempfile.mkdtemp()) + tmpfile = workdir.joinpath("tmp.tex") + dvifile = workdir.joinpath("tmp.dvi") + outfile = workdir.joinpath("tmp.png") + + with tmpfile.open("w", encoding="utf8") as f: + f.writelines(genelatex(s, wrap)) + + with open(os.devnull, 'wb') as devnull: + subprocess.check_call( + ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile], + cwd=workdir, stdout=devnull, stderr=devnull) + + resolution = round(150*scale) + subprocess.check_call( + [ + "dvipng", + "-T", + "tight", + "-D", + str(resolution), + "-z", + "9", + "-bg", + "Transparent", + "-o", + outfile, + dvifile, + "-fg", + color, + ], + cwd=workdir, + stdout=devnull, + stderr=devnull, + ) + + with outfile.open("rb") as f: + return f.read() + except subprocess.CalledProcessError: + return None + finally: + shutil.rmtree(workdir) + + +def kpsewhich(filename): + """Invoke kpsewhich command with an argument `filename`.""" + try: + find_cmd("kpsewhich") + proc = subprocess.Popen( + ["kpsewhich", filename], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + return stdout.strip().decode('utf8', 'replace') + except FindCmdError: + pass + + +def genelatex(body, wrap): + """Generate LaTeX document for dvipng backend.""" + lt = LaTeXTool.instance() + breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty") + yield r'\documentclass{article}' + packages = lt.packages + if breqn: + packages = packages + ['breqn'] + for pack in packages: + yield r'\usepackage{{{0}}}'.format(pack) + yield r'\pagestyle{empty}' + if lt.preamble: + yield lt.preamble + yield r'\begin{document}' + if breqn: + yield r'\begin{dmath*}' + yield body + yield r'\end{dmath*}' + elif wrap: + yield u'$${0}$$'.format(body) + else: + yield body + yield u'\\end{document}' + + +_data_uri_template_png = u"""%s""" + +def latex_to_html(s, alt='image'): + """Render LaTeX to HTML with embedded PNG data using data URIs. + + Parameters + ---------- + s : str + The raw string containing valid inline LateX. + alt : str + The alt text to use for the HTML. + """ + base64_data = latex_to_png(s, encode=True).decode('ascii') + if base64_data: + return _data_uri_template_png % (base64_data, alt) + + diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/lexers.py b/.venv/lib/python3.8/site-packages/IPython/lib/lexers.py new file mode 100644 index 0000000..0c9b6e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/lexers.py @@ -0,0 +1,526 @@ +# -*- coding: utf-8 -*- +""" +Defines a variety of Pygments lexers for highlighting IPython code. + +This includes: + + IPythonLexer, IPython3Lexer + Lexers for pure IPython (python + magic/shell commands) + + IPythonPartialTracebackLexer, IPythonTracebackLexer + Supports 2.x and 3.x via keyword `python3`. The partial traceback + lexer reads everything but the Python code appearing in a traceback. + The full lexer combines the partial lexer with an IPython lexer. + + IPythonConsoleLexer + A lexer for IPython console sessions, with support for tracebacks. + + IPyLexer + A friendly lexer which examines the first line of text and from it, + decides whether to use an IPython lexer or an IPython console lexer. + This is probably the only lexer that needs to be explicitly added + to Pygments. + +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2013, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +# Standard library +import re + +# Third party +from pygments.lexers import ( + BashLexer, HtmlLexer, JavascriptLexer, RubyLexer, PerlLexer, PythonLexer, + Python3Lexer, TexLexer) +from pygments.lexer import ( + Lexer, DelegatingLexer, RegexLexer, do_insertions, bygroups, using, +) +from pygments.token import ( + Generic, Keyword, Literal, Name, Operator, Other, Text, Error, +) +from pygments.util import get_bool_opt + +# Local + +line_re = re.compile('.*?\n') + +__all__ = ['build_ipy_lexer', 'IPython3Lexer', 'IPythonLexer', + 'IPythonPartialTracebackLexer', 'IPythonTracebackLexer', + 'IPythonConsoleLexer', 'IPyLexer'] + + +def build_ipy_lexer(python3): + """Builds IPython lexers depending on the value of `python3`. + + The lexer inherits from an appropriate Python lexer and then adds + information about IPython specific keywords (i.e. magic commands, + shell commands, etc.) + + Parameters + ---------- + python3 : bool + If `True`, then build an IPython lexer from a Python 3 lexer. + + """ + # It would be nice to have a single IPython lexer class which takes + # a boolean `python3`. But since there are two Python lexer classes, + # we will also have two IPython lexer classes. + if python3: + PyLexer = Python3Lexer + name = 'IPython3' + aliases = ['ipython3'] + doc = """IPython3 Lexer""" + else: + PyLexer = PythonLexer + name = 'IPython' + aliases = ['ipython2', 'ipython'] + doc = """IPython Lexer""" + + ipython_tokens = [ + (r'(?s)(\s*)(%%capture)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%debug)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?is)(\s*)(%%html)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(HtmlLexer))), + (r'(?s)(\s*)(%%javascript)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), + (r'(?s)(\s*)(%%js)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), + (r'(?s)(\s*)(%%latex)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(TexLexer))), + (r'(?s)(\s*)(%%perl)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PerlLexer))), + (r'(?s)(\s*)(%%prun)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%pypy)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%python)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%python2)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PythonLexer))), + (r'(?s)(\s*)(%%python3)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(Python3Lexer))), + (r'(?s)(\s*)(%%ruby)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(RubyLexer))), + (r'(?s)(\s*)(%%time)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%timeit)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%writefile)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%file)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r"(?s)(\s*)(%%)(\w+)(.*)", bygroups(Text, Operator, Keyword, Text)), + (r'(?s)(^\s*)(%%!)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(BashLexer))), + (r"(%%?)(\w+)(\?\??)$", bygroups(Operator, Keyword, Operator)), + (r"\b(\?\??)(\s*)$", bygroups(Operator, Text)), + (r'(%)(sx|sc|system)(.*)(\n)', bygroups(Operator, Keyword, + using(BashLexer), Text)), + (r'(%)(\w+)(.*\n)', bygroups(Operator, Keyword, Text)), + (r'^(!!)(.+)(\n)', bygroups(Operator, using(BashLexer), Text)), + (r'(!)(?!=)(.+)(\n)', bygroups(Operator, using(BashLexer), Text)), + (r'^(\s*)(\?\??)(\s*%{0,2}[\w\.\*]*)', bygroups(Text, Operator, Text)), + (r'(\s*%{0,2}[\w\.\*]*)(\?\??)(\s*)$', bygroups(Text, Operator, Text)), + ] + + tokens = PyLexer.tokens.copy() + tokens['root'] = ipython_tokens + tokens['root'] + + attrs = {'name': name, 'aliases': aliases, 'filenames': [], + '__doc__': doc, 'tokens': tokens} + + return type(name, (PyLexer,), attrs) + + +IPython3Lexer = build_ipy_lexer(python3=True) +IPythonLexer = build_ipy_lexer(python3=False) + + +class IPythonPartialTracebackLexer(RegexLexer): + """ + Partial lexer for IPython tracebacks. + + Handles all the non-python output. + + """ + name = 'IPython Partial Traceback' + + tokens = { + 'root': [ + # Tracebacks for syntax errors have a different style. + # For both types of tracebacks, we mark the first line with + # Generic.Traceback. For syntax errors, we mark the filename + # as we mark the filenames for non-syntax tracebacks. + # + # These two regexps define how IPythonConsoleLexer finds a + # traceback. + # + ## Non-syntax traceback + (r'^(\^C)?(-+\n)', bygroups(Error, Generic.Traceback)), + ## Syntax traceback + (r'^( File)(.*)(, line )(\d+\n)', + bygroups(Generic.Traceback, Name.Namespace, + Generic.Traceback, Literal.Number.Integer)), + + # (Exception Identifier)(Whitespace)(Traceback Message) + (r'(?u)(^[^\d\W]\w*)(\s*)(Traceback.*?\n)', + bygroups(Name.Exception, Generic.Whitespace, Text)), + # (Module/Filename)(Text)(Callee)(Function Signature) + # Better options for callee and function signature? + (r'(.*)( in )(.*)(\(.*\)\n)', + bygroups(Name.Namespace, Text, Name.Entity, Name.Tag)), + # Regular line: (Whitespace)(Line Number)(Python Code) + (r'(\s*?)(\d+)(.*?\n)', + bygroups(Generic.Whitespace, Literal.Number.Integer, Other)), + # Emphasized line: (Arrow)(Line Number)(Python Code) + # Using Exception token so arrow color matches the Exception. + (r'(-*>?\s?)(\d+)(.*?\n)', + bygroups(Name.Exception, Literal.Number.Integer, Other)), + # (Exception Identifier)(Message) + (r'(?u)(^[^\d\W]\w*)(:.*?\n)', + bygroups(Name.Exception, Text)), + # Tag everything else as Other, will be handled later. + (r'.*\n', Other), + ], + } + + +class IPythonTracebackLexer(DelegatingLexer): + """ + IPython traceback lexer. + + For doctests, the tracebacks can be snipped as much as desired with the + exception to the lines that designate a traceback. For non-syntax error + tracebacks, this is the line of hyphens. For syntax error tracebacks, + this is the line which lists the File and line number. + + """ + # The lexer inherits from DelegatingLexer. The "root" lexer is an + # appropriate IPython lexer, which depends on the value of the boolean + # `python3`. First, we parse with the partial IPython traceback lexer. + # Then, any code marked with the "Other" token is delegated to the root + # lexer. + # + name = 'IPython Traceback' + aliases = ['ipythontb'] + + def __init__(self, **options): + self.python3 = get_bool_opt(options, 'python3', False) + if self.python3: + self.aliases = ['ipython3tb'] + else: + self.aliases = ['ipython2tb', 'ipythontb'] + + if self.python3: + IPyLexer = IPython3Lexer + else: + IPyLexer = IPythonLexer + + DelegatingLexer.__init__(self, IPyLexer, + IPythonPartialTracebackLexer, **options) + +class IPythonConsoleLexer(Lexer): + """ + An IPython console lexer for IPython code-blocks and doctests, such as: + + .. code-block:: rst + + .. code-block:: ipythonconsole + + In [1]: a = 'foo' + + In [2]: a + Out[2]: 'foo' + + In [3]: print(a) + foo + + + Support is also provided for IPython exceptions: + + .. code-block:: rst + + .. code-block:: ipythonconsole + + In [1]: raise Exception + Traceback (most recent call last): + ... + Exception + + """ + name = 'IPython console session' + aliases = ['ipythonconsole'] + mimetypes = ['text/x-ipython-console'] + + # The regexps used to determine what is input and what is output. + # The default prompts for IPython are: + # + # in = 'In [#]: ' + # continuation = ' .D.: ' + # template = 'Out[#]: ' + # + # Where '#' is the 'prompt number' or 'execution count' and 'D' + # D is a number of dots matching the width of the execution count + # + in1_regex = r'In \[[0-9]+\]: ' + in2_regex = r' \.\.+\.: ' + out_regex = r'Out\[[0-9]+\]: ' + + #: The regex to determine when a traceback starts. + ipytb_start = re.compile(r'^(\^C)?(-+\n)|^( File)(.*)(, line )(\d+\n)') + + def __init__(self, **options): + """Initialize the IPython console lexer. + + Parameters + ---------- + python3 : bool + If `True`, then the console inputs are parsed using a Python 3 + lexer. Otherwise, they are parsed using a Python 2 lexer. + in1_regex : RegexObject + The compiled regular expression used to detect the start + of inputs. Although the IPython configuration setting may have a + trailing whitespace, do not include it in the regex. If `None`, + then the default input prompt is assumed. + in2_regex : RegexObject + The compiled regular expression used to detect the continuation + of inputs. Although the IPython configuration setting may have a + trailing whitespace, do not include it in the regex. If `None`, + then the default input prompt is assumed. + out_regex : RegexObject + The compiled regular expression used to detect outputs. If `None`, + then the default output prompt is assumed. + + """ + self.python3 = get_bool_opt(options, 'python3', False) + if self.python3: + self.aliases = ['ipython3console'] + else: + self.aliases = ['ipython2console', 'ipythonconsole'] + + in1_regex = options.get('in1_regex', self.in1_regex) + in2_regex = options.get('in2_regex', self.in2_regex) + out_regex = options.get('out_regex', self.out_regex) + + # So that we can work with input and output prompts which have been + # rstrip'd (possibly by editors) we also need rstrip'd variants. If + # we do not do this, then such prompts will be tagged as 'output'. + # The reason can't just use the rstrip'd variants instead is because + # we want any whitespace associated with the prompt to be inserted + # with the token. This allows formatted code to be modified so as hide + # the appearance of prompts, with the whitespace included. One example + # use of this is in copybutton.js from the standard lib Python docs. + in1_regex_rstrip = in1_regex.rstrip() + '\n' + in2_regex_rstrip = in2_regex.rstrip() + '\n' + out_regex_rstrip = out_regex.rstrip() + '\n' + + # Compile and save them all. + attrs = ['in1_regex', 'in2_regex', 'out_regex', + 'in1_regex_rstrip', 'in2_regex_rstrip', 'out_regex_rstrip'] + for attr in attrs: + self.__setattr__(attr, re.compile(locals()[attr])) + + Lexer.__init__(self, **options) + + if self.python3: + pylexer = IPython3Lexer + tblexer = IPythonTracebackLexer + else: + pylexer = IPythonLexer + tblexer = IPythonTracebackLexer + + self.pylexer = pylexer(**options) + self.tblexer = tblexer(**options) + + self.reset() + + def reset(self): + self.mode = 'output' + self.index = 0 + self.buffer = u'' + self.insertions = [] + + def buffered_tokens(self): + """ + Generator of unprocessed tokens after doing insertions and before + changing to a new state. + + """ + if self.mode == 'output': + tokens = [(0, Generic.Output, self.buffer)] + elif self.mode == 'input': + tokens = self.pylexer.get_tokens_unprocessed(self.buffer) + else: # traceback + tokens = self.tblexer.get_tokens_unprocessed(self.buffer) + + for i, t, v in do_insertions(self.insertions, tokens): + # All token indexes are relative to the buffer. + yield self.index + i, t, v + + # Clear it all + self.index += len(self.buffer) + self.buffer = u'' + self.insertions = [] + + def get_mci(self, line): + """ + Parses the line and returns a 3-tuple: (mode, code, insertion). + + `mode` is the next mode (or state) of the lexer, and is always equal + to 'input', 'output', or 'tb'. + + `code` is a portion of the line that should be added to the buffer + corresponding to the next mode and eventually lexed by another lexer. + For example, `code` could be Python code if `mode` were 'input'. + + `insertion` is a 3-tuple (index, token, text) representing an + unprocessed "token" that will be inserted into the stream of tokens + that are created from the buffer once we change modes. This is usually + the input or output prompt. + + In general, the next mode depends on current mode and on the contents + of `line`. + + """ + # To reduce the number of regex match checks, we have multiple + # 'if' blocks instead of 'if-elif' blocks. + + # Check for possible end of input + in2_match = self.in2_regex.match(line) + in2_match_rstrip = self.in2_regex_rstrip.match(line) + if (in2_match and in2_match.group().rstrip() == line.rstrip()) or \ + in2_match_rstrip: + end_input = True + else: + end_input = False + if end_input and self.mode != 'tb': + # Only look for an end of input when not in tb mode. + # An ellipsis could appear within the traceback. + mode = 'output' + code = u'' + insertion = (0, Generic.Prompt, line) + return mode, code, insertion + + # Check for output prompt + out_match = self.out_regex.match(line) + out_match_rstrip = self.out_regex_rstrip.match(line) + if out_match or out_match_rstrip: + mode = 'output' + if out_match: + idx = out_match.end() + else: + idx = out_match_rstrip.end() + code = line[idx:] + # Use the 'heading' token for output. We cannot use Generic.Error + # since it would conflict with exceptions. + insertion = (0, Generic.Heading, line[:idx]) + return mode, code, insertion + + + # Check for input or continuation prompt (non stripped version) + in1_match = self.in1_regex.match(line) + if in1_match or (in2_match and self.mode != 'tb'): + # New input or when not in tb, continued input. + # We do not check for continued input when in tb since it is + # allowable to replace a long stack with an ellipsis. + mode = 'input' + if in1_match: + idx = in1_match.end() + else: # in2_match + idx = in2_match.end() + code = line[idx:] + insertion = (0, Generic.Prompt, line[:idx]) + return mode, code, insertion + + # Check for input or continuation prompt (stripped version) + in1_match_rstrip = self.in1_regex_rstrip.match(line) + if in1_match_rstrip or (in2_match_rstrip and self.mode != 'tb'): + # New input or when not in tb, continued input. + # We do not check for continued input when in tb since it is + # allowable to replace a long stack with an ellipsis. + mode = 'input' + if in1_match_rstrip: + idx = in1_match_rstrip.end() + else: # in2_match + idx = in2_match_rstrip.end() + code = line[idx:] + insertion = (0, Generic.Prompt, line[:idx]) + return mode, code, insertion + + # Check for traceback + if self.ipytb_start.match(line): + mode = 'tb' + code = line + insertion = None + return mode, code, insertion + + # All other stuff... + if self.mode in ('input', 'output'): + # We assume all other text is output. Multiline input that + # does not use the continuation marker cannot be detected. + # For example, the 3 in the following is clearly output: + # + # In [1]: print 3 + # 3 + # + # But the following second line is part of the input: + # + # In [2]: while True: + # print True + # + # In both cases, the 2nd line will be 'output'. + # + mode = 'output' + else: + mode = 'tb' + + code = line + insertion = None + + return mode, code, insertion + + def get_tokens_unprocessed(self, text): + self.reset() + for match in line_re.finditer(text): + line = match.group() + mode, code, insertion = self.get_mci(line) + + if mode != self.mode: + # Yield buffered tokens before transitioning to new mode. + for token in self.buffered_tokens(): + yield token + self.mode = mode + + if insertion: + self.insertions.append((len(self.buffer), [insertion])) + self.buffer += code + + for token in self.buffered_tokens(): + yield token + +class IPyLexer(Lexer): + r""" + Primary lexer for all IPython-like code. + + This is a simple helper lexer. If the first line of the text begins with + "In \[[0-9]+\]:", then the entire text is parsed with an IPython console + lexer. If not, then the entire text is parsed with an IPython lexer. + + The goal is to reduce the number of lexers that are registered + with Pygments. + + """ + name = 'IPy session' + aliases = ['ipy'] + + def __init__(self, **options): + self.python3 = get_bool_opt(options, 'python3', False) + if self.python3: + self.aliases = ['ipy3'] + else: + self.aliases = ['ipy2', 'ipy'] + + Lexer.__init__(self, **options) + + self.IPythonLexer = IPythonLexer(**options) + self.IPythonConsoleLexer = IPythonConsoleLexer(**options) + + def get_tokens_unprocessed(self, text): + # Search for the input prompt anywhere...this allows code blocks to + # begin with comments as well. + if re.match(r'.*(In \[[0-9]+\]:)', text.strip(), re.DOTALL): + lex = self.IPythonConsoleLexer + else: + lex = self.IPythonLexer + for token in lex.get_tokens_unprocessed(text): + yield token + diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/pretty.py b/.venv/lib/python3.8/site-packages/IPython/lib/pretty.py new file mode 100644 index 0000000..72f1435 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/pretty.py @@ -0,0 +1,951 @@ +# -*- coding: utf-8 -*- +""" +Python advanced pretty printer. This pretty printer is intended to +replace the old `pprint` python module which does not allow developers +to provide their own pretty print callbacks. + +This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`. + + +Example Usage +------------- + +To directly print the representation of an object use `pprint`:: + + from pretty import pprint + pprint(complex_object) + +To get a string of the output use `pretty`:: + + from pretty import pretty + string = pretty(complex_object) + + +Extending +--------- + +The pretty library allows developers to add pretty printing rules for their +own objects. This process is straightforward. All you have to do is to +add a `_repr_pretty_` method to your object and call the methods on the +pretty printer passed:: + + class MyObject(object): + + def _repr_pretty_(self, p, cycle): + ... + +Here's an example for a class with a simple constructor:: + + class MySimpleObject: + + def __init__(self, a, b, *, c=None): + self.a = a + self.b = b + self.c = c + + def _repr_pretty_(self, p, cycle): + ctor = CallExpression.factory(self.__class__.__name__) + if self.c is None: + p.pretty(ctor(a, b)) + else: + p.pretty(ctor(a, b, c=c)) + +Here is an example implementation of a `_repr_pretty_` method for a list +subclass:: + + class MyList(list): + + def _repr_pretty_(self, p, cycle): + if cycle: + p.text('MyList(...)') + else: + with p.group(8, 'MyList([', '])'): + for idx, item in enumerate(self): + if idx: + p.text(',') + p.breakable() + p.pretty(item) + +The `cycle` parameter is `True` if pretty detected a cycle. You *have* to +react to that or the result is an infinite loop. `p.text()` just adds +non breaking text to the output, `p.breakable()` either adds a whitespace +or breaks here. If you pass it an argument it's used instead of the +default space. `p.pretty` prettyprints another object using the pretty print +method. + +The first parameter to the `group` function specifies the extra indentation +of the next line. In this example the next item will either be on the same +line (if the items are short enough) or aligned with the right edge of the +opening bracket of `MyList`. + +If you just want to indent something you can use the group function +without open / close parameters. You can also use this code:: + + with p.indent(2): + ... + +Inheritance diagram: + +.. inheritance-diagram:: IPython.lib.pretty + :parts: 3 + +:copyright: 2007 by Armin Ronacher. + Portions (c) 2009 by Robert Kern. +:license: BSD License. +""" + +from contextlib import contextmanager +import datetime +import os +import re +import sys +import types +from collections import deque +from inspect import signature +from io import StringIO +from warnings import warn + +from IPython.utils.decorators import undoc +from IPython.utils.py3compat import PYPY + +__all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', + 'for_type', 'for_type_by_name', 'RawText', 'RawStringLiteral', 'CallExpression'] + + +MAX_SEQ_LENGTH = 1000 +_re_pattern_type = type(re.compile('')) + +def _safe_getattr(obj, attr, default=None): + """Safe version of getattr. + + Same as getattr, but will return ``default`` on any Exception, + rather than raising. + """ + try: + return getattr(obj, attr, default) + except Exception: + return default + +@undoc +class CUnicodeIO(StringIO): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warn(("CUnicodeIO is deprecated since IPython 6.0. " + "Please use io.StringIO instead."), + DeprecationWarning, stacklevel=2) + +def _sorted_for_pprint(items): + """ + Sort the given items for pretty printing. Since some predictable + sorting is better than no sorting at all, we sort on the string + representation if normal sorting fails. + """ + items = list(items) + try: + return sorted(items) + except Exception: + try: + return sorted(items, key=str) + except Exception: + return items + +def pretty(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH): + """ + Pretty print the object's representation. + """ + stream = StringIO() + printer = RepresentationPrinter(stream, verbose, max_width, newline, max_seq_length=max_seq_length) + printer.pretty(obj) + printer.flush() + return stream.getvalue() + + +def pprint(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH): + """ + Like `pretty` but print to stdout. + """ + printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline, max_seq_length=max_seq_length) + printer.pretty(obj) + printer.flush() + sys.stdout.write(newline) + sys.stdout.flush() + +class _PrettyPrinterBase(object): + + @contextmanager + def indent(self, indent): + """with statement support for indenting/dedenting.""" + self.indentation += indent + try: + yield + finally: + self.indentation -= indent + + @contextmanager + def group(self, indent=0, open='', close=''): + """like begin_group / end_group but for the with statement.""" + self.begin_group(indent, open) + try: + yield + finally: + self.end_group(indent, close) + +class PrettyPrinter(_PrettyPrinterBase): + """ + Baseclass for the `RepresentationPrinter` prettyprinter that is used to + generate pretty reprs of objects. Contrary to the `RepresentationPrinter` + this printer knows nothing about the default pprinters or the `_repr_pretty_` + callback method. + """ + + def __init__(self, output, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH): + self.output = output + self.max_width = max_width + self.newline = newline + self.max_seq_length = max_seq_length + self.output_width = 0 + self.buffer_width = 0 + self.buffer = deque() + + root_group = Group(0) + self.group_stack = [root_group] + self.group_queue = GroupQueue(root_group) + self.indentation = 0 + + def _break_one_group(self, group): + while group.breakables: + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + while self.buffer and isinstance(self.buffer[0], Text): + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + + def _break_outer_groups(self): + while self.max_width < self.output_width + self.buffer_width: + group = self.group_queue.deq() + if not group: + return + self._break_one_group(group) + + def text(self, obj): + """Add literal text to the output.""" + width = len(obj) + if self.buffer: + text = self.buffer[-1] + if not isinstance(text, Text): + text = Text() + self.buffer.append(text) + text.add(obj, width) + self.buffer_width += width + self._break_outer_groups() + else: + self.output.write(obj) + self.output_width += width + + def breakable(self, sep=' '): + """ + Add a breakable separator to the output. This does not mean that it + will automatically break here. If no breaking on this position takes + place the `sep` is inserted which default to one space. + """ + width = len(sep) + group = self.group_stack[-1] + if group.want_break: + self.flush() + self.output.write(self.newline) + self.output.write(' ' * self.indentation) + self.output_width = self.indentation + self.buffer_width = 0 + else: + self.buffer.append(Breakable(sep, width, self)) + self.buffer_width += width + self._break_outer_groups() + + def break_(self): + """ + Explicitly insert a newline into the output, maintaining correct indentation. + """ + group = self.group_queue.deq() + if group: + self._break_one_group(group) + self.flush() + self.output.write(self.newline) + self.output.write(' ' * self.indentation) + self.output_width = self.indentation + self.buffer_width = 0 + + + def begin_group(self, indent=0, open=''): + """ + Begin a group. + The first parameter specifies the indentation for the next line (usually + the width of the opening text), the second the opening text. All + parameters are optional. + """ + if open: + self.text(open) + group = Group(self.group_stack[-1].depth + 1) + self.group_stack.append(group) + self.group_queue.enq(group) + self.indentation += indent + + def _enumerate(self, seq): + """like enumerate, but with an upper limit on the number of items""" + for idx, x in enumerate(seq): + if self.max_seq_length and idx >= self.max_seq_length: + self.text(',') + self.breakable() + self.text('...') + return + yield idx, x + + def end_group(self, dedent=0, close=''): + """End a group. See `begin_group` for more details.""" + self.indentation -= dedent + group = self.group_stack.pop() + if not group.breakables: + self.group_queue.remove(group) + if close: + self.text(close) + + def flush(self): + """Flush data that is left in the buffer.""" + for data in self.buffer: + self.output_width += data.output(self.output, self.output_width) + self.buffer.clear() + self.buffer_width = 0 + + +def _get_mro(obj_class): + """ Get a reasonable method resolution order of a class and its superclasses + for both old-style and new-style classes. + """ + if not hasattr(obj_class, '__mro__'): + # Old-style class. Mix in object to make a fake new-style class. + try: + obj_class = type(obj_class.__name__, (obj_class, object), {}) + except TypeError: + # Old-style extension type that does not descend from object. + # FIXME: try to construct a more thorough MRO. + mro = [obj_class] + else: + mro = obj_class.__mro__[1:-1] + else: + mro = obj_class.__mro__ + return mro + + +class RepresentationPrinter(PrettyPrinter): + """ + Special pretty printer that has a `pretty` method that calls the pretty + printer for a python object. + + This class stores processing data on `self` so you must *never* use + this class in a threaded environment. Always lock it or reinstanciate + it. + + Instances also have a verbose flag callbacks can access to control their + output. For example the default instance repr prints all attributes and + methods that are not prefixed by an underscore if the printer is in + verbose mode. + """ + + def __init__(self, output, verbose=False, max_width=79, newline='\n', + singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None, + max_seq_length=MAX_SEQ_LENGTH): + + PrettyPrinter.__init__(self, output, max_width, newline, max_seq_length=max_seq_length) + self.verbose = verbose + self.stack = [] + if singleton_pprinters is None: + singleton_pprinters = _singleton_pprinters.copy() + self.singleton_pprinters = singleton_pprinters + if type_pprinters is None: + type_pprinters = _type_pprinters.copy() + self.type_pprinters = type_pprinters + if deferred_pprinters is None: + deferred_pprinters = _deferred_type_pprinters.copy() + self.deferred_pprinters = deferred_pprinters + + def pretty(self, obj): + """Pretty print the given object.""" + obj_id = id(obj) + cycle = obj_id in self.stack + self.stack.append(obj_id) + self.begin_group() + try: + obj_class = _safe_getattr(obj, '__class__', None) or type(obj) + # First try to find registered singleton printers for the type. + try: + printer = self.singleton_pprinters[obj_id] + except (TypeError, KeyError): + pass + else: + return printer(obj, self, cycle) + # Next walk the mro and check for either: + # 1) a registered printer + # 2) a _repr_pretty_ method + for cls in _get_mro(obj_class): + if cls in self.type_pprinters: + # printer registered in self.type_pprinters + return self.type_pprinters[cls](obj, self, cycle) + else: + # deferred printer + printer = self._in_deferred_types(cls) + if printer is not None: + return printer(obj, self, cycle) + else: + # Finally look for special method names. + # Some objects automatically create any requested + # attribute. Try to ignore most of them by checking for + # callability. + if '_repr_pretty_' in cls.__dict__: + meth = cls._repr_pretty_ + if callable(meth): + return meth(obj, self, cycle) + if cls is not object \ + and callable(cls.__dict__.get('__repr__')): + return _repr_pprint(obj, self, cycle) + + return _default_pprint(obj, self, cycle) + finally: + self.end_group() + self.stack.pop() + + def _in_deferred_types(self, cls): + """ + Check if the given class is specified in the deferred type registry. + + Returns the printer from the registry if it exists, and None if the + class is not in the registry. Successful matches will be moved to the + regular type registry for future use. + """ + mod = _safe_getattr(cls, '__module__', None) + name = _safe_getattr(cls, '__name__', None) + key = (mod, name) + printer = None + if key in self.deferred_pprinters: + # Move the printer over to the regular registry. + printer = self.deferred_pprinters.pop(key) + self.type_pprinters[cls] = printer + return printer + + +class Printable(object): + + def output(self, stream, output_width): + return output_width + + +class Text(Printable): + + def __init__(self): + self.objs = [] + self.width = 0 + + def output(self, stream, output_width): + for obj in self.objs: + stream.write(obj) + return output_width + self.width + + def add(self, obj, width): + self.objs.append(obj) + self.width += width + + +class Breakable(Printable): + + def __init__(self, seq, width, pretty): + self.obj = seq + self.width = width + self.pretty = pretty + self.indentation = pretty.indentation + self.group = pretty.group_stack[-1] + self.group.breakables.append(self) + + def output(self, stream, output_width): + self.group.breakables.popleft() + if self.group.want_break: + stream.write(self.pretty.newline) + stream.write(' ' * self.indentation) + return self.indentation + if not self.group.breakables: + self.pretty.group_queue.remove(self.group) + stream.write(self.obj) + return output_width + self.width + + +class Group(Printable): + + def __init__(self, depth): + self.depth = depth + self.breakables = deque() + self.want_break = False + + +class GroupQueue(object): + + def __init__(self, *groups): + self.queue = [] + for group in groups: + self.enq(group) + + def enq(self, group): + depth = group.depth + while depth > len(self.queue) - 1: + self.queue.append([]) + self.queue[depth].append(group) + + def deq(self): + for stack in self.queue: + for idx, group in enumerate(reversed(stack)): + if group.breakables: + del stack[idx] + group.want_break = True + return group + for group in stack: + group.want_break = True + del stack[:] + + def remove(self, group): + try: + self.queue[group.depth].remove(group) + except ValueError: + pass + + +class RawText: + """ Object such that ``p.pretty(RawText(value))`` is the same as ``p.text(value)``. + + An example usage of this would be to show a list as binary numbers, using + ``p.pretty([RawText(bin(i)) for i in integers])``. + """ + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + p.text(self.value) + + +class CallExpression: + """ Object which emits a line-wrapped call expression in the form `__name(*args, **kwargs)` """ + def __init__(__self, __name, *args, **kwargs): + # dunders are to avoid clashes with kwargs, as python's name manging + # will kick in. + self = __self + self.name = __name + self.args = args + self.kwargs = kwargs + + @classmethod + def factory(cls, name): + def inner(*args, **kwargs): + return cls(name, *args, **kwargs) + return inner + + def _repr_pretty_(self, p, cycle): + # dunders are to avoid clashes with kwargs, as python's name manging + # will kick in. + + started = False + def new_item(): + nonlocal started + if started: + p.text(",") + p.breakable() + started = True + + prefix = self.name + "(" + with p.group(len(prefix), prefix, ")"): + for arg in self.args: + new_item() + p.pretty(arg) + for arg_name, arg in self.kwargs.items(): + new_item() + arg_prefix = arg_name + "=" + with p.group(len(arg_prefix), arg_prefix): + p.pretty(arg) + + +class RawStringLiteral: + """ Wrapper that shows a string with a `r` prefix """ + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + base_repr = repr(self.value) + if base_repr[:1] in 'uU': + base_repr = base_repr[1:] + prefix = 'ur' + else: + prefix = 'r' + base_repr = prefix + base_repr.replace('\\\\', '\\') + p.text(base_repr) + + +def _default_pprint(obj, p, cycle): + """ + The default print function. Used if an object does not provide one and + it's none of the builtin objects. + """ + klass = _safe_getattr(obj, '__class__', None) or type(obj) + if _safe_getattr(klass, '__repr__', None) is not object.__repr__: + # A user-provided repr. Find newlines and replace them with p.break_() + _repr_pprint(obj, p, cycle) + return + p.begin_group(1, '<') + p.pretty(klass) + p.text(' at 0x%x' % id(obj)) + if cycle: + p.text(' ...') + elif p.verbose: + first = True + for key in dir(obj): + if not key.startswith('_'): + try: + value = getattr(obj, key) + except AttributeError: + continue + if isinstance(value, types.MethodType): + continue + if not first: + p.text(',') + p.breakable() + p.text(key) + p.text('=') + step = len(key) + 1 + p.indentation += step + p.pretty(value) + p.indentation -= step + first = False + p.end_group(1, '>') + + +def _seq_pprinter_factory(start, end): + """ + Factory that returns a pprint function useful for sequences. Used by + the default pprint for tuples and lists. + """ + def inner(obj, p, cycle): + if cycle: + return p.text(start + '...' + end) + step = len(start) + p.begin_group(step, start) + for idx, x in p._enumerate(obj): + if idx: + p.text(',') + p.breakable() + p.pretty(x) + if len(obj) == 1 and isinstance(obj, tuple): + # Special case for 1-item tuples. + p.text(',') + p.end_group(step, end) + return inner + + +def _set_pprinter_factory(start, end): + """ + Factory that returns a pprint function useful for sets and frozensets. + """ + def inner(obj, p, cycle): + if cycle: + return p.text(start + '...' + end) + if len(obj) == 0: + # Special case. + p.text(type(obj).__name__ + '()') + else: + step = len(start) + p.begin_group(step, start) + # Like dictionary keys, we will try to sort the items if there aren't too many + if not (p.max_seq_length and len(obj) >= p.max_seq_length): + items = _sorted_for_pprint(obj) + else: + items = obj + for idx, x in p._enumerate(items): + if idx: + p.text(',') + p.breakable() + p.pretty(x) + p.end_group(step, end) + return inner + + +def _dict_pprinter_factory(start, end): + """ + Factory that returns a pprint function used by the default pprint of + dicts and dict proxies. + """ + def inner(obj, p, cycle): + if cycle: + return p.text('{...}') + step = len(start) + p.begin_group(step, start) + keys = obj.keys() + for idx, key in p._enumerate(keys): + if idx: + p.text(',') + p.breakable() + p.pretty(key) + p.text(': ') + p.pretty(obj[key]) + p.end_group(step, end) + return inner + + +def _super_pprint(obj, p, cycle): + """The pprint for the super type.""" + p.begin_group(8, '') + + + +class _ReFlags: + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + done_one = False + for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', + 'UNICODE', 'VERBOSE', 'DEBUG'): + if self.value & getattr(re, flag): + if done_one: + p.text('|') + p.text('re.' + flag) + done_one = True + + +def _re_pattern_pprint(obj, p, cycle): + """The pprint function for regular expression patterns.""" + re_compile = CallExpression.factory('re.compile') + if obj.flags: + p.pretty(re_compile(RawStringLiteral(obj.pattern), _ReFlags(obj.flags))) + else: + p.pretty(re_compile(RawStringLiteral(obj.pattern))) + + +def _types_simplenamespace_pprint(obj, p, cycle): + """The pprint function for types.SimpleNamespace.""" + namespace = CallExpression.factory('namespace') + if cycle: + p.pretty(namespace(RawText("..."))) + else: + p.pretty(namespace(**obj.__dict__)) + + +def _type_pprint(obj, p, cycle): + """The pprint for classes and types.""" + # Heap allocated types might not have the module attribute, + # and others may set it to None. + + # Checks for a __repr__ override in the metaclass. Can't compare the + # type(obj).__repr__ directly because in PyPy the representation function + # inherited from type isn't the same type.__repr__ + if [m for m in _get_mro(type(obj)) if "__repr__" in vars(m)][:1] != [type]: + _repr_pprint(obj, p, cycle) + return + + mod = _safe_getattr(obj, '__module__', None) + try: + name = obj.__qualname__ + if not isinstance(name, str): + # This can happen if the type implements __qualname__ as a property + # or other descriptor in Python 2. + raise Exception("Try __name__") + except Exception: + name = obj.__name__ + if not isinstance(name, str): + name = '' + + if mod in (None, '__builtin__', 'builtins', 'exceptions'): + p.text(name) + else: + p.text(mod + '.' + name) + + +def _repr_pprint(obj, p, cycle): + """A pprint that just redirects to the normal repr function.""" + # Find newlines and replace them with p.break_() + output = repr(obj) + lines = output.splitlines() + with p.group(): + for idx, output_line in enumerate(lines): + if idx: + p.break_() + p.text(output_line) + + +def _function_pprint(obj, p, cycle): + """Base pprint for all functions and builtin functions.""" + name = _safe_getattr(obj, '__qualname__', obj.__name__) + mod = obj.__module__ + if mod and mod not in ('__builtin__', 'builtins', 'exceptions'): + name = mod + '.' + name + try: + func_def = name + str(signature(obj)) + except ValueError: + func_def = name + p.text('' % func_def) + + +def _exception_pprint(obj, p, cycle): + """Base pprint for all exceptions.""" + name = getattr(obj.__class__, '__qualname__', obj.__class__.__name__) + if obj.__class__.__module__ not in ('exceptions', 'builtins'): + name = '%s.%s' % (obj.__class__.__module__, name) + + p.pretty(CallExpression(name, *getattr(obj, 'args', ()))) + + +#: the exception base +try: + _exception_base = BaseException +except NameError: + _exception_base = Exception + + +#: printers for builtin types +_type_pprinters = { + int: _repr_pprint, + float: _repr_pprint, + str: _repr_pprint, + tuple: _seq_pprinter_factory('(', ')'), + list: _seq_pprinter_factory('[', ']'), + dict: _dict_pprinter_factory('{', '}'), + set: _set_pprinter_factory('{', '}'), + frozenset: _set_pprinter_factory('frozenset({', '})'), + super: _super_pprint, + _re_pattern_type: _re_pattern_pprint, + type: _type_pprint, + types.FunctionType: _function_pprint, + types.BuiltinFunctionType: _function_pprint, + types.MethodType: _repr_pprint, + types.SimpleNamespace: _types_simplenamespace_pprint, + datetime.datetime: _repr_pprint, + datetime.timedelta: _repr_pprint, + _exception_base: _exception_pprint +} + +# render os.environ like a dict +_env_type = type(os.environ) +# future-proof in case os.environ becomes a plain dict? +if _env_type is not dict: + _type_pprinters[_env_type] = _dict_pprinter_factory('environ{', '}') + +try: + # In PyPy, types.DictProxyType is dict, setting the dictproxy printer + # using dict.setdefault avoids overwriting the dict printer + _type_pprinters.setdefault(types.DictProxyType, + _dict_pprinter_factory('dict_proxy({', '})')) + _type_pprinters[types.ClassType] = _type_pprint + _type_pprinters[types.SliceType] = _repr_pprint +except AttributeError: # Python 3 + _type_pprinters[types.MappingProxyType] = \ + _dict_pprinter_factory('mappingproxy({', '})') + _type_pprinters[slice] = _repr_pprint + +_type_pprinters[range] = _repr_pprint +_type_pprinters[bytes] = _repr_pprint + +#: printers for types specified by name +_deferred_type_pprinters = { +} + +def for_type(typ, func): + """ + Add a pretty printer for a given type. + """ + oldfunc = _type_pprinters.get(typ, None) + if func is not None: + # To support easy restoration of old pprinters, we need to ignore Nones. + _type_pprinters[typ] = func + return oldfunc + +def for_type_by_name(type_module, type_name, func): + """ + Add a pretty printer for a type specified by the module and name of a type + rather than the type object itself. + """ + key = (type_module, type_name) + oldfunc = _deferred_type_pprinters.get(key, None) + if func is not None: + # To support easy restoration of old pprinters, we need to ignore Nones. + _deferred_type_pprinters[key] = func + return oldfunc + + +#: printers for the default singletons +_singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis, + NotImplemented]), _repr_pprint) + + +def _defaultdict_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(obj.default_factory, dict(obj))) + +def _ordereddict_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif len(obj): + p.pretty(cls_ctor(list(obj.items()))) + else: + p.pretty(cls_ctor()) + +def _deque_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(list(obj))) + +def _counter_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif len(obj): + p.pretty(cls_ctor(dict(obj))) + else: + p.pretty(cls_ctor()) + + +def _userlist_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(obj.data)) + + +for_type_by_name('collections', 'defaultdict', _defaultdict_pprint) +for_type_by_name('collections', 'OrderedDict', _ordereddict_pprint) +for_type_by_name('collections', 'deque', _deque_pprint) +for_type_by_name('collections', 'Counter', _counter_pprint) +for_type_by_name("collections", "UserList", _userlist_pprint) + +if __name__ == '__main__': + from random import randrange + class Foo(object): + def __init__(self): + self.foo = 1 + self.bar = re.compile(r'\s+') + self.blub = dict.fromkeys(range(30), randrange(1, 40)) + self.hehe = 23424.234234 + self.list = ["blub", "blah", self] + + def get_foo(self): + print("foo") + + pprint(Foo(), verbose=True) diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__init__.py b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..0039e8f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_backgroundjobs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_backgroundjobs.cpython-38.pyc new file mode 100644 index 0000000..40ee633 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_backgroundjobs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_clipboard.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_clipboard.cpython-38.pyc new file mode 100644 index 0000000..be6c072 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_clipboard.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_deepreload.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_deepreload.cpython-38.pyc new file mode 100644 index 0000000..029da62 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_deepreload.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_display.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_display.cpython-38.pyc new file mode 100644 index 0000000..7e9086d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_display.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_editorhooks.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_editorhooks.cpython-38.pyc new file mode 100644 index 0000000..2303b17 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_editorhooks.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_imports.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_imports.cpython-38.pyc new file mode 100644 index 0000000..64e9535 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_imports.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_latextools.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_latextools.cpython-38.pyc new file mode 100644 index 0000000..5a20829 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_latextools.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_lexers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_lexers.cpython-38.pyc new file mode 100644 index 0000000..a50e328 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_lexers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_pretty.cpython-38.pyc b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_pretty.cpython-38.pyc new file mode 100644 index 0000000..ddda274 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/__pycache__/test_pretty.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/test.wav b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test.wav new file mode 100644 index 0000000..aa74fc5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test.wav differ diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_backgroundjobs.py b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_backgroundjobs.py new file mode 100644 index 0000000..fc76ff1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_backgroundjobs.py @@ -0,0 +1,85 @@ +"""Tests for pylab tools module. +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2011, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib imports +import time + +# Our own imports +from IPython.lib import backgroundjobs as bg + +#----------------------------------------------------------------------------- +# Globals and constants +#----------------------------------------------------------------------------- +t_short = 0.0001 # very short interval to wait on jobs + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- +def sleeper(interval=t_short, *a, **kw): + args = dict(interval=interval, + other_args=a, + kw_args=kw) + time.sleep(interval) + return args + +def crasher(interval=t_short, *a, **kw): + time.sleep(interval) + raise Exception("Dead job with interval %s" % interval) + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +def test_result(): + """Test job submission and result retrieval""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert j.result["interval"] == t_short + + +def test_flush(): + """Test job control""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert len(jobs.completed) == 1 + assert len(jobs.dead) == 0 + jobs.flush() + assert len(jobs.completed) == 0 + + +def test_dead(): + """Test control of dead jobs""" + jobs = bg.BackgroundJobManager() + j = jobs.new(crasher) + j.join() + assert len(jobs.completed) == 0 + assert len(jobs.dead) == 1 + jobs.flush() + assert len(jobs.dead) == 0 + + +def test_longer(): + """Test control of longer-running jobs""" + jobs = bg.BackgroundJobManager() + # Sleep for long enough for the following two checks to still report the + # job as running, but not so long that it makes the test suite noticeably + # slower. + j = jobs.new(sleeper, 0.1) + assert len(jobs.running) == 1 + assert len(jobs.completed) == 0 + j.join() + assert len(jobs.running) == 0 + assert len(jobs.completed) == 1 diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_clipboard.py b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_clipboard.py new file mode 100644 index 0000000..802f753 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_clipboard.py @@ -0,0 +1,19 @@ +from IPython.core.error import TryNext +from IPython.lib.clipboard import ClipboardEmpty +from IPython.testing.decorators import skip_if_no_x11 + +@skip_if_no_x11 +def test_clipboard_get(): + # Smoketest for clipboard access - we can't easily guarantee that the + # clipboard is accessible and has something on it, but this tries to + # exercise the relevant code anyway. + try: + a = get_ipython().hooks.clipboard_get() + except ClipboardEmpty: + # Nothing in clipboard to get + pass + except TryNext: + # No clipboard access API available + pass + else: + assert isinstance(a, str) diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_deepreload.py b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_deepreload.py new file mode 100644 index 0000000..5da606c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_deepreload.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +"""Test suite for the deepreload module.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import types +from pathlib import Path + +import pytest +from tempfile import TemporaryDirectory + +from IPython.lib.deepreload import modules_reloading +from IPython.lib.deepreload import reload as dreload +from IPython.utils.syspathcontext import prepended_to_syspath + + +def test_deepreload(): + "Test that dreload does deep reloads and skips excluded modules." + with TemporaryDirectory() as tmpdir: + with prepended_to_syspath(tmpdir): + tmpdirpath = Path(tmpdir) + with open(tmpdirpath / "A.py", "w", encoding="utf-8") as f: + f.write("class Object:\n pass\nok = True\n") + with open(tmpdirpath / "B.py", "w", encoding="utf-8") as f: + f.write("import A\nassert A.ok, 'we are fine'\n") + import A + import B + + # Test that A is not reloaded. + obj = A.Object() + dreload(B, exclude=["A"]) + assert isinstance(obj, A.Object) is True + + # Test that an import failure will not blow-up us. + A.ok = False + with pytest.raises(AssertionError, match="we are fine"): + dreload(B, exclude=["A"]) + assert len(modules_reloading) == 0 + assert not A.ok + + # Test that A is reloaded. + obj = A.Object() + A.ok = False + dreload(B) + assert A.ok + assert isinstance(obj, A.Object) is False + + +def test_not_module(): + pytest.raises(TypeError, dreload, "modulename") + + +def test_not_in_sys_modules(): + fake_module = types.ModuleType("fake_module") + with pytest.raises(ImportError, match="not in sys.modules"): + dreload(fake_module) diff --git a/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_display.py b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_display.py new file mode 100644 index 0000000..f5ed34c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/IPython/lib/tests/test_display.py @@ -0,0 +1,272 @@ +"""Tests for IPython.lib.display. + +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2012, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +from tempfile import NamedTemporaryFile, mkdtemp +from os.path import split, join as pjoin, dirname +import pathlib +from unittest import TestCase, mock +import struct +import wave +from io import BytesIO + +# Third-party imports +import pytest + +try: + import numpy +except ImportError: + pass + +# Our own imports +from IPython.lib import display + +from IPython.testing.decorators import skipif_not_numpy + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +#-------------------------- +# FileLink tests +#-------------------------- + +def test_instantiation_FileLink(): + """FileLink: Test class can be instantiated""" + fl = display.FileLink('example.txt') + # TODO: remove if when only Python >= 3.6 is supported + fl = display.FileLink(pathlib.PurePath('example.txt')) + +def test_warning_on_non_existent_path_FileLink(): + """FileLink: Calling _repr_html_ on non-existent files returns a warning""" + fl = display.FileLink("example.txt") + assert fl._repr_html_().startswith("Path (example.txt)") + + +def test_existing_path_FileLink(): + """FileLink: Calling _repr_html_ functions as expected on existing filepath + """ + tf = NamedTemporaryFile() + fl = display.FileLink(tf.name) + actual = fl._repr_html_() + expected = "%s
" % (tf.name, tf.name) + assert actual == expected + + +def test_existing_path_FileLink_repr(): + """FileLink: Calling repr() functions as expected on existing filepath + """ + tf = NamedTemporaryFile() + fl = display.FileLink(tf.name) + actual = repr(fl) + expected = tf.name + assert actual == expected + + +def test_error_on_directory_to_FileLink(): + """FileLink: Raises error when passed directory + """ + td = mkdtemp() + pytest.raises(ValueError, display.FileLink, td) + +#-------------------------- +# FileLinks tests +#-------------------------- + +def test_instantiation_FileLinks(): + """FileLinks: Test class can be instantiated + """ + fls = display.FileLinks('example') + +def test_warning_on_non_existent_path_FileLinks(): + """FileLinks: Calling _repr_html_ on non-existent files returns a warning""" + fls = display.FileLinks("example") + assert fls._repr_html_().startswith("Path (example)") + + +def test_existing_path_FileLinks(): + """FileLinks: Calling _repr_html_ functions as expected on existing dir + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + fl = display.FileLinks(td) + actual = fl._repr_html_() + actual = actual.split('\n') + actual.sort() + # the links should always have forward slashes, even on windows, so replace + # backslashes with forward slashes here + expected = ["%s/
" % td, + "  %s
" %\ + (tf2.name.replace("\\","/"),split(tf2.name)[1]), + "  %s
" %\ + (tf1.name.replace("\\","/"),split(tf1.name)[1])] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_alt_formatter(): + """FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + def fake_formatter(dirname,fnames,included_suffixes): + return ["hello","world"] + fl = display.FileLinks(td,notebook_display_formatter=fake_formatter) + actual = fl._repr_html_() + actual = actual.split('\n') + actual.sort() + expected = ["hello","world"] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_repr(): + """FileLinks: Calling repr() functions as expected on existing directory """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + fl = display.FileLinks(td) + actual = repr(fl) + actual = actual.split('\n') + actual.sort() + expected = ['%s/' % td, ' %s' % split(tf1.name)[1],' %s' % split(tf2.name)[1]] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_repr_alt_formatter(): + """FileLinks: Calling repr() functions as expected w/ alt formatter + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + def fake_formatter(dirname,fnames,included_suffixes): + return ["hello","world"] + fl = display.FileLinks(td,terminal_display_formatter=fake_formatter) + actual = repr(fl) + actual = actual.split('\n') + actual.sort() + expected = ["hello","world"] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_error_on_file_to_FileLinks(): + """FileLinks: Raises error when passed file + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + pytest.raises(ValueError, display.FileLinks, tf1.name) + + +def test_recursive_FileLinks(): + """FileLinks: Does not recurse when recursive=False + """ + td = mkdtemp() + tf = NamedTemporaryFile(dir=td) + subtd = mkdtemp(dir=td) + subtf = NamedTemporaryFile(dir=subtd) + fl = display.FileLinks(td) + actual = str(fl) + actual = actual.split('\n') + assert len(actual) == 4, actual + fl = display.FileLinks(td, recursive=False) + actual = str(fl) + actual = actual.split('\n') + assert len(actual) == 2, actual + +def test_audio_from_file(): + path = pjoin(dirname(__file__), 'test.wav') + display.Audio(filename=path) + +class TestAudioDataWithNumpy(TestCase): + + @skipif_not_numpy + def test_audio_from_numpy_array(self): + test_tone = get_test_tone() + audio = display.Audio(test_tone, rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + @skipif_not_numpy + def test_audio_from_list(self): + test_tone = get_test_tone() + audio = display.Audio(list(test_tone), rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + @skipif_not_numpy + def test_audio_from_numpy_array_without_rate_raises(self): + self.assertRaises(ValueError, display.Audio, get_test_tone()) + + @skipif_not_numpy + def test_audio_data_normalization(self): + expected_max_value = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 2]: + audio = display.Audio(get_test_tone(scale), rate=44100) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + @skipif_not_numpy + def test_audio_data_without_normalization(self): + max_int16 = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 0.2]: + test_tone = get_test_tone(scale) + test_tone_max_abs = numpy.max(numpy.abs(test_tone)) + expected_max_value = int(max_int16 * test_tone_max_abs) + audio = display.Audio(test_tone, rate=44100, normalize=False) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + def test_audio_data_without_normalization_raises_for_invalid_data(self): + self.assertRaises( + ValueError, + lambda: display.Audio([1.001], rate=44100, normalize=False)) + self.assertRaises( + ValueError, + lambda: display.Audio([-1.001], rate=44100, normalize=False)) + +def simulate_numpy_not_installed(): + try: + import numpy + return mock.patch('numpy.array', mock.MagicMock(side_effect=ImportError)) + except ModuleNotFoundError: + return lambda x:x + +@simulate_numpy_not_installed() +class TestAudioDataWithoutNumpy(TestAudioDataWithNumpy): + # All tests from `TestAudioDataWithNumpy` are inherited. + + @skipif_not_numpy + def test_audio_raises_for_nested_list(self): + stereo_signal = [list(get_test_tone())] * 2 + self.assertRaises(TypeError, lambda: display.Audio(stereo_signal, rate=44100)) + + +@skipif_not_numpy +def get_test_tone(scale=1): + return numpy.sin(2 * numpy.pi * 440 * numpy.linspace(0, 1, 44100)) * scale + +def read_wav(data): + with wave.open(BytesIO(data)) as wave_file: + wave_data = wave_file.readframes(wave_file.getnframes()) + num_samples = wave_file.getnframes() * wave_file.getnchannels() + return struct.unpack('<%sh' % num_samples, wave_data) + +def test_code_from_file(): + c = display.Code(filename=__file__) + assert c._repr_html_().startswith(' + + +

%(title)s

+ +''' + +DOC_HEADER_EXTERNALCSS = '''\ + + + + + %(title)s + + + + +

%(title)s

+ +''' + +DOC_FOOTER = '''\ + + +''' + + +class HtmlFormatter(Formatter): + r""" + Format tokens as HTML 4 ```` tags within a ``
`` tag, wrapped
+    in a ``
`` tag. The ``
``'s CSS class can be set by the `cssclass` + option. + + If the `linenos` option is set to ``"table"``, the ``
`` is
+    additionally wrapped inside a ```` which has one row and two
+    cells: one containing the line numbers and one containing the code.
+    Example:
+
+    .. sourcecode:: html
+
+        
+
+ + +
+
1
+            2
+
+
def foo(bar):
+              pass
+            
+
+ + (whitespace added to improve clarity). + + Wrapping can be disabled using the `nowrap` option. + + A list of lines can be specified using the `hl_lines` option to make these + lines highlighted (as of Pygments 0.11). + + With the `full` option, a complete HTML 4 document is output, including + the style definitions inside a `` + + + + +
{code}
+
+ + +""" + +_TERM_COLORS = {"256color": ColorSystem.EIGHT_BIT, "16color": ColorSystem.STANDARD} + + +class ConsoleDimensions(NamedTuple): + """Size of the terminal.""" + + width: int + """The width of the console in 'cells'.""" + height: int + """The height of the console in lines.""" + + +@dataclass +class ConsoleOptions: + """Options for __rich_console__ method.""" + + size: ConsoleDimensions + """Size of console.""" + legacy_windows: bool + """legacy_windows: flag for legacy windows.""" + min_width: int + """Minimum width of renderable.""" + max_width: int + """Maximum width of renderable.""" + is_terminal: bool + """True if the target is a terminal, otherwise False.""" + encoding: str + """Encoding of terminal.""" + max_height: int + """Height of container (starts as terminal)""" + justify: Optional[JustifyMethod] = None + """Justify value override for renderable.""" + overflow: Optional[OverflowMethod] = None + """Overflow value override for renderable.""" + no_wrap: Optional[bool] = False + """Disable wrapping for text.""" + highlight: Optional[bool] = None + """Highlight override for render_str.""" + markup: Optional[bool] = None + """Enable markup when rendering strings.""" + height: Optional[int] = None + + @property + def ascii_only(self) -> bool: + """Check if renderables should use ascii only.""" + return not self.encoding.startswith("utf") + + def copy(self) -> "ConsoleOptions": + """Return a copy of the options. + + Returns: + ConsoleOptions: a copy of self. + """ + options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) + options.__dict__ = self.__dict__.copy() + return options + + def update( + self, + *, + width: Union[int, NoChange] = NO_CHANGE, + min_width: Union[int, NoChange] = NO_CHANGE, + max_width: Union[int, NoChange] = NO_CHANGE, + justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE, + overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE, + no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE, + highlight: Union[Optional[bool], NoChange] = NO_CHANGE, + markup: Union[Optional[bool], NoChange] = NO_CHANGE, + height: Union[Optional[int], NoChange] = NO_CHANGE, + ) -> "ConsoleOptions": + """Update values, return a copy.""" + options = self.copy() + if not isinstance(width, NoChange): + options.min_width = options.max_width = max(0, width) + if not isinstance(min_width, NoChange): + options.min_width = min_width + if not isinstance(max_width, NoChange): + options.max_width = max_width + if not isinstance(justify, NoChange): + options.justify = justify + if not isinstance(overflow, NoChange): + options.overflow = overflow + if not isinstance(no_wrap, NoChange): + options.no_wrap = no_wrap + if not isinstance(highlight, NoChange): + options.highlight = highlight + if not isinstance(markup, NoChange): + options.markup = markup + if not isinstance(height, NoChange): + if height is not None: + options.max_height = height + options.height = None if height is None else max(0, height) + return options + + def update_width(self, width: int) -> "ConsoleOptions": + """Update just the width, return a copy. + + Args: + width (int): New width (sets both min_width and max_width) + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + return options + + def update_height(self, height: int) -> "ConsoleOptions": + """Update the height, and return a copy. + + Args: + height (int): New height + + Returns: + ~ConsoleOptions: New Console options instance. + """ + options = self.copy() + options.max_height = options.height = height + return options + + def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": + """Update the width and height, and return a copy. + + Args: + width (int): New width (sets both min_width and max_width). + height (int): New height. + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + options.height = options.max_height = height + return options + + +@runtime_checkable +class RichCast(Protocol): + """An object that may be 'cast' to a console renderable.""" + + def __rich__(self) -> Union["ConsoleRenderable", str]: # pragma: no cover + ... + + +@runtime_checkable +class ConsoleRenderable(Protocol): + """An object that supports the console protocol.""" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": # pragma: no cover + ... + + +# A type that may be rendered by Console. +RenderableType = Union[ConsoleRenderable, RichCast, str] + + +# The result of calling a __rich_console__ method. +RenderResult = Iterable[Union[RenderableType, Segment]] + + +_null_highlighter = NullHighlighter() + + +class CaptureError(Exception): + """An error in the Capture context manager.""" + + +class NewLine: + """A renderable to generate new line(s)""" + + def __init__(self, count: int = 1) -> None: + self.count = count + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> Iterable[Segment]: + yield Segment("\n" * self.count) + + +class ScreenUpdate: + """Render a list of lines at a given offset.""" + + def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None: + self._lines = lines + self.x = x + self.y = y + + def __rich_console__( + self, console: "Console", options: ConsoleOptions + ) -> RenderResult: + x = self.x + move_to = Control.move_to + for offset, line in enumerate(self._lines, self.y): + yield move_to(x, offset) + yield from line + + +class Capture: + """Context manager to capture the result of printing to the console. + See :meth:`~rich.console.Console.capture` for how to use. + + Args: + console (Console): A console instance to capture output. + """ + + def __init__(self, console: "Console") -> None: + self._console = console + self._result: Optional[str] = None + + def __enter__(self) -> "Capture": + self._console.begin_capture() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self._result = self._console.end_capture() + + def get(self) -> str: + """Get the result of the capture.""" + if self._result is None: + raise CaptureError( + "Capture result is not available until context manager exits." + ) + return self._result + + +class ThemeContext: + """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage.""" + + def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None: + self.console = console + self.theme = theme + self.inherit = inherit + + def __enter__(self) -> "ThemeContext": + self.console.push_theme(self.theme) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.console.pop_theme() + + +class PagerContext: + """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage.""" + + def __init__( + self, + console: "Console", + pager: Optional[Pager] = None, + styles: bool = False, + links: bool = False, + ) -> None: + self._console = console + self.pager = SystemPager() if pager is None else pager + self.styles = styles + self.links = links + + def __enter__(self) -> "PagerContext": + self._console._enter_buffer() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if exc_type is None: + with self._console._lock: + buffer: List[Segment] = self._console._buffer[:] + del self._console._buffer[:] + segments: Iterable[Segment] = buffer + if not self.styles: + segments = Segment.strip_styles(segments) + elif not self.links: + segments = Segment.strip_links(segments) + content = self._console._render_buffer(segments) + self.pager.show(content) + self._console._exit_buffer() + + +class ScreenContext: + """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage.""" + + def __init__( + self, console: "Console", hide_cursor: bool, style: StyleType = "" + ) -> None: + self.console = console + self.hide_cursor = hide_cursor + self.screen = Screen(style=style) + self._changed = False + + def update( + self, *renderables: RenderableType, style: Optional[StyleType] = None + ) -> None: + """Update the screen. + + Args: + renderable (RenderableType, optional): Optional renderable to replace current renderable, + or None for no change. Defaults to None. + style: (Style, optional): Replacement style, or None for no change. Defaults to None. + """ + if renderables: + self.screen.renderable = ( + Group(*renderables) if len(renderables) > 1 else renderables[0] + ) + if style is not None: + self.screen.style = style + self.console.print(self.screen, end="") + + def __enter__(self) -> "ScreenContext": + self._changed = self.console.set_alt_screen(True) + if self._changed and self.hide_cursor: + self.console.show_cursor(False) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if self._changed: + self.console.set_alt_screen(False) + if self.hide_cursor: + self.console.show_cursor(True) + + +class Group: + """Takes a group of renderables and returns a renderable object that renders the group. + + Args: + renderables (Iterable[RenderableType]): An iterable of renderable objects. + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None: + self._renderables = renderables + self.fit = fit + self._render: Optional[List[RenderableType]] = None + + @property + def renderables(self) -> List["RenderableType"]: + if self._render is None: + self._render = list(self._renderables) + return self._render + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.fit: + return measure_renderables(console, options, self.renderables) + else: + return Measurement(options.max_width, options.max_width) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> RenderResult: + yield from self.renderables + + +def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: + """A decorator that turns an iterable of renderables in to a group. + + Args: + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def decorator( + method: Callable[..., Iterable[RenderableType]] + ) -> Callable[..., Group]: + """Convert a method that returns an iterable of renderables in to a Group.""" + + @wraps(method) + def _replace(*args: Any, **kwargs: Any) -> Group: + renderables = method(*args, **kwargs) + return Group(*renderables, fit=fit) + + return _replace + + return decorator + + +def _is_jupyter() -> bool: # pragma: no cover + """Check if we're running in a Jupyter notebook.""" + try: + get_ipython # type: ignore + except NameError: + return False + ipython = get_ipython() # type: ignore + shell = ipython.__class__.__name__ + if "google.colab" in str(ipython.__class__) or shell == "ZMQInteractiveShell": + return True # Jupyter notebook or qtconsole + elif shell == "TerminalInteractiveShell": + return False # Terminal running IPython + else: + return False # Other type (?) + + +COLOR_SYSTEMS = { + "standard": ColorSystem.STANDARD, + "256": ColorSystem.EIGHT_BIT, + "truecolor": ColorSystem.TRUECOLOR, + "windows": ColorSystem.WINDOWS, +} + + +_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()} + + +@dataclass +class ConsoleThreadLocals(threading.local): + """Thread local values for Console context.""" + + theme_stack: ThemeStack + buffer: List[Segment] = field(default_factory=list) + buffer_index: int = 0 + + +class RenderHook(ABC): + """Provides hooks in to the render process.""" + + @abstractmethod + def process_renderables( + self, renderables: List[ConsoleRenderable] + ) -> List[ConsoleRenderable]: + """Called with a list of objects to render. + + This method can return a new list of renderables, or modify and return the same list. + + Args: + renderables (List[ConsoleRenderable]): A number of renderable objects. + + Returns: + List[ConsoleRenderable]: A replacement list of renderables. + """ + + +_windows_console_features: Optional["WindowsConsoleFeatures"] = None + + +def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover + global _windows_console_features + if _windows_console_features is not None: + return _windows_console_features + from ._windows import get_windows_console_features + + _windows_console_features = get_windows_console_features() + return _windows_console_features + + +def detect_legacy_windows() -> bool: + """Detect legacy Windows.""" + return WINDOWS and not get_windows_console_features().vt + + +if detect_legacy_windows(): # pragma: no cover + from pip._vendor.colorama import init + + init(strip=False) + + +class Console: + """A high level console interface. + + Args: + color_system (str, optional): The color system supported by your terminal, + either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect. + force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None. + force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None. + force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None. + soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False. + theme (Theme, optional): An optional style theme object, or ``None`` for default theme. + stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False. + file (IO, optional): A file object where the console should write to. Defaults to stdout. + quiet (bool, Optional): Boolean to suppress all output. Defaults to False. + width (int, optional): The width of the terminal. Leave as default to auto-detect width. + height (int, optional): The height of the terminal. Leave as default to auto-detect height. + style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None. + no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None. + tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8. + record (bool, optional): Boolean to enable recording of terminal output, + required to call :meth:`export_html` and :meth:`export_text`. Defaults to False. + markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True. + emoji (bool, optional): Enable emoji code. Defaults to True. + emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. + highlight (bool, optional): Enable automatic highlighting. Defaults to True. + log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True. + log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True. + log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ". + highlighter (HighlighterType, optional): Default highlighter. + legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``. + safe_box (bool, optional): Restrict box options that don't render on legacy Windows. + get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log), + or None for datetime.now. + get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic. + """ + + _environ: Mapping[str, str] = os.environ + + def __init__( + self, + *, + color_system: Optional[ + Literal["auto", "standard", "256", "truecolor", "windows"] + ] = "auto", + force_terminal: Optional[bool] = None, + force_jupyter: Optional[bool] = None, + force_interactive: Optional[bool] = None, + soft_wrap: bool = False, + theme: Optional[Theme] = None, + stderr: bool = False, + file: Optional[IO[str]] = None, + quiet: bool = False, + width: Optional[int] = None, + height: Optional[int] = None, + style: Optional[StyleType] = None, + no_color: Optional[bool] = None, + tab_size: int = 8, + record: bool = False, + markup: bool = True, + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, + highlight: bool = True, + log_time: bool = True, + log_path: bool = True, + log_time_format: Union[str, FormatTimeCallable] = "[%X]", + highlighter: Optional["HighlighterType"] = ReprHighlighter(), + legacy_windows: Optional[bool] = None, + safe_box: bool = True, + get_datetime: Optional[Callable[[], datetime]] = None, + get_time: Optional[Callable[[], float]] = None, + _environ: Optional[Mapping[str, str]] = None, + ): + # Copy of os.environ allows us to replace it for testing + if _environ is not None: + self._environ = _environ + + self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter + if self.is_jupyter: + width = width or 93 + height = height or 100 + + self.soft_wrap = soft_wrap + self._width = width + self._height = height + self.tab_size = tab_size + self.record = record + self._markup = markup + self._emoji = emoji + self._emoji_variant: Optional[EmojiVariant] = emoji_variant + self._highlight = highlight + self.legacy_windows: bool = ( + (detect_legacy_windows() and not self.is_jupyter) + if legacy_windows is None + else legacy_windows + ) + if width is None: + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) - self.legacy_windows + if height is None: + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + self.soft_wrap = soft_wrap + self._width = width + self._height = height + + self._color_system: Optional[ColorSystem] + self._force_terminal = force_terminal + self._file = file + self.quiet = quiet + self.stderr = stderr + + if color_system is None: + self._color_system = None + elif color_system == "auto": + self._color_system = self._detect_color_system() + else: + self._color_system = COLOR_SYSTEMS[color_system] + + self._lock = threading.RLock() + self._log_render = LogRender( + show_time=log_time, + show_path=log_path, + time_format=log_time_format, + ) + self.highlighter: HighlighterType = highlighter or _null_highlighter + self.safe_box = safe_box + self.get_datetime = get_datetime or datetime.now + self.get_time = get_time or monotonic + self.style = style + self.no_color = ( + no_color if no_color is not None else "NO_COLOR" in self._environ + ) + self.is_interactive = ( + (self.is_terminal and not self.is_dumb_terminal) + if force_interactive is None + else force_interactive + ) + + self._record_buffer_lock = threading.RLock() + self._thread_locals = ConsoleThreadLocals( + theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme) + ) + self._record_buffer: List[Segment] = [] + self._render_hooks: List[RenderHook] = [] + self._live: Optional["Live"] = None + self._is_alt_screen = False + + def __repr__(self) -> str: + return f"" + + @property + def file(self) -> IO[str]: + """Get the file object to write to.""" + file = self._file or (sys.stderr if self.stderr else sys.stdout) + file = getattr(file, "rich_proxied_file", file) + return file + + @file.setter + def file(self, new_file: IO[str]) -> None: + """Set a new file object.""" + self._file = new_file + + @property + def _buffer(self) -> List[Segment]: + """Get a thread local buffer.""" + return self._thread_locals.buffer + + @property + def _buffer_index(self) -> int: + """Get a thread local buffer.""" + return self._thread_locals.buffer_index + + @_buffer_index.setter + def _buffer_index(self, value: int) -> None: + self._thread_locals.buffer_index = value + + @property + def _theme_stack(self) -> ThemeStack: + """Get the thread local theme stack.""" + return self._thread_locals.theme_stack + + def _detect_color_system(self) -> Optional[ColorSystem]: + """Detect color system from env vars.""" + if self.is_jupyter: + return ColorSystem.TRUECOLOR + if not self.is_terminal or self.is_dumb_terminal: + return None + if WINDOWS: # pragma: no cover + if self.legacy_windows: # pragma: no cover + return ColorSystem.WINDOWS + windows_console_features = get_windows_console_features() + return ( + ColorSystem.TRUECOLOR + if windows_console_features.truecolor + else ColorSystem.EIGHT_BIT + ) + else: + color_term = self._environ.get("COLORTERM", "").strip().lower() + if color_term in ("truecolor", "24bit"): + return ColorSystem.TRUECOLOR + term = self._environ.get("TERM", "").strip().lower() + _term_name, _hyphen, colors = term.rpartition("-") + color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD) + return color_system + + def _enter_buffer(self) -> None: + """Enter in to a buffer context, and buffer all output.""" + self._buffer_index += 1 + + def _exit_buffer(self) -> None: + """Leave buffer context, and render content if required.""" + self._buffer_index -= 1 + self._check_buffer() + + def set_live(self, live: "Live") -> None: + """Set Live instance. Used by Live context manager. + + Args: + live (Live): Live instance using this Console. + + Raises: + errors.LiveError: If this Console has a Live context currently active. + """ + with self._lock: + if self._live is not None: + raise errors.LiveError("Only one live display may be active at once") + self._live = live + + def clear_live(self) -> None: + """Clear the Live instance.""" + with self._lock: + self._live = None + + def push_render_hook(self, hook: RenderHook) -> None: + """Add a new render hook to the stack. + + Args: + hook (RenderHook): Render hook instance. + """ + with self._lock: + self._render_hooks.append(hook) + + def pop_render_hook(self) -> None: + """Pop the last renderhook from the stack.""" + with self._lock: + self._render_hooks.pop() + + def __enter__(self) -> "Console": + """Own context manager to enter buffer context.""" + self._enter_buffer() + return self + + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: + """Exit buffer context.""" + self._exit_buffer() + + def begin_capture(self) -> None: + """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output.""" + self._enter_buffer() + + def end_capture(self) -> str: + """End capture mode and return captured string. + + Returns: + str: Console output. + """ + render_result = self._render_buffer(self._buffer) + del self._buffer[:] + self._exit_buffer() + return render_result + + def push_theme(self, theme: Theme, *, inherit: bool = True) -> None: + """Push a new theme on to the top of the stack, replacing the styles from the previous theme. + Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather + than calling this method directly. + + Args: + theme (Theme): A theme instance. + inherit (bool, optional): Inherit existing styles. Defaults to True. + """ + self._theme_stack.push_theme(theme, inherit=inherit) + + def pop_theme(self) -> None: + """Remove theme from top of stack, restoring previous theme.""" + self._theme_stack.pop_theme() + + def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext: + """Use a different theme for the duration of the context manager. + + Args: + theme (Theme): Theme instance to user. + inherit (bool, optional): Inherit existing console styles. Defaults to True. + + Returns: + ThemeContext: [description] + """ + return ThemeContext(self, theme, inherit) + + @property + def color_system(self) -> Optional[str]: + """Get color system string. + + Returns: + Optional[str]: "standard", "256" or "truecolor". + """ + + if self._color_system is not None: + return _COLOR_SYSTEMS_NAMES[self._color_system] + else: + return None + + @property + def encoding(self) -> str: + """Get the encoding of the console file, e.g. ``"utf-8"``. + + Returns: + str: A standard encoding string. + """ + return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower() + + @property + def is_terminal(self) -> bool: + """Check if the console is writing to a terminal. + + Returns: + bool: True if the console writing to a device capable of + understanding terminal codes, otherwise False. + """ + if self._force_terminal is not None: + return self._force_terminal + isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None) + try: + return False if isatty is None else isatty() + except ValueError: + # in some situation (at the end of a pytest run for example) isatty() can raise + # ValueError: I/O operation on closed file + # return False because we aren't in a terminal anymore + return False + + @property + def is_dumb_terminal(self) -> bool: + """Detect dumb terminal. + + Returns: + bool: True if writing to a dumb terminal, otherwise False. + + """ + _term = self._environ.get("TERM", "") + is_dumb = _term.lower() in ("dumb", "unknown") + return self.is_terminal and is_dumb + + @property + def options(self) -> ConsoleOptions: + """Get default console options.""" + return ConsoleOptions( + max_height=self.size.height, + size=self.size, + legacy_windows=self.legacy_windows, + min_width=1, + max_width=self.width, + encoding=self.encoding, + is_terminal=self.is_terminal, + ) + + @property + def size(self) -> ConsoleDimensions: + """Get the size of the console. + + Returns: + ConsoleDimensions: A named tuple containing the dimensions. + """ + + if self._width is not None and self._height is not None: + return ConsoleDimensions(self._width - self.legacy_windows, self._height) + + if self.is_dumb_terminal: + return ConsoleDimensions(80, 25) + + width: Optional[int] = None + height: Optional[int] = None + + if WINDOWS: # pragma: no cover + try: + width, height = os.get_terminal_size() + except OSError: # Probably not a terminal + pass + else: + try: + width, height = os.get_terminal_size(sys.__stdin__.fileno()) + except (AttributeError, ValueError, OSError): + try: + width, height = os.get_terminal_size(sys.__stdout__.fileno()) + except (AttributeError, ValueError, OSError): + pass + + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + # get_terminal_size can report 0, 0 if run from pseudo-terminal + width = width or 80 + height = height or 25 + return ConsoleDimensions( + width - self.legacy_windows if self._width is None else self._width, + height if self._height is None else self._height, + ) + + @size.setter + def size(self, new_size: Tuple[int, int]) -> None: + """Set a new size for the terminal. + + Args: + new_size (Tuple[int, int]): New width and height. + """ + width, height = new_size + self._width = width + self._height = height + + @property + def width(self) -> int: + """Get the width of the console. + + Returns: + int: The width (in characters) of the console. + """ + return self.size.width + + @width.setter + def width(self, width: int) -> None: + """Set width. + + Args: + width (int): New width. + """ + self._width = width + + @property + def height(self) -> int: + """Get the height of the console. + + Returns: + int: The height (in lines) of the console. + """ + return self.size.height + + @height.setter + def height(self, height: int) -> None: + """Set height. + + Args: + height (int): new height. + """ + self._height = height + + def bell(self) -> None: + """Play a 'bell' sound (if supported by the terminal).""" + self.control(Control.bell()) + + def capture(self) -> Capture: + """A context manager to *capture* the result of print() or log() in a string, + rather than writing it to the console. + + Example: + >>> from rich.console import Console + >>> console = Console() + >>> with console.capture() as capture: + ... console.print("[bold magenta]Hello World[/]") + >>> print(capture.get()) + + Returns: + Capture: Context manager with disables writing to the terminal. + """ + capture = Capture(self) + return capture + + def pager( + self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False + ) -> PagerContext: + """A context manager to display anything printed within a "pager". The pager application + is defined by the system and will typically support at least pressing a key to scroll. + + Args: + pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None. + styles (bool, optional): Show styles in pager. Defaults to False. + links (bool, optional): Show links in pager. Defaults to False. + + Example: + >>> from rich.console import Console + >>> from rich.__main__ import make_test_card + >>> console = Console() + >>> with console.pager(): + console.print(make_test_card()) + + Returns: + PagerContext: A context manager. + """ + return PagerContext(self, pager=pager, styles=styles, links=links) + + def line(self, count: int = 1) -> None: + """Write new line(s). + + Args: + count (int, optional): Number of new lines. Defaults to 1. + """ + + assert count >= 0, "count must be >= 0" + self.print(NewLine(count)) + + def clear(self, home: bool = True) -> None: + """Clear the screen. + + Args: + home (bool, optional): Also move the cursor to 'home' position. Defaults to True. + """ + if home: + self.control(Control.clear(), Control.home()) + else: + self.control(Control.clear()) + + def status( + self, + status: RenderableType, + *, + spinner: str = "dots", + spinner_style: str = "status.spinner", + speed: float = 1.0, + refresh_per_second: float = 12.5, + ) -> "Status": + """Display a status and spinner. + + Args: + status (RenderableType): A status renderable (str or Text typically). + spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots". + spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner". + speed (float, optional): Speed factor for spinner animation. Defaults to 1.0. + refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5. + + Returns: + Status: A Status object that may be used as a context manager. + """ + from .status import Status + + status_renderable = Status( + status, + console=self, + spinner=spinner, + spinner_style=spinner_style, + speed=speed, + refresh_per_second=refresh_per_second, + ) + return status_renderable + + def show_cursor(self, show: bool = True) -> bool: + """Show or hide the cursor. + + Args: + show (bool, optional): Set visibility of the cursor. + """ + if self.is_terminal and not self.legacy_windows: + self.control(Control.show_cursor(show)) + return True + return False + + def set_alt_screen(self, enable: bool = True) -> bool: + """Enables alternative screen mode. + + Note, if you enable this mode, you should ensure that is disabled before + the application exits. See :meth:`~rich.Console.screen` for a context manager + that handles this for you. + + Args: + enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True. + + Returns: + bool: True if the control codes were written. + + """ + changed = False + if self.is_terminal and not self.legacy_windows: + self.control(Control.alt_screen(enable)) + changed = True + self._is_alt_screen = enable + return changed + + @property + def is_alt_screen(self) -> bool: + """Check if the alt screen was enabled. + + Returns: + bool: True if the alt screen was enabled, otherwise False. + """ + return self._is_alt_screen + + def screen( + self, hide_cursor: bool = True, style: Optional[StyleType] = None + ) -> "ScreenContext": + """Context manager to enable and disable 'alternative screen' mode. + + Args: + hide_cursor (bool, optional): Also hide the cursor. Defaults to False. + style (Style, optional): Optional style for screen. Defaults to None. + + Returns: + ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit. + """ + return ScreenContext(self, hide_cursor=hide_cursor, style=style or "") + + def measure( + self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None + ) -> Measurement: + """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains + information regarding the number of characters required to print the renderable. + + Args: + renderable (RenderableType): Any renderable or string. + options (Optional[ConsoleOptions], optional): Options to use when measuring, or None + to use default options. Defaults to None. + + Returns: + Measurement: A measurement of the renderable. + """ + measurement = Measurement.get(self, options or self.options, renderable) + return measurement + + def render( + self, renderable: RenderableType, options: Optional[ConsoleOptions] = None + ) -> Iterable[Segment]: + """Render an object in to an iterable of `Segment` instances. + + This method contains the logic for rendering objects with the console protocol. + You are unlikely to need to use it directly, unless you are extending the library. + + Args: + renderable (RenderableType): An object supporting the console protocol, or + an object that may be converted to a string. + options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None. + + Returns: + Iterable[Segment]: An iterable of segments that may be rendered. + """ + + _options = options or self.options + if _options.max_width < 1: + # No space to render anything. This prevents potential recursion errors. + return + render_iterable: RenderResult + + renderable = rich_cast(renderable) + if hasattr(renderable, "__rich_console__") and not isclass(renderable): + render_iterable = renderable.__rich_console__(self, _options) # type: ignore + elif isinstance(renderable, str): + text_renderable = self.render_str( + renderable, highlight=_options.highlight, markup=_options.markup + ) + render_iterable = text_renderable.__rich_console__(self, _options) + else: + raise errors.NotRenderableError( + f"Unable to render {renderable!r}; " + "A str, Segment or object with __rich_console__ method is required" + ) + + try: + iter_render = iter(render_iterable) + except TypeError: + raise errors.NotRenderableError( + f"object {render_iterable!r} is not renderable" + ) + _Segment = Segment + for render_output in iter_render: + if isinstance(render_output, _Segment): + yield render_output + else: + yield from self.render(render_output, _options) + + def render_lines( + self, + renderable: RenderableType, + options: Optional[ConsoleOptions] = None, + *, + style: Optional[Style] = None, + pad: bool = True, + new_lines: bool = False, + ) -> List[List[Segment]]: + """Render objects in to a list of lines. + + The output of render_lines is useful when further formatting of rendered console text + is required, such as the Panel class which draws a border around any renderable object. + + Args: + renderable (RenderableType): Any object renderable in the console. + options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``. + style (Style, optional): Optional style to apply to renderables. Defaults to ``None``. + pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``. + new_lines (bool, optional): Include "\n" characters at end of lines. + + Returns: + List[List[Segment]]: A list of lines, where a line is a list of Segment objects. + """ + with self._lock: + render_options = options or self.options + _rendered = self.render(renderable, render_options) + if style: + _rendered = Segment.apply_style(_rendered, style) + lines = list( + islice( + Segment.split_and_crop_lines( + _rendered, + render_options.max_width, + include_new_lines=new_lines, + pad=pad, + ), + None, + render_options.height, + ) + ) + if render_options.height is not None: + extra_lines = render_options.height - len(lines) + if extra_lines > 0: + pad_line = [ + [Segment(" " * render_options.max_width, style), Segment("\n")] + if new_lines + else [Segment(" " * render_options.max_width, style)] + ] + lines.extend(pad_line * extra_lines) + + return lines + + def render_str( + self, + text: str, + *, + style: Union[str, Style] = "", + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + highlighter: Optional[HighlighterType] = None, + ) -> "Text": + """Convert a string to a Text instance. This is is called automatically if + you print or log a string. + + Args: + text (str): Text to render. + style (Union[str, Style], optional): Style to apply to rendered text. + justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default. + highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default. + highlighter (HighlighterType, optional): Optional highlighter to apply. + Returns: + ConsoleRenderable: Renderable object. + + """ + emoji_enabled = emoji or (emoji is None and self._emoji) + markup_enabled = markup or (markup is None and self._markup) + highlight_enabled = highlight or (highlight is None and self._highlight) + + if markup_enabled: + rich_text = render_markup( + text, + style=style, + emoji=emoji_enabled, + emoji_variant=self._emoji_variant, + ) + rich_text.justify = justify + rich_text.overflow = overflow + else: + rich_text = Text( + _emoji_replace(text, default_variant=self._emoji_variant) + if emoji_enabled + else text, + justify=justify, + overflow=overflow, + style=style, + ) + + _highlighter = (highlighter or self.highlighter) if highlight_enabled else None + if _highlighter is not None: + highlight_text = _highlighter(str(rich_text)) + highlight_text.copy_styles(rich_text) + return highlight_text + + return rich_text + + def get_style( + self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None + ) -> Style: + """Get a Style instance by it's theme name or parse a definition. + + Args: + name (str): The name of a style or a style definition. + + Returns: + Style: A Style object. + + Raises: + MissingStyle: If no style could be parsed from name. + + """ + if isinstance(name, Style): + return name + + try: + style = self._theme_stack.get(name) + if style is None: + style = Style.parse(name) + return style.copy() if style.link else style + except errors.StyleSyntaxError as error: + if default is not None: + return self.get_style(default) + raise errors.MissingStyle( + f"Failed to get style {name!r}; {error}" + ) from None + + def _collect_renderables( + self, + objects: Iterable[Any], + sep: str, + end: str, + *, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + ) -> List[ConsoleRenderable]: + """Combine a number of renderables and text into one renderable. + + Args: + objects (Iterable[Any]): Anything that Rich can render. + sep (str): String to write between print data. + end (str): String to write at end of print data. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. + + Returns: + List[ConsoleRenderable]: A list of things to render. + """ + renderables: List[ConsoleRenderable] = [] + _append = renderables.append + text: List[Text] = [] + append_text = text.append + + append = _append + if justify in ("left", "center", "right"): + + def align_append(renderable: RenderableType) -> None: + _append(Align(renderable, cast(AlignMethod, justify))) + + append = align_append + + _highlighter: HighlighterType = _null_highlighter + if highlight or (highlight is None and self._highlight): + _highlighter = self.highlighter + + def check_text() -> None: + if text: + sep_text = Text(sep, justify=justify, end=end) + append(sep_text.join(text)) + del text[:] + + for renderable in objects: + renderable = rich_cast(renderable) + if isinstance(renderable, str): + append_text( + self.render_str( + renderable, emoji=emoji, markup=markup, highlighter=_highlighter + ) + ) + elif isinstance(renderable, Text): + append_text(renderable) + elif isinstance(renderable, ConsoleRenderable): + check_text() + append(renderable) + elif is_expandable(renderable): + check_text() + append(Pretty(renderable, highlighter=_highlighter)) + else: + append_text(_highlighter(str(renderable))) + + check_text() + + if self.style is not None: + style = self.get_style(self.style) + renderables = [Styled(renderable, style) for renderable in renderables] + + return renderables + + def rule( + self, + title: TextType = "", + *, + characters: str = "─", + style: Union[str, Style] = "rule.line", + align: AlignMethod = "center", + ) -> None: + """Draw a line with optional centered title. + + Args: + title (str, optional): Text to render over the rule. Defaults to "". + characters (str, optional): Character(s) to form the line. Defaults to "─". + style (str, optional): Style of line. Defaults to "rule.line". + align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". + """ + from .rule import Rule + + rule = Rule(title=title, characters=characters, style=style, align=align) + self.print(rule) + + def control(self, *control: Control) -> None: + """Insert non-printing control codes. + + Args: + control_codes (str): Control codes, such as those that may move the cursor. + """ + if not self.is_dumb_terminal: + with self: + self._buffer.extend(_control.segment for _control in control) + + def out( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + highlight: Optional[bool] = None, + ) -> None: + """Output to the terminal. This is a low-level way of writing to the terminal which unlike + :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will + optionally apply highlighting and a basic style. + + Args: + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use + console default. Defaults to ``None``. + """ + raw_output: str = sep.join(str(_object) for _object in objects) + self.print( + raw_output, + style=style, + highlight=highlight, + emoji=False, + markup=False, + no_wrap=True, + overflow="ignore", + crop=False, + end=end, + ) + + def print( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + no_wrap: Optional[bool] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + width: Optional[int] = None, + height: Optional[int] = None, + crop: bool = True, + soft_wrap: Optional[bool] = None, + new_line_start: bool = False, + ) -> None: + """Print to the console. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``. + overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None. + no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``. + width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``. + crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True. + soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for + Console default. Defaults to ``None``. + new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``. + """ + if not objects: + objects = (NewLine(),) + + if soft_wrap is None: + soft_wrap = self.soft_wrap + if soft_wrap: + if no_wrap is None: + no_wrap = True + if overflow is None: + overflow = "ignore" + crop = False + render_hooks = self._render_hooks[:] + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + render_options = self.options.update( + justify=justify, + overflow=overflow, + width=min(width, self.width) if width is not None else NO_CHANGE, + height=height, + no_wrap=no_wrap, + markup=markup, + highlight=highlight, + ) + + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + if style is None: + for renderable in renderables: + extend(render(renderable, render_options)) + else: + for renderable in renderables: + extend( + Segment.apply_style( + render(renderable, render_options), self.get_style(style) + ) + ) + if new_line_start: + if ( + len("".join(segment.text for segment in new_segments).splitlines()) + > 1 + ): + new_segments.insert(0, Segment.line()) + if crop: + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + else: + self._buffer.extend(new_segments) + + def print_json( + self, + json: Optional[str] = None, + *, + data: Any = None, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + ) -> None: + """Pretty prints JSON. Output will be valid JSON. + + Args: + json (Optional[str]): A string containing JSON. + data (Any): If json is not supplied, then encode this data. + indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2. + highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + """ + from pip._vendor.rich.json import JSON + + if json is None: + json_renderable = JSON.from_data( + data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + else: + if not isinstance(json, str): + raise TypeError( + f"json must be str. Did you mean print_json(data={json!r}) ?" + ) + json_renderable = JSON( + json, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + self.print(json_renderable, soft_wrap=True) + + def update_screen( + self, + renderable: RenderableType, + *, + region: Optional[Region] = None, + options: Optional[ConsoleOptions] = None, + ) -> None: + """Update the screen at a given offset. + + Args: + renderable (RenderableType): A Rich renderable. + region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None. + x (int, optional): x offset. Defaults to 0. + y (int, optional): y offset. Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + render_options = options or self.options + if region is None: + x = y = 0 + render_options = render_options.update_dimensions( + render_options.max_width, render_options.height or self.height + ) + else: + x, y, width, height = region + render_options = render_options.update_dimensions(width, height) + + lines = self.render_lines(renderable, options=render_options) + self.update_screen_lines(lines, x, y) + + def update_screen_lines( + self, lines: List[List[Segment]], x: int = 0, y: int = 0 + ) -> None: + """Update lines of the screen at a given offset. + + Args: + lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`). + x (int, optional): x offset (column no). Defaults to 0. + y (int, optional): y offset (column no). Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + screen_update = ScreenUpdate(lines, x, y) + segments = self.render(screen_update) + self._buffer.extend(segments) + self._check_buffer() + + def print_exception( + self, + *, + width: Optional[int] = 100, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ) -> None: + """Prints a rich render of the last exception and traceback. + + Args: + width (Optional[int], optional): Number of characters used to render code. Defaults to 88. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + """ + from .traceback import Traceback + + traceback = Traceback( + width=width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + suppress=suppress, + max_frames=max_frames, + ) + self.print(traceback) + + @staticmethod + def _caller_frame_info( + offset: int, + currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe, + ) -> Tuple[str, int, Dict[str, Any]]: + """Get caller frame information. + + Args: + offset (int): the caller offset within the current frame stack. + currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to + retrieve the current frame. Defaults to ``inspect.currentframe``. + + Returns: + Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and + the dictionary of local variables associated with the caller frame. + + Raises: + RuntimeError: If the stack offset is invalid. + """ + # Ignore the frame of this local helper + offset += 1 + + frame = currentframe() + if frame is not None: + # Use the faster currentframe where implemented + while offset and frame: + frame = frame.f_back + offset -= 1 + assert frame is not None + return frame.f_code.co_filename, frame.f_lineno, frame.f_locals + else: + # Fallback to the slower stack + frame_info = inspect.stack()[offset] + return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals + + def log( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + log_locals: bool = False, + _stack_offset: int = 1, + ) -> None: + """Log rich content to the terminal. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None. + log_locals (bool, optional): Boolean to enable logging of locals where ``log()`` + was called. Defaults to False. + _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1. + """ + if not objects: + objects = (NewLine(),) + + render_hooks = self._render_hooks[:] + + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + if style is not None: + renderables = [Styled(renderable, style) for renderable in renderables] + + filename, line_no, locals = self._caller_frame_info(_stack_offset) + link_path = None if filename.startswith("<") else os.path.abspath(filename) + path = filename.rpartition(os.sep)[-1] + if log_locals: + locals_map = { + key: value + for key, value in locals.items() + if not key.startswith("__") + } + renderables.append(render_scope(locals_map, title="[i]locals")) + + renderables = [ + self._log_render( + self, + renderables, + log_time=self.get_datetime(), + path=path, + line_no=line_no, + link_path=link_path, + ) + ] + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + render_options = self.options + for renderable in renderables: + extend(render(renderable, render_options)) + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + + def _check_buffer(self) -> None: + """Check if the buffer may be rendered.""" + if self.quiet: + del self._buffer[:] + return + with self._lock: + if self._buffer_index == 0: + if self.is_jupyter: # pragma: no cover + from .jupyter import display + + display(self._buffer, self._render_buffer(self._buffer[:])) + del self._buffer[:] + else: + text = self._render_buffer(self._buffer[:]) + del self._buffer[:] + if text: + try: + if WINDOWS: # pragma: no cover + # https://bugs.python.org/issue37871 + write = self.file.write + for line in text.splitlines(True): + write(line) + else: + self.file.write(text) + self.file.flush() + except UnicodeEncodeError as error: + error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" + raise + + def _render_buffer(self, buffer: Iterable[Segment]) -> str: + """Render buffered output, and clear buffer.""" + output: List[str] = [] + append = output.append + color_system = self._color_system + legacy_windows = self.legacy_windows + if self.record: + with self._record_buffer_lock: + self._record_buffer.extend(buffer) + not_terminal = not self.is_terminal + if self.no_color and color_system: + buffer = Segment.remove_color(buffer) + for text, style, control in buffer: + if style: + append( + style.render( + text, + color_system=color_system, + legacy_windows=legacy_windows, + ) + ) + elif not (not_terminal and control): + append(text) + + rendered = "".join(output) + return rendered + + def input( + self, + prompt: TextType = "", + *, + markup: bool = True, + emoji: bool = True, + password: bool = False, + stream: Optional[TextIO] = None, + ) -> str: + """Displays a prompt and waits for input from the user. The prompt may contain color / style. + + It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded. + + Args: + prompt (Union[str, Text]): Text to render in the prompt. + markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True. + emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True. + password: (bool, optional): Hide typed text. Defaults to False. + stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None. + + Returns: + str: Text read from stdin. + """ + prompt_str = "" + if prompt: + with self.capture() as capture: + self.print(prompt, markup=markup, emoji=emoji, end="") + prompt_str = capture.get() + if self.legacy_windows: + # Legacy windows doesn't like ANSI codes in getpass or input (colorama bug)? + self.file.write(prompt_str) + prompt_str = "" + if password: + result = getpass(prompt_str, stream=stream) + else: + if stream: + self.file.write(prompt_str) + result = stream.readline() + else: + result = input(prompt_str) + return result + + def export_text(self, *, clear: bool = True, styles: bool = False) -> str: + """Generate text from console contents (requires record=True argument in constructor). + + Args: + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text. + Defaults to ``False``. + + Returns: + str: String containing console contents. + + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + + with self._record_buffer_lock: + if styles: + text = "".join( + (style.render(text) if style else text) + for text, style, _ in self._record_buffer + ) + else: + text = "".join( + segment.text + for segment in self._record_buffer + if not segment.control + ) + if clear: + del self._record_buffer[:] + return text + + def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None: + """Generate text from console and save to a given location (requires record=True argument in constructor). + + Args: + path (str): Path to write text files. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text. + Defaults to ``False``. + + """ + text = self.export_text(clear=clear, styles=styles) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(text) + + def export_html( + self, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: Optional[str] = None, + inline_styles: bool = False, + ) -> str: + """Generate HTML from console contents (requires record=True argument in constructor). + + Args: + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML, should contain {foreground} + {background} and {code}. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + Returns: + str: String containing console contents as HTML. + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + fragments: List[str] = [] + append = fragments.append + _theme = theme or DEFAULT_TERMINAL_THEME + stylesheet = "" + + render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format + + with self._record_buffer_lock: + if inline_styles: + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + if style.link: + text = f'{text}' + text = f'{text}' if rule else text + append(text) + else: + styles: Dict[str, int] = {} + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + style_number = styles.setdefault(rule, len(styles) + 1) + if style.link: + text = f'{text}' + else: + text = f'{text}' + append(text) + stylesheet_rules: List[str] = [] + stylesheet_append = stylesheet_rules.append + for style_rule, style_number in styles.items(): + if style_rule: + stylesheet_append(f".r{style_number} {{{style_rule}}}") + stylesheet = "\n".join(stylesheet_rules) + + rendered_code = render_code_format.format( + code="".join(fragments), + stylesheet=stylesheet, + foreground=_theme.foreground_color.hex, + background=_theme.background_color.hex, + ) + if clear: + del self._record_buffer[:] + return rendered_code + + def save_html( + self, + path: str, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_HTML_FORMAT, + inline_styles: bool = False, + ) -> None: + """Generate HTML from console contents and write to a file (requires record=True argument in constructor). + + Args: + path (str): Path to write html file. + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML, should contain {foreground} + {background} and {code}. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + """ + html = self.export_html( + theme=theme, + clear=clear, + code_format=code_format, + inline_styles=inline_styles, + ) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(html) + + +if __name__ == "__main__": # pragma: no cover + console = Console() + + console.log( + "JSONRPC [i]request[/i]", + 5, + 1.3, + True, + False, + None, + { + "jsonrpc": "2.0", + "method": "subtract", + "params": {"minuend": 42, "subtrahend": 23}, + "id": 3, + }, + ) + + console.log("Hello, World!", "{'a': 1}", repr(console)) + + console.print( + { + "name": None, + "empty": [], + "quiz": { + "sport": { + "answered": True, + "q1": { + "question": "Which one is correct team name in NBA?", + "options": [ + "New York Bulls", + "Los Angeles Kings", + "Golden State Warriors", + "Huston Rocket", + ], + "answer": "Huston Rocket", + }, + }, + "maths": { + "answered": False, + "q1": { + "question": "5 + 7 = ?", + "options": [10, 11, 12, 13], + "answer": 12, + }, + "q2": { + "question": "12 - 8 = ?", + "options": [1, 2, 3, 4], + "answer": 4, + }, + }, + }, + } + ) + console.log("foo") diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/constrain.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/constrain.py new file mode 100644 index 0000000..65fdf56 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/constrain.py @@ -0,0 +1,37 @@ +from typing import Optional, TYPE_CHECKING + +from .jupyter import JupyterMixin +from .measure import Measurement + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + + +class Constrain(JupyterMixin): + """Constrain the width of a renderable to a given number of characters. + + Args: + renderable (RenderableType): A renderable object. + width (int, optional): The maximum width (in characters) to render. Defaults to 80. + """ + + def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None: + self.renderable = renderable + self.width = width + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.width is None: + yield self.renderable + else: + child_options = options.update_width(min(self.width, options.max_width)) + yield from console.render(self.renderable, child_options) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.width is not None: + options = options.update_width(self.width) + measurement = Measurement.get(console, options, self.renderable) + return measurement diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/containers.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/containers.py new file mode 100644 index 0000000..e29cf36 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/containers.py @@ -0,0 +1,167 @@ +from itertools import zip_longest +from typing import ( + Iterator, + Iterable, + List, + Optional, + Union, + overload, + TypeVar, + TYPE_CHECKING, +) + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + JustifyMethod, + OverflowMethod, + RenderResult, + RenderableType, + ) + from .text import Text + +from .cells import cell_len +from .measure import Measurement + +T = TypeVar("T") + + +class Renderables: + """A list subclass which renders its contents to the console.""" + + def __init__( + self, renderables: Optional[Iterable["RenderableType"]] = None + ) -> None: + self._renderables: List["RenderableType"] = ( + list(renderables) if renderables is not None else [] + ) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._renderables + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + dimensions = [ + Measurement.get(console, options, renderable) + for renderable in self._renderables + ] + if not dimensions: + return Measurement(1, 1) + _min = max(dimension.minimum for dimension in dimensions) + _max = max(dimension.maximum for dimension in dimensions) + return Measurement(_min, _max) + + def append(self, renderable: "RenderableType") -> None: + self._renderables.append(renderable) + + def __iter__(self) -> Iterable["RenderableType"]: + return iter(self._renderables) + + +class Lines: + """A list subclass which can render to the console.""" + + def __init__(self, lines: Iterable["Text"] = ()) -> None: + self._lines: List["Text"] = list(lines) + + def __repr__(self) -> str: + return f"Lines({self._lines!r})" + + def __iter__(self) -> Iterator["Text"]: + return iter(self._lines) + + @overload + def __getitem__(self, index: int) -> "Text": + ... + + @overload + def __getitem__(self, index: slice) -> List["Text"]: + ... + + def __getitem__(self, index: Union[slice, int]) -> Union["Text", List["Text"]]: + return self._lines[index] + + def __setitem__(self, index: int, value: "Text") -> "Lines": + self._lines[index] = value + return self + + def __len__(self) -> int: + return self._lines.__len__() + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._lines + + def append(self, line: "Text") -> None: + self._lines.append(line) + + def extend(self, lines: Iterable["Text"]) -> None: + self._lines.extend(lines) + + def pop(self, index: int = -1) -> "Text": + return self._lines.pop(index) + + def justify( + self, + console: "Console", + width: int, + justify: "JustifyMethod" = "left", + overflow: "OverflowMethod" = "fold", + ) -> None: + """Justify and overflow text to a given width. + + Args: + console (Console): Console instance. + width (int): Number of characters per line. + justify (str, optional): Default justify method for text: "left", "center", "full" or "right". Defaults to "left". + overflow (str, optional): Default overflow for text: "crop", "fold", or "ellipsis". Defaults to "fold". + + """ + from .text import Text + + if justify == "left": + for line in self._lines: + line.truncate(width, overflow=overflow, pad=True) + elif justify == "center": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left((width - cell_len(line.plain)) // 2) + line.pad_right(width - cell_len(line.plain)) + elif justify == "right": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left(width - cell_len(line.plain)) + elif justify == "full": + for line_index, line in enumerate(self._lines): + if line_index == len(self._lines) - 1: + break + words = line.split(" ") + words_size = sum(cell_len(word.plain) for word in words) + num_spaces = len(words) - 1 + spaces = [1 for _ in range(num_spaces)] + index = 0 + if spaces: + while words_size + num_spaces < width: + spaces[len(spaces) - index - 1] += 1 + num_spaces += 1 + index = (index + 1) % len(spaces) + tokens: List[Text] = [] + for index, (word, next_word) in enumerate( + zip_longest(words, words[1:]) + ): + tokens.append(word) + if index < len(spaces): + style = word.get_style_at_offset(console, -1) + next_style = next_word.get_style_at_offset(console, 0) + space_style = style if style == next_style else line.style + tokens.append(Text(" " * spaces[index], style=space_style)) + self[line_index] = Text("").join(tokens) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/control.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/control.py new file mode 100644 index 0000000..c98d0d7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/control.py @@ -0,0 +1,175 @@ +from typing import Any, Callable, Dict, Iterable, List, TYPE_CHECKING, Union + +from .segment import ControlCode, ControlType, Segment + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + +STRIP_CONTROL_CODES = [ + 8, # Backspace + 11, # Vertical tab + 12, # Form feed + 13, # Carriage return +] +_CONTROL_TRANSLATE = {_codepoint: None for _codepoint in STRIP_CONTROL_CODES} + + +CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = { + ControlType.BELL: lambda: "\x07", + ControlType.CARRIAGE_RETURN: lambda: "\r", + ControlType.HOME: lambda: "\x1b[H", + ControlType.CLEAR: lambda: "\x1b[2J", + ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h", + ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l", + ControlType.SHOW_CURSOR: lambda: "\x1b[?25h", + ControlType.HIDE_CURSOR: lambda: "\x1b[?25l", + ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A", + ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B", + ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C", + ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D", + ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G", + ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K", + ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H", +} + + +class Control: + """A renderable that inserts a control code (non printable but may move cursor). + + Args: + *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a + tuple of ControlType and an integer parameter + """ + + __slots__ = ["segment"] + + def __init__(self, *codes: Union[ControlType, ControlCode]) -> None: + control_codes: List[ControlCode] = [ + (code,) if isinstance(code, ControlType) else code for code in codes + ] + _format_map = CONTROL_CODES_FORMAT + rendered_codes = "".join( + _format_map[code](*parameters) for code, *parameters in control_codes + ) + self.segment = Segment(rendered_codes, None, control_codes) + + @classmethod + def bell(cls) -> "Control": + """Ring the 'bell'.""" + return cls(ControlType.BELL) + + @classmethod + def home(cls) -> "Control": + """Move cursor to 'home' position.""" + return cls(ControlType.HOME) + + @classmethod + def move(cls, x: int = 0, y: int = 0) -> "Control": + """Move cursor relative to current position. + + Args: + x (int): X offset. + y (int): Y offset. + + Returns: + ~Control: Control object. + + """ + + def get_codes() -> Iterable[ControlCode]: + control = ControlType + if x: + yield ( + control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD, + abs(x), + ) + if y: + yield ( + control.CURSOR_DOWN if y > 0 else control.CURSOR_UP, + abs(y), + ) + + control = cls(*get_codes()) + return control + + @classmethod + def move_to_column(cls, x: int, y: int = 0) -> "Control": + """Move to the given column, optionally add offset to row. + + Returns: + x (int): absolute x (column) + y (int): optional y offset (row) + + Returns: + ~Control: Control object. + """ + + return ( + cls( + (ControlType.CURSOR_MOVE_TO_COLUMN, x), + ( + ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP, + abs(y), + ), + ) + if y + else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x)) + ) + + @classmethod + def move_to(cls, x: int, y: int) -> "Control": + """Move cursor to absolute position. + + Args: + x (int): x offset (column) + y (int): y offset (row) + + Returns: + ~Control: Control object. + """ + return cls((ControlType.CURSOR_MOVE_TO, x, y)) + + @classmethod + def clear(cls) -> "Control": + """Clear the screen.""" + return cls(ControlType.CLEAR) + + @classmethod + def show_cursor(cls, show: bool) -> "Control": + """Show or hide the cursor.""" + return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR) + + @classmethod + def alt_screen(cls, enable: bool) -> "Control": + """Enable or disable alt screen.""" + if enable: + return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME) + else: + return cls(ControlType.DISABLE_ALT_SCREEN) + + def __str__(self) -> str: + return self.segment.text + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.segment.text: + yield self.segment + + +def strip_control_codes( + text: str, _translate_table: Dict[int, None] = _CONTROL_TRANSLATE +) -> str: + """Remove control codes from text. + + Args: + text (str): A string possibly contain control codes. + + Returns: + str: String with control codes removed. + """ + return text.translate(_translate_table) + + +if __name__ == "__main__": # pragma: no cover + print(strip_control_codes("hello\rWorld")) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/default_styles.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/default_styles.py new file mode 100644 index 0000000..91ab232 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/default_styles.py @@ -0,0 +1,183 @@ +from typing import Dict + +from .style import Style + + +DEFAULT_STYLES: Dict[str, Style] = { + "none": Style.null(), + "reset": Style( + color="default", + bgcolor="default", + dim=False, + bold=False, + italic=False, + underline=False, + blink=False, + blink2=False, + reverse=False, + conceal=False, + strike=False, + ), + "dim": Style(dim=True), + "bright": Style(dim=False), + "bold": Style(bold=True), + "strong": Style(bold=True), + "code": Style(reverse=True, bold=True), + "italic": Style(italic=True), + "emphasize": Style(italic=True), + "underline": Style(underline=True), + "blink": Style(blink=True), + "blink2": Style(blink2=True), + "reverse": Style(reverse=True), + "strike": Style(strike=True), + "black": Style(color="black"), + "red": Style(color="red"), + "green": Style(color="green"), + "yellow": Style(color="yellow"), + "magenta": Style(color="magenta"), + "cyan": Style(color="cyan"), + "white": Style(color="white"), + "inspect.attr": Style(color="yellow", italic=True), + "inspect.attr.dunder": Style(color="yellow", italic=True, dim=True), + "inspect.callable": Style(bold=True, color="red"), + "inspect.def": Style(italic=True, color="bright_cyan"), + "inspect.error": Style(bold=True, color="red"), + "inspect.equals": Style(), + "inspect.help": Style(color="cyan"), + "inspect.doc": Style(dim=True), + "inspect.value.border": Style(color="green"), + "live.ellipsis": Style(bold=True, color="red"), + "layout.tree.row": Style(dim=False, color="red"), + "layout.tree.column": Style(dim=False, color="blue"), + "logging.keyword": Style(bold=True, color="yellow"), + "logging.level.notset": Style(dim=True), + "logging.level.debug": Style(color="green"), + "logging.level.info": Style(color="blue"), + "logging.level.warning": Style(color="red"), + "logging.level.error": Style(color="red", bold=True), + "logging.level.critical": Style(color="red", bold=True, reverse=True), + "log.level": Style.null(), + "log.time": Style(color="cyan", dim=True), + "log.message": Style.null(), + "log.path": Style(dim=True), + "repr.ellipsis": Style(color="yellow"), + "repr.indent": Style(color="green", dim=True), + "repr.error": Style(color="red", bold=True), + "repr.str": Style(color="green", italic=False, bold=False), + "repr.brace": Style(bold=True), + "repr.comma": Style(bold=True), + "repr.ipv4": Style(bold=True, color="bright_green"), + "repr.ipv6": Style(bold=True, color="bright_green"), + "repr.eui48": Style(bold=True, color="bright_green"), + "repr.eui64": Style(bold=True, color="bright_green"), + "repr.tag_start": Style(bold=True), + "repr.tag_name": Style(color="bright_magenta", bold=True), + "repr.tag_contents": Style(color="default"), + "repr.tag_end": Style(bold=True), + "repr.attrib_name": Style(color="yellow", italic=False), + "repr.attrib_equal": Style(bold=True), + "repr.attrib_value": Style(color="magenta", italic=False), + "repr.number": Style(color="cyan", bold=True, italic=False), + "repr.bool_true": Style(color="bright_green", italic=True), + "repr.bool_false": Style(color="bright_red", italic=True), + "repr.none": Style(color="magenta", italic=True), + "repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False), + "repr.uuid": Style(color="bright_yellow", bold=False), + "repr.call": Style(color="magenta", bold=True), + "repr.path": Style(color="magenta"), + "repr.filename": Style(color="bright_magenta"), + "rule.line": Style(color="bright_green"), + "rule.text": Style.null(), + "json.brace": Style(bold=True), + "json.bool_true": Style(color="bright_green", italic=True), + "json.bool_false": Style(color="bright_red", italic=True), + "json.null": Style(color="magenta", italic=True), + "json.number": Style(color="cyan", bold=True, italic=False), + "json.str": Style(color="green", italic=False, bold=False), + "json.key": Style(color="blue", bold=True), + "prompt": Style.null(), + "prompt.choices": Style(color="magenta", bold=True), + "prompt.default": Style(color="cyan", bold=True), + "prompt.invalid": Style(color="red"), + "prompt.invalid.choice": Style(color="red"), + "pretty": Style.null(), + "scope.border": Style(color="blue"), + "scope.key": Style(color="yellow", italic=True), + "scope.key.special": Style(color="yellow", italic=True, dim=True), + "scope.equals": Style(color="red"), + "table.header": Style(bold=True), + "table.footer": Style(bold=True), + "table.cell": Style.null(), + "table.title": Style(italic=True), + "table.caption": Style(italic=True, dim=True), + "traceback.error": Style(color="red", italic=True), + "traceback.border.syntax_error": Style(color="bright_red"), + "traceback.border": Style(color="red"), + "traceback.text": Style.null(), + "traceback.title": Style(color="red", bold=True), + "traceback.exc_type": Style(color="bright_red", bold=True), + "traceback.exc_value": Style.null(), + "traceback.offset": Style(color="bright_red", bold=True), + "bar.back": Style(color="grey23"), + "bar.complete": Style(color="rgb(249,38,114)"), + "bar.finished": Style(color="rgb(114,156,31)"), + "bar.pulse": Style(color="rgb(249,38,114)"), + "progress.description": Style.null(), + "progress.filesize": Style(color="green"), + "progress.filesize.total": Style(color="green"), + "progress.download": Style(color="green"), + "progress.elapsed": Style(color="yellow"), + "progress.percentage": Style(color="magenta"), + "progress.remaining": Style(color="cyan"), + "progress.data.speed": Style(color="red"), + "progress.spinner": Style(color="green"), + "status.spinner": Style(color="green"), + "tree": Style(), + "tree.line": Style(), + "markdown.paragraph": Style(), + "markdown.text": Style(), + "markdown.emph": Style(italic=True), + "markdown.strong": Style(bold=True), + "markdown.code": Style(bgcolor="black", color="bright_white"), + "markdown.code_block": Style(dim=True, color="cyan", bgcolor="black"), + "markdown.block_quote": Style(color="magenta"), + "markdown.list": Style(color="cyan"), + "markdown.item": Style(), + "markdown.item.bullet": Style(color="yellow", bold=True), + "markdown.item.number": Style(color="yellow", bold=True), + "markdown.hr": Style(color="yellow"), + "markdown.h1.border": Style(), + "markdown.h1": Style(bold=True), + "markdown.h2": Style(bold=True, underline=True), + "markdown.h3": Style(bold=True), + "markdown.h4": Style(bold=True, dim=True), + "markdown.h5": Style(underline=True), + "markdown.h6": Style(italic=True), + "markdown.h7": Style(italic=True, dim=True), + "markdown.link": Style(color="bright_blue"), + "markdown.link_url": Style(color="blue"), +} + + +if __name__ == "__main__": # pragma: no cover + import argparse + import io + + from pip._vendor.rich.console import Console + from pip._vendor.rich.table import Table + from pip._vendor.rich.text import Text + + parser = argparse.ArgumentParser() + parser.add_argument("--html", action="store_true", help="Export as HTML table") + args = parser.parse_args() + html: bool = args.html + console = Console(record=True, width=70, file=io.StringIO()) if html else Console() + + table = Table("Name", "Styling") + + for style_name, style in DEFAULT_STYLES.items(): + table.add_row(Text(style_name, style=style), str(style)) + + console.print(table) + if html: + print(console.export_html(inline_styles=True)) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/diagnose.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/diagnose.py new file mode 100644 index 0000000..38728da --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/diagnose.py @@ -0,0 +1,6 @@ +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + from pip._vendor.rich import inspect + + console = Console() + inspect(console) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/emoji.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/emoji.py new file mode 100644 index 0000000..791f046 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/emoji.py @@ -0,0 +1,96 @@ +import sys +from typing import TYPE_CHECKING, Optional, Union + +from .jupyter import JupyterMixin +from .segment import Segment +from .style import Style +from ._emoji_codes import EMOJI +from ._emoji_replace import _emoji_replace + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + + +EmojiVariant = Literal["emoji", "text"] + + +class NoEmoji(Exception): + """No emoji by that name.""" + + +class Emoji(JupyterMixin): + __slots__ = ["name", "style", "_char", "variant"] + + VARIANTS = {"text": "\uFE0E", "emoji": "\uFE0F"} + + def __init__( + self, + name: str, + style: Union[str, Style] = "none", + variant: Optional[EmojiVariant] = None, + ) -> None: + """A single emoji character. + + Args: + name (str): Name of emoji. + style (Union[str, Style], optional): Optional style. Defaults to None. + + Raises: + NoEmoji: If the emoji doesn't exist. + """ + self.name = name + self.style = style + self.variant = variant + try: + self._char = EMOJI[name] + except KeyError: + raise NoEmoji(f"No emoji called {name!r}") + if variant is not None: + self._char += self.VARIANTS.get(variant, "") + + @classmethod + def replace(cls, text: str) -> str: + """Replace emoji markup with corresponding unicode characters. + + Args: + text (str): A string with emojis codes, e.g. "Hello :smiley:!" + + Returns: + str: A string with emoji codes replaces with actual emoji. + """ + return _emoji_replace(text) + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self._char + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + yield Segment(self._char, console.get_style(self.style)) + + +if __name__ == "__main__": # pragma: no cover + import sys + + from pip._vendor.rich.columns import Columns + from pip._vendor.rich.console import Console + + console = Console(record=True) + + columns = Columns( + (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200D" not in name), + column_first=True, + ) + + console.print(columns) + if len(sys.argv) > 1: + console.save_html(sys.argv[1]) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/errors.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/errors.py new file mode 100644 index 0000000..0bcbe53 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/errors.py @@ -0,0 +1,34 @@ +class ConsoleError(Exception): + """An error in console operation.""" + + +class StyleError(Exception): + """An error in styles.""" + + +class StyleSyntaxError(ConsoleError): + """Style was badly formatted.""" + + +class MissingStyle(StyleError): + """No such style.""" + + +class StyleStackError(ConsoleError): + """Style stack is invalid.""" + + +class NotRenderableError(ConsoleError): + """Object is not renderable.""" + + +class MarkupError(ConsoleError): + """Markup was badly formatted.""" + + +class LiveError(ConsoleError): + """Error related to Live display.""" + + +class NoAltScreen(ConsoleError): + """Alt screen mode was required.""" diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/file_proxy.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/file_proxy.py new file mode 100644 index 0000000..3ec593a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/file_proxy.py @@ -0,0 +1,54 @@ +import io +from typing import List, Any, IO, TYPE_CHECKING + +from .ansi import AnsiDecoder +from .text import Text + +if TYPE_CHECKING: + from .console import Console + + +class FileProxy(io.TextIOBase): + """Wraps a file (e.g. sys.stdout) and redirects writes to a console.""" + + def __init__(self, console: "Console", file: IO[str]) -> None: + self.__console = console + self.__file = file + self.__buffer: List[str] = [] + self.__ansi_decoder = AnsiDecoder() + + @property + def rich_proxied_file(self) -> IO[str]: + """Get proxied file.""" + return self.__file + + def __getattr__(self, name: str) -> Any: + return getattr(self.__file, name) + + def write(self, text: str) -> int: + if not isinstance(text, str): + raise TypeError(f"write() argument must be str, not {type(text).__name__}") + buffer = self.__buffer + lines: List[str] = [] + while text: + line, new_line, text = text.partition("\n") + if new_line: + lines.append("".join(buffer) + line) + del buffer[:] + else: + buffer.append(line) + break + if lines: + console = self.__console + with console: + output = Text("\n").join( + self.__ansi_decoder.decode_line(line) for line in lines + ) + console.print(output) + return len(text) + + def flush(self) -> None: + buffer = self.__buffer + if buffer: + self.__console.print("".join(buffer)) + del buffer[:] diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/filesize.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/filesize.py new file mode 100644 index 0000000..b3a0996 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/filesize.py @@ -0,0 +1,89 @@ +# coding: utf-8 +"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2 + +The functions declared in this module should cover the different +usecases needed to generate a string representation of a file size +using several different units. Since there are many standards regarding +file size units, three different functions have been implemented. + +See Also: + * `Wikipedia: Binary prefix `_ + +""" + +__all__ = ["decimal"] + +from typing import Iterable, List, Tuple, Optional + + +def _to_str( + size: int, + suffixes: Iterable[str], + base: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + if size == 1: + return "1 byte" + elif size < base: + return "{:,} bytes".format(size) + + for i, suffix in enumerate(suffixes, 2): # noqa: B007 + unit = base ** i + if size < unit: + break + return "{:,.{precision}f}{separator}{}".format( + (base * size / unit), + suffix, + precision=precision, + separator=separator, + ) + + +def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]: + """Pick a suffix and base for the given size.""" + for i, suffix in enumerate(suffixes): + unit = base ** i + if size < unit * base: + break + return unit, suffix + + +def decimal( + size: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + """Convert a filesize in to a string (powers of 1000, SI prefixes). + + In this convention, ``1000 B = 1 kB``. + + This is typically the format used to advertise the storage + capacity of USB flash drives and the like (*256 MB* meaning + actually a storage capacity of more than *256 000 000 B*), + or used by **Mac OS X** since v10.6 to report file sizes. + + Arguments: + int (size): A file size. + int (precision): The number of decimal places to include (default = 1). + str (separator): The string to separate the value from the units (default = " "). + + Returns: + `str`: A string containing a abbreviated file size and units. + + Example: + >>> filesize.decimal(30000) + '30.0 kB' + >>> filesize.decimal(30000, precision=2, separator="") + '30.00kB' + + """ + return _to_str( + size, + ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"), + 1000, + precision=precision, + separator=separator, + ) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/highlighter.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/highlighter.py new file mode 100644 index 0000000..8afdd01 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/highlighter.py @@ -0,0 +1,147 @@ +from abc import ABC, abstractmethod +from typing import List, Union + +from .text import Text + + +def _combine_regex(*regexes: str) -> str: + """Combine a number of regexes in to a single regex. + + Returns: + str: New regex with all regexes ORed together. + """ + return "|".join(regexes) + + +class Highlighter(ABC): + """Abstract base class for highlighters.""" + + def __call__(self, text: Union[str, Text]) -> Text: + """Highlight a str or Text instance. + + Args: + text (Union[str, ~Text]): Text to highlight. + + Raises: + TypeError: If not called with text or str. + + Returns: + Text: A test instance with highlighting applied. + """ + if isinstance(text, str): + highlight_text = Text(text) + elif isinstance(text, Text): + highlight_text = text.copy() + else: + raise TypeError(f"str or Text instance required, not {text!r}") + self.highlight(highlight_text) + return highlight_text + + @abstractmethod + def highlight(self, text: Text) -> None: + """Apply highlighting in place to text. + + Args: + text (~Text): A text object highlight. + """ + + +class NullHighlighter(Highlighter): + """A highlighter object that doesn't highlight. + + May be used to disable highlighting entirely. + + """ + + def highlight(self, text: Text) -> None: + """Nothing to do""" + + +class RegexHighlighter(Highlighter): + """Applies highlighting from a list of regular expressions.""" + + highlights: List[str] = [] + base_style: str = "" + + def highlight(self, text: Text) -> None: + """Highlight :class:`rich.text.Text` using regular expressions. + + Args: + text (~Text): Text to highlighted. + + """ + + highlight_regex = text.highlight_regex + for re_highlight in self.highlights: + highlight_regex(re_highlight, style_prefix=self.base_style) + + +class ReprHighlighter(RegexHighlighter): + """Highlights the text typically produced from ``__repr__`` methods.""" + + base_style = "repr." + highlights = [ + r"(?P\<)(?P[\w\-\.\:]*)(?P[\w\W]*?)(?P\>)", + r"(?P[\w_]{1,50})=(?P\"?[\w_]+\"?)?", + r"(?P[\{\[\(\)\]\}])", + _combine_regex( + r"(?P[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})", + r"(?P([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})", + r"(?P[\w\.]*?)\(", + r"\b(?PTrue)\b|\b(?PFalse)\b|\b(?PNone)\b", + r"(?P\.\.\.)", + r"(?P(?\B(\/[\w\.\-\_\+]+)*\/)(?P[\w\.\-\_\+]*)?", + r"(?b?\'\'\'.*?(?[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12})", + r"(?P(file|https|http|ws|wss):\/\/[0-9a-zA-Z\$\-\_\+\!`\(\)\,\.\?\/\;\:\&\=\%\#]*)", + ), + ] + + +class JSONHighlighter(RegexHighlighter): + """Highlights JSON""" + + base_style = "json." + highlights = [ + _combine_regex( + r"(?P[\{\[\(\)\]\}])", + r"\b(?Ptrue)\b|\b(?Pfalse)\b|\b(?Pnull)\b", + r"(?P(?b?\".*?(?b?\".*?(? None: + data = loads(json) + json = dumps( + data, + indent=indent, + skipkeys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + highlighter = JSONHighlighter() if highlight else NullHighlighter() + self.text = highlighter(json) + self.text.no_wrap = True + self.text.overflow = None + + @classmethod + def from_data( + cls, + data: Any, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + ) -> "JSON": + """Encodes a JSON object from arbitrary data. + + Args: + data (Any): An object that may be encoded in to JSON + indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2. + highlight (bool, optional): Enable highlighting. Defaults to True. + default (Callable, optional): Optional callable which will be called for objects that cannot be serialized. Defaults to None. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + + Returns: + JSON: New JSON object from the given data. + """ + json_instance: "JSON" = cls.__new__(cls) + json = dumps( + data, + indent=indent, + skipkeys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + highlighter = JSONHighlighter() if highlight else NullHighlighter() + json_instance.text = highlighter(json) + json_instance.text.no_wrap = True + json_instance.text.overflow = None + return json_instance + + def __rich__(self) -> Text: + return self.text + + +if __name__ == "__main__": + + import argparse + import sys + + parser = argparse.ArgumentParser(description="Pretty print json") + parser.add_argument( + "path", + metavar="PATH", + help="path to file, or - for stdin", + ) + parser.add_argument( + "-i", + "--indent", + metavar="SPACES", + type=int, + help="Number of spaces in an indent", + default=2, + ) + args = parser.parse_args() + + from pip._vendor.rich.console import Console + + console = Console() + error_console = Console(stderr=True) + + try: + if args.path == "-": + json_data = sys.stdin.read() + else: + with open(args.path, "rt") as json_file: + json_data = json_file.read() + except Exception as error: + error_console.print(f"Unable to read {args.path!r}; {error}") + sys.exit(-1) + + console.print(JSON(json_data, indent=args.indent), soft_wrap=True) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/jupyter.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/jupyter.py new file mode 100644 index 0000000..bedf5cb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/jupyter.py @@ -0,0 +1,92 @@ +from typing import Any, Dict, Iterable, List + +from . import get_console +from .segment import Segment +from .terminal_theme import DEFAULT_TERMINAL_THEME + +JUPYTER_HTML_FORMAT = """\ +
{code}
+""" + + +class JupyterRenderable: + """A shim to write html to Jupyter notebook.""" + + def __init__(self, html: str, text: str) -> None: + self.html = html + self.text = text + + def _repr_mimebundle_( + self, include: Iterable[str], exclude: Iterable[str], **kwargs: Any + ) -> Dict[str, str]: + data = {"text/plain": self.text, "text/html": self.html} + if include: + data = {k: v for (k, v) in data.items() if k in include} + if exclude: + data = {k: v for (k, v) in data.items() if k not in exclude} + return data + + +class JupyterMixin: + """Add to an Rich renderable to make it render in Jupyter notebook.""" + + __slots__ = () + + def _repr_mimebundle_( + self, include: Iterable[str], exclude: Iterable[str], **kwargs: Any + ) -> Dict[str, str]: + console = get_console() + segments = list(console.render(self, console.options)) # type: ignore + html = _render_segments(segments) + text = console._render_buffer(segments) + data = {"text/plain": text, "text/html": html} + if include: + data = {k: v for (k, v) in data.items() if k in include} + if exclude: + data = {k: v for (k, v) in data.items() if k not in exclude} + return data + + +def _render_segments(segments: Iterable[Segment]) -> str: + def escape(text: str) -> str: + """Escape html.""" + return text.replace("&", "&").replace("<", "<").replace(">", ">") + + fragments: List[str] = [] + append_fragment = fragments.append + theme = DEFAULT_TERMINAL_THEME + for text, style, control in Segment.simplify(segments): + if control: + continue + text = escape(text) + if style: + rule = style.get_html_style(theme) + text = f'{text}' if rule else text + if style.link: + text = f'{text}' + append_fragment(text) + + code = "".join(fragments) + html = JUPYTER_HTML_FORMAT.format(code=code) + + return html + + +def display(segments: Iterable[Segment], text: str) -> None: + """Render segments to Jupyter.""" + html = _render_segments(segments) + jupyter_renderable = JupyterRenderable(html, text) + try: + from IPython.display import display as ipython_display + + ipython_display(jupyter_renderable) + except ModuleNotFoundError: + # Handle the case where the Console has force_jupyter=True, + # but IPython is not installed. + pass + + +def print(*args: Any, **kwargs: Any) -> None: + """Proxy for Console print.""" + console = get_console() + return console.print(*args, **kwargs) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/layout.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/layout.py new file mode 100644 index 0000000..22a4c54 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/layout.py @@ -0,0 +1,444 @@ +from abc import ABC, abstractmethod +from itertools import islice +from operator import itemgetter +from threading import RLock +from typing import ( + TYPE_CHECKING, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + Union, +) + +from ._ratio import ratio_resolve +from .align import Align +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .highlighter import ReprHighlighter +from .panel import Panel +from .pretty import Pretty +from .repr import rich_repr, Result +from .region import Region +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: + from pip._vendor.rich.tree import Tree + + +class LayoutRender(NamedTuple): + """An individual layout render.""" + + region: Region + render: List[List[Segment]] + + +RegionMap = Dict["Layout", Region] +RenderMap = Dict["Layout", LayoutRender] + + +class LayoutError(Exception): + """Layout related error.""" + + +class NoSplitter(LayoutError): + """Requested splitter does not exist.""" + + +class _Placeholder: + """An internal renderable used as a Layout placeholder.""" + + highlighter = ReprHighlighter() + + def __init__(self, layout: "Layout", style: StyleType = "") -> None: + self.layout = layout + self.style = style + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + width = options.max_width + height = options.height or options.size.height + layout = self.layout + title = ( + f"{layout.name!r} ({width} x {height})" + if layout.name + else f"({width} x {height})" + ) + yield Panel( + Align.center(Pretty(layout), vertical="middle"), + style=self.style, + title=self.highlighter(title), + border_style="blue", + ) + + +class Splitter(ABC): + """Base class for a splitter.""" + + name: str = "" + + @abstractmethod + def get_tree_icon(self) -> str: + """Get the icon (emoji) used in layout.tree""" + + @abstractmethod + def divide( + self, children: Sequence["Layout"], region: Region + ) -> Iterable[Tuple["Layout", Region]]: + """Divide a region amongst several child layouts. + + Args: + children (Sequence(Layout)): A number of child layouts. + region (Region): A rectangular region to divide. + """ + + +class RowSplitter(Splitter): + """Split a layout region in to rows.""" + + name = "row" + + def get_tree_icon(self) -> str: + return "[layout.tree.row]⬌" + + def divide( + self, children: Sequence["Layout"], region: Region + ) -> Iterable[Tuple["Layout", Region]]: + x, y, width, height = region + render_widths = ratio_resolve(width, children) + offset = 0 + _Region = Region + for child, child_width in zip(children, render_widths): + yield child, _Region(x + offset, y, child_width, height) + offset += child_width + + +class ColumnSplitter(Splitter): + """Split a layout region in to columns.""" + + name = "column" + + def get_tree_icon(self) -> str: + return "[layout.tree.column]â¬" + + def divide( + self, children: Sequence["Layout"], region: Region + ) -> Iterable[Tuple["Layout", Region]]: + x, y, width, height = region + render_heights = ratio_resolve(height, children) + offset = 0 + _Region = Region + for child, child_height in zip(children, render_heights): + yield child, _Region(x, y + offset, width, child_height) + offset += child_height + + +@rich_repr +class Layout: + """A renderable to divide a fixed height in to rows or columns. + + Args: + renderable (RenderableType, optional): Renderable content, or None for placeholder. Defaults to None. + name (str, optional): Optional identifier for Layout. Defaults to None. + size (int, optional): Optional fixed size of layout. Defaults to None. + minimum_size (int, optional): Minimum size of layout. Defaults to 1. + ratio (int, optional): Optional ratio for flexible layout. Defaults to 1. + visible (bool, optional): Visibility of layout. Defaults to True. + """ + + splitters = {"row": RowSplitter, "column": ColumnSplitter} + + def __init__( + self, + renderable: Optional[RenderableType] = None, + *, + name: Optional[str] = None, + size: Optional[int] = None, + minimum_size: int = 1, + ratio: int = 1, + visible: bool = True, + height: Optional[int] = None, + ) -> None: + self._renderable = renderable or _Placeholder(self) + self.size = size + self.minimum_size = minimum_size + self.ratio = ratio + self.name = name + self.visible = visible + self.height = height + self.splitter: Splitter = self.splitters["column"]() + self._children: List[Layout] = [] + self._render_map: RenderMap = {} + self._lock = RLock() + + def __rich_repr__(self) -> Result: + yield "name", self.name, None + yield "size", self.size, None + yield "minimum_size", self.minimum_size, 1 + yield "ratio", self.ratio, 1 + + @property + def renderable(self) -> RenderableType: + """Layout renderable.""" + return self if self._children else self._renderable + + @property + def children(self) -> List["Layout"]: + """Gets (visible) layout children.""" + return [child for child in self._children if child.visible] + + @property + def map(self) -> RenderMap: + """Get a map of the last render.""" + return self._render_map + + def get(self, name: str) -> Optional["Layout"]: + """Get a named layout, or None if it doesn't exist. + + Args: + name (str): Name of layout. + + Returns: + Optional[Layout]: Layout instance or None if no layout was found. + """ + if self.name == name: + return self + else: + for child in self._children: + named_layout = child.get(name) + if named_layout is not None: + return named_layout + return None + + def __getitem__(self, name: str) -> "Layout": + layout = self.get(name) + if layout is None: + raise KeyError(f"No layout with name {name!r}") + return layout + + @property + def tree(self) -> "Tree": + """Get a tree renderable to show layout structure.""" + from pip._vendor.rich.styled import Styled + from pip._vendor.rich.table import Table + from pip._vendor.rich.tree import Tree + + def summary(layout: "Layout") -> Table: + + icon = layout.splitter.get_tree_icon() + + table = Table.grid(padding=(0, 1, 0, 0)) + + text: RenderableType = ( + Pretty(layout) if layout.visible else Styled(Pretty(layout), "dim") + ) + table.add_row(icon, text) + _summary = table + return _summary + + layout = self + tree = Tree( + summary(layout), + guide_style=f"layout.tree.{layout.splitter.name}", + highlight=True, + ) + + def recurse(tree: "Tree", layout: "Layout") -> None: + for child in layout._children: + recurse( + tree.add( + summary(child), + guide_style=f"layout.tree.{child.splitter.name}", + ), + child, + ) + + recurse(tree, self) + return tree + + def split( + self, + *layouts: Union["Layout", RenderableType], + splitter: Union[Splitter, str] = "column", + ) -> None: + """Split the layout in to multiple sub-layouts. + + Args: + *layouts (Layout): Positional arguments should be (sub) Layout instances. + splitter (Union[Splitter, str]): Splitter instance or name of splitter. + """ + _layouts = [ + layout if isinstance(layout, Layout) else Layout(layout) + for layout in layouts + ] + try: + self.splitter = ( + splitter + if isinstance(splitter, Splitter) + else self.splitters[splitter]() + ) + except KeyError: + raise NoSplitter(f"No splitter called {splitter!r}") + self._children[:] = _layouts + + def add_split(self, *layouts: Union["Layout", RenderableType]) -> None: + """Add a new layout(s) to existing split. + + Args: + *layouts (Union[Layout, RenderableType]): Positional arguments should be renderables or (sub) Layout instances. + + """ + _layouts = ( + layout if isinstance(layout, Layout) else Layout(layout) + for layout in layouts + ) + self._children.extend(_layouts) + + def split_row(self, *layouts: Union["Layout", RenderableType]) -> None: + """Split the layout in tow a row (Layouts side by side). + + Args: + *layouts (Layout): Positional arguments should be (sub) Layout instances. + """ + self.split(*layouts, splitter="row") + + def split_column(self, *layouts: Union["Layout", RenderableType]) -> None: + """Split the layout in to a column (layouts stacked on top of each other). + + Args: + *layouts (Layout): Positional arguments should be (sub) Layout instances. + """ + self.split(*layouts, splitter="column") + + def unsplit(self) -> None: + """Reset splits to initial state.""" + del self._children[:] + + def update(self, renderable: RenderableType) -> None: + """Update renderable. + + Args: + renderable (RenderableType): New renderable object. + """ + with self._lock: + self._renderable = renderable + + def refresh_screen(self, console: "Console", layout_name: str) -> None: + """Refresh a sub-layout. + + Args: + console (Console): Console instance where Layout is to be rendered. + layout_name (str): Name of layout. + """ + with self._lock: + layout = self[layout_name] + region, _lines = self._render_map[layout] + (x, y, width, height) = region + lines = console.render_lines( + layout, console.options.update_dimensions(width, height) + ) + self._render_map[layout] = LayoutRender(region, lines) + console.update_screen_lines(lines, x, y) + + def _make_region_map(self, width: int, height: int) -> RegionMap: + """Create a dict that maps layout on to Region.""" + stack: List[Tuple[Layout, Region]] = [(self, Region(0, 0, width, height))] + push = stack.append + pop = stack.pop + layout_regions: List[Tuple[Layout, Region]] = [] + append_layout_region = layout_regions.append + while stack: + append_layout_region(pop()) + layout, region = layout_regions[-1] + children = layout.children + if children: + for child_and_region in layout.splitter.divide(children, region): + push(child_and_region) + + region_map = { + layout: region + for layout, region in sorted(layout_regions, key=itemgetter(1)) + } + return region_map + + def render(self, console: Console, options: ConsoleOptions) -> RenderMap: + """Render the sub_layouts. + + Args: + console (Console): Console instance. + options (ConsoleOptions): Console options. + + Returns: + RenderMap: A dict that maps Layout on to a tuple of Region, lines + """ + render_width = options.max_width + render_height = options.height or console.height + region_map = self._make_region_map(render_width, render_height) + layout_regions = [ + (layout, region) + for layout, region in region_map.items() + if not layout.children + ] + render_map: Dict["Layout", "LayoutRender"] = {} + render_lines = console.render_lines + update_dimensions = options.update_dimensions + + for layout, region in layout_regions: + lines = render_lines( + layout.renderable, update_dimensions(region.width, region.height) + ) + render_map[layout] = LayoutRender(region, lines) + return render_map + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + with self._lock: + width = options.max_width or console.width + height = options.height or console.height + render_map = self.render(console, options.update_dimensions(width, height)) + self._render_map = render_map + layout_lines: List[List[Segment]] = [[] for _ in range(height)] + _islice = islice + for (region, lines) in render_map.values(): + _x, y, _layout_width, layout_height = region + for row, line in zip( + _islice(layout_lines, y, y + layout_height), lines + ): + row.extend(line) + + new_line = Segment.line() + for layout_row in layout_lines: + yield from layout_row + yield new_line + + +if __name__ == "__main__": + from pip._vendor.rich.console import Console + + console = Console() + layout = Layout() + + layout.split_column( + Layout(name="header", size=3), + Layout(ratio=1, name="main"), + Layout(size=10, name="footer"), + ) + + layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2)) + + layout["body"].split_row(Layout(name="content", ratio=2), Layout(name="s2")) + + layout["s2"].split_column( + Layout(name="top"), Layout(name="middle"), Layout(name="bottom") + ) + + layout["side"].split_column(Layout(layout.tree, name="left1"), Layout(name="left2")) + + layout["content"].update("foo") + + console.print(layout) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/live.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/live.py new file mode 100644 index 0000000..6db5b60 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/live.py @@ -0,0 +1,365 @@ +import sys +from threading import Event, RLock, Thread +from types import TracebackType +from typing import IO, Any, Callable, List, Optional, TextIO, Type, cast + +from . import get_console +from .console import Console, ConsoleRenderable, RenderableType, RenderHook +from .control import Control +from .file_proxy import FileProxy +from .jupyter import JupyterMixin +from .live_render import LiveRender, VerticalOverflowMethod +from .screen import Screen +from .text import Text + + +class _RefreshThread(Thread): + """A thread that calls refresh() at regular intervals.""" + + def __init__(self, live: "Live", refresh_per_second: float) -> None: + self.live = live + self.refresh_per_second = refresh_per_second + self.done = Event() + super().__init__(daemon=True) + + def stop(self) -> None: + self.done.set() + + def run(self) -> None: + while not self.done.wait(1 / self.refresh_per_second): + with self.live._lock: + if not self.done.is_set(): + self.live.refresh() + + +class Live(JupyterMixin, RenderHook): + """Renders an auto-updating live display of any given renderable. + + Args: + renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing. + console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout. + screen (bool, optional): Enable alternate screen mode. Defaults to False. + auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True + refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4. + transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False. + redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True. + redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True. + vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis". + get_renderable (Callable[[], RenderableType], optional): Optional callable to get renderable. Defaults to None. + """ + + def __init__( + self, + renderable: Optional[RenderableType] = None, + *, + console: Optional[Console] = None, + screen: bool = False, + auto_refresh: bool = True, + refresh_per_second: float = 4, + transient: bool = False, + redirect_stdout: bool = True, + redirect_stderr: bool = True, + vertical_overflow: VerticalOverflowMethod = "ellipsis", + get_renderable: Optional[Callable[[], RenderableType]] = None, + ) -> None: + assert refresh_per_second > 0, "refresh_per_second must be > 0" + self._renderable = renderable + self.console = console if console is not None else get_console() + self._screen = screen + self._alt_screen = False + + self._redirect_stdout = redirect_stdout + self._redirect_stderr = redirect_stderr + self._restore_stdout: Optional[IO[str]] = None + self._restore_stderr: Optional[IO[str]] = None + + self._lock = RLock() + self.ipy_widget: Optional[Any] = None + self.auto_refresh = auto_refresh + self._started: bool = False + self.transient = True if screen else transient + + self._refresh_thread: Optional[_RefreshThread] = None + self.refresh_per_second = refresh_per_second + + self.vertical_overflow = vertical_overflow + self._get_renderable = get_renderable + self._live_render = LiveRender( + self.get_renderable(), vertical_overflow=vertical_overflow + ) + + @property + def is_started(self) -> bool: + """Check if live display has been started.""" + return self._started + + def get_renderable(self) -> RenderableType: + renderable = ( + self._get_renderable() + if self._get_renderable is not None + else self._renderable + ) + return renderable or "" + + def start(self, refresh: bool = False) -> None: + """Start live rendering display. + + Args: + refresh (bool, optional): Also refresh. Defaults to False. + """ + with self._lock: + if self._started: + return + self.console.set_live(self) + self._started = True + if self._screen: + self._alt_screen = self.console.set_alt_screen(True) + self.console.show_cursor(False) + self._enable_redirect_io() + self.console.push_render_hook(self) + if refresh: + self.refresh() + if self.auto_refresh: + self._refresh_thread = _RefreshThread(self, self.refresh_per_second) + self._refresh_thread.start() + + def stop(self) -> None: + """Stop live rendering display.""" + with self._lock: + if not self._started: + return + self.console.clear_live() + self._started = False + + if self.auto_refresh and self._refresh_thread is not None: + self._refresh_thread.stop() + self._refresh_thread = None + # allow it to fully render on the last even if overflow + self.vertical_overflow = "visible" + with self.console: + try: + if not self._alt_screen and not self.console.is_jupyter: + self.refresh() + finally: + self._disable_redirect_io() + self.console.pop_render_hook() + if not self._alt_screen and self.console.is_terminal: + self.console.line() + self.console.show_cursor(True) + if self._alt_screen: + self.console.set_alt_screen(False) + + if self.transient and not self._alt_screen: + self.console.control(self._live_render.restore_cursor()) + if self.ipy_widget is not None and self.transient: + self.ipy_widget.close() # pragma: no cover + + def __enter__(self) -> "Live": + self.start(refresh=self._renderable is not None) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.stop() + + def _enable_redirect_io(self) -> None: + """Enable redirecting of stdout / stderr.""" + if self.console.is_terminal or self.console.is_jupyter: + if self._redirect_stdout and not isinstance(sys.stdout, FileProxy): + self._restore_stdout = sys.stdout + sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout)) + if self._redirect_stderr and not isinstance(sys.stderr, FileProxy): + self._restore_stderr = sys.stderr + sys.stderr = cast("TextIO", FileProxy(self.console, sys.stderr)) + + def _disable_redirect_io(self) -> None: + """Disable redirecting of stdout / stderr.""" + if self._restore_stdout: + sys.stdout = cast("TextIO", self._restore_stdout) + self._restore_stdout = None + if self._restore_stderr: + sys.stderr = cast("TextIO", self._restore_stderr) + self._restore_stderr = None + + @property + def renderable(self) -> RenderableType: + """Get the renderable that is being displayed + + Returns: + RenderableType: Displayed renderable. + """ + renderable = self.get_renderable() + return Screen(renderable) if self._alt_screen else renderable + + def update(self, renderable: RenderableType, *, refresh: bool = False) -> None: + """Update the renderable that is being displayed + + Args: + renderable (RenderableType): New renderable to use. + refresh (bool, optional): Refresh the display. Defaults to False. + """ + with self._lock: + self._renderable = renderable + if refresh: + self.refresh() + + def refresh(self) -> None: + """Update the display of the Live Render.""" + with self._lock: + self._live_render.set_renderable(self.renderable) + if self.console.is_jupyter: # pragma: no cover + try: + from IPython.display import display + from ipywidgets import Output + except ImportError: + import warnings + + warnings.warn('install "ipywidgets" for Jupyter support') + else: + if self.ipy_widget is None: + self.ipy_widget = Output() + display(self.ipy_widget) + + with self.ipy_widget: + self.ipy_widget.clear_output(wait=True) + self.console.print(self._live_render.renderable) + elif self.console.is_terminal and not self.console.is_dumb_terminal: + with self.console: + self.console.print(Control()) + elif ( + not self._started and not self.transient + ): # if it is finished allow files or dumb-terminals to see final result + with self.console: + self.console.print(Control()) + + def process_renderables( + self, renderables: List[ConsoleRenderable] + ) -> List[ConsoleRenderable]: + """Process renderables to restore cursor and display progress.""" + self._live_render.vertical_overflow = self.vertical_overflow + if self.console.is_interactive: + # lock needs acquiring as user can modify live_render renderable at any time unlike in Progress. + with self._lock: + reset = ( + Control.home() + if self._alt_screen + else self._live_render.position_cursor() + ) + renderables = [reset, *renderables, self._live_render] + elif ( + not self._started and not self.transient + ): # if it is finished render the final output for files or dumb_terminals + renderables = [*renderables, self._live_render] + + return renderables + + +if __name__ == "__main__": # pragma: no cover + import random + import time + from itertools import cycle + from typing import Dict, List, Tuple + + from .align import Align + from .console import Console + from .live import Live as Live + from .panel import Panel + from .rule import Rule + from .syntax import Syntax + from .table import Table + + console = Console() + + syntax = Syntax( + '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value''', + "python", + line_numbers=True, + ) + + table = Table("foo", "bar", "baz") + table.add_row("1", "2", "3") + + progress_renderables = [ + "You can make the terminal shorter and taller to see the live table hide" + "Text may be printed while the progress bars are rendering.", + Panel("In fact, [i]any[/i] renderable will work"), + "Such as [magenta]tables[/]...", + table, + "Pretty printed structures...", + {"type": "example", "text": "Pretty printed"}, + "Syntax...", + syntax, + Rule("Give it a try!"), + ] + + examples = cycle(progress_renderables) + + exchanges = [ + "SGD", + "MYR", + "EUR", + "USD", + "AUD", + "JPY", + "CNH", + "HKD", + "CAD", + "INR", + "DKK", + "GBP", + "RUB", + "NZD", + "MXN", + "IDR", + "TWD", + "THB", + "VND", + ] + with Live(console=console) as live_table: + exchange_rate_dict: Dict[Tuple[str, str], float] = {} + + for index in range(100): + select_exchange = exchanges[index % len(exchanges)] + + for exchange in exchanges: + if exchange == select_exchange: + continue + time.sleep(0.4) + if random.randint(0, 10) < 1: + console.log(next(examples)) + exchange_rate_dict[(select_exchange, exchange)] = 200 / ( + (random.random() * 320) + 1 + ) + if len(exchange_rate_dict) > len(exchanges) - 1: + exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0]) + table = Table(title="Exchange Rates") + + table.add_column("Source Currency") + table.add_column("Destination Currency") + table.add_column("Exchange Rate") + + for ((source, dest), exchange_rate) in exchange_rate_dict.items(): + table.add_row( + source, + dest, + Text( + f"{exchange_rate:.4f}", + style="red" if exchange_rate < 1.0 else "green", + ), + ) + + live_table.update(Align.center(table)) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/live_render.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/live_render.py new file mode 100644 index 0000000..b90fbf7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/live_render.py @@ -0,0 +1,113 @@ +import sys +from typing import Optional, Tuple + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +from ._loop import loop_last +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .control import Control +from .segment import ControlType, Segment +from .style import StyleType +from .text import Text + +VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"] + + +class LiveRender: + """Creates a renderable that may be updated. + + Args: + renderable (RenderableType): Any renderable object. + style (StyleType, optional): An optional style to apply to the renderable. Defaults to "". + """ + + def __init__( + self, + renderable: RenderableType, + style: StyleType = "", + vertical_overflow: VerticalOverflowMethod = "ellipsis", + ) -> None: + self.renderable = renderable + self.style = style + self.vertical_overflow = vertical_overflow + self._shape: Optional[Tuple[int, int]] = None + + def set_renderable(self, renderable: RenderableType) -> None: + """Set a new renderable. + + Args: + renderable (RenderableType): Any renderable object, including str. + """ + self.renderable = renderable + + def position_cursor(self) -> Control: + """Get control codes to move cursor to beginning of live render. + + Returns: + Control: A control instance that may be printed. + """ + if self._shape is not None: + _, height = self._shape + return Control( + ControlType.CARRIAGE_RETURN, + (ControlType.ERASE_IN_LINE, 2), + *( + ( + (ControlType.CURSOR_UP, 1), + (ControlType.ERASE_IN_LINE, 2), + ) + * (height - 1) + ) + ) + return Control() + + def restore_cursor(self) -> Control: + """Get control codes to clear the render and restore the cursor to its previous position. + + Returns: + Control: A Control instance that may be printed. + """ + if self._shape is not None: + _, height = self._shape + return Control( + ControlType.CARRIAGE_RETURN, + *((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height + ) + return Control() + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + + renderable = self.renderable + style = console.get_style(self.style) + lines = console.render_lines(renderable, options, style=style, pad=False) + shape = Segment.get_shape(lines) + + _, height = shape + if height > options.size.height: + if self.vertical_overflow == "crop": + lines = lines[: options.size.height] + shape = Segment.get_shape(lines) + elif self.vertical_overflow == "ellipsis": + lines = lines[: (options.size.height - 1)] + overflow_text = Text( + "...", + overflow="crop", + justify="center", + end="", + style="live.ellipsis", + ) + lines.append(list(console.render(overflow_text))) + shape = Segment.get_shape(lines) + self._shape = shape + + new_line = Segment.line() + for last, line in loop_last(lines): + yield from line + if not last: + yield new_line diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/logging.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/logging.py new file mode 100644 index 0000000..002f1f7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/logging.py @@ -0,0 +1,268 @@ +import logging +from datetime import datetime +from logging import Handler, LogRecord +from pathlib import Path +from typing import ClassVar, List, Optional, Type, Union + +from . import get_console +from ._log_render import LogRender, FormatTimeCallable +from .console import Console, ConsoleRenderable +from .highlighter import Highlighter, ReprHighlighter +from .text import Text +from .traceback import Traceback + + +class RichHandler(Handler): + """A logging handler that renders output with Rich. The time / level / message and file are displayed in columns. + The level is color coded, and the message is syntax highlighted. + + Note: + Be careful when enabling console markup in log messages if you have configured logging for libraries not + under your control. If a dependency writes messages containing square brackets, it may not produce the intended output. + + Args: + level (Union[int, str], optional): Log level. Defaults to logging.NOTSET. + console (:class:`~rich.console.Console`, optional): Optional console instance to write logs. + Default will use a global console instance writing to stdout. + show_time (bool, optional): Show a column for the time. Defaults to True. + omit_repeated_times (bool, optional): Omit repetition of the same time. Defaults to True. + show_level (bool, optional): Show a column for the level. Defaults to True. + show_path (bool, optional): Show the path to the original log call. Defaults to True. + enable_link_path (bool, optional): Enable terminal link of path column to file. Defaults to True. + highlighter (Highlighter, optional): Highlighter to style log messages, or None to use ReprHighlighter. Defaults to None. + markup (bool, optional): Enable console markup in log messages. Defaults to False. + rich_tracebacks (bool, optional): Enable rich tracebacks with syntax highlighting and formatting. Defaults to False. + tracebacks_width (Optional[int], optional): Number of characters used to render tracebacks, or None for full width. Defaults to None. + tracebacks_extra_lines (int, optional): Additional lines of code to render tracebacks, or None for full width. Defaults to None. + tracebacks_theme (str, optional): Override pygments theme used in traceback. + tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True. + tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%x %X] ". + """ + + KEYWORDS: ClassVar[Optional[List[str]]] = [ + "GET", + "POST", + "HEAD", + "PUT", + "DELETE", + "OPTIONS", + "TRACE", + "PATCH", + ] + HIGHLIGHTER_CLASS: ClassVar[Type[Highlighter]] = ReprHighlighter + + def __init__( + self, + level: Union[int, str] = logging.NOTSET, + console: Optional[Console] = None, + *, + show_time: bool = True, + omit_repeated_times: bool = True, + show_level: bool = True, + show_path: bool = True, + enable_link_path: bool = True, + highlighter: Optional[Highlighter] = None, + markup: bool = False, + rich_tracebacks: bool = False, + tracebacks_width: Optional[int] = None, + tracebacks_extra_lines: int = 3, + tracebacks_theme: Optional[str] = None, + tracebacks_word_wrap: bool = True, + tracebacks_show_locals: bool = False, + locals_max_length: int = 10, + locals_max_string: int = 80, + log_time_format: Union[str, FormatTimeCallable] = "[%x %X]", + ) -> None: + super().__init__(level=level) + self.console = console or get_console() + self.highlighter = highlighter or self.HIGHLIGHTER_CLASS() + self._log_render = LogRender( + show_time=show_time, + show_level=show_level, + show_path=show_path, + time_format=log_time_format, + omit_repeated_times=omit_repeated_times, + level_width=None, + ) + self.enable_link_path = enable_link_path + self.markup = markup + self.rich_tracebacks = rich_tracebacks + self.tracebacks_width = tracebacks_width + self.tracebacks_extra_lines = tracebacks_extra_lines + self.tracebacks_theme = tracebacks_theme + self.tracebacks_word_wrap = tracebacks_word_wrap + self.tracebacks_show_locals = tracebacks_show_locals + self.locals_max_length = locals_max_length + self.locals_max_string = locals_max_string + + def get_level_text(self, record: LogRecord) -> Text: + """Get the level name from the record. + + Args: + record (LogRecord): LogRecord instance. + + Returns: + Text: A tuple of the style and level name. + """ + level_name = record.levelname + level_text = Text.styled( + level_name.ljust(8), f"logging.level.{level_name.lower()}" + ) + return level_text + + def emit(self, record: LogRecord) -> None: + """Invoked by logging.""" + message = self.format(record) + traceback = None + if ( + self.rich_tracebacks + and record.exc_info + and record.exc_info != (None, None, None) + ): + exc_type, exc_value, exc_traceback = record.exc_info + assert exc_type is not None + assert exc_value is not None + traceback = Traceback.from_exception( + exc_type, + exc_value, + exc_traceback, + width=self.tracebacks_width, + extra_lines=self.tracebacks_extra_lines, + theme=self.tracebacks_theme, + word_wrap=self.tracebacks_word_wrap, + show_locals=self.tracebacks_show_locals, + locals_max_length=self.locals_max_length, + locals_max_string=self.locals_max_string, + ) + message = record.getMessage() + if self.formatter: + record.message = record.getMessage() + formatter = self.formatter + if hasattr(formatter, "usesTime") and formatter.usesTime(): + record.asctime = formatter.formatTime(record, formatter.datefmt) + message = formatter.formatMessage(record) + + message_renderable = self.render_message(record, message) + log_renderable = self.render( + record=record, traceback=traceback, message_renderable=message_renderable + ) + try: + self.console.print(log_renderable) + except Exception: + self.handleError(record) + + def render_message(self, record: LogRecord, message: str) -> "ConsoleRenderable": + """Render message text in to Text. + + record (LogRecord): logging Record. + message (str): String containing log message. + + Returns: + ConsoleRenderable: Renderable to display log message. + """ + use_markup = getattr(record, "markup", self.markup) + message_text = Text.from_markup(message) if use_markup else Text(message) + + highlighter = getattr(record, "highlighter", self.highlighter) + if highlighter: + message_text = highlighter(message_text) + + if self.KEYWORDS: + message_text.highlight_words(self.KEYWORDS, "logging.keyword") + return message_text + + def render( + self, + *, + record: LogRecord, + traceback: Optional[Traceback], + message_renderable: "ConsoleRenderable", + ) -> "ConsoleRenderable": + """Render log for display. + + Args: + record (LogRecord): logging Record. + traceback (Optional[Traceback]): Traceback instance or None for no Traceback. + message_renderable (ConsoleRenderable): Renderable (typically Text) containing log message contents. + + Returns: + ConsoleRenderable: Renderable to display log. + """ + path = Path(record.pathname).name + level = self.get_level_text(record) + time_format = None if self.formatter is None else self.formatter.datefmt + log_time = datetime.fromtimestamp(record.created) + + log_renderable = self._log_render( + self.console, + [message_renderable] if not traceback else [message_renderable, traceback], + log_time=log_time, + time_format=time_format, + level=level, + path=path, + line_no=record.lineno, + link_path=record.pathname if self.enable_link_path else None, + ) + return log_renderable + + +if __name__ == "__main__": # pragma: no cover + from time import sleep + + FORMAT = "%(message)s" + # FORMAT = "%(asctime)-15s - %(levelname)s - %(message)s" + logging.basicConfig( + level="NOTSET", + format=FORMAT, + datefmt="[%X]", + handlers=[RichHandler(rich_tracebacks=True, tracebacks_show_locals=True)], + ) + log = logging.getLogger("rich") + + log.info("Server starting...") + log.info("Listening on http://127.0.0.1:8080") + sleep(1) + + log.info("GET /index.html 200 1298") + log.info("GET /imgs/backgrounds/back1.jpg 200 54386") + log.info("GET /css/styles.css 200 54386") + log.warning("GET /favicon.ico 404 242") + sleep(1) + + log.debug( + "JSONRPC request\n--> %r\n<-- %r", + { + "version": "1.1", + "method": "confirmFruitPurchase", + "params": [["apple", "orange", "mangoes", "pomelo"], 1.123], + "id": "194521489", + }, + {"version": "1.1", "result": True, "error": None, "id": "194521489"}, + ) + log.debug( + "Loading configuration file /adasd/asdasd/qeqwe/qwrqwrqwr/sdgsdgsdg/werwerwer/dfgerert/ertertert/ertetert/werwerwer" + ) + log.error("Unable to find 'pomelo' in database!") + log.info("POST /jsonrpc/ 200 65532") + log.info("POST /admin/ 401 42234") + log.warning("password was rejected for admin site.") + + def divide() -> None: + number = 1 + divisor = 0 + foos = ["foo"] * 100 + log.debug("in divide") + try: + number / divisor + except: + log.exception("An error of some kind occurred!") + + divide() + sleep(1) + log.critical("Out of memory!") + log.info("Server exited with code=-1") + log.info("[bold]EXITING...[/bold]", extra=dict(markup=True)) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/markup.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/markup.py new file mode 100644 index 0000000..6195402 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/markup.py @@ -0,0 +1,244 @@ +from ast import literal_eval +from operator import attrgetter +import re +from typing import Callable, Iterable, List, Match, NamedTuple, Optional, Tuple, Union + +from .errors import MarkupError +from .style import Style +from .text import Span, Text +from .emoji import EmojiVariant +from ._emoji_replace import _emoji_replace + + +RE_TAGS = re.compile( + r"""((\\*)\[([a-z#\/@].*?)\])""", + re.VERBOSE, +) + +RE_HANDLER = re.compile(r"^([\w\.]*?)(\(.*?\))?$") + + +class Tag(NamedTuple): + """A tag in console markup.""" + + name: str + """The tag name. e.g. 'bold'.""" + parameters: Optional[str] + """Any additional parameters after the name.""" + + def __str__(self) -> str: + return ( + self.name if self.parameters is None else f"{self.name} {self.parameters}" + ) + + @property + def markup(self) -> str: + """Get the string representation of this tag.""" + return ( + f"[{self.name}]" + if self.parameters is None + else f"[{self.name}={self.parameters}]" + ) + + +_ReStringMatch = Match[str] # regex match object +_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub +_EscapeSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re + + +def escape( + markup: str, _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#\/@].*?\])").sub +) -> str: + """Escapes text so that it won't be interpreted as markup. + + Args: + markup (str): Content to be inserted in to markup. + + Returns: + str: Markup with square brackets escaped. + """ + + def escape_backslashes(match: Match[str]) -> str: + """Called by re.sub replace matches.""" + backslashes, text = match.groups() + return f"{backslashes}{backslashes}\\{text}" + + markup = _escape(escape_backslashes, markup) + return markup + + +def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]: + """Parse markup in to an iterable of tuples of (position, text, tag). + + Args: + markup (str): A string containing console markup + + """ + position = 0 + _divmod = divmod + _Tag = Tag + for match in RE_TAGS.finditer(markup): + full_text, escapes, tag_text = match.groups() + start, end = match.span() + if start > position: + yield start, markup[position:start], None + if escapes: + backslashes, escaped = _divmod(len(escapes), 2) + if backslashes: + # Literal backslashes + yield start, "\\" * backslashes, None + start += backslashes * 2 + if escaped: + # Escape of tag + yield start, full_text[len(escapes) :], None + position = end + continue + text, equals, parameters = tag_text.partition("=") + yield start, None, _Tag(text, parameters if equals else None) + position = end + if position < len(markup): + yield position, markup[position:], None + + +def render( + markup: str, + style: Union[str, Style] = "", + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, +) -> Text: + """Render console markup in to a Text instance. + + Args: + markup (str): A string containing console markup. + emoji (bool, optional): Also render emoji code. Defaults to True. + + Raises: + MarkupError: If there is a syntax error in the markup. + + Returns: + Text: A test instance. + """ + emoji_replace = _emoji_replace + if "[" not in markup: + return Text( + emoji_replace(markup, default_variant=emoji_variant) if emoji else markup, + style=style, + ) + text = Text(style=style) + append = text.append + normalize = Style.normalize + + style_stack: List[Tuple[int, Tag]] = [] + pop = style_stack.pop + + spans: List[Span] = [] + append_span = spans.append + + _Span = Span + _Tag = Tag + + def pop_style(style_name: str) -> Tuple[int, Tag]: + """Pop tag matching given style name.""" + for index, (_, tag) in enumerate(reversed(style_stack), 1): + if tag.name == style_name: + return pop(-index) + raise KeyError(style_name) + + for position, plain_text, tag in _parse(markup): + if plain_text is not None: + append(emoji_replace(plain_text) if emoji else plain_text) + elif tag is not None: + if tag.name.startswith("/"): # Closing tag + style_name = tag.name[1:].strip() + + if style_name: # explicit close + style_name = normalize(style_name) + try: + start, open_tag = pop_style(style_name) + except KeyError: + raise MarkupError( + f"closing tag '{tag.markup}' at position {position} doesn't match any open tag" + ) from None + else: # implicit close + try: + start, open_tag = pop() + except IndexError: + raise MarkupError( + f"closing tag '[/]' at position {position} has nothing to close" + ) from None + + if open_tag.name.startswith("@"): + if open_tag.parameters: + handler_name = "" + parameters = open_tag.parameters.strip() + handler_match = RE_HANDLER.match(parameters) + if handler_match is not None: + handler_name, match_parameters = handler_match.groups() + parameters = ( + "()" if match_parameters is None else match_parameters + ) + + try: + meta_params = literal_eval(parameters) + except SyntaxError as error: + raise MarkupError( + f"error parsing {parameters!r} in {open_tag.parameters!r}; {error.msg}" + ) + except Exception as error: + raise MarkupError( + f"error parsing {open_tag.parameters!r}; {error}" + ) from None + + if handler_name: + meta_params = ( + handler_name, + meta_params + if isinstance(meta_params, tuple) + else (meta_params,), + ) + + else: + meta_params = () + + append_span( + _Span( + start, len(text), Style(meta={open_tag.name: meta_params}) + ) + ) + else: + append_span(_Span(start, len(text), str(open_tag))) + + else: # Opening tag + normalized_tag = _Tag(normalize(tag.name), tag.parameters) + style_stack.append((len(text), normalized_tag)) + + text_length = len(text) + while style_stack: + start, tag = style_stack.pop() + style = str(tag) + if style: + append_span(_Span(start, text_length, style)) + + text.spans = sorted(spans[::-1], key=attrgetter("start")) + return text + + +if __name__ == "__main__": # pragma: no cover + + MARKUP = [ + "[red]Hello World[/red]", + "[magenta]Hello [b]World[/b]", + "[bold]Bold[italic] bold and italic [/bold]italic[/italic]", + "Click [link=https://www.willmcgugan.com]here[/link] to visit my Blog", + ":warning-emoji: [bold red blink] DANGER![/]", + ] + + from pip._vendor.rich.table import Table + from pip._vendor.rich import print + + grid = Table("Markup", "Result", padding=(0, 1)) + + for markup in MARKUP: + grid.add_row(Text(markup), markup) + + print(grid) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/measure.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/measure.py new file mode 100644 index 0000000..aea238d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/measure.py @@ -0,0 +1,149 @@ +from operator import itemgetter +from typing import Callable, Iterable, NamedTuple, Optional, TYPE_CHECKING + +from . import errors +from .protocol import is_renderable, rich_cast + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType + + +class Measurement(NamedTuple): + """Stores the minimum and maximum widths (in characters) required to render an object.""" + + minimum: int + """Minimum number of cells required to render.""" + maximum: int + """Maximum number of cells required to render.""" + + @property + def span(self) -> int: + """Get difference between maximum and minimum.""" + return self.maximum - self.minimum + + def normalize(self) -> "Measurement": + """Get measurement that ensures that minimum <= maximum and minimum >= 0 + + Returns: + Measurement: A normalized measurement. + """ + minimum, maximum = self + minimum = min(max(0, minimum), maximum) + return Measurement(max(0, minimum), max(0, max(minimum, maximum))) + + def with_maximum(self, width: int) -> "Measurement": + """Get a RenderableWith where the widths are <= width. + + Args: + width (int): Maximum desired width. + + Returns: + Measurement: New Measurement object. + """ + minimum, maximum = self + return Measurement(min(minimum, width), min(maximum, width)) + + def with_minimum(self, width: int) -> "Measurement": + """Get a RenderableWith where the widths are >= width. + + Args: + width (int): Minimum desired width. + + Returns: + Measurement: New Measurement object. + """ + minimum, maximum = self + width = max(0, width) + return Measurement(max(minimum, width), max(maximum, width)) + + def clamp( + self, min_width: Optional[int] = None, max_width: Optional[int] = None + ) -> "Measurement": + """Clamp a measurement within the specified range. + + Args: + min_width (int): Minimum desired width, or ``None`` for no minimum. Defaults to None. + max_width (int): Maximum desired width, or ``None`` for no maximum. Defaults to None. + + Returns: + Measurement: New Measurement object. + """ + measurement = self + if min_width is not None: + measurement = measurement.with_minimum(min_width) + if max_width is not None: + measurement = measurement.with_maximum(max_width) + return measurement + + @classmethod + def get( + cls, console: "Console", options: "ConsoleOptions", renderable: "RenderableType" + ) -> "Measurement": + """Get a measurement for a renderable. + + Args: + console (~rich.console.Console): Console instance. + options (~rich.console.ConsoleOptions): Console options. + renderable (RenderableType): An object that may be rendered with Rich. + + Raises: + errors.NotRenderableError: If the object is not renderable. + + Returns: + Measurement: Measurement object containing range of character widths required to render the object. + """ + _max_width = options.max_width + if _max_width < 1: + return Measurement(0, 0) + if isinstance(renderable, str): + renderable = console.render_str(renderable, markup=options.markup) + renderable = rich_cast(renderable) + if is_renderable(renderable): + get_console_width: Optional[ + Callable[["Console", "ConsoleOptions"], "Measurement"] + ] = getattr(renderable, "__rich_measure__", None) + if get_console_width is not None: + render_width = ( + get_console_width(console, options) + .normalize() + .with_maximum(_max_width) + ) + if render_width.maximum < 1: + return Measurement(0, 0) + return render_width.normalize() + else: + return Measurement(0, _max_width) + else: + raise errors.NotRenderableError( + f"Unable to get render width for {renderable!r}; " + "a str, Segment, or object with __rich_console__ method is required" + ) + + +def measure_renderables( + console: "Console", + options: "ConsoleOptions", + renderables: Iterable["RenderableType"], +) -> "Measurement": + """Get a measurement that would fit a number of renderables. + + Args: + console (~rich.console.Console): Console instance. + options (~rich.console.ConsoleOptions): Console options. + renderables (Iterable[RenderableType]): One or more renderable objects. + + Returns: + Measurement: Measurement object containing range of character widths required to + contain all given renderables. + """ + if not renderables: + return Measurement(0, 0) + get_measurement = Measurement.get + measurements = [ + get_measurement(console, options, renderable) for renderable in renderables + ] + measured_width = Measurement( + max(measurements, key=itemgetter(0)).minimum, + max(measurements, key=itemgetter(1)).maximum, + ) + return measured_width diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/padding.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/padding.py new file mode 100644 index 0000000..1b2204f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/padding.py @@ -0,0 +1,141 @@ +from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + RenderableType, + RenderResult, + ) +from .jupyter import JupyterMixin +from .measure import Measurement +from .style import Style +from .segment import Segment + + +PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]] + + +class Padding(JupyterMixin): + """Draw space around content. + + Example: + >>> print(Padding("Hello", (2, 4), style="on blue")) + + Args: + renderable (RenderableType): String or other renderable. + pad (Union[int, Tuple[int]]): Padding for top, right, bottom, and left borders. + May be specified with 1, 2, or 4 integers (CSS style). + style (Union[str, Style], optional): Style for padding characters. Defaults to "none". + expand (bool, optional): Expand padding to fit available width. Defaults to True. + """ + + def __init__( + self, + renderable: "RenderableType", + pad: "PaddingDimensions" = (0, 0, 0, 0), + *, + style: Union[str, Style] = "none", + expand: bool = True, + ): + self.renderable = renderable + self.top, self.right, self.bottom, self.left = self.unpack(pad) + self.style = style + self.expand = expand + + @classmethod + def indent(cls, renderable: "RenderableType", level: int) -> "Padding": + """Make padding instance to render an indent. + + Args: + renderable (RenderableType): String or other renderable. + level (int): Number of characters to indent. + + Returns: + Padding: A Padding instance. + """ + + return Padding(renderable, pad=(0, 0, 0, level), expand=False) + + @staticmethod + def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]: + """Unpack padding specified in CSS style.""" + if isinstance(pad, int): + return (pad, pad, pad, pad) + if len(pad) == 1: + _pad = pad[0] + return (_pad, _pad, _pad, _pad) + if len(pad) == 2: + pad_top, pad_right = cast(Tuple[int, int], pad) + return (pad_top, pad_right, pad_top, pad_right) + if len(pad) == 4: + top, right, bottom, left = cast(Tuple[int, int, int, int], pad) + return (top, right, bottom, left) + raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given") + + def __repr__(self) -> str: + return f"Padding({self.renderable!r}, ({self.top},{self.right},{self.bottom},{self.left}))" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + style = console.get_style(self.style) + if self.expand: + width = options.max_width + else: + width = min( + Measurement.get(console, options, self.renderable).maximum + + self.left + + self.right, + options.max_width, + ) + render_options = options.update_width(width - self.left - self.right) + if render_options.height is not None: + render_options = render_options.update_height( + height=render_options.height - self.top - self.bottom + ) + lines = console.render_lines( + self.renderable, render_options, style=style, pad=True + ) + _Segment = Segment + + left = _Segment(" " * self.left, style) if self.left else None + right = ( + [_Segment(f'{" " * self.right}', style), _Segment.line()] + if self.right + else [_Segment.line()] + ) + blank_line: Optional[List[Segment]] = None + if self.top: + blank_line = [_Segment(f'{" " * width}\n', style)] + yield from blank_line * self.top + if left: + for line in lines: + yield left + yield from line + yield from right + else: + for line in lines: + yield from line + yield from right + if self.bottom: + blank_line = blank_line or [_Segment(f'{" " * width}\n', style)] + yield from blank_line * self.bottom + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + max_width = options.max_width + extra_width = self.left + self.right + if max_width - extra_width < 1: + return Measurement(max_width, max_width) + measure_min, measure_max = Measurement.get(console, options, self.renderable) + measurement = Measurement(measure_min + extra_width, measure_max + extra_width) + measurement = measurement.with_maximum(max_width) + return measurement + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich import print + + print(Padding("Hello, World", (2, 4), style="on blue")) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/pager.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/pager.py new file mode 100644 index 0000000..dbfb973 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/pager.py @@ -0,0 +1,34 @@ +from abc import ABC, abstractmethod +from typing import Any, Callable + + +class Pager(ABC): + """Base class for a pager.""" + + @abstractmethod + def show(self, content: str) -> None: + """Show content in pager. + + Args: + content (str): Content to be displayed. + """ + + +class SystemPager(Pager): + """Uses the pager installed on the system.""" + + def _pager(self, content: str) -> Any: #  pragma: no cover + return __import__("pydoc").pager(content) + + def show(self, content: str) -> None: + """Use the same pager used by pydoc.""" + self._pager(content) + + +if __name__ == "__main__": # pragma: no cover + from .__main__ import make_test_card + from .console import Console + + console = Console() + with console.pager(styles=True): + console.print(make_test_card()) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/palette.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/palette.py new file mode 100644 index 0000000..fa0c4dd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/palette.py @@ -0,0 +1,100 @@ +from math import sqrt +from functools import lru_cache +from typing import Sequence, Tuple, TYPE_CHECKING + +from .color_triplet import ColorTriplet + +if TYPE_CHECKING: + from pip._vendor.rich.table import Table + + +class Palette: + """A palette of available colors.""" + + def __init__(self, colors: Sequence[Tuple[int, int, int]]): + self._colors = colors + + def __getitem__(self, number: int) -> ColorTriplet: + return ColorTriplet(*self._colors[number]) + + def __rich__(self) -> "Table": + from pip._vendor.rich.color import Color + from pip._vendor.rich.style import Style + from pip._vendor.rich.text import Text + from pip._vendor.rich.table import Table + + table = Table( + "index", + "RGB", + "Color", + title="Palette", + caption=f"{len(self._colors)} colors", + highlight=True, + caption_justify="right", + ) + for index, color in enumerate(self._colors): + table.add_row( + str(index), + repr(color), + Text(" " * 16, style=Style(bgcolor=Color.from_rgb(*color))), + ) + return table + + # This is somewhat inefficient and needs caching + @lru_cache(maxsize=1024) + def match(self, color: Tuple[int, int, int]) -> int: + """Find a color from a palette that most closely matches a given color. + + Args: + color (Tuple[int, int, int]): RGB components in range 0 > 255. + + Returns: + int: Index of closes matching color. + """ + red1, green1, blue1 = color + _sqrt = sqrt + get_color = self._colors.__getitem__ + + def get_color_distance(index: int) -> float: + """Get the distance to a color.""" + red2, green2, blue2 = get_color(index) + red_mean = (red1 + red2) // 2 + red = red1 - red2 + green = green1 - green2 + blue = blue1 - blue2 + return _sqrt( + (((512 + red_mean) * red * red) >> 8) + + 4 * green * green + + (((767 - red_mean) * blue * blue) >> 8) + ) + + min_index = min(range(len(self._colors)), key=get_color_distance) + return min_index + + +if __name__ == "__main__": # pragma: no cover + import colorsys + from typing import Iterable + from pip._vendor.rich.color import Color + from pip._vendor.rich.console import Console, ConsoleOptions + from pip._vendor.rich.segment import Segment + from pip._vendor.rich.style import Style + + class ColorBox: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> Iterable[Segment]: + height = console.size.height - 3 + for y in range(0, height): + for x in range(options.max_width): + h = x / options.max_width + l = y / (height + 1) + r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0) + r2, g2, b2 = colorsys.hls_to_rgb(h, l + (1 / height / 2), 1.0) + bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255) + color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255) + yield Segment("â–„", Style(color=color, bgcolor=bgcolor)) + yield Segment.line() + + console = Console() + console.print(ColorBox()) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/panel.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/panel.py new file mode 100644 index 0000000..151fe5f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/panel.py @@ -0,0 +1,250 @@ +from typing import Optional, TYPE_CHECKING + +from .box import Box, ROUNDED + +from .align import AlignMethod +from .jupyter import JupyterMixin +from .measure import Measurement, measure_renderables +from .padding import Padding, PaddingDimensions +from .style import StyleType +from .text import Text, TextType +from .segment import Segment + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + + +class Panel(JupyterMixin): + """A console renderable that draws a border around its contents. + + Example: + >>> console.print(Panel("Hello, World!")) + + Args: + renderable (RenderableType): A console renderable object. + box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`. + Defaults to box.ROUNDED. + safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. + expand (bool, optional): If True the panel will stretch to fill the console + width, otherwise it will be sized to fit the contents. Defaults to True. + style (str, optional): The style of the panel (border and contents). Defaults to "none". + border_style (str, optional): The style of the border. Defaults to "none". + width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect. + height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect. + padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0. + highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False. + """ + + def __init__( + self, + renderable: "RenderableType", + box: Box = ROUNDED, + *, + title: Optional[TextType] = None, + title_align: AlignMethod = "center", + subtitle: Optional[TextType] = None, + subtitle_align: AlignMethod = "center", + safe_box: Optional[bool] = None, + expand: bool = True, + style: StyleType = "none", + border_style: StyleType = "none", + width: Optional[int] = None, + height: Optional[int] = None, + padding: PaddingDimensions = (0, 1), + highlight: bool = False, + ) -> None: + self.renderable = renderable + self.box = box + self.title = title + self.title_align: AlignMethod = title_align + self.subtitle = subtitle + self.subtitle_align = subtitle_align + self.safe_box = safe_box + self.expand = expand + self.style = style + self.border_style = border_style + self.width = width + self.height = height + self.padding = padding + self.highlight = highlight + + @classmethod + def fit( + cls, + renderable: "RenderableType", + box: Box = ROUNDED, + *, + title: Optional[TextType] = None, + title_align: AlignMethod = "center", + subtitle: Optional[TextType] = None, + subtitle_align: AlignMethod = "center", + safe_box: Optional[bool] = None, + style: StyleType = "none", + border_style: StyleType = "none", + width: Optional[int] = None, + padding: PaddingDimensions = (0, 1), + ) -> "Panel": + """An alternative constructor that sets expand=False.""" + return cls( + renderable, + box, + title=title, + title_align=title_align, + subtitle=subtitle, + subtitle_align=subtitle_align, + safe_box=safe_box, + style=style, + border_style=border_style, + width=width, + padding=padding, + expand=False, + ) + + @property + def _title(self) -> Optional[Text]: + if self.title: + title_text = ( + Text.from_markup(self.title) + if isinstance(self.title, str) + else self.title.copy() + ) + title_text.end = "" + title_text.plain = title_text.plain.replace("\n", " ") + title_text.no_wrap = True + title_text.expand_tabs() + title_text.pad(1) + return title_text + return None + + @property + def _subtitle(self) -> Optional[Text]: + if self.subtitle: + subtitle_text = ( + Text.from_markup(self.subtitle) + if isinstance(self.subtitle, str) + else self.subtitle.copy() + ) + subtitle_text.end = "" + subtitle_text.plain = subtitle_text.plain.replace("\n", " ") + subtitle_text.no_wrap = True + subtitle_text.expand_tabs() + subtitle_text.pad(1) + return subtitle_text + return None + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + _padding = Padding.unpack(self.padding) + renderable = ( + Padding(self.renderable, _padding) if any(_padding) else self.renderable + ) + style = console.get_style(self.style) + border_style = style + console.get_style(self.border_style) + width = ( + options.max_width + if self.width is None + else min(options.max_width, self.width) + ) + + safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box + box = self.box.substitute(options, safe=safe_box) + + title_text = self._title + if title_text is not None: + title_text.style = border_style + + child_width = ( + width - 2 + if self.expand + else console.measure( + renderable, options=options.update_width(width - 2) + ).maximum + ) + child_height = self.height or options.height or None + if child_height: + child_height -= 2 + if title_text is not None: + child_width = min( + options.max_width - 2, max(child_width, title_text.cell_len + 2) + ) + + width = child_width + 2 + child_options = options.update( + width=child_width, height=child_height, highlight=self.highlight + ) + lines = console.render_lines(renderable, child_options, style=style) + + line_start = Segment(box.mid_left, border_style) + line_end = Segment(f"{box.mid_right}", border_style) + new_line = Segment.line() + if title_text is None or width <= 4: + yield Segment(box.get_top([width - 2]), border_style) + else: + title_text.align(self.title_align, width - 4, character=box.top) + yield Segment(box.top_left + box.top, border_style) + yield from console.render(title_text) + yield Segment(box.top + box.top_right, border_style) + + yield new_line + for line in lines: + yield line_start + yield from line + yield line_end + yield new_line + + subtitle_text = self._subtitle + if subtitle_text is not None: + subtitle_text.style = border_style + + if subtitle_text is None or width <= 4: + yield Segment(box.get_bottom([width - 2]), border_style) + else: + subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom) + yield Segment(box.bottom_left + box.bottom, border_style) + yield from console.render(subtitle_text) + yield Segment(box.bottom + box.bottom_right, border_style) + + yield new_line + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + _title = self._title + _, right, _, left = Padding.unpack(self.padding) + padding = left + right + renderables = [self.renderable, _title] if _title else [self.renderable] + + if self.width is None: + width = ( + measure_renderables( + console, + options.update_width(options.max_width - padding - 2), + renderables, + ).maximum + + padding + + 2 + ) + else: + width = self.width + return Measurement(width, width) + + +if __name__ == "__main__": # pragma: no cover + from .console import Console + + c = Console() + + from .padding import Padding + from .box import ROUNDED, DOUBLE + + p = Panel( + "Hello, World!", + title="rich.Panel", + style="white on blue", + box=DOUBLE, + padding=1, + ) + + c.print() + c.print(p) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/pretty.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/pretty.py new file mode 100644 index 0000000..606ee33 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/pretty.py @@ -0,0 +1,903 @@ +import builtins +import dataclasses +import inspect +import os +import re +import sys +from array import array +from collections import Counter, UserDict, UserList, defaultdict, deque +from dataclasses import dataclass, fields, is_dataclass +from inspect import isclass +from itertools import islice +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + DefaultDict, + Dict, + Iterable, + List, + Optional, + Set, + Tuple, + Union, +) + +from pip._vendor.rich.repr import RichReprResult + +try: + import attr as _attr_module +except ImportError: # pragma: no cover + _attr_module = None # type: ignore + + +from . import get_console +from ._loop import loop_last +from ._pick import pick_bool +from .abc import RichRenderable +from .cells import cell_len +from .highlighter import ReprHighlighter +from .jupyter import JupyterMixin, JupyterRenderable +from .measure import Measurement +from .text import Text + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + HighlighterType, + JustifyMethod, + OverflowMethod, + RenderResult, + ) + + +def _is_attr_object(obj: Any) -> bool: + """Check if an object was created with attrs module.""" + return _attr_module is not None and _attr_module.has(type(obj)) + + +def _get_attr_fields(obj: Any) -> Iterable["_attr_module.Attribute[Any]"]: + """Get fields for an attrs object.""" + return _attr_module.fields(type(obj)) if _attr_module is not None else [] + + +def _is_dataclass_repr(obj: object) -> bool: + """Check if an instance of a dataclass contains the default repr. + + Args: + obj (object): A dataclass instance. + + Returns: + bool: True if the default repr is used, False if there is a custom repr. + """ + # Digging in to a lot of internals here + # Catching all exceptions in case something is missing on a non CPython implementation + try: + return obj.__repr__.__code__.co_filename == dataclasses.__file__ + except Exception: # pragma: no coverage + return False + + +def _ipy_display_hook( + value: Any, + console: Optional["Console"] = None, + overflow: "OverflowMethod" = "ignore", + crop: bool = False, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + expand_all: bool = False, +) -> None: + from .console import ConsoleRenderable # needed here to prevent circular import + + # always skip rich generated jupyter renderables or None values + if isinstance(value, JupyterRenderable) or value is None: + return + + console = console or get_console() + if console.is_jupyter: + # Delegate rendering to IPython if the object (and IPython) supports it + # https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display + ipython_repr_methods = [ + "_repr_html_", + "_repr_markdown_", + "_repr_json_", + "_repr_latex_", + "_repr_jpeg_", + "_repr_png_", + "_repr_svg_", + "_repr_mimebundle_", + ] + for repr_method in ipython_repr_methods: + method = getattr(value, repr_method, None) + if inspect.ismethod(method): + # Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods + # specifies that if they return None, then they should not be rendered + # by the notebook. + try: + repr_result = method() + except Exception: + continue # If the method raises, treat it as if it doesn't exist, try any others + if repr_result is not None: + return # Delegate rendering to IPython + + # certain renderables should start on a new line + if isinstance(value, ConsoleRenderable): + console.line() + + console.print( + value + if isinstance(value, RichRenderable) + else Pretty( + value, + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + expand_all=expand_all, + margin=12, + ), + crop=crop, + new_line_start=True, + ) + + +def install( + console: Optional["Console"] = None, + overflow: "OverflowMethod" = "ignore", + crop: bool = False, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + expand_all: bool = False, +) -> None: + """Install automatic pretty printing in the Python REPL. + + Args: + console (Console, optional): Console instance or ``None`` to use global console. Defaults to None. + overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore". + crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False. + indent_guides (bool, optional): Enable indentation guides. Defaults to False. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. + expand_all (bool, optional): Expand all containers. Defaults to False. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + """ + from pip._vendor.rich import get_console + + console = console or get_console() + assert console is not None + + def display_hook(value: Any) -> None: + """Replacement sys.displayhook which prettifies objects with Rich.""" + if value is not None: + assert console is not None + builtins._ = None # type: ignore + console.print( + value + if isinstance(value, RichRenderable) + else Pretty( + value, + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + expand_all=expand_all, + ), + crop=crop, + ) + builtins._ = value # type: ignore + + try: # pragma: no cover + ip = get_ipython() # type: ignore + from IPython.core.formatters import BaseFormatter + + class RichFormatter(BaseFormatter): # type: ignore + pprint: bool = True + + def __call__(self, value: Any) -> Any: + if self.pprint: + return _ipy_display_hook( + value, + console=get_console(), + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + expand_all=expand_all, + ) + else: + return repr(value) + + # replace plain text formatter with rich formatter + rich_formatter = RichFormatter() + ip.display_formatter.formatters["text/plain"] = rich_formatter + except Exception: + sys.displayhook = display_hook + + +class Pretty(JupyterMixin): + """A rich renderable that pretty prints an object. + + Args: + _object (Any): An object to pretty print. + highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None. + indent_size (int, optional): Number of spaces in indent. Defaults to 4. + justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None. + overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None. + no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False. + indent_guides (bool, optional): Enable indentation guides. Defaults to False. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. + max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None. + expand_all (bool, optional): Expand all containers. Defaults to False. + margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0. + insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False. + """ + + def __init__( + self, + _object: Any, + highlighter: Optional["HighlighterType"] = None, + *, + indent_size: int = 4, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = False, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, + margin: int = 0, + insert_line: bool = False, + ) -> None: + self._object = _object + self.highlighter = highlighter or ReprHighlighter() + self.indent_size = indent_size + self.justify: Optional["JustifyMethod"] = justify + self.overflow: Optional["OverflowMethod"] = overflow + self.no_wrap = no_wrap + self.indent_guides = indent_guides + self.max_length = max_length + self.max_string = max_string + self.max_depth = max_depth + self.expand_all = expand_all + self.margin = margin + self.insert_line = insert_line + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + pretty_str = pretty_repr( + self._object, + max_width=options.max_width - self.margin, + indent_size=self.indent_size, + max_length=self.max_length, + max_string=self.max_string, + max_depth=self.max_depth, + expand_all=self.expand_all, + ) + pretty_text = Text( + pretty_str, + justify=self.justify or options.justify, + overflow=self.overflow or options.overflow, + no_wrap=pick_bool(self.no_wrap, options.no_wrap), + style="pretty", + ) + pretty_text = ( + self.highlighter(pretty_text) + if pretty_text + else Text( + f"{type(self._object)}.__repr__ returned empty string", + style="dim italic", + ) + ) + if self.indent_guides and not options.ascii_only: + pretty_text = pretty_text.with_indent_guides( + self.indent_size, style="repr.indent" + ) + if self.insert_line and "\n" in pretty_text: + yield "" + yield pretty_text + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + pretty_str = pretty_repr( + self._object, + max_width=options.max_width, + indent_size=self.indent_size, + max_length=self.max_length, + max_string=self.max_string, + ) + text_width = ( + max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0 + ) + return Measurement(text_width, text_width) + + +def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]: + return ( + f"defaultdict({_object.default_factory!r}, {{", + "})", + f"defaultdict({_object.default_factory!r}, {{}})", + ) + + +def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]: + return (f"array({_object.typecode!r}, [", "])", "array({_object.typecode!r})") + + +_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = { + os._Environ: lambda _object: ("environ({", "})", "environ({})"), + array: _get_braces_for_array, + defaultdict: _get_braces_for_defaultdict, + Counter: lambda _object: ("Counter({", "})", "Counter()"), + deque: lambda _object: ("deque([", "])", "deque()"), + dict: lambda _object: ("{", "}", "{}"), + UserDict: lambda _object: ("{", "}", "{}"), + frozenset: lambda _object: ("frozenset({", "})", "frozenset()"), + list: lambda _object: ("[", "]", "[]"), + UserList: lambda _object: ("[", "]", "[]"), + set: lambda _object: ("{", "}", "set()"), + tuple: lambda _object: ("(", ")", "()"), + MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"), +} +_CONTAINERS = tuple(_BRACES.keys()) +_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict) + + +def is_expandable(obj: Any) -> bool: + """Check if an object may be expanded by pretty print.""" + return ( + isinstance(obj, _CONTAINERS) + or (is_dataclass(obj)) + or (hasattr(obj, "__rich_repr__")) + or _is_attr_object(obj) + ) and not isclass(obj) + + +@dataclass +class Node: + """A node in a repr tree. May be atomic or a container.""" + + key_repr: str = "" + value_repr: str = "" + open_brace: str = "" + close_brace: str = "" + empty: str = "" + last: bool = False + is_tuple: bool = False + children: Optional[List["Node"]] = None + key_separator = ": " + separator: str = ", " + + def iter_tokens(self) -> Iterable[str]: + """Generate tokens for this node.""" + if self.key_repr: + yield self.key_repr + yield self.key_separator + if self.value_repr: + yield self.value_repr + elif self.children is not None: + if self.children: + yield self.open_brace + if self.is_tuple and len(self.children) == 1: + yield from self.children[0].iter_tokens() + yield "," + else: + for child in self.children: + yield from child.iter_tokens() + if not child.last: + yield self.separator + yield self.close_brace + else: + yield self.empty + + def check_length(self, start_length: int, max_length: int) -> bool: + """Check the length fits within a limit. + + Args: + start_length (int): Starting length of the line (indent, prefix, suffix). + max_length (int): Maximum length. + + Returns: + bool: True if the node can be rendered within max length, otherwise False. + """ + total_length = start_length + for token in self.iter_tokens(): + total_length += cell_len(token) + if total_length > max_length: + return False + return True + + def __str__(self) -> str: + repr_text = "".join(self.iter_tokens()) + return repr_text + + def render( + self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False + ) -> str: + """Render the node to a pretty repr. + + Args: + max_width (int, optional): Maximum width of the repr. Defaults to 80. + indent_size (int, optional): Size of indents. Defaults to 4. + expand_all (bool, optional): Expand all levels. Defaults to False. + + Returns: + str: A repr string of the original object. + """ + lines = [_Line(node=self, is_root=True)] + line_no = 0 + while line_no < len(lines): + line = lines[line_no] + if line.expandable and not line.expanded: + if expand_all or not line.check_length(max_width): + lines[line_no : line_no + 1] = line.expand(indent_size) + line_no += 1 + + repr_str = "\n".join(str(line) for line in lines) + return repr_str + + +@dataclass +class _Line: + """A line in repr output.""" + + parent: Optional["_Line"] = None + is_root: bool = False + node: Optional[Node] = None + text: str = "" + suffix: str = "" + whitespace: str = "" + expanded: bool = False + last: bool = False + + @property + def expandable(self) -> bool: + """Check if the line may be expanded.""" + return bool(self.node is not None and self.node.children) + + def check_length(self, max_length: int) -> bool: + """Check this line fits within a given number of cells.""" + start_length = ( + len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix) + ) + assert self.node is not None + return self.node.check_length(start_length, max_length) + + def expand(self, indent_size: int) -> Iterable["_Line"]: + """Expand this line by adding children on their own line.""" + node = self.node + assert node is not None + whitespace = self.whitespace + assert node.children + if node.key_repr: + new_line = yield _Line( + text=f"{node.key_repr}{node.key_separator}{node.open_brace}", + whitespace=whitespace, + ) + else: + new_line = yield _Line(text=node.open_brace, whitespace=whitespace) + child_whitespace = self.whitespace + " " * indent_size + tuple_of_one = node.is_tuple and len(node.children) == 1 + for last, child in loop_last(node.children): + separator = "," if tuple_of_one else node.separator + line = _Line( + parent=new_line, + node=child, + whitespace=child_whitespace, + suffix=separator, + last=last and not tuple_of_one, + ) + yield line + + yield _Line( + text=node.close_brace, + whitespace=whitespace, + suffix=self.suffix, + last=self.last, + ) + + def __str__(self) -> str: + if self.last: + return f"{self.whitespace}{self.text}{self.node or ''}" + else: + return ( + f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}" + ) + + +def traverse( + _object: Any, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, +) -> Node: + """Traverse object and generate a tree. + + Args: + _object (Any): Object to be traversed. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. + Defaults to None. + max_depth (int, optional): Maximum depth of data structures, or None for no maximum. + Defaults to None. + + Returns: + Node: The root of a tree structure which can be used to render a pretty repr. + """ + + def to_repr(obj: Any) -> str: + """Get repr string for an object, but catch errors.""" + if ( + max_string is not None + and isinstance(obj, (bytes, str)) + and len(obj) > max_string + ): + truncated = len(obj) - max_string + obj_repr = f"{obj[:max_string]!r}+{truncated}" + else: + try: + obj_repr = repr(obj) + except Exception as error: + obj_repr = f"" + return obj_repr + + visited_ids: Set[int] = set() + push_visited = visited_ids.add + pop_visited = visited_ids.remove + + def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node: + """Walk the object depth first.""" + + obj_type = type(obj) + py_version = (sys.version_info.major, sys.version_info.minor) + children: List[Node] + reached_max_depth = max_depth is not None and depth >= max_depth + + def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]: + for arg in rich_args: + if isinstance(arg, tuple): + if len(arg) == 3: + key, child, default = arg + if default == child: + continue + yield key, child + elif len(arg) == 2: + key, child = arg + yield key, child + elif len(arg) == 1: + yield arg[0] + else: + yield arg + + try: + fake_attributes = hasattr( + obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492" + ) + except Exception: + fake_attributes = False + + rich_repr_result: Optional[RichReprResult] = None + if not fake_attributes: + try: + if hasattr(obj, "__rich_repr__") and not isclass(obj): + rich_repr_result = obj.__rich_repr__() + except Exception: + pass + + if rich_repr_result is not None: + angular = getattr(obj.__rich_repr__, "angular", False) + args = list(iter_rich_args(rich_repr_result)) + class_name = obj.__class__.__name__ + + if args: + children = [] + append = children.append + + if reached_max_depth: + node = Node(value_repr=f"...") + else: + if angular: + node = Node( + open_brace=f"<{class_name} ", + close_brace=">", + children=children, + last=root, + separator=" ", + ) + else: + node = Node( + open_brace=f"{class_name}(", + close_brace=")", + children=children, + last=root, + ) + for last, arg in loop_last(args): + if isinstance(arg, tuple): + key, child = arg + child_node = _traverse(child, depth=depth + 1) + child_node.last = last + child_node.key_repr = key + child_node.key_separator = "=" + append(child_node) + else: + child_node = _traverse(arg, depth=depth + 1) + child_node.last = last + append(child_node) + else: + node = Node( + value_repr=f"<{class_name}>" if angular else f"{class_name}()", + children=[], + last=root, + ) + elif _is_attr_object(obj) and not fake_attributes: + children = [] + append = children.append + + attr_fields = _get_attr_fields(obj) + if attr_fields: + if reached_max_depth: + node = Node(value_repr=f"...") + else: + node = Node( + open_brace=f"{obj.__class__.__name__}(", + close_brace=")", + children=children, + last=root, + ) + + def iter_attrs() -> Iterable[ + Tuple[str, Any, Optional[Callable[[Any], str]]] + ]: + """Iterate over attr fields and values.""" + for attr in attr_fields: + if attr.repr: + try: + value = getattr(obj, attr.name) + except Exception as error: + # Can happen, albeit rarely + yield (attr.name, error, None) + else: + yield ( + attr.name, + value, + attr.repr if callable(attr.repr) else None, + ) + + for last, (name, value, repr_callable) in loop_last(iter_attrs()): + if repr_callable: + child_node = Node(value_repr=str(repr_callable(value))) + else: + child_node = _traverse(value, depth=depth + 1) + child_node.last = last + child_node.key_repr = name + child_node.key_separator = "=" + append(child_node) + else: + node = Node( + value_repr=f"{obj.__class__.__name__}()", children=[], last=root + ) + + elif ( + is_dataclass(obj) + and not isinstance(obj, type) + and not fake_attributes + and (_is_dataclass_repr(obj) or py_version == (3, 6)) + ): + obj_id = id(obj) + if obj_id in visited_ids: + # Recursion detected + return Node(value_repr="...") + push_visited(obj_id) + + children = [] + append = children.append + if reached_max_depth: + node = Node(value_repr=f"...") + else: + node = Node( + open_brace=f"{obj.__class__.__name__}(", + close_brace=")", + children=children, + last=root, + ) + + for last, field in loop_last( + field for field in fields(obj) if field.repr + ): + child_node = _traverse(getattr(obj, field.name), depth=depth + 1) + child_node.key_repr = field.name + child_node.last = last + child_node.key_separator = "=" + append(child_node) + + pop_visited(obj_id) + + elif isinstance(obj, _CONTAINERS): + for container_type in _CONTAINERS: + if isinstance(obj, container_type): + obj_type = container_type + break + + obj_id = id(obj) + if obj_id in visited_ids: + # Recursion detected + return Node(value_repr="...") + push_visited(obj_id) + + open_brace, close_brace, empty = _BRACES[obj_type](obj) + + if reached_max_depth: + node = Node(value_repr=f"...", last=root) + elif obj_type.__repr__ != type(obj).__repr__: + node = Node(value_repr=to_repr(obj), last=root) + elif obj: + children = [] + node = Node( + open_brace=open_brace, + close_brace=close_brace, + children=children, + last=root, + ) + append = children.append + num_items = len(obj) + last_item_index = num_items - 1 + + if isinstance(obj, _MAPPING_CONTAINERS): + iter_items = iter(obj.items()) + if max_length is not None: + iter_items = islice(iter_items, max_length) + for index, (key, child) in enumerate(iter_items): + child_node = _traverse(child, depth=depth + 1) + child_node.key_repr = to_repr(key) + child_node.last = index == last_item_index + append(child_node) + else: + iter_values = iter(obj) + if max_length is not None: + iter_values = islice(iter_values, max_length) + for index, child in enumerate(iter_values): + child_node = _traverse(child, depth=depth + 1) + child_node.last = index == last_item_index + append(child_node) + if max_length is not None and num_items > max_length: + append(Node(value_repr=f"... +{num_items-max_length}", last=True)) + else: + node = Node(empty=empty, children=[], last=root) + + pop_visited(obj_id) + else: + node = Node(value_repr=to_repr(obj), last=root) + node.is_tuple = isinstance(obj, tuple) + return node + + node = _traverse(_object, root=True) + return node + + +def pretty_repr( + _object: Any, + *, + max_width: int = 80, + indent_size: int = 4, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, +) -> str: + """Prettify repr string by expanding on to new lines to fit within a given width. + + Args: + _object (Any): Object to repr. + max_width (int, optional): Desired maximum width of repr string. Defaults to 80. + indent_size (int, optional): Number of spaces to indent. Defaults to 4. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. + Defaults to None. + max_depth (int, optional): Maximum depth of nested data structure, or None for no depth. + Defaults to None. + expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False. + + Returns: + str: A possibly multi-line representation of the object. + """ + + if isinstance(_object, Node): + node = _object + else: + node = traverse( + _object, max_length=max_length, max_string=max_string, max_depth=max_depth + ) + repr_str = node.render( + max_width=max_width, indent_size=indent_size, expand_all=expand_all + ) + return repr_str + + +def pprint( + _object: Any, + *, + console: Optional["Console"] = None, + indent_guides: bool = True, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, +) -> None: + """A convenience function for pretty printing. + + Args: + _object (Any): Object to pretty print. + console (Console, optional): Console instance, or None to use default. Defaults to None. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None. + max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None. + indent_guides (bool, optional): Enable indentation guides. Defaults to True. + expand_all (bool, optional): Expand all containers. Defaults to False. + """ + _console = get_console() if console is None else console + _console.print( + Pretty( + _object, + max_length=max_length, + max_string=max_string, + max_depth=max_depth, + indent_guides=indent_guides, + expand_all=expand_all, + overflow="ignore", + ), + soft_wrap=True, + ) + + +if __name__ == "__main__": # pragma: no cover + + class BrokenRepr: + def __repr__(self) -> str: + 1 / 0 + return "this will fail" + + d = defaultdict(int) + d["foo"] = 5 + data = { + "foo": [ + 1, + "Hello World!", + 100.123, + 323.232, + 432324.0, + {5, 6, 7, (1, 2, 3, 4), 8}, + ], + "bar": frozenset({1, 2, 3}), + "defaultdict": defaultdict( + list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]} + ), + "counter": Counter( + [ + "apple", + "orange", + "pear", + "kumquat", + "kumquat", + "durian" * 100, + ] + ), + "atomic": (False, True, None), + "Broken": BrokenRepr(), + } + data["foo"].append(data) # type: ignore + + from pip._vendor.rich import print + + print(Pretty(data, indent_guides=True, max_string=20)) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/progress.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/progress.py new file mode 100644 index 0000000..1f670db --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/progress.py @@ -0,0 +1,1036 @@ +from abc import ABC, abstractmethod +from collections import deque +from collections.abc import Sized +from dataclasses import dataclass, field +from datetime import timedelta +from math import ceil +from threading import Event, RLock, Thread +from types import TracebackType +from typing import ( + Any, + Callable, + Deque, + Dict, + Iterable, + List, + NamedTuple, + NewType, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, +) + +from . import filesize, get_console +from .console import Console, JustifyMethod, RenderableType, Group +from .highlighter import Highlighter +from .jupyter import JupyterMixin +from .live import Live +from .progress_bar import ProgressBar +from .spinner import Spinner +from .style import StyleType +from .table import Column, Table +from .text import Text, TextType + +TaskID = NewType("TaskID", int) + +ProgressType = TypeVar("ProgressType") + +GetTimeCallable = Callable[[], float] + + +class _TrackThread(Thread): + """A thread to periodically update progress.""" + + def __init__(self, progress: "Progress", task_id: "TaskID", update_period: float): + self.progress = progress + self.task_id = task_id + self.update_period = update_period + self.done = Event() + + self.completed = 0 + super().__init__() + + def run(self) -> None: + task_id = self.task_id + advance = self.progress.advance + update_period = self.update_period + last_completed = 0 + wait = self.done.wait + while not wait(update_period): + completed = self.completed + if last_completed != completed: + advance(task_id, completed - last_completed) + last_completed = completed + + self.progress.update(self.task_id, completed=self.completed, refresh=True) + + def __enter__(self) -> "_TrackThread": + self.start() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.done.set() + self.join() + + +def track( + sequence: Union[Sequence[ProgressType], Iterable[ProgressType]], + description: str = "Working...", + total: Optional[float] = None, + auto_refresh: bool = True, + console: Optional[Console] = None, + transient: bool = False, + get_time: Optional[Callable[[], float]] = None, + refresh_per_second: float = 10, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + update_period: float = 0.1, + disable: bool = False, +) -> Iterable[ProgressType]: + """Track progress by iterating over a sequence. + + Args: + sequence (Iterable[ProgressType]): A sequence (must support "len") you wish to iterate over. + description (str, optional): Description of task show next to progress bar. Defaults to "Working". + total: (float, optional): Total number of steps. Default is len(sequence). + auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True. + transient: (bool, optional): Clear the progress on exit. Defaults to False. + console (Console, optional): Console to write to. Default creates internal Console instance. + refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10. + style (StyleType, optional): Style for the bar background. Defaults to "bar.back". + complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". + finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done". + pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". + update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1. + disable (bool, optional): Disable display of progress. + Returns: + Iterable[ProgressType]: An iterable of the values in the sequence. + + """ + + columns: List["ProgressColumn"] = ( + [TextColumn("[progress.description]{task.description}")] if description else [] + ) + columns.extend( + ( + BarColumn( + style=style, + complete_style=complete_style, + finished_style=finished_style, + pulse_style=pulse_style, + ), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + TimeRemainingColumn(), + ) + ) + progress = Progress( + *columns, + auto_refresh=auto_refresh, + console=console, + transient=transient, + get_time=get_time, + refresh_per_second=refresh_per_second or 10, + disable=disable, + ) + + with progress: + yield from progress.track( + sequence, total=total, description=description, update_period=update_period + ) + + +class ProgressColumn(ABC): + """Base class for a widget to use in progress display.""" + + max_refresh: Optional[float] = None + + def __init__(self, table_column: Optional[Column] = None) -> None: + self._table_column = table_column + self._renderable_cache: Dict[TaskID, Tuple[float, RenderableType]] = {} + self._update_time: Optional[float] = None + + def get_table_column(self) -> Column: + """Get a table column, used to build tasks table.""" + return self._table_column or Column() + + def __call__(self, task: "Task") -> RenderableType: + """Called by the Progress object to return a renderable for the given task. + + Args: + task (Task): An object containing information regarding the task. + + Returns: + RenderableType: Anything renderable (including str). + """ + current_time = task.get_time() + if self.max_refresh is not None and not task.completed: + try: + timestamp, renderable = self._renderable_cache[task.id] + except KeyError: + pass + else: + if timestamp + self.max_refresh > current_time: + return renderable + + renderable = self.render(task) + self._renderable_cache[task.id] = (current_time, renderable) + return renderable + + @abstractmethod + def render(self, task: "Task") -> RenderableType: + """Should return a renderable object.""" + + +class RenderableColumn(ProgressColumn): + """A column to insert an arbitrary column. + + Args: + renderable (RenderableType, optional): Any renderable. Defaults to empty string. + """ + + def __init__( + self, renderable: RenderableType = "", *, table_column: Optional[Column] = None + ): + self.renderable = renderable + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> RenderableType: + return self.renderable + + +class SpinnerColumn(ProgressColumn): + """A column with a 'spinner' animation. + + Args: + spinner_name (str, optional): Name of spinner animation. Defaults to "dots". + style (StyleType, optional): Style of spinner. Defaults to "progress.spinner". + speed (float, optional): Speed factor of spinner. Defaults to 1.0. + finished_text (TextType, optional): Text used when task is finished. Defaults to " ". + """ + + def __init__( + self, + spinner_name: str = "dots", + style: Optional[StyleType] = "progress.spinner", + speed: float = 1.0, + finished_text: TextType = " ", + table_column: Optional[Column] = None, + ): + self.spinner = Spinner(spinner_name, style=style, speed=speed) + self.finished_text = ( + Text.from_markup(finished_text) + if isinstance(finished_text, str) + else finished_text + ) + super().__init__(table_column=table_column) + + def set_spinner( + self, + spinner_name: str, + spinner_style: Optional[StyleType] = "progress.spinner", + speed: float = 1.0, + ) -> None: + """Set a new spinner. + + Args: + spinner_name (str): Spinner name, see python -m rich.spinner. + spinner_style (Optional[StyleType], optional): Spinner style. Defaults to "progress.spinner". + speed (float, optional): Speed factor of spinner. Defaults to 1.0. + """ + self.spinner = Spinner(spinner_name, style=spinner_style, speed=speed) + + def render(self, task: "Task") -> RenderableType: + text = ( + self.finished_text + if task.finished + else self.spinner.render(task.get_time()) + ) + return text + + +class TextColumn(ProgressColumn): + """A column containing text.""" + + def __init__( + self, + text_format: str, + style: StyleType = "none", + justify: JustifyMethod = "left", + markup: bool = True, + highlighter: Optional[Highlighter] = None, + table_column: Optional[Column] = None, + ) -> None: + self.text_format = text_format + self.justify: JustifyMethod = justify + self.style = style + self.markup = markup + self.highlighter = highlighter + super().__init__(table_column=table_column or Column(no_wrap=True)) + + def render(self, task: "Task") -> Text: + _text = self.text_format.format(task=task) + if self.markup: + text = Text.from_markup(_text, style=self.style, justify=self.justify) + else: + text = Text(_text, style=self.style, justify=self.justify) + if self.highlighter: + self.highlighter.highlight(text) + return text + + +class BarColumn(ProgressColumn): + """Renders a visual progress bar. + + Args: + bar_width (Optional[int], optional): Width of bar or None for full width. Defaults to 40. + style (StyleType, optional): Style for the bar background. Defaults to "bar.back". + complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". + finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done". + pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". + """ + + def __init__( + self, + bar_width: Optional[int] = 40, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + table_column: Optional[Column] = None, + ) -> None: + self.bar_width = bar_width + self.style = style + self.complete_style = complete_style + self.finished_style = finished_style + self.pulse_style = pulse_style + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> ProgressBar: + """Gets a progress bar widget for a task.""" + return ProgressBar( + total=max(0, task.total), + completed=max(0, task.completed), + width=None if self.bar_width is None else max(1, self.bar_width), + pulse=not task.started, + animation_time=task.get_time(), + style=self.style, + complete_style=self.complete_style, + finished_style=self.finished_style, + pulse_style=self.pulse_style, + ) + + +class TimeElapsedColumn(ProgressColumn): + """Renders time elapsed.""" + + def render(self, task: "Task") -> Text: + """Show time remaining.""" + elapsed = task.finished_time if task.finished else task.elapsed + if elapsed is None: + return Text("-:--:--", style="progress.elapsed") + delta = timedelta(seconds=int(elapsed)) + return Text(str(delta), style="progress.elapsed") + + +class TimeRemainingColumn(ProgressColumn): + """Renders estimated time remaining.""" + + # Only refresh twice a second to prevent jitter + max_refresh = 0.5 + + def render(self, task: "Task") -> Text: + """Show time remaining.""" + remaining = task.time_remaining + if remaining is None: + return Text("-:--:--", style="progress.remaining") + remaining_delta = timedelta(seconds=int(remaining)) + return Text(str(remaining_delta), style="progress.remaining") + + +class FileSizeColumn(ProgressColumn): + """Renders completed filesize.""" + + def render(self, task: "Task") -> Text: + """Show data completed.""" + data_size = filesize.decimal(int(task.completed)) + return Text(data_size, style="progress.filesize") + + +class TotalFileSizeColumn(ProgressColumn): + """Renders total filesize.""" + + def render(self, task: "Task") -> Text: + """Show data completed.""" + data_size = filesize.decimal(int(task.total)) + return Text(data_size, style="progress.filesize.total") + + +class DownloadColumn(ProgressColumn): + """Renders file size downloaded and total, e.g. '0.5/2.3 GB'. + + Args: + binary_units (bool, optional): Use binary units, KiB, MiB etc. Defaults to False. + """ + + def __init__( + self, binary_units: bool = False, table_column: Optional[Column] = None + ) -> None: + self.binary_units = binary_units + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> Text: + """Calculate common unit for completed and total.""" + completed = int(task.completed) + total = int(task.total) + if self.binary_units: + unit, suffix = filesize.pick_unit_and_suffix( + total, + ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"], + 1024, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix( + total, ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], 1000 + ) + completed_ratio = completed / unit + total_ratio = total / unit + precision = 0 if unit == 1 else 1 + completed_str = f"{completed_ratio:,.{precision}f}" + total_str = f"{total_ratio:,.{precision}f}" + download_status = f"{completed_str}/{total_str} {suffix}" + download_text = Text(download_status, style="progress.download") + return download_text + + +class TransferSpeedColumn(ProgressColumn): + """Renders human readable transfer speed.""" + + def render(self, task: "Task") -> Text: + """Show data transfer speed.""" + speed = task.finished_speed or task.speed + if speed is None: + return Text("?", style="progress.data.speed") + data_speed = filesize.decimal(int(speed)) + return Text(f"{data_speed}/s", style="progress.data.speed") + + +class ProgressSample(NamedTuple): + """Sample of progress for a given time.""" + + timestamp: float + """Timestamp of sample.""" + completed: float + """Number of steps completed.""" + + +@dataclass +class Task: + """Information regarding a progress task. + + This object should be considered read-only outside of the :class:`~Progress` class. + + """ + + id: TaskID + """Task ID associated with this task (used in Progress methods).""" + + description: str + """str: Description of the task.""" + + total: float + """str: Total number of steps in this task.""" + + completed: float + """float: Number of steps completed""" + + _get_time: GetTimeCallable + """Callable to get the current time.""" + + finished_time: Optional[float] = None + """float: Time task was finished.""" + + visible: bool = True + """bool: Indicates if this task is visible in the progress display.""" + + fields: Dict[str, Any] = field(default_factory=dict) + """dict: Arbitrary fields passed in via Progress.update.""" + + start_time: Optional[float] = field(default=None, init=False, repr=False) + """Optional[float]: Time this task was started, or None if not started.""" + + stop_time: Optional[float] = field(default=None, init=False, repr=False) + """Optional[float]: Time this task was stopped, or None if not stopped.""" + + finished_speed: Optional[float] = None + """Optional[float]: The last speed for a finished task.""" + + _progress: Deque[ProgressSample] = field( + default_factory=deque, init=False, repr=False + ) + + _lock: RLock = field(repr=False, default_factory=RLock) + """Thread lock.""" + + def get_time(self) -> float: + """float: Get the current time, in seconds.""" + return self._get_time() + + @property + def started(self) -> bool: + """bool: Check if the task as started.""" + return self.start_time is not None + + @property + def remaining(self) -> float: + """float: Get the number of steps remaining.""" + return self.total - self.completed + + @property + def elapsed(self) -> Optional[float]: + """Optional[float]: Time elapsed since task was started, or ``None`` if the task hasn't started.""" + if self.start_time is None: + return None + if self.stop_time is not None: + return self.stop_time - self.start_time + return self.get_time() - self.start_time + + @property + def finished(self) -> bool: + """Check if the task has finished.""" + return self.finished_time is not None + + @property + def percentage(self) -> float: + """float: Get progress of task as a percentage.""" + if not self.total: + return 0.0 + completed = (self.completed / self.total) * 100.0 + completed = min(100.0, max(0.0, completed)) + return completed + + @property + def speed(self) -> Optional[float]: + """Optional[float]: Get the estimated speed in steps per second.""" + if self.start_time is None: + return None + with self._lock: + progress = self._progress + if not progress: + return None + total_time = progress[-1].timestamp - progress[0].timestamp + if total_time == 0: + return None + iter_progress = iter(progress) + next(iter_progress) + total_completed = sum(sample.completed for sample in iter_progress) + speed = total_completed / total_time + return speed + + @property + def time_remaining(self) -> Optional[float]: + """Optional[float]: Get estimated time to completion, or ``None`` if no data.""" + if self.finished: + return 0.0 + speed = self.speed + if not speed: + return None + estimate = ceil(self.remaining / speed) + return estimate + + def _reset(self) -> None: + """Reset progress.""" + self._progress.clear() + self.finished_time = None + self.finished_speed = None + + +class Progress(JupyterMixin): + """Renders an auto-updating progress bar(s). + + Args: + console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout. + auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`. + refresh_per_second (Optional[float], optional): Number of times per second to refresh the progress information or None to use default (10). Defaults to None. + speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30. + transient: (bool, optional): Clear the progress on exit. Defaults to False. + redirect_stdout: (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True. + redirect_stderr: (bool, optional): Enable redirection of stderr. Defaults to True. + get_time: (Callable, optional): A callable that gets the current time, or None to use Console.get_time. Defaults to None. + disable (bool, optional): Disable progress display. Defaults to False + expand (bool, optional): Expand tasks table to fit width. Defaults to False. + """ + + def __init__( + self, + *columns: Union[str, ProgressColumn], + console: Optional[Console] = None, + auto_refresh: bool = True, + refresh_per_second: float = 10, + speed_estimate_period: float = 30.0, + transient: bool = False, + redirect_stdout: bool = True, + redirect_stderr: bool = True, + get_time: Optional[GetTimeCallable] = None, + disable: bool = False, + expand: bool = False, + ) -> None: + assert ( + refresh_per_second is None or refresh_per_second > 0 + ), "refresh_per_second must be > 0" + self._lock = RLock() + self.columns = columns or ( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + TimeRemainingColumn(), + ) + self.speed_estimate_period = speed_estimate_period + + self.disable = disable + self.expand = expand + self._tasks: Dict[TaskID, Task] = {} + self._task_index: TaskID = TaskID(0) + self.live = Live( + console=console or get_console(), + auto_refresh=auto_refresh, + refresh_per_second=refresh_per_second, + transient=transient, + redirect_stdout=redirect_stdout, + redirect_stderr=redirect_stderr, + get_renderable=self.get_renderable, + ) + self.get_time = get_time or self.console.get_time + self.print = self.console.print + self.log = self.console.log + + @property + def console(self) -> Console: + return self.live.console + + @property + def tasks(self) -> List[Task]: + """Get a list of Task instances.""" + with self._lock: + return list(self._tasks.values()) + + @property + def task_ids(self) -> List[TaskID]: + """A list of task IDs.""" + with self._lock: + return list(self._tasks.keys()) + + @property + def finished(self) -> bool: + """Check if all tasks have been completed.""" + with self._lock: + if not self._tasks: + return True + return all(task.finished for task in self._tasks.values()) + + def start(self) -> None: + """Start the progress display.""" + if not self.disable: + self.live.start(refresh=True) + + def stop(self) -> None: + """Stop the progress display.""" + self.live.stop() + if not self.console.is_interactive: + self.console.print() + + def __enter__(self) -> "Progress": + self.start() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.stop() + + def track( + self, + sequence: Union[Iterable[ProgressType], Sequence[ProgressType]], + total: Optional[float] = None, + task_id: Optional[TaskID] = None, + description: str = "Working...", + update_period: float = 0.1, + ) -> Iterable[ProgressType]: + """Track progress by iterating over a sequence. + + Args: + sequence (Sequence[ProgressType]): A sequence of values you want to iterate over and track progress. + total: (float, optional): Total number of steps. Default is len(sequence). + task_id: (TaskID): Task to track. Default is new task. + description: (str, optional): Description of task, if new task is created. + update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1. + + Returns: + Iterable[ProgressType]: An iterable of values taken from the provided sequence. + """ + + if total is None: + if isinstance(sequence, Sized): + task_total = float(len(sequence)) + else: + raise ValueError( + f"unable to get size of {sequence!r}, please specify 'total'" + ) + else: + task_total = total + + if task_id is None: + task_id = self.add_task(description, total=task_total) + else: + self.update(task_id, total=task_total) + + if self.live.auto_refresh: + with _TrackThread(self, task_id, update_period) as track_thread: + for value in sequence: + yield value + track_thread.completed += 1 + else: + advance = self.advance + refresh = self.refresh + for value in sequence: + yield value + advance(task_id, 1) + refresh() + + def start_task(self, task_id: TaskID) -> None: + """Start a task. + + Starts a task (used when calculating elapsed time). You may need to call this manually, + if you called ``add_task`` with ``start=False``. + + Args: + task_id (TaskID): ID of task. + """ + with self._lock: + task = self._tasks[task_id] + if task.start_time is None: + task.start_time = self.get_time() + + def stop_task(self, task_id: TaskID) -> None: + """Stop a task. + + This will freeze the elapsed time on the task. + + Args: + task_id (TaskID): ID of task. + """ + with self._lock: + task = self._tasks[task_id] + current_time = self.get_time() + if task.start_time is None: + task.start_time = current_time + task.stop_time = current_time + + def update( + self, + task_id: TaskID, + *, + total: Optional[float] = None, + completed: Optional[float] = None, + advance: Optional[float] = None, + description: Optional[str] = None, + visible: Optional[bool] = None, + refresh: bool = False, + **fields: Any, + ) -> None: + """Update information associated with a task. + + Args: + task_id (TaskID): Task id (returned by add_task). + total (float, optional): Updates task.total if not None. + completed (float, optional): Updates task.completed if not None. + advance (float, optional): Add a value to task.completed if not None. + description (str, optional): Change task description if not None. + visible (bool, optional): Set visible flag if not None. + refresh (bool): Force a refresh of progress information. Default is False. + **fields (Any): Additional data fields required for rendering. + """ + with self._lock: + task = self._tasks[task_id] + completed_start = task.completed + + if total is not None and total != task.total: + task.total = total + task._reset() + if advance is not None: + task.completed += advance + if completed is not None: + task.completed = completed + if description is not None: + task.description = description + if visible is not None: + task.visible = visible + task.fields.update(fields) + update_completed = task.completed - completed_start + + current_time = self.get_time() + old_sample_time = current_time - self.speed_estimate_period + _progress = task._progress + + popleft = _progress.popleft + while _progress and _progress[0].timestamp < old_sample_time: + popleft() + while len(_progress) > 1000: + popleft() + if update_completed > 0: + _progress.append(ProgressSample(current_time, update_completed)) + if task.completed >= task.total and task.finished_time is None: + task.finished_time = task.elapsed + + if refresh: + self.refresh() + + def reset( + self, + task_id: TaskID, + *, + start: bool = True, + total: Optional[float] = None, + completed: int = 0, + visible: Optional[bool] = None, + description: Optional[str] = None, + **fields: Any, + ) -> None: + """Reset a task so completed is 0 and the clock is reset. + + Args: + task_id (TaskID): ID of task. + start (bool, optional): Start the task after reset. Defaults to True. + total (float, optional): New total steps in task, or None to use current total. Defaults to None. + completed (int, optional): Number of steps completed. Defaults to 0. + **fields (str): Additional data fields required for rendering. + """ + current_time = self.get_time() + with self._lock: + task = self._tasks[task_id] + task._reset() + task.start_time = current_time if start else None + if total is not None: + task.total = total + task.completed = completed + if visible is not None: + task.visible = visible + if fields: + task.fields = fields + if description is not None: + task.description = description + task.finished_time = None + self.refresh() + + def advance(self, task_id: TaskID, advance: float = 1) -> None: + """Advance task by a number of steps. + + Args: + task_id (TaskID): ID of task. + advance (float): Number of steps to advance. Default is 1. + """ + current_time = self.get_time() + with self._lock: + task = self._tasks[task_id] + completed_start = task.completed + task.completed += advance + update_completed = task.completed - completed_start + old_sample_time = current_time - self.speed_estimate_period + _progress = task._progress + + popleft = _progress.popleft + while _progress and _progress[0].timestamp < old_sample_time: + popleft() + while len(_progress) > 1000: + popleft() + _progress.append(ProgressSample(current_time, update_completed)) + if task.completed >= task.total and task.finished_time is None: + task.finished_time = task.elapsed + task.finished_speed = task.speed + + def refresh(self) -> None: + """Refresh (render) the progress information.""" + if not self.disable and self.live.is_started: + self.live.refresh() + + def get_renderable(self) -> RenderableType: + """Get a renderable for the progress display.""" + renderable = Group(*self.get_renderables()) + return renderable + + def get_renderables(self) -> Iterable[RenderableType]: + """Get a number of renderables for the progress display.""" + table = self.make_tasks_table(self.tasks) + yield table + + def make_tasks_table(self, tasks: Iterable[Task]) -> Table: + """Get a table to render the Progress display. + + Args: + tasks (Iterable[Task]): An iterable of Task instances, one per row of the table. + + Returns: + Table: A table instance. + """ + table_columns = ( + ( + Column(no_wrap=True) + if isinstance(_column, str) + else _column.get_table_column().copy() + ) + for _column in self.columns + ) + table = Table.grid(*table_columns, padding=(0, 1), expand=self.expand) + + for task in tasks: + if task.visible: + table.add_row( + *( + ( + column.format(task=task) + if isinstance(column, str) + else column(task) + ) + for column in self.columns + ) + ) + return table + + def __rich__(self) -> RenderableType: + """Makes the Progress class itself renderable.""" + with self._lock: + return self.get_renderable() + + def add_task( + self, + description: str, + start: bool = True, + total: float = 100.0, + completed: int = 0, + visible: bool = True, + **fields: Any, + ) -> TaskID: + """Add a new 'task' to the Progress display. + + Args: + description (str): A description of the task. + start (bool, optional): Start the task immediately (to calculate elapsed time). If set to False, + you will need to call `start` manually. Defaults to True. + total (float, optional): Number of total steps in the progress if know. Defaults to 100. + completed (int, optional): Number of steps completed so far.. Defaults to 0. + visible (bool, optional): Enable display of the task. Defaults to True. + **fields (str): Additional data fields required for rendering. + + Returns: + TaskID: An ID you can use when calling `update`. + """ + with self._lock: + task = Task( + self._task_index, + description, + total, + completed, + visible=visible, + fields=fields, + _get_time=self.get_time, + _lock=self._lock, + ) + self._tasks[self._task_index] = task + if start: + self.start_task(self._task_index) + new_task_index = self._task_index + self._task_index = TaskID(int(self._task_index) + 1) + self.refresh() + return new_task_index + + def remove_task(self, task_id: TaskID) -> None: + """Delete a task if it exists. + + Args: + task_id (TaskID): A task ID. + + """ + with self._lock: + del self._tasks[task_id] + + +if __name__ == "__main__": # pragma: no coverage + + import random + import time + + from .panel import Panel + from .rule import Rule + from .syntax import Syntax + from .table import Table + + syntax = Syntax( + '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value''', + "python", + line_numbers=True, + ) + + table = Table("foo", "bar", "baz") + table.add_row("1", "2", "3") + + progress_renderables = [ + "Text may be printed while the progress bars are rendering.", + Panel("In fact, [i]any[/i] renderable will work"), + "Such as [magenta]tables[/]...", + table, + "Pretty printed structures...", + {"type": "example", "text": "Pretty printed"}, + "Syntax...", + syntax, + Rule("Give it a try!"), + ] + + from itertools import cycle + + examples = cycle(progress_renderables) + + console = Console(record=True) + + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + TimeRemainingColumn(), + TimeElapsedColumn(), + console=console, + transient=True, + ) as progress: + + task1 = progress.add_task("[red]Downloading", total=1000) + task2 = progress.add_task("[green]Processing", total=1000) + task3 = progress.add_task("[yellow]Thinking", total=1000, start=False) + + while not progress.finished: + progress.update(task1, advance=0.5) + progress.update(task2, advance=0.3) + time.sleep(0.01) + if random.randint(0, 100) < 1: + progress.log(next(examples)) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/progress_bar.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/progress_bar.py new file mode 100644 index 0000000..1797b5f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/progress_bar.py @@ -0,0 +1,216 @@ +import math +from functools import lru_cache +from time import monotonic +from typing import Iterable, List, Optional + +from .color import Color, blend_rgb +from .color_triplet import ColorTriplet +from .console import Console, ConsoleOptions, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style, StyleType + +# Number of characters before 'pulse' animation repeats +PULSE_SIZE = 20 + + +class ProgressBar(JupyterMixin): + """Renders a (progress) bar. Used by rich.progress. + + Args: + total (float, optional): Number of steps in the bar. Defaults to 100. + completed (float, optional): Number of steps completed. Defaults to 0. + width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None. + pulse (bool, optional): Enable pulse effect. Defaults to False. + style (StyleType, optional): Style for the bar background. Defaults to "bar.back". + complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". + finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done". + pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". + animation_time (Optional[float], optional): Time in seconds to use for animation, or None to use system time. + """ + + def __init__( + self, + total: float = 100.0, + completed: float = 0, + width: Optional[int] = None, + pulse: bool = False, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + animation_time: Optional[float] = None, + ): + self.total = total + self.completed = completed + self.width = width + self.pulse = pulse + self.style = style + self.complete_style = complete_style + self.finished_style = finished_style + self.pulse_style = pulse_style + self.animation_time = animation_time + + self._pulse_segments: Optional[List[Segment]] = None + + def __repr__(self) -> str: + return f"" + + @property + def percentage_completed(self) -> float: + """Calculate percentage complete.""" + completed = (self.completed / self.total) * 100.0 + completed = min(100, max(0.0, completed)) + return completed + + @lru_cache(maxsize=16) + def _get_pulse_segments( + self, + fore_style: Style, + back_style: Style, + color_system: str, + no_color: bool, + ascii: bool = False, + ) -> List[Segment]: + """Get a list of segments to render a pulse animation. + + Returns: + List[Segment]: A list of segments, one segment per character. + """ + bar = "-" if ascii else "â”" + segments: List[Segment] = [] + if color_system not in ("standard", "eight_bit", "truecolor") or no_color: + segments += [Segment(bar, fore_style)] * (PULSE_SIZE // 2) + segments += [Segment(" " if no_color else bar, back_style)] * ( + PULSE_SIZE - (PULSE_SIZE // 2) + ) + return segments + + append = segments.append + fore_color = ( + fore_style.color.get_truecolor() + if fore_style.color + else ColorTriplet(255, 0, 255) + ) + back_color = ( + back_style.color.get_truecolor() + if back_style.color + else ColorTriplet(0, 0, 0) + ) + cos = math.cos + pi = math.pi + _Segment = Segment + _Style = Style + from_triplet = Color.from_triplet + + for index in range(PULSE_SIZE): + position = index / PULSE_SIZE + fade = 0.5 + cos((position * pi * 2)) / 2.0 + color = blend_rgb(fore_color, back_color, cross_fade=fade) + append(_Segment(bar, _Style(color=from_triplet(color)))) + return segments + + def update(self, completed: float, total: Optional[float] = None) -> None: + """Update progress with new values. + + Args: + completed (float): Number of steps completed. + total (float, optional): Total number of steps, or ``None`` to not change. Defaults to None. + """ + self.completed = completed + self.total = total if total is not None else self.total + + def _render_pulse( + self, console: Console, width: int, ascii: bool = False + ) -> Iterable[Segment]: + """Renders the pulse animation. + + Args: + console (Console): Console instance. + width (int): Width in characters of pulse animation. + + Returns: + RenderResult: [description] + + Yields: + Iterator[Segment]: Segments to render pulse + """ + fore_style = console.get_style(self.pulse_style, default="white") + back_style = console.get_style(self.style, default="black") + + pulse_segments = self._get_pulse_segments( + fore_style, back_style, console.color_system, console.no_color, ascii=ascii + ) + segment_count = len(pulse_segments) + current_time = ( + monotonic() if self.animation_time is None else self.animation_time + ) + segments = pulse_segments * (int(width / segment_count) + 2) + offset = int(-current_time * 15) % segment_count + segments = segments[offset : offset + width] + yield from segments + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + + width = min(self.width or options.max_width, options.max_width) + ascii = options.legacy_windows or options.ascii_only + if self.pulse: + yield from self._render_pulse(console, width, ascii=ascii) + return + + completed = min(self.total, max(0, self.completed)) + + bar = "-" if ascii else "â”" + half_bar_right = " " if ascii else "╸" + half_bar_left = " " if ascii else "╺" + complete_halves = ( + int(width * 2 * completed / self.total) if self.total else width * 2 + ) + bar_count = complete_halves // 2 + half_bar_count = complete_halves % 2 + style = console.get_style(self.style) + complete_style = console.get_style( + self.complete_style if self.completed < self.total else self.finished_style + ) + _Segment = Segment + if bar_count: + yield _Segment(bar * bar_count, complete_style) + if half_bar_count: + yield _Segment(half_bar_right * half_bar_count, complete_style) + + if not console.no_color: + remaining_bars = width - bar_count - half_bar_count + if remaining_bars and console.color_system is not None: + if not half_bar_count and bar_count: + yield _Segment(half_bar_left, style) + remaining_bars -= 1 + if remaining_bars: + yield _Segment(bar * remaining_bars, style) + + def __rich_measure__( + self, console: Console, options: ConsoleOptions + ) -> Measurement: + return ( + Measurement(self.width, self.width) + if self.width is not None + else Measurement(4, options.max_width) + ) + + +if __name__ == "__main__": # pragma: no cover + console = Console() + bar = ProgressBar(width=50, total=100) + + import time + + console.show_cursor(False) + for n in range(0, 101, 1): + bar.update(n) + console.print(bar) + console.file.write("\r") + time.sleep(0.05) + console.show_cursor(True) + console.print() diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/prompt.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/prompt.py new file mode 100644 index 0000000..b2cea2b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/prompt.py @@ -0,0 +1,376 @@ +from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload + +from . import get_console +from .console import Console +from .text import Text, TextType + +PromptType = TypeVar("PromptType") +DefaultType = TypeVar("DefaultType") + + +class PromptError(Exception): + """Exception base class for prompt related errors.""" + + +class InvalidResponse(PromptError): + """Exception to indicate a response was invalid. Raise this within process_response() to indicate an error + and provide an error message. + + Args: + message (Union[str, Text]): Error message. + """ + + def __init__(self, message: TextType) -> None: + self.message = message + + def __rich__(self) -> TextType: + return self.message + + +class PromptBase(Generic[PromptType]): + """Ask the user for input until a valid response is received. This is the base class, see one of + the concrete classes for examples. + + Args: + prompt (TextType, optional): Prompt text. Defaults to "". + console (Console, optional): A Console instance or None to use global console. Defaults to None. + password (bool, optional): Enable password input. Defaults to False. + choices (List[str], optional): A list of valid choices. Defaults to None. + show_default (bool, optional): Show default in prompt. Defaults to True. + show_choices (bool, optional): Show choices in prompt. Defaults to True. + """ + + response_type: type = str + + validate_error_message = "[prompt.invalid]Please enter a valid value" + illegal_choice_message = ( + "[prompt.invalid.choice]Please select one of the available options" + ) + prompt_suffix = ": " + + choices: Optional[List[str]] = None + + def __init__( + self, + prompt: TextType = "", + *, + console: Optional[Console] = None, + password: bool = False, + choices: Optional[List[str]] = None, + show_default: bool = True, + show_choices: bool = True, + ) -> None: + self.console = console or get_console() + self.prompt = ( + Text.from_markup(prompt, style="prompt") + if isinstance(prompt, str) + else prompt + ) + self.password = password + if choices is not None: + self.choices = choices + self.show_default = show_default + self.show_choices = show_choices + + @classmethod + @overload + def ask( + cls, + prompt: TextType = "", + *, + console: Optional[Console] = None, + password: bool = False, + choices: Optional[List[str]] = None, + show_default: bool = True, + show_choices: bool = True, + default: DefaultType, + stream: Optional[TextIO] = None, + ) -> Union[DefaultType, PromptType]: + ... + + @classmethod + @overload + def ask( + cls, + prompt: TextType = "", + *, + console: Optional[Console] = None, + password: bool = False, + choices: Optional[List[str]] = None, + show_default: bool = True, + show_choices: bool = True, + stream: Optional[TextIO] = None, + ) -> PromptType: + ... + + @classmethod + def ask( + cls, + prompt: TextType = "", + *, + console: Optional[Console] = None, + password: bool = False, + choices: Optional[List[str]] = None, + show_default: bool = True, + show_choices: bool = True, + default: Any = ..., + stream: Optional[TextIO] = None, + ) -> Any: + """Shortcut to construct and run a prompt loop and return the result. + + Example: + >>> filename = Prompt.ask("Enter a filename") + + Args: + prompt (TextType, optional): Prompt text. Defaults to "". + console (Console, optional): A Console instance or None to use global console. Defaults to None. + password (bool, optional): Enable password input. Defaults to False. + choices (List[str], optional): A list of valid choices. Defaults to None. + show_default (bool, optional): Show default in prompt. Defaults to True. + show_choices (bool, optional): Show choices in prompt. Defaults to True. + stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None. + """ + _prompt = cls( + prompt, + console=console, + password=password, + choices=choices, + show_default=show_default, + show_choices=show_choices, + ) + return _prompt(default=default, stream=stream) + + def render_default(self, default: DefaultType) -> Text: + """Turn the supplied default in to a Text instance. + + Args: + default (DefaultType): Default value. + + Returns: + Text: Text containing rendering of default value. + """ + return Text(f"({default})", "prompt.default") + + def make_prompt(self, default: DefaultType) -> Text: + """Make prompt text. + + Args: + default (DefaultType): Default value. + + Returns: + Text: Text to display in prompt. + """ + prompt = self.prompt.copy() + prompt.end = "" + + if self.show_choices and self.choices: + _choices = "/".join(self.choices) + choices = f"[{_choices}]" + prompt.append(" ") + prompt.append(choices, "prompt.choices") + + if ( + default != ... + and self.show_default + and isinstance(default, (str, self.response_type)) + ): + prompt.append(" ") + _default = self.render_default(default) + prompt.append(_default) + + prompt.append(self.prompt_suffix) + + return prompt + + @classmethod + def get_input( + cls, + console: Console, + prompt: TextType, + password: bool, + stream: Optional[TextIO] = None, + ) -> str: + """Get input from user. + + Args: + console (Console): Console instance. + prompt (TextType): Prompt text. + password (bool): Enable password entry. + + Returns: + str: String from user. + """ + return console.input(prompt, password=password, stream=stream) + + def check_choice(self, value: str) -> bool: + """Check value is in the list of valid choices. + + Args: + value (str): Value entered by user. + + Returns: + bool: True if choice was valid, otherwise False. + """ + assert self.choices is not None + return value.strip() in self.choices + + def process_response(self, value: str) -> PromptType: + """Process response from user, convert to prompt type. + + Args: + value (str): String typed by user. + + Raises: + InvalidResponse: If ``value`` is invalid. + + Returns: + PromptType: The value to be returned from ask method. + """ + value = value.strip() + try: + return_value = self.response_type(value) + except ValueError: + raise InvalidResponse(self.validate_error_message) + + if self.choices is not None and not self.check_choice(value): + raise InvalidResponse(self.illegal_choice_message) + + return return_value # type: ignore + + def on_validate_error(self, value: str, error: InvalidResponse) -> None: + """Called to handle validation error. + + Args: + value (str): String entered by user. + error (InvalidResponse): Exception instance the initiated the error. + """ + self.console.print(error) + + def pre_prompt(self) -> None: + """Hook to display something before the prompt.""" + + @overload + def __call__(self, *, stream: Optional[TextIO] = None) -> PromptType: + ... + + @overload + def __call__( + self, *, default: DefaultType, stream: Optional[TextIO] = None + ) -> Union[PromptType, DefaultType]: + ... + + def __call__(self, *, default: Any = ..., stream: Optional[TextIO] = None) -> Any: + """Run the prompt loop. + + Args: + default (Any, optional): Optional default value. + + Returns: + PromptType: Processed value. + """ + while True: + self.pre_prompt() + prompt = self.make_prompt(default) + value = self.get_input(self.console, prompt, self.password, stream=stream) + if value == "" and default != ...: + return default + try: + return_value = self.process_response(value) + except InvalidResponse as error: + self.on_validate_error(value, error) + continue + else: + return return_value + + +class Prompt(PromptBase[str]): + """A prompt that returns a str. + + Example: + >>> name = Prompt.ask("Enter your name") + + + """ + + response_type = str + + +class IntPrompt(PromptBase[int]): + """A prompt that returns an integer. + + Example: + >>> burrito_count = IntPrompt.ask("How many burritos do you want to order") + + """ + + response_type = int + validate_error_message = "[prompt.invalid]Please enter a valid integer number" + + +class FloatPrompt(PromptBase[int]): + """A prompt that returns a float. + + Example: + >>> temperature = FloatPrompt.ask("Enter desired temperature") + + """ + + response_type = float + validate_error_message = "[prompt.invalid]Please enter a number" + + +class Confirm(PromptBase[bool]): + """A yes / no confirmation prompt. + + Example: + >>> if Confirm.ask("Continue"): + run_job() + + """ + + response_type = bool + validate_error_message = "[prompt.invalid]Please enter Y or N" + choices: List[str] = ["y", "n"] + + def render_default(self, default: DefaultType) -> Text: + """Render the default as (y) or (n) rather than True/False.""" + yes, no = self.choices + return Text(f"({yes})" if default else f"({no})", style="prompt.default") + + def process_response(self, value: str) -> bool: + """Convert choices to a bool.""" + value = value.strip().lower() + if value not in self.choices: + raise InvalidResponse(self.validate_error_message) + return value == self.choices[0] + + +if __name__ == "__main__": # pragma: no cover + + from pip._vendor.rich import print + + if Confirm.ask("Run [i]prompt[/i] tests?", default=True): + while True: + result = IntPrompt.ask( + ":rocket: Enter a number between [b]1[/b] and [b]10[/b]", default=5 + ) + if result >= 1 and result <= 10: + break + print(":pile_of_poo: [prompt.invalid]Number must be between 1 and 10") + print(f"number={result}") + + while True: + password = Prompt.ask( + "Please enter a password [cyan](must be at least 5 characters)", + password=True, + ) + if len(password) >= 5: + break + print("[prompt.invalid]password too short") + print(f"password={password!r}") + + fruit = Prompt.ask("Enter a fruit", choices=["apple", "orange", "pear"]) + print(f"fruit={fruit!r}") + + else: + print("[b]OK :loudly_crying_face:") diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/protocol.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/protocol.py new file mode 100644 index 0000000..6248052 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/protocol.py @@ -0,0 +1,42 @@ +from typing import Any, Callable, cast, Set, TYPE_CHECKING +from inspect import isclass + +if TYPE_CHECKING: + from pip._vendor.rich.console import RenderableType + +_GIBBERISH = """aihwerij235234ljsdnp34ksodfipwoe234234jlskjdf""" + + +def is_renderable(check_object: Any) -> bool: + """Check if an object may be rendered by Rich.""" + return ( + isinstance(check_object, str) + or hasattr(check_object, "__rich__") + or hasattr(check_object, "__rich_console__") + ) + + +def rich_cast(renderable: object) -> "RenderableType": + """Cast an object to a renderable by calling __rich__ if present. + + Args: + renderable (object): A potentially renderable object + + Returns: + object: The result of recursively calling __rich__. + """ + from pip._vendor.rich.console import RenderableType + + rich_visited_set: Set[type] = set() # Prevent potential infinite loop + while hasattr(renderable, "__rich__") and not isclass(renderable): + # Detect object which claim to have all the attributes + if hasattr(renderable, _GIBBERISH): + return repr(renderable) + cast_method = getattr(renderable, "__rich__") + renderable = cast_method() + renderable_type = type(renderable) + if renderable_type in rich_visited_set: + break + rich_visited_set.add(renderable_type) + + return cast(RenderableType, renderable) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/region.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/region.py new file mode 100644 index 0000000..75b3631 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/region.py @@ -0,0 +1,10 @@ +from typing import NamedTuple + + +class Region(NamedTuple): + """Defines a rectangular region of the screen.""" + + x: int + y: int + width: int + height: int diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/repr.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/repr.py new file mode 100644 index 0000000..17147fd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/repr.py @@ -0,0 +1,151 @@ +from functools import partial +import inspect + +from typing import ( + Any, + Callable, + Iterable, + List, + Optional, + overload, + Union, + Tuple, + Type, + TypeVar, +) + + +T = TypeVar("T") + + +Result = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]] +RichReprResult = Result + + +class ReprError(Exception): + """An error occurred when attempting to build a repr.""" + + +@overload +def auto(cls: Optional[T]) -> T: + ... + + +@overload +def auto(*, angular: bool = False) -> Callable[[T], T]: + ... + + +def auto( + cls: Optional[T] = None, *, angular: Optional[bool] = None +) -> Union[T, Callable[[T], T]]: + """Class decorator to create __repr__ from __rich_repr__""" + + def do_replace(cls: Type[T], angular: Optional[bool] = None) -> Type[T]: + def auto_repr(self: Type[T]) -> str: + """Create repr string from __rich_repr__""" + repr_str: List[str] = [] + append = repr_str.append + + angular = getattr(self.__rich_repr__, "angular", False) # type: ignore + for arg in self.__rich_repr__(): # type: ignore + if isinstance(arg, tuple): + if len(arg) == 1: + append(repr(arg[0])) + else: + key, value, *default = arg + if key is None: + append(repr(value)) + else: + if len(default) and default[0] == value: + continue + append(f"{key}={value!r}") + else: + append(repr(arg)) + if angular: + return f"<{self.__class__.__name__} {' '.join(repr_str)}>" + else: + return f"{self.__class__.__name__}({', '.join(repr_str)})" + + def auto_rich_repr(self: Type[T]) -> Result: + """Auto generate __rich_rep__ from signature of __init__""" + try: + signature = inspect.signature(self.__init__) ## type: ignore + for name, param in signature.parameters.items(): + if param.kind == param.POSITIONAL_ONLY: + yield getattr(self, name) + elif param.kind in ( + param.POSITIONAL_OR_KEYWORD, + param.KEYWORD_ONLY, + ): + if param.default == param.empty: + yield getattr(self, param.name) + else: + yield param.name, getattr(self, param.name), param.default + except Exception as error: + raise ReprError( + f"Failed to auto generate __rich_repr__; {error}" + ) from None + + if not hasattr(cls, "__rich_repr__"): + auto_rich_repr.__doc__ = "Build a rich repr" + cls.__rich_repr__ = auto_rich_repr # type: ignore + + auto_repr.__doc__ = "Return repr(self)" + cls.__repr__ = auto_repr # type: ignore + if angular is not None: + cls.__rich_repr__.angular = angular # type: ignore + return cls + + if cls is None: + return partial(do_replace, angular=angular) # type: ignore + else: + return do_replace(cls, angular=angular) # type: ignore + + +@overload +def rich_repr(cls: Optional[T]) -> T: + ... + + +@overload +def rich_repr(*, angular: bool = False) -> Callable[[T], T]: + ... + + +def rich_repr( + cls: Optional[T] = None, *, angular: bool = False +) -> Union[T, Callable[[T], T]]: + if cls is None: + return auto(angular=angular) + else: + return auto(cls) + + +if __name__ == "__main__": + + @auto + class Foo: + def __rich_repr__(self) -> Result: + yield "foo" + yield "bar", {"shopping": ["eggs", "ham", "pineapple"]} + yield "buy", "hand sanitizer" + + foo = Foo() + from pip._vendor.rich.console import Console + + console = Console() + + console.rule("Standard repr") + console.print(foo) + + console.print(foo, width=60) + console.print(foo, width=30) + + console.rule("Angular repr") + Foo.__rich_repr__.angular = True # type: ignore + + console.print(foo) + + console.print(foo, width=60) + console.print(foo, width=30) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/rule.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/rule.py new file mode 100644 index 0000000..ce4754f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/rule.py @@ -0,0 +1,115 @@ +from typing import Union + +from .align import AlignMethod +from .cells import cell_len, set_cell_size +from .console import Console, ConsoleOptions, RenderResult +from .jupyter import JupyterMixin +from .style import Style +from .text import Text + + +class Rule(JupyterMixin): + """A console renderable to draw a horizontal rule (line). + + Args: + title (Union[str, Text], optional): Text to render in the rule. Defaults to "". + characters (str, optional): Character(s) used to draw the line. Defaults to "─". + style (StyleType, optional): Style of Rule. Defaults to "rule.line". + end (str, optional): Character at end of Rule. defaults to "\\\\n" + align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". + """ + + def __init__( + self, + title: Union[str, Text] = "", + *, + characters: str = "─", + style: Union[str, Style] = "rule.line", + end: str = "\n", + align: AlignMethod = "center", + ) -> None: + if cell_len(characters) < 1: + raise ValueError( + "'characters' argument must have a cell width of at least 1" + ) + if align not in ("left", "center", "right"): + raise ValueError( + f'invalid value for align, expected "left", "center", "right" (not {align!r})' + ) + self.title = title + self.characters = characters + self.style = style + self.end = end + self.align = align + + def __repr__(self) -> str: + return f"Rule({self.title!r}, {self.characters!r})" + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + width = options.max_width + + # Python3.6 doesn't have an isascii method on str + isascii = getattr(str, "isascii", None) or ( + lambda s: all(ord(c) < 128 for c in s) + ) + characters = ( + "-" + if (options.ascii_only and not isascii(self.characters)) + else self.characters + ) + + chars_len = cell_len(characters) + if not self.title: + rule_text = Text(characters * ((width // chars_len) + 1), self.style) + rule_text.truncate(width) + rule_text.plain = set_cell_size(rule_text.plain, width) + yield rule_text + return + + if isinstance(self.title, Text): + title_text = self.title + else: + title_text = console.render_str(self.title, style="rule.text") + + title_text.plain = title_text.plain.replace("\n", " ") + title_text.expand_tabs() + rule_text = Text(end=self.end) + + if self.align == "center": + title_text.truncate(width - 4, overflow="ellipsis") + side_width = (width - cell_len(title_text.plain)) // 2 + left = Text(characters * (side_width // chars_len + 1)) + left.truncate(side_width - 1) + right_length = width - cell_len(left.plain) - cell_len(title_text.plain) + right = Text(characters * (side_width // chars_len + 1)) + right.truncate(right_length) + rule_text.append(left.plain + " ", self.style) + rule_text.append(title_text) + rule_text.append(" " + right.plain, self.style) + elif self.align == "left": + title_text.truncate(width - 2, overflow="ellipsis") + rule_text.append(title_text) + rule_text.append(" ") + rule_text.append(characters * (width - rule_text.cell_len), self.style) + elif self.align == "right": + title_text.truncate(width - 2, overflow="ellipsis") + rule_text.append(characters * (width - title_text.cell_len - 1), self.style) + rule_text.append(" ") + rule_text.append(title_text) + + rule_text.plain = set_cell_size(rule_text.plain, width) + yield rule_text + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + import sys + + try: + text = sys.argv[1] + except IndexError: + text = "Hello, World" + console = Console() + console.print(Rule(title=text)) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/scope.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/scope.py new file mode 100644 index 0000000..6822b8c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/scope.py @@ -0,0 +1,86 @@ +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Optional, Tuple + +from .highlighter import ReprHighlighter +from .panel import Panel +from .pretty import Pretty +from .table import Table +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import ConsoleRenderable + + +def render_scope( + scope: "Mapping[str, Any]", + *, + title: Optional[TextType] = None, + sort_keys: bool = True, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, +) -> "ConsoleRenderable": + """Render python variables in a given scope. + + Args: + scope (Mapping): A mapping containing variable names and values. + title (str, optional): Optional title. Defaults to None. + sort_keys (bool, optional): Enable sorting of items. Defaults to True. + indent_guides (bool, optional): Enable indentaton guides. Defaults to False. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. + + Returns: + ConsoleRenderable: A renderable object. + """ + highlighter = ReprHighlighter() + items_table = Table.grid(padding=(0, 1), expand=False) + items_table.add_column(justify="right") + + def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]: + """Sort special variables first, then alphabetically.""" + key, _ = item + return (not key.startswith("__"), key.lower()) + + items = sorted(scope.items(), key=sort_items) if sort_keys else scope.items() + for key, value in items: + key_text = Text.assemble( + (key, "scope.key.special" if key.startswith("__") else "scope.key"), + (" =", "scope.equals"), + ) + items_table.add_row( + key_text, + Pretty( + value, + highlighter=highlighter, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + ), + ) + return Panel.fit( + items_table, + title=title, + border_style="scope.border", + padding=(0, 1), + ) + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich import print + + print() + + def test(foo: float, bar: float) -> None: + list_of_things = [1, 2, 3, None, 4, True, False, "Hello World"] + dict_of_things = { + "version": "1.1", + "method": "confirmFruitPurchase", + "params": [["apple", "orange", "mangoes", "pomelo"], 1.123], + "id": "194521489", + } + print(render_scope(locals(), title="[i]locals", sort_keys=False)) + + test(20.3423, 3.1427) + print() diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/screen.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/screen.py new file mode 100644 index 0000000..7f416e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/screen.py @@ -0,0 +1,54 @@ +from typing import Optional, TYPE_CHECKING + +from .segment import Segment +from .style import StyleType +from ._loop import loop_last + + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + RenderResult, + RenderableType, + Group, + ) + + +class Screen: + """A renderable that fills the terminal screen and crops excess. + + Args: + renderable (RenderableType): Child renderable. + style (StyleType, optional): Optional background style. Defaults to None. + """ + + renderable: "RenderableType" + + def __init__( + self, + *renderables: "RenderableType", + style: Optional[StyleType] = None, + application_mode: bool = False, + ) -> None: + from pip._vendor.rich.console import Group + + self.renderable = Group(*renderables) + self.style = style + self.application_mode = application_mode + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + width, height = options.size + style = console.get_style(self.style) if self.style else None + render_options = options.update(width=width, height=height) + lines = console.render_lines( + self.renderable or "", render_options, style=style, pad=True + ) + lines = Segment.set_shape(lines, width, height, style=style) + new_line = Segment("\n\r") if self.application_mode else Segment.line() + for last, line in loop_last(lines): + yield from line + if not last: + yield new_line diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/segment.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/segment.py new file mode 100644 index 0000000..94ca730 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/segment.py @@ -0,0 +1,720 @@ +from enum import IntEnum +from functools import lru_cache +from itertools import filterfalse +from logging import getLogger +from operator import attrgetter +from typing import ( + TYPE_CHECKING, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +from .cells import ( + _is_single_cell_widths, + cell_len, + get_character_cell_size, + set_cell_size, +) +from .repr import Result, rich_repr +from .style import Style + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + +log = getLogger("rich") + + +class ControlType(IntEnum): + """Non-printable control codes which typically translate to ANSI codes.""" + + BELL = 1 + CARRIAGE_RETURN = 2 + HOME = 3 + CLEAR = 4 + SHOW_CURSOR = 5 + HIDE_CURSOR = 6 + ENABLE_ALT_SCREEN = 7 + DISABLE_ALT_SCREEN = 8 + CURSOR_UP = 9 + CURSOR_DOWN = 10 + CURSOR_FORWARD = 11 + CURSOR_BACKWARD = 12 + CURSOR_MOVE_TO_COLUMN = 13 + CURSOR_MOVE_TO = 14 + ERASE_IN_LINE = 15 + + +ControlCode = Union[ + Tuple[ControlType], Tuple[ControlType, int], Tuple[ControlType, int, int] +] + + +@rich_repr() +class Segment(NamedTuple): + """A piece of text with associated style. Segments are produced by the Console render process and + are ultimately converted in to strings to be written to the terminal. + + Args: + text (str): A piece of text. + style (:class:`~rich.style.Style`, optional): An optional style to apply to the text. + control (Tuple[ControlCode..], optional): Optional sequence of control codes. + """ + + text: str = "" + """Raw text.""" + style: Optional[Style] = None + """An optional style.""" + control: Optional[Sequence[ControlCode]] = None + """Optional sequence of control codes.""" + + def __rich_repr__(self) -> Result: + yield self.text + if self.control is None: + if self.style is not None: + yield self.style + else: + yield self.style + yield self.control + + def __bool__(self) -> bool: + """Check if the segment contains text.""" + return bool(self.text) + + @property + def cell_length(self) -> int: + """Get cell length of segment.""" + return 0 if self.control else cell_len(self.text) + + @property + def is_control(self) -> bool: + """Check if the segment contains control codes.""" + return self.control is not None + + @classmethod + @lru_cache(1024 * 16) + def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]: # type: ignore + + text, style, control = segment + _Segment = Segment + + cell_length = segment.cell_length + if cut >= cell_length: + return segment, _Segment("", style, control) + + cell_size = get_character_cell_size + + pos = int((cut / cell_length) * len(text)) + + before = text[:pos] + cell_pos = cell_len(before) + if cell_pos == cut: + return ( + _Segment(before, style, control), + _Segment(text[pos:], style, control), + ) + while pos < len(text): + char = text[pos] + pos += 1 + cell_pos += cell_size(char) + before = text[:pos] + if cell_pos == cut: + return ( + _Segment(before, style, control), + _Segment(text[pos:], style, control), + ) + if cell_pos > cut: + return ( + _Segment(before[: pos - 1] + " ", style, control), + _Segment(" " + text[pos:], style, control), + ) + + def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]: + """Split segment in to two segments at the specified column. + + If the cut point falls in the middle of a 2-cell wide character then it is replaced + by two spaces, to preserve the display width of the parent segment. + + Returns: + Tuple[Segment, Segment]: Two segments. + """ + text, style, control = self + + if _is_single_cell_widths(text): + # Fast path with all 1 cell characters + if cut >= len(text): + return self, Segment("", style, control) + return ( + Segment(text[:cut], style, control), + Segment(text[cut:], style, control), + ) + + return self._split_cells(self, cut) + + @classmethod + def line(cls) -> "Segment": + """Make a new line segment.""" + return cls("\n") + + @classmethod + def apply_style( + cls, + segments: Iterable["Segment"], + style: Optional[Style] = None, + post_style: Optional[Style] = None, + ) -> Iterable["Segment"]: + """Apply style(s) to an iterable of segments. + + Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``. + + Args: + segments (Iterable[Segment]): Segments to process. + style (Style, optional): Base style. Defaults to None. + post_style (Style, optional): Style to apply on top of segment style. Defaults to None. + + Returns: + Iterable[Segments]: A new iterable of segments (possibly the same iterable). + """ + result_segments = segments + if style: + apply = style.__add__ + result_segments = ( + cls(text, None if control else apply(_style), control) + for text, _style, control in result_segments + ) + if post_style: + result_segments = ( + cls( + text, + ( + None + if control + else (_style + post_style if _style else post_style) + ), + control, + ) + for text, _style, control in result_segments + ) + return result_segments + + @classmethod + def filter_control( + cls, segments: Iterable["Segment"], is_control: bool = False + ) -> Iterable["Segment"]: + """Filter segments by ``is_control`` attribute. + + Args: + segments (Iterable[Segment]): An iterable of Segment instances. + is_control (bool, optional): is_control flag to match in search. + + Returns: + Iterable[Segment]: And iterable of Segment instances. + + """ + if is_control: + return filter(attrgetter("control"), segments) + else: + return filterfalse(attrgetter("control"), segments) + + @classmethod + def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]: + """Split a sequence of segments in to a list of lines. + + Args: + segments (Iterable[Segment]): Segments potentially containing line feeds. + + Yields: + Iterable[List[Segment]]: Iterable of segment lists, one per line. + """ + line: List[Segment] = [] + append = line.append + + for segment in segments: + if "\n" in segment.text and not segment.control: + text, style, _ = segment + while text: + _text, new_line, text = text.partition("\n") + if _text: + append(cls(_text, style)) + if new_line: + yield line + line = [] + append = line.append + else: + append(segment) + if line: + yield line + + @classmethod + def split_and_crop_lines( + cls, + segments: Iterable["Segment"], + length: int, + style: Optional[Style] = None, + pad: bool = True, + include_new_lines: bool = True, + ) -> Iterable[List["Segment"]]: + """Split segments in to lines, and crop lines greater than a given length. + + Args: + segments (Iterable[Segment]): An iterable of segments, probably + generated from console.render. + length (int): Desired line length. + style (Style, optional): Style to use for any padding. + pad (bool): Enable padding of lines that are less than `length`. + + Returns: + Iterable[List[Segment]]: An iterable of lines of segments. + """ + line: List[Segment] = [] + append = line.append + + adjust_line_length = cls.adjust_line_length + new_line_segment = cls("\n") + + for segment in segments: + if "\n" in segment.text and not segment.control: + text, style, _ = segment + while text: + _text, new_line, text = text.partition("\n") + if _text: + append(cls(_text, style)) + if new_line: + cropped_line = adjust_line_length( + line, length, style=style, pad=pad + ) + if include_new_lines: + cropped_line.append(new_line_segment) + yield cropped_line + del line[:] + else: + append(segment) + if line: + yield adjust_line_length(line, length, style=style, pad=pad) + + @classmethod + def adjust_line_length( + cls, + line: List["Segment"], + length: int, + style: Optional[Style] = None, + pad: bool = True, + ) -> List["Segment"]: + """Adjust a line to a given width (cropping or padding as required). + + Args: + segments (Iterable[Segment]): A list of segments in a single line. + length (int): The desired width of the line. + style (Style, optional): The style of padding if used (space on the end). Defaults to None. + pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True. + + Returns: + List[Segment]: A line of segments with the desired length. + """ + line_length = sum(segment.cell_length for segment in line) + new_line: List[Segment] + + if line_length < length: + if pad: + new_line = line + [cls(" " * (length - line_length), style)] + else: + new_line = line[:] + elif line_length > length: + new_line = [] + append = new_line.append + line_length = 0 + for segment in line: + segment_length = segment.cell_length + if line_length + segment_length < length or segment.control: + append(segment) + line_length += segment_length + else: + text, segment_style, _ = segment + text = set_cell_size(text, length - line_length) + append(cls(text, segment_style)) + break + else: + new_line = line[:] + return new_line + + @classmethod + def get_line_length(cls, line: List["Segment"]) -> int: + """Get the length of list of segments. + + Args: + line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters), + + Returns: + int: The length of the line. + """ + _cell_len = cell_len + return sum(_cell_len(segment.text) for segment in line) + + @classmethod + def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]: + """Get the shape (enclosing rectangle) of a list of lines. + + Args: + lines (List[List[Segment]]): A list of lines (no '\\\\n' characters). + + Returns: + Tuple[int, int]: Width and height in characters. + """ + get_line_length = cls.get_line_length + max_width = max(get_line_length(line) for line in lines) if lines else 0 + return (max_width, len(lines)) + + @classmethod + def set_shape( + cls, + lines: List[List["Segment"]], + width: int, + height: Optional[int] = None, + style: Optional[Style] = None, + new_lines: bool = False, + ) -> List[List["Segment"]]: + """Set the shape of a list of lines (enclosing rectangle). + + Args: + lines (List[List[Segment]]): A list of lines. + width (int): Desired width. + height (int, optional): Desired height or None for no change. + style (Style, optional): Style of any padding added. + new_lines (bool, optional): Padded lines should include "\n". Defaults to False. + + Returns: + List[List[Segment]]: New list of lines. + """ + _height = height or len(lines) + + blank = ( + [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)] + ) + + adjust_line_length = cls.adjust_line_length + shaped_lines = lines[:_height] + shaped_lines[:] = [ + adjust_line_length(line, width, style=style) for line in lines + ] + if len(shaped_lines) < _height: + shaped_lines.extend([blank] * (_height - len(shaped_lines))) + return shaped_lines + + @classmethod + def align_top( + cls: Type["Segment"], + lines: List[List["Segment"]], + width: int, + height: int, + style: Style, + new_lines: bool = False, + ) -> List[List["Segment"]]: + """Aligns lines to top (adds extra lines to bottom as required). + + Args: + lines (List[List[Segment]]): A list of lines. + width (int): Desired width. + height (int, optional): Desired height or None for no change. + style (Style): Style of any padding added. + new_lines (bool, optional): Padded lines should include "\n". Defaults to False. + + Returns: + List[List[Segment]]: New list of lines. + """ + extra_lines = height - len(lines) + if not extra_lines: + return lines[:] + lines = lines[:height] + blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) + lines = lines + [[blank]] * extra_lines + return lines + + @classmethod + def align_bottom( + cls: Type["Segment"], + lines: List[List["Segment"]], + width: int, + height: int, + style: Style, + new_lines: bool = False, + ) -> List[List["Segment"]]: + """Aligns render to bottom (adds extra lines above as required). + + Args: + lines (List[List[Segment]]): A list of lines. + width (int): Desired width. + height (int, optional): Desired height or None for no change. + style (Style): Style of any padding added. Defaults to None. + new_lines (bool, optional): Padded lines should include "\n". Defaults to False. + + Returns: + List[List[Segment]]: New list of lines. + """ + extra_lines = height - len(lines) + if not extra_lines: + return lines[:] + lines = lines[:height] + blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) + lines = [[blank]] * extra_lines + lines + return lines + + @classmethod + def align_middle( + cls: Type["Segment"], + lines: List[List["Segment"]], + width: int, + height: int, + style: Style, + new_lines: bool = False, + ) -> List[List["Segment"]]: + """Aligns lines to middle (adds extra lines to above and below as required). + + Args: + lines (List[List[Segment]]): A list of lines. + width (int): Desired width. + height (int, optional): Desired height or None for no change. + style (Style): Style of any padding added. + new_lines (bool, optional): Padded lines should include "\n". Defaults to False. + + Returns: + List[List[Segment]]: New list of lines. + """ + extra_lines = height - len(lines) + if not extra_lines: + return lines[:] + lines = lines[:height] + blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) + top_lines = extra_lines // 2 + bottom_lines = extra_lines - top_lines + lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines + return lines + + @classmethod + def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: + """Simplify an iterable of segments by combining contiguous segments with the same style. + + Args: + segments (Iterable[Segment]): An iterable of segments. + + Returns: + Iterable[Segment]: A possibly smaller iterable of segments that will render the same way. + """ + iter_segments = iter(segments) + try: + last_segment = next(iter_segments) + except StopIteration: + return + + _Segment = Segment + for segment in iter_segments: + if last_segment.style == segment.style and not segment.control: + last_segment = _Segment( + last_segment.text + segment.text, last_segment.style + ) + else: + yield last_segment + last_segment = segment + yield last_segment + + @classmethod + def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: + """Remove all links from an iterable of styles. + + Args: + segments (Iterable[Segment]): An iterable segments. + + Yields: + Segment: Segments with link removed. + """ + for segment in segments: + if segment.control or segment.style is None: + yield segment + else: + text, style, _control = segment + yield cls(text, style.update_link(None) if style else None) + + @classmethod + def strip_styles(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: + """Remove all styles from an iterable of segments. + + Args: + segments (Iterable[Segment]): An iterable segments. + + Yields: + Segment: Segments with styles replace with None + """ + for text, _style, control in segments: + yield cls(text, None, control) + + @classmethod + def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: + """Remove all color from an iterable of segments. + + Args: + segments (Iterable[Segment]): An iterable segments. + + Yields: + Segment: Segments with colorless style. + """ + + cache: Dict[Style, Style] = {} + for text, style, control in segments: + if style: + colorless_style = cache.get(style) + if colorless_style is None: + colorless_style = style.without_color + cache[style] = colorless_style + yield cls(text, colorless_style, control) + else: + yield cls(text, None, control) + + @classmethod + def divide( + cls, segments: Iterable["Segment"], cuts: Iterable[int] + ) -> Iterable[List["Segment"]]: + """Divides an iterable of segments in to portions. + + Args: + cuts (Iterable[int]): Cell positions where to divide. + + Yields: + [Iterable[List[Segment]]]: An iterable of Segments in List. + """ + split_segments: List["Segment"] = [] + add_segment = split_segments.append + + iter_cuts = iter(cuts) + + while True: + try: + cut = next(iter_cuts) + except StopIteration: + return [] + if cut != 0: + break + yield [] + pos = 0 + + for segment in segments: + while segment.text: + end_pos = pos + segment.cell_length + if end_pos < cut: + add_segment(segment) + pos = end_pos + break + + try: + if end_pos == cut: + add_segment(segment) + yield split_segments[:] + del split_segments[:] + pos = end_pos + break + else: + before, segment = segment.split_cells(cut - pos) + add_segment(before) + yield split_segments[:] + del split_segments[:] + pos = cut + finally: + try: + cut = next(iter_cuts) + except StopIteration: + if split_segments: + yield split_segments[:] + return + yield split_segments[:] + + +class Segments: + """A simple renderable to render an iterable of segments. This class may be useful if + you want to print segments outside of a __rich_console__ method. + + Args: + segments (Iterable[Segment]): An iterable of segments. + new_lines (bool, optional): Add new lines between segments. Defaults to False. + """ + + def __init__(self, segments: Iterable[Segment], new_lines: bool = False) -> None: + self.segments = list(segments) + self.new_lines = new_lines + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.new_lines: + line = Segment.line() + for segment in self.segments: + yield segment + yield line + else: + yield from self.segments + + +class SegmentLines: + def __init__(self, lines: Iterable[List[Segment]], new_lines: bool = False) -> None: + """A simple renderable containing a number of lines of segments. May be used as an intermediate + in rendering process. + + Args: + lines (Iterable[List[Segment]]): Lists of segments forming lines. + new_lines (bool, optional): Insert new lines after each line. Defaults to False. + """ + self.lines = list(lines) + self.new_lines = new_lines + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.new_lines: + new_line = Segment.line() + for line in self.lines: + yield from line + yield new_line + else: + for line in self.lines: + yield from line + + +if __name__ == "__main__": + + if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + from pip._vendor.rich.syntax import Syntax + from pip._vendor.rich.text import Text + + code = """from rich.console import Console + console = Console() + text = Text.from_markup("Hello, [bold magenta]World[/]!") + console.print(text)""" + + text = Text.from_markup("Hello, [bold magenta]World[/]!") + + console = Console() + + console.rule("rich.Segment") + console.print( + "A Segment is the last step in the Rich render process before generating text with ANSI codes." + ) + console.print("\nConsider the following code:\n") + console.print(Syntax(code, "python", line_numbers=True)) + console.print() + console.print( + "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the the following:\n" + ) + fragments = list(console.render(text)) + console.print(fragments) + console.print() + console.print( + "The Segments are then processed to produce the following output:\n" + ) + console.print(text) + console.print( + "\nYou will only need to know this if you are implementing your own Rich renderables." + ) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/spinner.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/spinner.py new file mode 100644 index 0000000..5b13b1e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/spinner.py @@ -0,0 +1,134 @@ +from typing import cast, List, Optional, TYPE_CHECKING + +from ._spinners import SPINNERS +from .measure import Measurement +from .table import Table +from .text import Text + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult, RenderableType + from .style import StyleType + + +class Spinner: + def __init__( + self, + name: str, + text: "RenderableType" = "", + *, + style: Optional["StyleType"] = None, + speed: float = 1.0, + ) -> None: + """A spinner animation. + + Args: + name (str): Name of spinner (run python -m rich.spinner). + text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "". + style (StyleType, optional): Style for spinner animation. Defaults to None. + speed (float, optional): Speed factor for animation. Defaults to 1.0. + + Raises: + KeyError: If name isn't one of the supported spinner animations. + """ + try: + spinner = SPINNERS[name] + except KeyError: + raise KeyError(f"no spinner called {name!r}") + self.text = Text.from_markup(text) if isinstance(text, str) else text + self.frames = cast(List[str], spinner["frames"])[:] + self.interval = cast(float, spinner["interval"]) + self.start_time: Optional[float] = None + self.style = style + self.speed = speed + self.frame_no_offset: float = 0.0 + self._update_speed = 0.0 + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + yield self.render(console.get_time()) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + text = self.render(0) + return Measurement.get(console, options, text) + + def render(self, time: float) -> "RenderableType": + """Render the spinner for a given time. + + Args: + time (float): Time in seconds. + + Returns: + RenderableType: A renderable containing animation frame. + """ + if self.start_time is None: + self.start_time = time + + frame_no = ((time - self.start_time) * self.speed) / ( + self.interval / 1000.0 + ) + self.frame_no_offset + frame = Text( + self.frames[int(frame_no) % len(self.frames)], style=self.style or "" + ) + + if self._update_speed: + self.frame_no_offset = frame_no + self.start_time = time + self.speed = self._update_speed + self._update_speed = 0.0 + + if not self.text: + return frame + elif isinstance(self.text, (str, Text)): + return Text.assemble(frame, " ", self.text) + else: + table = Table.grid(padding=1) + table.add_row(frame, self.text) + return table + + def update( + self, + *, + text: "RenderableType" = "", + style: Optional["StyleType"] = None, + speed: Optional[float] = None, + ) -> None: + """Updates attributes of a spinner after it has been started. + + Args: + text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "". + style (StyleType, optional): Style for spinner animation. Defaults to None. + speed (float, optional): Speed factor for animation. Defaults to None. + """ + if text: + self.text = Text.from_markup(text) if isinstance(text, str) else text + if style: + self.style = style + if speed: + self._update_speed = speed + + +if __name__ == "__main__": # pragma: no cover + from time import sleep + + from .columns import Columns + from .panel import Panel + from .live import Live + + all_spinners = Columns( + [ + Spinner(spinner_name, text=Text(repr(spinner_name), style="green")) + for spinner_name in sorted(SPINNERS.keys()) + ], + column_first=True, + expand=True, + ) + + with Live( + Panel(all_spinners, title="Spinners", border_style="blue"), + refresh_per_second=20, + ) as live: + while True: + sleep(0.1) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/status.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/status.py new file mode 100644 index 0000000..09eff40 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/status.py @@ -0,0 +1,132 @@ +from types import TracebackType +from typing import Optional, Type + +from .console import Console, RenderableType +from .jupyter import JupyterMixin +from .live import Live +from .spinner import Spinner +from .style import StyleType + + +class Status(JupyterMixin): + """Displays a status indicator with a 'spinner' animation. + + Args: + status (RenderableType): A status renderable (str or Text typically). + console (Console, optional): Console instance to use, or None for global console. Defaults to None. + spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots". + spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner". + speed (float, optional): Speed factor for spinner animation. Defaults to 1.0. + refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5. + """ + + def __init__( + self, + status: RenderableType, + *, + console: Optional[Console] = None, + spinner: str = "dots", + spinner_style: StyleType = "status.spinner", + speed: float = 1.0, + refresh_per_second: float = 12.5, + ): + self.status = status + self.spinner_style = spinner_style + self.speed = speed + self._spinner = Spinner(spinner, text=status, style=spinner_style, speed=speed) + self._live = Live( + self.renderable, + console=console, + refresh_per_second=refresh_per_second, + transient=True, + ) + + @property + def renderable(self) -> Spinner: + return self._spinner + + @property + def console(self) -> "Console": + """Get the Console used by the Status objects.""" + return self._live.console + + def update( + self, + status: Optional[RenderableType] = None, + *, + spinner: Optional[str] = None, + spinner_style: Optional[StyleType] = None, + speed: Optional[float] = None, + ) -> None: + """Update status. + + Args: + status (Optional[RenderableType], optional): New status renderable or None for no change. Defaults to None. + spinner (Optional[str], optional): New spinner or None for no change. Defaults to None. + spinner_style (Optional[StyleType], optional): New spinner style or None for no change. Defaults to None. + speed (Optional[float], optional): Speed factor for spinner animation or None for no change. Defaults to None. + """ + if status is not None: + self.status = status + if spinner_style is not None: + self.spinner_style = spinner_style + if speed is not None: + self.speed = speed + if spinner is not None: + self._spinner = Spinner( + spinner, text=self.status, style=self.spinner_style, speed=self.speed + ) + self._live.update(self.renderable, refresh=True) + else: + self._spinner.update( + text=self.status, style=self.spinner_style, speed=self.speed + ) + + def start(self) -> None: + """Start the status animation.""" + self._live.start() + + def stop(self) -> None: + """Stop the spinner animation.""" + self._live.stop() + + def __rich__(self) -> RenderableType: + return self.renderable + + def __enter__(self) -> "Status": + self.start() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.stop() + + +if __name__ == "__main__": # pragma: no cover + + from time import sleep + + from .console import Console + + console = Console() + with console.status("[magenta]Covid detector booting up") as status: + sleep(3) + console.log("Importing advanced AI") + sleep(3) + console.log("Advanced Covid AI Ready") + sleep(3) + status.update(status="[bold blue] Scanning for Covid", spinner="earth") + sleep(3) + console.log("Found 10,000,000,000 copies of Covid32.exe") + sleep(3) + status.update( + status="[bold red]Moving Covid32.exe to Trash", + spinner="bouncingBall", + spinner_style="yellow", + ) + sleep(5) + console.print("[bold green]Covid deleted successfully") diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/style.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/style.py new file mode 100644 index 0000000..0787c33 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/style.py @@ -0,0 +1,785 @@ +import sys +from functools import lru_cache +from marshal import loads, dumps +from random import randint +from typing import Any, cast, Dict, Iterable, List, Optional, Type, Union + +from . import errors +from .color import Color, ColorParseError, ColorSystem, blend_rgb +from .repr import rich_repr, Result +from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme + + +# Style instances and style definitions are often interchangeable +StyleType = Union[str, "Style"] + + +class _Bit: + """A descriptor to get/set a style attribute bit.""" + + __slots__ = ["bit"] + + def __init__(self, bit_no: int) -> None: + self.bit = 1 << bit_no + + def __get__(self, obj: "Style", objtype: Type["Style"]) -> Optional[bool]: + if obj._set_attributes & self.bit: + return obj._attributes & self.bit != 0 + return None + + +@rich_repr +class Style: + """A terminal style. + + A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such + as bold, italic etc. The attributes have 3 states: they can either be on + (``True``), off (``False``), or not set (``None``). + + Args: + color (Union[Color, str], optional): Color of terminal text. Defaults to None. + bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None. + bold (bool, optional): Enable bold text. Defaults to None. + dim (bool, optional): Enable dim text. Defaults to None. + italic (bool, optional): Enable italic text. Defaults to None. + underline (bool, optional): Enable underlined text. Defaults to None. + blink (bool, optional): Enabled blinking text. Defaults to None. + blink2 (bool, optional): Enable fast blinking text. Defaults to None. + reverse (bool, optional): Enabled reverse text. Defaults to None. + conceal (bool, optional): Enable concealed text. Defaults to None. + strike (bool, optional): Enable strikethrough text. Defaults to None. + underline2 (bool, optional): Enable doubly underlined text. Defaults to None. + frame (bool, optional): Enable framed text. Defaults to None. + encircle (bool, optional): Enable encircled text. Defaults to None. + overline (bool, optional): Enable overlined text. Defaults to None. + link (str, link): Link URL. Defaults to None. + + """ + + _color: Optional[Color] + _bgcolor: Optional[Color] + _attributes: int + _set_attributes: int + _hash: int + _null: bool + _meta: Optional[bytes] + + __slots__ = [ + "_color", + "_bgcolor", + "_attributes", + "_set_attributes", + "_link", + "_link_id", + "_ansi", + "_style_definition", + "_hash", + "_null", + "_meta", + ] + + # maps bits on to SGR parameter + _style_map = { + 0: "1", + 1: "2", + 2: "3", + 3: "4", + 4: "5", + 5: "6", + 6: "7", + 7: "8", + 8: "9", + 9: "21", + 10: "51", + 11: "52", + 12: "53", + } + + STYLE_ATTRIBUTES = { + "dim": "dim", + "d": "dim", + "bold": "bold", + "b": "bold", + "italic": "italic", + "i": "italic", + "underline": "underline", + "u": "underline", + "blink": "blink", + "blink2": "blink2", + "reverse": "reverse", + "r": "reverse", + "conceal": "conceal", + "c": "conceal", + "strike": "strike", + "s": "strike", + "underline2": "underline2", + "uu": "underline2", + "frame": "frame", + "encircle": "encircle", + "overline": "overline", + "o": "overline", + } + + def __init__( + self, + *, + color: Optional[Union[Color, str]] = None, + bgcolor: Optional[Union[Color, str]] = None, + bold: Optional[bool] = None, + dim: Optional[bool] = None, + italic: Optional[bool] = None, + underline: Optional[bool] = None, + blink: Optional[bool] = None, + blink2: Optional[bool] = None, + reverse: Optional[bool] = None, + conceal: Optional[bool] = None, + strike: Optional[bool] = None, + underline2: Optional[bool] = None, + frame: Optional[bool] = None, + encircle: Optional[bool] = None, + overline: Optional[bool] = None, + link: Optional[str] = None, + meta: Optional[Dict[str, Any]] = None, + ): + self._ansi: Optional[str] = None + self._style_definition: Optional[str] = None + + def _make_color(color: Union[Color, str]) -> Color: + return color if isinstance(color, Color) else Color.parse(color) + + self._color = None if color is None else _make_color(color) + self._bgcolor = None if bgcolor is None else _make_color(bgcolor) + self._set_attributes = sum( + ( + bold is not None, + dim is not None and 2, + italic is not None and 4, + underline is not None and 8, + blink is not None and 16, + blink2 is not None and 32, + reverse is not None and 64, + conceal is not None and 128, + strike is not None and 256, + underline2 is not None and 512, + frame is not None and 1024, + encircle is not None and 2048, + overline is not None and 4096, + ) + ) + self._attributes = ( + sum( + ( + bold and 1 or 0, + dim and 2 or 0, + italic and 4 or 0, + underline and 8 or 0, + blink and 16 or 0, + blink2 and 32 or 0, + reverse and 64 or 0, + conceal and 128 or 0, + strike and 256 or 0, + underline2 and 512 or 0, + frame and 1024 or 0, + encircle and 2048 or 0, + overline and 4096 or 0, + ) + ) + if self._set_attributes + else 0 + ) + + self._link = link + self._link_id = f"{randint(0, 999999)}" if link else "" + self._meta = None if meta is None else dumps(meta) + self._hash = hash( + ( + self._color, + self._bgcolor, + self._attributes, + self._set_attributes, + link, + self._meta, + ) + ) + self._null = not (self._set_attributes or color or bgcolor or link or meta) + + @classmethod + def null(cls) -> "Style": + """Create an 'null' style, equivalent to Style(), but more performant.""" + return NULL_STYLE + + @classmethod + def from_color( + cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None + ) -> "Style": + """Create a new style with colors and no attributes. + + Returns: + color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None. + bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None. + """ + style: Style = cls.__new__(Style) + style._ansi = None + style._style_definition = None + style._color = color + style._bgcolor = bgcolor + style._set_attributes = 0 + style._attributes = 0 + style._link = None + style._link_id = "" + style._meta = None + style._hash = hash( + ( + color, + bgcolor, + None, + None, + None, + None, + ) + ) + style._null = not (color or bgcolor) + return style + + @classmethod + def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style": + """Create a new style with meta data. + + Returns: + meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None. + """ + style: Style = cls.__new__(Style) + style._ansi = None + style._style_definition = None + style._color = None + style._bgcolor = None + style._set_attributes = 0 + style._attributes = 0 + style._link = None + style._link_id = "" + style._meta = dumps(meta) + style._hash = hash( + ( + None, + None, + None, + None, + None, + style._meta, + ) + ) + style._null = not (meta) + return style + + @classmethod + def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style": + """Create a blank style with meta information. + + Example: + style = Style.on(click=self.on_click) + + Args: + meta (Optiona[Dict[str, Any]], optional): An optional dict of meta information. + **handlers (Any): Keyword arguments are translated in to handlers. + + Returns: + Style: A Style with meta information attached. + """ + meta = {} if meta is None else meta + meta.update({f"@{key}": value for key, value in handlers.items()}) + return cls.from_meta(meta) + + bold = _Bit(0) + dim = _Bit(1) + italic = _Bit(2) + underline = _Bit(3) + blink = _Bit(4) + blink2 = _Bit(5) + reverse = _Bit(6) + conceal = _Bit(7) + strike = _Bit(8) + underline2 = _Bit(9) + frame = _Bit(10) + encircle = _Bit(11) + overline = _Bit(12) + + @property + def link_id(self) -> str: + """Get a link id, used in ansi code for links.""" + return self._link_id + + def __str__(self) -> str: + """Re-generate style definition from attributes.""" + if self._style_definition is None: + attributes: List[str] = [] + append = attributes.append + bits = self._set_attributes + if bits & 0b0000000001111: + if bits & 1: + append("bold" if self.bold else "not bold") + if bits & (1 << 1): + append("dim" if self.dim else "not dim") + if bits & (1 << 2): + append("italic" if self.italic else "not italic") + if bits & (1 << 3): + append("underline" if self.underline else "not underline") + if bits & 0b0000111110000: + if bits & (1 << 4): + append("blink" if self.blink else "not blink") + if bits & (1 << 5): + append("blink2" if self.blink2 else "not blink2") + if bits & (1 << 6): + append("reverse" if self.reverse else "not reverse") + if bits & (1 << 7): + append("conceal" if self.conceal else "not conceal") + if bits & (1 << 8): + append("strike" if self.strike else "not strike") + if bits & 0b1111000000000: + if bits & (1 << 9): + append("underline2" if self.underline2 else "not underline2") + if bits & (1 << 10): + append("frame" if self.frame else "not frame") + if bits & (1 << 11): + append("encircle" if self.encircle else "not encircle") + if bits & (1 << 12): + append("overline" if self.overline else "not overline") + if self._color is not None: + append(self._color.name) + if self._bgcolor is not None: + append("on") + append(self._bgcolor.name) + if self._link: + append("link") + append(self._link) + self._style_definition = " ".join(attributes) or "none" + return self._style_definition + + def __bool__(self) -> bool: + """A Style is false if it has no attributes, colors, or links.""" + return not self._null + + def _make_ansi_codes(self, color_system: ColorSystem) -> str: + """Generate ANSI codes for this style. + + Args: + color_system (ColorSystem): Color system. + + Returns: + str: String containing codes. + """ + if self._ansi is None: + sgr: List[str] = [] + append = sgr.append + _style_map = self._style_map + attributes = self._attributes & self._set_attributes + if attributes: + if attributes & 1: + append(_style_map[0]) + if attributes & 2: + append(_style_map[1]) + if attributes & 4: + append(_style_map[2]) + if attributes & 8: + append(_style_map[3]) + if attributes & 0b0000111110000: + for bit in range(4, 9): + if attributes & (1 << bit): + append(_style_map[bit]) + if attributes & 0b1111000000000: + for bit in range(9, 13): + if attributes & (1 << bit): + append(_style_map[bit]) + if self._color is not None: + sgr.extend(self._color.downgrade(color_system).get_ansi_codes()) + if self._bgcolor is not None: + sgr.extend( + self._bgcolor.downgrade(color_system).get_ansi_codes( + foreground=False + ) + ) + self._ansi = ";".join(sgr) + return self._ansi + + @classmethod + @lru_cache(maxsize=1024) + def normalize(cls, style: str) -> str: + """Normalize a style definition so that styles with the same effect have the same string + representation. + + Args: + style (str): A style definition. + + Returns: + str: Normal form of style definition. + """ + try: + return str(cls.parse(style)) + except errors.StyleSyntaxError: + return style.strip().lower() + + @classmethod + def pick_first(cls, *values: Optional[StyleType]) -> StyleType: + """Pick first non-None style.""" + for value in values: + if value is not None: + return value + raise ValueError("expected at least one non-None style") + + def __rich_repr__(self) -> Result: + yield "color", self.color, None + yield "bgcolor", self.bgcolor, None + yield "bold", self.bold, None, + yield "dim", self.dim, None, + yield "italic", self.italic, None + yield "underline", self.underline, None, + yield "blink", self.blink, None + yield "blink2", self.blink2, None + yield "reverse", self.reverse, None + yield "conceal", self.conceal, None + yield "strike", self.strike, None + yield "underline2", self.underline2, None + yield "frame", self.frame, None + yield "encircle", self.encircle, None + yield "link", self.link, None + if self._meta: + yield "meta", self.meta + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Style): + return NotImplemented + return ( + self._color == other._color + and self._bgcolor == other._bgcolor + and self._set_attributes == other._set_attributes + and self._attributes == other._attributes + and self._link == other._link + and self._meta == other._meta + ) + + def __hash__(self) -> int: + return self._hash + + @property + def color(self) -> Optional[Color]: + """The foreground color or None if it is not set.""" + return self._color + + @property + def bgcolor(self) -> Optional[Color]: + """The background color or None if it is not set.""" + return self._bgcolor + + @property + def link(self) -> Optional[str]: + """Link text, if set.""" + return self._link + + @property + def transparent_background(self) -> bool: + """Check if the style specified a transparent background.""" + return self.bgcolor is None or self.bgcolor.is_default + + @property + def background_style(self) -> "Style": + """A Style with background only.""" + return Style(bgcolor=self.bgcolor) + + @property + def meta(self) -> Dict[str, Any]: + """Get meta information (can not be changed after construction).""" + return {} if self._meta is None else cast(Dict[str, Any], loads(self._meta)) + + @property + def without_color(self) -> "Style": + """Get a copy of the style with color removed.""" + if self._null: + return NULL_STYLE + style: Style = self.__new__(Style) + style._ansi = None + style._style_definition = None + style._color = None + style._bgcolor = None + style._attributes = self._attributes + style._set_attributes = self._set_attributes + style._link = self._link + style._link_id = f"{randint(0, 999999)}" if self._link else "" + style._hash = self._hash + style._null = False + style._meta = None + return style + + @classmethod + @lru_cache(maxsize=4096) + def parse(cls, style_definition: str) -> "Style": + """Parse a style definition. + + Args: + style_definition (str): A string containing a style. + + Raises: + errors.StyleSyntaxError: If the style definition syntax is invalid. + + Returns: + `Style`: A Style instance. + """ + if style_definition.strip() == "none" or not style_definition: + return cls.null() + + STYLE_ATTRIBUTES = cls.STYLE_ATTRIBUTES + color: Optional[str] = None + bgcolor: Optional[str] = None + attributes: Dict[str, Optional[Any]] = {} + link: Optional[str] = None + + words = iter(style_definition.split()) + for original_word in words: + word = original_word.lower() + if word == "on": + word = next(words, "") + if not word: + raise errors.StyleSyntaxError("color expected after 'on'") + try: + Color.parse(word) is None + except ColorParseError as error: + raise errors.StyleSyntaxError( + f"unable to parse {word!r} as background color; {error}" + ) from None + bgcolor = word + + elif word == "not": + word = next(words, "") + attribute = STYLE_ATTRIBUTES.get(word) + if attribute is None: + raise errors.StyleSyntaxError( + f"expected style attribute after 'not', found {word!r}" + ) + attributes[attribute] = False + + elif word == "link": + word = next(words, "") + if not word: + raise errors.StyleSyntaxError("URL expected after 'link'") + link = word + + elif word in STYLE_ATTRIBUTES: + attributes[STYLE_ATTRIBUTES[word]] = True + + else: + try: + Color.parse(word) + except ColorParseError as error: + raise errors.StyleSyntaxError( + f"unable to parse {word!r} as color; {error}" + ) from None + color = word + style = Style(color=color, bgcolor=bgcolor, link=link, **attributes) + return style + + @lru_cache(maxsize=1024) + def get_html_style(self, theme: Optional[TerminalTheme] = None) -> str: + """Get a CSS style rule.""" + theme = theme or DEFAULT_TERMINAL_THEME + css: List[str] = [] + append = css.append + + color = self.color + bgcolor = self.bgcolor + if self.reverse: + color, bgcolor = bgcolor, color + if self.dim: + foreground_color = ( + theme.foreground_color if color is None else color.get_truecolor(theme) + ) + color = Color.from_triplet( + blend_rgb(foreground_color, theme.background_color, 0.5) + ) + if color is not None: + theme_color = color.get_truecolor(theme) + append(f"color: {theme_color.hex}") + append(f"text-decoration-color: {theme_color.hex}") + if bgcolor is not None: + theme_color = bgcolor.get_truecolor(theme, foreground=False) + append(f"background-color: {theme_color.hex}") + if self.bold: + append("font-weight: bold") + if self.italic: + append("font-style: italic") + if self.underline: + append("text-decoration: underline") + if self.strike: + append("text-decoration: line-through") + if self.overline: + append("text-decoration: overline") + return "; ".join(css) + + @classmethod + def combine(cls, styles: Iterable["Style"]) -> "Style": + """Combine styles and get result. + + Args: + styles (Iterable[Style]): Styles to combine. + + Returns: + Style: A new style instance. + """ + iter_styles = iter(styles) + return sum(iter_styles, next(iter_styles)) + + @classmethod + def chain(cls, *styles: "Style") -> "Style": + """Combine styles from positional argument in to a single style. + + Args: + *styles (Iterable[Style]): Styles to combine. + + Returns: + Style: A new style instance. + """ + iter_styles = iter(styles) + return sum(iter_styles, next(iter_styles)) + + def copy(self) -> "Style": + """Get a copy of this style. + + Returns: + Style: A new Style instance with identical attributes. + """ + if self._null: + return NULL_STYLE + style: Style = self.__new__(Style) + style._ansi = self._ansi + style._style_definition = self._style_definition + style._color = self._color + style._bgcolor = self._bgcolor + style._attributes = self._attributes + style._set_attributes = self._set_attributes + style._link = self._link + style._link_id = f"{randint(0, 999999)}" if self._link else "" + style._hash = self._hash + style._null = False + style._meta = self._meta + return style + + def update_link(self, link: Optional[str] = None) -> "Style": + """Get a copy with a different value for link. + + Args: + link (str, optional): New value for link. Defaults to None. + + Returns: + Style: A new Style instance. + """ + style: Style = self.__new__(Style) + style._ansi = self._ansi + style._style_definition = self._style_definition + style._color = self._color + style._bgcolor = self._bgcolor + style._attributes = self._attributes + style._set_attributes = self._set_attributes + style._link = link + style._link_id = f"{randint(0, 999999)}" if link else "" + style._hash = self._hash + style._null = False + style._meta = self._meta + return style + + def render( + self, + text: str = "", + *, + color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR, + legacy_windows: bool = False, + ) -> str: + """Render the ANSI codes for the style. + + Args: + text (str, optional): A string to style. Defaults to "". + color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR. + + Returns: + str: A string containing ANSI style codes. + """ + if not text or color_system is None: + return text + attrs = self._make_ansi_codes(color_system) + rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text + if self._link and not legacy_windows: + rendered = ( + f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\" + ) + return rendered + + def test(self, text: Optional[str] = None) -> None: + """Write text with style directly to terminal. + + This method is for testing purposes only. + + Args: + text (Optional[str], optional): Text to style or None for style name. + + """ + text = text or str(self) + sys.stdout.write(f"{self.render(text)}\n") + + def __add__(self, style: Optional["Style"]) -> "Style": + if not (isinstance(style, Style) or style is None): + return NotImplemented + if style is None or style._null: + return self + if self._null: + return style + new_style: Style = self.__new__(Style) + new_style._ansi = None + new_style._style_definition = None + new_style._color = style._color or self._color + new_style._bgcolor = style._bgcolor or self._bgcolor + new_style._attributes = (self._attributes & ~style._set_attributes) | ( + style._attributes & style._set_attributes + ) + new_style._set_attributes = self._set_attributes | style._set_attributes + new_style._link = style._link or self._link + new_style._link_id = style._link_id or self._link_id + new_style._hash = style._hash + new_style._null = self._null or style._null + if self._meta and style._meta: + new_style._meta = dumps({**self.meta, **style.meta}) + else: + new_style._meta = self._meta or style._meta + return new_style + + +NULL_STYLE = Style() + + +class StyleStack: + """A stack of styles.""" + + __slots__ = ["_stack"] + + def __init__(self, default_style: "Style") -> None: + self._stack: List[Style] = [default_style] + + def __repr__(self) -> str: + return f"" + + @property + def current(self) -> Style: + """Get the Style at the top of the stack.""" + return self._stack[-1] + + def push(self, style: Style) -> None: + """Push a new style on to the stack. + + Args: + style (Style): New style to combine with current style. + """ + self._stack.append(self._stack[-1] + style) + + def pop(self) -> Style: + """Pop last style and discard. + + Returns: + Style: New current style (also available as stack.current) + """ + self._stack.pop() + return self._stack[-1] diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/styled.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/styled.py new file mode 100644 index 0000000..91cd0db --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/styled.py @@ -0,0 +1,42 @@ +from typing import TYPE_CHECKING + +from .measure import Measurement +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult, RenderableType + + +class Styled: + """Apply a style to a renderable. + + Args: + renderable (RenderableType): Any renderable. + style (StyleType): A style to apply across the entire renderable. + """ + + def __init__(self, renderable: "RenderableType", style: "StyleType") -> None: + self.renderable = renderable + self.style = style + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + style = console.get_style(self.style) + rendered_segments = console.render(self.renderable, options) + segments = Segment.apply_style(rendered_segments, style) + return segments + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + return Measurement.get(console, options, self.renderable) + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich import print + from pip._vendor.rich.panel import Panel + + panel = Styled(Panel("hello"), "on blue") + print(panel) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/syntax.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/syntax.py new file mode 100644 index 0000000..58cc103 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/syntax.py @@ -0,0 +1,735 @@ +import os.path +import platform +from pip._vendor.rich.containers import Lines +import textwrap +from abc import ABC, abstractmethod +from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union + +from pip._vendor.pygments.lexer import Lexer +from pip._vendor.pygments.lexers import get_lexer_by_name, guess_lexer_for_filename +from pip._vendor.pygments.style import Style as PygmentsStyle +from pip._vendor.pygments.styles import get_style_by_name +from pip._vendor.pygments.token import ( + Comment, + Error, + Generic, + Keyword, + Name, + Number, + Operator, + String, + Token, + Whitespace, +) +from pip._vendor.pygments.util import ClassNotFound + +from ._loop import loop_first +from .color import Color, blend_rgb +from .console import Console, ConsoleOptions, JustifyMethod, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style +from .text import Text + +TokenType = Tuple[str, ...] + +WINDOWS = platform.system() == "Windows" +DEFAULT_THEME = "monokai" + +# The following styles are based on https://github.com/pygments/pygments/blob/master/pygments/formatters/terminal.py +# A few modifications were made + +ANSI_LIGHT: Dict[TokenType, Style] = { + Token: Style(), + Whitespace: Style(color="white"), + Comment: Style(dim=True), + Comment.Preproc: Style(color="cyan"), + Keyword: Style(color="blue"), + Keyword.Type: Style(color="cyan"), + Operator.Word: Style(color="magenta"), + Name.Builtin: Style(color="cyan"), + Name.Function: Style(color="green"), + Name.Namespace: Style(color="cyan", underline=True), + Name.Class: Style(color="green", underline=True), + Name.Exception: Style(color="cyan"), + Name.Decorator: Style(color="magenta", bold=True), + Name.Variable: Style(color="red"), + Name.Constant: Style(color="red"), + Name.Attribute: Style(color="cyan"), + Name.Tag: Style(color="bright_blue"), + String: Style(color="yellow"), + Number: Style(color="blue"), + Generic.Deleted: Style(color="bright_red"), + Generic.Inserted: Style(color="green"), + Generic.Heading: Style(bold=True), + Generic.Subheading: Style(color="magenta", bold=True), + Generic.Prompt: Style(bold=True), + Generic.Error: Style(color="bright_red"), + Error: Style(color="red", underline=True), +} + +ANSI_DARK: Dict[TokenType, Style] = { + Token: Style(), + Whitespace: Style(color="bright_black"), + Comment: Style(dim=True), + Comment.Preproc: Style(color="bright_cyan"), + Keyword: Style(color="bright_blue"), + Keyword.Type: Style(color="bright_cyan"), + Operator.Word: Style(color="bright_magenta"), + Name.Builtin: Style(color="bright_cyan"), + Name.Function: Style(color="bright_green"), + Name.Namespace: Style(color="bright_cyan", underline=True), + Name.Class: Style(color="bright_green", underline=True), + Name.Exception: Style(color="bright_cyan"), + Name.Decorator: Style(color="bright_magenta", bold=True), + Name.Variable: Style(color="bright_red"), + Name.Constant: Style(color="bright_red"), + Name.Attribute: Style(color="bright_cyan"), + Name.Tag: Style(color="bright_blue"), + String: Style(color="yellow"), + Number: Style(color="bright_blue"), + Generic.Deleted: Style(color="bright_red"), + Generic.Inserted: Style(color="bright_green"), + Generic.Heading: Style(bold=True), + Generic.Subheading: Style(color="bright_magenta", bold=True), + Generic.Prompt: Style(bold=True), + Generic.Error: Style(color="bright_red"), + Error: Style(color="red", underline=True), +} + +RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK} + + +class SyntaxTheme(ABC): + """Base class for a syntax theme.""" + + @abstractmethod + def get_style_for_token(self, token_type: TokenType) -> Style: + """Get a style for a given Pygments token.""" + raise NotImplementedError # pragma: no cover + + @abstractmethod + def get_background_style(self) -> Style: + """Get the background color.""" + raise NotImplementedError # pragma: no cover + + +class PygmentsSyntaxTheme(SyntaxTheme): + """Syntax theme that delegates to Pygments theme.""" + + def __init__(self, theme: Union[str, Type[PygmentsStyle]]) -> None: + self._style_cache: Dict[TokenType, Style] = {} + if isinstance(theme, str): + try: + self._pygments_style_class = get_style_by_name(theme) + except ClassNotFound: + self._pygments_style_class = get_style_by_name("default") + else: + self._pygments_style_class = theme + + self._background_color = self._pygments_style_class.background_color + self._background_style = Style(bgcolor=self._background_color) + + def get_style_for_token(self, token_type: TokenType) -> Style: + """Get a style from a Pygments class.""" + try: + return self._style_cache[token_type] + except KeyError: + try: + pygments_style = self._pygments_style_class.style_for_token(token_type) + except KeyError: + style = Style.null() + else: + color = pygments_style["color"] + bgcolor = pygments_style["bgcolor"] + style = Style( + color="#" + color if color else "#000000", + bgcolor="#" + bgcolor if bgcolor else self._background_color, + bold=pygments_style["bold"], + italic=pygments_style["italic"], + underline=pygments_style["underline"], + ) + self._style_cache[token_type] = style + return style + + def get_background_style(self) -> Style: + return self._background_style + + +class ANSISyntaxTheme(SyntaxTheme): + """Syntax theme to use standard colors.""" + + def __init__(self, style_map: Dict[TokenType, Style]) -> None: + self.style_map = style_map + self._missing_style = Style.null() + self._background_style = Style.null() + self._style_cache: Dict[TokenType, Style] = {} + + def get_style_for_token(self, token_type: TokenType) -> Style: + """Look up style in the style map.""" + try: + return self._style_cache[token_type] + except KeyError: + # Styles form a hierarchy + # We need to go from most to least specific + # e.g. ("foo", "bar", "baz") to ("foo", "bar") to ("foo",) + get_style = self.style_map.get + token = tuple(token_type) + style = self._missing_style + while token: + _style = get_style(token) + if _style is not None: + style = _style + break + token = token[:-1] + self._style_cache[token_type] = style + return style + + def get_background_style(self) -> Style: + return self._background_style + + +class Syntax(JupyterMixin): + """Construct a Syntax object to render syntax highlighted code. + + Args: + code (str): Code to highlight. + lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/) + theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai". + dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False. + line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False. + start_line (int, optional): Starting number for line numbers. Defaults to 1. + line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render. + highlight_lines (Set[int]): A set of line numbers to highlight. + code_width: Width of code to render (not including line numbers), or ``None`` to use all available width. + tab_size (int, optional): Size of tabs. Defaults to 4. + word_wrap (bool, optional): Enable word wrapping. + background_color (str, optional): Optional background color, or None to use theme color. Defaults to None. + indent_guides (bool, optional): Show indent guides. Defaults to False. + """ + + _pygments_style_class: Type[PygmentsStyle] + _theme: SyntaxTheme + + @classmethod + def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme: + """Get a syntax theme instance.""" + if isinstance(name, SyntaxTheme): + return name + theme: SyntaxTheme + if name in RICH_SYNTAX_THEMES: + theme = ANSISyntaxTheme(RICH_SYNTAX_THEMES[name]) + else: + theme = PygmentsSyntaxTheme(name) + return theme + + def __init__( + self, + code: str, + lexer: Union[Lexer, str], + *, + theme: Union[str, SyntaxTheme] = DEFAULT_THEME, + dedent: bool = False, + line_numbers: bool = False, + start_line: int = 1, + line_range: Optional[Tuple[int, int]] = None, + highlight_lines: Optional[Set[int]] = None, + code_width: Optional[int] = None, + tab_size: int = 4, + word_wrap: bool = False, + background_color: Optional[str] = None, + indent_guides: bool = False, + ) -> None: + self.code = code + self._lexer = lexer + self.dedent = dedent + self.line_numbers = line_numbers + self.start_line = start_line + self.line_range = line_range + self.highlight_lines = highlight_lines or set() + self.code_width = code_width + self.tab_size = tab_size + self.word_wrap = word_wrap + self.background_color = background_color + self.background_style = ( + Style(bgcolor=background_color) if background_color else Style() + ) + self.indent_guides = indent_guides + + self._theme = self.get_theme(theme) + + @classmethod + def from_path( + cls, + path: str, + encoding: str = "utf-8", + theme: Union[str, SyntaxTheme] = DEFAULT_THEME, + dedent: bool = False, + line_numbers: bool = False, + line_range: Optional[Tuple[int, int]] = None, + start_line: int = 1, + highlight_lines: Optional[Set[int]] = None, + code_width: Optional[int] = None, + tab_size: int = 4, + word_wrap: bool = False, + background_color: Optional[str] = None, + indent_guides: bool = False, + ) -> "Syntax": + """Construct a Syntax object from a file. + + Args: + path (str): Path to file to highlight. + encoding (str): Encoding of file. + theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs". + dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True. + line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False. + start_line (int, optional): Starting number for line numbers. Defaults to 1. + line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render. + highlight_lines (Set[int]): A set of line numbers to highlight. + code_width: Width of code to render (not including line numbers), or ``None`` to use all available width. + tab_size (int, optional): Size of tabs. Defaults to 4. + word_wrap (bool, optional): Enable word wrapping of code. + background_color (str, optional): Optional background color, or None to use theme color. Defaults to None. + indent_guides (bool, optional): Show indent guides. Defaults to False. + + Returns: + [Syntax]: A Syntax object that may be printed to the console + """ + with open(path, "rt", encoding=encoding) as code_file: + code = code_file.read() + + lexer = None + lexer_name = "default" + try: + _, ext = os.path.splitext(path) + if ext: + extension = ext.lstrip(".").lower() + lexer = get_lexer_by_name(extension) + lexer_name = lexer.name + except ClassNotFound: + pass + + if lexer is None: + try: + lexer_name = guess_lexer_for_filename(path, code).name + except ClassNotFound: + pass + + return cls( + code, + lexer_name, + theme=theme, + dedent=dedent, + line_numbers=line_numbers, + line_range=line_range, + start_line=start_line, + highlight_lines=highlight_lines, + code_width=code_width, + tab_size=tab_size, + word_wrap=word_wrap, + background_color=background_color, + indent_guides=indent_guides, + ) + + def _get_base_style(self) -> Style: + """Get the base style.""" + default_style = self._theme.get_background_style() + self.background_style + return default_style + + def _get_token_color(self, token_type: TokenType) -> Optional[Color]: + """Get a color (if any) for the given token. + + Args: + token_type (TokenType): A token type tuple from Pygments. + + Returns: + Optional[Color]: Color from theme, or None for no color. + """ + style = self._theme.get_style_for_token(token_type) + return style.color + + @property + def lexer(self) -> Optional[Lexer]: + """The lexer for this syntax, or None if no lexer was found. + + Tries to find the lexer by name if a string was passed to the constructor. + """ + + if isinstance(self._lexer, Lexer): + return self._lexer + try: + return get_lexer_by_name( + self._lexer, + stripnl=False, + ensurenl=True, + tabsize=self.tab_size, + ) + except ClassNotFound: + return None + + def highlight( + self, code: str, line_range: Optional[Tuple[int, int]] = None + ) -> Text: + """Highlight code and return a Text instance. + + Args: + code (str): Code to highlight. + line_range(Tuple[int, int], optional): Optional line range to highlight. + + Returns: + Text: A text instance containing highlighted syntax. + """ + + base_style = self._get_base_style() + justify: JustifyMethod = ( + "default" if base_style.transparent_background else "left" + ) + + text = Text( + justify=justify, + style=base_style, + tab_size=self.tab_size, + no_wrap=not self.word_wrap, + ) + _get_theme_style = self._theme.get_style_for_token + + lexer = self.lexer + + if lexer is None: + text.append(code) + else: + if line_range: + # More complicated path to only stylize a portion of the code + # This speeds up further operations as there are less spans to process + line_start, line_end = line_range + + def line_tokenize() -> Iterable[Tuple[Any, str]]: + """Split tokens to one per line.""" + assert lexer + + for token_type, token in lexer.get_tokens(code): + while token: + line_token, new_line, token = token.partition("\n") + yield token_type, line_token + new_line + + def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]: + """Convert tokens to spans.""" + tokens = iter(line_tokenize()) + line_no = 0 + _line_start = line_start - 1 + + # Skip over tokens until line start + while line_no < _line_start: + _token_type, token = next(tokens) + yield (token, None) + if token.endswith("\n"): + line_no += 1 + # Generate spans until line end + for token_type, token in tokens: + yield (token, _get_theme_style(token_type)) + if token.endswith("\n"): + line_no += 1 + if line_no >= line_end: + break + + text.append_tokens(tokens_to_spans()) + + else: + text.append_tokens( + (token, _get_theme_style(token_type)) + for token_type, token in lexer.get_tokens(code) + ) + if self.background_color is not None: + text.stylize(f"on {self.background_color}") + return text + + def _get_line_numbers_color(self, blend: float = 0.3) -> Color: + background_style = self._theme.get_background_style() + self.background_style + background_color = background_style.bgcolor + if background_color is None or background_color.is_system_defined: + return Color.default() + foreground_color = self._get_token_color(Token.Text) + if foreground_color is None or foreground_color.is_system_defined: + return foreground_color or Color.default() + new_color = blend_rgb( + background_color.get_truecolor(), + foreground_color.get_truecolor(), + cross_fade=blend, + ) + return Color.from_triplet(new_color) + + @property + def _numbers_column_width(self) -> int: + """Get the number of characters used to render the numbers column.""" + column_width = 0 + if self.line_numbers: + column_width = len(str(self.start_line + self.code.count("\n"))) + 2 + return column_width + + def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]: + """Get background, number, and highlight styles for line numbers.""" + background_style = self._get_base_style() + if background_style.transparent_background: + return Style.null(), Style(dim=True), Style.null() + if console.color_system in ("256", "truecolor"): + number_style = Style.chain( + background_style, + self._theme.get_style_for_token(Token.Text), + Style(color=self._get_line_numbers_color()), + self.background_style, + ) + highlight_number_style = Style.chain( + background_style, + self._theme.get_style_for_token(Token.Text), + Style(bold=True, color=self._get_line_numbers_color(0.9)), + self.background_style, + ) + else: + number_style = background_style + Style(dim=True) + highlight_number_style = background_style + Style(dim=False) + return background_style, number_style, highlight_number_style + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.code_width is not None: + width = self.code_width + self._numbers_column_width + return Measurement(self._numbers_column_width, width) + return Measurement(self._numbers_column_width, options.max_width) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + + transparent_background = self._get_base_style().transparent_background + code_width = ( + ( + (options.max_width - self._numbers_column_width - 1) + if self.line_numbers + else options.max_width + ) + if self.code_width is None + else self.code_width + ) + + line_offset = 0 + if self.line_range: + start_line, end_line = self.line_range + line_offset = max(0, start_line - 1) + + ends_on_nl = self.code.endswith("\n") + code = self.code if ends_on_nl else self.code + "\n" + code = textwrap.dedent(code) if self.dedent else code + code = code.expandtabs(self.tab_size) + text = self.highlight(code, self.line_range) + + ( + background_style, + number_style, + highlight_number_style, + ) = self._get_number_styles(console) + + if not self.line_numbers and not self.word_wrap and not self.line_range: + if not ends_on_nl: + text.remove_suffix("\n") + # Simple case of just rendering text + style = ( + self._get_base_style() + + self._theme.get_style_for_token(Comment) + + Style(dim=True) + + self.background_style + ) + if self.indent_guides and not options.ascii_only: + text = text.with_indent_guides(self.tab_size, style=style) + text.overflow = "crop" + if style.transparent_background: + yield from console.render( + text, options=options.update(width=code_width) + ) + else: + syntax_lines = console.render_lines( + text, + options.update(width=code_width, height=None), + style=self.background_style, + pad=True, + new_lines=True, + ) + for syntax_line in syntax_lines: + yield from syntax_line + return + + lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl) + if self.line_range: + lines = lines[line_offset:end_line] + + if self.indent_guides and not options.ascii_only: + style = ( + self._get_base_style() + + self._theme.get_style_for_token(Comment) + + Style(dim=True) + + self.background_style + ) + lines = ( + Text("\n") + .join(lines) + .with_indent_guides(self.tab_size, style=style) + .split("\n", allow_blank=True) + ) + + numbers_column_width = self._numbers_column_width + render_options = options.update(width=code_width) + + highlight_line = self.highlight_lines.__contains__ + _Segment = Segment + padding = _Segment(" " * numbers_column_width + " ", background_style) + new_line = _Segment("\n") + + line_pointer = "> " if options.legacy_windows else "â± " + + for line_no, line in enumerate(lines, self.start_line + line_offset): + if self.word_wrap: + wrapped_lines = console.render_lines( + line, + render_options.update(height=None), + style=background_style, + pad=not transparent_background, + ) + + else: + segments = list(line.render(console, end="")) + if options.no_wrap: + wrapped_lines = [segments] + else: + wrapped_lines = [ + _Segment.adjust_line_length( + segments, + render_options.max_width, + style=background_style, + pad=not transparent_background, + ) + ] + if self.line_numbers: + for first, wrapped_line in loop_first(wrapped_lines): + if first: + line_column = str(line_no).rjust(numbers_column_width - 2) + " " + if highlight_line(line_no): + yield _Segment(line_pointer, Style(color="red")) + yield _Segment(line_column, highlight_number_style) + else: + yield _Segment(" ", highlight_number_style) + yield _Segment(line_column, number_style) + else: + yield padding + yield from wrapped_line + yield new_line + else: + for wrapped_line in wrapped_lines: + yield from wrapped_line + yield new_line + + +if __name__ == "__main__": # pragma: no cover + + import argparse + import sys + + parser = argparse.ArgumentParser( + description="Render syntax to the console with Rich" + ) + parser.add_argument( + "path", + metavar="PATH", + help="path to file, or - for stdin", + ) + parser.add_argument( + "-c", + "--force-color", + dest="force_color", + action="store_true", + default=None, + help="force color for non-terminals", + ) + parser.add_argument( + "-i", + "--indent-guides", + dest="indent_guides", + action="store_true", + default=False, + help="display indent guides", + ) + parser.add_argument( + "-l", + "--line-numbers", + dest="line_numbers", + action="store_true", + help="render line numbers", + ) + parser.add_argument( + "-w", + "--width", + type=int, + dest="width", + default=None, + help="width of output (default will auto-detect)", + ) + parser.add_argument( + "-r", + "--wrap", + dest="word_wrap", + action="store_true", + default=False, + help="word wrap long lines", + ) + parser.add_argument( + "-s", + "--soft-wrap", + action="store_true", + dest="soft_wrap", + default=False, + help="enable soft wrapping mode", + ) + parser.add_argument( + "-t", "--theme", dest="theme", default="monokai", help="pygments theme" + ) + parser.add_argument( + "-b", + "--background-color", + dest="background_color", + default=None, + help="Override background color", + ) + parser.add_argument( + "-x", + "--lexer", + default="default", + dest="lexer_name", + help="Lexer name", + ) + args = parser.parse_args() + + from pip._vendor.rich.console import Console + + console = Console(force_terminal=args.force_color, width=args.width) + + if args.path == "-": + code = sys.stdin.read() + syntax = Syntax( + code=code, + lexer=args.lexer_name, + line_numbers=args.line_numbers, + word_wrap=args.word_wrap, + theme=args.theme, + background_color=args.background_color, + indent_guides=args.indent_guides, + ) + else: + syntax = Syntax.from_path( + args.path, + line_numbers=args.line_numbers, + word_wrap=args.word_wrap, + theme=args.theme, + background_color=args.background_color, + indent_guides=args.indent_guides, + ) + console.print(syntax, soft_wrap=args.soft_wrap) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/table.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/table.py new file mode 100644 index 0000000..da43860 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/table.py @@ -0,0 +1,968 @@ +from dataclasses import dataclass, field, replace +from typing import ( + TYPE_CHECKING, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + Union, +) + +from . import box, errors +from ._loop import loop_first_last, loop_last +from ._pick import pick_bool +from ._ratio import ratio_distribute, ratio_reduce +from .align import VerticalAlignMethod +from .jupyter import JupyterMixin +from .measure import Measurement +from .padding import Padding, PaddingDimensions +from .protocol import is_renderable +from .segment import Segment +from .style import Style, StyleType +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + JustifyMethod, + OverflowMethod, + RenderableType, + RenderResult, + ) + + +@dataclass +class Column: + """Defines a column in a table.""" + + header: "RenderableType" = "" + """RenderableType: Renderable for the header (typically a string)""" + + footer: "RenderableType" = "" + """RenderableType: Renderable for the footer (typically a string)""" + + header_style: StyleType = "" + """StyleType: The style of the header.""" + + footer_style: StyleType = "" + """StyleType: The style of the footer.""" + + style: StyleType = "" + """StyleType: The style of the column.""" + + justify: "JustifyMethod" = "left" + """str: How to justify text within the column ("left", "center", "right", or "full")""" + + vertical: "VerticalAlignMethod" = "top" + """str: How to vertically align content ("top", "middle", or "bottom")""" + + overflow: "OverflowMethod" = "ellipsis" + """str: Overflow method.""" + + width: Optional[int] = None + """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width.""" + + min_width: Optional[int] = None + """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None.""" + + max_width: Optional[int] = None + """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None.""" + + ratio: Optional[int] = None + """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents.""" + + no_wrap: bool = False + """bool: Prevent wrapping of text within the column. Defaults to ``False``.""" + + _index: int = 0 + """Index of column.""" + + _cells: List["RenderableType"] = field(default_factory=list) + + def copy(self) -> "Column": + """Return a copy of this Column.""" + return replace(self, _cells=[]) + + @property + def cells(self) -> Iterable["RenderableType"]: + """Get all cells in the column, not including header.""" + yield from self._cells + + @property + def flexible(self) -> bool: + """Check if this column is flexible.""" + return self.ratio is not None + + +@dataclass +class Row: + """Information regarding a row.""" + + style: Optional[StyleType] = None + """Style to apply to row.""" + + end_section: bool = False + """Indicated end of section, which will force a line beneath the row.""" + + +class _Cell(NamedTuple): + """A single cell in a table.""" + + style: StyleType + """Style to apply to cell.""" + renderable: "RenderableType" + """Cell renderable.""" + vertical: VerticalAlignMethod + """Cell vertical alignment.""" + + +class Table(JupyterMixin): + """A console renderable to draw a table. + + Args: + *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. + title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. + caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. + width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. + min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. + box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD. + safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. + padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. + pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. + expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. + show_header (bool, optional): Show a header row. Defaults to True. + show_footer (bool, optional): Show a footer row. Defaults to False. + show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. + show_lines (bool, optional): Draw lines between every row. Defaults to False. + leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. + style (Union[str, Style], optional): Default style for the table. Defaults to "none". + row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None. + header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". + footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". + border_style (Union[str, Style], optional): Style of the border. Defaults to None. + title_style (Union[str, Style], optional): Style of the title. Defaults to None. + caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. + title_justify (str, optional): Justify method for title. Defaults to "center". + caption_justify (str, optional): Justify method for caption. Defaults to "center". + highlight (bool, optional): Highlight cell contents (if str). Defaults to False. + """ + + columns: List[Column] + rows: List[Row] + + def __init__( + self, + *headers: Union[Column, str], + title: Optional[TextType] = None, + caption: Optional[TextType] = None, + width: Optional[int] = None, + min_width: Optional[int] = None, + box: Optional[box.Box] = box.HEAVY_HEAD, + safe_box: Optional[bool] = None, + padding: PaddingDimensions = (0, 1), + collapse_padding: bool = False, + pad_edge: bool = True, + expand: bool = False, + show_header: bool = True, + show_footer: bool = False, + show_edge: bool = True, + show_lines: bool = False, + leading: int = 0, + style: StyleType = "none", + row_styles: Optional[Iterable[StyleType]] = None, + header_style: Optional[StyleType] = "table.header", + footer_style: Optional[StyleType] = "table.footer", + border_style: Optional[StyleType] = None, + title_style: Optional[StyleType] = None, + caption_style: Optional[StyleType] = None, + title_justify: "JustifyMethod" = "center", + caption_justify: "JustifyMethod" = "center", + highlight: bool = False, + ) -> None: + + self.columns: List[Column] = [] + self.rows: List[Row] = [] + self.title = title + self.caption = caption + self.width = width + self.min_width = min_width + self.box = box + self.safe_box = safe_box + self._padding = Padding.unpack(padding) + self.pad_edge = pad_edge + self._expand = expand + self.show_header = show_header + self.show_footer = show_footer + self.show_edge = show_edge + self.show_lines = show_lines + self.leading = leading + self.collapse_padding = collapse_padding + self.style = style + self.header_style = header_style or "" + self.footer_style = footer_style or "" + self.border_style = border_style + self.title_style = title_style + self.caption_style = caption_style + self.title_justify: "JustifyMethod" = title_justify + self.caption_justify: "JustifyMethod" = caption_justify + self.highlight = highlight + self.row_styles: Sequence[StyleType] = list(row_styles or []) + append_column = self.columns.append + for header in headers: + if isinstance(header, str): + self.add_column(header=header) + else: + header._index = len(self.columns) + append_column(header) + + @classmethod + def grid( + cls, + *headers: Union[Column, str], + padding: PaddingDimensions = 0, + collapse_padding: bool = True, + pad_edge: bool = False, + expand: bool = False, + ) -> "Table": + """Get a table with no lines, headers, or footer. + + Args: + *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. + padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0. + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True. + pad_edge (bool, optional): Enable padding around edges of table. Defaults to False. + expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. + + Returns: + Table: A table instance. + """ + return cls( + *headers, + box=None, + padding=padding, + collapse_padding=collapse_padding, + show_header=False, + show_footer=False, + show_edge=False, + pad_edge=pad_edge, + expand=expand, + ) + + @property + def expand(self) -> bool: + """Setting a non-None self.width implies expand.""" + return self._expand or self.width is not None + + @expand.setter + def expand(self, expand: bool) -> None: + """Set expand.""" + self._expand = expand + + @property + def _extra_width(self) -> int: + """Get extra width to add to cell content.""" + width = 0 + if self.box and self.show_edge: + width += 2 + if self.box: + width += len(self.columns) - 1 + return width + + @property + def row_count(self) -> int: + """Get the current number of rows.""" + return len(self.rows) + + def get_row_style(self, console: "Console", index: int) -> StyleType: + """Get the current row style.""" + style = Style.null() + if self.row_styles: + style += console.get_style(self.row_styles[index % len(self.row_styles)]) + row_style = self.rows[index].style + if row_style is not None: + style += console.get_style(row_style) + return style + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + max_width = options.max_width + if self.width is not None: + max_width = self.width + if max_width < 0: + return Measurement(0, 0) + + extra_width = self._extra_width + max_width = sum( + self._calculate_column_widths( + console, options.update_width(max_width - extra_width) + ) + ) + _measure_column = self._measure_column + + measurements = [ + _measure_column(console, options.update_width(max_width), column) + for column in self.columns + ] + minimum_width = ( + sum(measurement.minimum for measurement in measurements) + extra_width + ) + maximum_width = ( + sum(measurement.maximum for measurement in measurements) + extra_width + if (self.width is None) + else self.width + ) + measurement = Measurement(minimum_width, maximum_width) + measurement = measurement.clamp(self.min_width) + return measurement + + @property + def padding(self) -> Tuple[int, int, int, int]: + """Get cell padding.""" + return self._padding + + @padding.setter + def padding(self, padding: PaddingDimensions) -> "Table": + """Set cell padding.""" + self._padding = Padding.unpack(padding) + return self + + def add_column( + self, + header: "RenderableType" = "", + footer: "RenderableType" = "", + *, + header_style: Optional[StyleType] = None, + footer_style: Optional[StyleType] = None, + style: Optional[StyleType] = None, + justify: "JustifyMethod" = "left", + vertical: "VerticalAlignMethod" = "top", + overflow: "OverflowMethod" = "ellipsis", + width: Optional[int] = None, + min_width: Optional[int] = None, + max_width: Optional[int] = None, + ratio: Optional[int] = None, + no_wrap: bool = False, + ) -> None: + """Add a column to the table. + + Args: + header (RenderableType, optional): Text or renderable for the header. + Defaults to "". + footer (RenderableType, optional): Text or renderable for the footer. + Defaults to "". + header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None. + footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None. + style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None. + justify (JustifyMethod, optional): Alignment for cells. Defaults to "left". + vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top". + overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis". + width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None. + min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None. + max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None. + ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None. + no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column. + """ + + column = Column( + _index=len(self.columns), + header=header, + footer=footer, + header_style=header_style or "", + footer_style=footer_style or "", + style=style or "", + justify=justify, + vertical=vertical, + overflow=overflow, + width=width, + min_width=min_width, + max_width=max_width, + ratio=ratio, + no_wrap=no_wrap, + ) + self.columns.append(column) + + def add_row( + self, + *renderables: Optional["RenderableType"], + style: Optional[StyleType] = None, + end_section: bool = False, + ) -> None: + """Add a row of renderables. + + Args: + *renderables (None or renderable): Each cell in a row must be a renderable object (including str), + or ``None`` for a blank cell. + style (StyleType, optional): An optional style to apply to the entire row. Defaults to None. + end_section (bool, optional): End a section and draw a line. Defaults to False. + + Raises: + errors.NotRenderableError: If you add something that can't be rendered. + """ + + def add_cell(column: Column, renderable: "RenderableType") -> None: + column._cells.append(renderable) + + cell_renderables: List[Optional["RenderableType"]] = list(renderables) + + columns = self.columns + if len(cell_renderables) < len(columns): + cell_renderables = [ + *cell_renderables, + *[None] * (len(columns) - len(cell_renderables)), + ] + for index, renderable in enumerate(cell_renderables): + if index == len(columns): + column = Column(_index=index) + for _ in self.rows: + add_cell(column, Text("")) + self.columns.append(column) + else: + column = columns[index] + if renderable is None: + add_cell(column, "") + elif is_renderable(renderable): + add_cell(column, renderable) + else: + raise errors.NotRenderableError( + f"unable to render {type(renderable).__name__}; a string or other renderable object is required" + ) + self.rows.append(Row(style=style, end_section=end_section)) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + + if not self.columns: + yield Segment("\n") + return + + max_width = options.max_width + if self.width is not None: + max_width = self.width + + extra_width = self._extra_width + widths = self._calculate_column_widths( + console, options.update_width(max_width - extra_width) + ) + table_width = sum(widths) + extra_width + + render_options = options.update( + width=table_width, highlight=self.highlight, height=None + ) + + def render_annotation( + text: TextType, style: StyleType, justify: "JustifyMethod" = "center" + ) -> "RenderResult": + render_text = ( + console.render_str(text, style=style, highlight=False) + if isinstance(text, str) + else text + ) + return console.render( + render_text, options=render_options.update(justify=justify) + ) + + if self.title: + yield from render_annotation( + self.title, + style=Style.pick_first(self.title_style, "table.title"), + justify=self.title_justify, + ) + yield from self._render(console, render_options, widths) + if self.caption: + yield from render_annotation( + self.caption, + style=Style.pick_first(self.caption_style, "table.caption"), + justify=self.caption_justify, + ) + + def _calculate_column_widths( + self, console: "Console", options: "ConsoleOptions" + ) -> List[int]: + """Calculate the widths of each column, including padding, not including borders.""" + max_width = options.max_width + columns = self.columns + width_ranges = [ + self._measure_column(console, options, column) for column in columns + ] + widths = [_range.maximum or 1 for _range in width_ranges] + get_padding_width = self._get_padding_width + extra_width = self._extra_width + if self.expand: + ratios = [col.ratio or 0 for col in columns if col.flexible] + if any(ratios): + fixed_widths = [ + 0 if column.flexible else _range.maximum + for _range, column in zip(width_ranges, columns) + ] + flex_minimum = [ + (column.width or 1) + get_padding_width(column._index) + for column in columns + if column.flexible + ] + flexible_width = max_width - sum(fixed_widths) + flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum) + iter_flex_widths = iter(flex_widths) + for index, column in enumerate(columns): + if column.flexible: + widths[index] = fixed_widths[index] + next(iter_flex_widths) + table_width = sum(widths) + + if table_width > max_width: + widths = self._collapse_widths( + widths, + [(column.width is None and not column.no_wrap) for column in columns], + max_width, + ) + table_width = sum(widths) + # last resort, reduce columns evenly + if table_width > max_width: + excess_width = table_width - max_width + widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths) + table_width = sum(widths) + + width_ranges = [ + self._measure_column(console, options.update_width(width), column) + for width, column in zip(widths, columns) + ] + widths = [_range.maximum or 0 for _range in width_ranges] + + if (table_width < max_width and self.expand) or ( + self.min_width is not None and table_width < (self.min_width - extra_width) + ): + _max_width = ( + max_width + if self.min_width is None + else min(self.min_width - extra_width, max_width) + ) + pad_widths = ratio_distribute(_max_width - table_width, widths) + widths = [_width + pad for _width, pad in zip(widths, pad_widths)] + + return widths + + @classmethod + def _collapse_widths( + cls, widths: List[int], wrapable: List[bool], max_width: int + ) -> List[int]: + """Reduce widths so that the total is under max_width. + + Args: + widths (List[int]): List of widths. + wrapable (List[bool]): List of booleans that indicate if a column may shrink. + max_width (int): Maximum width to reduce to. + + Returns: + List[int]: A new list of widths. + """ + total_width = sum(widths) + excess_width = total_width - max_width + if any(wrapable): + while total_width and excess_width > 0: + max_column = max( + width for width, allow_wrap in zip(widths, wrapable) if allow_wrap + ) + second_max_column = max( + width if allow_wrap and width != max_column else 0 + for width, allow_wrap in zip(widths, wrapable) + ) + column_difference = max_column - second_max_column + ratios = [ + (1 if (width == max_column and allow_wrap) else 0) + for width, allow_wrap in zip(widths, wrapable) + ] + if not any(ratios) or not column_difference: + break + max_reduce = [min(excess_width, column_difference)] * len(widths) + widths = ratio_reduce(excess_width, ratios, max_reduce, widths) + + total_width = sum(widths) + excess_width = total_width - max_width + return widths + + def _get_cells( + self, console: "Console", column_index: int, column: Column + ) -> Iterable[_Cell]: + """Get all the cells with padding and optional header.""" + + collapse_padding = self.collapse_padding + pad_edge = self.pad_edge + padding = self.padding + any_padding = any(padding) + + first_column = column_index == 0 + last_column = column_index == len(self.columns) - 1 + + _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {} + + def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]: + cached = _padding_cache.get((first_row, last_row)) + if cached: + return cached + top, right, bottom, left = padding + + if collapse_padding: + if not first_column: + left = max(0, left - right) + if not last_row: + bottom = max(0, top - bottom) + + if not pad_edge: + if first_column: + left = 0 + if last_column: + right = 0 + if first_row: + top = 0 + if last_row: + bottom = 0 + _padding = (top, right, bottom, left) + _padding_cache[(first_row, last_row)] = _padding + return _padding + + raw_cells: List[Tuple[StyleType, "RenderableType"]] = [] + _append = raw_cells.append + get_style = console.get_style + if self.show_header: + header_style = get_style(self.header_style or "") + get_style( + column.header_style + ) + _append((header_style, column.header)) + cell_style = get_style(column.style or "") + for cell in column.cells: + _append((cell_style, cell)) + if self.show_footer: + footer_style = get_style(self.footer_style or "") + get_style( + column.footer_style + ) + _append((footer_style, column.footer)) + + if any_padding: + _Padding = Padding + for first, last, (style, renderable) in loop_first_last(raw_cells): + yield _Cell( + style, + _Padding(renderable, get_padding(first, last)), + getattr(renderable, "vertical", None) or column.vertical, + ) + else: + for (style, renderable) in raw_cells: + yield _Cell( + style, + renderable, + getattr(renderable, "vertical", None) or column.vertical, + ) + + def _get_padding_width(self, column_index: int) -> int: + """Get extra width from padding.""" + _, pad_right, _, pad_left = self.padding + if self.collapse_padding: + if column_index > 0: + pad_left = max(0, pad_left - pad_right) + return pad_left + pad_right + + def _measure_column( + self, + console: "Console", + options: "ConsoleOptions", + column: Column, + ) -> Measurement: + """Get the minimum and maximum width of the column.""" + + max_width = options.max_width + if max_width < 1: + return Measurement(0, 0) + + padding_width = self._get_padding_width(column._index) + + if column.width is not None: + # Fixed width column + return Measurement( + column.width + padding_width, column.width + padding_width + ).with_maximum(max_width) + # Flexible column, we need to measure contents + min_widths: List[int] = [] + max_widths: List[int] = [] + append_min = min_widths.append + append_max = max_widths.append + get_render_width = Measurement.get + for cell in self._get_cells(console, column._index, column): + _min, _max = get_render_width(console, options, cell.renderable) + append_min(_min) + append_max(_max) + + measurement = Measurement( + max(min_widths) if min_widths else 1, + max(max_widths) if max_widths else max_width, + ).with_maximum(max_width) + measurement = measurement.clamp( + None if column.min_width is None else column.min_width + padding_width, + None if column.max_width is None else column.max_width + padding_width, + ) + return measurement + + def _render( + self, console: "Console", options: "ConsoleOptions", widths: List[int] + ) -> "RenderResult": + table_style = console.get_style(self.style or "") + + border_style = table_style + console.get_style(self.border_style or "") + _column_cells = ( + self._get_cells(console, column_index, column) + for column_index, column in enumerate(self.columns) + ) + row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells)) + _box = ( + self.box.substitute( + options, safe=pick_bool(self.safe_box, console.safe_box) + ) + if self.box + else None + ) + + # _box = self.box + new_line = Segment.line() + + columns = self.columns + show_header = self.show_header + show_footer = self.show_footer + show_edge = self.show_edge + show_lines = self.show_lines + leading = self.leading + + _Segment = Segment + if _box: + box_segments = [ + ( + _Segment(_box.head_left, border_style), + _Segment(_box.head_right, border_style), + _Segment(_box.head_vertical, border_style), + ), + ( + _Segment(_box.foot_left, border_style), + _Segment(_box.foot_right, border_style), + _Segment(_box.foot_vertical, border_style), + ), + ( + _Segment(_box.mid_left, border_style), + _Segment(_box.mid_right, border_style), + _Segment(_box.mid_vertical, border_style), + ), + ] + if show_edge: + yield _Segment(_box.get_top(widths), border_style) + yield new_line + else: + box_segments = [] + + get_row_style = self.get_row_style + get_style = console.get_style + + for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)): + header_row = first and show_header + footer_row = last and show_footer + row = ( + self.rows[index - show_header] + if (not header_row and not footer_row) + else None + ) + max_height = 1 + cells: List[List[List[Segment]]] = [] + if header_row or footer_row: + row_style = Style.null() + else: + row_style = get_style( + get_row_style(console, index - 1 if show_header else index) + ) + for width, cell, column in zip(widths, row_cell, columns): + render_options = options.update( + width=width, + justify=column.justify, + no_wrap=column.no_wrap, + overflow=column.overflow, + height=None, + ) + lines = console.render_lines( + cell.renderable, + render_options, + style=get_style(cell.style) + row_style, + ) + max_height = max(max_height, len(lines)) + cells.append(lines) + + row_height = max(len(cell) for cell in cells) + + def align_cell( + cell: List[List[Segment]], + vertical: "VerticalAlignMethod", + width: int, + style: Style, + ) -> List[List[Segment]]: + if header_row: + vertical = "bottom" + elif footer_row: + vertical = "top" + + if vertical == "top": + return _Segment.align_top(cell, width, row_height, style) + elif vertical == "middle": + return _Segment.align_middle(cell, width, row_height, style) + return _Segment.align_bottom(cell, width, row_height, style) + + cells[:] = [ + _Segment.set_shape( + align_cell( + cell, + _cell.vertical, + width, + get_style(_cell.style) + row_style, + ), + width, + max_height, + ) + for width, _cell, cell, column in zip(widths, row_cell, cells, columns) + ] + + if _box: + if last and show_footer: + yield _Segment( + _box.get_row(widths, "foot", edge=show_edge), border_style + ) + yield new_line + left, right, _divider = box_segments[0 if first else (2 if last else 1)] + + # If the column divider is whitespace also style it with the row background + divider = ( + _divider + if _divider.text.strip() + else _Segment( + _divider.text, row_style.background_style + _divider.style + ) + ) + for line_no in range(max_height): + if show_edge: + yield left + for last_cell, rendered_cell in loop_last(cells): + yield from rendered_cell[line_no] + if not last_cell: + yield divider + if show_edge: + yield right + yield new_line + else: + for line_no in range(max_height): + for rendered_cell in cells: + yield from rendered_cell[line_no] + yield new_line + if _box and first and show_header: + yield _Segment( + _box.get_row(widths, "head", edge=show_edge), border_style + ) + yield new_line + end_section = row and row.end_section + if _box and (show_lines or leading or end_section): + if ( + not last + and not (show_footer and index >= len(row_cells) - 2) + and not (show_header and header_row) + ): + if leading: + yield _Segment( + _box.get_row(widths, "mid", edge=show_edge) * leading, + border_style, + ) + else: + yield _Segment( + _box.get_row(widths, "row", edge=show_edge), border_style + ) + yield new_line + + if _box and show_edge: + yield _Segment(_box.get_bottom(widths), border_style) + yield new_line + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + from pip._vendor.rich.highlighter import ReprHighlighter + from pip._vendor.rich.table import Table as Table + + from ._timer import timer + + with timer("Table render"): + table = Table( + title="Star Wars Movies", + caption="Rich example table", + caption_justify="right", + ) + + table.add_column( + "Released", header_style="bright_cyan", style="cyan", no_wrap=True + ) + table.add_column("Title", style="magenta") + table.add_column("Box Office", justify="right", style="green") + + table.add_row( + "Dec 20, 2019", + "Star Wars: The Rise of Skywalker", + "$952,110,690", + ) + table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") + table.add_row( + "Dec 15, 2017", + "Star Wars Ep. V111: The Last Jedi", + "$1,332,539,889", + style="on black", + end_section=True, + ) + table.add_row( + "Dec 16, 2016", + "Rogue One: A Star Wars Story", + "$1,332,439,889", + ) + + def header(text: str) -> None: + console.print() + console.rule(highlight(text)) + console.print() + + console = Console() + highlight = ReprHighlighter() + header("Example Table") + console.print(table, justify="center") + + table.expand = True + header("expand=True") + console.print(table) + + table.width = 50 + header("width=50") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + header("row_styles=['dim', 'none']") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.leading = 1 + header("leading=1, row_styles=['dim', 'none']") + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.show_lines = True + table.leading = 0 + header("show_lines=True, row_styles=['dim', 'none']") + console.print(table, justify="center") diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/tabulate.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/tabulate.py new file mode 100644 index 0000000..6889f2d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/tabulate.py @@ -0,0 +1,51 @@ +from collections.abc import Mapping +from typing import Any, Optional +import warnings + +from pip._vendor.rich.console import JustifyMethod + +from . import box +from .highlighter import ReprHighlighter +from .pretty import Pretty +from .table import Table + + +def tabulate_mapping( + mapping: "Mapping[Any, Any]", + title: Optional[str] = None, + caption: Optional[str] = None, + title_justify: Optional[JustifyMethod] = None, + caption_justify: Optional[JustifyMethod] = None, +) -> Table: + """Generate a simple table from a mapping. + + Args: + mapping (Mapping): A mapping object (e.g. a dict); + title (str, optional): Optional title to be displayed over the table. + caption (str, optional): Optional caption to be displayed below the table. + title_justify (str, optional): Justify method for title. Defaults to None. + caption_justify (str, optional): Justify method for caption. Defaults to None. + + Returns: + Table: A table instance which may be rendered by the Console. + """ + warnings.warn("tabulate_mapping will be deprecated in Rich v11", DeprecationWarning) + table = Table( + show_header=False, + title=title, + caption=caption, + box=box.ROUNDED, + border_style="blue", + ) + table.title = title + table.caption = caption + if title_justify is not None: + table.title_justify = title_justify + if caption_justify is not None: + table.caption_justify = caption_justify + highlighter = ReprHighlighter() + for key, value in mapping.items(): + table.add_row( + Pretty(key, highlighter=highlighter), Pretty(value, highlighter=highlighter) + ) + return table diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/terminal_theme.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/terminal_theme.py new file mode 100644 index 0000000..801ac0b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/terminal_theme.py @@ -0,0 +1,55 @@ +from typing import List, Optional, Tuple + +from .color_triplet import ColorTriplet +from .palette import Palette + +_ColorTuple = Tuple[int, int, int] + + +class TerminalTheme: + """A color theme used when exporting console content. + + Args: + background (Tuple[int, int, int]): The background color. + foreground (Tuple[int, int, int]): The foreground (text) color. + normal (List[Tuple[int, int, int]]): A list of 8 normal intensity colors. + bright (List[Tuple[int, int, int]], optional): A list of 8 bright colors, or None + to repeat normal intensity. Defaults to None. + """ + + def __init__( + self, + background: _ColorTuple, + foreground: _ColorTuple, + normal: List[_ColorTuple], + bright: Optional[List[_ColorTuple]] = None, + ) -> None: + self.background_color = ColorTriplet(*background) + self.foreground_color = ColorTriplet(*foreground) + self.ansi_colors = Palette(normal + (bright or normal)) + + +DEFAULT_TERMINAL_THEME = TerminalTheme( + (255, 255, 255), + (0, 0, 0), + [ + (0, 0, 0), + (128, 0, 0), + (0, 128, 0), + (128, 128, 0), + (0, 0, 128), + (128, 0, 128), + (0, 128, 128), + (192, 192, 192), + ], + [ + (128, 128, 128), + (255, 0, 0), + (0, 255, 0), + (255, 255, 0), + (0, 0, 255), + (255, 0, 255), + (0, 255, 255), + (255, 255, 255), + ], +) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/text.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/text.py new file mode 100644 index 0000000..ea12c09 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/text.py @@ -0,0 +1,1282 @@ +import re +from functools import partial, reduce +from math import gcd +from operator import itemgetter +from pip._vendor.rich.emoji import EmojiVariant +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Tuple, + Union, +) + +from ._loop import loop_last +from ._pick import pick_bool +from ._wrap import divide_line +from .align import AlignMethod +from .cells import cell_len, set_cell_size +from .containers import Lines +from .control import strip_control_codes +from .emoji import EmojiVariant +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style, StyleType + +if TYPE_CHECKING: # pragma: no cover + from .console import Console, ConsoleOptions, JustifyMethod, OverflowMethod + +DEFAULT_JUSTIFY: "JustifyMethod" = "default" +DEFAULT_OVERFLOW: "OverflowMethod" = "fold" + + +_re_whitespace = re.compile(r"\s+$") + +TextType = Union[str, "Text"] + +GetStyleCallable = Callable[[str], Optional[StyleType]] + + +class Span(NamedTuple): + """A marked up region in some text.""" + + start: int + """Span start index.""" + end: int + """Span end index.""" + style: Union[str, Style] + """Style associated with the span.""" + + def __repr__(self) -> str: + return ( + f"Span({self.start}, {self.end}, {self.style!r})" + if (isinstance(self.style, Style) and self.style._meta) + else f"Span({self.start}, {self.end}, {repr(self.style)})" + ) + + def __bool__(self) -> bool: + return self.end > self.start + + def split(self, offset: int) -> Tuple["Span", Optional["Span"]]: + """Split a span in to 2 from a given offset.""" + + if offset < self.start: + return self, None + if offset >= self.end: + return self, None + + start, end, style = self + span1 = Span(start, min(end, offset), style) + span2 = Span(span1.end, end, style) + return span1, span2 + + def move(self, offset: int) -> "Span": + """Move start and end by a given offset. + + Args: + offset (int): Number of characters to add to start and end. + + Returns: + TextSpan: A new TextSpan with adjusted position. + """ + start, end, style = self + return Span(start + offset, end + offset, style) + + def right_crop(self, offset: int) -> "Span": + """Crop the span at the given offset. + + Args: + offset (int): A value between start and end. + + Returns: + Span: A new (possibly smaller) span. + """ + start, end, style = self + if offset >= end: + return self + return Span(start, min(offset, end), style) + + +class Text(JupyterMixin): + """Text with color / style. + + Args: + text (str, optional): Default unstyled text. Defaults to "". + style (Union[str, Style], optional): Base style for text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8. + spans (List[Span], optional). A list of predefined style spans. Defaults to None. + """ + + __slots__ = [ + "_text", + "style", + "justify", + "overflow", + "no_wrap", + "end", + "tab_size", + "_spans", + "_length", + ] + + def __init__( + self, + text: str = "", + style: Union[str, Style] = "", + *, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = None, + end: str = "\n", + tab_size: Optional[int] = 8, + spans: Optional[List[Span]] = None, + ) -> None: + self._text = [strip_control_codes(text)] + self.style = style + self.justify: Optional["JustifyMethod"] = justify + self.overflow: Optional["OverflowMethod"] = overflow + self.no_wrap = no_wrap + self.end = end + self.tab_size = tab_size + self._spans: List[Span] = spans or [] + self._length: int = len(text) + + def __len__(self) -> int: + return self._length + + def __bool__(self) -> bool: + return bool(self._length) + + def __str__(self) -> str: + return self.plain + + def __repr__(self) -> str: + return f"" + + def __add__(self, other: Any) -> "Text": + if isinstance(other, (str, Text)): + result = self.copy() + result.append(other) + return result + return NotImplemented + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Text): + return NotImplemented + return self.plain == other.plain and self._spans == other._spans + + def __contains__(self, other: object) -> bool: + if isinstance(other, str): + return other in self.plain + elif isinstance(other, Text): + return other.plain in self.plain + return False + + def __getitem__(self, slice: Union[int, slice]) -> "Text": + def get_text_at(offset: int) -> "Text": + _Span = Span + text = Text( + self.plain[offset], + spans=[ + _Span(0, 1, style) + for start, end, style in self._spans + if end > offset >= start + ], + end="", + ) + return text + + if isinstance(slice, int): + return get_text_at(slice) + else: + start, stop, step = slice.indices(len(self.plain)) + if step == 1: + lines = self.divide([start, stop]) + return lines[1] + else: + # This would be a bit of work to implement efficiently + # For now, its not required + raise TypeError("slices with step!=1 are not supported") + + @property + def cell_len(self) -> int: + """Get the number of cells required to render this text.""" + return cell_len(self.plain) + + @property + def markup(self) -> str: + """Get console markup to render this Text. + + Returns: + str: A string potentially creating markup tags. + """ + from .markup import escape + + output: List[str] = [] + + plain = self.plain + markup_spans = [ + (0, False, self.style), + *((span.start, False, span.style) for span in self._spans), + *((span.end, True, span.style) for span in self._spans), + (len(plain), True, self.style), + ] + markup_spans.sort(key=itemgetter(0, 1)) + position = 0 + append = output.append + for offset, closing, style in markup_spans: + if offset > position: + append(escape(plain[position:offset])) + position = offset + if style: + append(f"[/{style}]" if closing else f"[{style}]") + markup = "".join(output) + return markup + + @classmethod + def from_markup( + cls, + text: str, + *, + style: Union[str, Style] = "", + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + ) -> "Text": + """Create Text instance from markup. + + Args: + text (str): A string containing console markup. + emoji (bool, optional): Also render emoji code. Defaults to True. + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + + Returns: + Text: A Text instance with markup rendered. + """ + from .markup import render + + rendered_text = render(text, style, emoji=emoji, emoji_variant=emoji_variant) + rendered_text.justify = justify + rendered_text.overflow = overflow + return rendered_text + + @classmethod + def from_ansi( + cls, + text: str, + *, + style: Union[str, Style] = "", + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = None, + end: str = "\n", + tab_size: Optional[int] = 8, + ) -> "Text": + """Create a Text object from a string containing ANSI escape codes. + + Args: + text (str): A string containing escape codes. + style (Union[str, Style], optional): Base style for text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8. + """ + from .ansi import AnsiDecoder + + joiner = Text( + "\n", + justify=justify, + overflow=overflow, + no_wrap=no_wrap, + end=end, + tab_size=tab_size, + style=style, + ) + decoder = AnsiDecoder() + result = joiner.join(line for line in decoder.decode(text)) + return result + + @classmethod + def styled( + cls, + text: str, + style: StyleType = "", + *, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + ) -> "Text": + """Construct a Text instance with a pre-applied styled. A style applied in this way won't be used + to pad the text when it is justified. + + Args: + text (str): A string containing console markup. + style (Union[str, Style]): Style to apply to the text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + + Returns: + Text: A text instance with a style applied to the entire string. + """ + styled_text = cls(text, justify=justify, overflow=overflow) + styled_text.stylize(style) + return styled_text + + @classmethod + def assemble( + cls, + *parts: Union[str, "Text", Tuple[str, StyleType]], + style: Union[str, Style] = "", + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = None, + end: str = "\n", + tab_size: int = 8, + meta: Optional[Dict[str, Any]] = None, + ) -> "Text": + """Construct a text instance by combining a sequence of strings with optional styles. + The positional arguments should be either strings, or a tuple of string + style. + + Args: + style (Union[str, Style], optional): Base style for text. Defaults to "". + justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. + overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. + end (str, optional): Character to end text with. Defaults to "\\\\n". + tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8. + meta (Dict[str, Any], optional). Meta data to apply to text, or None for no meta data. Default to None + + Returns: + Text: A new text instance. + """ + text = cls( + style=style, + justify=justify, + overflow=overflow, + no_wrap=no_wrap, + end=end, + tab_size=tab_size, + ) + append = text.append + _Text = Text + for part in parts: + if isinstance(part, (_Text, str)): + append(part) + else: + append(*part) + if meta: + text.apply_meta(meta) + return text + + @property + def plain(self) -> str: + """Get the text as a single string.""" + if len(self._text) != 1: + self._text[:] = ["".join(self._text)] + return self._text[0] + + @plain.setter + def plain(self, new_text: str) -> None: + """Set the text to a new value.""" + if new_text != self.plain: + self._text[:] = [new_text] + old_length = self._length + self._length = len(new_text) + if old_length > self._length: + self._trim_spans() + + @property + def spans(self) -> List[Span]: + """Get a reference to the internal list of spans.""" + return self._spans + + @spans.setter + def spans(self, spans: List[Span]) -> None: + """Set spans.""" + self._spans = spans[:] + + def blank_copy(self, plain: str = "") -> "Text": + """Return a new Text instance with copied meta data (but not the string or spans).""" + copy_self = Text( + plain, + style=self.style, + justify=self.justify, + overflow=self.overflow, + no_wrap=self.no_wrap, + end=self.end, + tab_size=self.tab_size, + ) + return copy_self + + def copy(self) -> "Text": + """Return a copy of this instance.""" + copy_self = Text( + self.plain, + style=self.style, + justify=self.justify, + overflow=self.overflow, + no_wrap=self.no_wrap, + end=self.end, + tab_size=self.tab_size, + ) + copy_self._spans[:] = self._spans + return copy_self + + def stylize( + self, + style: Union[str, Style], + start: int = 0, + end: Optional[int] = None, + ) -> None: + """Apply a style to the text, or a portion of the text. + + Args: + style (Union[str, Style]): Style instance or style definition to apply. + start (int): Start offset (negative indexing is supported). Defaults to 0. + end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. + + """ + if style: + length = len(self) + if start < 0: + start = length + start + if end is None: + end = length + if end < 0: + end = length + end + if start >= length or end <= start: + # Span not in text or not valid + return + self._spans.append(Span(start, min(length, end), style)) + + def apply_meta( + self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None + ) -> None: + """Apply meta data to the text, or a portion of the text. + + Args: + meta (Dict[str, Any]): A dict of meta information. + start (int): Start offset (negative indexing is supported). Defaults to 0. + end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. + + """ + style = Style.from_meta(meta) + self.stylize(style, start=start, end=end) + + def on(self, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Text": + """Apply event handlers (used by Textual project). + + Example: + >>> from rich.text import Text + >>> text = Text("hello world") + >>> text.on(click="view.toggle('world')") + + Args: + meta (Dict[str, Any]): Mapping of meta information. + **handlers: Keyword args are prefixed with "@" to defined handlers. + + Returns: + Text: Self is returned to method may be chained. + """ + meta = {} if meta is None else meta + meta.update({f"@{key}": value for key, value in handlers.items()}) + self.stylize(Style.from_meta(meta)) + return self + + def remove_suffix(self, suffix: str) -> None: + """Remove a suffix if it exists. + + Args: + suffix (str): Suffix to remove. + """ + if self.plain.endswith(suffix): + self.right_crop(len(suffix)) + + def get_style_at_offset(self, console: "Console", offset: int) -> Style: + """Get the style of a character at give offset. + + Args: + console (~Console): Console where text will be rendered. + offset (int): Offset in to text (negative indexing supported) + + Returns: + Style: A Style instance. + """ + # TODO: This is a little inefficient, it is only used by full justify + if offset < 0: + offset = len(self) + offset + get_style = console.get_style + style = get_style(self.style).copy() + for start, end, span_style in self._spans: + if end > offset >= start: + style += get_style(span_style, default="") + return style + + def highlight_regex( + self, + re_highlight: str, + style: Optional[Union[GetStyleCallable, StyleType]] = None, + *, + style_prefix: str = "", + ) -> int: + """Highlight text with a regular expression, where group names are + translated to styles. + + Args: + re_highlight (str): A regular expression. + style (Union[GetStyleCallable, StyleType]): Optional style to apply to whole match, or a callable + which accepts the matched text and returns a style. Defaults to None. + style_prefix (str, optional): Optional prefix to add to style group names. + + Returns: + int: Number of regex matches + """ + count = 0 + append_span = self._spans.append + _Span = Span + plain = self.plain + for match in re.finditer(re_highlight, plain): + get_span = match.span + if style: + start, end = get_span() + match_style = style(plain[start:end]) if callable(style) else style + if match_style is not None and end > start: + append_span(_Span(start, end, match_style)) + + count += 1 + for name in match.groupdict().keys(): + start, end = get_span(name) + if start != -1 and end > start: + append_span(_Span(start, end, f"{style_prefix}{name}")) + return count + + def highlight_words( + self, + words: Iterable[str], + style: Union[str, Style], + *, + case_sensitive: bool = True, + ) -> int: + """Highlight words with a style. + + Args: + words (Iterable[str]): Worlds to highlight. + style (Union[str, Style]): Style to apply. + case_sensitive (bool, optional): Enable case sensitive matchings. Defaults to True. + + Returns: + int: Number of words highlighted. + """ + re_words = "|".join(re.escape(word) for word in words) + add_span = self._spans.append + count = 0 + _Span = Span + for match in re.finditer( + re_words, self.plain, flags=0 if case_sensitive else re.IGNORECASE + ): + start, end = match.span(0) + add_span(_Span(start, end, style)) + count += 1 + return count + + def rstrip(self) -> None: + """Strip whitespace from end of text.""" + self.plain = self.plain.rstrip() + + def rstrip_end(self, size: int) -> None: + """Remove whitespace beyond a certain width at the end of the text. + + Args: + size (int): The desired size of the text. + """ + text_length = len(self) + if text_length > size: + excess = text_length - size + whitespace_match = _re_whitespace.search(self.plain) + if whitespace_match is not None: + whitespace_count = len(whitespace_match.group(0)) + self.right_crop(min(whitespace_count, excess)) + + def set_length(self, new_length: int) -> None: + """Set new length of the text, clipping or padding is required.""" + length = len(self) + if length != new_length: + if length < new_length: + self.pad_right(new_length - length) + else: + self.right_crop(length - new_length) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> Iterable[Segment]: + tab_size: int = console.tab_size or self.tab_size or 8 + justify = self.justify or options.justify or DEFAULT_JUSTIFY + + overflow = self.overflow or options.overflow or DEFAULT_OVERFLOW + + lines = self.wrap( + console, + options.max_width, + justify=justify, + overflow=overflow, + tab_size=tab_size or 8, + no_wrap=pick_bool(self.no_wrap, options.no_wrap, False), + ) + all_lines = Text("\n").join(lines) + yield from all_lines.render(console, end=self.end) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + text = self.plain + lines = text.splitlines() + max_text_width = max(cell_len(line) for line in lines) if lines else 0 + words = text.split() + min_text_width = ( + max(cell_len(word) for word in words) if words else max_text_width + ) + return Measurement(min_text_width, max_text_width) + + def render(self, console: "Console", end: str = "") -> Iterable["Segment"]: + """Render the text as Segments. + + Args: + console (Console): Console instance. + end (Optional[str], optional): Optional end character. + + Returns: + Iterable[Segment]: Result of render that may be written to the console. + """ + _Segment = Segment + text = self.plain + if not self._spans: + yield Segment(text) + if end: + yield _Segment(end) + return + get_style = partial(console.get_style, default=Style.null()) + + enumerated_spans = list(enumerate(self._spans, 1)) + style_map = {index: get_style(span.style) for index, span in enumerated_spans} + style_map[0] = get_style(self.style) + + spans = [ + (0, False, 0), + *((span.start, False, index) for index, span in enumerated_spans), + *((span.end, True, index) for index, span in enumerated_spans), + (len(text), True, 0), + ] + spans.sort(key=itemgetter(0, 1)) + + stack: List[int] = [] + stack_append = stack.append + stack_pop = stack.remove + + style_cache: Dict[Tuple[Style, ...], Style] = {} + style_cache_get = style_cache.get + combine = Style.combine + + def get_current_style() -> Style: + """Construct current style from stack.""" + styles = tuple(style_map[_style_id] for _style_id in sorted(stack)) + cached_style = style_cache_get(styles) + if cached_style is not None: + return cached_style + current_style = combine(styles) + style_cache[styles] = current_style + return current_style + + for (offset, leaving, style_id), (next_offset, _, _) in zip(spans, spans[1:]): + if leaving: + stack_pop(style_id) + else: + stack_append(style_id) + if next_offset > offset: + yield _Segment(text[offset:next_offset], get_current_style()) + if end: + yield _Segment(end) + + def join(self, lines: Iterable["Text"]) -> "Text": + """Join text together with this instance as the separator. + + Args: + lines (Iterable[Text]): An iterable of Text instances to join. + + Returns: + Text: A new text instance containing join text. + """ + + new_text = self.blank_copy() + + def iter_text() -> Iterable["Text"]: + if self.plain: + for last, line in loop_last(lines): + yield line + if not last: + yield self + else: + yield from lines + + extend_text = new_text._text.extend + append_span = new_text._spans.append + extend_spans = new_text._spans.extend + offset = 0 + _Span = Span + + for text in iter_text(): + extend_text(text._text) + if text.style: + append_span(_Span(offset, offset + len(text), text.style)) + extend_spans( + _Span(offset + start, offset + end, style) + for start, end, style in text._spans + ) + offset += len(text) + new_text._length = offset + return new_text + + def expand_tabs(self, tab_size: Optional[int] = None) -> None: + """Converts tabs to spaces. + + Args: + tab_size (int, optional): Size of tabs. Defaults to 8. + + """ + if "\t" not in self.plain: + return + pos = 0 + if tab_size is None: + tab_size = self.tab_size + assert tab_size is not None + result = self.blank_copy() + append = result.append + + _style = self.style + for line in self.split("\n", include_separator=True): + parts = line.split("\t", include_separator=True) + for part in parts: + if part.plain.endswith("\t"): + part._text = [part.plain[:-1] + " "] + append(part) + pos += len(part) + spaces = tab_size - ((pos - 1) % tab_size) - 1 + if spaces: + append(" " * spaces, _style) + pos += spaces + else: + append(part) + self._text = [result.plain] + self._length = len(self.plain) + self._spans[:] = result._spans + + def truncate( + self, + max_width: int, + *, + overflow: Optional["OverflowMethod"] = None, + pad: bool = False, + ) -> None: + """Truncate text if it is longer that a given width. + + Args: + max_width (int): Maximum number of characters in text. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None, to use self.overflow. + pad (bool, optional): Pad with spaces if the length is less than max_width. Defaults to False. + """ + _overflow = overflow or self.overflow or DEFAULT_OVERFLOW + if _overflow != "ignore": + length = cell_len(self.plain) + if length > max_width: + if _overflow == "ellipsis": + self.plain = set_cell_size(self.plain, max_width - 1) + "…" + else: + self.plain = set_cell_size(self.plain, max_width) + if pad and length < max_width: + spaces = max_width - length + self._text = [f"{self.plain}{' ' * spaces}"] + self._length = len(self.plain) + + def _trim_spans(self) -> None: + """Remove or modify any spans that are over the end of the text.""" + max_offset = len(self.plain) + _Span = Span + self._spans[:] = [ + ( + span + if span.end < max_offset + else _Span(span.start, min(max_offset, span.end), span.style) + ) + for span in self._spans + if span.start < max_offset + ] + + def pad(self, count: int, character: str = " ") -> None: + """Pad left and right with a given number of characters. + + Args: + count (int): Width of padding. + """ + assert len(character) == 1, "Character must be a string of length 1" + if count: + pad_characters = character * count + self.plain = f"{pad_characters}{self.plain}{pad_characters}" + _Span = Span + self._spans[:] = [ + _Span(start + count, end + count, style) + for start, end, style in self._spans + ] + + def pad_left(self, count: int, character: str = " ") -> None: + """Pad the left with a given character. + + Args: + count (int): Number of characters to pad. + character (str, optional): Character to pad with. Defaults to " ". + """ + assert len(character) == 1, "Character must be a string of length 1" + if count: + self.plain = f"{character * count}{self.plain}" + _Span = Span + self._spans[:] = [ + _Span(start + count, end + count, style) + for start, end, style in self._spans + ] + + def pad_right(self, count: int, character: str = " ") -> None: + """Pad the right with a given character. + + Args: + count (int): Number of characters to pad. + character (str, optional): Character to pad with. Defaults to " ". + """ + assert len(character) == 1, "Character must be a string of length 1" + if count: + self.plain = f"{self.plain}{character * count}" + + def align(self, align: AlignMethod, width: int, character: str = " ") -> None: + """Align text to a given width. + + Args: + align (AlignMethod): One of "left", "center", or "right". + width (int): Desired width. + character (str, optional): Character to pad with. Defaults to " ". + """ + self.truncate(width) + excess_space = width - cell_len(self.plain) + if excess_space: + if align == "left": + self.pad_right(excess_space, character) + elif align == "center": + left = excess_space // 2 + self.pad_left(left, character) + self.pad_right(excess_space - left, character) + else: + self.pad_left(excess_space, character) + + def append( + self, text: Union["Text", str], style: Optional[Union[str, "Style"]] = None + ) -> "Text": + """Add text with an optional style. + + Args: + text (Union[Text, str]): A str or Text to append. + style (str, optional): A style name. Defaults to None. + + Returns: + Text: Returns self for chaining. + """ + + if not isinstance(text, (str, Text)): + raise TypeError("Only str or Text can be appended to Text") + + if len(text): + if isinstance(text, str): + text = strip_control_codes(text) + self._text.append(text) + offset = len(self) + text_length = len(text) + if style is not None: + self._spans.append(Span(offset, offset + text_length, style)) + self._length += text_length + elif isinstance(text, Text): + _Span = Span + if style is not None: + raise ValueError( + "style must not be set when appending Text instance" + ) + text_length = self._length + if text.style is not None: + self._spans.append( + _Span(text_length, text_length + len(text), text.style) + ) + self._text.append(text.plain) + self._spans.extend( + _Span(start + text_length, end + text_length, style) + for start, end, style in text._spans + ) + self._length += len(text) + return self + + def append_text(self, text: "Text") -> "Text": + """Append another Text instance. This method is more performant that Text.append, but + only works for Text. + + Returns: + Text: Returns self for chaining. + """ + _Span = Span + text_length = self._length + if text.style is not None: + self._spans.append(_Span(text_length, text_length + len(text), text.style)) + self._text.append(text.plain) + self._spans.extend( + _Span(start + text_length, end + text_length, style) + for start, end, style in text._spans + ) + self._length += len(text) + return self + + def append_tokens( + self, tokens: Iterable[Tuple[str, Optional[StyleType]]] + ) -> "Text": + """Append iterable of str and style. Style may be a Style instance or a str style definition. + + Args: + pairs (Iterable[Tuple[str, Optional[StyleType]]]): An iterable of tuples containing str content and style. + + Returns: + Text: Returns self for chaining. + """ + append_text = self._text.append + append_span = self._spans.append + _Span = Span + offset = len(self) + for content, style in tokens: + append_text(content) + if style is not None: + append_span(_Span(offset, offset + len(content), style)) + offset += len(content) + self._length = offset + return self + + def copy_styles(self, text: "Text") -> None: + """Copy styles from another Text instance. + + Args: + text (Text): A Text instance to copy styles from, must be the same length. + """ + self._spans.extend(text._spans) + + def split( + self, + separator: str = "\n", + *, + include_separator: bool = False, + allow_blank: bool = False, + ) -> Lines: + """Split rich text in to lines, preserving styles. + + Args: + separator (str, optional): String to split on. Defaults to "\\\\n". + include_separator (bool, optional): Include the separator in the lines. Defaults to False. + allow_blank (bool, optional): Return a blank line if the text ends with a separator. Defaults to False. + + Returns: + List[RichText]: A list of rich text, one per line of the original. + """ + assert separator, "separator must not be empty" + + text = self.plain + if separator not in text: + return Lines([self.copy()]) + + if include_separator: + lines = self.divide( + match.end() for match in re.finditer(re.escape(separator), text) + ) + else: + + def flatten_spans() -> Iterable[int]: + for match in re.finditer(re.escape(separator), text): + start, end = match.span() + yield start + yield end + + lines = Lines( + line for line in self.divide(flatten_spans()) if line.plain != separator + ) + + if not allow_blank and text.endswith(separator): + lines.pop() + + return lines + + def divide(self, offsets: Iterable[int]) -> Lines: + """Divide text in to a number of lines at given offsets. + + Args: + offsets (Iterable[int]): Offsets used to divide text. + + Returns: + Lines: New RichText instances between offsets. + """ + _offsets = list(offsets) + + if not _offsets: + return Lines([self.copy()]) + + text = self.plain + text_length = len(text) + divide_offsets = [0, *_offsets, text_length] + line_ranges = list(zip(divide_offsets, divide_offsets[1:])) + + style = self.style + justify = self.justify + overflow = self.overflow + _Text = Text + new_lines = Lines( + _Text( + text[start:end], + style=style, + justify=justify, + overflow=overflow, + ) + for start, end in line_ranges + ) + if not self._spans: + return new_lines + + _line_appends = [line._spans.append for line in new_lines._lines] + line_count = len(line_ranges) + _Span = Span + + for span_start, span_end, style in self._spans: + + lower_bound = 0 + upper_bound = line_count + start_line_no = (lower_bound + upper_bound) // 2 + + while True: + line_start, line_end = line_ranges[start_line_no] + if span_start < line_start: + upper_bound = start_line_no - 1 + elif span_start > line_end: + lower_bound = start_line_no + 1 + else: + break + start_line_no = (lower_bound + upper_bound) // 2 + + if span_end < line_end: + end_line_no = start_line_no + else: + end_line_no = lower_bound = start_line_no + upper_bound = line_count + + while True: + line_start, line_end = line_ranges[end_line_no] + if span_end < line_start: + upper_bound = end_line_no - 1 + elif span_end > line_end: + lower_bound = end_line_no + 1 + else: + break + end_line_no = (lower_bound + upper_bound) // 2 + + for line_no in range(start_line_no, end_line_no + 1): + line_start, line_end = line_ranges[line_no] + new_start = max(0, span_start - line_start) + new_end = min(span_end - line_start, line_end - line_start) + if new_end > new_start: + _line_appends[line_no](_Span(new_start, new_end, style)) + + return new_lines + + def right_crop(self, amount: int = 1) -> None: + """Remove a number of characters from the end of the text.""" + max_offset = len(self.plain) - amount + _Span = Span + self._spans[:] = [ + ( + span + if span.end < max_offset + else _Span(span.start, min(max_offset, span.end), span.style) + ) + for span in self._spans + if span.start < max_offset + ] + self._text = [self.plain[:-amount]] + self._length -= amount + + def wrap( + self, + console: "Console", + width: int, + *, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + tab_size: int = 8, + no_wrap: Optional[bool] = None, + ) -> Lines: + """Word wrap the text. + + Args: + console (Console): Console instance. + width (int): Number of characters per line. + emoji (bool, optional): Also render emoji code. Defaults to True. + justify (str, optional): Justify method: "default", "left", "center", "full", "right". Defaults to "default". + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None. + tab_size (int, optional): Default tab size. Defaults to 8. + no_wrap (bool, optional): Disable wrapping, Defaults to False. + + Returns: + Lines: Number of lines. + """ + wrap_justify = justify or self.justify or DEFAULT_JUSTIFY + wrap_overflow = overflow or self.overflow or DEFAULT_OVERFLOW + + no_wrap = pick_bool(no_wrap, self.no_wrap, False) or overflow == "ignore" + + lines = Lines() + for line in self.split(allow_blank=True): + if "\t" in line: + line.expand_tabs(tab_size) + if no_wrap: + new_lines = Lines([line]) + else: + offsets = divide_line(str(line), width, fold=wrap_overflow == "fold") + new_lines = line.divide(offsets) + for line in new_lines: + line.rstrip_end(width) + if wrap_justify: + new_lines.justify( + console, width, justify=wrap_justify, overflow=wrap_overflow + ) + for line in new_lines: + line.truncate(width, overflow=wrap_overflow) + lines.extend(new_lines) + return lines + + def fit(self, width: int) -> Lines: + """Fit the text in to given width by chopping in to lines. + + Args: + width (int): Maximum characters in a line. + + Returns: + Lines: List of lines. + """ + lines: Lines = Lines() + append = lines.append + for line in self.split(): + line.set_length(width) + append(line) + return lines + + def detect_indentation(self) -> int: + """Auto-detect indentation of code. + + Returns: + int: Number of spaces used to indent code. + """ + + _indentations = { + len(match.group(1)) + for match in re.finditer(r"^( *)(.*)$", self.plain, flags=re.MULTILINE) + } + + try: + indentation = ( + reduce(gcd, [indent for indent in _indentations if not indent % 2]) or 1 + ) + except TypeError: + indentation = 1 + + return indentation + + def with_indent_guides( + self, + indent_size: Optional[int] = None, + *, + character: str = "│", + style: StyleType = "dim green", + ) -> "Text": + """Adds indent guide lines to text. + + Args: + indent_size (Optional[int]): Size of indentation, or None to auto detect. Defaults to None. + character (str, optional): Character to use for indentation. Defaults to "│". + style (Union[Style, str], optional): Style of indent guides. + + Returns: + Text: New text with indentation guides. + """ + + _indent_size = self.detect_indentation() if indent_size is None else indent_size + + text = self.copy() + text.expand_tabs() + indent_line = f"{character}{' ' * (_indent_size - 1)}" + + re_indent = re.compile(r"^( *)(.*)$") + new_lines: List[Text] = [] + add_line = new_lines.append + blank_lines = 0 + for line in text.split(allow_blank=True): + match = re_indent.match(line.plain) + if not match or not match.group(2): + blank_lines += 1 + continue + indent = match.group(1) + full_indents, remaining_space = divmod(len(indent), _indent_size) + new_indent = f"{indent_line * full_indents}{' ' * remaining_space}" + line.plain = new_indent + line.plain[len(new_indent) :] + line.stylize(style, 0, len(new_indent)) + if blank_lines: + new_lines.extend([Text(new_indent, style=style)] * blank_lines) + blank_lines = 0 + add_line(line) + if blank_lines: + new_lines.extend([Text("", style=style)] * blank_lines) + + new_text = text.blank_copy("\n").join(new_lines) + return new_text + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + + text = Text( + """\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n""" + ) + text.highlight_words(["Lorem"], "bold") + text.highlight_words(["ipsum"], "italic") + + console = Console() + + console.rule("justify='left'") + console.print(text, style="red") + console.print() + + console.rule("justify='center'") + console.print(text, style="green", justify="center") + console.print() + + console.rule("justify='right'") + console.print(text, style="blue", justify="right") + console.print() + + console.rule("justify='full'") + console.print(text, style="magenta", justify="full") + console.print() diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/theme.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/theme.py new file mode 100644 index 0000000..bfb3c7f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/theme.py @@ -0,0 +1,112 @@ +import configparser +from typing import Dict, List, IO, Mapping, Optional + +from .default_styles import DEFAULT_STYLES +from .style import Style, StyleType + + +class Theme: + """A container for style information, used by :class:`~rich.console.Console`. + + Args: + styles (Dict[str, Style], optional): A mapping of style names on to styles. Defaults to None for a theme with no styles. + inherit (bool, optional): Inherit default styles. Defaults to True. + """ + + styles: Dict[str, Style] + + def __init__( + self, styles: Optional[Mapping[str, StyleType]] = None, inherit: bool = True + ): + self.styles = DEFAULT_STYLES.copy() if inherit else {} + if styles is not None: + self.styles.update( + { + name: style if isinstance(style, Style) else Style.parse(style) + for name, style in styles.items() + } + ) + + @property + def config(self) -> str: + """Get contents of a config file for this theme.""" + config = "[styles]\n" + "\n".join( + f"{name} = {style}" for name, style in sorted(self.styles.items()) + ) + return config + + @classmethod + def from_file( + cls, config_file: IO[str], source: Optional[str] = None, inherit: bool = True + ) -> "Theme": + """Load a theme from a text mode file. + + Args: + config_file (IO[str]): An open conf file. + source (str, optional): The filename of the open file. Defaults to None. + inherit (bool, optional): Inherit default styles. Defaults to True. + + Returns: + Theme: A New theme instance. + """ + config = configparser.ConfigParser() + config.read_file(config_file, source=source) + styles = {name: Style.parse(value) for name, value in config.items("styles")} + theme = Theme(styles, inherit=inherit) + return theme + + @classmethod + def read(cls, path: str, inherit: bool = True) -> "Theme": + """Read a theme from a path. + + Args: + path (str): Path to a config file readable by Python configparser module. + inherit (bool, optional): Inherit default styles. Defaults to True. + + Returns: + Theme: A new theme instance. + """ + with open(path, "rt") as config_file: + return cls.from_file(config_file, source=path, inherit=inherit) + + +class ThemeStackError(Exception): + """Base exception for errors related to the theme stack.""" + + +class ThemeStack: + """A stack of themes. + + Args: + theme (Theme): A theme instance + """ + + def __init__(self, theme: Theme) -> None: + self._entries: List[Dict[str, Style]] = [theme.styles] + self.get = self._entries[-1].get + + def push_theme(self, theme: Theme, inherit: bool = True) -> None: + """Push a theme on the top of the stack. + + Args: + theme (Theme): A Theme instance. + inherit (boolean, optional): Inherit styles from current top of stack. + """ + styles: Dict[str, Style] + styles = ( + {**self._entries[-1], **theme.styles} if inherit else theme.styles.copy() + ) + self._entries.append(styles) + self.get = self._entries[-1].get + + def pop_theme(self) -> None: + """Pop (and discard) the top-most theme.""" + if len(self._entries) == 1: + raise ThemeStackError("Unable to pop base theme") + self._entries.pop() + self.get = self._entries[-1].get + + +if __name__ == "__main__": # pragma: no cover + theme = Theme() + print(theme.config) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/themes.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/themes.py new file mode 100644 index 0000000..bf6db10 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/themes.py @@ -0,0 +1,5 @@ +from .default_styles import DEFAULT_STYLES +from .theme import Theme + + +DEFAULT = Theme(DEFAULT_STYLES) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/traceback.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/traceback.py new file mode 100644 index 0000000..66a39eb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/traceback.py @@ -0,0 +1,678 @@ +from __future__ import absolute_import + +import os +import platform +import sys +from dataclasses import dataclass, field +from traceback import walk_tb +from types import ModuleType, TracebackType +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, Union + +from pip._vendor.pygments.lexers import guess_lexer_for_filename +from pip._vendor.pygments.token import Comment, Keyword, Name, Number, Operator, String +from pip._vendor.pygments.token import Text as TextToken +from pip._vendor.pygments.token import Token + +from . import pretty +from ._loop import loop_first, loop_last +from .columns import Columns +from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group +from .constrain import Constrain +from .highlighter import RegexHighlighter, ReprHighlighter +from .panel import Panel +from .scope import render_scope +from .style import Style +from .syntax import Syntax +from .text import Text +from .theme import Theme + +WINDOWS = platform.system() == "Windows" + +LOCALS_MAX_LENGTH = 10 +LOCALS_MAX_STRING = 80 + + +def install( + *, + console: Optional[Console] = None, + width: Optional[int] = 100, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + indent_guides: bool = True, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, +) -> Callable[[Type[BaseException], BaseException, Optional[TracebackType]], Any]: + """Install a rich traceback handler. + + Once installed, any tracebacks will be printed with syntax highlighting and rich formatting. + + + Args: + console (Optional[Console], optional): Console to write exception to. Default uses internal Console instance. + width (Optional[int], optional): Width (in characters) of traceback. Defaults to 100. + extra_lines (int, optional): Extra lines of code. Defaults to 3. + theme (Optional[str], optional): Pygments theme to use in traceback. Defaults to ``None`` which will pick + a theme appropriate for the platform. + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. + suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + + Returns: + Callable: The previous exception handler that was replaced. + + """ + traceback_console = Console(file=sys.stderr) if console is None else console + + def excepthook( + type_: Type[BaseException], + value: BaseException, + traceback: Optional[TracebackType], + ) -> None: + traceback_console.print( + Traceback.from_exception( + type_, + value, + traceback, + width=width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + indent_guides=indent_guides, + suppress=suppress, + max_frames=max_frames, + ) + ) + + def ipy_excepthook_closure(ip: Any) -> None: # pragma: no cover + tb_data = {} # store information about showtraceback call + default_showtraceback = ip.showtraceback # keep reference of default traceback + + def ipy_show_traceback(*args: Any, **kwargs: Any) -> None: + """wrap the default ip.showtraceback to store info for ip._showtraceback""" + nonlocal tb_data + tb_data = kwargs + default_showtraceback(*args, **kwargs) + + def ipy_display_traceback( + *args: Any, is_syntax: bool = False, **kwargs: Any + ) -> None: + """Internally called traceback from ip._showtraceback""" + nonlocal tb_data + exc_tuple = ip._get_exc_info() + + # do not display trace on syntax error + tb: Optional[TracebackType] = None if is_syntax else exc_tuple[2] + + # determine correct tb_offset + compiled = tb_data.get("running_compiled_code", False) + tb_offset = tb_data.get("tb_offset", 1 if compiled else 0) + # remove ipython internal frames from trace with tb_offset + for _ in range(tb_offset): + if tb is None: + break + tb = tb.tb_next + + excepthook(exc_tuple[0], exc_tuple[1], tb) + tb_data = {} # clear data upon usage + + # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work + # this is also what the ipython docs recommends to modify when subclassing InteractiveShell + ip._showtraceback = ipy_display_traceback + # add wrapper to capture tb_data + ip.showtraceback = ipy_show_traceback + ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback( + *args, is_syntax=True, **kwargs + ) + + try: # pragma: no cover + # if within ipython, use customized traceback + ip = get_ipython() # type: ignore + ipy_excepthook_closure(ip) + return sys.excepthook + except Exception: + # otherwise use default system hook + old_excepthook = sys.excepthook + sys.excepthook = excepthook + return old_excepthook + + +@dataclass +class Frame: + filename: str + lineno: int + name: str + line: str = "" + locals: Optional[Dict[str, pretty.Node]] = None + + +@dataclass +class _SyntaxError: + offset: int + filename: str + line: str + lineno: int + msg: str + + +@dataclass +class Stack: + exc_type: str + exc_value: str + syntax_error: Optional[_SyntaxError] = None + is_cause: bool = False + frames: List[Frame] = field(default_factory=list) + + +@dataclass +class Trace: + stacks: List[Stack] + + +class PathHighlighter(RegexHighlighter): + highlights = [r"(?P.*/)(?P.+)"] + + +class Traceback: + """A Console renderable that renders a traceback. + + Args: + trace (Trace, optional): A `Trace` object produced from `extract`. Defaults to None, which uses + the last exception. + width (Optional[int], optional): Number of characters used to traceback. Defaults to 100. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback. + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + + """ + + LEXERS = { + "": "text", + ".py": "python", + ".pxd": "cython", + ".pyx": "cython", + ".pxi": "pyrex", + } + + def __init__( + self, + trace: Optional[Trace] = None, + width: Optional[int] = 100, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + indent_guides: bool = True, + locals_max_length: int = LOCALS_MAX_LENGTH, + locals_max_string: int = LOCALS_MAX_STRING, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ): + if trace is None: + exc_type, exc_value, traceback = sys.exc_info() + if exc_type is None or exc_value is None or traceback is None: + raise ValueError( + "Value for 'trace' required if not called in except: block" + ) + trace = self.extract( + exc_type, exc_value, traceback, show_locals=show_locals + ) + self.trace = trace + self.width = width + self.extra_lines = extra_lines + self.theme = Syntax.get_theme(theme or "ansi_dark") + self.word_wrap = word_wrap + self.show_locals = show_locals + self.indent_guides = indent_guides + self.locals_max_length = locals_max_length + self.locals_max_string = locals_max_string + + self.suppress: Sequence[str] = [] + for suppress_entity in suppress: + if not isinstance(suppress_entity, str): + assert ( + suppress_entity.__file__ is not None + ), f"{suppress_entity!r} must be a module with '__file__' attribute" + path = os.path.dirname(suppress_entity.__file__) + else: + path = suppress_entity + path = os.path.normpath(os.path.abspath(path)) + self.suppress.append(path) + self.max_frames = max(4, max_frames) if max_frames > 0 else 0 + + @classmethod + def from_exception( + cls, + exc_type: Type[Any], + exc_value: BaseException, + traceback: Optional[TracebackType], + width: Optional[int] = 100, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + indent_guides: bool = True, + locals_max_length: int = LOCALS_MAX_LENGTH, + locals_max_string: int = LOCALS_MAX_STRING, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ) -> "Traceback": + """Create a traceback from exception info + + Args: + exc_type (Type[BaseException]): Exception type. + exc_value (BaseException): Exception value. + traceback (TracebackType): Python Traceback object. + width (Optional[int], optional): Number of characters used to traceback. Defaults to 100. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback. + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + + Returns: + Traceback: A Traceback instance that may be printed. + """ + rich_traceback = cls.extract( + exc_type, exc_value, traceback, show_locals=show_locals + ) + return cls( + rich_traceback, + width=width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + indent_guides=indent_guides, + locals_max_length=locals_max_length, + locals_max_string=locals_max_string, + suppress=suppress, + max_frames=max_frames, + ) + + @classmethod + def extract( + cls, + exc_type: Type[BaseException], + exc_value: BaseException, + traceback: Optional[TracebackType], + show_locals: bool = False, + locals_max_length: int = LOCALS_MAX_LENGTH, + locals_max_string: int = LOCALS_MAX_STRING, + ) -> Trace: + """Extract traceback information. + + Args: + exc_type (Type[BaseException]): Exception type. + exc_value (BaseException): Exception value. + traceback (TracebackType): Python Traceback object. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to 10. + locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. + + Returns: + Trace: A Trace instance which you can use to construct a `Traceback`. + """ + + stacks: List[Stack] = [] + is_cause = False + + from pip._vendor.rich import _IMPORT_CWD + + def safe_str(_object: Any) -> str: + """Don't allow exceptions from __str__ to propegate.""" + try: + return str(_object) + except Exception: + return "" + + while True: + stack = Stack( + exc_type=safe_str(exc_type.__name__), + exc_value=safe_str(exc_value), + is_cause=is_cause, + ) + + if isinstance(exc_value, SyntaxError): + stack.syntax_error = _SyntaxError( + offset=exc_value.offset or 0, + filename=exc_value.filename or "?", + lineno=exc_value.lineno or 0, + line=exc_value.text or "", + msg=exc_value.msg, + ) + + stacks.append(stack) + append = stack.frames.append + + for frame_summary, line_no in walk_tb(traceback): + filename = frame_summary.f_code.co_filename + if filename and not filename.startswith("<"): + if not os.path.isabs(filename): + filename = os.path.join(_IMPORT_CWD, filename) + frame = Frame( + filename=filename or "?", + lineno=line_no, + name=frame_summary.f_code.co_name, + locals={ + key: pretty.traverse( + value, + max_length=locals_max_length, + max_string=locals_max_string, + ) + for key, value in frame_summary.f_locals.items() + } + if show_locals + else None, + ) + append(frame) + if "_rich_traceback_guard" in frame_summary.f_locals: + del stack.frames[:] + + cause = getattr(exc_value, "__cause__", None) + if cause and cause.__traceback__: + exc_type = cause.__class__ + exc_value = cause + traceback = cause.__traceback__ + if traceback: + is_cause = True + continue + + cause = exc_value.__context__ + if ( + cause + and cause.__traceback__ + and not getattr(exc_value, "__suppress_context__", False) + ): + exc_type = cause.__class__ + exc_value = cause + traceback = cause.__traceback__ + if traceback: + is_cause = False + continue + # No cover, code is reached but coverage doesn't recognize it. + break # pragma: no cover + + trace = Trace(stacks=stacks) + return trace + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + theme = self.theme + background_style = theme.get_background_style() + token_style = theme.get_style_for_token + + traceback_theme = Theme( + { + "pretty": token_style(TextToken), + "pygments.text": token_style(Token), + "pygments.string": token_style(String), + "pygments.function": token_style(Name.Function), + "pygments.number": token_style(Number), + "repr.indent": token_style(Comment) + Style(dim=True), + "repr.str": token_style(String), + "repr.brace": token_style(TextToken) + Style(bold=True), + "repr.number": token_style(Number), + "repr.bool_true": token_style(Keyword.Constant), + "repr.bool_false": token_style(Keyword.Constant), + "repr.none": token_style(Keyword.Constant), + "scope.border": token_style(String.Delimiter), + "scope.equals": token_style(Operator), + "scope.key": token_style(Name), + "scope.key.special": token_style(Name.Constant) + Style(dim=True), + }, + inherit=False, + ) + + highlighter = ReprHighlighter() + for last, stack in loop_last(reversed(self.trace.stacks)): + if stack.frames: + stack_renderable: ConsoleRenderable = Panel( + self._render_stack(stack), + title="[traceback.title]Traceback [dim](most recent call last)", + style=background_style, + border_style="traceback.border", + expand=True, + padding=(0, 1), + ) + stack_renderable = Constrain(stack_renderable, self.width) + with console.use_theme(traceback_theme): + yield stack_renderable + if stack.syntax_error is not None: + with console.use_theme(traceback_theme): + yield Constrain( + Panel( + self._render_syntax_error(stack.syntax_error), + style=background_style, + border_style="traceback.border.syntax_error", + expand=True, + padding=(0, 1), + width=self.width, + ), + self.width, + ) + yield Text.assemble( + (f"{stack.exc_type}: ", "traceback.exc_type"), + highlighter(stack.syntax_error.msg), + ) + elif stack.exc_value: + yield Text.assemble( + (f"{stack.exc_type}: ", "traceback.exc_type"), + highlighter(stack.exc_value), + ) + else: + yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type")) + + if not last: + if stack.is_cause: + yield Text.from_markup( + "\n[i]The above exception was the direct cause of the following exception:\n", + ) + else: + yield Text.from_markup( + "\n[i]During handling of the above exception, another exception occurred:\n", + ) + + @group() + def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult: + highlighter = ReprHighlighter() + path_highlighter = PathHighlighter() + if syntax_error.filename != "": + text = Text.assemble( + (f" {syntax_error.filename}", "pygments.string"), + (":", "pygments.text"), + (str(syntax_error.lineno), "pygments.number"), + style="pygments.text", + ) + yield path_highlighter(text) + syntax_error_text = highlighter(syntax_error.line.rstrip()) + syntax_error_text.no_wrap = True + offset = min(syntax_error.offset - 1, len(syntax_error_text)) + syntax_error_text.stylize("bold underline", offset, offset) + syntax_error_text += Text.from_markup( + "\n" + " " * offset + "[traceback.offset]â–²[/]", + style="pygments.text", + ) + yield syntax_error_text + + @classmethod + def _guess_lexer(cls, filename: str, code: str) -> str: + ext = os.path.splitext(filename)[-1] + if not ext: + # No extension, look at first line to see if it is a hashbang + # Note, this is an educated guess and not a guarantee + # If it fails, the only downside is that the code is highlighted strangely + new_line_index = code.index("\n") + first_line = code[:new_line_index] if new_line_index != -1 else code + if first_line.startswith("#!") and "python" in first_line.lower(): + return "python" + lexer_name = ( + cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name + ) + return lexer_name + + @group() + def _render_stack(self, stack: Stack) -> RenderResult: + path_highlighter = PathHighlighter() + theme = self.theme + code_cache: Dict[str, str] = {} + + def read_code(filename: str) -> str: + """Read files, and cache results on filename. + + Args: + filename (str): Filename to read + + Returns: + str: Contents of file + """ + code = code_cache.get(filename) + if code is None: + with open( + filename, "rt", encoding="utf-8", errors="replace" + ) as code_file: + code = code_file.read() + code_cache[filename] = code + return code + + def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]: + if frame.locals: + yield render_scope( + frame.locals, + title="locals", + indent_guides=self.indent_guides, + max_length=self.locals_max_length, + max_string=self.locals_max_string, + ) + + exclude_frames: Optional[range] = None + if self.max_frames != 0: + exclude_frames = range( + self.max_frames // 2, + len(stack.frames) - self.max_frames // 2, + ) + + excluded = False + for frame_index, frame in enumerate(stack.frames): + + if exclude_frames and frame_index in exclude_frames: + excluded = True + continue + + if excluded: + assert exclude_frames is not None + yield Text( + f"\n... {len(exclude_frames)} frames hidden ...", + justify="center", + style="traceback.error", + ) + excluded = False + + first = frame_index == 1 + frame_filename = frame.filename + suppressed = any(frame_filename.startswith(path) for path in self.suppress) + + text = Text.assemble( + path_highlighter(Text(frame.filename, style="pygments.string")), + (":", "pygments.text"), + (str(frame.lineno), "pygments.number"), + " in ", + (frame.name, "pygments.function"), + style="pygments.text", + ) + if not frame.filename.startswith("<") and not first: + yield "" + yield text + if frame.filename.startswith("<"): + yield from render_locals(frame) + continue + if not suppressed: + try: + code = read_code(frame.filename) + lexer_name = self._guess_lexer(frame.filename, code) + syntax = Syntax( + code, + lexer_name, + theme=theme, + line_numbers=True, + line_range=( + frame.lineno - self.extra_lines, + frame.lineno + self.extra_lines, + ), + highlight_lines={frame.lineno}, + word_wrap=self.word_wrap, + code_width=88, + indent_guides=self.indent_guides, + dedent=False, + ) + yield "" + except Exception as error: + yield Text.assemble( + (f"\n{error}", "traceback.error"), + ) + else: + yield ( + Columns( + [ + syntax, + *render_locals(frame), + ], + padding=1, + ) + if frame.locals + else syntax + ) + + +if __name__ == "__main__": # pragma: no cover + + from .console import Console + + console = Console() + import sys + + def bar(a: Any) -> None: # 这是对亚洲语言支æŒçš„æµ‹è¯•。é¢å¯¹æ¨¡æ£±ä¸¤å¯çš„æƒ³æ³•,拒ç»çŒœæµ‹çš„诱惑 + one = 1 + print(one / a) + + def foo(a: Any) -> None: + _rich_traceback_guard = True + zed = { + "characters": { + "Paul Atreides", + "Vladimir Harkonnen", + "Thufir Hawat", + "Duncan Idaho", + }, + "atomic_types": (None, False, True), + } + bar(a) + + def error() -> None: + + try: + try: + foo(0) + except: + slfkjsldkfj # type: ignore + except: + console.print_exception(show_locals=True) + + error() diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/rich/tree.py b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/tree.py new file mode 100644 index 0000000..c5ec27d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/rich/tree.py @@ -0,0 +1,249 @@ +from typing import Iterator, List, Optional, Tuple + +from ._loop import loop_first, loop_last +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style, StyleStack, StyleType +from .styled import Styled + + +class Tree(JupyterMixin): + """A renderable for a tree structure. + + Args: + label (RenderableType): The renderable or str for the tree label. + style (StyleType, optional): Style of this tree. Defaults to "tree". + guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line". + expanded (bool, optional): Also display children. Defaults to True. + highlight (bool, optional): Highlight renderable (if str). Defaults to False. + """ + + def __init__( + self, + label: RenderableType, + *, + style: StyleType = "tree", + guide_style: StyleType = "tree.line", + expanded: bool = True, + highlight: bool = False, + hide_root: bool = False, + ) -> None: + self.label = label + self.style = style + self.guide_style = guide_style + self.children: List[Tree] = [] + self.expanded = expanded + self.highlight = highlight + self.hide_root = hide_root + + def add( + self, + label: RenderableType, + *, + style: Optional[StyleType] = None, + guide_style: Optional[StyleType] = None, + expanded: bool = True, + highlight: bool = False, + ) -> "Tree": + """Add a child tree. + + Args: + label (RenderableType): The renderable or str for the tree label. + style (StyleType, optional): Style of this tree. Defaults to "tree". + guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line". + expanded (bool, optional): Also display children. Defaults to True. + highlight (Optional[bool], optional): Highlight renderable (if str). Defaults to False. + + Returns: + Tree: A new child Tree, which may be further modified. + """ + node = Tree( + label, + style=self.style if style is None else style, + guide_style=self.guide_style if guide_style is None else guide_style, + expanded=expanded, + highlight=self.highlight if highlight is None else highlight, + ) + self.children.append(node) + return node + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + + stack: List[Iterator[Tuple[bool, Tree]]] = [] + pop = stack.pop + push = stack.append + new_line = Segment.line() + + get_style = console.get_style + null_style = Style.null() + guide_style = get_style(self.guide_style, default="") or null_style + SPACE, CONTINUE, FORK, END = range(4) + + ASCII_GUIDES = (" ", "| ", "+-- ", "`-- ") + TREE_GUIDES = [ + (" ", "│ ", "├── ", "└── "), + (" ", "┃ ", "┣â”â” ", "â”—â”â” "), + (" ", "â•‘ ", "â• â•â• ", "╚â•â• "), + ] + _Segment = Segment + + def make_guide(index: int, style: Style) -> Segment: + """Make a Segment for a level of the guide lines.""" + if options.ascii_only: + line = ASCII_GUIDES[index] + else: + guide = 1 if style.bold else (2 if style.underline2 else 0) + line = TREE_GUIDES[0 if options.legacy_windows else guide][index] + return _Segment(line, style) + + levels: List[Segment] = [make_guide(CONTINUE, guide_style)] + push(iter(loop_last([self]))) + + guide_style_stack = StyleStack(get_style(self.guide_style)) + style_stack = StyleStack(get_style(self.style)) + remove_guide_styles = Style(bold=False, underline2=False) + + depth = 0 + + while stack: + stack_node = pop() + try: + last, node = next(stack_node) + except StopIteration: + levels.pop() + if levels: + guide_style = levels[-1].style or null_style + levels[-1] = make_guide(FORK, guide_style) + guide_style_stack.pop() + style_stack.pop() + continue + push(stack_node) + if last: + levels[-1] = make_guide(END, levels[-1].style or null_style) + + guide_style = guide_style_stack.current + get_style(node.guide_style) + style = style_stack.current + get_style(node.style) + prefix = levels[(2 if self.hide_root else 1) :] + renderable_lines = console.render_lines( + Styled(node.label, style), + options.update( + width=options.max_width + - sum(level.cell_length for level in prefix), + highlight=self.highlight, + height=None, + ), + ) + + if not (depth == 0 and self.hide_root): + for first, line in loop_first(renderable_lines): + if prefix: + yield from _Segment.apply_style( + prefix, + style.background_style, + post_style=remove_guide_styles, + ) + yield from line + yield new_line + if first and prefix: + prefix[-1] = make_guide( + SPACE if last else CONTINUE, prefix[-1].style or null_style + ) + + if node.expanded and node.children: + levels[-1] = make_guide( + SPACE if last else CONTINUE, levels[-1].style or null_style + ) + levels.append( + make_guide(END if len(node.children) == 1 else FORK, guide_style) + ) + style_stack.push(get_style(node.style)) + guide_style_stack.push(get_style(node.guide_style)) + push(iter(loop_last(node.children))) + depth += 1 + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + stack: List[Iterator[Tree]] = [iter([self])] + pop = stack.pop + push = stack.append + minimum = 0 + maximum = 0 + measure = Measurement.get + level = 0 + while stack: + iter_tree = pop() + try: + tree = next(iter_tree) + except StopIteration: + level -= 1 + continue + push(iter_tree) + min_measure, max_measure = measure(console, options, tree.label) + indent = level * 4 + minimum = max(min_measure + indent, minimum) + maximum = max(max_measure + indent, maximum) + if tree.expanded and tree.children: + push(iter(tree.children)) + level += 1 + return Measurement(minimum, maximum) + + +if __name__ == "__main__": # pragma: no cover + + from pip._vendor.rich.console import Group + from pip._vendor.rich.markdown import Markdown + from pip._vendor.rich.panel import Panel + from pip._vendor.rich.syntax import Syntax + from pip._vendor.rich.table import Table + + table = Table(row_styles=["", "dim"]) + + table.add_column("Released", style="cyan", no_wrap=True) + table.add_column("Title", style="magenta") + table.add_column("Box Office", justify="right", style="green") + + table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690") + table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") + table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889") + table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889") + + code = """\ +class Segment(NamedTuple): + text: str = "" + style: Optional[Style] = None + is_control: bool = False +""" + syntax = Syntax(code, "python", theme="monokai", line_numbers=True) + + markdown = Markdown( + """\ +### example.md +> Hello, World! +> +> Markdown _all_ the things +""" + ) + + root = Tree("🌲 [b green]Rich Tree", highlight=True, hide_root=True) + + node = root.add(":file_folder: Renderables", guide_style="red") + simple_node = node.add(":file_folder: [bold yellow]Atomic", guide_style="uu green") + simple_node.add(Group("📄 Syntax", syntax)) + simple_node.add(Group("📄 Markdown", Panel(markdown, border_style="green"))) + + containers_node = node.add( + ":file_folder: [bold magenta]Containers", guide_style="bold magenta" + ) + containers_node.expanded = True + panel = Panel.fit("Just a panel", border_style="red") + containers_node.add(Group("📄 Panels", panel)) + + containers_node.add(Group("📄 [b magenta]Table", table)) + + console = Console() + console.print(root) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/six.py b/.venv/lib/python3.8/site-packages/pip/_vendor/six.py new file mode 100644 index 0000000..4e15675 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/six.py @@ -0,0 +1,998 @@ +# Copyright (c) 2010-2020 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.16.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + del io + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] > (3,): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper(wrapper, wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + return functools.partial(_update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + wraps.__doc__ = functools.wraps.__doc__ + +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + + def __new__(cls, name, this_bases, d): + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d['__orig_bases__'] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + if hasattr(cls, '__qualname__'): + orig_vars['__qualname__'] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def ensure_binary(s, encoding='utf-8', errors='strict'): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, binary_type): + return s + if isinstance(s, text_type): + return s.encode(encoding, errors) + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding='utf-8', errors='strict'): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + # Optimization: Fast return for the common case. + if type(s) is str: + return s + if PY2 and isinstance(s, text_type): + return s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + return s + + +def ensure_text(s, encoding='utf-8', errors='strict'): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def python_2_unicode_compatible(klass): + """ + A class decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__init__.py new file mode 100644 index 0000000..086ad46 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__init__.py @@ -0,0 +1,517 @@ +# Copyright 2016-2018 Julien Danjou +# Copyright 2017 Elisey Zanko +# Copyright 2016 Étienne Bersac +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools +import sys +import threading +import time +import typing as t +import warnings +from abc import ABC, abstractmethod +from concurrent import futures +from inspect import iscoroutinefunction + +# Import all built-in retry strategies for easier usage. +from .retry import retry_base # noqa +from .retry import retry_all # noqa +from .retry import retry_always # noqa +from .retry import retry_any # noqa +from .retry import retry_if_exception # noqa +from .retry import retry_if_exception_type # noqa +from .retry import retry_if_not_exception_type # noqa +from .retry import retry_if_not_result # noqa +from .retry import retry_if_result # noqa +from .retry import retry_never # noqa +from .retry import retry_unless_exception_type # noqa +from .retry import retry_if_exception_message # noqa +from .retry import retry_if_not_exception_message # noqa + +# Import all nap strategies for easier usage. +from .nap import sleep # noqa +from .nap import sleep_using_event # noqa + +# Import all built-in stop strategies for easier usage. +from .stop import stop_after_attempt # noqa +from .stop import stop_after_delay # noqa +from .stop import stop_all # noqa +from .stop import stop_any # noqa +from .stop import stop_never # noqa +from .stop import stop_when_event_set # noqa + +# Import all built-in wait strategies for easier usage. +from .wait import wait_chain # noqa +from .wait import wait_combine # noqa +from .wait import wait_exponential # noqa +from .wait import wait_fixed # noqa +from .wait import wait_incrementing # noqa +from .wait import wait_none # noqa +from .wait import wait_random # noqa +from .wait import wait_random_exponential # noqa +from .wait import wait_random_exponential as wait_full_jitter # noqa + +# Import all built-in before strategies for easier usage. +from .before import before_log # noqa +from .before import before_nothing # noqa + +# Import all built-in after strategies for easier usage. +from .after import after_log # noqa +from .after import after_nothing # noqa + +# Import all built-in after strategies for easier usage. +from .before_sleep import before_sleep_log # noqa +from .before_sleep import before_sleep_nothing # noqa + +# Replace a conditional import with a hard-coded None so that pip does +# not attempt to use tornado even if it is present in the environment. +# If tornado is non-None, tenacity will attempt to execute some code +# that is sensitive to the version of tornado, which could break pip +# if an old version is found. +tornado = None # type: ignore + +if t.TYPE_CHECKING: + import types + + from .wait import wait_base + from .stop import stop_base + + +WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable) +_RetValT = t.TypeVar("_RetValT") + + +@t.overload +def retry(fn: WrappedFn) -> WrappedFn: + pass + + +@t.overload +def retry(*dargs: t.Any, **dkw: t.Any) -> t.Callable[[WrappedFn], WrappedFn]: # noqa + pass + + +def retry(*dargs: t.Any, **dkw: t.Any) -> t.Union[WrappedFn, t.Callable[[WrappedFn], WrappedFn]]: # noqa + """Wrap a function with a new `Retrying` object. + + :param dargs: positional arguments passed to Retrying object + :param dkw: keyword arguments passed to the Retrying object + """ + # support both @retry and @retry() as valid syntax + if len(dargs) == 1 and callable(dargs[0]): + return retry()(dargs[0]) + else: + + def wrap(f: WrappedFn) -> WrappedFn: + if isinstance(f, retry_base): + warnings.warn( + f"Got retry_base instance ({f.__class__.__name__}) as callable argument, " + f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)" + ) + if iscoroutinefunction(f): + r: "BaseRetrying" = AsyncRetrying(*dargs, **dkw) + elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f): + r = TornadoRetrying(*dargs, **dkw) + else: + r = Retrying(*dargs, **dkw) + + return r.wraps(f) + + return wrap + + +class TryAgain(Exception): + """Always retry the executed function when raised.""" + + +NO_RESULT = object() + + +class DoAttempt: + pass + + +class DoSleep(float): + pass + + +class BaseAction: + """Base class for representing actions to take by retry object. + + Concrete implementations must define: + - __init__: to initialize all necessary fields + - REPR_FIELDS: class variable specifying attributes to include in repr(self) + - NAME: for identification in retry object methods and callbacks + """ + + REPR_FIELDS: t.Sequence[str] = () + NAME: t.Optional[str] = None + + def __repr__(self) -> str: + state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS) + return f"{self.__class__.__name__}({state_str})" + + def __str__(self) -> str: + return repr(self) + + +class RetryAction(BaseAction): + REPR_FIELDS = ("sleep",) + NAME = "retry" + + def __init__(self, sleep: t.SupportsFloat) -> None: + self.sleep = float(sleep) + + +_unset = object() + + +def _first_set(first: t.Union[t.Any, object], second: t.Any) -> t.Any: + return second if first is _unset else first + + +class RetryError(Exception): + """Encapsulates the last attempt instance right before giving up.""" + + def __init__(self, last_attempt: "Future") -> None: + self.last_attempt = last_attempt + super().__init__(last_attempt) + + def reraise(self) -> "t.NoReturn": + if self.last_attempt.failed: + raise self.last_attempt.result() + raise self + + def __str__(self) -> str: + return f"{self.__class__.__name__}[{self.last_attempt}]" + + +class AttemptManager: + """Manage attempt context.""" + + def __init__(self, retry_state: "RetryCallState"): + self.retry_state = retry_state + + def __enter__(self) -> None: + pass + + def __exit__( + self, + exc_type: t.Optional[t.Type[BaseException]], + exc_value: t.Optional[BaseException], + traceback: t.Optional["types.TracebackType"], + ) -> t.Optional[bool]: + if isinstance(exc_value, BaseException): + self.retry_state.set_exception((exc_type, exc_value, traceback)) + return True # Swallow exception. + else: + # We don't have the result, actually. + self.retry_state.set_result(None) + return None + + +class BaseRetrying(ABC): + def __init__( + self, + sleep: t.Callable[[t.Union[int, float]], None] = sleep, + stop: "stop_base" = stop_never, + wait: "wait_base" = wait_none(), + retry: retry_base = retry_if_exception_type(), + before: t.Callable[["RetryCallState"], None] = before_nothing, + after: t.Callable[["RetryCallState"], None] = after_nothing, + before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None, + reraise: bool = False, + retry_error_cls: t.Type[RetryError] = RetryError, + retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None, + ): + self.sleep = sleep + self.stop = stop + self.wait = wait + self.retry = retry + self.before = before + self.after = after + self.before_sleep = before_sleep + self.reraise = reraise + self._local = threading.local() + self.retry_error_cls = retry_error_cls + self.retry_error_callback = retry_error_callback + + def copy( + self, + sleep: t.Union[t.Callable[[t.Union[int, float]], None], object] = _unset, + stop: t.Union["stop_base", object] = _unset, + wait: t.Union["wait_base", object] = _unset, + retry: t.Union[retry_base, object] = _unset, + before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, + after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, + before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset, + reraise: t.Union[bool, object] = _unset, + retry_error_cls: t.Union[t.Type[RetryError], object] = _unset, + retry_error_callback: t.Union[t.Optional[t.Callable[["RetryCallState"], t.Any]], object] = _unset, + ) -> "BaseRetrying": + """Copy this object with some parameters changed if needed.""" + return self.__class__( + sleep=_first_set(sleep, self.sleep), + stop=_first_set(stop, self.stop), + wait=_first_set(wait, self.wait), + retry=_first_set(retry, self.retry), + before=_first_set(before, self.before), + after=_first_set(after, self.after), + before_sleep=_first_set(before_sleep, self.before_sleep), + reraise=_first_set(reraise, self.reraise), + retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls), + retry_error_callback=_first_set(retry_error_callback, self.retry_error_callback), + ) + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} object at 0x{id(self):x} (" + f"stop={self.stop}, " + f"wait={self.wait}, " + f"sleep={self.sleep}, " + f"retry={self.retry}, " + f"before={self.before}, " + f"after={self.after})>" + ) + + @property + def statistics(self) -> t.Dict[str, t.Any]: + """Return a dictionary of runtime statistics. + + This dictionary will be empty when the controller has never been + ran. When it is running or has ran previously it should have (but + may not) have useful and/or informational keys and values when + running is underway and/or completed. + + .. warning:: The keys in this dictionary **should** be some what + stable (not changing), but there existence **may** + change between major releases as new statistics are + gathered or removed so before accessing keys ensure that + they actually exist and handle when they do not. + + .. note:: The values in this dictionary are local to the thread + running call (so if multiple threads share the same retrying + object - either directly or indirectly) they will each have + there own view of statistics they have collected (in the + future we may provide a way to aggregate the various + statistics from each thread). + """ + try: + return self._local.statistics + except AttributeError: + self._local.statistics = {} + return self._local.statistics + + def wraps(self, f: WrappedFn) -> WrappedFn: + """Wrap a function for retrying. + + :param f: A function to wraps for retrying. + """ + + @functools.wraps(f) + def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any: + return self(f, *args, **kw) + + def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn: + return self.copy(*args, **kwargs).wraps(f) + + wrapped_f.retry = self + wrapped_f.retry_with = retry_with + + return wrapped_f + + def begin(self) -> None: + self.statistics.clear() + self.statistics["start_time"] = time.monotonic() + self.statistics["attempt_number"] = 1 + self.statistics["idle_for"] = 0 + + def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa + fut = retry_state.outcome + if fut is None: + if self.before is not None: + self.before(retry_state) + return DoAttempt() + + is_explicit_retry = retry_state.outcome.failed and isinstance(retry_state.outcome.exception(), TryAgain) + if not (is_explicit_retry or self.retry(retry_state=retry_state)): + return fut.result() + + if self.after is not None: + self.after(retry_state) + + self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start + if self.stop(retry_state=retry_state): + if self.retry_error_callback: + return self.retry_error_callback(retry_state) + retry_exc = self.retry_error_cls(fut) + if self.reraise: + raise retry_exc.reraise() + raise retry_exc from fut.exception() + + if self.wait: + sleep = self.wait(retry_state=retry_state) + else: + sleep = 0.0 + retry_state.next_action = RetryAction(sleep) + retry_state.idle_for += sleep + self.statistics["idle_for"] += sleep + self.statistics["attempt_number"] += 1 + + if self.before_sleep is not None: + self.before_sleep(retry_state) + + return DoSleep(sleep) + + def __iter__(self) -> t.Generator[AttemptManager, None, None]: + self.begin() + + retry_state = RetryCallState(self, fn=None, args=(), kwargs={}) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + yield AttemptManager(retry_state=retry_state) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + self.sleep(do) + else: + break + + @abstractmethod + def __call__(self, fn: t.Callable[..., _RetValT], *args: t.Any, **kwargs: t.Any) -> _RetValT: + pass + + +class Retrying(BaseRetrying): + """Retrying controller.""" + + def __call__(self, fn: t.Callable[..., _RetValT], *args: t.Any, **kwargs: t.Any) -> _RetValT: + self.begin() + + retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + try: + result = fn(*args, **kwargs) + except BaseException: # noqa: B902 + retry_state.set_exception(sys.exc_info()) + else: + retry_state.set_result(result) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + self.sleep(do) + else: + return do + + +class Future(futures.Future): + """Encapsulates a (future or past) attempted call to a target function.""" + + def __init__(self, attempt_number: int) -> None: + super().__init__() + self.attempt_number = attempt_number + + @property + def failed(self) -> bool: + """Return whether a exception is being held in this future.""" + return self.exception() is not None + + @classmethod + def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future": + """Construct a new Future object.""" + fut = cls(attempt_number) + if has_exception: + fut.set_exception(value) + else: + fut.set_result(value) + return fut + + +class RetryCallState: + """State related to a single call wrapped with Retrying.""" + + def __init__( + self, + retry_object: BaseRetrying, + fn: t.Optional[WrappedFn], + args: t.Any, + kwargs: t.Any, + ) -> None: + #: Retry call start timestamp + self.start_time = time.monotonic() + #: Retry manager object + self.retry_object = retry_object + #: Function wrapped by this retry call + self.fn = fn + #: Arguments of the function wrapped by this retry call + self.args = args + #: Keyword arguments of the function wrapped by this retry call + self.kwargs = kwargs + + #: The number of the current attempt + self.attempt_number: int = 1 + #: Last outcome (result or exception) produced by the function + self.outcome: t.Optional[Future] = None + #: Timestamp of the last outcome + self.outcome_timestamp: t.Optional[float] = None + #: Time spent sleeping in retries + self.idle_for: float = 0.0 + #: Next action as decided by the retry manager + self.next_action: t.Optional[RetryAction] = None + + @property + def seconds_since_start(self) -> t.Optional[float]: + if self.outcome_timestamp is None: + return None + return self.outcome_timestamp - self.start_time + + def prepare_for_next_attempt(self) -> None: + self.outcome = None + self.outcome_timestamp = None + self.attempt_number += 1 + self.next_action = None + + def set_result(self, val: t.Any) -> None: + ts = time.monotonic() + fut = Future(self.attempt_number) + fut.set_result(val) + self.outcome, self.outcome_timestamp = fut, ts + + def set_exception(self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType"]) -> None: + ts = time.monotonic() + fut = Future(self.attempt_number) + fut.set_exception(exc_info[1]) + self.outcome, self.outcome_timestamp = fut, ts + + def __repr__(self): + if self.outcome is None: + result = "none yet" + elif self.outcome.failed: + exception = self.outcome.exception() + result = f"failed ({exception.__class__.__name__} {exception})" + else: + result = f"returned {self.outcome.result()}" + + slept = float(round(self.idle_for, 2)) + clsname = self.__class__.__name__ + return f"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>" + + +from pip._vendor.tenacity._asyncio import AsyncRetrying # noqa:E402,I100 + +if tornado: + from pip._vendor.tenacity.tornadoweb import TornadoRetrying diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3345daa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/_asyncio.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/_asyncio.cpython-38.pyc new file mode 100644 index 0000000..5a07e9e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/_asyncio.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/_utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/_utils.cpython-38.pyc new file mode 100644 index 0000000..f0c97da Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/_utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/after.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/after.cpython-38.pyc new file mode 100644 index 0000000..a010a83 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/after.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/before.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/before.cpython-38.pyc new file mode 100644 index 0000000..5c07203 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/before.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/before_sleep.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/before_sleep.cpython-38.pyc new file mode 100644 index 0000000..e9af05b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/before_sleep.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/nap.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/nap.cpython-38.pyc new file mode 100644 index 0000000..0083f13 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/nap.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/retry.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/retry.cpython-38.pyc new file mode 100644 index 0000000..a482ea0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/retry.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/stop.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/stop.cpython-38.pyc new file mode 100644 index 0000000..abf090c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/stop.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/tornadoweb.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/tornadoweb.cpython-38.pyc new file mode 100644 index 0000000..40640d3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/tornadoweb.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/wait.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/wait.cpython-38.pyc new file mode 100644 index 0000000..c1a2276 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/__pycache__/wait.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/_asyncio.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/_asyncio.py new file mode 100644 index 0000000..0f32b5f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/_asyncio.py @@ -0,0 +1,92 @@ +# Copyright 2016 Étienne Bersac +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools +import sys +import typing +from asyncio import sleep + +from pip._vendor.tenacity import AttemptManager +from pip._vendor.tenacity import BaseRetrying +from pip._vendor.tenacity import DoAttempt +from pip._vendor.tenacity import DoSleep +from pip._vendor.tenacity import RetryCallState + +WrappedFn = typing.TypeVar("WrappedFn", bound=typing.Callable) +_RetValT = typing.TypeVar("_RetValT") + + +class AsyncRetrying(BaseRetrying): + def __init__(self, sleep: typing.Callable[[float], typing.Awaitable] = sleep, **kwargs: typing.Any) -> None: + super().__init__(**kwargs) + self.sleep = sleep + + async def __call__( # type: ignore # Change signature from supertype + self, + fn: typing.Callable[..., typing.Awaitable[_RetValT]], + *args: typing.Any, + **kwargs: typing.Any, + ) -> _RetValT: + self.begin() + + retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + try: + result = await fn(*args, **kwargs) + except BaseException: # noqa: B902 + retry_state.set_exception(sys.exc_info()) + else: + retry_state.set_result(result) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + await self.sleep(do) + else: + return do + + def __aiter__(self) -> "AsyncRetrying": + self.begin() + self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={}) + return self + + async def __anext__(self) -> typing.Union[AttemptManager, typing.Any]: + while True: + do = self.iter(retry_state=self._retry_state) + if do is None: + raise StopAsyncIteration + elif isinstance(do, DoAttempt): + return AttemptManager(retry_state=self._retry_state) + elif isinstance(do, DoSleep): + self._retry_state.prepare_for_next_attempt() + await self.sleep(do) + else: + return do + + def wraps(self, fn: WrappedFn) -> WrappedFn: + fn = super().wraps(fn) + # Ensure wrapper is recognized as a coroutine function. + + @functools.wraps(fn) + async def async_wrapped(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: + return await fn(*args, **kwargs) + + # Preserve attributes + async_wrapped.retry = fn.retry + async_wrapped.retry_with = fn.retry_with + + return async_wrapped diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/_utils.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/_utils.py new file mode 100644 index 0000000..d5c4c9d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/_utils.py @@ -0,0 +1,68 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import typing + + +# sys.maxsize: +# An integer giving the maximum value a variable of type Py_ssize_t can take. +MAX_WAIT = sys.maxsize / 2 + + +def find_ordinal(pos_num: int) -> str: + # See: https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers + if pos_num == 0: + return "th" + elif pos_num == 1: + return "st" + elif pos_num == 2: + return "nd" + elif pos_num == 3: + return "rd" + elif 4 <= pos_num <= 20: + return "th" + else: + return find_ordinal(pos_num % 10) + + +def to_ordinal(pos_num: int) -> str: + return f"{pos_num}{find_ordinal(pos_num)}" + + +def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str: + """Get a callback fully-qualified name. + + If no name can be produced ``repr(cb)`` is called and returned. + """ + segments = [] + try: + segments.append(cb.__qualname__) + except AttributeError: + try: + segments.append(cb.__name__) + except AttributeError: + pass + if not segments: + return repr(cb) + else: + try: + # When running under sphinx it appears this can be none? + if cb.__module__: + segments.insert(0, cb.__module__) + except AttributeError: + pass + return ".".join(segments) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/after.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/after.py new file mode 100644 index 0000000..c056700 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/after.py @@ -0,0 +1,46 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing + +from pip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + import logging + + from pip._vendor.tenacity import RetryCallState + + +def after_nothing(retry_state: "RetryCallState") -> None: + """After call strategy that does nothing.""" + + +def after_log( + logger: "logging.Logger", + log_level: int, + sec_format: str = "%0.3f", +) -> typing.Callable[["RetryCallState"], None]: + """After call strategy that logs to some logger the finished attempt.""" + + def log_it(retry_state: "RetryCallState") -> None: + logger.log( + log_level, + f"Finished call to '{_utils.get_callback_name(retry_state.fn)}' " + f"after {sec_format % retry_state.seconds_since_start}(s), " + f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.", + ) + + return log_it diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/before.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/before.py new file mode 100644 index 0000000..a72c2c5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/before.py @@ -0,0 +1,41 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing + +from pip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + import logging + + from pip._vendor.tenacity import RetryCallState + + +def before_nothing(retry_state: "RetryCallState") -> None: + """Before call strategy that does nothing.""" + + +def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["RetryCallState"], None]: + """Before call strategy that logs to some logger the attempt.""" + + def log_it(retry_state: "RetryCallState") -> None: + logger.log( + log_level, + f"Starting call to '{_utils.get_callback_name(retry_state.fn)}', " + f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.", + ) + + return log_it diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/before_sleep.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/before_sleep.py new file mode 100644 index 0000000..b35564f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/before_sleep.py @@ -0,0 +1,58 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing + +from pip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + import logging + + from pip._vendor.tenacity import RetryCallState + + +def before_sleep_nothing(retry_state: "RetryCallState") -> None: + """Before call strategy that does nothing.""" + + +def before_sleep_log( + logger: "logging.Logger", + log_level: int, + exc_info: bool = False, +) -> typing.Callable[["RetryCallState"], None]: + """Before call strategy that logs to some logger the attempt.""" + + def log_it(retry_state: "RetryCallState") -> None: + if retry_state.outcome.failed: + ex = retry_state.outcome.exception() + verb, value = "raised", f"{ex.__class__.__name__}: {ex}" + + if exc_info: + local_exc_info = retry_state.outcome.exception() + else: + local_exc_info = False + else: + verb, value = "returned", retry_state.outcome.result() + local_exc_info = False # exc_info does not apply when no exception + + logger.log( + log_level, + f"Retrying {_utils.get_callback_name(retry_state.fn)} " + f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.", + exc_info=local_exc_info, + ) + + return log_it diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/nap.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/nap.py new file mode 100644 index 0000000..72aa5bf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/nap.py @@ -0,0 +1,43 @@ +# Copyright 2016 Étienne Bersac +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import typing + +if typing.TYPE_CHECKING: + import threading + + +def sleep(seconds: float) -> None: + """ + Sleep strategy that delays execution for a given number of seconds. + + This is the default strategy, and may be mocked out for unit testing. + """ + time.sleep(seconds) + + +class sleep_using_event: + """Sleep strategy that waits on an event to be set.""" + + def __init__(self, event: "threading.Event") -> None: + self.event = event + + def __call__(self, timeout: typing.Optional[float]) -> None: + # NOTE(harlowja): this may *not* actually wait for timeout + # seconds if the event is set (ie this may eject out early). + self.event.wait(timeout=timeout) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/retry.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/retry.py new file mode 100644 index 0000000..1d727e9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/retry.py @@ -0,0 +1,213 @@ +# Copyright 2016–2021 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc +import re +import typing + +if typing.TYPE_CHECKING: + from pip._vendor.tenacity import RetryCallState + + +class retry_base(abc.ABC): + """Abstract base class for retry strategies.""" + + @abc.abstractmethod + def __call__(self, retry_state: "RetryCallState") -> bool: + pass + + def __and__(self, other: "retry_base") -> "retry_all": + return retry_all(self, other) + + def __or__(self, other: "retry_base") -> "retry_any": + return retry_any(self, other) + + +class _retry_never(retry_base): + """Retry strategy that never rejects any result.""" + + def __call__(self, retry_state: "RetryCallState") -> bool: + return False + + +retry_never = _retry_never() + + +class _retry_always(retry_base): + """Retry strategy that always rejects any result.""" + + def __call__(self, retry_state: "RetryCallState") -> bool: + return True + + +retry_always = _retry_always() + + +class retry_if_exception(retry_base): + """Retry strategy that retries if an exception verifies a predicate.""" + + def __init__(self, predicate: typing.Callable[[BaseException], bool]) -> None: + self.predicate = predicate + + def __call__(self, retry_state: "RetryCallState") -> bool: + if retry_state.outcome.failed: + return self.predicate(retry_state.outcome.exception()) + else: + return False + + +class retry_if_exception_type(retry_if_exception): + """Retries if an exception has been raised of one or more types.""" + + def __init__( + self, + exception_types: typing.Union[ + typing.Type[BaseException], + typing.Tuple[typing.Type[BaseException], ...], + ] = Exception, + ) -> None: + self.exception_types = exception_types + super().__init__(lambda e: isinstance(e, exception_types)) + + +class retry_if_not_exception_type(retry_if_exception): + """Retries except an exception has been raised of one or more types.""" + + def __init__( + self, + exception_types: typing.Union[ + typing.Type[BaseException], + typing.Tuple[typing.Type[BaseException], ...], + ] = Exception, + ) -> None: + self.exception_types = exception_types + super().__init__(lambda e: not isinstance(e, exception_types)) + + +class retry_unless_exception_type(retry_if_exception): + """Retries until an exception is raised of one or more types.""" + + def __init__( + self, + exception_types: typing.Union[ + typing.Type[BaseException], + typing.Tuple[typing.Type[BaseException], ...], + ] = Exception, + ) -> None: + self.exception_types = exception_types + super().__init__(lambda e: not isinstance(e, exception_types)) + + def __call__(self, retry_state: "RetryCallState") -> bool: + # always retry if no exception was raised + if not retry_state.outcome.failed: + return True + return self.predicate(retry_state.outcome.exception()) + + +class retry_if_result(retry_base): + """Retries if the result verifies a predicate.""" + + def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None: + self.predicate = predicate + + def __call__(self, retry_state: "RetryCallState") -> bool: + if not retry_state.outcome.failed: + return self.predicate(retry_state.outcome.result()) + else: + return False + + +class retry_if_not_result(retry_base): + """Retries if the result refutes a predicate.""" + + def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None: + self.predicate = predicate + + def __call__(self, retry_state: "RetryCallState") -> bool: + if not retry_state.outcome.failed: + return not self.predicate(retry_state.outcome.result()) + else: + return False + + +class retry_if_exception_message(retry_if_exception): + """Retries if an exception message equals or matches.""" + + def __init__( + self, + message: typing.Optional[str] = None, + match: typing.Optional[str] = None, + ) -> None: + if message and match: + raise TypeError(f"{self.__class__.__name__}() takes either 'message' or 'match', not both") + + # set predicate + if message: + + def message_fnc(exception: BaseException) -> bool: + return message == str(exception) + + predicate = message_fnc + elif match: + prog = re.compile(match) + + def match_fnc(exception: BaseException) -> bool: + return bool(prog.match(str(exception))) + + predicate = match_fnc + else: + raise TypeError(f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'") + + super().__init__(predicate) + + +class retry_if_not_exception_message(retry_if_exception_message): + """Retries until an exception message equals or matches.""" + + def __init__( + self, + message: typing.Optional[str] = None, + match: typing.Optional[str] = None, + ) -> None: + super().__init__(message, match) + # invert predicate + if_predicate = self.predicate + self.predicate = lambda *args_, **kwargs_: not if_predicate(*args_, **kwargs_) + + def __call__(self, retry_state: "RetryCallState") -> bool: + if not retry_state.outcome.failed: + return True + return self.predicate(retry_state.outcome.exception()) + + +class retry_any(retry_base): + """Retries if any of the retries condition is valid.""" + + def __init__(self, *retries: retry_base) -> None: + self.retries = retries + + def __call__(self, retry_state: "RetryCallState") -> bool: + return any(r(retry_state) for r in self.retries) + + +class retry_all(retry_base): + """Retries if all the retries condition are valid.""" + + def __init__(self, *retries: retry_base) -> None: + self.retries = retries + + def __call__(self, retry_state: "RetryCallState") -> bool: + return all(r(retry_state) for r in self.retries) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/stop.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/stop.py new file mode 100644 index 0000000..faaae9a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/stop.py @@ -0,0 +1,96 @@ +# Copyright 2016–2021 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import abc +import typing + +if typing.TYPE_CHECKING: + import threading + + from pip._vendor.tenacity import RetryCallState + + +class stop_base(abc.ABC): + """Abstract base class for stop strategies.""" + + @abc.abstractmethod + def __call__(self, retry_state: "RetryCallState") -> bool: + pass + + def __and__(self, other: "stop_base") -> "stop_all": + return stop_all(self, other) + + def __or__(self, other: "stop_base") -> "stop_any": + return stop_any(self, other) + + +class stop_any(stop_base): + """Stop if any of the stop condition is valid.""" + + def __init__(self, *stops: stop_base) -> None: + self.stops = stops + + def __call__(self, retry_state: "RetryCallState") -> bool: + return any(x(retry_state) for x in self.stops) + + +class stop_all(stop_base): + """Stop if all the stop conditions are valid.""" + + def __init__(self, *stops: stop_base) -> None: + self.stops = stops + + def __call__(self, retry_state: "RetryCallState") -> bool: + return all(x(retry_state) for x in self.stops) + + +class _stop_never(stop_base): + """Never stop.""" + + def __call__(self, retry_state: "RetryCallState") -> bool: + return False + + +stop_never = _stop_never() + + +class stop_when_event_set(stop_base): + """Stop when the given event is set.""" + + def __init__(self, event: "threading.Event") -> None: + self.event = event + + def __call__(self, retry_state: "RetryCallState") -> bool: + return self.event.is_set() + + +class stop_after_attempt(stop_base): + """Stop when the previous attempt >= max_attempt.""" + + def __init__(self, max_attempt_number: int) -> None: + self.max_attempt_number = max_attempt_number + + def __call__(self, retry_state: "RetryCallState") -> bool: + return retry_state.attempt_number >= self.max_attempt_number + + +class stop_after_delay(stop_base): + """Stop when the time from the first attempt >= limit.""" + + def __init__(self, max_delay: float) -> None: + self.max_delay = max_delay + + def __call__(self, retry_state: "RetryCallState") -> bool: + return retry_state.seconds_since_start >= self.max_delay diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/tornadoweb.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/tornadoweb.py new file mode 100644 index 0000000..8f7731a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/tornadoweb.py @@ -0,0 +1,59 @@ +# Copyright 2017 Elisey Zanko +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import typing + +from pip._vendor.tenacity import BaseRetrying +from pip._vendor.tenacity import DoAttempt +from pip._vendor.tenacity import DoSleep +from pip._vendor.tenacity import RetryCallState + +from tornado import gen + +if typing.TYPE_CHECKING: + from tornado.concurrent import Future + +_RetValT = typing.TypeVar("_RetValT") + + +class TornadoRetrying(BaseRetrying): + def __init__(self, sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, **kwargs: typing.Any) -> None: + super().__init__(**kwargs) + self.sleep = sleep + + @gen.coroutine + def __call__( # type: ignore # Change signature from supertype + self, + fn: "typing.Callable[..., typing.Union[typing.Generator[typing.Any, typing.Any, _RetValT], Future[_RetValT]]]", + *args: typing.Any, + **kwargs: typing.Any, + ) -> "typing.Generator[typing.Any, typing.Any, _RetValT]": + self.begin() + + retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + try: + result = yield fn(*args, **kwargs) + except BaseException: # noqa: B902 + retry_state.set_exception(sys.exc_info()) + else: + retry_state.set_result(result) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + yield self.sleep(do) + else: + raise gen.Return(do) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/wait.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/wait.py new file mode 100644 index 0000000..6ed97a7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tenacity/wait.py @@ -0,0 +1,191 @@ +# Copyright 2016–2021 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc +import random +import typing + +from pip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + from pip._vendor.tenacity import RetryCallState + + +class wait_base(abc.ABC): + """Abstract base class for wait strategies.""" + + @abc.abstractmethod + def __call__(self, retry_state: "RetryCallState") -> float: + pass + + def __add__(self, other: "wait_base") -> "wait_combine": + return wait_combine(self, other) + + def __radd__(self, other: "wait_base") -> typing.Union["wait_combine", "wait_base"]: + # make it possible to use multiple waits with the built-in sum function + if other == 0: + return self + return self.__add__(other) + + +class wait_fixed(wait_base): + """Wait strategy that waits a fixed amount of time between each retry.""" + + def __init__(self, wait: float) -> None: + self.wait_fixed = wait + + def __call__(self, retry_state: "RetryCallState") -> float: + return self.wait_fixed + + +class wait_none(wait_fixed): + """Wait strategy that doesn't wait at all before retrying.""" + + def __init__(self) -> None: + super().__init__(0) + + +class wait_random(wait_base): + """Wait strategy that waits a random amount of time between min/max.""" + + def __init__(self, min: typing.Union[int, float] = 0, max: typing.Union[int, float] = 1) -> None: # noqa + self.wait_random_min = min + self.wait_random_max = max + + def __call__(self, retry_state: "RetryCallState") -> float: + return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min)) + + +class wait_combine(wait_base): + """Combine several waiting strategies.""" + + def __init__(self, *strategies: wait_base) -> None: + self.wait_funcs = strategies + + def __call__(self, retry_state: "RetryCallState") -> float: + return sum(x(retry_state=retry_state) for x in self.wait_funcs) + + +class wait_chain(wait_base): + """Chain two or more waiting strategies. + + If all strategies are exhausted, the very last strategy is used + thereafter. + + For example:: + + @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] + + [wait_fixed(2) for j in range(5)] + + [wait_fixed(5) for k in range(4))) + def wait_chained(): + print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s + thereafter.") + """ + + def __init__(self, *strategies: wait_base) -> None: + self.strategies = strategies + + def __call__(self, retry_state: "RetryCallState") -> float: + wait_func_no = min(max(retry_state.attempt_number, 1), len(self.strategies)) + wait_func = self.strategies[wait_func_no - 1] + return wait_func(retry_state=retry_state) + + +class wait_incrementing(wait_base): + """Wait an incremental amount of time after each attempt. + + Starting at a starting value and incrementing by a value for each attempt + (and restricting the upper limit to some maximum value). + """ + + def __init__( + self, + start: typing.Union[int, float] = 0, + increment: typing.Union[int, float] = 100, + max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa + ) -> None: + self.start = start + self.increment = increment + self.max = max + + def __call__(self, retry_state: "RetryCallState") -> float: + result = self.start + (self.increment * (retry_state.attempt_number - 1)) + return max(0, min(result, self.max)) + + +class wait_exponential(wait_base): + """Wait strategy that applies exponential backoff. + + It allows for a customized multiplier and an ability to restrict the + upper and lower limits to some maximum and minimum value. + + The intervals are fixed (i.e. there is no jitter), so this strategy is + suitable for balancing retries against latency when a required resource is + unavailable for an unknown duration, but *not* suitable for resolving + contention between multiple processes for a shared resource. Use + wait_random_exponential for the latter case. + """ + + def __init__( + self, + multiplier: typing.Union[int, float] = 1, + max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa + exp_base: typing.Union[int, float] = 2, + min: typing.Union[int, float] = 0, # noqa + ) -> None: + self.multiplier = multiplier + self.min = min + self.max = max + self.exp_base = exp_base + + def __call__(self, retry_state: "RetryCallState") -> float: + try: + exp = self.exp_base ** (retry_state.attempt_number - 1) + result = self.multiplier * exp + except OverflowError: + return self.max + return max(max(0, self.min), min(result, self.max)) + + +class wait_random_exponential(wait_exponential): + """Random wait with exponentially widening window. + + An exponential backoff strategy used to mediate contention between multiple + uncoordinated processes for a shared resource in distributed systems. This + is the sense in which "exponential backoff" is meant in e.g. Ethernet + networking, and corresponds to the "Full Jitter" algorithm described in + this blog post: + + https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ + + Each retry occurs at a random time in a geometrically expanding interval. + It allows for a custom multiplier and an ability to restrict the upper + limit of the random interval to some maximum value. + + Example:: + + wait_random_exponential(multiplier=0.5, # initial window 0.5s + max=60) # max 60s timeout + + When waiting for an unavailable resource to become available again, as + opposed to trying to resolve contention for a shared resource, the + wait_exponential strategy (which uses a fixed interval) may be preferable. + + """ + + def __call__(self, retry_state: "RetryCallState") -> float: + high = super().__call__(retry_state=retry_state) + return random.uniform(0, high) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__init__.py new file mode 100644 index 0000000..1cd8e07 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__init__.py @@ -0,0 +1,6 @@ +"""A lil' TOML parser.""" + +__all__ = ("loads", "load", "TOMLDecodeError") +__version__ = "1.0.3" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT + +from pip._vendor.tomli._parser import TOMLDecodeError, load, loads diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..a8f55ab Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-38.pyc new file mode 100644 index 0000000..426e50f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-38.pyc new file mode 100644 index 0000000..a1cf10e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/_parser.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/_parser.py new file mode 100644 index 0000000..730a746 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/_parser.py @@ -0,0 +1,703 @@ +import string +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + FrozenSet, + Iterable, + Optional, + TextIO, + Tuple, +) + +from pip._vendor.tomli._re import ( + RE_BIN, + RE_DATETIME, + RE_HEX, + RE_LOCALTIME, + RE_NUMBER, + RE_OCT, + match_to_datetime, + match_to_localtime, + match_to_number, +) + +if TYPE_CHECKING: + from re import Pattern + + +ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) + +# Neither of these sets include quotation mark or backslash. They are +# currently handled as separate cases in the parser functions. +ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t") +ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n\r") + +ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS +ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ASCII_CTRL - frozenset("\t\n") + +ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS + +TOML_WS = frozenset(" \t") +TOML_WS_AND_NEWLINE = TOML_WS | frozenset("\n") +BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + "-_") +KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'") + +BASIC_STR_ESCAPE_REPLACEMENTS = MappingProxyType( + { + "\\b": "\u0008", # backspace + "\\t": "\u0009", # tab + "\\n": "\u000A", # linefeed + "\\f": "\u000C", # form feed + "\\r": "\u000D", # carriage return + '\\"': "\u0022", # quote + "\\\\": "\u005C", # backslash + } +) + +# Type annotations +ParseFloat = Callable[[str], Any] +Key = Tuple[str, ...] +Pos = int + + +class TOMLDecodeError(ValueError): + """An error raised if a document is not valid TOML.""" + + +def load(fp: TextIO, *, parse_float: ParseFloat = float) -> Dict[str, Any]: + """Parse TOML from a file object.""" + s = fp.read() + return loads(s, parse_float=parse_float) + + +def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa: C901 + """Parse TOML from a string.""" + + # The spec allows converting "\r\n" to "\n", even in string + # literals. Let's do so to simplify parsing. + src = s.replace("\r\n", "\n") + pos = 0 + state = State() + + # Parse one statement at a time + # (typically means one line in TOML source) + while True: + # 1. Skip line leading whitespace + pos = skip_chars(src, pos, TOML_WS) + + # 2. Parse rules. Expect one of the following: + # - end of file + # - end of line + # - comment + # - key/value pair + # - append dict to list (and move to its namespace) + # - create dict (and move to its namespace) + # Skip trailing whitespace when applicable. + try: + char = src[pos] + except IndexError: + break + if char == "\n": + pos += 1 + continue + if char in KEY_INITIAL_CHARS: + pos = key_value_rule(src, pos, state, parse_float) + pos = skip_chars(src, pos, TOML_WS) + elif char == "[": + try: + second_char: Optional[str] = src[pos + 1] + except IndexError: + second_char = None + if second_char == "[": + pos = create_list_rule(src, pos, state) + else: + pos = create_dict_rule(src, pos, state) + pos = skip_chars(src, pos, TOML_WS) + elif char != "#": + raise suffixed_err(src, pos, "Invalid statement") + + # 3. Skip comment + pos = skip_comment(src, pos) + + # 4. Expect end of line or end of file + try: + char = src[pos] + except IndexError: + break + if char != "\n": + raise suffixed_err( + src, pos, "Expected newline or end of document after a statement" + ) + pos += 1 + + return state.out.dict + + +class State: + def __init__(self) -> None: + # Mutable, read-only + self.out = NestedDict() + self.flags = Flags() + + # Immutable, read and write + self.header_namespace: Key = () + + +class Flags: + """Flags that map to parsed keys/namespaces.""" + + # Marks an immutable namespace (inline array or inline table). + FROZEN = 0 + # Marks a nest that has been explicitly created and can no longer + # be opened using the "[table]" syntax. + EXPLICIT_NEST = 1 + + def __init__(self) -> None: + self._flags: Dict[str, dict] = {} + + def unset_all(self, key: Key) -> None: + cont = self._flags + for k in key[:-1]: + if k not in cont: + return + cont = cont[k]["nested"] + cont.pop(key[-1], None) + + def set_for_relative_key(self, head_key: Key, rel_key: Key, flag: int) -> None: + cont = self._flags + for k in head_key: + if k not in cont: + cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + for k in rel_key: + if k in cont: + cont[k]["flags"].add(flag) + else: + cont[k] = {"flags": {flag}, "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + + def set(self, key: Key, flag: int, *, recursive: bool) -> None: # noqa: A003 + cont = self._flags + key_parent, key_stem = key[:-1], key[-1] + for k in key_parent: + if k not in cont: + cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + if key_stem not in cont: + cont[key_stem] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont[key_stem]["recursive_flags" if recursive else "flags"].add(flag) + + def is_(self, key: Key, flag: int) -> bool: + if not key: + return False # document root has no flags + cont = self._flags + for k in key[:-1]: + if k not in cont: + return False + inner_cont = cont[k] + if flag in inner_cont["recursive_flags"]: + return True + cont = inner_cont["nested"] + key_stem = key[-1] + if key_stem in cont: + cont = cont[key_stem] + return flag in cont["flags"] or flag in cont["recursive_flags"] + return False + + +class NestedDict: + def __init__(self) -> None: + # The parsed content of the TOML document + self.dict: Dict[str, Any] = {} + + def get_or_create_nest( + self, + key: Key, + *, + access_lists: bool = True, + ) -> dict: + cont: Any = self.dict + for k in key: + if k not in cont: + cont[k] = {} + cont = cont[k] + if access_lists and isinstance(cont, list): + cont = cont[-1] + if not isinstance(cont, dict): + raise KeyError("There is no nest behind this key") + return cont + + def append_nest_to_list(self, key: Key) -> None: + cont = self.get_or_create_nest(key[:-1]) + last_key = key[-1] + if last_key in cont: + list_ = cont[last_key] + if not isinstance(list_, list): + raise KeyError("An object other than list found behind this key") + list_.append({}) + else: + cont[last_key] = [{}] + + +def skip_chars(src: str, pos: Pos, chars: Iterable[str]) -> Pos: + try: + while src[pos] in chars: + pos += 1 + except IndexError: + pass + return pos + + +def skip_until( + src: str, + pos: Pos, + expect: str, + *, + error_on: FrozenSet[str], + error_on_eof: bool, +) -> Pos: + try: + new_pos = src.index(expect, pos) + except ValueError: + new_pos = len(src) + if error_on_eof: + raise suffixed_err(src, new_pos, f'Expected "{expect!r}"') + + bad_chars = error_on.intersection(src[pos:new_pos]) + if bad_chars: + bad_char = next(iter(bad_chars)) + bad_pos = src.index(bad_char, pos) + raise suffixed_err(src, bad_pos, f'Found invalid character "{bad_char!r}"') + return new_pos + + +def skip_comment(src: str, pos: Pos) -> Pos: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char == "#": + return skip_until( + src, pos + 1, "\n", error_on=ILLEGAL_COMMENT_CHARS, error_on_eof=False + ) + return pos + + +def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos: + while True: + pos_before_skip = pos + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + pos = skip_comment(src, pos) + if pos == pos_before_skip: + return pos + + +def create_dict_rule(src: str, pos: Pos, state: State) -> Pos: + pos += 1 # Skip "[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if state.flags.is_(key, Flags.EXPLICIT_NEST) or state.flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not declare {key} twice") + state.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + state.out.get_or_create_nest(key) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + state.header_namespace = key + + if src[pos : pos + 1] != "]": + raise suffixed_err(src, pos, 'Expected "]" at the end of a table declaration') + return pos + 1 + + +def create_list_rule(src: str, pos: Pos, state: State) -> Pos: + pos += 2 # Skip "[[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if state.flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + # Free the namespace now that it points to another empty list item... + state.flags.unset_all(key) + # ...but this key precisely is still prohibited from table declaration + state.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + state.out.append_nest_to_list(key) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + state.header_namespace = key + + end_marker = src[pos : pos + 2] + if end_marker != "]]": + raise suffixed_err( + src, + pos, + f'Found "{end_marker!r}" at the end of an array declaration.' + ' Expected "]]"', + ) + return pos + 2 + + +def key_value_rule(src: str, pos: Pos, state: State, parse_float: ParseFloat) -> Pos: + pos, key, value = parse_key_value_pair(src, pos, parse_float) + key_parent, key_stem = key[:-1], key[-1] + abs_key_parent = state.header_namespace + key_parent + + if state.flags.is_(abs_key_parent, Flags.FROZEN): + raise suffixed_err( + src, pos, f"Can not mutate immutable namespace {abs_key_parent}" + ) + # Containers in the relative path can't be opened with the table syntax after this + state.flags.set_for_relative_key(state.header_namespace, key, Flags.EXPLICIT_NEST) + try: + nest = state.out.get_or_create_nest(abs_key_parent) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + if key_stem in nest: + raise suffixed_err(src, pos, "Can not overwrite a value") + # Mark inline table and array namespaces recursively immutable + if isinstance(value, (dict, list)): + abs_key = state.header_namespace + key + state.flags.set(abs_key, Flags.FROZEN, recursive=True) + nest[key_stem] = value + return pos + + +def parse_key_value_pair( + src: str, pos: Pos, parse_float: ParseFloat +) -> Tuple[Pos, Key, Any]: + pos, key = parse_key(src, pos) + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char != "=": + raise suffixed_err(src, pos, 'Expected "=" after a key in a key/value pair') + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, value = parse_value(src, pos, parse_float) + return pos, key, value + + +def parse_key(src: str, pos: Pos) -> Tuple[Pos, Key]: + pos, key_part = parse_key_part(src, pos) + key = [key_part] + pos = skip_chars(src, pos, TOML_WS) + while True: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char != ".": + return pos, tuple(key) + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, key_part = parse_key_part(src, pos) + key.append(key_part) + pos = skip_chars(src, pos, TOML_WS) + + +def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char in BARE_KEY_CHARS: + start_pos = pos + pos = skip_chars(src, pos, BARE_KEY_CHARS) + return pos, src[start_pos:pos] + if char == "'": + return parse_literal_str(src, pos) + if char == '"': + return parse_one_line_basic_str(src, pos) + raise suffixed_err(src, pos, "Invalid initial character for a key part") + + +def parse_one_line_basic_str(src: str, pos: Pos) -> Tuple[Pos, str]: + pos += 1 + return parse_basic_str(src, pos, multiline=False) + + +def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list]: + pos += 1 + array: list = [] + + pos = skip_comments_and_array_ws(src, pos) + if src[pos : pos + 1] == "]": + return pos + 1, array + while True: + pos, val = parse_value(src, pos, parse_float) + array.append(val) + pos = skip_comments_and_array_ws(src, pos) + + c = src[pos : pos + 1] + if c == "]": + return pos + 1, array + if c != ",": + raise suffixed_err(src, pos, "Unclosed array") + pos += 1 + + pos = skip_comments_and_array_ws(src, pos) + if src[pos : pos + 1] == "]": + return pos + 1, array + + +def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, dict]: + pos += 1 + nested_dict = NestedDict() + flags = Flags() + + pos = skip_chars(src, pos, TOML_WS) + if src[pos : pos + 1] == "}": + return pos + 1, nested_dict.dict + while True: + pos, key, value = parse_key_value_pair(src, pos, parse_float) + key_parent, key_stem = key[:-1], key[-1] + if flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + try: + nest = nested_dict.get_or_create_nest(key_parent, access_lists=False) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + if key_stem in nest: + raise suffixed_err(src, pos, f'Duplicate inline table key "{key_stem}"') + nest[key_stem] = value + pos = skip_chars(src, pos, TOML_WS) + c = src[pos : pos + 1] + if c == "}": + return pos + 1, nested_dict.dict + if c != ",": + raise suffixed_err(src, pos, "Unclosed inline table") + if isinstance(value, (dict, list)): + flags.set(key, Flags.FROZEN, recursive=True) + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + + +def parse_basic_str_escape( + src: str, pos: Pos, *, multiline: bool = False +) -> Tuple[Pos, str]: + escape_id = src[pos : pos + 2] + pos += 2 + if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}: + # Skip whitespace until next non-whitespace character or end of + # the doc. Error if non-whitespace is found before newline. + if escape_id != "\\\n": + pos = skip_chars(src, pos, TOML_WS) + char = src[pos : pos + 1] + if not char: + return pos, "" + if char != "\n": + raise suffixed_err(src, pos, 'Unescaped "\\" in a string') + pos += 1 + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + return pos, "" + if escape_id == "\\u": + return parse_hex_char(src, pos, 4) + if escape_id == "\\U": + return parse_hex_char(src, pos, 8) + try: + return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id] + except KeyError: + if len(escape_id) != 2: + raise suffixed_err(src, pos, "Unterminated string") + raise suffixed_err(src, pos, 'Unescaped "\\" in a string') + + +def parse_basic_str_escape_multiline(src: str, pos: Pos) -> Tuple[Pos, str]: + return parse_basic_str_escape(src, pos, multiline=True) + + +def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]: + hex_str = src[pos : pos + hex_len] + if len(hex_str) != hex_len or any(c not in string.hexdigits for c in hex_str): + raise suffixed_err(src, pos, "Invalid hex value") + pos += hex_len + hex_int = int(hex_str, 16) + if not is_unicode_scalar_value(hex_int): + raise suffixed_err(src, pos, "Escaped character is not a Unicode scalar value") + return pos, chr(hex_int) + + +def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]: + pos += 1 # Skip starting apostrophe + start_pos = pos + pos = skip_until( + src, pos, "'", error_on=ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True + ) + return pos + 1, src[start_pos:pos] # Skip ending apostrophe + + +def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str]: + pos += 3 + if src[pos : pos + 1] == "\n": + pos += 1 + + if literal: + delim = "'" + end_pos = skip_until( + src, + pos, + "'''", + error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS, + error_on_eof=True, + ) + result = src[pos:end_pos] + pos = end_pos + 3 + else: + delim = '"' + pos, result = parse_basic_str(src, pos, multiline=True) + + # Add at maximum two extra apostrophes/quotes if the end sequence + # is 4 or 5 chars long instead of just 3. + if src[pos : pos + 1] != delim: + return pos, result + pos += 1 + if src[pos : pos + 1] != delim: + return pos, result + delim + pos += 1 + return pos, result + (delim * 2) + + +def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]: + if multiline: + error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape_multiline + else: + error_on = ILLEGAL_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape + result = "" + start_pos = pos + while True: + try: + char = src[pos] + except IndexError: + raise suffixed_err(src, pos, "Unterminated string") + if char == '"': + if not multiline: + return pos + 1, result + src[start_pos:pos] + if src[pos + 1 : pos + 3] == '""': + return pos + 3, result + src[start_pos:pos] + pos += 1 + continue + if char == "\\": + result += src[start_pos:pos] + pos, parsed_escape = parse_escapes(src, pos) + result += parsed_escape + start_pos = pos + continue + if char in error_on: + raise suffixed_err(src, pos, f'Illegal character "{char!r}"') + pos += 1 + + +def parse_regex(src: str, pos: Pos, regex: "Pattern") -> Tuple[Pos, str]: + match = regex.match(src, pos) + if not match: + raise suffixed_err(src, pos, "Unexpected sequence") + return match.end(), match.group() + + +def parse_value( # noqa: C901 + src: str, pos: Pos, parse_float: ParseFloat +) -> Tuple[Pos, Any]: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + + # Basic strings + if char == '"': + if src[pos + 1 : pos + 3] == '""': + return parse_multiline_str(src, pos, literal=False) + return parse_one_line_basic_str(src, pos) + + # Literal strings + if char == "'": + if src[pos + 1 : pos + 3] == "''": + return parse_multiline_str(src, pos, literal=True) + return parse_literal_str(src, pos) + + # Booleans + if char == "t": + if src[pos + 1 : pos + 4] == "rue": + return pos + 4, True + if char == "f": + if src[pos + 1 : pos + 5] == "alse": + return pos + 5, False + + # Dates and times + datetime_match = RE_DATETIME.match(src, pos) + if datetime_match: + try: + datetime_obj = match_to_datetime(datetime_match) + except ValueError: + raise suffixed_err(src, pos, "Invalid date or datetime") + return datetime_match.end(), datetime_obj + localtime_match = RE_LOCALTIME.match(src, pos) + if localtime_match: + return localtime_match.end(), match_to_localtime(localtime_match) + + # Non-decimal integers + if char == "0": + second_char = src[pos + 1 : pos + 2] + if second_char == "x": + pos, hex_str = parse_regex(src, pos + 2, RE_HEX) + return pos, int(hex_str, 16) + if second_char == "o": + pos, oct_str = parse_regex(src, pos + 2, RE_OCT) + return pos, int(oct_str, 8) + if second_char == "b": + pos, bin_str = parse_regex(src, pos + 2, RE_BIN) + return pos, int(bin_str, 2) + + # Decimal integers and "normal" floats. + # The regex will greedily match any type starting with a decimal + # char, so needs to be located after handling of non-decimal ints, + # and dates and times. + number_match = RE_NUMBER.match(src, pos) + if number_match: + return number_match.end(), match_to_number(number_match, parse_float) + + # Arrays + if char == "[": + return parse_array(src, pos, parse_float) + + # Inline tables + if char == "{": + return parse_inline_table(src, pos, parse_float) + + # Special floats + first_three = src[pos : pos + 3] + if first_three in {"inf", "nan"}: + return pos + 3, parse_float(first_three) + first_four = src[pos : pos + 4] + if first_four in {"-inf", "+inf", "-nan", "+nan"}: + return pos + 4, parse_float(first_four) + + raise suffixed_err(src, pos, "Invalid value") + + +def suffixed_err(src: str, pos: Pos, msg: str) -> TOMLDecodeError: + """Return a `TOMLDecodeError` where error message is suffixed with + coordinates in source.""" + + def coord_repr(src: str, pos: Pos) -> str: + if pos >= len(src): + return "end of document" + line = src.count("\n", 0, pos) + 1 + if line == 1: + column = pos + 1 + else: + column = pos - src.rindex("\n", 0, pos) + return f"line {line}, column {column}" + + return TOMLDecodeError(f"{msg} (at {coord_repr(src, pos)})") + + +def is_unicode_scalar_value(codepoint: int) -> bool: + return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/_re.py b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/_re.py new file mode 100644 index 0000000..3883fdd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/tomli/_re.py @@ -0,0 +1,83 @@ +from datetime import date, datetime, time, timedelta, timezone, tzinfo +import re +from typing import TYPE_CHECKING, Any, Optional, Union + +if TYPE_CHECKING: + from re import Match + + from pip._vendor.tomli._parser import ParseFloat + +# E.g. +# - 00:32:00.999999 +# - 00:32:00 +_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?" + +RE_HEX = re.compile(r"[0-9A-Fa-f](?:_?[0-9A-Fa-f])*") +RE_BIN = re.compile(r"[01](?:_?[01])*") +RE_OCT = re.compile(r"[0-7](?:_?[0-7])*") +RE_NUMBER = re.compile( + r"[+-]?(?:0|[1-9](?:_?[0-9])*)" # integer + + r"(?:\.[0-9](?:_?[0-9])*)?" # optional fractional part + + r"(?:[eE][+-]?[0-9](?:_?[0-9])*)?" # optional exponent part +) +RE_LOCALTIME = re.compile(_TIME_RE_STR) +RE_DATETIME = re.compile( + r"([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[01])" # date, e.g. 1988-10-27 + + r"(?:" + + r"[T ]" + + _TIME_RE_STR + + r"(?:(Z)|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))?" # time offset + + r")?" +) + + +def match_to_datetime(match: "Match") -> Union[datetime, date]: + """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`. + + Raises ValueError if the match does not correspond to a valid date + or datetime. + """ + ( + year_str, + month_str, + day_str, + hour_str, + minute_str, + sec_str, + micros_str, + zulu_time, + offset_dir_str, + offset_hour_str, + offset_minute_str, + ) = match.groups() + year, month, day = int(year_str), int(month_str), int(day_str) + if hour_str is None: + return date(year, month, day) + hour, minute, sec = int(hour_str), int(minute_str), int(sec_str) + micros = int(micros_str[1:].ljust(6, "0")[:6]) if micros_str else 0 + if offset_dir_str: + offset_dir = 1 if offset_dir_str == "+" else -1 + tz: Optional[tzinfo] = timezone( + timedelta( + hours=offset_dir * int(offset_hour_str), + minutes=offset_dir * int(offset_minute_str), + ) + ) + elif zulu_time: + tz = timezone.utc + else: # local date-time + tz = None + return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz) + + +def match_to_localtime(match: "Match") -> time: + hour_str, minute_str, sec_str, micros_str = match.groups() + micros = int(micros_str[1:].ljust(6, "0")[:6]) if micros_str else 0 + return time(int(hour_str), int(minute_str), int(sec_str), micros) + + +def match_to_number(match: "Match", parse_float: "ParseFloat") -> Any: + match_str = match.group() + if "." in match_str or "e" in match_str or "E" in match_str: + return parse_float(match_str) + return int(match_str) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/typing_extensions.py b/.venv/lib/python3.8/site-packages/pip/_vendor/typing_extensions.py new file mode 100644 index 0000000..9f1c7aa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/typing_extensions.py @@ -0,0 +1,2296 @@ +import abc +import collections +import collections.abc +import operator +import sys +import typing + +# After PEP 560, internal typing API was substantially reworked. +# This is especially important for Protocol class which uses internal APIs +# quite extensively. +PEP_560 = sys.version_info[:3] >= (3, 7, 0) + +if PEP_560: + GenericMeta = type +else: + # 3.6 + from typing import GenericMeta, _type_vars # noqa + +# The two functions below are copies of typing internal helpers. +# They are needed by _ProtocolMeta + + +def _no_slots_copy(dct): + dict_copy = dict(dct) + if '__slots__' in dict_copy: + for slot in dict_copy['__slots__']: + dict_copy.pop(slot, None) + return dict_copy + + +def _check_generic(cls, parameters): + if not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + alen = len(parameters) + elen = len(cls.__parameters__) + if alen != elen: + raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" + f" actual {alen}, expected {elen}") + + +# Please keep __all__ alphabetized within each category. +__all__ = [ + # Super-special typing primitives. + 'ClassVar', + 'Concatenate', + 'Final', + 'ParamSpec', + 'Self', + 'Type', + + # ABCs (from collections.abc). + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'ChainMap', + + # Concrete collection types. + 'ContextManager', + 'Counter', + 'Deque', + 'DefaultDict', + 'OrderedDict', + 'TypedDict', + + # Structural checks, a.k.a. protocols. + 'SupportsIndex', + + # One-off things. + 'Annotated', + 'final', + 'IntVar', + 'Literal', + 'NewType', + 'overload', + 'Protocol', + 'runtime', + 'runtime_checkable', + 'Text', + 'TypeAlias', + 'TypeGuard', + 'TYPE_CHECKING', +] + +if PEP_560: + __all__.extend(["get_args", "get_origin", "get_type_hints"]) + +# 3.6.2+ +if hasattr(typing, 'NoReturn'): + NoReturn = typing.NoReturn +# 3.6.0-3.6.1 +else: + class _NoReturn(typing._FinalTypingBase, _root=True): + """Special type indicating functions that never return. + Example:: + + from typing import NoReturn + + def stop() -> NoReturn: + raise Exception('no way') + + This type is invalid in other positions, e.g., ``List[NoReturn]`` + will fail in static type checkers. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("NoReturn cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("NoReturn cannot be used with issubclass().") + + NoReturn = _NoReturn(_root=True) + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = typing.TypeVar('T') # Any type. +KT = typing.TypeVar('KT') # Key type. +VT = typing.TypeVar('VT') # Value type. +T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. +T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. + +ClassVar = typing.ClassVar + +# On older versions of typing there is an internal class named "Final". +# 3.8+ +if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): + Final = typing.Final +# 3.7 +elif sys.version_info[:2] >= (3, 7): + class _FinalForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + + Final = _FinalForm('Final', + doc="""A special typing construct to indicate that a name + cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties.""") +# 3.6 +else: + class _Final(typing._FinalTypingBase, _root=True): + """A special typing construct to indicate that a name + cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties. + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + f'{cls.__name__[1:]} accepts only single type.'), + _root=True) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += f'[{typing._type_repr(self.__type__)}]' + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _Final): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + Final = _Final(_root=True) + + +# 3.8+ +if hasattr(typing, 'final'): + final = typing.final +# 3.6-3.7 +else: + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. + """ + return f + + +def IntVar(name): + return typing.TypeVar(name) + + +# 3.8+: +if hasattr(typing, 'Literal'): + Literal = typing.Literal +# 3.7: +elif sys.version_info[:2] >= (3, 7): + class _LiteralForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return typing._GenericAlias(self, parameters) + + Literal = _LiteralForm('Literal', + doc="""A type that can be used to indicate to type checkers + that the corresponding value has a value literally equivalent + to the provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to + the value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime + checking verifying that the parameter is actually a value + instead of a type.""") +# 3.6: +else: + class _Literal(typing._FinalTypingBase, _root=True): + """A type that can be used to indicate to type checkers that the + corresponding value has a value literally equivalent to the + provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to the + value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime checking + verifying that the parameter is actually a value instead of a type. + """ + + __slots__ = ('__values__',) + + def __init__(self, values=None, **kwds): + self.__values__ = values + + def __getitem__(self, values): + cls = type(self) + if self.__values__ is None: + if not isinstance(values, tuple): + values = (values,) + return cls(values, _root=True) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') + + def _eval_type(self, globalns, localns): + return self + + def __repr__(self): + r = super().__repr__() + if self.__values__ is not None: + r += f'[{", ".join(map(typing._type_repr, self.__values__))}]' + return r + + def __hash__(self): + return hash((type(self).__name__, self.__values__)) + + def __eq__(self, other): + if not isinstance(other, _Literal): + return NotImplemented + if self.__values__ is not None: + return self.__values__ == other.__values__ + return self is other + + Literal = _Literal(_root=True) + + +_overload_dummy = typing._overload_dummy # noqa +overload = typing.overload + + +# This is not a real generic class. Don't use outside annotations. +Type = typing.Type + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. + + +class _ExtensionsGenericMeta(GenericMeta): + def __subclasscheck__(self, subclass): + """This mimics a more modern GenericMeta.__subclasscheck__() logic + (that does not have problems with recursion) to work around interactions + between collections, typing, and typing_extensions on older + versions of Python, see https://github.com/python/typing/issues/501. + """ + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if not self.__extra__: + return super().__subclasscheck__(subclass) + res = self.__extra__.__subclasshook__(subclass) + if res is not NotImplemented: + return res + if self.__extra__ in subclass.__mro__: + return True + for scls in self.__extra__.__subclasses__(): + if isinstance(scls, GenericMeta): + continue + if issubclass(subclass, scls): + return True + return False + + +Awaitable = typing.Awaitable +Coroutine = typing.Coroutine +AsyncIterable = typing.AsyncIterable +AsyncIterator = typing.AsyncIterator + +# 3.6.1+ +if hasattr(typing, 'Deque'): + Deque = typing.Deque +# 3.6.0 +else: + class Deque(collections.deque, typing.MutableSequence[T], + metaclass=_ExtensionsGenericMeta, + extra=collections.deque): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Deque: + return collections.deque(*args, **kwds) + return typing._generic_new(collections.deque, cls, *args, **kwds) + +ContextManager = typing.ContextManager +# 3.6.2+ +if hasattr(typing, 'AsyncContextManager'): + AsyncContextManager = typing.AsyncContextManager +# 3.6.0-3.6.1 +else: + from _collections_abc import _check_methods as _check_methods_in_mro # noqa + + class AsyncContextManager(typing.Generic[T_co]): + __slots__ = () + + async def __aenter__(self): + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncContextManager: + return _check_methods_in_mro(C, "__aenter__", "__aexit__") + return NotImplemented + +DefaultDict = typing.DefaultDict + +# 3.7.2+ +if hasattr(typing, 'OrderedDict'): + OrderedDict = typing.OrderedDict +# 3.7.0-3.7.2 +elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) +# 3.6 +else: + class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.OrderedDict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is OrderedDict: + return collections.OrderedDict(*args, **kwds) + return typing._generic_new(collections.OrderedDict, cls, *args, **kwds) + +# 3.6.2+ +if hasattr(typing, 'Counter'): + Counter = typing.Counter +# 3.6.0-3.6.1 +else: + class Counter(collections.Counter, + typing.Dict[T, int], + metaclass=_ExtensionsGenericMeta, extra=collections.Counter): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Counter: + return collections.Counter(*args, **kwds) + return typing._generic_new(collections.Counter, cls, *args, **kwds) + +# 3.6.1+ +if hasattr(typing, 'ChainMap'): + ChainMap = typing.ChainMap +elif hasattr(collections, 'ChainMap'): + class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.ChainMap): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is ChainMap: + return collections.ChainMap(*args, **kwds) + return typing._generic_new(collections.ChainMap, cls, *args, **kwds) + +# 3.6.1+ +if hasattr(typing, 'AsyncGenerator'): + AsyncGenerator = typing.AsyncGenerator +# 3.6.0 +else: + class AsyncGenerator(AsyncIterator[T_co], typing.Generic[T_co, T_contra], + metaclass=_ExtensionsGenericMeta, + extra=collections.abc.AsyncGenerator): + __slots__ = () + +NewType = typing.NewType +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING + + +def _gorg(cls): + """This function exists for compatibility with old typing versions.""" + assert isinstance(cls, GenericMeta) + if hasattr(cls, '_gorg'): + return cls._gorg + while cls.__origin__ is not None: + cls = cls.__origin__ + return cls + + +_PROTO_WHITELIST = ['Callable', 'Awaitable', + 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', + 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', + 'ContextManager', 'AsyncContextManager'] + + +def _get_protocol_attrs(cls): + attrs = set() + for base in cls.__mro__[:-1]: # without object + if base.__name__ in ('Protocol', 'Generic'): + continue + annotations = getattr(base, '__annotations__', {}) + for attr in list(base.__dict__.keys()) + list(annotations.keys()): + if (not attr.startswith('_abc_') and attr not in ( + '__abstractmethods__', '__annotations__', '__weakref__', + '_is_protocol', '_is_runtime_protocol', '__dict__', + '__args__', '__slots__', + '__next_in_mro__', '__parameters__', '__origin__', + '__orig_bases__', '__extra__', '__tree_hash__', + '__doc__', '__subclasshook__', '__init__', '__new__', + '__module__', '_MutableMapping__marker', '_gorg')): + attrs.add(attr) + return attrs + + +def _is_callable_members_only(cls): + return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls)) + + +# 3.8+ +if hasattr(typing, 'Protocol'): + Protocol = typing.Protocol +# 3.7 +elif PEP_560: + from typing import _collect_type_vars # noqa + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + class _ProtocolMeta(abc.ABCMeta): + # This metaclass is a bit unfortunate and exists only because of the lack + # of __instancehook__. + def __instancecheck__(cls, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if ((not getattr(cls, '_is_protocol', False) or + _is_callable_members_only(cls)) and + issubclass(instance.__class__, cls)): + return True + if cls._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(cls, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(cls)): + return True + return super().__instancecheck__(instance) + + class Protocol(metaclass=_ProtocolMeta): + # There is quite a lot of overlapping code with typing.Generic. + # Unfortunately it is hard to avoid this while these live in two different + # modules. The duplicated code will be removed when Protocol is moved to typing. + """Base class for protocol classes. Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing_extensions.runtime act as simple-minded runtime protocol that checks + only the presence of given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self) -> T: + ... + """ + __slots__ = () + _is_protocol = True + + def __new__(cls, *args, **kwds): + if cls is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can only be used as a base class") + return super().__new__(cls) + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple): + params = (params,) + if not params and cls is not typing.Tuple: + raise TypeError( + f"Parameter list to {cls.__qualname__}[...] cannot be empty") + msg = "Parameters to generic types must be types." + params = tuple(typing._type_check(p, msg) for p in params) # noqa + if cls is Protocol: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, typing.TypeVar) for p in params): + i = 0 + while isinstance(params[i], typing.TypeVar): + i += 1 + raise TypeError( + "Parameters to Protocol[...] must all be type variables." + f" Parameter {i + 1} is {params[i]}") + if len(set(params)) != len(params): + raise TypeError( + "Parameters to Protocol[...] must all be unique") + else: + # Subscripting a regular Generic subclass. + _check_generic(cls, params) + return typing._GenericAlias(cls, params) + + def __init_subclass__(cls, *args, **kwargs): + tvars = [] + if '__orig_bases__' in cls.__dict__: + error = typing.Generic in cls.__orig_bases__ + else: + error = typing.Generic in cls.__bases__ + if error: + raise TypeError("Cannot inherit from plain Generic") + if '__orig_bases__' in cls.__dict__: + tvars = _collect_type_vars(cls.__orig_bases__) + # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...] and/or Protocol[...]. + gvars = None + for base in cls.__orig_bases__: + if (isinstance(base, typing._GenericAlias) and + base.__origin__ in (typing.Generic, Protocol)): + # for error messages + the_base = base.__origin__.__name__ + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...]" + " and/or Protocol[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) + s_args = ', '.join(str(g) for g in gvars) + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {the_base}[{s_args}]") + tvars = gvars + cls.__parameters__ = tuple(tvars) + + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + def _proto_hook(other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not getattr(cls, '_is_runtime_protocol', False): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if not _is_callable_members_only(cls): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + if not isinstance(other, type): + # Same error as for issubclass(1, int) + raise TypeError('issubclass() arg 1 must be a class') + for attr in _get_protocol_attrs(cls): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + annotations = getattr(base, '__annotations__', {}) + if (isinstance(annotations, typing.Mapping) and + attr in annotations and + isinstance(other, _ProtocolMeta) and + other._is_protocol): + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # We have nothing more to do for non-protocols. + if not cls._is_protocol: + return + + # Check consistency of bases. + for base in cls.__bases__: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, _ProtocolMeta) and base._is_protocol): + raise TypeError('Protocols can only inherit from other' + f' protocols, got {repr(base)}') + cls.__init__ = _no_init +# 3.6 +else: + from typing import _next_in_mro, _type_check # noqa + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + class _ProtocolMeta(GenericMeta): + """Internal metaclass for Protocol. + + This exists so Protocol classes can be generic without deriving + from Generic. + """ + def __new__(cls, name, bases, namespace, + tvars=None, args=None, origin=None, extra=None, orig_bases=None): + # This is just a version copied from GenericMeta.__new__ that + # includes "Protocol" special treatment. (Comments removed for brevity.) + assert extra is None # Protocols should not have extra + if tvars is not None: + assert origin is not None + assert all(isinstance(t, typing.TypeVar) for t in tvars), tvars + else: + tvars = _type_vars(bases) + gvars = None + for base in bases: + if base is typing.Generic: + raise TypeError("Cannot inherit from plain Generic") + if (isinstance(base, GenericMeta) and + base.__origin__ in (typing.Generic, Protocol)): + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...] or" + " Protocol[...] multiple times.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + s_vars = ", ".join(str(t) for t in tvars if t not in gvarset) + s_args = ", ".join(str(g) for g in gvars) + cls_name = "Generic" if any(b.__origin__ is typing.Generic + for b in bases) else "Protocol" + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {cls_name}[{s_args}]") + tvars = gvars + + initial_bases = bases + if (extra is not None and type(extra) is abc.ABCMeta and + extra not in bases): + bases = (extra,) + bases + bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b + for b in bases) + if any(isinstance(b, GenericMeta) and b is not typing.Generic for b in bases): + bases = tuple(b for b in bases if b is not typing.Generic) + namespace.update({'__origin__': origin, '__extra__': extra}) + self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, + _root=True) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else + _gorg(origin)) + self.__parameters__ = tvars + self.__args__ = tuple(... if a is typing._TypingEllipsis else + () if a is typing._TypingEmpty else + a for a in args) if args else None + self.__next_in_mro__ = _next_in_mro(self) + if orig_bases is None: + self.__orig_bases__ = initial_bases + elif origin is not None: + self._abc_registry = origin._abc_registry + self._abc_cache = origin._abc_cache + if hasattr(self, '_subs_tree'): + self.__tree_hash__ = (hash(self._subs_tree()) if origin else + super(GenericMeta, self).__hash__()) + return self + + def __init__(cls, *args, **kwargs): + super().__init__(*args, **kwargs) + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol or + isinstance(b, _ProtocolMeta) and + b.__origin__ is Protocol + for b in cls.__bases__) + if cls._is_protocol: + for base in cls.__mro__[1:]: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, typing.TypingMeta) and base._is_protocol or + isinstance(base, GenericMeta) and + base.__origin__ is typing.Generic): + raise TypeError(f'Protocols can only inherit from other' + f' protocols, got {repr(base)}') + + cls.__init__ = _no_init + + def _proto_hook(other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not isinstance(other, type): + # Same error as for issubclass(1, int) + raise TypeError('issubclass() arg 1 must be a class') + for attr in _get_protocol_attrs(cls): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + annotations = getattr(base, '__annotations__', {}) + if (isinstance(annotations, typing.Mapping) and + attr in annotations and + isinstance(other, _ProtocolMeta) and + other._is_protocol): + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + def __instancecheck__(self, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if ((not getattr(self, '_is_protocol', False) or + _is_callable_members_only(self)) and + issubclass(instance.__class__, self)): + return True + if self._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(self, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(self)): + return True + return super(GenericMeta, self).__instancecheck__(instance) + + def __subclasscheck__(self, cls): + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if (self.__dict__.get('_is_protocol', None) and + not self.__dict__.get('_is_runtime_protocol', None)): + if sys._getframe(1).f_globals['__name__'] in ['abc', + 'functools', + 'typing']: + return False + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if (self.__dict__.get('_is_runtime_protocol', None) and + not _is_callable_members_only(self)): + if sys._getframe(1).f_globals['__name__'] in ['abc', + 'functools', + 'typing']: + return super(GenericMeta, self).__subclasscheck__(cls) + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + return super(GenericMeta, self).__subclasscheck__(cls) + + @typing._tp_cache + def __getitem__(self, params): + # We also need to copy this from GenericMeta.__getitem__ to get + # special treatment of "Protocol". (Comments removed for brevity.) + if not isinstance(params, tuple): + params = (params,) + if not params and _gorg(self) is not typing.Tuple: + raise TypeError( + f"Parameter list to {self.__qualname__}[...] cannot be empty") + msg = "Parameters to generic types must be types." + params = tuple(_type_check(p, msg) for p in params) + if self in (typing.Generic, Protocol): + if not all(isinstance(p, typing.TypeVar) for p in params): + raise TypeError( + f"Parameters to {repr(self)}[...] must all be type variables") + if len(set(params)) != len(params): + raise TypeError( + f"Parameters to {repr(self)}[...] must all be unique") + tvars = params + args = params + elif self in (typing.Tuple, typing.Callable): + tvars = _type_vars(params) + args = params + elif self.__origin__ in (typing.Generic, Protocol): + raise TypeError(f"Cannot subscript already-subscripted {repr(self)}") + else: + _check_generic(self, params) + tvars = _type_vars(params) + args = params + + prepend = (self,) if self.__origin__ is None else () + return self.__class__(self.__name__, + prepend + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=tvars, + args=args, + origin=self, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + class Protocol(metaclass=_ProtocolMeta): + """Base class for protocol classes. Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing_extensions.runtime act as simple-minded runtime protocol that checks + only the presence of given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self) -> T: + ... + """ + __slots__ = () + _is_protocol = True + + def __new__(cls, *args, **kwds): + if _gorg(cls) is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can be used only as a base class") + return typing._generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +# 3.8+ +if hasattr(typing, 'runtime_checkable'): + runtime_checkable = typing.runtime_checkable +# 3.6-3.7 +else: + def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol, so that it + can be used with isinstance() and issubclass(). Raise TypeError + if applied to a non-protocol class. + + This allows a simple-minded structural check very similar to the + one-offs in collections.abc such as Hashable. + """ + if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: + raise TypeError('@runtime_checkable can be only applied to protocol classes,' + f' got {cls!r}') + cls._is_runtime_protocol = True + return cls + + +# Exists for backwards compatibility. +runtime = runtime_checkable + + +# 3.8+ +if hasattr(typing, 'SupportsIndex'): + SupportsIndex = typing.SupportsIndex +# 3.6-3.7 +else: + @runtime_checkable + class SupportsIndex(Protocol): + __slots__ = () + + @abc.abstractmethod + def __index__(self) -> int: + pass + + +if sys.version_info >= (3, 9, 2): + # The standard library TypedDict in Python 3.8 does not store runtime information + # about which (if any) keys are optional. See https://bugs.python.org/issue38834 + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 + TypedDict = typing.TypedDict +else: + def _check_fails(cls, other): + try: + if sys._getframe(1).f_globals['__name__'] not in ['abc', + 'functools', + 'typing']: + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + except (AttributeError, ValueError): + pass + return False + + def _dict_new(*args, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + return dict(*args, **kwargs) + + _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /, **kwargs)' + + def _typeddict_new(*args, total=True, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + if args: + typename, args = args[0], args[1:] # allow the "_typename" keyword be passed + elif '_typename' in kwargs: + typename = kwargs.pop('_typename') + import warnings + warnings.warn("Passing '_typename' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + raise TypeError("TypedDict.__new__() missing 1 required positional " + "argument: '_typename'") + if args: + try: + fields, = args # allow the "_fields" keyword be passed + except ValueError: + raise TypeError('TypedDict.__new__() takes from 2 to 3 ' + f'positional arguments but {len(args) + 2} ' + 'were given') + elif '_fields' in kwargs and len(kwargs) == 1: + fields = kwargs.pop('_fields') + import warnings + warnings.warn("Passing '_fields' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + fields = None + + if fields is None: + fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + + ns = {'__annotations__': dict(fields)} + try: + # Setting correct module is necessary to make typed dict classes pickleable. + ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + + return _TypedDictMeta(typename, (), ns, total=total) + + _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,' + ' /, *, total=True, **kwargs)') + + class _TypedDictMeta(type): + def __init__(cls, name, bases, ns, total=True): + super().__init__(name, bases, ns) + + def __new__(cls, name, bases, ns, total=True): + # Create new typed dict class object. + # This method is called directly when TypedDict is subclassed, + # or via _typeddict_new when TypedDict is instantiated. This way + # TypedDict supports all three syntaxes described in its docstring. + # Subclasses and instances of TypedDict return actual dictionaries + # via _dict_new. + ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new + tp_dict = super().__new__(cls, name, (dict,), ns) + + annotations = {} + own_annotations = ns.get('__annotations__', {}) + own_annotation_keys = set(own_annotations.keys()) + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + own_annotations = { + n: typing._type_check(tp, msg) for n, tp in own_annotations.items() + } + required_keys = set() + optional_keys = set() + + for base in bases: + annotations.update(base.__dict__.get('__annotations__', {})) + required_keys.update(base.__dict__.get('__required_keys__', ())) + optional_keys.update(base.__dict__.get('__optional_keys__', ())) + + annotations.update(own_annotations) + if total: + required_keys.update(own_annotation_keys) + else: + optional_keys.update(own_annotation_keys) + + tp_dict.__annotations__ = annotations + tp_dict.__required_keys__ = frozenset(required_keys) + tp_dict.__optional_keys__ = frozenset(optional_keys) + if not hasattr(tp_dict, '__total__'): + tp_dict.__total__ = total + return tp_dict + + __instancecheck__ = __subclasscheck__ = _check_fails + + TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) + TypedDict.__module__ = __name__ + TypedDict.__doc__ = \ + """A simple typed name space. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type that expects all of its + instances to have a certain set of keys, with each key + associated with a value of a consistent type. This expectation + is not checked at runtime but is only enforced by type checkers. + Usage:: + + class Point2D(TypedDict): + x: int + y: int + label: str + + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + + The type info can be accessed via the Point2D.__annotations__ dict, and + the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. + TypedDict supports two additional equivalent forms:: + + Point2D = TypedDict('Point2D', x=int, y=int, label=str) + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + The class syntax is only supported in Python 3.6+, while two other + syntax forms work for Python 2.7 and 3.2+ + """ + + +# Python 3.9+ has PEP 593 (Annotated and modified get_type_hints) +if hasattr(typing, 'Annotated'): + Annotated = typing.Annotated + get_type_hints = typing.get_type_hints + # Not exported and not a public API, but needed for get_origin() and get_args() + # to work. + _AnnotatedAlias = typing._AnnotatedAlias +# 3.7-3.8 +elif PEP_560: + class _AnnotatedAlias(typing._GenericAlias, _root=True): + """Runtime representation of an annotated type. + + At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't' + with extra annotations. The alias behaves like a normal typing alias, + instantiating is the same as instantiating the underlying type, binding + it to types is also the same. + """ + def __init__(self, origin, metadata): + if isinstance(origin, _AnnotatedAlias): + metadata = origin.__metadata__ + metadata + origin = origin.__origin__ + super().__init__(origin, origin) + self.__metadata__ = metadata + + def copy_with(self, params): + assert len(params) == 1 + new_type = params[0] + return _AnnotatedAlias(new_type, self.__metadata__) + + def __repr__(self): + return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, " + f"{', '.join(repr(a) for a in self.__metadata__)}]") + + def __reduce__(self): + return operator.getitem, ( + Annotated, (self.__origin__,) + self.__metadata__ + ) + + def __eq__(self, other): + if not isinstance(other, _AnnotatedAlias): + return NotImplemented + if self.__origin__ != other.__origin__: + return False + return self.__metadata__ == other.__metadata__ + + def __hash__(self): + return hash((self.__origin__, self.__metadata__)) + + class Annotated: + """Add context specific metadata to a type. + + Example: Annotated[int, runtime_check.Unsigned] indicates to the + hypothetical runtime_check module that this type is an unsigned int. + Every other consumer of this type can ignore this metadata and treat + this type as int. + + The first argument to Annotated must be a valid type (and will be in + the __origin__ field), the remaining arguments are kept as a tuple in + the __extra__ field. + + Details: + + - It's an error to call `Annotated` with less than two arguments. + - Nested Annotated are flattened:: + + Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] + + - Instantiating an annotated type is equivalent to instantiating the + underlying type:: + + Annotated[C, Ann1](5) == C(5) + + - Annotated can be used as a generic type alias:: + + Optimized = Annotated[T, runtime.Optimize()] + Optimized[int] == Annotated[int, runtime.Optimize()] + + OptimizedList = Annotated[List[T], runtime.Optimize()] + OptimizedList[int] == Annotated[List[int], runtime.Optimize()] + """ + + __slots__ = () + + def __new__(cls, *args, **kwargs): + raise TypeError("Type Annotated cannot be instantiated.") + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple) or len(params) < 2: + raise TypeError("Annotated[...] should be used " + "with at least two arguments (a type and an " + "annotation).") + msg = "Annotated[t, ...]: t must be a type." + origin = typing._type_check(params[0], msg) + metadata = tuple(params[1:]) + return _AnnotatedAlias(origin, metadata) + + def __init_subclass__(cls, *args, **kwargs): + raise TypeError( + f"Cannot subclass {cls.__module__}.Annotated" + ) + + def _strip_annotations(t): + """Strips the annotations from a given type. + """ + if isinstance(t, _AnnotatedAlias): + return _strip_annotations(t.__origin__) + if isinstance(t, typing._GenericAlias): + stripped_args = tuple(_strip_annotations(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + res = t.copy_with(stripped_args) + res._special = t._special + return res + return t + + def get_type_hints(obj, globalns=None, localns=None, include_extras=False): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]' with 'T' (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) + if include_extras: + return hint + return {k: _strip_annotations(t) for k, t in hint.items()} +# 3.6 +else: + + def _is_dunder(name): + """Returns True if name is a __dunder_variable_name__.""" + return len(name) > 4 and name.startswith('__') and name.endswith('__') + + # Prior to Python 3.7 types did not have `copy_with`. A lot of the equality + # checks, argument expansion etc. are done on the _subs_tre. As a result we + # can't provide a get_type_hints function that strips out annotations. + + class AnnotatedMeta(typing.GenericMeta): + """Metaclass for Annotated""" + + def __new__(cls, name, bases, namespace, **kwargs): + if any(b is not object for b in bases): + raise TypeError("Cannot subclass " + str(Annotated)) + return super().__new__(cls, name, bases, namespace, **kwargs) + + @property + def __metadata__(self): + return self._subs_tree()[2] + + def _tree_repr(self, tree): + cls, origin, metadata = tree + if not isinstance(origin, tuple): + tp_repr = typing._type_repr(origin) + else: + tp_repr = origin[0]._tree_repr(origin) + metadata_reprs = ", ".join(repr(arg) for arg in metadata) + return f'{cls}[{tp_repr}, {metadata_reprs}]' + + def _subs_tree(self, tvars=None, args=None): # noqa + if self is Annotated: + return Annotated + res = super()._subs_tree(tvars=tvars, args=args) + # Flatten nested Annotated + if isinstance(res[1], tuple) and res[1][0] is Annotated: + sub_tp = res[1][1] + sub_annot = res[1][2] + return (Annotated, sub_tp, sub_annot + res[2]) + return res + + def _get_cons(self): + """Return the class used to create instance of this type.""" + if self.__origin__ is None: + raise TypeError("Cannot get the underlying type of a " + "non-specialized Annotated type.") + tree = self._subs_tree() + while isinstance(tree, tuple) and tree[0] is Annotated: + tree = tree[1] + if isinstance(tree, tuple): + return tree[0] + else: + return tree + + @typing._tp_cache + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + if self.__origin__ is not None: # specializing an instantiated type + return super().__getitem__(params) + elif not isinstance(params, tuple) or len(params) < 2: + raise TypeError("Annotated[...] should be instantiated " + "with at least two arguments (a type and an " + "annotation).") + else: + msg = "Annotated[t, ...]: t must be a type." + tp = typing._type_check(params[0], msg) + metadata = tuple(params[1:]) + return self.__class__( + self.__name__, + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=_type_vars((tp,)), + # Metadata is a tuple so it won't be touched by _replace_args et al. + args=(tp, metadata), + origin=self, + ) + + def __call__(self, *args, **kwargs): + cons = self._get_cons() + result = cons(*args, **kwargs) + try: + result.__orig_class__ = self + except AttributeError: + pass + return result + + def __getattr__(self, attr): + # For simplicity we just don't relay all dunder names + if self.__origin__ is not None and not _is_dunder(attr): + return getattr(self._get_cons(), attr) + raise AttributeError(attr) + + def __setattr__(self, attr, value): + if _is_dunder(attr) or attr.startswith('_abc_'): + super().__setattr__(attr, value) + elif self.__origin__ is None: + raise AttributeError(attr) + else: + setattr(self._get_cons(), attr, value) + + def __instancecheck__(self, obj): + raise TypeError("Annotated cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Annotated cannot be used with issubclass().") + + class Annotated(metaclass=AnnotatedMeta): + """Add context specific metadata to a type. + + Example: Annotated[int, runtime_check.Unsigned] indicates to the + hypothetical runtime_check module that this type is an unsigned int. + Every other consumer of this type can ignore this metadata and treat + this type as int. + + The first argument to Annotated must be a valid type, the remaining + arguments are kept as a tuple in the __metadata__ field. + + Details: + + - It's an error to call `Annotated` with less than two arguments. + - Nested Annotated are flattened:: + + Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] + + - Instantiating an annotated type is equivalent to instantiating the + underlying type:: + + Annotated[C, Ann1](5) == C(5) + + - Annotated can be used as a generic type alias:: + + Optimized = Annotated[T, runtime.Optimize()] + Optimized[int] == Annotated[int, runtime.Optimize()] + + OptimizedList = Annotated[List[T], runtime.Optimize()] + OptimizedList[int] == Annotated[List[int], runtime.Optimize()] + """ + +# Python 3.8 has get_origin() and get_args() but those implementations aren't +# Annotated-aware, so we can't use those. Python 3.9's versions don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +if sys.version_info[:2] >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.7-3.9 +elif PEP_560: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = typing._GenericAlias + + def get_origin(tp): + """Get the unsubscripted version of a type. + + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: + + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None + + def get_args(tp): + """Get type arguments with all substitutions performed. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if isinstance(tp, _AnnotatedAlias): + return (tp.__origin__,) + tp.__metadata__ + if isinstance(tp, (typing._GenericAlias, GenericAlias)): + if getattr(tp, "_special", False): + return () + res = tp.__args__ + if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return () + + +# 3.10+ +if hasattr(typing, 'TypeAlias'): + TypeAlias = typing.TypeAlias +# 3.9 +elif sys.version_info[:2] >= (3, 9): + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeAliasForm + def TypeAlias(self, parameters): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + raise TypeError(f"{self} is not subscriptable") +# 3.7-3.8 +elif sys.version_info[:2] >= (3, 7): + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + TypeAlias = _TypeAliasForm('TypeAlias', + doc="""Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example + above.""") +# 3.6 +else: + class _TypeAliasMeta(typing.TypingMeta): + """Metaclass for TypeAlias""" + + def __repr__(self): + return 'typing_extensions.TypeAlias' + + class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta, _root=True): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("TypeAlias cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("TypeAlias cannot be used with issubclass().") + + def __repr__(self): + return 'typing_extensions.TypeAlias' + + TypeAlias = _TypeAliasBase(_root=True) + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +# 3.6-3.9 +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.args" + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.kwargs" + +# 3.10+ +if hasattr(typing, 'ParamSpec'): + ParamSpec = typing.ParamSpec +# 3.6-3.9 +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False): + super().__init__([self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + if not PEP_560: + # Only needed in 3.6. + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + +# 3.6-3.9 +if not hasattr(typing, 'Concatenate'): + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + if PEP_560: + __class__ = typing._GenericAlias + else: + __class__ = typing._TypingBase + + # Flag in 3.8. + _special = False + # Attribute in 3.6 and earlier. + _gorg = typing.Generic + + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return (f'{_type_repr(self.__origin__)}' + f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple( + tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) + ) + + if not PEP_560: + # Only required in 3.6. + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + typing._get_type_vars(self.__parameters__, tvars) + + +# 3.6-3.9 +@typing._tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not isinstance(parameters[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = tuple(typing._type_check(p, msg) for p in parameters) + return _ConcatenateGenericAlias(self, parameters) + + +# 3.10+ +if hasattr(typing, 'Concatenate'): + Concatenate = typing.Concatenate + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_TypeAliasForm + def Concatenate(self, parameters): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + return _concatenate_getitem(self, parameters) +# 3.7-8 +elif sys.version_info[:2] >= (3, 7): + class _ConcatenateForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateForm( + 'Concatenate', + doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """) +# 3.6 +else: + class _ConcatenateAliasMeta(typing.TypingMeta): + """Metaclass for Concatenate.""" + + def __repr__(self): + return 'typing_extensions.Concatenate' + + class _ConcatenateAliasBase(typing._FinalTypingBase, + metaclass=_ConcatenateAliasMeta, + _root=True): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("Concatenate cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Concatenate cannot be used with issubclass().") + + def __repr__(self): + return 'typing_extensions.Concatenate' + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateAliasBase(_root=True) + +# 3.10+ +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +# 3.9 +elif sys.version_info[:2] >= (3, 9): + class _TypeGuardForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeGuardForm + def TypeGuard(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + item = typing._type_check(parameters, f'{self} accepts only single type.') + return typing._GenericAlias(self, (item,)) +# 3.7-3.8 +elif sys.version_info[:2] >= (3, 7): + class _TypeGuardForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type') + return typing._GenericAlias(self, (item,)) + + TypeGuard = _TypeGuardForm( + 'TypeGuard', + doc="""Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """) +# 3.6 +else: + class _TypeGuard(typing._FinalTypingBase, _root=True): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + f'{cls.__name__[1:]} accepts only a single type.'), + _root=True) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += f'[{typing._type_repr(self.__type__)}]' + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _TypeGuard): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + TypeGuard = _TypeGuard(_root=True) + +if hasattr(typing, "Self"): + Self = typing.Self +elif sys.version_info[:2] >= (3, 7): + # Vendored from cpython typing._SpecialFrom + class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') + + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ + + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") + + def __repr__(self): + return f'typing_extensions.{self._name}' + + def __reduce__(self): + return self._name + + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") + + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) + + @_SpecialForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") +else: + class _Self(typing._FinalTypingBase, _root=True): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + Self = _Self(_root=True) + + +if hasattr(typing, 'Required'): + Required = typing.Required + NotRequired = typing.NotRequired +elif sys.version_info[:2] >= (3, 9): + class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_ExtensionsSpecialForm + def Required(self, parameters): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + +elif sys.version_info[:2] >= (3, 7): + class _RequiredForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + '{} accepts only single type'.format(self._name)) + return typing._GenericAlias(self, (item,)) + + Required = _RequiredForm( + 'Required', + doc="""A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """) + NotRequired = _RequiredForm( + 'NotRequired', + doc="""A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """) +else: + # NOTE: Modeled after _Final's implementation when _FinalTypingBase available + class _MaybeRequired(typing._FinalTypingBase, _root=True): + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, type(self)): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + class _Required(_MaybeRequired, _root=True): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + + class _NotRequired(_MaybeRequired, _root=True): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + + Required = _Required(_root=True) + NotRequired = _NotRequired(_root=True) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__init__.py new file mode 100644 index 0000000..fe86b59 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__init__.py @@ -0,0 +1,85 @@ +""" +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more +""" +from __future__ import absolute_import + +# Set default logging handler to avoid "No handler found" warnings. +import logging +import warnings +from logging import NullHandler + +from . import exceptions +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url +from .filepost import encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import HTTPResponse +from .util.request import make_headers +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ + +__all__ = ( + "HTTPConnectionPool", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "get_host", + "make_headers", + "proxy_from_url", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger(level=logging.DEBUG): + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) + + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..e7036da Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-38.pyc new file mode 100644 index 0000000..5f0c1a4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-38.pyc new file mode 100644 index 0000000..24d4015 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-38.pyc new file mode 100644 index 0000000..18a86d8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-38.pyc new file mode 100644 index 0000000..44748ee Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-38.pyc new file mode 100644 index 0000000..1ab75da Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-38.pyc new file mode 100644 index 0000000..99874db Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-38.pyc new file mode 100644 index 0000000..07be675 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-38.pyc new file mode 100644 index 0000000..70458ea Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-38.pyc new file mode 100644 index 0000000..5dc9c2c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-38.pyc new file mode 100644 index 0000000..2e183a9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_collections.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_collections.py new file mode 100644 index 0000000..da9857e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_collections.py @@ -0,0 +1,337 @@ +from __future__ import absolute_import + +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +from collections import OrderedDict + +from .exceptions import InvalidHeader +from .packages import six +from .packages.six import iterkeys, itervalues + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +_Null = object() + + +class RecentlyUsedContainer(MutableMapping): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): + self._maxsize = maxsize + self.dispose_func = dispose_func + + self._container = self.ContainerCls() + self.lock = RLock() + + def __getitem__(self, key): + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key, value): + evicted_value = _Null + with self.lock: + # Possibly evict the existing value of 'key' + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: + self.dispose_func(evicted_value) + + def __delitem__(self, key): + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self): + with self.lock: + return len(self._container) + + def __iter__(self): + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self): + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(itervalues(self._container)) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self): + with self.lock: + return list(iterkeys(self._container)) + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key, val): + self._container[key.lower()] = [key, val] + return self._container[key.lower()] + + def __getitem__(self, key): + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key): + del self._container[key.lower()] + + def __contains__(self, key): + return key.lower() in self._container + + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): + return False + if not isinstance(other, type(self)): + other = type(self)(other) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) + + def __ne__(self, other): + return not self.__eq__(other) + + if six.PY2: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): + return len(self._container) + + def __iter__(self): + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def pop(self, key, default=__marker): + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): + try: + del self[key] + except KeyError: + pass + + def add(self, key, val): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + vals.append(val) + + def extend(self, *args, **kwargs): + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError( + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) + ) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): + for key in other.keys(): + self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) + + for key, value in kwargs.items(): + self.add(key, value) + + def getlist(self, key, default=__marker): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + if default is self.__marker: + return [] + return default + else: + return vals[1:] + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) + + def _copy_from(self, other): + for key in other: + val = other.getlist(key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val + + def copy(self): + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self): + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ", ".join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (" ", "\t") + headers = [] + + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + "Header continuation with no previous header: %s" % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + " " + line.strip()) + continue + + key, value = line.split(":", 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_version.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_version.py new file mode 100644 index 0000000..fa8979d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/_version.py @@ -0,0 +1,2 @@ +# This file is protected via CODEOWNERS +__version__ = "1.26.8" diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connection.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connection.py new file mode 100644 index 0000000..4d92ac6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connection.py @@ -0,0 +1,569 @@ +from __future__ import absolute_import + +import datetime +import logging +import os +import re +import socket +import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from .packages import six +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection +from .packages.six.moves.http_client import HTTPException # noqa: F401 +from .util.proxy import create_proxy_ssl_context + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: + # Python 3: not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: + # Python 2 + class ConnectionError(Exception): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + BrokenPipeError = BrokenPipeError +except NameError: # Python 2: + + class BrokenPipeError(Exception): + pass + + +from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) +from ._version import __version__ +from .exceptions import ( + ConnectTimeoutError, + NewConnectionError, + SubjectAltNameWarning, + SystemTimeWarning, +) +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection +from .util.ssl_ import ( + assert_fingerprint, + create_urllib3_context, + is_ipaddress, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .util.ssl_match_hostname import CertificateError, match_hostname + +log = logging.getLogger(__name__) + +port_by_scheme = {"http": 80, "https": 443} + +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2020, 7, 1) + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +class HTTPConnection(_HTTPConnection, object): + """ + Based on :class:`http.client.HTTPConnection` but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: + + .. code-block:: python + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port = port_by_scheme["http"] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False + + #: Whether this proxy connection (if used) verifies the proxy host's + #: certificate. + proxy_is_verified = None + + def __init__(self, *args, **kw): + if not six.PY2: + kw.pop("strict", None) + + # Pre-set source_address. + self.source_address = kw.get("source_address") + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop("socket_options", self.default_socket_options) + + # Proxy options provided by the user. + self.proxy = kw.pop("proxy", None) + self.proxy_config = kw.pop("proxy_config", None) + + _HTTPConnection.__init__(self, *args, **kw) + + @property + def host(self): + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip(".") + + @host.setter + def host(self, value): + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self): + """Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + def _is_using_tunnel(self): + # Google App Engine's httplib does not define _tunnel_host + return getattr(self, "_tunnel_host", None) + + def _prepare_conn(self, conn): + self.sock = conn + if self._is_using_tunnel(): + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + def putrequest(self, method, url, *args, **kwargs): + """ """ + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + "Method cannot contain non-token characters %r (found at least %r)" + % (method, match.group()) + ) + + return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) + + def putheader(self, header, *values): + """ """ + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + _HTTPConnection.putheader(self, header, *values) + elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: + raise ValueError( + "urllib3.util.SKIP_HEADER only supports '%s'" + % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) + ) + + def request(self, method, url, body=None, headers=None): + if headers is None: + headers = {} + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = _get_default_user_agent() + super(HTTPConnection, self).request(method, url, body=body, headers=headers) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = headers or {} + header_keys = set([six.ensure_str(k.lower()) for k in headers]) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys + self.putrequest( + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host + ) + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) + for header, value in headers.items(): + self.putheader(header, value) + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") + self.endheaders() + + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: + if not chunk: + continue + if not isinstance(chunk, bytes): + chunk = chunk.encode("utf8") + len_str = hex(len(chunk))[2:] + to_send = bytearray(len_str.encode()) + to_send += b"\r\n" + to_send += chunk + to_send += b"\r\n" + self.send(to_send) + + # After the if clause, to always have a closed body + self.send(b"0\r\n\r\n") + + +class HTTPSConnection(HTTPConnection): + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + + default_port = port_by_scheme["https"] + + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ca_cert_data = None + ssl_version = None + assert_fingerprint = None + tls_in_tls_required = False + + def __init__( + self, + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): + + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = "https" + + def set_cert( + self, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ca_cert_data=None, + ): + """ + This method should only be called once, before the connection is used. + """ + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + def connect(self): + # Add certificate verification + conn = self._new_conn() + hostname = self.host + tls_in_tls = False + + if self._is_using_tunnel(): + if self.tls_in_tls_required: + conn = self._connect_tls_proxy(hostname, conn) + tls_in_tls = True + + self.sock = conn + + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, + ) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + default_ssl_context = False + if self.ssl_context is None: + default_ssl_context = True + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), + ) + + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) + + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and not self.ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + key_password=self.key_password, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + # If we're using all defaults and the connection + # is TLSv1 or TLSv1.1 we throw a DeprecationWarning + # for the host. + if ( + default_ssl_context + and self.ssl_version is None + and hasattr(self.sock, "version") + and self.sock.version() in {"TLSv1", "TLSv1.1"} + ): + warnings.warn( + "Negotiating TLSv1/TLSv1.1 by default is deprecated " + "and will be disabled in urllib3 v2.0.0. Connecting to " + "'%s' with '%s' can be enabled by explicitly opting-in " + "with 'ssl_version'" % (self.host, self.sock.version()), + DeprecationWarning, + ) + + if self.assert_fingerprint: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, self.assert_hostname or server_hostname) + + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) + + def _connect_tls_proxy(self, hostname, conn): + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + proxy_config = self.proxy_config + ssl_context = proxy_config.ssl_context + if ssl_context: + # If the user provided a proxy context, we assume CA and client + # certificates have already been set + return ssl_wrap_socket( + sock=conn, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + ssl_context = create_proxy_ssl_context( + self.ssl_version, + self.cert_reqs, + self.ca_certs, + self.ca_cert_dir, + self.ca_cert_data, + ) + + # If no cert was provided, use only the default options for server + # certificate validation + socket = ssl_wrap_socket( + sock=conn, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + if ssl_context.verify_mode != ssl.CERT_NONE and not getattr( + ssl_context, "check_hostname", False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = socket.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, hostname) + + self.proxy_is_verified = ssl_context.verify_mode == ssl.CERT_REQUIRED + return socket + + +def _match_hostname(cert, asserted_hostname): + # Our upstream implementation of ssl.match_hostname() + # only applies this normalization to IP addresses so it doesn't + # match DNS SANs so we do the same thing! + stripped_hostname = asserted_hostname.strip("u[]") + if is_ipaddress(stripped_hostname): + asserted_hostname = stripped_hostname + + try: + match_hostname(cert, asserted_hostname) + except CertificateError as e: + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert + raise + + +def _get_default_user_agent(): + return "python-urllib3/%s" % __version__ + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + + pass + + +if not ssl: + HTTPSConnection = DummyConnection # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connectionpool.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connectionpool.py new file mode 100644 index 0000000..15bffcb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/connectionpool.py @@ -0,0 +1,1108 @@ +from __future__ import absolute_import + +import errno +import logging +import re +import socket +import sys +import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + VerifiedHTTPSConnection, + port_by_scheme, +) +from .exceptions import ( + ClosedPoolError, + EmptyPoolError, + HeaderParsingError, + HostChangedError, + InsecureRequestWarning, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, +) +from .packages import six +from .packages.six.moves import queue +from .request import RequestMethods +from .response import HTTPResponse +from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.queue import LifoQueue +from .util.request import set_file_position +from .util.response import assert_header_parsing +from .util.retry import Retry +from .util.ssl_match_hostname import CertificateError +from .util.timeout import Timeout +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import get_host, parse_url + +xrange = six.moves.xrange + +log = logging.getLogger(__name__) + +_Default = object() + + +# Pool objects +class ConnectionPool(object): + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. + """ + + scheme = None + QueueCls = LifoQueue + + def __init__(self, host, port=None): + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self._proxy_host = host.lower() + self.port = port + + def __str__(self): + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + pass + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`http.client.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`http.client.HTTPConnection`. + + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`http.client.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.ProxyManager` + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.ProxyManager` + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = "http" + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + _proxy_config=None, + **conn_kw + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + self.strict = strict + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config + + # Fill the queue up so that doing get() on it will block properly + for _ in xrange(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config + + def _new_conn(self): + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) + + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + **self.conn_kw + ) + return conn + + def _get_conn(self, timeout=None): + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + if getattr(conn, "auto_open", 1) == 0: + # This is a proxied connection that has been mutated by + # http.client._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None + + return conn or self._new_conn() + + def _put_conn(self, conn): + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout): + """Helper that always returns a :class:`urllib3.util.Timeout`""" + if timeout is _Default: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + def _make_request( + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = timeout_obj.connect_timeout + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + # Python 3 + pass + except IOError as e: + # Python 2 and macOS/Linux + # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + if e.errno not in { + errno.EPIPE, + errno.ESHUTDOWN, + errno.EPROTOTYPE, + }: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, "sock", None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout + ) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: + # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: + # Python 3 + try: + httplib_response = conn.getresponse() + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + http_version, + httplib_response.status, + httplib_response.length, + ) + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + try: + while True: + conn = old_pool.get(block=False) + if conn: + conn.close() + + except queue.Empty: + pass # Done. + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When ``False``, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` + """ + + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = response_kw.get("preload_content", True) + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = six.ensure_str(_encode_target(url)) + else: + url = six.ensure_str(parsed_url.url) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] + release_this_conn = release_conn + + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: + headers = headers.copy() + headers.update(self.proxy_headers) + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout + + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn and http_tunnel_required: + self._prepare_proxy(conn) + + # Make the request on the httplib connection object. + httplib_response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + ) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw["request_method"] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw + ) + + # Everything went great! + clean_exit = True + + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise + + except ( + TimeoutError, + HTTPException, + SocketError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + + def _is_ssl_error_message_from_http_proxy(ssl_error): + # We're trying to detect the message 'WRONG_VERSION_NUMBER' but + # SSLErrors are kinda all over the place when it comes to the message, + # so we try to cover our bases here! + message = " ".join(re.split("[^a-z]", str(ssl_error).lower())) + return ( + "wrong version number" in message or "unknown protocol" in message + ) + + # Try to detect a common user error with proxies which is to + # set an HTTP proxy to be HTTPS when it should be 'http://' + # (ie {'http': 'http://proxy', 'https': 'https://proxy'}) + # Instead we add a nice error message and point to a URL. + if ( + isinstance(e, BaseSSLError) + and self.proxy + and _is_ssl_error_message_from_http_proxy(e) + ): + e = ProxyError( + "Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#https-proxy-error-http-proxy", + SSLError(e), + ) + elif isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError("Cannot connect to proxy.", e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError("Connection aborted.", e) + + retries = retries.increment( + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] + ) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + conn = conn and conn.close() + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + method = "GET" + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.getheader("Retry-After")) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, + url, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = "https" + ConnectionCls = HTTPSConnection + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): + + HTTPConnectionPool.__init__( + self, + host, + port, + strict, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw + ) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establishes a tunnel connection through HTTP CONNECT. + + Tunnel connection is established early because otherwise httplib would + improperly set Host: header to proxy's IP:port. + """ + + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + + if self.proxy.scheme == "https": + conn.tls_in_tls_required = True + + conn.connect() + + def _new_conn(self): + """ + Return a fresh :class:`http.client.HTTPSConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host = self.host + actual_port = self.port + if self.proxy is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + conn = self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + **self.conn_kw + ) + + return self._prepare_conn(conn) + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` + conn.connect() + + if not conn.is_verified: + warnings.warn( + ( + "Unverified HTTPS request is being made to host '%s'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) + + if getattr(conn, "proxy_is_verified", None) is False: + warnings.warn( + ( + "Unverified HTTPS connection done to an HTTPS proxy. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) + + +def _normalize_host(host, scheme): + """ + Normalize hosts for comparisons and use with sockets. + """ + + host = normalize_host(host, scheme) + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + if host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..0bdc364 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-38.pyc new file mode 100644 index 0000000..39865e3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-38.pyc new file mode 100644 index 0000000..6395373 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-38.pyc new file mode 100644 index 0000000..e6eeaef Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-38.pyc new file mode 100644 index 0000000..94957d4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-38.pyc new file mode 100644 index 0000000..e6f0c85 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-38.pyc new file mode 100644 index 0000000..a0e67ce Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py new file mode 100644 index 0000000..8765b90 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py @@ -0,0 +1,36 @@ +""" +This module provides means to detect the App Engine environment. +""" + +import os + + +def is_appengine(): + return is_local_appengine() or is_prod_appengine() + + +def is_appengine_sandbox(): + """Reports if the app is running in the first generation sandbox. + + The second generation runtimes are technically still in a sandbox, but it + is much less restrictive, so generally you shouldn't need to check for it. + see https://cloud.google.com/appengine/docs/standard/runtimes + """ + return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" + + +def is_local_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Development/") + + +def is_prod_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Google App Engine/") + + +def is_prod_appengine_mvms(): + """Deprecated.""" + return False diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..9370cde Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-38.pyc new file mode 100644 index 0000000..5322b95 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-38.pyc new file mode 100644 index 0000000..166c232 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py new file mode 100644 index 0000000..264d564 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py @@ -0,0 +1,519 @@ +""" +This module uses ctypes to bind a whole bunch of functions and constants from +SecureTransport. The goal here is to provide the low-level API to +SecureTransport. These are essentially the C-level functions and constants, and +they're pretty gross to work with. + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import platform +from ctypes import ( + CDLL, + CFUNCTYPE, + POINTER, + c_bool, + c_byte, + c_char_p, + c_int32, + c_long, + c_size_t, + c_uint32, + c_ulong, + c_void_p, +) +from ctypes.util import find_library + +from ...packages.six import raise_from + +if platform.system() != "Darwin": + raise ImportError("Only macOS is supported") + +version = platform.mac_ver()[0] +version_info = tuple(map(int, version.split("."))) +if version_info < (10, 8): + raise OSError( + "Only OS X 10.8 and newer are supported, not %s.%s" + % (version_info[0], version_info[1]) + ) + + +def load_cdll(name, macos10_16_path): + """Loads a CDLL by name, falling back to known path on 10.16+""" + try: + # Big Sur is technically 11 but we use 10.16 due to the Big Sur + # beta being labeled as 10.16. + if version_info >= (10, 16): + path = macos10_16_path + else: + path = find_library(name) + if not path: + raise OSError # Caught and reraised as 'ImportError' + return CDLL(path, use_errno=True) + except OSError: + raise_from(ImportError("The library %s failed to load" % name), None) + + +Security = load_cdll( + "Security", "/System/Library/Frameworks/Security.framework/Security" +) +CoreFoundation = load_cdll( + "CoreFoundation", + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", +) + + +Boolean = c_bool +CFIndex = c_long +CFStringEncoding = c_uint32 +CFData = c_void_p +CFString = c_void_p +CFArray = c_void_p +CFMutableArray = c_void_p +CFDictionary = c_void_p +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong + +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFDictionaryRef = POINTER(CFDictionary) +CFArrayCallBacks = c_void_p +CFDictionaryKeyCallBacks = c_void_p +CFDictionaryValueCallBacks = c_void_p + +SecCertificateRef = POINTER(c_void_p) +SecExternalFormat = c_uint32 +SecExternalItemType = c_uint32 +SecIdentityRef = POINTER(c_void_p) +SecItemImportExportFlags = c_uint32 +SecItemImportExportKeyParameters = c_void_p +SecKeychainRef = POINTER(c_void_p) +SSLProtocol = c_uint32 +SSLCipherSuite = c_uint32 +SSLContextRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SSLConnectionRef = c_uint32 +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 +SSLProtocolSide = c_uint32 +SSLConnectionType = c_uint32 +SSLSessionOption = c_uint32 + + +try: + Security.SecItemImport.argtypes = [ + CFDataRef, + CFStringRef, + POINTER(SecExternalFormat), + POINTER(SecExternalItemType), + SecItemImportExportFlags, + POINTER(SecItemImportExportKeyParameters), + SecKeychainRef, + POINTER(CFArrayRef), + ] + Security.SecItemImport.restype = OSStatus + + Security.SecCertificateGetTypeID.argtypes = [] + Security.SecCertificateGetTypeID.restype = CFTypeID + + Security.SecIdentityGetTypeID.argtypes = [] + Security.SecIdentityGetTypeID.restype = CFTypeID + + Security.SecKeyGetTypeID.argtypes = [] + Security.SecKeyGetTypeID.restype = CFTypeID + + Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] + Security.SecCertificateCreateWithData.restype = SecCertificateRef + + Security.SecCertificateCopyData.argtypes = [SecCertificateRef] + Security.SecCertificateCopyData.restype = CFDataRef + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SecIdentityCreateWithCertificate.argtypes = [ + CFTypeRef, + SecCertificateRef, + POINTER(SecIdentityRef), + ] + Security.SecIdentityCreateWithCertificate.restype = OSStatus + + Security.SecKeychainCreate.argtypes = [ + c_char_p, + c_uint32, + c_void_p, + Boolean, + c_void_p, + POINTER(SecKeychainRef), + ] + Security.SecKeychainCreate.restype = OSStatus + + Security.SecKeychainDelete.argtypes = [SecKeychainRef] + Security.SecKeychainDelete.restype = OSStatus + + Security.SecPKCS12Import.argtypes = [ + CFDataRef, + CFDictionaryRef, + POINTER(CFArrayRef), + ] + Security.SecPKCS12Import.restype = OSStatus + + SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE( + OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) + ) + + Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] + Security.SSLSetIOFuncs.restype = OSStatus + + Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerID.restype = OSStatus + + Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetCertificate.restype = OSStatus + + Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] + Security.SSLSetCertificateAuthorities.restype = OSStatus + + Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] + Security.SSLSetConnection.restype = OSStatus + + Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerDomainName.restype = OSStatus + + Security.SSLHandshake.argtypes = [SSLContextRef] + Security.SSLHandshake.restype = OSStatus + + Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLRead.restype = OSStatus + + Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLWrite.restype = OSStatus + + Security.SSLClose.argtypes = [SSLContextRef] + Security.SSLClose.restype = OSStatus + + Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberSupportedCiphers.restype = OSStatus + + Security.SSLGetSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetSupportedCiphers.restype = OSStatus + + Security.SSLSetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + c_size_t, + ] + Security.SSLSetEnabledCiphers.restype = OSStatus + + Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberEnabledCiphers.restype = OSStatus + + Security.SSLGetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetEnabledCiphers.restype = OSStatus + + Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] + Security.SSLGetNegotiatedCipher.restype = OSStatus + + Security.SSLGetNegotiatedProtocolVersion.argtypes = [ + SSLContextRef, + POINTER(SSLProtocol), + ] + Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus + + Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] + Security.SSLCopyPeerTrust.restype = OSStatus + + Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] + Security.SecTrustGetCertificateCount.restype = CFIndex + + Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] + Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef + + Security.SSLCreateContext.argtypes = [ + CFAllocatorRef, + SSLProtocolSide, + SSLConnectionType, + ] + Security.SSLCreateContext.restype = SSLContextRef + + Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] + Security.SSLSetSessionOption.restype = OSStatus + + Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMin.restype = OSStatus + + Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMax.restype = OSStatus + + try: + Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetALPNProtocols.restype = OSStatus + except AttributeError: + # Supported only in 10.12+ + pass + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SSLReadFunc = SSLReadFunc + Security.SSLWriteFunc = SSLWriteFunc + Security.SSLContextRef = SSLContextRef + Security.SSLProtocol = SSLProtocol + Security.SSLCipherSuite = SSLCipherSuite + Security.SecIdentityRef = SecIdentityRef + Security.SecKeychainRef = SecKeychainRef + Security.SecTrustRef = SecTrustRef + Security.SecTrustResultType = SecTrustResultType + Security.SecExternalFormat = SecExternalFormat + Security.OSStatus = OSStatus + + Security.kSecImportExportPassphrase = CFStringRef.in_dll( + Security, "kSecImportExportPassphrase" + ) + Security.kSecImportItemIdentity = CFStringRef.in_dll( + Security, "kSecImportItemIdentity" + ) + + # CoreFoundation time! + CoreFoundation.CFRetain.argtypes = [CFTypeRef] + CoreFoundation.CFRetain.restype = CFTypeRef + + CoreFoundation.CFRelease.argtypes = [CFTypeRef] + CoreFoundation.CFRelease.restype = None + + CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] + CoreFoundation.CFGetTypeID.restype = CFTypeID + + CoreFoundation.CFStringCreateWithCString.argtypes = [ + CFAllocatorRef, + c_char_p, + CFStringEncoding, + ] + CoreFoundation.CFStringCreateWithCString.restype = CFStringRef + + CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] + CoreFoundation.CFStringGetCStringPtr.restype = c_char_p + + CoreFoundation.CFStringGetCString.argtypes = [ + CFStringRef, + c_char_p, + CFIndex, + CFStringEncoding, + ] + CoreFoundation.CFStringGetCString.restype = c_bool + + CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] + CoreFoundation.CFDataCreate.restype = CFDataRef + + CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] + CoreFoundation.CFDataGetLength.restype = CFIndex + + CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] + CoreFoundation.CFDataGetBytePtr.restype = c_void_p + + CoreFoundation.CFDictionaryCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + POINTER(CFTypeRef), + CFIndex, + CFDictionaryKeyCallBacks, + CFDictionaryValueCallBacks, + ] + CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef + + CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] + CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef + + CoreFoundation.CFArrayCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreate.restype = CFArrayRef + + CoreFoundation.CFArrayCreateMutable.argtypes = [ + CFAllocatorRef, + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef + + CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] + CoreFoundation.CFArrayAppendValue.restype = None + + CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] + CoreFoundation.CFArrayGetCount.restype = CFIndex + + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] + CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeArrayCallBacks" + ) + CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryKeyCallBacks" + ) + CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryValueCallBacks" + ) + + CoreFoundation.CFTypeRef = CFTypeRef + CoreFoundation.CFArrayRef = CFArrayRef + CoreFoundation.CFStringRef = CFStringRef + CoreFoundation.CFDictionaryRef = CFDictionaryRef + +except (AttributeError): + raise ImportError("Error initializing ctypes") + + +class CFConst(object): + """ + A class object that acts as essentially a namespace for CoreFoundation + constants. + """ + + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + +class SecurityConst(object): + """ + A class object that acts as essentially a namespace for Security constants. + """ + + kSSLSessionOptionBreakOnServerAuth = 0 + + kSSLProtocol2 = 1 + kSSLProtocol3 = 2 + kTLSProtocol1 = 4 + kTLSProtocol11 = 7 + kTLSProtocol12 = 8 + # SecureTransport does not support TLS 1.3 even if there's a constant for it + kTLSProtocol13 = 10 + kTLSProtocolMaxSupported = 999 + + kSSLClientSide = 1 + kSSLStreamType = 0 + + kSecFormatPEMSequence = 10 + + kSecTrustResultInvalid = 0 + kSecTrustResultProceed = 1 + # This gap is present on purpose: this was kSecTrustResultConfirm, which + # is deprecated. + kSecTrustResultDeny = 3 + kSecTrustResultUnspecified = 4 + kSecTrustResultRecoverableTrustFailure = 5 + kSecTrustResultFatalTrustFailure = 6 + kSecTrustResultOtherError = 7 + + errSSLProtocol = -9800 + errSSLWouldBlock = -9803 + errSSLClosedGraceful = -9805 + errSSLClosedNoNotify = -9816 + errSSLClosedAbort = -9806 + + errSSLXCertChainInvalid = -9807 + errSSLCrypto = -9809 + errSSLInternal = -9810 + errSSLCertExpired = -9814 + errSSLCertNotYetValid = -9815 + errSSLUnknownRootCert = -9812 + errSSLNoRootCert = -9813 + errSSLHostNameMismatch = -9843 + errSSLPeerHandshakeFail = -9824 + errSSLPeerUserCancelled = -9839 + errSSLWeakPeerEphemeralDHKey = -9850 + errSSLServerAuthCompleted = -9841 + errSSLRecordOverflow = -9847 + + errSecVerifyFailed = -67808 + errSecNoTrustSettings = -25263 + errSecItemNotFound = -25300 + errSecInvalidTrustSettings = -25262 + + # Cipher suites. We only pick the ones our default cipher string allows. + # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_AES_128_GCM_SHA256 = 0x1301 + TLS_AES_256_GCM_SHA384 = 0x1302 + TLS_AES_128_CCM_8_SHA256 = 0x1305 + TLS_AES_128_CCM_SHA256 = 0x1304 diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py new file mode 100644 index 0000000..fa0b245 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py @@ -0,0 +1,397 @@ +""" +Low-level helpers for the SecureTransport bindings. + +These are Python functions that are not directly related to the high-level APIs +but are necessary to get them to work. They include a whole bunch of low-level +CoreFoundation messing about and memory management. The concerns in this module +are almost entirely about trying to avoid memory leaks and providing +appropriate and useful assistance to the higher-level code. +""" +import base64 +import ctypes +import itertools +import os +import re +import ssl +import struct +import tempfile + +from .bindings import CFConst, CoreFoundation, Security + +# This regular expression is used to grab PEM data out of a PEM bundle. +_PEM_CERTS_RE = re.compile( + b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL +) + + +def _cf_data_from_bytes(bytestring): + """ + Given a bytestring, create a CFData object from it. This CFData object must + be CFReleased by the caller. + """ + return CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) + ) + + +def _cf_dictionary_from_tuples(tuples): + """ + Given a list of Python tuples, create an associated CFDictionary. + """ + dictionary_size = len(tuples) + + # We need to get the dictionary keys and values out in the same order. + keys = (t[0] for t in tuples) + values = (t[1] for t in tuples) + cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) + cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) + + return CoreFoundation.CFDictionaryCreate( + CoreFoundation.kCFAllocatorDefault, + cf_keys, + cf_values, + dictionary_size, + CoreFoundation.kCFTypeDictionaryKeyCallBacks, + CoreFoundation.kCFTypeDictionaryValueCallBacks, + ) + + +def _cfstr(py_bstr): + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(py_bstr) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, + c_str, + CFConst.kCFStringEncodingUTF8, + ) + return cf_str + + +def _create_cfstring_array(lst): + """ + Given a list of Python binary data, create an associated CFMutableArray. + The array must be CFReleased by the caller. + + Raises an ssl.SSLError on failure. + """ + cf_arr = None + try: + cf_arr = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_arr: + raise MemoryError("Unable to allocate memory!") + for item in lst: + cf_str = _cfstr(item) + if not cf_str: + raise MemoryError("Unable to allocate memory!") + try: + CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) + finally: + CoreFoundation.CFRelease(cf_str) + except BaseException as e: + if cf_arr: + CoreFoundation.CFRelease(cf_arr) + raise ssl.SSLError("Unable to allocate array: %s" % (e,)) + return cf_arr + + +def _cf_string_to_unicode(value): + """ + Creates a Unicode string from a CFString object. Used entirely for error + reporting. + + Yes, it annoys me quite a lot that this function is this complex. + """ + value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) + + string = CoreFoundation.CFStringGetCStringPtr( + value_as_void_p, CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 + ) + if not result: + raise OSError("Error copying C string from CFStringRef") + string = buffer.value + if string is not None: + string = string.decode("utf-8") + return string + + +def _assert_no_error(error, exception_class=None): + """ + Checks the return code and throws an exception if there is an error to + report + """ + if error == 0: + return + + cf_error_string = Security.SecCopyErrorMessageString(error, None) + output = _cf_string_to_unicode(cf_error_string) + CoreFoundation.CFRelease(cf_error_string) + + if output is None or output == u"": + output = u"OSStatus %s" % error + + if exception_class is None: + exception_class = ssl.SSLError + + raise exception_class(output) + + +def _cert_array_from_pem(pem_bundle): + """ + Given a bundle of certs in PEM format, turns them into a CFArray of certs + that can be used to validate a cert chain. + """ + # Normalize the PEM bundle's line endings. + pem_bundle = pem_bundle.replace(b"\r\n", b"\n") + + der_certs = [ + base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) + ] + if not der_certs: + raise ssl.SSLError("No root certificates specified") + + cert_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cert_array: + raise ssl.SSLError("Unable to allocate memory!") + + try: + for der_bytes in der_certs: + certdata = _cf_data_from_bytes(der_bytes) + if not certdata: + raise ssl.SSLError("Unable to allocate memory!") + cert = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, certdata + ) + CoreFoundation.CFRelease(certdata) + if not cert: + raise ssl.SSLError("Unable to build cert object!") + + CoreFoundation.CFArrayAppendValue(cert_array, cert) + CoreFoundation.CFRelease(cert) + except Exception: + # We need to free the array before the exception bubbles further. + # We only want to do that if an error occurs: otherwise, the caller + # should free. + CoreFoundation.CFRelease(cert_array) + raise + + return cert_array + + +def _is_cert(item): + """ + Returns True if a given CFTypeRef is a certificate. + """ + expected = Security.SecCertificateGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _is_identity(item): + """ + Returns True if a given CFTypeRef is an identity. + """ + expected = Security.SecIdentityGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _temporary_keychain(): + """ + This function creates a temporary Mac keychain that we can use to work with + credentials. This keychain uses a one-time password and a temporary file to + store the data. We expect to have one keychain per socket. The returned + SecKeychainRef must be freed by the caller, including calling + SecKeychainDelete. + + Returns a tuple of the SecKeychainRef and the path to the temporary + directory that contains it. + """ + # Unfortunately, SecKeychainCreate requires a path to a keychain. This + # means we cannot use mkstemp to use a generic temporary file. Instead, + # we're going to create a temporary directory and a filename to use there. + # This filename will be 8 random bytes expanded into base64. We also need + # some random bytes to password-protect the keychain we're creating, so we + # ask for 40 random bytes. + random_bytes = os.urandom(40) + filename = base64.b16encode(random_bytes[:8]).decode("utf-8") + password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 + tempdirectory = tempfile.mkdtemp() + + keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") + + # We now want to create the keychain itself. + keychain = Security.SecKeychainRef() + status = Security.SecKeychainCreate( + keychain_path, len(password), password, False, None, ctypes.byref(keychain) + ) + _assert_no_error(status) + + # Having created the keychain, we want to pass it off to the caller. + return keychain, tempdirectory + + +def _load_items_from_file(keychain, path): + """ + Given a single file, loads all the trust objects from it into arrays and + the keychain. + Returns a tuple of lists: the first list is a list of identities, the + second a list of certs. + """ + certificates = [] + identities = [] + result_array = None + + with open(path, "rb") as f: + raw_filedata = f.read() + + try: + filedata = CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) + ) + result_array = CoreFoundation.CFArrayRef() + result = Security.SecItemImport( + filedata, # cert data + None, # Filename, leaving it out for now + None, # What the type of the file is, we don't care + None, # what's in the file, we don't care + 0, # import flags + None, # key params, can include passphrase in the future + keychain, # The keychain to insert into + ctypes.byref(result_array), # Results + ) + _assert_no_error(result) + + # A CFArray is not very useful to us as an intermediary + # representation, so we are going to extract the objects we want + # and then free the array. We don't need to keep hold of keys: the + # keychain already has them! + result_count = CoreFoundation.CFArrayGetCount(result_array) + for index in range(result_count): + item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) + item = ctypes.cast(item, CoreFoundation.CFTypeRef) + + if _is_cert(item): + CoreFoundation.CFRetain(item) + certificates.append(item) + elif _is_identity(item): + CoreFoundation.CFRetain(item) + identities.append(item) + finally: + if result_array: + CoreFoundation.CFRelease(result_array) + + CoreFoundation.CFRelease(filedata) + + return (identities, certificates) + + +def _load_client_cert_chain(keychain, *paths): + """ + Load certificates and maybe keys from a number of files. Has the end goal + of returning a CFArray containing one SecIdentityRef, and then zero or more + SecCertificateRef objects, suitable for use as a client certificate trust + chain. + """ + # Ok, the strategy. + # + # This relies on knowing that macOS will not give you a SecIdentityRef + # unless you have imported a key into a keychain. This is a somewhat + # artificial limitation of macOS (for example, it doesn't necessarily + # affect iOS), but there is nothing inside Security.framework that lets you + # get a SecIdentityRef without having a key in a keychain. + # + # So the policy here is we take all the files and iterate them in order. + # Each one will use SecItemImport to have one or more objects loaded from + # it. We will also point at a keychain that macOS can use to work with the + # private key. + # + # Once we have all the objects, we'll check what we actually have. If we + # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, + # we'll take the first certificate (which we assume to be our leaf) and + # ask the keychain to give us a SecIdentityRef with that cert's associated + # key. + # + # We'll then return a CFArray containing the trust chain: one + # SecIdentityRef and then zero-or-more SecCertificateRef objects. The + # responsibility for freeing this CFArray will be with the caller. This + # CFArray must remain alive for the entire connection, so in practice it + # will be stored with a single SSLSocket, along with the reference to the + # keychain. + certificates = [] + identities = [] + + # Filter out bad paths. + paths = (path for path in paths if path) + + try: + for file_path in paths: + new_identities, new_certs = _load_items_from_file(keychain, file_path) + identities.extend(new_identities) + certificates.extend(new_certs) + + # Ok, we have everything. The question is: do we have an identity? If + # not, we want to grab one from the first cert we have. + if not identities: + new_identity = Security.SecIdentityRef() + status = Security.SecIdentityCreateWithCertificate( + keychain, certificates[0], ctypes.byref(new_identity) + ) + _assert_no_error(status) + identities.append(new_identity) + + # We now want to release the original certificate, as we no longer + # need it. + CoreFoundation.CFRelease(certificates.pop(0)) + + # We now need to build a new CFArray that holds the trust chain. + trust_chain = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + for item in itertools.chain(identities, certificates): + # ArrayAppendValue does a CFRetain on the item. That's fine, + # because the finally block will release our other refs to them. + CoreFoundation.CFArrayAppendValue(trust_chain, item) + + return trust_chain + finally: + for obj in itertools.chain(identities, certificates): + CoreFoundation.CFRelease(obj) + + +TLS_PROTOCOL_VERSIONS = { + "SSLv2": (0, 2), + "SSLv3": (3, 0), + "TLSv1": (3, 1), + "TLSv1.1": (3, 2), + "TLSv1.2": (3, 3), +} + + +def _build_tls_unknown_ca_alert(version): + """ + Builds a TLS alert record for an unknown CA. + """ + ver_maj, ver_min = TLS_PROTOCOL_VERSIONS[version] + severity_fatal = 0x02 + description_unknown_ca = 0x30 + msg = struct.pack(">BB", severity_fatal, description_unknown_ca) + msg_len = len(msg) + record_type_alert = 0x15 + record = struct.pack(">BBBH", record_type_alert, ver_maj, ver_min, msg_len) + msg + return record diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/appengine.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/appengine.py new file mode 100644 index 0000000..6685386 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/appengine.py @@ -0,0 +1,314 @@ +""" +This module provides a pool manager that uses Google App Engine's +`URLFetch Service `_. + +Example usage:: + + from pip._vendor.urllib3 import PoolManager + from pip._vendor.urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + + if is_appengine_sandbox(): + # AppEngineManager uses AppEngine's URLFetch API behind the scenes + http = AppEngineManager() + else: + # PoolManager uses a socket-level API behind the scenes + http = PoolManager() + + r = http.request('GET', 'https://google.com/') + +There are `limitations `_ to the URLFetch service and it may not be +the best choice for your application. There are three options for using +urllib3 on Google App Engine: + +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is + cost-effective in many circumstances as long as your usage is within the + limitations. +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. + Sockets also have `limitations and restrictions + `_ and have a lower free quota than URLFetch. + To use sockets, be sure to specify the following in your ``app.yaml``:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + +3. If you are using `App Engine Flexible +`_, you can use the standard +:class:`PoolManager` without any configuration or special environment variables. +""" + +from __future__ import absolute_import + +import io +import logging +import warnings + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + SSLError, + TimeoutError, +) +from ..packages.six.moves.urllib.parse import urljoin +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.retry import Retry +from ..util.timeout import Timeout +from . import _appengine_environ + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation `here + `_. + + Notably it will raise an :class:`AppEnginePlatformError` if: + * URLFetch is not available. + * If you attempt to use this on App Engine Flexible, as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabytes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__( + self, + headers=None, + retries=None, + validate_certificate=True, + urlfetch_retries=True, + ): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment." + ) + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.io/en/1.26.x/reference/urllib3.contrib.html.", + AppEnginePlatformWarning, + ) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + self.urlfetch_retries = urlfetch_retries + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw + ): + + retries = self._get_retries(retries, redirect) + + try: + follow_redirects = redirect and retries.redirect != 0 and retries.total + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=self.urlfetch_retries and follow_redirects, + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if "too large" in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", + e, + ) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if "Too many redirects" in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", + e, + ) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e + ) + + http_response = self._urlfetch_response_to_http_response( + response, retries=retries, **response_kw + ) + + # Handle redirect? + redirect_location = redirect and http_response.get_redirect_location() + if redirect_location: + # Check for redirect response + if self.urlfetch_retries and retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + else: + if http_response.status == 303: + method = "GET" + + try: + retries = retries.increment( + method, url, response=http_response, _pool=self + ) + except MaxRetryError: + if retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + return http_response + + retries.sleep_for_retry(http_response) + log.debug("Redirecting %s -> %s", url, redirect_location) + redirect_url = urljoin(url, redirect_location) + return self.urlopen( + method, + redirect_url, + body, + headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(http_response.getheader("Retry-After")) + if retries.is_retry(method, http_response.status, has_retry_after): + retries = retries.increment(method, url, response=http_response, _pool=self) + log.debug("Retry: %s", url) + retries.sleep(http_response) + return self.urlopen( + method, + url, + body=body, + headers=headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get("content-encoding") + + if content_encoding == "deflate": + del urlfetch_resp.headers["content-encoding"] + + transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") + # We have a full response's content, + # so let's make sure we don't report ourselves as chunked data. + if transfer_encoding == "chunked": + encodings = transfer_encoding.split(",") + encodings.remove("chunked") + urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) + + original_response = HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=io.BytesIO(urlfetch_resp.content), + msg=urlfetch_resp.header_msg, + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + return HTTPResponse( + body=io.BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + original_response=original_response, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return None # Defer to URLFetch's default. + if isinstance(timeout, Timeout): + if timeout._read is not None or timeout._connect is not None: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total or default URLFetch timeout.", + AppEnginePlatformWarning, + ) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning, + ) + + return retries + + +# Alias methods from _appengine_environ to maintain public API interface. + +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py new file mode 100644 index 0000000..41a8fd1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py @@ -0,0 +1,130 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +import warnings +from logging import getLogger + +from ntlm import ntlm + +from .. import HTTPSConnectionPool +from ..packages.six.moves.http_client import HTTPSConnection + +warnings.warn( + "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed " + "in urllib3 v2.0 release, urllib3 is not able to support it properly due " + "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. " + "If you are a user of this module please comment in the mentioned issue.", + DeprecationWarning, +) + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = "https" + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split("\\", 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug( + "Starting NTLM HTTPS connection no. %d: https://%s%s", + self.num_connections, + self.host, + self.authurl, + ) + + headers = {"Connection": "Keep-Alive"} + req_header = "Authorization" + resp_header = "www-authenticate" + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( + self.rawuser + ) + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.getheaders()) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", reshdr) + log.debug("Response data: %s [...]", res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(", ") + auth_header_value = None + for s in auth_header_values: + if s[:5] == "NTLM ": + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception( + "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) + ) + + # Send authentication message + ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( + auth_header_value + ) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( + ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags + ) + headers[req_header] = "NTLM %s" % auth_msg + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", dict(res.getheaders())) + log.debug("Response data: %s [...]", res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception("Server rejected request: wrong username or password") + raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) + + res.fp = None + log.debug("Connection established") + return conn + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=3, + redirect=True, + assert_same_host=True, + ): + if headers is None: + headers = {} + headers["Connection"] = "Keep-Alive" + return super(NTLMConnectionPool, self).urlopen( + method, url, body, headers, retries, redirect, assert_same_host + ) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000..3130f51 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py @@ -0,0 +1,511 @@ +""" +TLS with SNI_-support for Python 2. Follow these instructions if you would +like to verify TLS certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. + +This needs the following packages installed: + +* `pyOpenSSL`_ (tested with 16.0.0) +* `cryptography`_ (minimum 1.3.4, from pyopenssl) +* `idna`_ (minimum 2.0, from cryptography) + +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. + +You can install them with the following command: + +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this: + +.. code-block:: python + + try: + import pip._vendor.urllib3.contrib.pyopenssl as pyopenssl + pyopenssl.inject_into_urllib3() + except ImportError: + pass + +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna +""" +from __future__ import absolute_import + +import OpenSSL.SSL +from cryptography import x509 +from cryptography.hazmat.backends.openssl import backend as openssl_backend +from cryptography.hazmat.backends.openssl.x509 import _Certificate + +try: + from cryptography.x509 import UnsupportedExtension +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): + pass + + +from io import BytesIO +from socket import error as SocketError +from socket import timeout + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +import logging +import ssl +import sys + +from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works. +HAS_SNI = True + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions = { + util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): + _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3(): + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3(): + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met(): + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) + + +def _dnsname_to_stdlib(name): + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + + def idna_encode(name): + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + from pip._vendor import idna + + try: + for prefix in [u"*.", u"."]: + if name.startswith(prefix): + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + + name = idna_encode(name) + if name is None: + return None + elif sys.version_info >= (3, 0): + name = name.decode("utf-8") + return name + + +def get_subj_alt_name(peer_cert): + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + # Pass the cert to cryptography, which has much better APIs for this. + if hasattr(peer_cert, "to_cryptography"): + cert = peer_cert.to_cryptography() + else: + # This is technically using private APIs, but should work across all + # relevant versions before PyOpenSSL got a proper API for this. + cert = _Certificate(openssl_backend, peer_cert._x509) + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket(object): + """API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + """ + + def __init__(self, connection, socket, suppress_ragged_eofs=True): + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + self._closed = False + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + else: + return data + + def recv_into(self, *args, **kwargs): + try: + return self.connection.recv_into(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + + def settimeout(self, timeout): + return self.socket.settimeout(timeout) + + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise timeout() + continue + except OpenSSL.SSL.SysCallError as e: + raise SocketError(str(e)) + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) + total_sent += sent + + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() + + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) + + return { + "subject": ((("commonName", x509.get_subject().CN),),), + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self): + return self.connection.get_protocol_version_name() + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + + +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile + + +class PyOpenSSLContext(object): + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + + def __init__(self, protocol): + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + + @property + def options(self): + return self._options + + @options.setter + def options(self, value): + self._options = value + self._ctx.set_options(value) + + @property + def verify_mode(self): + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value): + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self): + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + if cafile is not None: + cafile = cafile.encode("utf-8") + if capath is not None: + capath = capath.encode("utf-8") + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("unable to load trusted certificates: %r" % e) + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, six.binary_type): + password = password.encode("utf-8") + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode("utf-8") + + if server_hostname is not None: + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(sock, sock.gettimeout()): + raise timeout("select timed out") + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("bad handshake: %r" % e) + break + + return WrappedSocket(cnx, sock) + + +def _verify_callback(cnx, x509, err_no, err_depth, return_code): + return err_no == 0 diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/securetransport.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/securetransport.py new file mode 100644 index 0000000..b4ca80b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/securetransport.py @@ -0,0 +1,922 @@ +""" +SecureTranport support for urllib3 via ctypes. + +This makes platform-native TLS available to urllib3 users on macOS without the +use of a compiler. This is an important feature because the Python Package +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve +this is to give macOS users an alternative solution to the problem, and that +solution is to use SecureTransport. + +We use ctypes here because this solution must not require a compiler. That's +because pip is not allowed to require a compiler either. + +This is not intended to be a seriously long-term solution to this problem. +The hope is that PEP 543 will eventually solve this issue for us, at which +point we can retire this contrib module. But in the short term, we need to +solve the impending tire fire that is Python on Mac without this kind of +contrib module. So...here we are. + +To use this module, simply import and inject it:: + + import pip._vendor.urllib3.contrib.securetransport as securetransport + securetransport.inject_into_urllib3() + +Happy TLSing! + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + +.. code-block:: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import contextlib +import ctypes +import errno +import os.path +import shutil +import socket +import ssl +import struct +import threading +import weakref + +from pip._vendor import six + +from .. import util +from ..util.ssl_ import PROTOCOL_TLS_CLIENT +from ._securetransport.bindings import CoreFoundation, Security, SecurityConst +from ._securetransport.low_level import ( + _assert_no_error, + _build_tls_unknown_ca_alert, + _cert_array_from_pem, + _create_cfstring_array, + _load_client_cert_chain, + _temporary_keychain, +) + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works +HAS_SNI = True + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + +# This dictionary is used by the read callback to obtain a handle to the +# calling wrapped socket. This is a pretty silly approach, but for now it'll +# do. I feel like I should be able to smuggle a handle to the wrapped socket +# directly in the SSLConnectionRef, but for now this approach will work I +# guess. +# +# We need to lock around this structure for inserts, but we don't do it for +# reads/writes in the callbacks. The reasoning here goes as follows: +# +# 1. It is not possible to call into the callbacks before the dictionary is +# populated, so once in the callback the id must be in the dictionary. +# 2. The callbacks don't mutate the dictionary, they only read from it, and +# so cannot conflict with any of the insertions. +# +# This is good: if we had to lock in the callbacks we'd drastically slow down +# the performance of this code. +_connection_refs = weakref.WeakValueDictionary() +_connection_ref_lock = threading.Lock() + +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over +# for no better reason than we need *a* limit, and this one is right there. +SSL_WRITE_BLOCKSIZE = 16384 + +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to +# individual cipher suites. We need to do this because this is how +# SecureTransport wants them. +CIPHER_SUITES = [ + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_AES_128_GCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_AES_128_CCM_8_SHA256, + SecurityConst.TLS_AES_128_CCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, +] + +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +# TLSv1 to 1.2 are supported on macOS 10.8+ +_protocol_to_min_max = { + util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), + PROTOCOL_TLS_CLIENT: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), +} + +if hasattr(ssl, "PROTOCOL_SSLv2"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( + SecurityConst.kSSLProtocol2, + SecurityConst.kSSLProtocol2, + ) +if hasattr(ssl, "PROTOCOL_SSLv3"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( + SecurityConst.kSSLProtocol3, + SecurityConst.kSSLProtocol3, + ) +if hasattr(ssl, "PROTOCOL_TLSv1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( + SecurityConst.kTLSProtocol1, + SecurityConst.kTLSProtocol1, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( + SecurityConst.kTLSProtocol11, + SecurityConst.kTLSProtocol11, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_2"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( + SecurityConst.kTLSProtocol12, + SecurityConst.kTLSProtocol12, + ) + + +def inject_into_urllib3(): + """ + Monkey-patch urllib3 with SecureTransport-backed SSL-support. + """ + util.SSLContext = SecureTransportContext + util.ssl_.SSLContext = SecureTransportContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_SECURETRANSPORT = True + util.ssl_.IS_SECURETRANSPORT = True + + +def extract_from_urllib3(): + """ + Undo monkey-patching by :func:`inject_into_urllib3`. + """ + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_SECURETRANSPORT = False + util.ssl_.IS_SECURETRANSPORT = False + + +def _read_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport read callback. This is called by ST to request that data + be returned from the socket. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + requested_length = data_length_pointer[0] + + timeout = wrapped_socket.gettimeout() + error = None + read_count = 0 + + try: + while read_count < requested_length: + if timeout is None or timeout >= 0: + if not util.wait_for_read(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + + remaining = requested_length - read_count + buffer = (ctypes.c_char * remaining).from_address( + data_buffer + read_count + ) + chunk_size = base_socket.recv_into(buffer, remaining) + read_count += chunk_size + if not chunk_size: + if not read_count: + return SecurityConst.errSSLClosedGraceful + break + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = read_count + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = read_count + + if read_count != requested_length: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +def _write_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport write callback. This is called by ST to request that data + actually be sent on the network. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + bytes_to_write = data_length_pointer[0] + data = ctypes.string_at(data_buffer, bytes_to_write) + + timeout = wrapped_socket.gettimeout() + error = None + sent = 0 + + try: + while sent < bytes_to_write: + if timeout is None or timeout >= 0: + if not util.wait_for_write(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + chunk_sent = base_socket.send(data) + sent += chunk_sent + + # This has some needless copying here, but I'm not sure there's + # much value in optimising this data path. + data = data[chunk_sent:] + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = sent + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = sent + + if sent != bytes_to_write: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +# We need to keep these two objects references alive: if they get GC'd while +# in use then SecureTransport could attempt to call a function that is in freed +# memory. That would be...uh...bad. Yeah, that's the word. Bad. +_read_callback_pointer = Security.SSLReadFunc(_read_callback) +_write_callback_pointer = Security.SSLWriteFunc(_write_callback) + + +class WrappedSocket(object): + """ + API-compatibility wrapper for Python's OpenSSL wrapped socket object. + + Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage + collector of PyPy. + """ + + def __init__(self, socket): + self.socket = socket + self.context = None + self._makefile_refs = 0 + self._closed = False + self._exception = None + self._keychain = None + self._keychain_dir = None + self._client_cert_chain = None + + # We save off the previously-configured timeout and then set it to + # zero. This is done because we use select and friends to handle the + # timeouts, but if we leave the timeout set on the lower socket then + # Python will "kindly" call select on that socket again for us. Avoid + # that by forcing the timeout to zero. + self._timeout = self.socket.gettimeout() + self.socket.settimeout(0) + + @contextlib.contextmanager + def _raise_on_error(self): + """ + A context manager that can be used to wrap calls that do I/O from + SecureTransport. If any of the I/O callbacks hit an exception, this + context manager will correctly propagate the exception after the fact. + This avoids silently swallowing those exceptions. + + It also correctly forces the socket closed. + """ + self._exception = None + + # We explicitly don't catch around this yield because in the unlikely + # event that an exception was hit in the block we don't want to swallow + # it. + yield + if self._exception is not None: + exception, self._exception = self._exception, None + self.close() + raise exception + + def _set_ciphers(self): + """ + Sets up the allowed ciphers. By default this matches the set in + util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done + custom and doesn't allow changing at this time, mostly because parsing + OpenSSL cipher strings is going to be a freaking nightmare. + """ + ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) + result = Security.SSLSetEnabledCiphers( + self.context, ciphers, len(CIPHER_SUITES) + ) + _assert_no_error(result) + + def _set_alpn_protocols(self, protocols): + """ + Sets up the ALPN protocols on the context. + """ + if not protocols: + return + protocols_arr = _create_cfstring_array(protocols) + try: + result = Security.SSLSetALPNProtocols(self.context, protocols_arr) + _assert_no_error(result) + finally: + CoreFoundation.CFRelease(protocols_arr) + + def _custom_validate(self, verify, trust_bundle): + """ + Called when we have set custom validation. We do this in two cases: + first, when cert validation is entirely disabled; and second, when + using a custom trust DB. + Raises an SSLError if the connection is not trusted. + """ + # If we disabled cert validation, just say: cool. + if not verify: + return + + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed, + ) + try: + trust_result = self._evaluate_trust(trust_bundle) + if trust_result in successes: + return + reason = "error code: %d" % (trust_result,) + except Exception as e: + # Do not trust on error + reason = "exception: %r" % (e,) + + # SecureTransport does not send an alert nor shuts down the connection. + rec = _build_tls_unknown_ca_alert(self.version()) + self.socket.sendall(rec) + # close the connection immediately + # l_onoff = 1, activate linger + # l_linger = 0, linger for 0 seoncds + opts = struct.pack("ii", 1, 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) + self.close() + raise ssl.SSLError("certificate verify failed, %s" % reason) + + def _evaluate_trust(self, trust_bundle): + # We want data in memory, so load it up. + if os.path.isfile(trust_bundle): + with open(trust_bundle, "rb") as f: + trust_bundle = f.read() + + cert_array = None + trust = Security.SecTrustRef() + + try: + # Get a CFArray that contains the certs we want. + cert_array = _cert_array_from_pem(trust_bundle) + + # Ok, now the hard part. We want to get the SecTrustRef that ST has + # created for this connection, shove our CAs into it, tell ST to + # ignore everything else it knows, and then ask if it can build a + # chain. This is a buuuunch of code. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + raise ssl.SSLError("Failed to copy trust reference") + + result = Security.SecTrustSetAnchorCertificates(trust, cert_array) + _assert_no_error(result) + + result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) + _assert_no_error(result) + + trust_result = Security.SecTrustResultType() + result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) + _assert_no_error(result) + finally: + if trust: + CoreFoundation.CFRelease(trust) + + if cert_array is not None: + CoreFoundation.CFRelease(cert_array) + + return trust_result.value + + def handshake( + self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase, + alpn_protocols, + ): + """ + Actually performs the TLS handshake. This is run automatically by + wrapped socket, and shouldn't be needed in user code. + """ + # First, we do the initial bits of connection setup. We need to create + # a context, set its I/O funcs, and set the connection reference. + self.context = Security.SSLCreateContext( + None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType + ) + result = Security.SSLSetIOFuncs( + self.context, _read_callback_pointer, _write_callback_pointer + ) + _assert_no_error(result) + + # Here we need to compute the handle to use. We do this by taking the + # id of self modulo 2**31 - 1. If this is already in the dictionary, we + # just keep incrementing by one until we find a free space. + with _connection_ref_lock: + handle = id(self) % 2147483647 + while handle in _connection_refs: + handle = (handle + 1) % 2147483647 + _connection_refs[handle] = self + + result = Security.SSLSetConnection(self.context, handle) + _assert_no_error(result) + + # If we have a server hostname, we should set that too. + if server_hostname: + if not isinstance(server_hostname, bytes): + server_hostname = server_hostname.encode("utf-8") + + result = Security.SSLSetPeerDomainName( + self.context, server_hostname, len(server_hostname) + ) + _assert_no_error(result) + + # Setup the ciphers. + self._set_ciphers() + + # Setup the ALPN protocols. + self._set_alpn_protocols(alpn_protocols) + + # Set the minimum and maximum TLS versions. + result = Security.SSLSetProtocolVersionMin(self.context, min_version) + _assert_no_error(result) + + result = Security.SSLSetProtocolVersionMax(self.context, max_version) + _assert_no_error(result) + + # If there's a trust DB, we need to use it. We do that by telling + # SecureTransport to break on server auth. We also do that if we don't + # want to validate the certs at all: we just won't actually do any + # authing in that case. + if not verify or trust_bundle is not None: + result = Security.SSLSetSessionOption( + self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True + ) + _assert_no_error(result) + + # If there's a client cert, we need to use it. + if client_cert: + self._keychain, self._keychain_dir = _temporary_keychain() + self._client_cert_chain = _load_client_cert_chain( + self._keychain, client_cert, client_key + ) + result = Security.SSLSetCertificate(self.context, self._client_cert_chain) + _assert_no_error(result) + + while True: + with self._raise_on_error(): + result = Security.SSLHandshake(self.context) + + if result == SecurityConst.errSSLWouldBlock: + raise socket.timeout("handshake timed out") + elif result == SecurityConst.errSSLServerAuthCompleted: + self._custom_validate(verify, trust_bundle) + continue + else: + _assert_no_error(result) + break + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, bufsiz): + buffer = ctypes.create_string_buffer(bufsiz) + bytes_read = self.recv_into(buffer, bufsiz) + data = buffer[:bytes_read] + return data + + def recv_into(self, buffer, nbytes=None): + # Read short on EOF. + if self._closed: + return 0 + + if nbytes is None: + nbytes = len(buffer) + + buffer = (ctypes.c_char * nbytes).from_buffer(buffer) + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLRead( + self.context, buffer, nbytes, ctypes.byref(processed_bytes) + ) + + # There are some result codes that we want to treat as "not always + # errors". Specifically, those are errSSLWouldBlock, + # errSSLClosedGraceful, and errSSLClosedNoNotify. + if result == SecurityConst.errSSLWouldBlock: + # If we didn't process any bytes, then this was just a time out. + # However, we can get errSSLWouldBlock in situations when we *did* + # read some data, and in those cases we should just read "short" + # and return. + if processed_bytes.value == 0: + # Timed out, no data read. + raise socket.timeout("recv timed out") + elif result in ( + SecurityConst.errSSLClosedGraceful, + SecurityConst.errSSLClosedNoNotify, + ): + # The remote peer has closed this connection. We should do so as + # well. Note that we don't actually return here because in + # principle this could actually be fired along with return data. + # It's unlikely though. + self.close() + else: + _assert_no_error(result) + + # Ok, we read and probably succeeded. We should return whatever data + # was actually read. + return processed_bytes.value + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def send(self, data): + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLWrite( + self.context, data, len(data), ctypes.byref(processed_bytes) + ) + + if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: + # Timed out + raise socket.timeout("send timed out") + else: + _assert_no_error(result) + + # We sent, and probably succeeded. Tell them how much we sent. + return processed_bytes.value + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + with self._raise_on_error(): + Security.SSLClose(self.context) + + def close(self): + # TODO: should I do clean shutdown here? Do I have to? + if self._makefile_refs < 1: + self._closed = True + if self.context: + CoreFoundation.CFRelease(self.context) + self.context = None + if self._client_cert_chain: + CoreFoundation.CFRelease(self._client_cert_chain) + self._client_cert_chain = None + if self._keychain: + Security.SecKeychainDelete(self._keychain) + CoreFoundation.CFRelease(self._keychain) + shutil.rmtree(self._keychain_dir) + self._keychain = self._keychain_dir = None + return self.socket.close() + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + # Urgh, annoying. + # + # Here's how we do this: + # + # 1. Call SSLCopyPeerTrust to get hold of the trust object for this + # connection. + # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. + # 3. To get the CN, call SecCertificateCopyCommonName and process that + # string so that it's of the appropriate type. + # 4. To get the SAN, we need to do something a bit more complex: + # a. Call SecCertificateCopyValues to get the data, requesting + # kSecOIDSubjectAltName. + # b. Mess about with this dictionary to try to get the SANs out. + # + # This is gross. Really gross. It's going to be a few hundred LoC extra + # just to repeat something that SecureTransport can *already do*. So my + # operating assumption at this time is that what we want to do is + # instead to just flag to urllib3 that it shouldn't do its own hostname + # validation when using SecureTransport. + if not binary_form: + raise ValueError("SecureTransport only supports dumping binary certs") + trust = Security.SecTrustRef() + certdata = None + der_bytes = None + + try: + # Grab the trust store. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + # Probably we haven't done the handshake yet. No biggie. + return None + + cert_count = Security.SecTrustGetCertificateCount(trust) + if not cert_count: + # Also a case that might happen if we haven't handshaked. + # Handshook? Handshaken? + return None + + leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) + assert leaf + + # Ok, now we want the DER bytes. + certdata = Security.SecCertificateCopyData(leaf) + assert certdata + + data_length = CoreFoundation.CFDataGetLength(certdata) + data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) + der_bytes = ctypes.string_at(data_buffer, data_length) + finally: + if certdata: + CoreFoundation.CFRelease(certdata) + if trust: + CoreFoundation.CFRelease(trust) + + return der_bytes + + def version(self): + protocol = Security.SSLProtocol() + result = Security.SSLGetNegotiatedProtocolVersion( + self.context, ctypes.byref(protocol) + ) + _assert_no_error(result) + if protocol.value == SecurityConst.kTLSProtocol13: + raise ssl.SSLError("SecureTransport does not support TLS 1.3") + elif protocol.value == SecurityConst.kTLSProtocol12: + return "TLSv1.2" + elif protocol.value == SecurityConst.kTLSProtocol11: + return "TLSv1.1" + elif protocol.value == SecurityConst.kTLSProtocol1: + return "TLSv1" + elif protocol.value == SecurityConst.kSSLProtocol3: + return "SSLv3" + elif protocol.value == SecurityConst.kSSLProtocol2: + return "SSLv2" + else: + raise ssl.SSLError("Unknown TLS version: %r" % protocol) + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + + +else: # Platform-specific: Python 3 + + def makefile(self, mode="r", buffering=None, *args, **kwargs): + # We disable buffering with SecureTransport because it conflicts with + # the buffering that ST does internally (see issue #1153 for more). + buffering = 0 + return backport_makefile(self, mode, buffering, *args, **kwargs) + + +WrappedSocket.makefile = makefile + + +class SecureTransportContext(object): + """ + I am a wrapper class for the SecureTransport library, to translate the + interface of the standard library ``SSLContext`` object to calls into + SecureTransport. + """ + + def __init__(self, protocol): + self._min_version, self._max_version = _protocol_to_min_max[protocol] + self._options = 0 + self._verify = False + self._trust_bundle = None + self._client_cert = None + self._client_key = None + self._client_key_passphrase = None + self._alpn_protocols = None + + @property + def check_hostname(self): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + return True + + @check_hostname.setter + def check_hostname(self, value): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + pass + + @property + def options(self): + # TODO: Well, crap. + # + # So this is the bit of the code that is the most likely to cause us + # trouble. Essentially we need to enumerate all of the SSL options that + # users might want to use and try to see if we can sensibly translate + # them, or whether we should just ignore them. + return self._options + + @options.setter + def options(self, value): + # TODO: Update in line with above. + self._options = value + + @property + def verify_mode(self): + return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE + + @verify_mode.setter + def verify_mode(self, value): + self._verify = True if value == ssl.CERT_REQUIRED else False + + def set_default_verify_paths(self): + # So, this has to do something a bit weird. Specifically, what it does + # is nothing. + # + # This means that, if we had previously had load_verify_locations + # called, this does not undo that. We need to do that because it turns + # out that the rest of the urllib3 code will attempt to load the + # default verify paths if it hasn't been told about any paths, even if + # the context itself was sometime earlier. We resolve that by just + # ignoring it. + pass + + def load_default_certs(self): + return self.set_default_verify_paths() + + def set_ciphers(self, ciphers): + # For now, we just require the default cipher string. + if ciphers != util.ssl_.DEFAULT_CIPHERS: + raise ValueError("SecureTransport doesn't support custom cipher strings") + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + # OK, we only really support cadata and cafile. + if capath is not None: + raise ValueError("SecureTransport does not support cert directories") + + # Raise if cafile does not exist. + if cafile is not None: + with open(cafile): + pass + + self._trust_bundle = cafile or cadata + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._client_cert = certfile + self._client_key = keyfile + self._client_cert_passphrase = password + + def set_alpn_protocols(self, protocols): + """ + Sets the ALPN protocols that will later be set on the context. + + Raises a NotImplementedError if ALPN is not supported. + """ + if not hasattr(Security, "SSLSetALPNProtocols"): + raise NotImplementedError( + "SecureTransport supports ALPN only in macOS 10.12+" + ) + self._alpn_protocols = [six.ensure_binary(p) for p in protocols] + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + # So, what do we do here? Firstly, we assert some properties. This is a + # stripped down shim, so there is some functionality we don't support. + # See PEP 543 for the real deal. + assert not server_side + assert do_handshake_on_connect + assert suppress_ragged_eofs + + # Ok, we're good to go. Now we want to create the wrapped socket object + # and store it in the appropriate place. + wrapped_socket = WrappedSocket(sock) + + # Now we can handshake + wrapped_socket.handshake( + server_hostname, + self._verify, + self._trust_bundle, + self._min_version, + self._max_version, + self._client_cert, + self._client_key, + self._client_key_passphrase, + self._alpn_protocols, + ) + return wrapped_socket diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/socks.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/socks.py new file mode 100644 index 0000000..c326e80 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/contrib/socks.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) +- Usernames and passwords for the SOCKS proxy + +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python + + proxy_url="socks4a://@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy: + +.. code-block:: python + + proxy_url="socks5h://:@proxy-host" + +""" +from __future__ import absolute_import + +try: + import socks +except ImportError: + import warnings + + from ..exceptions import DependencyWarning + + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop("_socks_options") + super(SOCKSConnection, self).__init__(*args, **kwargs) + + def _new_conn(self): + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], + timeout=self.timeout, + **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % error + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + except SocketError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + + pool_classes_by_scheme = { + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, + } + + def __init__( + self, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw + ): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(":") + if len(split) == 2: + username, password = split + if parsed.scheme == "socks5": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == "socks5h": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == "socks4": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == "socks4a": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) + + self.proxy_url = proxy_url + + socks_options = { + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, + } + connection_pool_kw["_socks_options"] = socks_options + + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/exceptions.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/exceptions.py new file mode 100644 index 0000000..cba6f3f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/exceptions.py @@ -0,0 +1,323 @@ +from __future__ import absolute_import + +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead + +# Base Exceptions + + +class HTTPError(Exception): + """Base exception used by this module.""" + + pass + + +class HTTPWarning(Warning): + """Base warning used by this module.""" + + pass + + +class PoolError(HTTPError): + """Base exception for errors caused within a pool.""" + + def __init__(self, pool, message): + self.pool = pool + HTTPError.__init__(self, "%s: %s" % (pool, message)) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + """Base exception for PoolErrors that have associated URLs.""" + + def __init__(self, pool, url, message): + self.url = url + PoolError.__init__(self, pool, message) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + """Raised when SSL certificate fails in an HTTPS connection.""" + + pass + + +class ProxyError(HTTPError): + """Raised when the connection to a proxy fails.""" + + def __init__(self, message, error, *args): + super(ProxyError, self).__init__(message, error, *args) + self.original_error = error + + +class DecodeError(HTTPError): + """Raised when automatic decoding based on Content-Type fails.""" + + pass + + +class ProtocolError(HTTPError): + """Raised when something unexpected happens mid-request/response.""" + + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ + + def __init__(self, pool, url, reason=None): + self.reason = reason + + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) + + RequestError.__init__(self, pool, url, message) + + +class HostChangedError(RequestError): + """Raised when an existing pool gets a request for a foreign host.""" + + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """Raised when passing an invalid state to a timeout""" + + pass + + +class TimeoutError(HTTPError): + """Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + pass + + +class ReadTimeoutError(TimeoutError, RequestError): + """Raised when a socket timeout occurs while receiving data from a server""" + + pass + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + """Raised when a socket timeout occurs while connecting to a server""" + + pass + + +class NewConnectionError(ConnectTimeoutError, PoolError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + + pass + + +class EmptyPoolError(PoolError): + """Raised when a pool runs out of connections and no more are allowed.""" + + pass + + +class ClosedPoolError(PoolError): + """Raised when a request enters a pool after the pool has been closed.""" + + pass + + +class LocationValueError(ValueError, HTTPError): + """Raised when there is something wrong with a given URL input.""" + + pass + + +class LocationParseError(LocationValueError): + """Raised when get_host or similar fails to parse the URL input.""" + + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) + + self.location = location + + +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme): + message = "Not supported URL scheme %s" % scheme + super(URLSchemeUnknown, self).__init__(message) + + self.scheme = scheme + + +class ResponseError(HTTPError): + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" + + +class SecurityWarning(HTTPWarning): + """Warned when performing security reducing actions""" + + pass + + +class SubjectAltNameWarning(SecurityWarning): + """Warned when connecting to a host with a certificate missing a SAN.""" + + pass + + +class InsecureRequestWarning(SecurityWarning): + """Warned when making an unverified HTTPS request.""" + + pass + + +class SystemTimeWarning(SecurityWarning): + """Warned when system time is suspected to be wrong""" + + pass + + +class InsecurePlatformWarning(SecurityWarning): + """Warned when certain TLS/SSL configuration is not available on a platform.""" + + pass + + +class SNIMissingWarning(HTTPWarning): + """Warned when making a HTTPS request without SNI available.""" + + pass + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + pass + + +class ResponseNotChunked(ProtocolError, ValueError): + """Response needs to be chunked in order to read it as chunks.""" + + pass + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). + """ + + pass + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. + """ + + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) + + def __repr__(self): + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response, length): + super(InvalidChunkLength, self).__init__( + response.tell(), response.length_remaining + ) + self.response = response + self.length = length + + def __repr__(self): + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) + + +class InvalidHeader(HTTPError): + """The header provided was somehow invalid.""" + + pass + + +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme): + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = ( + "Proxy URL had unsupported scheme %s, should use http:// or https://" + % scheme + ) + super(ProxySchemeUnknown, self).__init__(message) + + +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + pass + + +class HeaderParsingError(HTTPError): + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + + def __init__(self, defects, unparsed_data): + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) + super(HeaderParsingError, self).__init__(message) + + +class UnrewindableBodyError(HTTPError): + """urllib3 encountered an error when trying to rewind a body""" + + pass diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/fields.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/fields.py new file mode 100644 index 0000000..9d630f4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/fields.py @@ -0,0 +1,274 @@ +from __future__ import absolute_import + +import email.utils +import mimetypes +import re + +from .packages import six + + +def guess_content_type(filename, default="application/octet-stream"): + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param_rfc2231(name, value): + """ + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 `_. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + An RFC-2231-formatted unicode string. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = u'%s="%s"' % (name, value) + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + if six.PY2: # Python 2: + value = value.encode("utf-8") + + # encode_rfc2231 accepts an encoded string and returns an ascii-encoded + # string in Python 2 but accepts and returns unicode strings in Python 3 + value = email.utils.encode_rfc2231(value, "utf-8") + value = "%s*=%s" % (name, value) + + if six.PY2: # Python 2: + value = value.decode("utf-8") + + return value + + +_HTML5_REPLACEMENTS = { + u"\u0022": u"%22", + # Replace "\" with "\\". + u"\u005C": u"\u005C\u005C", +} + +# All control characters from 0x00 to 0x1F *except* 0x1B. +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) + + +def _replace_multiple(value, needles_and_replacements): + def replacer(match): + return needles_and_replacements[match.group(0)] + + pattern = re.compile( + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) + ) + + result = pattern.sub(replacer, value) + + return result + + +def format_header_param_html5(name, value): + """ + Helper function to format and quote a single header parameter using the + HTML5 strategy. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows the `HTML5 Working Draft + Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. + + .. _HTML5 Working Draft Section 4.10.22.7: + https://w3c.github.io/html/sec-forms.html#multipart-form-data + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + A unicode string, stripped of troublesome characters. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + value = _replace_multiple(value, _HTML5_REPLACEMENTS) + + return u'%s="%s"' % (name, value) + + +# For backwards-compatibility. +format_header_param = format_header_param_html5 + + +class RequestField(object): + """ + A data container for request body parameters. + + :param name: + The name of this request field. Must be unicode. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. Must be unicode. + :param headers: + An optional dict-like object of headers to initially use for the field. + :param header_formatter: + An optional callable that is used to encode and format the headers. By + default, this is :func:`format_header_param_html5`. + """ + + def __init__( + self, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, + ): + self._name = name + self._filename = filename + self.data = data + self.headers = {} + if headers: + self.headers = dict(headers) + self.header_formatter = header_formatter + + @classmethod + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name, value): + """ + Overridable helper function to format a single header parameter. By + default, this calls ``self.header_formatter``. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + + return self.header_formatter(name, value) + + def _render_parts(self, header_parts): + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + parts = [] + iterable = header_parts + if isinstance(header_parts, dict): + iterable = header_parts.items() + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return u"; ".join(parts) + + def render_headers(self): + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append(u"%s: %s" % (header_name, header_value)) + + lines.append(u"\r\n") + return u"\r\n".join(lines) + + def make_multipart( + self, content_disposition=None, content_type=None, content_location=None + ): + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( + [ + u"", + self._render_parts( + ((u"name", self._name), (u"filename", self._filename)) + ), + ] + ) + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/filepost.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/filepost.py new file mode 100644 index 0000000..36c9252 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/filepost.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import + +import binascii +import codecs +import os +from io import BytesIO + +from .fields import RequestField +from .packages import six +from .packages.six import b + +writer = codecs.lookup("utf-8")[3] + + +def choose_boundary(): + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + boundary = binascii.hexlify(os.urandom(16)) + if not six.PY2: + boundary = boundary.decode("ascii") + return boundary + + +def iter_field_objects(fields): + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + if isinstance(fields, dict): + i = six.iteritems(fields) + else: + i = iter(fields) + + for field in i: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(b("--%s\r\n" % (boundary))) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, six.text_type): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(b("--%s--\r\n" % (boundary))) + + content_type = str("multipart/form-data; boundary=%s" % boundary) + + return body.getvalue(), content_type diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..8b46d55 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-38.pyc new file mode 100644 index 0000000..3a7c72d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..31d3a9f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-38.pyc new file mode 100644 index 0000000..ee7678b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py new file mode 100644 index 0000000..b8fb215 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" +backports.makefile +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``socket.makefile`` method for use with anything that +wants to create a "fake" socket object. +""" +import io +from socket import SocketIO + + +def backport_makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None +): + """ + Backport of ``socket.makefile`` from Python 3.5. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = SocketIO(self, rawmode) + self._makefile_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/six.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/six.py new file mode 100644 index 0000000..ba50acb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/packages/six.py @@ -0,0 +1,1077 @@ +# Copyright (c) 2010-2020 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.16.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = (str,) + integer_types = (int,) + class_types = (type,) + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = (basestring,) + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + + get_source = get_code # same as get_code + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute( + "filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse" + ), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute( + "reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload" + ), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute( + "zip_longest", "itertools", "itertools", "izip_longest", "zip_longest" + ), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule( + "collections_abc", + "collections", + "collections.abc" if sys.version_info >= (3, 3) else "collections", + ), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule( + "_dummy_thread", + "dummy_thread", + "_dummy_thread" if sys.version_info < (3, 9) else "_thread", + ), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule( + "email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart" + ), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute( + "unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes" + ), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", + "moves.urllib.parse", +) + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", + "moves.urllib.error", +) + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", + "moves.urllib.request", +) + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", + "moves.urllib.response", +) + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = ( + _urllib_robotparser_moved_attributes +) + +_importer._add_module( + Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", + "moves.urllib.robotparser", +) + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ["parse", "error", "request", "response", "robotparser"] + + +_importer._add_module( + Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib" +) + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + + def advance_iterator(it): + return it.next() + + +next = advance_iterator + + +try: + callable = callable +except NameError: + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc( + get_unbound_function, """Get the function out of a possibly unbound function""" +) + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc( + iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary." +) + + +if PY3: + + def b(s): + return s.encode("latin-1") + + def u(s): + return s + + unichr = chr + import struct + + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + + StringIO = io.StringIO + BytesIO = io.BytesIO + del io + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" +else: + + def b(s): + return s + + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape") + + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + + +else: + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec ("""exec _code_ in _globs_, _locs_""") + + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) + + +if sys.version_info[:2] > (3,): + exec_( + """def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""" + ) +else: + + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if ( + isinstance(fp, file) + and isinstance(data, unicode) + and fp.encoding is not None + ): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + + +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper( + wrapper, + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps( + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + return functools.partial( + _update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated + ) + + wraps.__doc__ = functools.wraps.__doc__ + +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + def __new__(cls, name, this_bases, d): + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d["__orig_bases__"] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + + return type.__new__(metaclass, "temporary_class", (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get("__slots__") + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + if hasattr(cls, "__qualname__"): + orig_vars["__qualname__"] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + + return wrapper + + +def ensure_binary(s, encoding="utf-8", errors="strict"): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, binary_type): + return s + if isinstance(s, text_type): + return s.encode(encoding, errors) + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding="utf-8", errors="strict"): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + # Optimization: Fast return for the common case. + if type(s) is str: + return s + if PY2 and isinstance(s, text_type): + return s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + return s + + +def ensure_text(s, encoding="utf-8", errors="strict"): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def python_2_unicode_compatible(klass): + """ + A class decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if "__str__" not in klass.__dict__: + raise ValueError( + "@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % klass.__name__ + ) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode("utf-8") + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if ( + type(importer).__name__ == "_SixMetaPathImporter" + and importer.name == __name__ + ): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/poolmanager.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/poolmanager.py new file mode 100644 index 0000000..3a31a28 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/poolmanager.py @@ -0,0 +1,536 @@ +from __future__ import absolute_import + +import collections +import functools +import logging + +from ._collections import RecentlyUsedContainer +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + ProxySchemeUnsupported, + URLSchemeUnknown, +) +from .packages import six +from .packages.six.moves.urllib.parse import urljoin +from .request import RequestMethods +from .util.proxy import connection_requires_http_tunnel +from .util.retry import Retry +from .util.url import parse_url + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ssl_version", + "ca_cert_dir", + "ssl_context", + "key_password", +) + +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key__proxy_config", # class + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple("PoolKey", _key_fields) + +_proxy_config_fields = ("ssl_context", "use_forwarding_for_https") +ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields) + + +def _default_key_normalizer(key_class, request_context): + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ("headers", "_proxy_headers", "_socks_options"): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get("socket_options") + if socket_opts is not None: + context["socket_options"] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context["key_" + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example:: + + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 + + """ + + proxy = None + proxy_config = None + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close()) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool(self, scheme, host, port, request_context=None): + """ + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ("scheme", "host", "port"): + request_context.pop(key, None) + + if scheme == "http": + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self): + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context["scheme"] = scheme or "http" + if not port: + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host + + return self.connection_from_context(request_context) + + def connection_from_context(self, request_context): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key(self, pool_key, request_context=None): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url(self, url, pool_kwargs=None): + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) + + def _merge_pool_kwargs(self, override): + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def _proxy_requires_url_absolute_form(self, parsed_url): + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def _validate_proxy_scheme_url_selection(self, url_scheme): + """ + Validates that were not attempting to do TLS in TLS connections on + Python2 or with unsupported SSL implementations. + """ + if self.proxy is None or url_scheme != "https": + return + + if self.proxy.scheme != "https": + return + + if six.PY2 and not self.proxy_config.use_forwarding_for_https: + raise ProxySchemeUnsupported( + "Contacting HTTPS destinations through HTTPS proxies " + "'via CONNECT tunnels' is not supported in Python 2" + ) + + def urlopen(self, method, url, redirect=True, **kw): + """ + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + self._validate_proxy_scheme_url_selection(u.scheme) + + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw["assert_same_host"] = False + kw["redirect"] = False + + if "headers" not in kw: + kw["headers"] = self.headers.copy() + + if self._proxy_requires_url_absolute_form(u): + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + # RFC 7231, Section 6.4.4 + if response.status == 303: + method = "GET" + + retries = kw.get("retries") + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + headers = list(six.iterkeys(kw["headers"])) + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + kw["headers"].pop(header, None) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + kw["retries"] = retries + kw["redirect"] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + + Example: + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 + + """ + + def __init__( + self, + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + proxy_ssl_context=None, + use_forwarding_for_https=False, + **connection_pool_kw + ): + + if isinstance(proxy_url, HTTPConnectionPool): + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) + proxy = parse_url(proxy_url) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https) + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config + + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + if scheme == "https": + return super(ProxyManager, self).connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs + ) + + def _set_proxy_headers(self, url, headers=None): + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {"Accept": "*/*"} + + netloc = parse_url(url).netloc + if netloc: + headers_["Host"] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen(self, method, url, redirect=True, **kw): + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) + + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url, **kw): + return ProxyManager(proxy_url=url, **kw) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/request.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/request.py new file mode 100644 index 0000000..398386a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/request.py @@ -0,0 +1,170 @@ +from __future__ import absolute_import + +from .filepost import encode_multipart_formdata +from .packages.six.moves.urllib.parse import urlencode + +__all__ = ["RequestMethods"] + + +class RequestMethods(object): + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} + + def __init__(self, headers=None): + self.headers = headers or {} + + def urlopen( + self, + method, + url, + body=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **kw + ): # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + """ + method = method.upper() + + urlopen_kw["request_url"] = url + + if method in self._encode_url_methods: + return self.request_encode_url( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": headers} + extra_kw.update(urlopen_kw) + + if fields: + url += "?" + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body( + self, + method, + url, + fields=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **urlopen_kw + ): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :func:`urllib3.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :func:`urllib.parse.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": {}} + + if fields: + if "body" in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one." + ) + + if encode_multipart: + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) + else: + body, content_type = ( + urlencode(fields), + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"] = {"Content-Type": content_type} + + extra_kw["headers"].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/response.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/response.py new file mode 100644 index 0000000..38693f4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/response.py @@ -0,0 +1,821 @@ +from __future__ import absolute_import + +import io +import logging +import zlib +from contextlib import contextmanager +from socket import error as SocketError +from socket import timeout as SocketTimeout + +try: + import brotli +except ImportError: + brotli = None + +from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPException +from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, +) +from .packages import six +from .util.response import is_fp_closed, is_response_to_head + +log = logging.getLogger(__name__) + + +class DeflateDecoder(object): + def __init__(self): + self._first_try = True + self._data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + decompressed = self._obj.decompress(data) + if decompressed: + self._first_try = False + self._data = None + return decompressed + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None + + +class GzipDecoderState(object): + + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(object): + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: + return bytes(ret) + while True: + try: + ret += self._obj.decompress(data) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + data = self._obj.unused_data + if not data: + return bytes(ret) + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + +if brotli is not None: + + class BrotliDecoder(object): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): + self.decompress = self._obj.decompress + else: + self.decompress = self._obj.process + + def flush(self): + if hasattr(self._obj, "flush"): + return self._obj.flush() + return b"" + + +class MultiDecoder(object): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] + + def flush(self): + return self._decoders[0].flush() + + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + +def _get_decoder(mode): + if "," in mode: + return MultiDecoder(mode) + + if mode == "gzip": + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + return DeflateDecoder() + + +class HTTPResponse(io.IOBase): + """ + HTTP Response container. + + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + CONTENT_DECODERS = ["gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + def __init__( + self, + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): + + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._decoder = None + self._body = None + self._fp = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + self._request_url = request_url + + if body and isinstance(body, (six.string_types, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body + + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + + return False + + def release_conn(self): + if not self._pool or not self._connection: + return + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self): + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self.read() + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + + @property + def data(self): + # For backwards-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body + + if self._fp: + return self.read(cache_content=True) + + @property + def connection(self): + return self._connection + + def isclosed(self): + return is_fp_closed(self._fp) + + def tell(self): + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` + if bytes are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method): + """ + Set initial length value for Response content if available. + """ + length = self.headers.get("content-length") + + if length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = set([int(val) for val in length.split(",")]) + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": + length = 0 + + return length + + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + DECODER_ERROR_CLASSES = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b"") + return buf + self._decoder.flush() + + return b"" + + @contextmanager + def _error_catcher(self): + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if "read operation timed out" not in str(e): + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) + + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError("Connection broken: %r" % e, e) + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def read(self, amt=None, decode_content=None, cache_content=False): + """ + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if self._fp is None: + return + + flush_decoder = False + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + if amt is None: + # cStringIO doesn't like amt=None + data = self._fp.read() if not fp_closed else b"" + flush_decoder = True + else: + cache_content = False + data = self._fp.read(amt) if not fp_closed else b"" + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + + data = self._decode(data, decode_content, flush_decoder) + + if cache_content: + self._body = data + + return data + + def stream(self, amt=2 ** 16, decode_content=None): + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked and self.supports_chunked_reads(): + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`http.client.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if six.PY2: + # Python 2.7 + headers = HTTPHeaderDict.from_httplib(headers) + else: + headers = HTTPHeaderDict(headers.items()) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) + return resp + + # Backwards-compatibility methods for http.client.HTTPResponse + def getheaders(self): + return self.headers + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers + + # Overrides from io.IOBase + def close(self): + if not self.closed: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self): + if not self.auto_close: + return io.IOBase.closed.__get__(self) + elif self._fp is None: + return True + elif hasattr(self._fp, "isclosed"): + return self._fp.isclosed() + elif hasattr(self._fp, "closed"): + return self._fp.closed + else: + return True + + def fileno(self): + if self._fp is None: + raise IOError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise IOError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self): + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): + """ + Checks if the underlying file-like object looks like a + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, "fp") + + def _update_chunk_length(self): + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return + line = self._fp.fp.readline() + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise InvalidChunkLength(self, line) + + def _handle_chunk(self, amt): + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + returned_chunk = chunk + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk + + def read_chunked(self, amt=None, decode_content=None): + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing." + ) + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: + return + + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, decode_content=decode_content, flush_decoder=False + ) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while True: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b"\r\n": + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + def geturl(self): + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url + + def __iter__(self): + buffer = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunk = chunk.split(b"\n") + yield b"".join(buffer) + chunk[0] + b"\n" + for x in chunk[1:-1]: + yield x + b"\n" + if chunk[-1]: + buffer = [chunk[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__init__.py new file mode 100644 index 0000000..4547fc5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__init__.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import + +# For backwards compatibility, provide imports that used to be here. +from .connection import is_connection_dropped +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers +from .response import is_fp_closed +from .retry import Retry +from .ssl_ import ( + ALPN_PROTOCOLS, + HAS_SNI, + IS_PYOPENSSL, + IS_SECURETRANSPORT, + PROTOCOL_TLS, + SSLContext, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import Timeout, current_time +from .url import Url, get_host, parse_url, split_first +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "HAS_SNI", + "IS_PYOPENSSL", + "IS_SECURETRANSPORT", + "SSLContext", + "PROTOCOL_TLS", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "current_time", + "is_connection_dropped", + "is_fp_closed", + "get_host", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "split_first", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", +) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..127cde7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-38.pyc new file mode 100644 index 0000000..cec9842 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-38.pyc new file mode 100644 index 0000000..556956b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-38.pyc new file mode 100644 index 0000000..7b7cf3c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-38.pyc new file mode 100644 index 0000000..2498125 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-38.pyc new file mode 100644 index 0000000..ac22f45 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-38.pyc new file mode 100644 index 0000000..64e1b8d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-38.pyc new file mode 100644 index 0000000..e5eb69b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-38.pyc new file mode 100644 index 0000000..4f509c0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-38.pyc new file mode 100644 index 0000000..5e42133 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-38.pyc new file mode 100644 index 0000000..a12597d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-38.pyc new file mode 100644 index 0000000..e7eb79a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-38.pyc new file mode 100644 index 0000000..948104d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/connection.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/connection.py new file mode 100644 index 0000000..6af1138 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/connection.py @@ -0,0 +1,149 @@ +from __future__ import absolute_import + +import socket + +from ..contrib import _appengine_environ +from ..exceptions import LocationParseError +from ..packages import six +from .wait import NoWayToWaitForSocketError, wait_for_read + + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`http.client.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, "sock", False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection( + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`socket.getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith("["): + host = host.strip("[]") + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + try: + host.encode("idna") + except UnicodeError: + return six.raise_from( + LocationParseError(u"'%s', label empty or too long" % host), None + ) + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as e: + err = e + if sock is not None: + sock.close() + sock = None + + if err is not None: + raise err + + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family(): + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host): + """Returns True if the system can bind an IPv6 address.""" + sock = None + has_ipv6 = False + + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/urllib3/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6("::1") diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/proxy.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/proxy.py new file mode 100644 index 0000000..2199cc7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/proxy.py @@ -0,0 +1,57 @@ +from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version + + +def connection_requires_http_tunnel( + proxy_url=None, proxy_config=None, destination_scheme=None +): + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True + + +def create_proxy_ssl_context( + ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None +): + """ + Generates a default proxy ssl context if one hasn't been provided by the + user. + """ + ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and hasattr(ssl_context, "load_default_certs") + ): + ssl_context.load_default_certs() + + return ssl_context diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/queue.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/queue.py new file mode 100644 index 0000000..4178410 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/queue.py @@ -0,0 +1,22 @@ +import collections + +from ..packages import six +from ..packages.six.moves import queue + +if six.PY2: + # Queue is imported for side effects on MS Windows. See issue #229. + import Queue as _unused_module_Queue # noqa: F401 + + +class LifoQueue(queue.Queue): + def _init(self, _): + self.queue = collections.deque() + + def _qsize(self, len=len): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/request.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/request.py new file mode 100644 index 0000000..2510338 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/request.py @@ -0,0 +1,143 @@ +from __future__ import absolute_import + +from base64 import b64encode + +from ..exceptions import UnrewindableBodyError +from ..packages.six import b, integer_types + +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" +try: + import brotli as _unused_module_brotli # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" + +_FAILEDTELL = object() + + +def make_headers( + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ",".join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers["accept-encoding"] = accept_encoding + + if user_agent: + headers["user-agent"] = user_agent + + if keep_alive: + headers["connection"] = "keep-alive" + + if basic_auth: + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") + + if proxy_basic_auth: + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position(body, pos): + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, "tell", None) is not None: + try: + pos = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body, body_pos): + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, "seek", None) + if body_seek is not None and isinstance(body_pos, integer_types): + try: + body_seek(body_pos) + except (IOError, OSError): + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + "body_pos must be of type integer, instead it was %s." % type(body_pos) + ) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/response.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/response.py new file mode 100644 index 0000000..5ea609c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/response.py @@ -0,0 +1,107 @@ +from __future__ import absolute_import + +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect + +from ..exceptions import HeaderParsingError +from ..packages.six.moves import http_client as httplib + + +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers): + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param http.client.HTTPMessage headers: Headers to verify. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) + + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) + + unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + if defects: + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response): + """ + Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. + + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == "HEAD" diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/retry.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/retry.py new file mode 100644 index 0000000..3398323 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/retry.py @@ -0,0 +1,620 @@ +from __future__ import absolute_import + +import email +import logging +import re +import time +import warnings +from collections import namedtuple +from itertools import takewhile + +from ..exceptions import ( + ConnectTimeoutError, + InvalidHeader, + MaxRetryError, + ProtocolError, + ProxyError, + ReadTimeoutError, + ResponseError, +) +from ..packages import six + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) + + +# TODO: In v2 we can remove this sentinel and metaclass with deprecated options. +_Default = object() + + +class _RetryMeta(type): + @property + def DEFAULT_METHOD_WHITELIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + return cls.DEFAULT_ALLOWED_METHODS + + @DEFAULT_METHOD_WHITELIST.setter + def DEFAULT_METHOD_WHITELIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + cls.DEFAULT_ALLOWED_METHODS = value + + @property + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + return cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + @DEFAULT_REDIRECT_HEADERS_BLACKLIST.setter + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value + + @property + def BACKOFF_MAX(cls): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + return cls.DEFAULT_BACKOFF_MAX + + @BACKOFF_MAX.setter + def BACKOFF_MAX(cls, value): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + cls.DEFAULT_BACKOFF_MAX = value + + +@six.add_metaclass(_RetryMeta) +class Retry(object): + """Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param iterable allowed_methods: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. + + Set to a ``False`` value to retry on any verb. + + .. warning:: + + Previously this parameter was named ``method_whitelist``, that + usage is deprecated in v1.26.0 and will be removed in v2.0. + + :param iterable status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``allowed_methods`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of total retries} - 1)) + + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.DEFAULT_BACKOFF_MAX`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param iterable remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + """ + + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + + #: Default status codes to be used for ``status_forcelist`` + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"]) + + #: Maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 + + def __init__( + self, + total=10, + connect=None, + read=None, + redirect=None, + status=None, + other=None, + allowed_methods=_Default, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=_Default, + # TODO: Deprecated, remove in v2.0 + method_whitelist=_Default, + ): + + if method_whitelist is not _Default: + if allowed_methods is not _Default: + raise ValueError( + "Using both 'allowed_methods' and " + "'method_whitelist' together is not allowed. " + "Instead only use 'allowed_methods'" + ) + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + stacklevel=2, + ) + allowed_methods = method_whitelist + if allowed_methods is _Default: + allowed_methods = self.DEFAULT_ALLOWED_METHODS + if remove_headers_on_redirect is _Default: + remove_headers_on_redirect = self.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + self.total = total + self.connect = connect + self.read = read + self.status = status + self.other = other + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.allowed_methods = allowed_methods + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or tuple() + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + [h.lower() for h in remove_headers_on_redirect] + ) + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, + ) + + # TODO: If already given in **kw we use what's given to us + # If not given we need to figure out what to pass. We decide + # based on whether our class has the 'method_whitelist' property + # and if so we pass the deprecated 'method_whitelist' otherwise + # we use 'allowed_methods'. Remove in v2.0 + if "method_whitelist" not in kw and "allowed_methods" not in kw: + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + params["method_whitelist"] = self.allowed_methods + else: + params["allowed_methods"] = self.allowed_methods + + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self): + """Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + return min(self.DEFAULT_BACKOFF_MAX, backoff_value) + + def parse_retry_after(self, retry_after): + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + def get_retry_after(self, response): + """Get the value of Retry-After in seconds.""" + + retry_after = response.getheader("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response=None): + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self): + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response=None): + """Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if self.respect_retry_after_header and response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err): + """Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + if isinstance(err, ProxyError): + err = err.original_error + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method): + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods + """ + # TODO: For now favor if the Retry implementation sets its own method_whitelist + # property outside of our constructor to avoid breaking custom implementations. + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + allowed_methods = self.method_whitelist + else: + allowed_methods = self.allowed_methods + + if allowed_methods and method.upper() not in allowed_methods: + return False + return True + + def is_retry(self, method, status_code, has_retry_after=False): + """Is this method/status code retryable? (Based on allowlists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return ( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) + + def is_exhausted(self): + """Are we out of retries?""" + retry_counts = ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): + """Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + other = self.other + cause = "unknown" + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif error: + # Other retry? + if other is not None: + other -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = "too many redirects" + redirect_location = response.get_redirect_location() + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and the given method is in the allowed_methods + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) + status = response.status + + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) + + new_retry = self.new( + total=total, + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error or ResponseError(cause)) + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self): + return ( + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + def __getattr__(self, item): + if item == "method_whitelist": + # TODO: Remove this deprecated alias in v2.0 + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + return self.allowed_methods + try: + return getattr(super(Retry, self), item) + except AttributeError: + return getattr(Retry, item) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_.py new file mode 100644 index 0000000..2b45d39 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_.py @@ -0,0 +1,495 @@ +from __future__ import absolute_import + +import hmac +import os +import sys +import warnings +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from ..exceptions import ( + InsecurePlatformWarning, + ProxySchemeUnsupported, + SNIMissingWarning, + SSLError, +) +from ..packages import six +from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE + +SSLContext = None +SSLTransport = None +HAS_SNI = False +IS_PYOPENSSL = False +IS_SECURETRANSPORT = False +ALPN_PROTOCOLS = ["http/1.1"] + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} + + +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for left, right in zip(bytearray(a), bytearray(b)): + result |= left ^ right + return result == 0 + + +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) + +try: # Test for SSL features + import ssl + from ssl import CERT_REQUIRED, wrap_socket +except ImportError: + pass + +try: + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + +try: + from .ssltransport import SSLTransport +except ImportError: + pass + + +try: # Platform-specific: Python 3.6 + from ssl import PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS +except ImportError: + try: + from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS + except ImportError: + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 + +try: + from ssl import PROTOCOL_TLS_CLIENT +except ImportError: + PROTOCOL_TLS_CLIENT = PROTOCOL_TLS + + +try: + from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + + +try: # OP_NO_TICKET was added in Python 3.6 + from ssl import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = 0x4000 + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs, DSS, and other +# insecure ciphers for security reasons. +# - NOTE: TLS 1.3 cipher suites are managed through a different interface +# not exposed by CPython (yet!) and are enabled by default if they're available. +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + if cadata is not None: + raise SSLError("CA data not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, + ) + kwargs = { + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not _const_compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) + ) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_REQUIRED`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_REQUIRED + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "CERT_" + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_TLS + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "PROTOCOL_" + candidate) + return res + + return candidate + + +def create_urllib3_context( + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from pip._vendor.urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + # PROTOCOL_TLS is deprecated in Python 3.10 + if not ssl_version or ssl_version == PROTOCOL_TLS: + ssl_version = PROTOCOL_TLS_CLIENT + + context = SSLContext(ssl_version) + + context.set_ciphers(ciphers or DEFAULT_CIPHERS) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET + + context.options |= options + + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: + context.post_handshake_auth = True + + def disable_check_hostname(): + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more + # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used + # or not so we don't know the initial state of the freshly created SSLContext. + if cert_reqs == ssl.CERT_REQUIRED: + context.verify_mode = cert_reqs + disable_check_hostname() + else: + disable_check_hostname() + context.verify_mode = cert_reqs + + # Enable logging of TLS session keys via defacto standard environment variable + # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. + if hasattr(context, "keylog_filename"): + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + + return context + + +def ssl_wrap_socket( + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, + ca_cert_data=None, + tls_in_tls=False, +): + """ + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) + + if ca_certs or ca_cert_dir or ca_cert_data: + try: + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except (IOError, OSError) as e: + raise SSLError(e) + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows (require Python3.4+) + context.load_default_certs() + + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + + if certfile: + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols + pass + + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) + # SecureTransport uses server_hostname in certificate verification. + send_sni = (use_sni_hostname and HAS_SNI) or ( + IS_SECURETRANSPORT and server_hostname + ) + # Do not warn the user if server_hostname is an invalid SNI hostname. + if not HAS_SNI and use_sni_hostname: + warnings.warn( + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, + ) + + if send_sni: + ssl_sock = _ssl_wrap_socket_impl( + sock, context, tls_in_tls, server_hostname=server_hostname + ) + else: + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) + return ssl_sock + + +def is_ipaddress(hostname): + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if not six.PY2 and isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode("ascii") + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + + +def _is_key_file_encrypted(key_file): + """Detects if a key file is encrypted or not.""" + with open(key_file, "r") as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + + return False + + +def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + if server_hostname: + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + else: + return ssl_context.wrap_socket(sock) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py new file mode 100644 index 0000000..a4b4a56 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py @@ -0,0 +1,161 @@ +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html + +import re +import sys + +# ipaddress has been backported to 2.6+ in pypi. If it is installed on the +# system, use it to handle IPAddress ServerAltnames (this was added in +# python-3.5) otherwise only do DNS matching. This allows +# util.ssl_match_hostname to continue to be used in Python 2.7. +try: + import ipaddress +except ImportError: + ipaddress = None + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r".") + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count("*") + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn) + ) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == "*": + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) + return pat.match(hostname) + + +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + # ignored flake8 # F821 to support python 2.7 function + obj = unicode(obj, encoding="ascii", errors="strict") # noqa: F821 + return obj + + +def _ipaddress_match(ipname, host_ip): + """Exact matching of IP addresses. + + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) + try: + # Divergence from upstream: ipaddress can't handle byte str + host_ip = ipaddress.ip_address(_to_unicode(hostname)) + except ValueError: + # Not an IP address (common case) + host_ip = None + except UnicodeError: + # Divergence from upstream: Have to deal with ipaddress not taking + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case + host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: + raise + dnsnames = [] + san = cert.get("subjectAltName", ()) + for key, value in san: + if key == "DNS": + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == "IP Address": + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get("subject", ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) + else: + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssltransport.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssltransport.py new file mode 100644 index 0000000..4a7105d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/ssltransport.py @@ -0,0 +1,221 @@ +import io +import socket +import ssl + +from ..exceptions import ProxySchemeUnsupported +from ..packages import six + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context): + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + if six.PY2: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "supported on Python 2" + ) + else: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True + ): + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self): + return self + + def __exit__(self, *_): + self.close() + + def fileno(self): + return self.socket.fileno() + + def read(self, len=1024, buffer=None): + return self._wrap_ssl_read(len, buffer) + + def recv(self, len=1024, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(len) + + def recv_into(self, buffer, nbytes=None, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if buffer and (nbytes is None): + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + return self.read(nbytes, buffer) + + def sendall(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + response = self._ssl_io_loop(self.sslobj.write, data) + return response + + def makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None + ): + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) + self.socket._io_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text + + def unwrap(self): + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self): + self.socket.close() + + def getpeercert(self, binary_form=False): + return self.sslobj.getpeercert(binary_form) + + def version(self): + return self.sslobj.version() + + def cipher(self): + return self.sslobj.cipher() + + def selected_alpn_protocol(self): + return self.sslobj.selected_alpn_protocol() + + def selected_npn_protocol(self): + return self.sslobj.selected_npn_protocol() + + def shared_ciphers(self): + return self.sslobj.shared_ciphers() + + def compression(self): + return self.sslobj.compression() + + def settimeout(self, value): + self.socket.settimeout(value) + + def gettimeout(self): + return self.socket.gettimeout() + + def _decref_socketios(self): + self.socket._decref_socketios() + + def _wrap_ssl_read(self, len, buffer=None): + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + def _ssl_io_loop(self, func, *args): + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + ret = func(*args) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return ret diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/timeout.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/timeout.py new file mode 100644 index 0000000..ff69593 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/timeout.py @@ -0,0 +1,268 @@ +from __future__ import absolute_import + +import time + +# The default socket timeout, used by httplib to indicate that no timeout was +# specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT + +from ..exceptions import TimeoutStateError + +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() + + +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) + + +class Timeout(object): + """Timeout configuration. + + Timeouts can be defined as a default for a pool: + + .. code-block:: python + + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: int, float, or None + + :param connect: + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: int, float, or None + + :param read: + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: int, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect = None + + def __repr__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) + + # __str__ provided for backwards compatibility + __str__ = __repr__ + + @classmethod + def _validate_timeout(cls, value, name): + """Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) + try: + float(value) + except (TypeError, ValueError): + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + try: + if value <= 0: + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + # Python 3 + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + return value + + @classmethod + def from_float(cls, timeout): + """Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, sentinel default object, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, total=self.total) + + def start_connect(self): + """Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time in seconds. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if ( + self.total is not None + and self.total is not self.DEFAULT_TIMEOUT + and self._read is not None + and self._read is not self.DEFAULT_TIMEOUT + ): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/url.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/url.py new file mode 100644 index 0000000..3651c43 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/url.py @@ -0,0 +1,432 @@ +from __future__ import absolute_import + +import re +from collections import namedtuple + +from ..exceptions import LocationParseError +from ..packages import six + +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::([0-9]{0,5}))?$") % ( + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} + + +class Url(namedtuple("Url", url_attrs)): + """ + Data structure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + + __slots__ = () + + def __new__( + cls, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return "%s:%d" % (self.host, self.port) + return self.host + + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = u"" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + u"://" + if auth is not None: + url += auth + u"@" + if host is not None: + url += host + if port is not None: + url += u":" + str(port) + if path is not None: + url += path + if query is not None: + url += u"?" + query + if fragment is not None: + url += u"#" + fragment + + return url + + def __str__(self): + return self.url + + +def split_first(s, delims): + """ + .. deprecated:: 1.25 + + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, "", None + + return s[:min_idx], s[min_idx + 1 :], min_delim + + +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = six.ensure_text(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring on both Python 2 & 3 + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode(encoding) + + +def _remove_path_dot_segments(path): + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + elif segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +def _normalize_host(host, scheme): + if host: + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) + if is_ipv6: + match = ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] + else: + return host.lower() + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) + ) + return host + + +def _idna_encode(name): + if name and any([ord(x) > 128 for x in name]): + try: + from pip._vendor import idna + except ImportError: + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) + return name.lower().encode("ascii") + + +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) + if query is not None: + target += "?" + query + return target + + +def parse_url(url): + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. + + Partly backwards-compatible with :mod:`urlparse`. + + Example:: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + if not url: + # Empty + return Url() + + source_url = url + if not SCHEME_RE.search(url): + url = "//" + url + + try: + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES + + if scheme: + scheme = scheme.lower() + + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None + + if port is not None: + port = int(port) + if not (0 <= port <= 65535): + raise LocationParseError(url) + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) + + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. + if not path: + if query is not None or fragment is not None: + path = "" + else: + path = None + + # Ensure that each part of the URL is a `str` for + # backwards compatibility. + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str + + def ensure_type(x): + return x if x is None else ensure_func(x) + + return Url( + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), + ) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or "http", p.hostname, p.port diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/wait.py b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/wait.py new file mode 100644 index 0000000..c280646 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/urllib3/util/wait.py @@ -0,0 +1,153 @@ +import errno +import select +import sys +from functools import partial + +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) + + +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = _retry_on_intr(fn, timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t): + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(_retry_on_intr(do_poll, timeout)) + + +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") + + +def _have_working_poll(): + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + _retry_on_intr(poll_obj.poll, 0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket(*args, **kwargs): + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) + + +def wait_for_read(sock, timeout=None): + """Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock, timeout=None): + """Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/vendor.txt b/.venv/lib/python3.8/site-packages/pip/_vendor/vendor.txt new file mode 100644 index 0000000..2c93c0f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/vendor.txt @@ -0,0 +1,25 @@ +CacheControl==0.12.10 # Make sure to update the license in pyproject.toml for this. +colorama==0.4.4 +distlib==0.3.3 +distro==1.6.0 +html5lib==1.1 +msgpack==1.0.3 +packaging==21.3 +pep517==0.12.0 +platformdirs==2.4.1 +progress==1.6 +pyparsing==3.0.7 +requests==2.27.1 + certifi==2021.10.08 + chardet==4.0.0 + idna==3.3 + urllib3==1.26.8 +rich==11.0.0 + pygments==2.11.2 + typing_extensions==4.0.1 +resolvelib==0.8.1 +setuptools==44.0.0 +six==1.16.0 +tenacity==8.0.1 +tomli==1.0.3 +webencodings==0.5.1 diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__init__.py b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__init__.py new file mode 100644 index 0000000..d21d697 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__init__.py @@ -0,0 +1,342 @@ +# coding: utf-8 +""" + + webencodings + ~~~~~~~~~~~~ + + This is a Python implementation of the `WHATWG Encoding standard + `. See README for details. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +import codecs + +from .labels import LABELS + + +VERSION = '0.5.1' + + +# Some names in Encoding are not valid Python aliases. Remap these. +PYTHON_NAMES = { + 'iso-8859-8-i': 'iso-8859-8', + 'x-mac-cyrillic': 'mac-cyrillic', + 'macintosh': 'mac-roman', + 'windows-874': 'cp874'} + +CACHE = {} + + +def ascii_lower(string): + r"""Transform (only) ASCII letters to lower case: A-Z is mapped to a-z. + + :param string: An Unicode string. + :returns: A new Unicode string. + + This is used for `ASCII case-insensitive + `_ + matching of encoding labels. + The same matching is also used, among other things, + for `CSS keywords `_. + + This is different from the :meth:`~py:str.lower` method of Unicode strings + which also affect non-ASCII characters, + sometimes mapping them into the ASCII range: + + >>> keyword = u'Bac\N{KELVIN SIGN}ground' + >>> assert keyword.lower() == u'background' + >>> assert ascii_lower(keyword) != keyword.lower() + >>> assert ascii_lower(keyword) == u'bac\N{KELVIN SIGN}ground' + + """ + # This turns out to be faster than unicode.translate() + return string.encode('utf8').lower().decode('utf8') + + +def lookup(label): + """ + Look for an encoding by its label. + This is the spec’s `get an encoding + `_ algorithm. + Supported labels are listed there. + + :param label: A string. + :returns: + An :class:`Encoding` object, or :obj:`None` for an unknown label. + + """ + # Only strip ASCII whitespace: U+0009, U+000A, U+000C, U+000D, and U+0020. + label = ascii_lower(label.strip('\t\n\f\r ')) + name = LABELS.get(label) + if name is None: + return None + encoding = CACHE.get(name) + if encoding is None: + if name == 'x-user-defined': + from .x_user_defined import codec_info + else: + python_name = PYTHON_NAMES.get(name, name) + # Any python_name value that gets to here should be valid. + codec_info = codecs.lookup(python_name) + encoding = Encoding(name, codec_info) + CACHE[name] = encoding + return encoding + + +def _get_encoding(encoding_or_label): + """ + Accept either an encoding object or label. + + :param encoding: An :class:`Encoding` object or a label string. + :returns: An :class:`Encoding` object. + :raises: :exc:`~exceptions.LookupError` for an unknown label. + + """ + if hasattr(encoding_or_label, 'codec_info'): + return encoding_or_label + + encoding = lookup(encoding_or_label) + if encoding is None: + raise LookupError('Unknown encoding label: %r' % encoding_or_label) + return encoding + + +class Encoding(object): + """Reresents a character encoding such as UTF-8, + that can be used for decoding or encoding. + + .. attribute:: name + + Canonical name of the encoding + + .. attribute:: codec_info + + The actual implementation of the encoding, + a stdlib :class:`~codecs.CodecInfo` object. + See :func:`codecs.register`. + + """ + def __init__(self, name, codec_info): + self.name = name + self.codec_info = codec_info + + def __repr__(self): + return '' % self.name + + +#: The UTF-8 encoding. Should be used for new content and formats. +UTF8 = lookup('utf-8') + +_UTF16LE = lookup('utf-16le') +_UTF16BE = lookup('utf-16be') + + +def decode(input, fallback_encoding, errors='replace'): + """ + Decode a single string. + + :param input: A byte string + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :return: + A ``(output, encoding)`` tuple of an Unicode string + and an :obj:`Encoding`. + + """ + # Fail early if `encoding` is an invalid label. + fallback_encoding = _get_encoding(fallback_encoding) + bom_encoding, input = _detect_bom(input) + encoding = bom_encoding or fallback_encoding + return encoding.codec_info.decode(input, errors)[0], encoding + + +def _detect_bom(input): + """Return (bom_encoding, input), with any BOM removed from the input.""" + if input.startswith(b'\xFF\xFE'): + return _UTF16LE, input[2:] + if input.startswith(b'\xFE\xFF'): + return _UTF16BE, input[2:] + if input.startswith(b'\xEF\xBB\xBF'): + return UTF8, input[3:] + return None, input + + +def encode(input, encoding=UTF8, errors='strict'): + """ + Encode a single string. + + :param input: An Unicode string. + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :return: A byte string. + + """ + return _get_encoding(encoding).codec_info.encode(input, errors)[0] + + +def iter_decode(input, fallback_encoding, errors='replace'): + """ + "Pull"-based decoder. + + :param input: + An iterable of byte strings. + + The input is first consumed just enough to determine the encoding + based on the precense of a BOM, + then consumed on demand when the return value is. + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :returns: + An ``(output, encoding)`` tuple. + :obj:`output` is an iterable of Unicode strings, + :obj:`encoding` is the :obj:`Encoding` that is being used. + + """ + + decoder = IncrementalDecoder(fallback_encoding, errors) + generator = _iter_decode_generator(input, decoder) + encoding = next(generator) + return generator, encoding + + +def _iter_decode_generator(input, decoder): + """Return a generator that first yields the :obj:`Encoding`, + then yields output chukns as Unicode strings. + + """ + decode = decoder.decode + input = iter(input) + for chunck in input: + output = decode(chunck) + if output: + assert decoder.encoding is not None + yield decoder.encoding + yield output + break + else: + # Input exhausted without determining the encoding + output = decode(b'', final=True) + assert decoder.encoding is not None + yield decoder.encoding + if output: + yield output + return + + for chunck in input: + output = decode(chunck) + if output: + yield output + output = decode(b'', final=True) + if output: + yield output + + +def iter_encode(input, encoding=UTF8, errors='strict'): + """ + “Pullâ€-based encoder. + + :param input: An iterable of Unicode strings. + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :returns: An iterable of byte strings. + + """ + # Fail early if `encoding` is an invalid label. + encode = IncrementalEncoder(encoding, errors).encode + return _iter_encode_generator(input, encode) + + +def _iter_encode_generator(input, encode): + for chunck in input: + output = encode(chunck) + if output: + yield output + output = encode('', final=True) + if output: + yield output + + +class IncrementalDecoder(object): + """ + “Pushâ€-based decoder. + + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + + """ + def __init__(self, fallback_encoding, errors='replace'): + # Fail early if `encoding` is an invalid label. + self._fallback_encoding = _get_encoding(fallback_encoding) + self._errors = errors + self._buffer = b'' + self._decoder = None + #: The actual :class:`Encoding` that is being used, + #: or :obj:`None` if that is not determined yet. + #: (Ie. if there is not enough input yet to determine + #: if there is a BOM.) + self.encoding = None # Not known yet. + + def decode(self, input, final=False): + """Decode one chunk of the input. + + :param input: A byte string. + :param final: + Indicate that no more input is available. + Must be :obj:`True` if this is the last call. + :returns: An Unicode string. + + """ + decoder = self._decoder + if decoder is not None: + return decoder(input, final) + + input = self._buffer + input + encoding, input = _detect_bom(input) + if encoding is None: + if len(input) < 3 and not final: # Not enough data yet. + self._buffer = input + return '' + else: # No BOM + encoding = self._fallback_encoding + decoder = encoding.codec_info.incrementaldecoder(self._errors).decode + self._decoder = decoder + self.encoding = encoding + return decoder(input, final) + + +class IncrementalEncoder(object): + """ + “Pushâ€-based encoder. + + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + + .. method:: encode(input, final=False) + + :param input: An Unicode string. + :param final: + Indicate that no more input is available. + Must be :obj:`True` if this is the last call. + :returns: A byte string. + + """ + def __init__(self, encoding=UTF8, errors='strict'): + encoding = _get_encoding(encoding) + self.encode = encoding.codec_info.incrementalencoder(errors).encode diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..bf9e0db Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/labels.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/labels.cpython-38.pyc new file mode 100644 index 0000000..552ca37 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/labels.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/mklabels.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/mklabels.cpython-38.pyc new file mode 100644 index 0000000..90bb298 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/mklabels.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/tests.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/tests.cpython-38.pyc new file mode 100644 index 0000000..a0bf0e2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/tests.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/x_user_defined.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/x_user_defined.cpython-38.pyc new file mode 100644 index 0000000..f0eb80a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/__pycache__/x_user_defined.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/labels.py b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/labels.py new file mode 100644 index 0000000..29cbf91 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/labels.py @@ -0,0 +1,231 @@ +""" + + webencodings.labels + ~~~~~~~~~~~~~~~~~~~ + + Map encoding labels to their name. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +# XXX Do not edit! +# This file is automatically generated by mklabels.py + +LABELS = { + 'unicode-1-1-utf-8': 'utf-8', + 'utf-8': 'utf-8', + 'utf8': 'utf-8', + '866': 'ibm866', + 'cp866': 'ibm866', + 'csibm866': 'ibm866', + 'ibm866': 'ibm866', + 'csisolatin2': 'iso-8859-2', + 'iso-8859-2': 'iso-8859-2', + 'iso-ir-101': 'iso-8859-2', + 'iso8859-2': 'iso-8859-2', + 'iso88592': 'iso-8859-2', + 'iso_8859-2': 'iso-8859-2', + 'iso_8859-2:1987': 'iso-8859-2', + 'l2': 'iso-8859-2', + 'latin2': 'iso-8859-2', + 'csisolatin3': 'iso-8859-3', + 'iso-8859-3': 'iso-8859-3', + 'iso-ir-109': 'iso-8859-3', + 'iso8859-3': 'iso-8859-3', + 'iso88593': 'iso-8859-3', + 'iso_8859-3': 'iso-8859-3', + 'iso_8859-3:1988': 'iso-8859-3', + 'l3': 'iso-8859-3', + 'latin3': 'iso-8859-3', + 'csisolatin4': 'iso-8859-4', + 'iso-8859-4': 'iso-8859-4', + 'iso-ir-110': 'iso-8859-4', + 'iso8859-4': 'iso-8859-4', + 'iso88594': 'iso-8859-4', + 'iso_8859-4': 'iso-8859-4', + 'iso_8859-4:1988': 'iso-8859-4', + 'l4': 'iso-8859-4', + 'latin4': 'iso-8859-4', + 'csisolatincyrillic': 'iso-8859-5', + 'cyrillic': 'iso-8859-5', + 'iso-8859-5': 'iso-8859-5', + 'iso-ir-144': 'iso-8859-5', + 'iso8859-5': 'iso-8859-5', + 'iso88595': 'iso-8859-5', + 'iso_8859-5': 'iso-8859-5', + 'iso_8859-5:1988': 'iso-8859-5', + 'arabic': 'iso-8859-6', + 'asmo-708': 'iso-8859-6', + 'csiso88596e': 'iso-8859-6', + 'csiso88596i': 'iso-8859-6', + 'csisolatinarabic': 'iso-8859-6', + 'ecma-114': 'iso-8859-6', + 'iso-8859-6': 'iso-8859-6', + 'iso-8859-6-e': 'iso-8859-6', + 'iso-8859-6-i': 'iso-8859-6', + 'iso-ir-127': 'iso-8859-6', + 'iso8859-6': 'iso-8859-6', + 'iso88596': 'iso-8859-6', + 'iso_8859-6': 'iso-8859-6', + 'iso_8859-6:1987': 'iso-8859-6', + 'csisolatingreek': 'iso-8859-7', + 'ecma-118': 'iso-8859-7', + 'elot_928': 'iso-8859-7', + 'greek': 'iso-8859-7', + 'greek8': 'iso-8859-7', + 'iso-8859-7': 'iso-8859-7', + 'iso-ir-126': 'iso-8859-7', + 'iso8859-7': 'iso-8859-7', + 'iso88597': 'iso-8859-7', + 'iso_8859-7': 'iso-8859-7', + 'iso_8859-7:1987': 'iso-8859-7', + 'sun_eu_greek': 'iso-8859-7', + 'csiso88598e': 'iso-8859-8', + 'csisolatinhebrew': 'iso-8859-8', + 'hebrew': 'iso-8859-8', + 'iso-8859-8': 'iso-8859-8', + 'iso-8859-8-e': 'iso-8859-8', + 'iso-ir-138': 'iso-8859-8', + 'iso8859-8': 'iso-8859-8', + 'iso88598': 'iso-8859-8', + 'iso_8859-8': 'iso-8859-8', + 'iso_8859-8:1988': 'iso-8859-8', + 'visual': 'iso-8859-8', + 'csiso88598i': 'iso-8859-8-i', + 'iso-8859-8-i': 'iso-8859-8-i', + 'logical': 'iso-8859-8-i', + 'csisolatin6': 'iso-8859-10', + 'iso-8859-10': 'iso-8859-10', + 'iso-ir-157': 'iso-8859-10', + 'iso8859-10': 'iso-8859-10', + 'iso885910': 'iso-8859-10', + 'l6': 'iso-8859-10', + 'latin6': 'iso-8859-10', + 'iso-8859-13': 'iso-8859-13', + 'iso8859-13': 'iso-8859-13', + 'iso885913': 'iso-8859-13', + 'iso-8859-14': 'iso-8859-14', + 'iso8859-14': 'iso-8859-14', + 'iso885914': 'iso-8859-14', + 'csisolatin9': 'iso-8859-15', + 'iso-8859-15': 'iso-8859-15', + 'iso8859-15': 'iso-8859-15', + 'iso885915': 'iso-8859-15', + 'iso_8859-15': 'iso-8859-15', + 'l9': 'iso-8859-15', + 'iso-8859-16': 'iso-8859-16', + 'cskoi8r': 'koi8-r', + 'koi': 'koi8-r', + 'koi8': 'koi8-r', + 'koi8-r': 'koi8-r', + 'koi8_r': 'koi8-r', + 'koi8-u': 'koi8-u', + 'csmacintosh': 'macintosh', + 'mac': 'macintosh', + 'macintosh': 'macintosh', + 'x-mac-roman': 'macintosh', + 'dos-874': 'windows-874', + 'iso-8859-11': 'windows-874', + 'iso8859-11': 'windows-874', + 'iso885911': 'windows-874', + 'tis-620': 'windows-874', + 'windows-874': 'windows-874', + 'cp1250': 'windows-1250', + 'windows-1250': 'windows-1250', + 'x-cp1250': 'windows-1250', + 'cp1251': 'windows-1251', + 'windows-1251': 'windows-1251', + 'x-cp1251': 'windows-1251', + 'ansi_x3.4-1968': 'windows-1252', + 'ascii': 'windows-1252', + 'cp1252': 'windows-1252', + 'cp819': 'windows-1252', + 'csisolatin1': 'windows-1252', + 'ibm819': 'windows-1252', + 'iso-8859-1': 'windows-1252', + 'iso-ir-100': 'windows-1252', + 'iso8859-1': 'windows-1252', + 'iso88591': 'windows-1252', + 'iso_8859-1': 'windows-1252', + 'iso_8859-1:1987': 'windows-1252', + 'l1': 'windows-1252', + 'latin1': 'windows-1252', + 'us-ascii': 'windows-1252', + 'windows-1252': 'windows-1252', + 'x-cp1252': 'windows-1252', + 'cp1253': 'windows-1253', + 'windows-1253': 'windows-1253', + 'x-cp1253': 'windows-1253', + 'cp1254': 'windows-1254', + 'csisolatin5': 'windows-1254', + 'iso-8859-9': 'windows-1254', + 'iso-ir-148': 'windows-1254', + 'iso8859-9': 'windows-1254', + 'iso88599': 'windows-1254', + 'iso_8859-9': 'windows-1254', + 'iso_8859-9:1989': 'windows-1254', + 'l5': 'windows-1254', + 'latin5': 'windows-1254', + 'windows-1254': 'windows-1254', + 'x-cp1254': 'windows-1254', + 'cp1255': 'windows-1255', + 'windows-1255': 'windows-1255', + 'x-cp1255': 'windows-1255', + 'cp1256': 'windows-1256', + 'windows-1256': 'windows-1256', + 'x-cp1256': 'windows-1256', + 'cp1257': 'windows-1257', + 'windows-1257': 'windows-1257', + 'x-cp1257': 'windows-1257', + 'cp1258': 'windows-1258', + 'windows-1258': 'windows-1258', + 'x-cp1258': 'windows-1258', + 'x-mac-cyrillic': 'x-mac-cyrillic', + 'x-mac-ukrainian': 'x-mac-cyrillic', + 'chinese': 'gbk', + 'csgb2312': 'gbk', + 'csiso58gb231280': 'gbk', + 'gb2312': 'gbk', + 'gb_2312': 'gbk', + 'gb_2312-80': 'gbk', + 'gbk': 'gbk', + 'iso-ir-58': 'gbk', + 'x-gbk': 'gbk', + 'gb18030': 'gb18030', + 'hz-gb-2312': 'hz-gb-2312', + 'big5': 'big5', + 'big5-hkscs': 'big5', + 'cn-big5': 'big5', + 'csbig5': 'big5', + 'x-x-big5': 'big5', + 'cseucpkdfmtjapanese': 'euc-jp', + 'euc-jp': 'euc-jp', + 'x-euc-jp': 'euc-jp', + 'csiso2022jp': 'iso-2022-jp', + 'iso-2022-jp': 'iso-2022-jp', + 'csshiftjis': 'shift_jis', + 'ms_kanji': 'shift_jis', + 'shift-jis': 'shift_jis', + 'shift_jis': 'shift_jis', + 'sjis': 'shift_jis', + 'windows-31j': 'shift_jis', + 'x-sjis': 'shift_jis', + 'cseuckr': 'euc-kr', + 'csksc56011987': 'euc-kr', + 'euc-kr': 'euc-kr', + 'iso-ir-149': 'euc-kr', + 'korean': 'euc-kr', + 'ks_c_5601-1987': 'euc-kr', + 'ks_c_5601-1989': 'euc-kr', + 'ksc5601': 'euc-kr', + 'ksc_5601': 'euc-kr', + 'windows-949': 'euc-kr', + 'csiso2022kr': 'iso-2022-kr', + 'iso-2022-kr': 'iso-2022-kr', + 'utf-16be': 'utf-16be', + 'utf-16': 'utf-16le', + 'utf-16le': 'utf-16le', + 'x-user-defined': 'x-user-defined', +} diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/mklabels.py b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/mklabels.py new file mode 100644 index 0000000..295dc92 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/mklabels.py @@ -0,0 +1,59 @@ +""" + + webencodings.mklabels + ~~~~~~~~~~~~~~~~~~~~~ + + Regenarate the webencodings.labels module. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +import json +try: + from urllib import urlopen +except ImportError: + from urllib.request import urlopen + + +def assert_lower(string): + assert string == string.lower() + return string + + +def generate(url): + parts = ['''\ +""" + + webencodings.labels + ~~~~~~~~~~~~~~~~~~~ + + Map encoding labels to their name. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +# XXX Do not edit! +# This file is automatically generated by mklabels.py + +LABELS = { +'''] + labels = [ + (repr(assert_lower(label)).lstrip('u'), + repr(encoding['name']).lstrip('u')) + for category in json.loads(urlopen(url).read().decode('ascii')) + for encoding in category['encodings'] + for label in encoding['labels']] + max_len = max(len(label) for label, name in labels) + parts.extend( + ' %s:%s %s,\n' % (label, ' ' * (max_len - len(label)), name) + for label, name in labels) + parts.append('}') + return ''.join(parts) + + +if __name__ == '__main__': + print(generate('http://encoding.spec.whatwg.org/encodings.json')) diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/tests.py b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/tests.py new file mode 100644 index 0000000..e12c10d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/tests.py @@ -0,0 +1,153 @@ +# coding: utf-8 +""" + + webencodings.tests + ~~~~~~~~~~~~~~~~~~ + + A basic test suite for Encoding. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +from . import (lookup, LABELS, decode, encode, iter_decode, iter_encode, + IncrementalDecoder, IncrementalEncoder, UTF8) + + +def assert_raises(exception, function, *args, **kwargs): + try: + function(*args, **kwargs) + except exception: + return + else: # pragma: no cover + raise AssertionError('Did not raise %s.' % exception) + + +def test_labels(): + assert lookup('utf-8').name == 'utf-8' + assert lookup('Utf-8').name == 'utf-8' + assert lookup('UTF-8').name == 'utf-8' + assert lookup('utf8').name == 'utf-8' + assert lookup('utf8').name == 'utf-8' + assert lookup('utf8 ').name == 'utf-8' + assert lookup(' \r\nutf8\t').name == 'utf-8' + assert lookup('u8') is None # Python label. + assert lookup('utf-8 ') is None # Non-ASCII white space. + + assert lookup('US-ASCII').name == 'windows-1252' + assert lookup('iso-8859-1').name == 'windows-1252' + assert lookup('latin1').name == 'windows-1252' + assert lookup('LATIN1').name == 'windows-1252' + assert lookup('latin-1') is None + assert lookup('LATİN1') is None # ASCII-only case insensitivity. + + +def test_all_labels(): + for label in LABELS: + assert decode(b'', label) == ('', lookup(label)) + assert encode('', label) == b'' + for repeat in [0, 1, 12]: + output, _ = iter_decode([b''] * repeat, label) + assert list(output) == [] + assert list(iter_encode([''] * repeat, label)) == [] + decoder = IncrementalDecoder(label) + assert decoder.decode(b'') == '' + assert decoder.decode(b'', final=True) == '' + encoder = IncrementalEncoder(label) + assert encoder.encode('') == b'' + assert encoder.encode('', final=True) == b'' + # All encoding names are valid labels too: + for name in set(LABELS.values()): + assert lookup(name).name == name + + +def test_invalid_label(): + assert_raises(LookupError, decode, b'\xEF\xBB\xBF\xc3\xa9', 'invalid') + assert_raises(LookupError, encode, 'é', 'invalid') + assert_raises(LookupError, iter_decode, [], 'invalid') + assert_raises(LookupError, iter_encode, [], 'invalid') + assert_raises(LookupError, IncrementalDecoder, 'invalid') + assert_raises(LookupError, IncrementalEncoder, 'invalid') + + +def test_decode(): + assert decode(b'\x80', 'latin1') == ('€', lookup('latin1')) + assert decode(b'\x80', lookup('latin1')) == ('€', lookup('latin1')) + assert decode(b'\xc3\xa9', 'utf8') == ('é', lookup('utf8')) + assert decode(b'\xc3\xa9', UTF8) == ('é', lookup('utf8')) + assert decode(b'\xc3\xa9', 'ascii') == ('é', lookup('ascii')) + assert decode(b'\xEF\xBB\xBF\xc3\xa9', 'ascii') == ('é', lookup('utf8')) # UTF-8 with BOM + + assert decode(b'\xFE\xFF\x00\xe9', 'ascii') == ('é', lookup('utf-16be')) # UTF-16-BE with BOM + assert decode(b'\xFF\xFE\xe9\x00', 'ascii') == ('é', lookup('utf-16le')) # UTF-16-LE with BOM + assert decode(b'\xFE\xFF\xe9\x00', 'ascii') == ('\ue900', lookup('utf-16be')) + assert decode(b'\xFF\xFE\x00\xe9', 'ascii') == ('\ue900', lookup('utf-16le')) + + assert decode(b'\x00\xe9', 'UTF-16BE') == ('é', lookup('utf-16be')) + assert decode(b'\xe9\x00', 'UTF-16LE') == ('é', lookup('utf-16le')) + assert decode(b'\xe9\x00', 'UTF-16') == ('é', lookup('utf-16le')) + + assert decode(b'\xe9\x00', 'UTF-16BE') == ('\ue900', lookup('utf-16be')) + assert decode(b'\x00\xe9', 'UTF-16LE') == ('\ue900', lookup('utf-16le')) + assert decode(b'\x00\xe9', 'UTF-16') == ('\ue900', lookup('utf-16le')) + + +def test_encode(): + assert encode('é', 'latin1') == b'\xe9' + assert encode('é', 'utf8') == b'\xc3\xa9' + assert encode('é', 'utf8') == b'\xc3\xa9' + assert encode('é', 'utf-16') == b'\xe9\x00' + assert encode('é', 'utf-16le') == b'\xe9\x00' + assert encode('é', 'utf-16be') == b'\x00\xe9' + + +def test_iter_decode(): + def iter_decode_to_string(input, fallback_encoding): + output, _encoding = iter_decode(input, fallback_encoding) + return ''.join(output) + assert iter_decode_to_string([], 'latin1') == '' + assert iter_decode_to_string([b''], 'latin1') == '' + assert iter_decode_to_string([b'\xe9'], 'latin1') == 'é' + assert iter_decode_to_string([b'hello'], 'latin1') == 'hello' + assert iter_decode_to_string([b'he', b'llo'], 'latin1') == 'hello' + assert iter_decode_to_string([b'hell', b'o'], 'latin1') == 'hello' + assert iter_decode_to_string([b'\xc3\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xEF\xBB\xBF\xc3\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'\xEF\xBB\xBF', b'\xc3', b'\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'\xEF\xBB\xBF', b'a', b'\xc3'], 'latin1') == 'a\uFFFD' + assert iter_decode_to_string([ + b'', b'\xEF', b'', b'', b'\xBB\xBF\xc3', b'\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xEF\xBB\xBF'], 'latin1') == '' + assert iter_decode_to_string([b'\xEF\xBB'], 'latin1') == 'ï»' + assert iter_decode_to_string([b'\xFE\xFF\x00\xe9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xFF\xFE\xe9\x00'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'', b'\xFF', b'', b'', b'\xFE\xe9', b'\x00'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'', b'h\xe9', b'llo'], 'x-user-defined') == 'h\uF7E9llo' + + +def test_iter_encode(): + assert b''.join(iter_encode([], 'latin1')) == b'' + assert b''.join(iter_encode([''], 'latin1')) == b'' + assert b''.join(iter_encode(['é'], 'latin1')) == b'\xe9' + assert b''.join(iter_encode(['', 'é', '', ''], 'latin1')) == b'\xe9' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16')) == b'\xe9\x00' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16le')) == b'\xe9\x00' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16be')) == b'\x00\xe9' + assert b''.join(iter_encode([ + '', 'h\uF7E9', '', 'llo'], 'x-user-defined')) == b'h\xe9llo' + + +def test_x_user_defined(): + encoded = b'2,\x0c\x0b\x1aO\xd9#\xcb\x0f\xc9\xbbt\xcf\xa8\xca' + decoded = '2,\x0c\x0b\x1aO\uf7d9#\uf7cb\x0f\uf7c9\uf7bbt\uf7cf\uf7a8\uf7ca' + encoded = b'aa' + decoded = 'aa' + assert decode(encoded, 'x-user-defined') == (decoded, lookup('x-user-defined')) + assert encode(decoded, 'x-user-defined') == encoded diff --git a/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/x_user_defined.py b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/x_user_defined.py new file mode 100644 index 0000000..d16e326 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/_vendor/webencodings/x_user_defined.py @@ -0,0 +1,325 @@ +# coding: utf-8 +""" + + webencodings.x_user_defined + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + An implementation of the x-user-defined encoding. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +import codecs + + +### Codec APIs + +class Codec(codecs.Codec): + + def encode(self, input, errors='strict'): + return codecs.charmap_encode(input, errors, encoding_table) + + def decode(self, input, errors='strict'): + return codecs.charmap_decode(input, errors, decoding_table) + + +class IncrementalEncoder(codecs.IncrementalEncoder): + def encode(self, input, final=False): + return codecs.charmap_encode(input, self.errors, encoding_table)[0] + + +class IncrementalDecoder(codecs.IncrementalDecoder): + def decode(self, input, final=False): + return codecs.charmap_decode(input, self.errors, decoding_table)[0] + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +### encodings module API + +codec_info = codecs.CodecInfo( + name='x-user-defined', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamreader=StreamReader, + streamwriter=StreamWriter, +) + + +### Decoding Table + +# Python 3: +# for c in range(256): print(' %r' % chr(c if c < 128 else c + 0xF700)) +decoding_table = ( + '\x00' + '\x01' + '\x02' + '\x03' + '\x04' + '\x05' + '\x06' + '\x07' + '\x08' + '\t' + '\n' + '\x0b' + '\x0c' + '\r' + '\x0e' + '\x0f' + '\x10' + '\x11' + '\x12' + '\x13' + '\x14' + '\x15' + '\x16' + '\x17' + '\x18' + '\x19' + '\x1a' + '\x1b' + '\x1c' + '\x1d' + '\x1e' + '\x1f' + ' ' + '!' + '"' + '#' + '$' + '%' + '&' + "'" + '(' + ')' + '*' + '+' + ',' + '-' + '.' + '/' + '0' + '1' + '2' + '3' + '4' + '5' + '6' + '7' + '8' + '9' + ':' + ';' + '<' + '=' + '>' + '?' + '@' + 'A' + 'B' + 'C' + 'D' + 'E' + 'F' + 'G' + 'H' + 'I' + 'J' + 'K' + 'L' + 'M' + 'N' + 'O' + 'P' + 'Q' + 'R' + 'S' + 'T' + 'U' + 'V' + 'W' + 'X' + 'Y' + 'Z' + '[' + '\\' + ']' + '^' + '_' + '`' + 'a' + 'b' + 'c' + 'd' + 'e' + 'f' + 'g' + 'h' + 'i' + 'j' + 'k' + 'l' + 'm' + 'n' + 'o' + 'p' + 'q' + 'r' + 's' + 't' + 'u' + 'v' + 'w' + 'x' + 'y' + 'z' + '{' + '|' + '}' + '~' + '\x7f' + '\uf780' + '\uf781' + '\uf782' + '\uf783' + '\uf784' + '\uf785' + '\uf786' + '\uf787' + '\uf788' + '\uf789' + '\uf78a' + '\uf78b' + '\uf78c' + '\uf78d' + '\uf78e' + '\uf78f' + '\uf790' + '\uf791' + '\uf792' + '\uf793' + '\uf794' + '\uf795' + '\uf796' + '\uf797' + '\uf798' + '\uf799' + '\uf79a' + '\uf79b' + '\uf79c' + '\uf79d' + '\uf79e' + '\uf79f' + '\uf7a0' + '\uf7a1' + '\uf7a2' + '\uf7a3' + '\uf7a4' + '\uf7a5' + '\uf7a6' + '\uf7a7' + '\uf7a8' + '\uf7a9' + '\uf7aa' + '\uf7ab' + '\uf7ac' + '\uf7ad' + '\uf7ae' + '\uf7af' + '\uf7b0' + '\uf7b1' + '\uf7b2' + '\uf7b3' + '\uf7b4' + '\uf7b5' + '\uf7b6' + '\uf7b7' + '\uf7b8' + '\uf7b9' + '\uf7ba' + '\uf7bb' + '\uf7bc' + '\uf7bd' + '\uf7be' + '\uf7bf' + '\uf7c0' + '\uf7c1' + '\uf7c2' + '\uf7c3' + '\uf7c4' + '\uf7c5' + '\uf7c6' + '\uf7c7' + '\uf7c8' + '\uf7c9' + '\uf7ca' + '\uf7cb' + '\uf7cc' + '\uf7cd' + '\uf7ce' + '\uf7cf' + '\uf7d0' + '\uf7d1' + '\uf7d2' + '\uf7d3' + '\uf7d4' + '\uf7d5' + '\uf7d6' + '\uf7d7' + '\uf7d8' + '\uf7d9' + '\uf7da' + '\uf7db' + '\uf7dc' + '\uf7dd' + '\uf7de' + '\uf7df' + '\uf7e0' + '\uf7e1' + '\uf7e2' + '\uf7e3' + '\uf7e4' + '\uf7e5' + '\uf7e6' + '\uf7e7' + '\uf7e8' + '\uf7e9' + '\uf7ea' + '\uf7eb' + '\uf7ec' + '\uf7ed' + '\uf7ee' + '\uf7ef' + '\uf7f0' + '\uf7f1' + '\uf7f2' + '\uf7f3' + '\uf7f4' + '\uf7f5' + '\uf7f6' + '\uf7f7' + '\uf7f8' + '\uf7f9' + '\uf7fa' + '\uf7fb' + '\uf7fc' + '\uf7fd' + '\uf7fe' + '\uf7ff' +) + +### Encoding table +encoding_table = codecs.charmap_build(decoding_table) diff --git a/.venv/lib/python3.8/site-packages/pip/py.typed b/.venv/lib/python3.8/site-packages/pip/py.typed new file mode 100644 index 0000000..493b53e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pip/py.typed @@ -0,0 +1,4 @@ +pip is a command line program. While it is implemented in Python, and so is +available for import, you must not use pip's internal APIs in this way. Typing +information is provided as a convenience only and is not a guarantee. Expect +unannounced changes to the API and types in releases. diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/__init__.py b/.venv/lib/python3.8/site-packages/pkg_resources/__init__.py new file mode 100644 index 0000000..c84f1dd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/__init__.py @@ -0,0 +1,3288 @@ +""" +Package resource API +-------------------- + +A resource is a logical file contained within a package, or a logical +subdirectory thereof. The package resource API expects resource names +to have their path parts separated with ``/``, *not* whatever the local +path separator is. Do not use os.path operations to manipulate resource +names being passed into the API. + +The package resource API is designed to work with normal filesystem packages, +.egg files, and unpacked .egg files. It can also work in a limited way with +.zip files and with custom PEP 302 loaders that support the ``get_data()`` +method. +""" + +import sys +import os +import io +import time +import re +import types +import zipfile +import zipimport +import warnings +import stat +import functools +import pkgutil +import operator +import platform +import collections +import plistlib +import email.parser +import errno +import tempfile +import textwrap +import itertools +import inspect +import ntpath +import posixpath +import importlib +from pkgutil import get_importer + +try: + import _imp +except ImportError: + # Python 3.2 compatibility + import imp as _imp + +try: + FileExistsError +except NameError: + FileExistsError = OSError + +# capture these to bypass sandboxing +from os import utime +try: + from os import mkdir, rename, unlink + WRITE_SUPPORT = True +except ImportError: + # no write support, probably under GAE + WRITE_SUPPORT = False + +from os import open as os_open +from os.path import isdir, split + +try: + import importlib.machinery as importlib_machinery + # access attribute to force import under delayed import mechanisms. + importlib_machinery.__name__ +except ImportError: + importlib_machinery = None + +from pkg_resources.extern import appdirs +from pkg_resources.extern import packaging +__import__('pkg_resources.extern.packaging.version') +__import__('pkg_resources.extern.packaging.specifiers') +__import__('pkg_resources.extern.packaging.requirements') +__import__('pkg_resources.extern.packaging.markers') + +if sys.version_info < (3, 5): + raise RuntimeError("Python 3.5 or later is required") + +# declare some globals that will be defined later to +# satisfy the linters. +require = None +working_set = None +add_activation_listener = None +resources_stream = None +cleanup_resources = None +resource_dir = None +resource_stream = None +set_extraction_path = None +resource_isdir = None +resource_string = None +iter_entry_points = None +resource_listdir = None +resource_filename = None +resource_exists = None +_distribution_finders = None +_namespace_handlers = None +_namespace_packages = None + + +class PEP440Warning(RuntimeWarning): + """ + Used when there is an issue with a version or specifier not complying with + PEP 440. + """ + + +def parse_version(v): + try: + return packaging.version.Version(v) + except packaging.version.InvalidVersion: + return packaging.version.LegacyVersion(v) + + +_state_vars = {} + + +def _declare_state(vartype, **kw): + globals().update(kw) + _state_vars.update(dict.fromkeys(kw, vartype)) + + +def __getstate__(): + state = {} + g = globals() + for k, v in _state_vars.items(): + state[k] = g['_sget_' + v](g[k]) + return state + + +def __setstate__(state): + g = globals() + for k, v in state.items(): + g['_sset_' + _state_vars[k]](k, g[k], v) + return state + + +def _sget_dict(val): + return val.copy() + + +def _sset_dict(key, ob, state): + ob.clear() + ob.update(state) + + +def _sget_object(val): + return val.__getstate__() + + +def _sset_object(key, ob, state): + ob.__setstate__(state) + + +_sget_none = _sset_none = lambda *args: None + + +def get_supported_platform(): + """Return this platform's maximum compatible version. + + distutils.util.get_platform() normally reports the minimum version + of macOS that would be required to *use* extensions produced by + distutils. But what we want when checking compatibility is to know the + version of macOS that we are *running*. To allow usage of packages that + explicitly require a newer version of macOS, we must also know the + current version of the OS. + + If this condition occurs for any other platform with a version in its + platform strings, this function should be extended accordingly. + """ + plat = get_build_platform() + m = macosVersionString.match(plat) + if m is not None and sys.platform == "darwin": + try: + plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3)) + except ValueError: + # not macOS + pass + return plat + + +__all__ = [ + # Basic resource access and distribution/entry point discovery + 'require', 'run_script', 'get_provider', 'get_distribution', + 'load_entry_point', 'get_entry_map', 'get_entry_info', + 'iter_entry_points', + 'resource_string', 'resource_stream', 'resource_filename', + 'resource_listdir', 'resource_exists', 'resource_isdir', + + # Environmental control + 'declare_namespace', 'working_set', 'add_activation_listener', + 'find_distributions', 'set_extraction_path', 'cleanup_resources', + 'get_default_cache', + + # Primary implementation classes + 'Environment', 'WorkingSet', 'ResourceManager', + 'Distribution', 'Requirement', 'EntryPoint', + + # Exceptions + 'ResolutionError', 'VersionConflict', 'DistributionNotFound', + 'UnknownExtra', 'ExtractionError', + + # Warnings + 'PEP440Warning', + + # Parsing functions and string utilities + 'parse_requirements', 'parse_version', 'safe_name', 'safe_version', + 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections', + 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker', + + # filesystem utilities + 'ensure_directory', 'normalize_path', + + # Distribution "precedence" constants + 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST', + + # "Provider" interfaces, implementations, and registration/lookup APIs + 'IMetadataProvider', 'IResourceProvider', 'FileMetadata', + 'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider', + 'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider', + 'register_finder', 'register_namespace_handler', 'register_loader_type', + 'fixup_namespace_packages', 'get_importer', + + # Warnings + 'PkgResourcesDeprecationWarning', + + # Deprecated/backward compatibility only + 'run_main', 'AvailableDistributions', +] + + +class ResolutionError(Exception): + """Abstract base for dependency resolution errors""" + + def __repr__(self): + return self.__class__.__name__ + repr(self.args) + + +class VersionConflict(ResolutionError): + """ + An already-installed version conflicts with the requested version. + + Should be initialized with the installed Distribution and the requested + Requirement. + """ + + _template = "{self.dist} is installed but {self.req} is required" + + @property + def dist(self): + return self.args[0] + + @property + def req(self): + return self.args[1] + + def report(self): + return self._template.format(**locals()) + + def with_context(self, required_by): + """ + If required_by is non-empty, return a version of self that is a + ContextualVersionConflict. + """ + if not required_by: + return self + args = self.args + (required_by,) + return ContextualVersionConflict(*args) + + +class ContextualVersionConflict(VersionConflict): + """ + A VersionConflict that accepts a third parameter, the set of the + requirements that required the installed Distribution. + """ + + _template = VersionConflict._template + ' by {self.required_by}' + + @property + def required_by(self): + return self.args[2] + + +class DistributionNotFound(ResolutionError): + """A requested distribution was not found""" + + _template = ("The '{self.req}' distribution was not found " + "and is required by {self.requirers_str}") + + @property + def req(self): + return self.args[0] + + @property + def requirers(self): + return self.args[1] + + @property + def requirers_str(self): + if not self.requirers: + return 'the application' + return ', '.join(self.requirers) + + def report(self): + return self._template.format(**locals()) + + def __str__(self): + return self.report() + + +class UnknownExtra(ResolutionError): + """Distribution doesn't have an "extra feature" of the given name""" + + +_provider_factories = {} + +PY_MAJOR = '{}.{}'.format(*sys.version_info) +EGG_DIST = 3 +BINARY_DIST = 2 +SOURCE_DIST = 1 +CHECKOUT_DIST = 0 +DEVELOP_DIST = -1 + + +def register_loader_type(loader_type, provider_factory): + """Register `provider_factory` to make providers for `loader_type` + + `loader_type` is the type or class of a PEP 302 ``module.__loader__``, + and `provider_factory` is a function that, passed a *module* object, + returns an ``IResourceProvider`` for that module. + """ + _provider_factories[loader_type] = provider_factory + + +def get_provider(moduleOrReq): + """Return an IResourceProvider for the named module or requirement""" + if isinstance(moduleOrReq, Requirement): + return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0] + try: + module = sys.modules[moduleOrReq] + except KeyError: + __import__(moduleOrReq) + module = sys.modules[moduleOrReq] + loader = getattr(module, '__loader__', None) + return _find_adapter(_provider_factories, loader)(module) + + +def _macos_vers(_cache=[]): + if not _cache: + version = platform.mac_ver()[0] + # fallback for MacPorts + if version == '': + plist = '/System/Library/CoreServices/SystemVersion.plist' + if os.path.exists(plist): + if hasattr(plistlib, 'readPlist'): + plist_content = plistlib.readPlist(plist) + if 'ProductVersion' in plist_content: + version = plist_content['ProductVersion'] + + _cache.append(version.split('.')) + return _cache[0] + + +def _macos_arch(machine): + return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine) + + +def get_build_platform(): + """Return this platform's string for platform-specific distributions + + XXX Currently this is the same as ``distutils.util.get_platform()``, but it + needs some hacks for Linux and macOS. + """ + from sysconfig import get_platform + + plat = get_platform() + if sys.platform == "darwin" and not plat.startswith('macosx-'): + try: + version = _macos_vers() + machine = os.uname()[4].replace(" ", "_") + return "macosx-%d.%d-%s" % ( + int(version[0]), int(version[1]), + _macos_arch(machine), + ) + except ValueError: + # if someone is running a non-Mac darwin system, this will fall + # through to the default implementation + pass + return plat + + +macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)") +darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)") +# XXX backward compat +get_platform = get_build_platform + + +def compatible_platforms(provided, required): + """Can code for the `provided` platform run on the `required` platform? + + Returns true if either platform is ``None``, or the platforms are equal. + + XXX Needs compatibility checks for Linux and other unixy OSes. + """ + if provided is None or required is None or provided == required: + # easy case + return True + + # macOS special cases + reqMac = macosVersionString.match(required) + if reqMac: + provMac = macosVersionString.match(provided) + + # is this a Mac package? + if not provMac: + # this is backwards compatibility for packages built before + # setuptools 0.6. All packages built after this point will + # use the new macOS designation. + provDarwin = darwinVersionString.match(provided) + if provDarwin: + dversion = int(provDarwin.group(1)) + macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2)) + if dversion == 7 and macosversion >= "10.3" or \ + dversion == 8 and macosversion >= "10.4": + return True + # egg isn't macOS or legacy darwin + return False + + # are they the same major version and machine type? + if provMac.group(1) != reqMac.group(1) or \ + provMac.group(3) != reqMac.group(3): + return False + + # is the required OS major update >= the provided one? + if int(provMac.group(2)) > int(reqMac.group(2)): + return False + + return True + + # XXX Linux and other platforms' special cases should go here + return False + + +def run_script(dist_spec, script_name): + """Locate distribution `dist_spec` and run its `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + require(dist_spec)[0].run_script(script_name, ns) + + +# backward compatibility +run_main = run_script + + +def get_distribution(dist): + """Return a current distribution object for a Requirement or string""" + if isinstance(dist, str): + dist = Requirement.parse(dist) + if isinstance(dist, Requirement): + dist = get_provider(dist) + if not isinstance(dist, Distribution): + raise TypeError("Expected string, Requirement, or Distribution", dist) + return dist + + +def load_entry_point(dist, group, name): + """Return `name` entry point of `group` for `dist` or raise ImportError""" + return get_distribution(dist).load_entry_point(group, name) + + +def get_entry_map(dist, group=None): + """Return the entry point map for `group`, or the full entry map""" + return get_distribution(dist).get_entry_map(group) + + +def get_entry_info(dist, group, name): + """Return the EntryPoint object for `group`+`name`, or ``None``""" + return get_distribution(dist).get_entry_info(group, name) + + +class IMetadataProvider: + def has_metadata(name): + """Does the package's distribution contain the named metadata?""" + + def get_metadata(name): + """The named metadata resource as a string""" + + def get_metadata_lines(name): + """Yield named metadata resource as list of non-blank non-comment lines + + Leading and trailing whitespace is stripped from each line, and lines + with ``#`` as the first non-blank character are omitted.""" + + def metadata_isdir(name): + """Is the named metadata a directory? (like ``os.path.isdir()``)""" + + def metadata_listdir(name): + """List of metadata names in the directory (like ``os.listdir()``)""" + + def run_script(script_name, namespace): + """Execute the named script in the supplied namespace dictionary""" + + +class IResourceProvider(IMetadataProvider): + """An object that provides access to package resources""" + + def get_resource_filename(manager, resource_name): + """Return a true filesystem path for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_stream(manager, resource_name): + """Return a readable file-like object for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_string(manager, resource_name): + """Return a string containing the contents of `resource_name` + + `manager` must be an ``IResourceManager``""" + + def has_resource(resource_name): + """Does the package contain the named resource?""" + + def resource_isdir(resource_name): + """Is the named resource a directory? (like ``os.path.isdir()``)""" + + def resource_listdir(resource_name): + """List of resource names in the directory (like ``os.listdir()``)""" + + +class WorkingSet: + """A collection of active distributions on sys.path (or a similar list)""" + + def __init__(self, entries=None): + """Create working set from list of path entries (default=sys.path)""" + self.entries = [] + self.entry_keys = {} + self.by_key = {} + self.callbacks = [] + + if entries is None: + entries = sys.path + + for entry in entries: + self.add_entry(entry) + + @classmethod + def _build_master(cls): + """ + Prepare the master working set. + """ + ws = cls() + try: + from __main__ import __requires__ + except ImportError: + # The main program does not list any requirements + return ws + + # ensure the requirements are met + try: + ws.require(__requires__) + except VersionConflict: + return cls._build_from_requirements(__requires__) + + return ws + + @classmethod + def _build_from_requirements(cls, req_spec): + """ + Build a working set from a requirement spec. Rewrites sys.path. + """ + # try it without defaults already on sys.path + # by starting with an empty path + ws = cls([]) + reqs = parse_requirements(req_spec) + dists = ws.resolve(reqs, Environment()) + for dist in dists: + ws.add(dist) + + # add any missing entries from sys.path + for entry in sys.path: + if entry not in ws.entries: + ws.add_entry(entry) + + # then copy back to sys.path + sys.path[:] = ws.entries + return ws + + def add_entry(self, entry): + """Add a path item to ``.entries``, finding any distributions on it + + ``find_distributions(entry, True)`` is used to find distributions + corresponding to the path entry, and they are added. `entry` is + always appended to ``.entries``, even if it is already present. + (This is because ``sys.path`` can contain the same value more than + once, and the ``.entries`` of the ``sys.path`` WorkingSet should always + equal ``sys.path``.) + """ + self.entry_keys.setdefault(entry, []) + self.entries.append(entry) + for dist in find_distributions(entry, True): + self.add(dist, entry, False) + + def __contains__(self, dist): + """True if `dist` is the active distribution for its project""" + return self.by_key.get(dist.key) == dist + + def find(self, req): + """Find a distribution matching requirement `req` + + If there is an active distribution for the requested project, this + returns it as long as it meets the version requirement specified by + `req`. But, if there is an active distribution for the project and it + does *not* meet the `req` requirement, ``VersionConflict`` is raised. + If there is no active distribution for the requested project, ``None`` + is returned. + """ + dist = self.by_key.get(req.key) + if dist is not None and dist not in req: + # XXX add more info + raise VersionConflict(dist, req) + return dist + + def iter_entry_points(self, group, name=None): + """Yield entry point objects from `group` matching `name` + + If `name` is None, yields all entry points in `group` from all + distributions in the working set, otherwise only ones matching + both `group` and `name` are yielded (in distribution order). + """ + return ( + entry + for dist in self + for entry in dist.get_entry_map(group).values() + if name is None or name == entry.name + ) + + def run_script(self, requires, script_name): + """Locate distribution for `requires` and run `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + self.require(requires)[0].run_script(script_name, ns) + + def __iter__(self): + """Yield distributions for non-duplicate projects in the working set + + The yield order is the order in which the items' path entries were + added to the working set. + """ + seen = {} + for item in self.entries: + if item not in self.entry_keys: + # workaround a cache issue + continue + + for key in self.entry_keys[item]: + if key not in seen: + seen[key] = 1 + yield self.by_key[key] + + def add(self, dist, entry=None, insert=True, replace=False): + """Add `dist` to working set, associated with `entry` + + If `entry` is unspecified, it defaults to the ``.location`` of `dist`. + On exit from this routine, `entry` is added to the end of the working + set's ``.entries`` (if it wasn't already present). + + `dist` is only added to the working set if it's for a project that + doesn't already have a distribution in the set, unless `replace=True`. + If it's added, any callbacks registered with the ``subscribe()`` method + will be called. + """ + if insert: + dist.insert_on(self.entries, entry, replace=replace) + + if entry is None: + entry = dist.location + keys = self.entry_keys.setdefault(entry, []) + keys2 = self.entry_keys.setdefault(dist.location, []) + if not replace and dist.key in self.by_key: + # ignore hidden distros + return + + self.by_key[dist.key] = dist + if dist.key not in keys: + keys.append(dist.key) + if dist.key not in keys2: + keys2.append(dist.key) + self._added_new(dist) + + # FIXME: 'WorkingSet.resolve' is too complex (11) + def resolve(self, requirements, env=None, installer=None, # noqa: C901 + replace_conflicting=False, extras=None): + """List all distributions needed to (recursively) meet `requirements` + + `requirements` must be a sequence of ``Requirement`` objects. `env`, + if supplied, should be an ``Environment`` instance. If + not supplied, it defaults to all distributions available within any + entry or distribution in the working set. `installer`, if supplied, + will be invoked with each requirement that cannot be met by an + already-installed distribution; it should return a ``Distribution`` or + ``None``. + + Unless `replace_conflicting=True`, raises a VersionConflict exception + if + any requirements are found on the path that have the correct name but + the wrong version. Otherwise, if an `installer` is supplied it will be + invoked to obtain the correct version of the requirement and activate + it. + + `extras` is a list of the extras to be used with these requirements. + This is important because extra requirements may look like `my_req; + extra = "my_extra"`, which would otherwise be interpreted as a purely + optional requirement. Instead, we want to be able to assert that these + requirements are truly required. + """ + + # set up the stack + requirements = list(requirements)[::-1] + # set of processed requirements + processed = {} + # key -> dist + best = {} + to_activate = [] + + req_extras = _ReqExtras() + + # Mapping of requirement to set of distributions that required it; + # useful for reporting info about conflicts. + required_by = collections.defaultdict(set) + + while requirements: + # process dependencies breadth-first + req = requirements.pop(0) + if req in processed: + # Ignore cyclic or redundant dependencies + continue + + if not req_extras.markers_pass(req, extras): + continue + + dist = best.get(req.key) + if dist is None: + # Find the best distribution and add it to the map + dist = self.by_key.get(req.key) + if dist is None or (dist not in req and replace_conflicting): + ws = self + if env is None: + if dist is None: + env = Environment(self.entries) + else: + # Use an empty environment and workingset to avoid + # any further conflicts with the conflicting + # distribution + env = Environment([]) + ws = WorkingSet([]) + dist = best[req.key] = env.best_match( + req, ws, installer, + replace_conflicting=replace_conflicting + ) + if dist is None: + requirers = required_by.get(req, None) + raise DistributionNotFound(req, requirers) + to_activate.append(dist) + if dist not in req: + # Oops, the "best" so far conflicts with a dependency + dependent_req = required_by[req] + raise VersionConflict(dist, req).with_context(dependent_req) + + # push the new requirements onto the stack + new_requirements = dist.requires(req.extras)[::-1] + requirements.extend(new_requirements) + + # Register the new requirements needed by req + for new_requirement in new_requirements: + required_by[new_requirement].add(req.project_name) + req_extras[new_requirement] = req.extras + + processed[req] = True + + # return list of distros to activate + return to_activate + + def find_plugins( + self, plugin_env, full_env=None, installer=None, fallback=True): + """Find all activatable distributions in `plugin_env` + + Example usage:: + + distributions, errors = working_set.find_plugins( + Environment(plugin_dirlist) + ) + # add plugins+libs to sys.path + map(working_set.add, distributions) + # display errors + print('Could not load', errors) + + The `plugin_env` should be an ``Environment`` instance that contains + only distributions that are in the project's "plugin directory" or + directories. The `full_env`, if supplied, should be an ``Environment`` + contains all currently-available distributions. If `full_env` is not + supplied, one is created automatically from the ``WorkingSet`` this + method is called on, which will typically mean that every directory on + ``sys.path`` will be scanned for distributions. + + `installer` is a standard installer callback as used by the + ``resolve()`` method. The `fallback` flag indicates whether we should + attempt to resolve older versions of a plugin if the newest version + cannot be resolved. + + This method returns a 2-tuple: (`distributions`, `error_info`), where + `distributions` is a list of the distributions found in `plugin_env` + that were loadable, along with any other distributions that are needed + to resolve their dependencies. `error_info` is a dictionary mapping + unloadable plugin distributions to an exception instance describing the + error that occurred. Usually this will be a ``DistributionNotFound`` or + ``VersionConflict`` instance. + """ + + plugin_projects = list(plugin_env) + # scan project names in alphabetic order + plugin_projects.sort() + + error_info = {} + distributions = {} + + if full_env is None: + env = Environment(self.entries) + env += plugin_env + else: + env = full_env + plugin_env + + shadow_set = self.__class__([]) + # put all our entries in shadow_set + list(map(shadow_set.add, self)) + + for project_name in plugin_projects: + + for dist in plugin_env[project_name]: + + req = [dist.as_requirement()] + + try: + resolvees = shadow_set.resolve(req, env, installer) + + except ResolutionError as v: + # save error info + error_info[dist] = v + if fallback: + # try the next older version of project + continue + else: + # give up on this project, keep going + break + + else: + list(map(shadow_set.add, resolvees)) + distributions.update(dict.fromkeys(resolvees)) + + # success, no need to try any more versions of this project + break + + distributions = list(distributions) + distributions.sort() + + return distributions, error_info + + def require(self, *requirements): + """Ensure that distributions matching `requirements` are activated + + `requirements` must be a string or a (possibly-nested) sequence + thereof, specifying the distributions and versions required. The + return value is a sequence of the distributions that needed to be + activated to fulfill the requirements; all relevant distributions are + included, even if they were already activated in this working set. + """ + needed = self.resolve(parse_requirements(requirements)) + + for dist in needed: + self.add(dist) + + return needed + + def subscribe(self, callback, existing=True): + """Invoke `callback` for all distributions + + If `existing=True` (default), + call on all existing ones, as well. + """ + if callback in self.callbacks: + return + self.callbacks.append(callback) + if not existing: + return + for dist in self: + callback(dist) + + def _added_new(self, dist): + for callback in self.callbacks: + callback(dist) + + def __getstate__(self): + return ( + self.entries[:], self.entry_keys.copy(), self.by_key.copy(), + self.callbacks[:] + ) + + def __setstate__(self, e_k_b_c): + entries, keys, by_key, callbacks = e_k_b_c + self.entries = entries[:] + self.entry_keys = keys.copy() + self.by_key = by_key.copy() + self.callbacks = callbacks[:] + + +class _ReqExtras(dict): + """ + Map each requirement to the extras that demanded it. + """ + + def markers_pass(self, req, extras=None): + """ + Evaluate markers for req against each extra that + demanded it. + + Return False if the req has a marker and fails + evaluation. Otherwise, return True. + """ + extra_evals = ( + req.marker.evaluate({'extra': extra}) + for extra in self.get(req, ()) + (extras or (None,)) + ) + return not req.marker or any(extra_evals) + + +class Environment: + """Searchable snapshot of distributions on a search path""" + + def __init__( + self, search_path=None, platform=get_supported_platform(), + python=PY_MAJOR): + """Snapshot distributions available on a search path + + Any distributions found on `search_path` are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. + + `platform` is an optional string specifying the name of the platform + that platform-specific distributions must be compatible with. If + unspecified, it defaults to the current platform. `python` is an + optional string naming the desired version of Python (e.g. ``'3.6'``); + it defaults to the current version. + + You may explicitly set `platform` (and/or `python`) to ``None`` if you + wish to map *all* distributions, not just those compatible with the + running platform or Python version. + """ + self._distmap = {} + self.platform = platform + self.python = python + self.scan(search_path) + + def can_add(self, dist): + """Is distribution `dist` acceptable for this environment? + + The distribution must match the platform and python version + requirements specified when this environment was created, or False + is returned. + """ + py_compat = ( + self.python is None + or dist.py_version is None + or dist.py_version == self.python + ) + return py_compat and compatible_platforms(dist.platform, self.platform) + + def remove(self, dist): + """Remove `dist` from the environment""" + self._distmap[dist.key].remove(dist) + + def scan(self, search_path=None): + """Scan `search_path` for distributions usable in this environment + + Any distributions found are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. Only distributions conforming to + the platform/python version defined at initialization are added. + """ + if search_path is None: + search_path = sys.path + + for item in search_path: + for dist in find_distributions(item): + self.add(dist) + + def __getitem__(self, project_name): + """Return a newest-to-oldest list of distributions for `project_name` + + Uses case-insensitive `project_name` comparison, assuming all the + project's distributions use their project's name converted to all + lowercase as their key. + + """ + distribution_key = project_name.lower() + return self._distmap.get(distribution_key, []) + + def add(self, dist): + """Add `dist` if we ``can_add()`` it and it has not already been added + """ + if self.can_add(dist) and dist.has_version(): + dists = self._distmap.setdefault(dist.key, []) + if dist not in dists: + dists.append(dist) + dists.sort(key=operator.attrgetter('hashcmp'), reverse=True) + + def best_match( + self, req, working_set, installer=None, replace_conflicting=False): + """Find distribution best matching `req` and usable on `working_set` + + This calls the ``find(req)`` method of the `working_set` to see if a + suitable distribution is already active. (This may raise + ``VersionConflict`` if an unsuitable version of the project is already + active in the specified `working_set`.) If a suitable distribution + isn't active, this method returns the newest distribution in the + environment that meets the ``Requirement`` in `req`. If no suitable + distribution is found, and `installer` is supplied, then the result of + calling the environment's ``obtain(req, installer)`` method will be + returned. + """ + try: + dist = working_set.find(req) + except VersionConflict: + if not replace_conflicting: + raise + dist = None + if dist is not None: + return dist + for dist in self[req.key]: + if dist in req: + return dist + # try to download/install + return self.obtain(req, installer) + + def obtain(self, requirement, installer=None): + """Obtain a distribution matching `requirement` (e.g. via download) + + Obtain a distro that matches requirement (e.g. via download). In the + base ``Environment`` class, this routine just returns + ``installer(requirement)``, unless `installer` is None, in which case + None is returned instead. This method is a hook that allows subclasses + to attempt other ways of obtaining a distribution before falling back + to the `installer` argument.""" + if installer is not None: + return installer(requirement) + + def __iter__(self): + """Yield the unique project names of the available distributions""" + for key in self._distmap.keys(): + if self[key]: + yield key + + def __iadd__(self, other): + """In-place addition of a distribution or environment""" + if isinstance(other, Distribution): + self.add(other) + elif isinstance(other, Environment): + for project in other: + for dist in other[project]: + self.add(dist) + else: + raise TypeError("Can't add %r to environment" % (other,)) + return self + + def __add__(self, other): + """Add an environment or distribution to an environment""" + new = self.__class__([], platform=None, python=None) + for env in self, other: + new += env + return new + + +# XXX backward compatibility +AvailableDistributions = Environment + + +class ExtractionError(RuntimeError): + """An error occurred extracting a resource + + The following attributes are available from instances of this exception: + + manager + The resource manager that raised this exception + + cache_path + The base directory for resource extraction + + original_error + The exception instance that caused extraction to fail + """ + + +class ResourceManager: + """Manage resource extraction and packages""" + extraction_path = None + + def __init__(self): + self.cached_files = {} + + def resource_exists(self, package_or_requirement, resource_name): + """Does the named resource exist?""" + return get_provider(package_or_requirement).has_resource(resource_name) + + def resource_isdir(self, package_or_requirement, resource_name): + """Is the named resource an existing directory?""" + return get_provider(package_or_requirement).resource_isdir( + resource_name + ) + + def resource_filename(self, package_or_requirement, resource_name): + """Return a true filesystem path for specified resource""" + return get_provider(package_or_requirement).get_resource_filename( + self, resource_name + ) + + def resource_stream(self, package_or_requirement, resource_name): + """Return a readable file-like object for specified resource""" + return get_provider(package_or_requirement).get_resource_stream( + self, resource_name + ) + + def resource_string(self, package_or_requirement, resource_name): + """Return specified resource as a string""" + return get_provider(package_or_requirement).get_resource_string( + self, resource_name + ) + + def resource_listdir(self, package_or_requirement, resource_name): + """List the contents of the named resource directory""" + return get_provider(package_or_requirement).resource_listdir( + resource_name + ) + + def extraction_error(self): + """Give an error message for problems extracting file(s)""" + + old_exc = sys.exc_info()[1] + cache_path = self.extraction_path or get_default_cache() + + tmpl = textwrap.dedent(""" + Can't extract file(s) to egg cache + + The following error occurred while trying to extract file(s) + to the Python egg cache: + + {old_exc} + + The Python egg cache directory is currently set to: + + {cache_path} + + Perhaps your account does not have write access to this directory? + You can change the cache directory by setting the PYTHON_EGG_CACHE + environment variable to point to an accessible directory. + """).lstrip() + err = ExtractionError(tmpl.format(**locals())) + err.manager = self + err.cache_path = cache_path + err.original_error = old_exc + raise err + + def get_cache_path(self, archive_name, names=()): + """Return absolute location in cache for `archive_name` and `names` + + The parent directory of the resulting path will be created if it does + not already exist. `archive_name` should be the base filename of the + enclosing egg (which may not be the name of the enclosing zipfile!), + including its ".egg" extension. `names`, if provided, should be a + sequence of path name parts "under" the egg's extraction location. + + This method should only be called by resource providers that need to + obtain an extraction location, and only for names they intend to + extract, as it tracks the generated names for possible cleanup later. + """ + extract_path = self.extraction_path or get_default_cache() + target_path = os.path.join(extract_path, archive_name + '-tmp', *names) + try: + _bypass_ensure_directory(target_path) + except Exception: + self.extraction_error() + + self._warn_unsafe_extraction_path(extract_path) + + self.cached_files[target_path] = 1 + return target_path + + @staticmethod + def _warn_unsafe_extraction_path(path): + """ + If the default extraction path is overridden and set to an insecure + location, such as /tmp, it opens up an opportunity for an attacker to + replace an extracted file with an unauthorized payload. Warn the user + if a known insecure location is used. + + See Distribute #375 for more details. + """ + if os.name == 'nt' and not path.startswith(os.environ['windir']): + # On Windows, permissions are generally restrictive by default + # and temp directories are not writable by other users, so + # bypass the warning. + return + mode = os.stat(path).st_mode + if mode & stat.S_IWOTH or mode & stat.S_IWGRP: + msg = ( + "Extraction path is writable by group/others " + "and vulnerable to attack when " + "used with get_resource_filename ({path}). " + "Consider a more secure " + "location (set with .set_extraction_path or the " + "PYTHON_EGG_CACHE environment variable)." + ).format(**locals()) + warnings.warn(msg, UserWarning) + + def postprocess(self, tempname, filename): + """Perform any platform-specific postprocessing of `tempname` + + This is where Mac header rewrites should be done; other platforms don't + have anything special they should do. + + Resource providers should call this method ONLY after successfully + extracting a compressed resource. They must NOT call it on resources + that are already in the filesystem. + + `tempname` is the current (temporary) name of the file, and `filename` + is the name it will be renamed to by the caller after this routine + returns. + """ + + if os.name == 'posix': + # Make the resource executable + mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777 + os.chmod(tempname, mode) + + def set_extraction_path(self, path): + """Set the base path where resources will be extracted to, if needed. + + If you do not call this routine before any extractions take place, the + path defaults to the return value of ``get_default_cache()``. (Which + is based on the ``PYTHON_EGG_CACHE`` environment variable, with various + platform-specific fallbacks. See that routine's documentation for more + details.) + + Resources are extracted to subdirectories of this path based upon + information given by the ``IResourceProvider``. You may set this to a + temporary directory, but then you must call ``cleanup_resources()`` to + delete the extracted files when done. There is no guarantee that + ``cleanup_resources()`` will be able to remove all extracted files. + + (Note: you may not change the extraction path for a given resource + manager once resources have been extracted, unless you first call + ``cleanup_resources()``.) + """ + if self.cached_files: + raise ValueError( + "Can't change extraction path, files already extracted" + ) + + self.extraction_path = path + + def cleanup_resources(self, force=False): + """ + Delete all extracted resource files and directories, returning a list + of the file and directory names that could not be successfully removed. + This function does not have any concurrency protection, so it should + generally only be called when the extraction path is a temporary + directory exclusive to a single process. This method is not + automatically called; you must call it explicitly or register it as an + ``atexit`` function if you wish to ensure cleanup of a temporary + directory used for extractions. + """ + # XXX + + +def get_default_cache(): + """ + Return the ``PYTHON_EGG_CACHE`` environment variable + or a platform-relevant user cache dir for an app + named "Python-Eggs". + """ + return ( + os.environ.get('PYTHON_EGG_CACHE') + or appdirs.user_cache_dir(appname='Python-Eggs') + ) + + +def safe_name(name): + """Convert an arbitrary string to a standard distribution name + + Any runs of non-alphanumeric/. characters are replaced with a single '-'. + """ + return re.sub('[^A-Za-z0-9.]+', '-', name) + + +def safe_version(version): + """ + Convert an arbitrary string to a standard version string + """ + try: + # normalize the version + return str(packaging.version.Version(version)) + except packaging.version.InvalidVersion: + version = version.replace(' ', '.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + +def safe_extra(extra): + """Convert an arbitrary string to a standard 'extra' name + + Any runs of non-alphanumeric characters are replaced with a single '_', + and the result is always lowercased. + """ + return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower() + + +def to_filename(name): + """Convert a project or version name to its filename-escaped form + + Any '-' characters are currently replaced with '_'. + """ + return name.replace('-', '_') + + +def invalid_marker(text): + """ + Validate text as a PEP 508 environment marker; return an exception + if invalid or False otherwise. + """ + try: + evaluate_marker(text) + except SyntaxError as e: + e.filename = None + e.lineno = None + return e + return False + + +def evaluate_marker(text, extra=None): + """ + Evaluate a PEP 508 environment marker. + Return a boolean indicating the marker result in this environment. + Raise SyntaxError if marker is invalid. + + This implementation uses the 'pyparsing' module. + """ + try: + marker = packaging.markers.Marker(text) + return marker.evaluate() + except packaging.markers.InvalidMarker as e: + raise SyntaxError(e) from e + + +class NullProvider: + """Try to implement resources and metadata for arbitrary PEP 302 loaders""" + + egg_name = None + egg_info = None + loader = None + + def __init__(self, module): + self.loader = getattr(module, '__loader__', None) + self.module_path = os.path.dirname(getattr(module, '__file__', '')) + + def get_resource_filename(self, manager, resource_name): + return self._fn(self.module_path, resource_name) + + def get_resource_stream(self, manager, resource_name): + return io.BytesIO(self.get_resource_string(manager, resource_name)) + + def get_resource_string(self, manager, resource_name): + return self._get(self._fn(self.module_path, resource_name)) + + def has_resource(self, resource_name): + return self._has(self._fn(self.module_path, resource_name)) + + def _get_metadata_path(self, name): + return self._fn(self.egg_info, name) + + def has_metadata(self, name): + if not self.egg_info: + return self.egg_info + + path = self._get_metadata_path(name) + return self._has(path) + + def get_metadata(self, name): + if not self.egg_info: + return "" + path = self._get_metadata_path(name) + value = self._get(path) + try: + return value.decode('utf-8') + except UnicodeDecodeError as exc: + # Include the path in the error message to simplify + # troubleshooting, and without changing the exception type. + exc.reason += ' in {} file at path: {}'.format(name, path) + raise + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + def resource_isdir(self, resource_name): + return self._isdir(self._fn(self.module_path, resource_name)) + + def metadata_isdir(self, name): + return self.egg_info and self._isdir(self._fn(self.egg_info, name)) + + def resource_listdir(self, resource_name): + return self._listdir(self._fn(self.module_path, resource_name)) + + def metadata_listdir(self, name): + if self.egg_info: + return self._listdir(self._fn(self.egg_info, name)) + return [] + + def run_script(self, script_name, namespace): + script = 'scripts/' + script_name + if not self.has_metadata(script): + raise ResolutionError( + "Script {script!r} not found in metadata at {self.egg_info!r}" + .format(**locals()), + ) + script_text = self.get_metadata(script).replace('\r\n', '\n') + script_text = script_text.replace('\r', '\n') + script_filename = self._fn(self.egg_info, script) + namespace['__file__'] = script_filename + if os.path.exists(script_filename): + with open(script_filename) as fid: + source = fid.read() + code = compile(source, script_filename, 'exec') + exec(code, namespace, namespace) + else: + from linecache import cache + cache[script_filename] = ( + len(script_text), 0, script_text.split('\n'), script_filename + ) + script_code = compile(script_text, script_filename, 'exec') + exec(script_code, namespace, namespace) + + def _has(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _isdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _listdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _fn(self, base, resource_name): + self._validate_resource_path(resource_name) + if resource_name: + return os.path.join(base, *resource_name.split('/')) + return base + + @staticmethod + def _validate_resource_path(path): + """ + Validate the resource paths according to the docs. + https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access + + >>> warned = getfixture('recwarn') + >>> warnings.simplefilter('always') + >>> vrp = NullProvider._validate_resource_path + >>> vrp('foo/bar.txt') + >>> bool(warned) + False + >>> vrp('../foo/bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('/foo/bar.txt') + >>> bool(warned) + True + >>> vrp('foo/../../bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('foo/f../bar.txt') + >>> bool(warned) + False + + Windows path separators are straight-up disallowed. + >>> vrp(r'\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + >>> vrp(r'C:\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + Blank values are allowed + + >>> vrp('') + >>> bool(warned) + False + + Non-string values are not. + + >>> vrp(None) + Traceback (most recent call last): + ... + AttributeError: ... + """ + invalid = ( + os.path.pardir in path.split(posixpath.sep) or + posixpath.isabs(path) or + ntpath.isabs(path) + ) + if not invalid: + return + + msg = "Use of .. or absolute path in a resource path is not allowed." + + # Aggressively disallow Windows absolute paths + if ntpath.isabs(path) and not posixpath.isabs(path): + raise ValueError(msg) + + # for compatibility, warn; in future + # raise ValueError(msg) + warnings.warn( + msg[:-1] + " and will raise exceptions in a future release.", + DeprecationWarning, + stacklevel=4, + ) + + def _get(self, path): + if hasattr(self.loader, 'get_data'): + return self.loader.get_data(path) + raise NotImplementedError( + "Can't perform this operation for loaders without 'get_data()'" + ) + + +register_loader_type(object, NullProvider) + + +def _parents(path): + """ + yield all parents of path including path + """ + last = None + while path != last: + yield path + last = path + path, _ = os.path.split(path) + + +class EggProvider(NullProvider): + """Provider based on a virtual filesystem""" + + def __init__(self, module): + NullProvider.__init__(self, module) + self._setup_prefix() + + def _setup_prefix(self): + # Assume that metadata may be nested inside a "basket" + # of multiple eggs and use module_path instead of .archive. + eggs = filter(_is_egg_path, _parents(self.module_path)) + egg = next(eggs, None) + egg and self._set_egg(egg) + + def _set_egg(self, path): + self.egg_name = os.path.basename(path) + self.egg_info = os.path.join(path, 'EGG-INFO') + self.egg_root = path + + +class DefaultProvider(EggProvider): + """Provides access to package resources in the filesystem""" + + def _has(self, path): + return os.path.exists(path) + + def _isdir(self, path): + return os.path.isdir(path) + + def _listdir(self, path): + return os.listdir(path) + + def get_resource_stream(self, manager, resource_name): + return open(self._fn(self.module_path, resource_name), 'rb') + + def _get(self, path): + with open(path, 'rb') as stream: + return stream.read() + + @classmethod + def _register(cls): + loader_names = 'SourceFileLoader', 'SourcelessFileLoader', + for name in loader_names: + loader_cls = getattr(importlib_machinery, name, type(None)) + register_loader_type(loader_cls, cls) + + +DefaultProvider._register() + + +class EmptyProvider(NullProvider): + """Provider that returns nothing for all requests""" + + module_path = None + + _isdir = _has = lambda self, path: False + + def _get(self, path): + return '' + + def _listdir(self, path): + return [] + + def __init__(self): + pass + + +empty_provider = EmptyProvider() + + +class ZipManifests(dict): + """ + zip manifest builder + """ + + @classmethod + def build(cls, path): + """ + Build a dictionary similar to the zipimport directory + caches, except instead of tuples, store ZipInfo objects. + + Use a platform-specific path separator (os.sep) for the path keys + for compatibility with pypy on Windows. + """ + with zipfile.ZipFile(path) as zfile: + items = ( + ( + name.replace('/', os.sep), + zfile.getinfo(name), + ) + for name in zfile.namelist() + ) + return dict(items) + + load = build + + +class MemoizedZipManifests(ZipManifests): + """ + Memoized zipfile manifests. + """ + manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime') + + def load(self, path): + """ + Load a manifest at path or return a suitable manifest already loaded. + """ + path = os.path.normpath(path) + mtime = os.stat(path).st_mtime + + if path not in self or self[path].mtime != mtime: + manifest = self.build(path) + self[path] = self.manifest_mod(manifest, mtime) + + return self[path].manifest + + +class ZipProvider(EggProvider): + """Resource support for zips and eggs""" + + eagers = None + _zip_manifests = MemoizedZipManifests() + + def __init__(self, module): + EggProvider.__init__(self, module) + self.zip_pre = self.loader.archive + os.sep + + def _zipinfo_name(self, fspath): + # Convert a virtual filename (full path to file) into a zipfile subpath + # usable with the zipimport directory cache for our target archive + fspath = fspath.rstrip(os.sep) + if fspath == self.loader.archive: + return '' + if fspath.startswith(self.zip_pre): + return fspath[len(self.zip_pre):] + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.zip_pre) + ) + + def _parts(self, zip_path): + # Convert a zipfile subpath into an egg-relative path part list. + # pseudo-fs path + fspath = self.zip_pre + zip_path + if fspath.startswith(self.egg_root + os.sep): + return fspath[len(self.egg_root) + 1:].split(os.sep) + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.egg_root) + ) + + @property + def zipinfo(self): + return self._zip_manifests.load(self.loader.archive) + + def get_resource_filename(self, manager, resource_name): + if not self.egg_name: + raise NotImplementedError( + "resource_filename() only supported for .egg, not .zip" + ) + # no need to lock for extraction, since we use temp names + zip_path = self._resource_to_zip(resource_name) + eagers = self._get_eager_resources() + if '/'.join(self._parts(zip_path)) in eagers: + for name in eagers: + self._extract_resource(manager, self._eager_to_zip(name)) + return self._extract_resource(manager, zip_path) + + @staticmethod + def _get_date_and_size(zip_stat): + size = zip_stat.file_size + # ymdhms+wday, yday, dst + date_time = zip_stat.date_time + (0, 0, -1) + # 1980 offset already done + timestamp = time.mktime(date_time) + return timestamp, size + + # FIXME: 'ZipProvider._extract_resource' is too complex (12) + def _extract_resource(self, manager, zip_path): # noqa: C901 + + if zip_path in self._index(): + for name in self._index()[zip_path]: + last = self._extract_resource( + manager, os.path.join(zip_path, name) + ) + # return the extracted directory name + return os.path.dirname(last) + + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + + if not WRITE_SUPPORT: + raise IOError('"os.rename" and "os.unlink" are not supported ' + 'on this platform') + try: + + real_path = manager.get_cache_path( + self.egg_name, self._parts(zip_path) + ) + + if self._is_current(real_path, zip_path): + return real_path + + outf, tmpnam = _mkstemp( + ".$extract", + dir=os.path.dirname(real_path), + ) + os.write(outf, self.loader.get_data(zip_path)) + os.close(outf) + utime(tmpnam, (timestamp, timestamp)) + manager.postprocess(tmpnam, real_path) + + try: + rename(tmpnam, real_path) + + except os.error: + if os.path.isfile(real_path): + if self._is_current(real_path, zip_path): + # the file became current since it was checked above, + # so proceed. + return real_path + # Windows, del old file and retry + elif os.name == 'nt': + unlink(real_path) + rename(tmpnam, real_path) + return real_path + raise + + except os.error: + # report a user-friendly error + manager.extraction_error() + + return real_path + + def _is_current(self, file_path, zip_path): + """ + Return True if the file_path is current for this zip_path + """ + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + if not os.path.isfile(file_path): + return False + stat = os.stat(file_path) + if stat.st_size != size or stat.st_mtime != timestamp: + return False + # check that the contents match + zip_contents = self.loader.get_data(zip_path) + with open(file_path, 'rb') as f: + file_contents = f.read() + return zip_contents == file_contents + + def _get_eager_resources(self): + if self.eagers is None: + eagers = [] + for name in ('native_libs.txt', 'eager_resources.txt'): + if self.has_metadata(name): + eagers.extend(self.get_metadata_lines(name)) + self.eagers = eagers + return self.eagers + + def _index(self): + try: + return self._dirindex + except AttributeError: + ind = {} + for path in self.zipinfo: + parts = path.split(os.sep) + while parts: + parent = os.sep.join(parts[:-1]) + if parent in ind: + ind[parent].append(parts[-1]) + break + else: + ind[parent] = [parts.pop()] + self._dirindex = ind + return ind + + def _has(self, fspath): + zip_path = self._zipinfo_name(fspath) + return zip_path in self.zipinfo or zip_path in self._index() + + def _isdir(self, fspath): + return self._zipinfo_name(fspath) in self._index() + + def _listdir(self, fspath): + return list(self._index().get(self._zipinfo_name(fspath), ())) + + def _eager_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.egg_root, resource_name)) + + def _resource_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.module_path, resource_name)) + + +register_loader_type(zipimport.zipimporter, ZipProvider) + + +class FileMetadata(EmptyProvider): + """Metadata handler for standalone PKG-INFO files + + Usage:: + + metadata = FileMetadata("/path/to/PKG-INFO") + + This provider rejects all data and metadata requests except for PKG-INFO, + which is treated as existing, and will be the contents of the file at + the provided location. + """ + + def __init__(self, path): + self.path = path + + def _get_metadata_path(self, name): + return self.path + + def has_metadata(self, name): + return name == 'PKG-INFO' and os.path.isfile(self.path) + + def get_metadata(self, name): + if name != 'PKG-INFO': + raise KeyError("No metadata except PKG-INFO is available") + + with io.open(self.path, encoding='utf-8', errors="replace") as f: + metadata = f.read() + self._warn_on_replacement(metadata) + return metadata + + def _warn_on_replacement(self, metadata): + replacement_char = '�' + if replacement_char in metadata: + tmpl = "{self.path} could not be properly decoded in UTF-8" + msg = tmpl.format(**locals()) + warnings.warn(msg) + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + +class PathMetadata(DefaultProvider): + """Metadata provider for egg directories + + Usage:: + + # Development eggs: + + egg_info = "/path/to/PackageName.egg-info" + base_dir = os.path.dirname(egg_info) + metadata = PathMetadata(base_dir, egg_info) + dist_name = os.path.splitext(os.path.basename(egg_info))[0] + dist = Distribution(basedir, project_name=dist_name, metadata=metadata) + + # Unpacked egg directories: + + egg_path = "/path/to/PackageName-ver-pyver-etc.egg" + metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO')) + dist = Distribution.from_filename(egg_path, metadata=metadata) + """ + + def __init__(self, path, egg_info): + self.module_path = path + self.egg_info = egg_info + + +class EggMetadata(ZipProvider): + """Metadata provider for .egg files""" + + def __init__(self, importer): + """Create a metadata provider from a zipimporter""" + + self.zip_pre = importer.archive + os.sep + self.loader = importer + if importer.prefix: + self.module_path = os.path.join(importer.archive, importer.prefix) + else: + self.module_path = importer.archive + self._setup_prefix() + + +_declare_state('dict', _distribution_finders={}) + + +def register_finder(importer_type, distribution_finder): + """Register `distribution_finder` to find distributions in sys.path items + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `distribution_finder` is a callable that, passed a path + item and the importer instance, yields ``Distribution`` instances found on + that path item. See ``pkg_resources.find_on_path`` for an example.""" + _distribution_finders[importer_type] = distribution_finder + + +def find_distributions(path_item, only=False): + """Yield distributions accessible via `path_item`""" + importer = get_importer(path_item) + finder = _find_adapter(_distribution_finders, importer) + return finder(importer, path_item, only) + + +def find_eggs_in_zip(importer, path_item, only=False): + """ + Find eggs in zip files; possibly multiple nested eggs. + """ + if importer.archive.endswith('.whl'): + # wheels are not supported with this finder + # they don't have PKG-INFO metadata, and won't ever contain eggs + return + metadata = EggMetadata(importer) + if metadata.has_metadata('PKG-INFO'): + yield Distribution.from_filename(path_item, metadata=metadata) + if only: + # don't yield nested distros + return + for subitem in metadata.resource_listdir(''): + if _is_egg_path(subitem): + subpath = os.path.join(path_item, subitem) + dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) + for dist in dists: + yield dist + elif subitem.lower().endswith(('.dist-info', '.egg-info')): + subpath = os.path.join(path_item, subitem) + submeta = EggMetadata(zipimport.zipimporter(subpath)) + submeta.egg_info = subpath + yield Distribution.from_location(path_item, subitem, submeta) + + +register_finder(zipimport.zipimporter, find_eggs_in_zip) + + +def find_nothing(importer, path_item, only=False): + return () + + +register_finder(object, find_nothing) + + +def _by_version_descending(names): + """ + Given a list of filenames, return them in descending order + by version number. + + >>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg' + >>> _by_version_descending(names) + ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar'] + >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg' + >>> _by_version_descending(names) + ['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg'] + >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg' + >>> _by_version_descending(names) + ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg'] + """ + def _by_version(name): + """ + Parse each component of the filename + """ + name, ext = os.path.splitext(name) + parts = itertools.chain(name.split('-'), [ext]) + return [packaging.version.parse(part) for part in parts] + + return sorted(names, key=_by_version, reverse=True) + + +def find_on_path(importer, path_item, only=False): + """Yield distributions accessible on a sys.path directory""" + path_item = _normalize_cached(path_item) + + if _is_unpacked_egg(path_item): + yield Distribution.from_filename( + path_item, metadata=PathMetadata( + path_item, os.path.join(path_item, 'EGG-INFO') + ) + ) + return + + entries = ( + os.path.join(path_item, child) + for child in safe_listdir(path_item) + ) + + # for performance, before sorting by version, + # screen entries for only those that will yield + # distributions + filtered = ( + entry + for entry in entries + if dist_factory(path_item, entry, only) + ) + + # scan for .egg and .egg-info in directory + path_item_entries = _by_version_descending(filtered) + for entry in path_item_entries: + fullpath = os.path.join(path_item, entry) + factory = dist_factory(path_item, entry, only) + for dist in factory(fullpath): + yield dist + + +def dist_factory(path_item, entry, only): + """Return a dist_factory for the given entry.""" + lower = entry.lower() + is_egg_info = lower.endswith('.egg-info') + is_dist_info = ( + lower.endswith('.dist-info') and + os.path.isdir(os.path.join(path_item, entry)) + ) + is_meta = is_egg_info or is_dist_info + return ( + distributions_from_metadata + if is_meta else + find_distributions + if not only and _is_egg_path(entry) else + resolve_egg_link + if not only and lower.endswith('.egg-link') else + NoDists() + ) + + +class NoDists: + """ + >>> bool(NoDists()) + False + + >>> list(NoDists()('anything')) + [] + """ + def __bool__(self): + return False + + def __call__(self, fullpath): + return iter(()) + + +def safe_listdir(path): + """ + Attempt to list contents of path, but suppress some exceptions. + """ + try: + return os.listdir(path) + except (PermissionError, NotADirectoryError): + pass + except OSError as e: + # Ignore the directory if does not exist, not a directory or + # permission denied + if e.errno not in (errno.ENOTDIR, errno.EACCES, errno.ENOENT): + raise + return () + + +def distributions_from_metadata(path): + root = os.path.dirname(path) + if os.path.isdir(path): + if len(os.listdir(path)) == 0: + # empty metadata dir; skip + return + metadata = PathMetadata(root, path) + else: + metadata = FileMetadata(path) + entry = os.path.basename(path) + yield Distribution.from_location( + root, entry, metadata, precedence=DEVELOP_DIST, + ) + + +def non_empty_lines(path): + """ + Yield non-empty lines from file at path + """ + with open(path) as f: + for line in f: + line = line.strip() + if line: + yield line + + +def resolve_egg_link(path): + """ + Given a path to an .egg-link, resolve distributions + present in the referenced path. + """ + referenced_paths = non_empty_lines(path) + resolved_paths = ( + os.path.join(os.path.dirname(path), ref) + for ref in referenced_paths + ) + dist_groups = map(find_distributions, resolved_paths) + return next(dist_groups, ()) + + +register_finder(pkgutil.ImpImporter, find_on_path) + +if hasattr(importlib_machinery, 'FileFinder'): + register_finder(importlib_machinery.FileFinder, find_on_path) + +_declare_state('dict', _namespace_handlers={}) +_declare_state('dict', _namespace_packages={}) + + +def register_namespace_handler(importer_type, namespace_handler): + """Register `namespace_handler` to declare namespace packages + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `namespace_handler` is a callable like this:: + + def namespace_handler(importer, path_entry, moduleName, module): + # return a path_entry to use for child packages + + Namespace handlers are only called if the importer object has already + agreed that it can handle the relevant path item, and they should only + return a subpath if the module __path__ does not already contain an + equivalent subpath. For an example namespace handler, see + ``pkg_resources.file_ns_handler``. + """ + _namespace_handlers[importer_type] = namespace_handler + + +def _handle_ns(packageName, path_item): + """Ensure that named package includes a subpath of path_item (if needed)""" + + importer = get_importer(path_item) + if importer is None: + return None + + # use find_spec (PEP 451) and fall-back to find_module (PEP 302) + try: + loader = importer.find_spec(packageName).loader + except AttributeError: + # capture warnings due to #1111 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + loader = importer.find_module(packageName) + + if loader is None: + return None + module = sys.modules.get(packageName) + if module is None: + module = sys.modules[packageName] = types.ModuleType(packageName) + module.__path__ = [] + _set_parent_ns(packageName) + elif not hasattr(module, '__path__'): + raise TypeError("Not a package:", packageName) + handler = _find_adapter(_namespace_handlers, importer) + subpath = handler(importer, path_item, packageName, module) + if subpath is not None: + path = module.__path__ + path.append(subpath) + importlib.import_module(packageName) + _rebuild_mod_path(path, packageName, module) + return subpath + + +def _rebuild_mod_path(orig_path, package_name, module): + """ + Rebuild module.__path__ ensuring that all entries are ordered + corresponding to their sys.path order + """ + sys_path = [_normalize_cached(p) for p in sys.path] + + def safe_sys_path_index(entry): + """ + Workaround for #520 and #513. + """ + try: + return sys_path.index(entry) + except ValueError: + return float('inf') + + def position_in_sys_path(path): + """ + Return the ordinal of the path based on its position in sys.path + """ + path_parts = path.split(os.sep) + module_parts = package_name.count('.') + 1 + parts = path_parts[:-module_parts] + return safe_sys_path_index(_normalize_cached(os.sep.join(parts))) + + new_path = sorted(orig_path, key=position_in_sys_path) + new_path = [_normalize_cached(p) for p in new_path] + + if isinstance(module.__path__, list): + module.__path__[:] = new_path + else: + module.__path__ = new_path + + +def declare_namespace(packageName): + """Declare that package 'packageName' is a namespace package""" + + _imp.acquire_lock() + try: + if packageName in _namespace_packages: + return + + path = sys.path + parent, _, _ = packageName.rpartition('.') + + if parent: + declare_namespace(parent) + if parent not in _namespace_packages: + __import__(parent) + try: + path = sys.modules[parent].__path__ + except AttributeError as e: + raise TypeError("Not a package:", parent) from e + + # Track what packages are namespaces, so when new path items are added, + # they can be updated + _namespace_packages.setdefault(parent or None, []).append(packageName) + _namespace_packages.setdefault(packageName, []) + + for path_item in path: + # Ensure all the parent's path items are reflected in the child, + # if they apply + _handle_ns(packageName, path_item) + + finally: + _imp.release_lock() + + +def fixup_namespace_packages(path_item, parent=None): + """Ensure that previously-declared namespace packages include path_item""" + _imp.acquire_lock() + try: + for package in _namespace_packages.get(parent, ()): + subpath = _handle_ns(package, path_item) + if subpath: + fixup_namespace_packages(subpath, package) + finally: + _imp.release_lock() + + +def file_ns_handler(importer, path_item, packageName, module): + """Compute an ns-package subpath for a filesystem or zipfile importer""" + + subpath = os.path.join(path_item, packageName.split('.')[-1]) + normalized = _normalize_cached(subpath) + for item in module.__path__: + if _normalize_cached(item) == normalized: + break + else: + # Only return the path if it's not already there + return subpath + + +register_namespace_handler(pkgutil.ImpImporter, file_ns_handler) +register_namespace_handler(zipimport.zipimporter, file_ns_handler) + +if hasattr(importlib_machinery, 'FileFinder'): + register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler) + + +def null_ns_handler(importer, path_item, packageName, module): + return None + + +register_namespace_handler(object, null_ns_handler) + + +def normalize_path(filename): + """Normalize a file/dir name for comparison purposes""" + return os.path.normcase(os.path.realpath(os.path.normpath( + _cygwin_patch(filename)))) + + +def _cygwin_patch(filename): # pragma: nocover + """ + Contrary to POSIX 2008, on Cygwin, getcwd (3) contains + symlink components. Using + os.path.abspath() works around this limitation. A fix in os.getcwd() + would probably better, in Cygwin even more so, except + that this seems to be by design... + """ + return os.path.abspath(filename) if sys.platform == 'cygwin' else filename + + +def _normalize_cached(filename, _cache={}): + try: + return _cache[filename] + except KeyError: + _cache[filename] = result = normalize_path(filename) + return result + + +def _is_egg_path(path): + """ + Determine if given path appears to be an egg. + """ + return _is_zip_egg(path) or _is_unpacked_egg(path) + + +def _is_zip_egg(path): + return ( + path.lower().endswith('.egg') and + os.path.isfile(path) and + zipfile.is_zipfile(path) + ) + + +def _is_unpacked_egg(path): + """ + Determine if given path appears to be an unpacked egg. + """ + return ( + path.lower().endswith('.egg') and + os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO')) + ) + + +def _set_parent_ns(packageName): + parts = packageName.split('.') + name = parts.pop() + if parts: + parent = '.'.join(parts) + setattr(sys.modules[parent], name, sys.modules[packageName]) + + +def yield_lines(strs): + """Yield non-empty/non-comment lines of a string or sequence""" + if isinstance(strs, str): + for s in strs.splitlines(): + s = s.strip() + # skip blank lines/comments + if s and not s.startswith('#'): + yield s + else: + for ss in strs: + for s in yield_lines(ss): + yield s + + +MODULE = re.compile(r"\w+(\.\w+)*$").match +EGG_NAME = re.compile( + r""" + (?P[^-]+) ( + -(?P[^-]+) ( + -py(?P[^-]+) ( + -(?P.+) + )? + )? + )? + """, + re.VERBOSE | re.IGNORECASE, +).match + + +class EntryPoint: + """Object representing an advertised importable object""" + + def __init__(self, name, module_name, attrs=(), extras=(), dist=None): + if not MODULE(module_name): + raise ValueError("Invalid module name", module_name) + self.name = name + self.module_name = module_name + self.attrs = tuple(attrs) + self.extras = tuple(extras) + self.dist = dist + + def __str__(self): + s = "%s = %s" % (self.name, self.module_name) + if self.attrs: + s += ':' + '.'.join(self.attrs) + if self.extras: + s += ' [%s]' % ','.join(self.extras) + return s + + def __repr__(self): + return "EntryPoint.parse(%r)" % str(self) + + def load(self, require=True, *args, **kwargs): + """ + Require packages for this EntryPoint, then resolve it. + """ + if not require or args or kwargs: + warnings.warn( + "Parameters to load are deprecated. Call .resolve and " + ".require separately.", + PkgResourcesDeprecationWarning, + stacklevel=2, + ) + if require: + self.require(*args, **kwargs) + return self.resolve() + + def resolve(self): + """ + Resolve the entry point from its module and attrs. + """ + module = __import__(self.module_name, fromlist=['__name__'], level=0) + try: + return functools.reduce(getattr, self.attrs, module) + except AttributeError as exc: + raise ImportError(str(exc)) from exc + + def require(self, env=None, installer=None): + if self.extras and not self.dist: + raise UnknownExtra("Can't require() without a distribution", self) + + # Get the requirements for this entry point with all its extras and + # then resolve them. We have to pass `extras` along when resolving so + # that the working set knows what extras we want. Otherwise, for + # dist-info distributions, the working set will assume that the + # requirements for that extra are purely optional and skip over them. + reqs = self.dist.requires(self.extras) + items = working_set.resolve(reqs, env, installer, extras=self.extras) + list(map(working_set.add, items)) + + pattern = re.compile( + r'\s*' + r'(?P.+?)\s*' + r'=\s*' + r'(?P[\w.]+)\s*' + r'(:\s*(?P[\w.]+))?\s*' + r'(?P\[.*\])?\s*$' + ) + + @classmethod + def parse(cls, src, dist=None): + """Parse a single entry point from string `src` + + Entry point syntax follows the form:: + + name = some.module:some.attr [extra1, extra2] + + The entry name and module name are required, but the ``:attrs`` and + ``[extras]`` parts are optional + """ + m = cls.pattern.match(src) + if not m: + msg = "EntryPoint must be in 'name=module:attrs [extras]' format" + raise ValueError(msg, src) + res = m.groupdict() + extras = cls._parse_extras(res['extras']) + attrs = res['attr'].split('.') if res['attr'] else () + return cls(res['name'], res['module'], attrs, extras, dist) + + @classmethod + def _parse_extras(cls, extras_spec): + if not extras_spec: + return () + req = Requirement.parse('x' + extras_spec) + if req.specs: + raise ValueError() + return req.extras + + @classmethod + def parse_group(cls, group, lines, dist=None): + """Parse an entry point group""" + if not MODULE(group): + raise ValueError("Invalid group name", group) + this = {} + for line in yield_lines(lines): + ep = cls.parse(line, dist) + if ep.name in this: + raise ValueError("Duplicate entry point", group, ep.name) + this[ep.name] = ep + return this + + @classmethod + def parse_map(cls, data, dist=None): + """Parse a map of entry point groups""" + if isinstance(data, dict): + data = data.items() + else: + data = split_sections(data) + maps = {} + for group, lines in data: + if group is None: + if not lines: + continue + raise ValueError("Entry points must be listed in groups") + group = group.strip() + if group in maps: + raise ValueError("Duplicate group name", group) + maps[group] = cls.parse_group(group, lines, dist) + return maps + + +def _version_from_file(lines): + """ + Given an iterable of lines from a Metadata file, return + the value of the Version field, if present, or None otherwise. + """ + def is_version_line(line): + return line.lower().startswith('version:') + version_lines = filter(is_version_line, lines) + line = next(iter(version_lines), '') + _, _, value = line.partition(':') + return safe_version(value.strip()) or None + + +class Distribution: + """Wrap an actual or potential sys.path entry w/metadata""" + PKG_INFO = 'PKG-INFO' + + def __init__( + self, location=None, metadata=None, project_name=None, + version=None, py_version=PY_MAJOR, platform=None, + precedence=EGG_DIST): + self.project_name = safe_name(project_name or 'Unknown') + if version is not None: + self._version = safe_version(version) + self.py_version = py_version + self.platform = platform + self.location = location + self.precedence = precedence + self._provider = metadata or empty_provider + + @classmethod + def from_location(cls, location, basename, metadata=None, **kw): + project_name, version, py_version, platform = [None] * 4 + basename, ext = os.path.splitext(basename) + if ext.lower() in _distributionImpl: + cls = _distributionImpl[ext.lower()] + + match = EGG_NAME(basename) + if match: + project_name, version, py_version, platform = match.group( + 'name', 'ver', 'pyver', 'plat' + ) + return cls( + location, metadata, project_name=project_name, version=version, + py_version=py_version, platform=platform, **kw + )._reload_version() + + def _reload_version(self): + return self + + @property + def hashcmp(self): + return ( + self.parsed_version, + self.precedence, + self.key, + self.location, + self.py_version or '', + self.platform or '', + ) + + def __hash__(self): + return hash(self.hashcmp) + + def __lt__(self, other): + return self.hashcmp < other.hashcmp + + def __le__(self, other): + return self.hashcmp <= other.hashcmp + + def __gt__(self, other): + return self.hashcmp > other.hashcmp + + def __ge__(self, other): + return self.hashcmp >= other.hashcmp + + def __eq__(self, other): + if not isinstance(other, self.__class__): + # It's not a Distribution, so they are not equal + return False + return self.hashcmp == other.hashcmp + + def __ne__(self, other): + return not self == other + + # These properties have to be lazy so that we don't have to load any + # metadata until/unless it's actually needed. (i.e., some distributions + # may not know their name or version without loading PKG-INFO) + + @property + def key(self): + try: + return self._key + except AttributeError: + self._key = key = self.project_name.lower() + return key + + @property + def parsed_version(self): + if not hasattr(self, "_parsed_version"): + self._parsed_version = parse_version(self.version) + + return self._parsed_version + + def _warn_legacy_version(self): + LV = packaging.version.LegacyVersion + is_legacy = isinstance(self._parsed_version, LV) + if not is_legacy: + return + + # While an empty version is technically a legacy version and + # is not a valid PEP 440 version, it's also unlikely to + # actually come from someone and instead it is more likely that + # it comes from setuptools attempting to parse a filename and + # including it in the list. So for that we'll gate this warning + # on if the version is anything at all or not. + if not self.version: + return + + tmpl = textwrap.dedent(""" + '{project_name} ({version})' is being parsed as a legacy, + non PEP 440, + version. You may find odd behavior and sort order. + In particular it will be sorted as less than 0.0. It + is recommended to migrate to PEP 440 compatible + versions. + """).strip().replace('\n', ' ') + + warnings.warn(tmpl.format(**vars(self)), PEP440Warning) + + @property + def version(self): + try: + return self._version + except AttributeError as e: + version = self._get_version() + if version is None: + path = self._get_metadata_path_for_display(self.PKG_INFO) + msg = ( + "Missing 'Version:' header and/or {} file at path: {}" + ).format(self.PKG_INFO, path) + raise ValueError(msg, self) from e + + return version + + @property + def _dep_map(self): + """ + A map of extra to its list of (direct) requirements + for this distribution, including the null extra. + """ + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._filter_extras(self._build_dep_map()) + return self.__dep_map + + @staticmethod + def _filter_extras(dm): + """ + Given a mapping of extras to dependencies, strip off + environment markers and filter out any dependencies + not matching the markers. + """ + for extra in list(filter(None, dm)): + new_extra = extra + reqs = dm.pop(extra) + new_extra, _, marker = extra.partition(':') + fails_marker = marker and ( + invalid_marker(marker) + or not evaluate_marker(marker) + ) + if fails_marker: + reqs = [] + new_extra = safe_extra(new_extra) or None + + dm.setdefault(new_extra, []).extend(reqs) + return dm + + def _build_dep_map(self): + dm = {} + for name in 'requires.txt', 'depends.txt': + for extra, reqs in split_sections(self._get_metadata(name)): + dm.setdefault(extra, []).extend(parse_requirements(reqs)) + return dm + + def requires(self, extras=()): + """List of Requirements needed for this distro if `extras` are used""" + dm = self._dep_map + deps = [] + deps.extend(dm.get(None, ())) + for ext in extras: + try: + deps.extend(dm[safe_extra(ext)]) + except KeyError as e: + raise UnknownExtra( + "%s has no such extra feature %r" % (self, ext) + ) from e + return deps + + def _get_metadata_path_for_display(self, name): + """ + Return the path to the given metadata file, if available. + """ + try: + # We need to access _get_metadata_path() on the provider object + # directly rather than through this class's __getattr__() + # since _get_metadata_path() is marked private. + path = self._provider._get_metadata_path(name) + + # Handle exceptions e.g. in case the distribution's metadata + # provider doesn't support _get_metadata_path(). + except Exception: + return '[could not detect]' + + return path + + def _get_metadata(self, name): + if self.has_metadata(name): + for line in self.get_metadata_lines(name): + yield line + + def _get_version(self): + lines = self._get_metadata(self.PKG_INFO) + version = _version_from_file(lines) + + return version + + def activate(self, path=None, replace=False): + """Ensure distribution is importable on `path` (default=sys.path)""" + if path is None: + path = sys.path + self.insert_on(path, replace=replace) + if path is sys.path: + fixup_namespace_packages(self.location) + for pkg in self._get_metadata('namespace_packages.txt'): + if pkg in sys.modules: + declare_namespace(pkg) + + def egg_name(self): + """Return what this distribution's standard .egg filename should be""" + filename = "%s-%s-py%s" % ( + to_filename(self.project_name), to_filename(self.version), + self.py_version or PY_MAJOR + ) + + if self.platform: + filename += '-' + self.platform + return filename + + def __repr__(self): + if self.location: + return "%s (%s)" % (self, self.location) + else: + return str(self) + + def __str__(self): + try: + version = getattr(self, 'version', None) + except ValueError: + version = None + version = version or "[unknown version]" + return "%s %s" % (self.project_name, version) + + def __getattr__(self, attr): + """Delegate all unrecognized public attributes to .metadata provider""" + if attr.startswith('_'): + raise AttributeError(attr) + return getattr(self._provider, attr) + + def __dir__(self): + return list( + set(super(Distribution, self).__dir__()) + | set( + attr for attr in self._provider.__dir__() + if not attr.startswith('_') + ) + ) + + @classmethod + def from_filename(cls, filename, metadata=None, **kw): + return cls.from_location( + _normalize_cached(filename), os.path.basename(filename), metadata, + **kw + ) + + def as_requirement(self): + """Return a ``Requirement`` that matches this distribution exactly""" + if isinstance(self.parsed_version, packaging.version.Version): + spec = "%s==%s" % (self.project_name, self.parsed_version) + else: + spec = "%s===%s" % (self.project_name, self.parsed_version) + + return Requirement.parse(spec) + + def load_entry_point(self, group, name): + """Return the `name` entry point of `group` or raise ImportError""" + ep = self.get_entry_info(group, name) + if ep is None: + raise ImportError("Entry point %r not found" % ((group, name),)) + return ep.load() + + def get_entry_map(self, group=None): + """Return the entry point map for `group`, or the full entry map""" + try: + ep_map = self._ep_map + except AttributeError: + ep_map = self._ep_map = EntryPoint.parse_map( + self._get_metadata('entry_points.txt'), self + ) + if group is not None: + return ep_map.get(group, {}) + return ep_map + + def get_entry_info(self, group, name): + """Return the EntryPoint object for `group`+`name`, or ``None``""" + return self.get_entry_map(group).get(name) + + # FIXME: 'Distribution.insert_on' is too complex (13) + def insert_on(self, path, loc=None, replace=False): # noqa: C901 + """Ensure self.location is on path + + If replace=False (default): + - If location is already in path anywhere, do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent. + - Else: add to the end of path. + If replace=True: + - If location is already on path anywhere (not eggs) + or higher priority than its parent (eggs) + do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent, + removing any lower-priority entries. + - Else: add it to the front of path. + """ + + loc = loc or self.location + if not loc: + return + + nloc = _normalize_cached(loc) + bdir = os.path.dirname(nloc) + npath = [(p and _normalize_cached(p) or p) for p in path] + + for p, item in enumerate(npath): + if item == nloc: + if replace: + break + else: + # don't modify path (even removing duplicates) if + # found and not replace + return + elif item == bdir and self.precedence == EGG_DIST: + # if it's an .egg, give it precedence over its directory + # UNLESS it's already been added to sys.path and replace=False + if (not replace) and nloc in npath[p:]: + return + if path is sys.path: + self.check_version_conflict() + path.insert(p, loc) + npath.insert(p, nloc) + break + else: + if path is sys.path: + self.check_version_conflict() + if replace: + path.insert(0, loc) + else: + path.append(loc) + return + + # p is the spot where we found or inserted loc; now remove duplicates + while True: + try: + np = npath.index(nloc, p + 1) + except ValueError: + break + else: + del npath[np], path[np] + # ha! + p = np + + return + + def check_version_conflict(self): + if self.key == 'setuptools': + # ignore the inevitable setuptools self-conflicts :( + return + + nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt')) + loc = normalize_path(self.location) + for modname in self._get_metadata('top_level.txt'): + if (modname not in sys.modules or modname in nsp + or modname in _namespace_packages): + continue + if modname in ('pkg_resources', 'setuptools', 'site'): + continue + fn = getattr(sys.modules[modname], '__file__', None) + if fn and (normalize_path(fn).startswith(loc) or + fn.startswith(self.location)): + continue + issue_warning( + "Module %s was already imported from %s, but %s is being added" + " to sys.path" % (modname, fn, self.location), + ) + + def has_version(self): + try: + self.version + except ValueError: + issue_warning("Unbuilt egg for " + repr(self)) + return False + return True + + def clone(self, **kw): + """Copy this distribution, substituting in any changed keyword args""" + names = 'project_name version py_version platform location precedence' + for attr in names.split(): + kw.setdefault(attr, getattr(self, attr, None)) + kw.setdefault('metadata', self._provider) + return self.__class__(**kw) + + @property + def extras(self): + return [dep for dep in self._dep_map if dep] + + +class EggInfoDistribution(Distribution): + def _reload_version(self): + """ + Packages installed by distutils (e.g. numpy or scipy), + which uses an old safe_version, and so + their version numbers can get mangled when + converted to filenames (e.g., 1.11.0.dev0+2329eae to + 1.11.0.dev0_2329eae). These distributions will not be + parsed properly + downstream by Distribution and safe_version, so + take an extra step and try to get the version number from + the metadata file itself instead of the filename. + """ + md_version = self._get_version() + if md_version: + self._version = md_version + return self + + +class DistInfoDistribution(Distribution): + """ + Wrap an actual or potential sys.path entry + w/metadata, .dist-info style. + """ + PKG_INFO = 'METADATA' + EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])") + + @property + def _parsed_pkg_info(self): + """Parse and cache metadata""" + try: + return self._pkg_info + except AttributeError: + metadata = self.get_metadata(self.PKG_INFO) + self._pkg_info = email.parser.Parser().parsestr(metadata) + return self._pkg_info + + @property + def _dep_map(self): + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._compute_dependencies() + return self.__dep_map + + def _compute_dependencies(self): + """Recompute this distribution's dependencies.""" + dm = self.__dep_map = {None: []} + + reqs = [] + # Including any condition expressions + for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: + reqs.extend(parse_requirements(req)) + + def reqs_for_extra(extra): + for req in reqs: + if not req.marker or req.marker.evaluate({'extra': extra}): + yield req + + common = frozenset(reqs_for_extra(None)) + dm[None].extend(common) + + for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: + s_extra = safe_extra(extra.strip()) + dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common) + + return dm + + +_distributionImpl = { + '.egg': Distribution, + '.egg-info': EggInfoDistribution, + '.dist-info': DistInfoDistribution, +} + + +def issue_warning(*args, **kw): + level = 1 + g = globals() + try: + # find the first stack frame that is *not* code in + # the pkg_resources module, to use for the warning + while sys._getframe(level).f_globals is g: + level += 1 + except ValueError: + pass + warnings.warn(stacklevel=level + 1, *args, **kw) + + +def parse_requirements(strs): + """Yield ``Requirement`` objects for each specification in `strs` + + `strs` must be a string, or a (possibly-nested) iterable thereof. + """ + # create a steppable iterator, so we can handle \-continuations + lines = iter(yield_lines(strs)) + + for line in lines: + # Drop comments -- a hash without a space may be in a URL. + if ' #' in line: + line = line[:line.find(' #')] + # If there is a line continuation, drop it, and append the next line. + if line.endswith('\\'): + line = line[:-2].strip() + try: + line += next(lines) + except StopIteration: + return + yield Requirement(line) + + +class RequirementParseError(packaging.requirements.InvalidRequirement): + "Compatibility wrapper for InvalidRequirement" + + +class Requirement(packaging.requirements.Requirement): + def __init__(self, requirement_string): + """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!""" + super(Requirement, self).__init__(requirement_string) + self.unsafe_name = self.name + project_name = safe_name(self.name) + self.project_name, self.key = project_name, project_name.lower() + self.specs = [ + (spec.operator, spec.version) for spec in self.specifier] + self.extras = tuple(map(safe_extra, self.extras)) + self.hashCmp = ( + self.key, + self.url, + self.specifier, + frozenset(self.extras), + str(self.marker) if self.marker else None, + ) + self.__hash = hash(self.hashCmp) + + def __eq__(self, other): + return ( + isinstance(other, Requirement) and + self.hashCmp == other.hashCmp + ) + + def __ne__(self, other): + return not self == other + + def __contains__(self, item): + if isinstance(item, Distribution): + if item.key != self.key: + return False + + item = item.version + + # Allow prereleases always in order to match the previous behavior of + # this method. In the future this should be smarter and follow PEP 440 + # more accurately. + return self.specifier.contains(item, prereleases=True) + + def __hash__(self): + return self.__hash + + def __repr__(self): + return "Requirement.parse(%r)" % str(self) + + @staticmethod + def parse(s): + req, = parse_requirements(s) + return req + + +def _always_object(classes): + """ + Ensure object appears in the mro even + for old-style classes. + """ + if object not in classes: + return classes + (object,) + return classes + + +def _find_adapter(registry, ob): + """Return an adapter factory for `ob` from `registry`""" + types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob)))) + for t in types: + if t in registry: + return registry[t] + + +def ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + os.makedirs(dirname, exist_ok=True) + + +def _bypass_ensure_directory(path): + """Sandbox-bypassing version of ensure_directory()""" + if not WRITE_SUPPORT: + raise IOError('"os.mkdir" not supported on this platform.') + dirname, filename = split(path) + if dirname and filename and not isdir(dirname): + _bypass_ensure_directory(dirname) + try: + mkdir(dirname, 0o755) + except FileExistsError: + pass + + +def split_sections(s): + """Split a string or iterable thereof into (section, content) pairs + + Each ``section`` is a stripped version of the section header ("[section]") + and each ``content`` is a list of stripped lines excluding blank lines and + comment-only lines. If there are any such lines before the first section + header, they're returned in a first ``section`` of ``None``. + """ + section = None + content = [] + for line in yield_lines(s): + if line.startswith("["): + if line.endswith("]"): + if section or content: + yield section, content + section = line[1:-1].strip() + content = [] + else: + raise ValueError("Invalid section heading", line) + else: + content.append(line) + + # wrap up last segment + yield section, content + + +def _mkstemp(*args, **kw): + old_open = os.open + try: + # temporarily bypass sandboxing + os.open = os_open + return tempfile.mkstemp(*args, **kw) + finally: + # and then put it back + os.open = old_open + + +# Silence the PEP440Warning by default, so that end users don't get hit by it +# randomly just because they use pkg_resources. We want to append the rule +# because we want earlier uses of filterwarnings to take precedence over this +# one. +warnings.filterwarnings("ignore", category=PEP440Warning, append=True) + + +# from jaraco.functools 1.3 +def _call_aside(f, *args, **kwargs): + f(*args, **kwargs) + return f + + +@_call_aside +def _initialize(g=globals()): + "Set up global resource manager (deliberately not state-saved)" + manager = ResourceManager() + g['_manager'] = manager + g.update( + (name, getattr(manager, name)) + for name in dir(manager) + if not name.startswith('_') + ) + + +@_call_aside +def _initialize_master_working_set(): + """ + Prepare the master working set and make the ``require()`` + API available. + + This function has explicit effects on the global state + of pkg_resources. It is intended to be invoked once at + the initialization of this module. + + Invocation by other packages is unsupported and done + at their own risk. + """ + working_set = WorkingSet._build_master() + _declare_state('object', working_set=working_set) + + require = working_set.require + iter_entry_points = working_set.iter_entry_points + add_activation_listener = working_set.subscribe + run_script = working_set.run_script + # backward compatibility + run_main = run_script + # Activate all distributions already on sys.path with replace=False and + # ensure that all distributions added to the working set in the future + # (e.g. by calling ``require()``) will get activated as well, + # with higher priority (replace=True). + tuple( + dist.activate(replace=False) + for dist in working_set + ) + add_activation_listener( + lambda dist: dist.activate(replace=True), + existing=False, + ) + working_set.entries = [] + # match order + list(map(working_set.add_entry, sys.path)) + globals().update(locals()) + + +class PkgResourcesDeprecationWarning(Warning): + """ + Base class for warning about deprecations in ``pkg_resources`` + + This class is not derived from ``DeprecationWarning``, and as such is + visible by default. + """ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..5c971f2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__init__.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..cbd3d6d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/appdirs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/appdirs.cpython-38.pyc new file mode 100644 index 0000000..ec1540d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/appdirs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-38.pyc new file mode 100644 index 0000000..6517216 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/appdirs.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/appdirs.py new file mode 100644 index 0000000..ae67001 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/appdirs.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy PetriÈ™or + +"""Utilities for determining application-specific dirs. + +See for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version_info__ = (1, 4, 3) +__version__ = '.'.join(map(str, __version_info__)) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/ + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ + Win 7 (not roaming): C:\Users\\AppData\Local\\ + Win 7 (roaming): C:\Users\\AppData\Roaming\\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/ + Unix: /usr/local/share/ or /usr/share/ + Win XP: C:\Documents and Settings\All Users\Application Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user config directories are: + Mac OS X: same as user_data_dir + Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system in ["win32", "darwin"]: + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista: C:\Users\\AppData\Local\\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/ + Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs + Vista: C:\Users\\AppData\Local\\\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_pywin32(csidl_name): + from win32com.shell import shellcon, shell + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) + # Try to make this a unicode path because SHGetFolderPath does + # not return unicode strings when there is unicode data in the + # path. + try: + dir = unicode(dir) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + try: + import win32api + dir = win32api.GetShortPathName(dir) + except ImportError: + pass + except UnicodeError: + pass + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +if system == "win32": + try: + import win32com.shell + _get_win_folder = _get_win_folder_with_pywin32 + except ImportError: + try: + from ctypes import windll + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + try: + import com.sun.jna + _get_win_folder = _get_win_folder_with_jna + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__about__.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__about__.py new file mode 100644 index 0000000..4d99857 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__about__.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "20.4" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = "Copyright 2014-2019 %s" % __author__ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__init__.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__init__.py new file mode 100644 index 0000000..a0cf67d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__init__.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +from .__about__ import ( + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, +) + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-38.pyc new file mode 100644 index 0000000..4fbbbb8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ba1fc77 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_compat.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_compat.cpython-38.pyc new file mode 100644 index 0000000..c678a5f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_compat.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-38.pyc new file mode 100644 index 0000000..5a0d714 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_typing.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_typing.cpython-38.pyc new file mode 100644 index 0000000..1a6475c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/_typing.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-38.pyc new file mode 100644 index 0000000..9161953 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-38.pyc new file mode 100644 index 0000000..15c5861 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc new file mode 100644 index 0000000..8e908c3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-38.pyc new file mode 100644 index 0000000..6568800 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..9e0392d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-38.pyc new file mode 100644 index 0000000..c7547ae Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_compat.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_compat.py new file mode 100644 index 0000000..e54bd4e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_compat.py @@ -0,0 +1,38 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import sys + +from ._typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from typing import Any, Dict, Tuple, Type + + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +# flake8: noqa + +if PY3: + string_types = (str,) +else: + string_types = (basestring,) + + +def with_metaclass(meta, *bases): + # type: (Type[Any], Tuple[Type[Any], ...]) -> Any + """ + Create a base class with a metaclass. + """ + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): # type: ignore + def __new__(cls, name, this_bases, d): + # type: (Type[Any], str, Tuple[Any], Dict[Any, Any]) -> Any + return meta(name, bases, d) + + return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_structures.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_structures.py new file mode 100644 index 0000000..800d5c5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_structures.py @@ -0,0 +1,86 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + + +class InfinityType(object): + def __repr__(self): + # type: () -> str + return "Infinity" + + def __hash__(self): + # type: () -> int + return hash(repr(self)) + + def __lt__(self, other): + # type: (object) -> bool + return False + + def __le__(self, other): + # type: (object) -> bool + return False + + def __eq__(self, other): + # type: (object) -> bool + return isinstance(other, self.__class__) + + def __ne__(self, other): + # type: (object) -> bool + return not isinstance(other, self.__class__) + + def __gt__(self, other): + # type: (object) -> bool + return True + + def __ge__(self, other): + # type: (object) -> bool + return True + + def __neg__(self): + # type: (object) -> NegativeInfinityType + return NegativeInfinity + + +Infinity = InfinityType() + + +class NegativeInfinityType(object): + def __repr__(self): + # type: () -> str + return "-Infinity" + + def __hash__(self): + # type: () -> int + return hash(repr(self)) + + def __lt__(self, other): + # type: (object) -> bool + return True + + def __le__(self, other): + # type: (object) -> bool + return True + + def __eq__(self, other): + # type: (object) -> bool + return isinstance(other, self.__class__) + + def __ne__(self, other): + # type: (object) -> bool + return not isinstance(other, self.__class__) + + def __gt__(self, other): + # type: (object) -> bool + return False + + def __ge__(self, other): + # type: (object) -> bool + return False + + def __neg__(self): + # type: (object) -> InfinityType + return Infinity + + +NegativeInfinity = NegativeInfinityType() diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_typing.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_typing.py new file mode 100644 index 0000000..77a8b91 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_typing.py @@ -0,0 +1,48 @@ +"""For neatly implementing static typing in packaging. + +`mypy` - the static type analysis tool we use - uses the `typing` module, which +provides core functionality fundamental to mypy's functioning. + +Generally, `typing` would be imported at runtime and used in that fashion - +it acts as a no-op at runtime and does not have any run-time overhead by +design. + +As it turns out, `typing` is not vendorable - it uses separate sources for +Python 2/Python 3. Thus, this codebase can not expect it to be present. +To work around this, mypy allows the typing import to be behind a False-y +optional to prevent it from running at runtime and type-comments can be used +to remove the need for the types to be accessible directly during runtime. + +This module provides the False-y guard in a nicely named fashion so that a +curious maintainer can reach here to read this. + +In packaging, all static-typing related imports should be guarded as follows: + + from packaging._typing import TYPE_CHECKING + + if TYPE_CHECKING: + from typing import ... + +Ref: https://github.com/python/mypy/issues/3216 +""" + +__all__ = ["TYPE_CHECKING", "cast"] + +# The TYPE_CHECKING constant defined by the typing module is False at runtime +# but True while type checking. +if False: # pragma: no cover + from typing import TYPE_CHECKING +else: + TYPE_CHECKING = False + +# typing's cast syntax requires calling typing.cast at runtime, but we don't +# want to import typing at runtime. Here, we inform the type checkers that +# we're importing `typing.cast` as `cast` and re-implement typing.cast's +# runtime behavior in a block that is ignored by type checkers. +if TYPE_CHECKING: # pragma: no cover + # not executed at runtime + from typing import cast +else: + # executed at runtime + def cast(type_, value): # noqa + return value diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/markers.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/markers.py new file mode 100644 index 0000000..fd1559c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/markers.py @@ -0,0 +1,328 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import operator +import os +import platform +import sys + +from pkg_resources.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd +from pkg_resources.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString +from pkg_resources.extern.pyparsing import Literal as L # noqa + +from ._compat import string_types +from ._typing import TYPE_CHECKING +from .specifiers import Specifier, InvalidSpecifier + +if TYPE_CHECKING: # pragma: no cover + from typing import Any, Callable, Dict, List, Optional, Tuple, Union + + Operator = Callable[[str, str], bool] + + +__all__ = [ + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", +] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node(object): + def __init__(self, value): + # type: (Any) -> None + self.value = value + + def __str__(self): + # type: () -> str + return str(self.value) + + def __repr__(self): + # type: () -> str + return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + + def serialize(self): + # type: () -> str + raise NotImplementedError + + +class Variable(Node): + def serialize(self): + # type: () -> str + return str(self) + + +class Value(Node): + def serialize(self): + # type: () -> str + return '"{0}"'.format(self) + + +class Op(Node): + def serialize(self): + # type: () -> str + return str(self) + + +VARIABLE = ( + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") # PEP-345 + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # undocumented setuptools legacy + | L("extra") # PEP-508 +) +ALIASES = { + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results): + # type: (Union[ParseResults, List[Any]]) -> List[Any] + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker(marker, first=True): + # type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str + + assert isinstance(marker, (list, tuple, string_types)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} # type: Dict[str, Operator] + + +def _eval_op(lhs, op, rhs): + # type: (str, Op, str) -> bool + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper = _operators.get(op.serialize()) # type: Optional[Operator] + if oper is None: + raise UndefinedComparison( + "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) + ) + + return oper(lhs, rhs) + + +class Undefined(object): + pass + + +_undefined = Undefined() + + +def _get_env(environment, name): + # type: (Dict[str, str], str) -> str + value = environment.get(name, _undefined) # type: Union[str, Undefined] + + if isinstance(value, Undefined): + raise UndefinedEnvironmentName( + "{0!r} does not exist in evaluation environment.".format(name) + ) + + return value + + +def _evaluate_markers(markers, environment): + # type: (List[Any], Dict[str, str]) -> bool + groups = [[]] # type: List[List[bool]] + + for marker in markers: + assert isinstance(marker, (list, tuple, string_types)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info): + # type: (sys._version_info) -> str + version = "{0.major}.{0.minor}.{0.micro}".format(info) + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment(): + # type: () -> Dict[str, str] + if hasattr(sys, "implementation"): + # Ignoring the `sys.implementation` reference for type checking due to + # mypy not liking that the attribute doesn't exist in Python 2.7 when + # run with the `--py27` flag. + iver = format_full_version(sys.implementation.version) # type: ignore + implementation_name = sys.implementation.name # type: ignore + else: + iver = "0" + implementation_name = "" + + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker(object): + def __init__(self, marker): + # type: (str) -> None + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( + marker, marker[e.loc : e.loc + 8] + ) + raise InvalidMarker(err_str) + + def __str__(self): + # type: () -> str + return _format_marker(self._markers) + + def __repr__(self): + # type: () -> str + return "".format(str(self)) + + def evaluate(self, environment=None): + # type: (Optional[Dict[str, str]]) -> bool + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/requirements.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/requirements.py new file mode 100644 index 0000000..9495a1d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/requirements.py @@ -0,0 +1,145 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import string +import re + +from pkg_resources.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException +from pkg_resources.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine +from pkg_resources.extern.pyparsing import Literal as L # noqa +from urllib import parse as urlparse + +from ._typing import TYPE_CHECKING +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + +if TYPE_CHECKING: # pragma: no cover + from typing import List + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r"[^ ]+")("url") +URL = AT + URI + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") +_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start : t._original_end]) +) +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# pkg_resources.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") + + +class Requirement(object): + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string): + # type: (str) -> None + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + 'Parse error at "{0!r}": {1}'.format( + requirement_string[e.loc : e.loc + 8], e.msg + ) + ) + + self.name = req.name + if req.url: + parsed_url = urlparse.urlparse(req.url) + if parsed_url.scheme == "file": + if urlparse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement("Invalid URL: {0}".format(req.url)) + self.url = req.url + else: + self.url = None + self.extras = set(req.extras.asList() if req.extras else []) + self.specifier = SpecifierSet(req.specifier) + self.marker = req.marker if req.marker else None + + def __str__(self): + # type: () -> str + parts = [self.name] # type: List[str] + + if self.extras: + parts.append("[{0}]".format(",".join(sorted(self.extras)))) + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append("@ {0}".format(self.url)) + if self.marker: + parts.append(" ") + + if self.marker: + parts.append("; {0}".format(self.marker)) + + return "".join(parts) + + def __repr__(self): + # type: () -> str + return "".format(str(self)) diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/specifiers.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/specifiers.py new file mode 100644 index 0000000..fe09bb1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/specifiers.py @@ -0,0 +1,863 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import abc +import functools +import itertools +import re + +from ._compat import string_types, with_metaclass +from ._typing import TYPE_CHECKING +from .utils import canonicalize_version +from .version import Version, LegacyVersion, parse + +if TYPE_CHECKING: # pragma: no cover + from typing import ( + List, + Dict, + Union, + Iterable, + Iterator, + Optional, + Callable, + Tuple, + FrozenSet, + ) + + ParsedVersion = Union[Version, LegacyVersion] + UnparsedVersion = Union[Version, LegacyVersion, str] + CallableOperator = Callable[[ParsedVersion, str], bool] + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore + @abc.abstractmethod + def __str__(self): + # type: () -> str + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self): + # type: () -> int + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other): + # type: (object) -> bool + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other): + # type: (object) -> bool + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self): + # type: () -> Optional[bool] + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item, prereleases=None): + # type: (str, Optional[bool]) -> bool + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter(self, iterable, prereleases=None): + # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators = {} # type: Dict[str, str] + + def __init__(self, spec="", prereleases=None): + # type: (str, Optional[bool]) -> None + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + + self._spec = ( + match.group("operator").strip(), + match.group("version").strip(), + ) # type: Tuple[str, str] + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self): + # type: () -> str + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) + + def __str__(self): + # type: () -> str + return "{0}{1}".format(*self._spec) + + @property + def _canonical_spec(self): + # type: () -> Tuple[str, Union[Version, str]] + return self._spec[0], canonicalize_version(self._spec[1]) + + def __hash__(self): + # type: () -> int + return hash(self._canonical_spec) + + def __eq__(self, other): + # type: (object) -> bool + if isinstance(other, string_types): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._canonical_spec == other._canonical_spec + + def __ne__(self, other): + # type: (object) -> bool + if isinstance(other, string_types): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op): + # type: (str) -> CallableOperator + operator_callable = getattr( + self, "_compare_{0}".format(self._operators[op]) + ) # type: CallableOperator + return operator_callable + + def _coerce_version(self, version): + # type: (UnparsedVersion) -> ParsedVersion + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self): + # type: () -> str + return self._spec[0] + + @property + def version(self): + # type: () -> str + return self._spec[1] + + @property + def prereleases(self): + # type: () -> Optional[bool] + return self._prereleases + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + self._prereleases = value + + def __contains__(self, item): + # type: (str) -> bool + return self.contains(item) + + def contains(self, item, prereleases=None): + # type: (UnparsedVersion, Optional[bool]) -> bool + + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + normalized_item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if normalized_item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + operator_callable = self._get_operator(self.operator) # type: CallableOperator + return operator_callable(normalized_item, self.version) + + def filter(self, iterable, prereleases=None): + # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later incase nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(==|!=|<=|>=|<|>)) + \s* + (?P + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def _coerce_version(self, version): + # type: (Union[ParsedVersion, str]) -> LegacyVersion + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective > self._coerce_version(spec) + + +def _require_version_compare( + fn # type: (Callable[[Specifier, ParsedVersion, str], bool]) +): + # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool] + @functools.wraps(fn) + def wrapped(self, prospective, spec): + # type: (Specifier, ParsedVersion, str) -> bool + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore post and dev releases and we want to treat the pre-release as + # it's own separate segment. + prefix = ".".join( + list( + itertools.takewhile( + lambda x: (not x.startswith("post") and not x.startswith("dev")), + _version_split(spec), + ) + )[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + @_require_version_compare + def _compare_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + split_spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + split_prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + shortened_prospective = split_prospective[: len(split_spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + padded_spec, padded_prospective = _pad_version( + split_spec, shortened_prospective + ) + + return padded_prospective == padded_spec + else: + # Convert our spec string into a Version + spec_version = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec_version.local: + prospective = Version(prospective.public) + + return prospective == spec_version + + @_require_version_compare + def _compare_not_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective, spec_str): + # type: (ParsedVersion, str) -> bool + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective, spec_str): + # type: (ParsedVersion, str) -> bool + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective, spec): + # type: (Version, str) -> bool + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self): + # type: () -> bool + + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version): + # type: (str) -> List[str] + result = [] # type: List[str] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _pad_version(left, right): + # type: (List[str], List[str]) -> Tuple[List[str], List[str]] + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) + + +class SpecifierSet(BaseSpecifier): + def __init__(self, specifiers="", prereleases=None): + # type: (str, Optional[bool]) -> None + + # Split on , to break each individual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed = set() + for specifier in split_specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self): + # type: () -> str + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "".format(str(self), pre) + + def __str__(self): + # type: () -> str + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self): + # type: () -> int + return hash(self._specs) + + def __and__(self, other): + # type: (Union[SpecifierSet, str]) -> SpecifierSet + if isinstance(other, string_types): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other): + # type: (object) -> bool + if isinstance(other, (string_types, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other): + # type: (object) -> bool + if isinstance(other, (string_types, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self): + # type: () -> int + return len(self._specs) + + def __iter__(self): + # type: () -> Iterator[FrozenSet[_IndividualSpecifier]] + return iter(self._specs) + + @property + def prereleases(self): + # type: () -> Optional[bool] + + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + self._prereleases = value + + def __contains__(self, item): + # type: (Union[ParsedVersion, str]) -> bool + return self.contains(item) + + def contains(self, item, prereleases=None): + # type: (Union[ParsedVersion, str], Optional[bool]) -> bool + + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter( + self, + iterable, # type: Iterable[Union[ParsedVersion, str]] + prereleases=None, # type: Optional[bool] + ): + # type: (...) -> Iterable[Union[ParsedVersion, str]] + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered = [] # type: List[Union[ParsedVersion, str]] + found_prereleases = [] # type: List[Union[ParsedVersion, str]] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/tags.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/tags.py new file mode 100644 index 0000000..9064910 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/tags.py @@ -0,0 +1,751 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import + +import distutils.util + +try: + from importlib.machinery import EXTENSION_SUFFIXES +except ImportError: # pragma: no cover + import imp + + EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] + del imp +import logging +import os +import platform +import re +import struct +import sys +import sysconfig +import warnings + +from ._typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: # pragma: no cover + from typing import ( + Dict, + FrozenSet, + IO, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + ) + + PythonVersion = Sequence[int] + MacVersion = Tuple[int, int] + GlibcVersion = Tuple[int, int] + + +logger = logging.getLogger(__name__) + +INTERPRETER_SHORT_NAMES = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} # type: Dict[str, str] + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag(object): + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform"] + + def __init__(self, interpreter, abi, platform): + # type: (str, str, str) -> None + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + + @property + def interpreter(self): + # type: () -> str + return self._interpreter + + @property + def abi(self): + # type: () -> str + return self._abi + + @property + def platform(self): + # type: () -> str + return self._platform + + def __eq__(self, other): + # type: (object) -> bool + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self.platform == other.platform) + and (self.abi == other.abi) + and (self.interpreter == other.interpreter) + ) + + def __hash__(self): + # type: () -> int + return hash((self._interpreter, self._abi, self._platform)) + + def __str__(self): + # type: () -> str + return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) + + def __repr__(self): + # type: () -> str + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag): + # type: (str) -> FrozenSet[Tag] + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _warn_keyword_parameter(func_name, kwargs): + # type: (str, Dict[str, bool]) -> bool + """ + Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. + """ + if not kwargs: + return False + elif len(kwargs) > 1 or "warn" not in kwargs: + kwargs.pop("warn", None) + arg = next(iter(kwargs.keys())) + raise TypeError( + "{}() got an unexpected keyword argument {!r}".format(func_name, arg) + ) + return kwargs["warn"] + + +def _get_config_var(name, warn=False): + # type: (str, bool) -> Union[int, str, None] + value = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string): + # type: (str) -> str + return string.replace(".", "_").replace("-", "_") + + +def _abi3_applies(python_version): + # type: (PythonVersion) -> bool + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) + + +def _cpython_abis(py_version, warn=False): + # type: (PythonVersion, bool) -> List[str] + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append("cp{version}".format(version=version)) + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def cpython_tags( + python_version=None, # type: Optional[PythonVersion] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + warn = _warn_keyword_parameter("cpython_tags", kwargs) + if not python_version: + python_version = sys.version_info[:2] + + interpreter = "cp{}".format(_version_nodot(python_version[:2])) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or _platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if _abi3_applies(python_version): + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): + yield tag + + if _abi3_applies(python_version): + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi(): + # type: () -> Iterator[str] + abi = sysconfig.get_config_var("SOABI") + if abi: + yield _normalize_string(abi) + + +def generic_tags( + interpreter=None, # type: Optional[str] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - -- + + The "none" ABI will be added if it was not explicitly provided. + """ + warn = _warn_keyword_parameter("generic_tags", kwargs) + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or _platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version): + # type: (PythonVersion) -> Iterator[str] + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield "py{version}".format(version=_version_nodot(py_version[:2])) + yield "py{major}".format(major=py_version[0]) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + + +def compatible_tags( + python_version=None, # type: Optional[PythonVersion] + interpreter=None, # type: Optional[str] + platforms=None, # type: Optional[Iterable[str]] +): + # type: (...) -> Iterator[Tag] + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or _platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): + # type: (str, bool) -> str + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version, cpu_arch): + # type: (MacVersion, str) -> List[str] + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + formats.append("universal") + return formats + + +def mac_platforms(version=None, arch=None): + # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() # type: ignore + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + for minor_version in range(version[1], -1, -1): + compat_version = version[0], minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +# From PEP 513. +def _is_manylinux_compatible(name, glibc_version): + # type: (str, GlibcVersion) -> bool + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + + return bool(getattr(_manylinux, name + "_compatible")) + except (ImportError, AttributeError): + # Fall through to heuristic check below. + pass + + return _have_compatible_glibc(*glibc_version) + + +def _glibc_version_string(): + # type: () -> Optional[str] + # Returns glibc version string, or None if not using glibc. + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _glibc_version_string_confstr(): + # type: () -> Optional[str] + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr( # type: ignore[attr-defined] # noqa: F821 + "CS_GNU_LIBC_VERSION" + ) + assert version_string is not None + _, version = version_string.split() # type: Tuple[str, str] + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes(): + # type: () -> Optional[str] + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # Note: typeshed is wrong here so we are ignoring this line. + process_namespace = ctypes.CDLL(None) # type: ignore + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() # type: str + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# Separated out from have_compatible_glibc for easier unit testing. +def _check_glibc_version(version_str, required_major, minimum_minor): + # type: (str, int, int) -> bool + # Parse string and check against requested version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return False + return ( + int(m.group("major")) == required_major + and int(m.group("minor")) >= minimum_minor + ) + + +def _have_compatible_glibc(required_major, minimum_minor): + # type: (int, int) -> bool + version_str = _glibc_version_string() + if version_str is None: + return False + return _check_glibc_version(version_str, required_major, minimum_minor) + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader(object): + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file): + # type: (IO[bytes]) -> None + def unpack(fmt): + # type: (str) -> int + try: + (result,) = struct.unpack( + fmt, file.read(struct.calcsize(fmt)) + ) # type: (int, ) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "H" + format_i = "I" + format_q = "Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header(): + # type: () -> Optional[_ELFFileHeader] + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf(): + # type: () -> bool + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686(): + # type: () -> bool + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_manylinux_abi(arch): + # type: (str) -> bool + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return True + + +def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): + # type: (bool) -> Iterator[str] + linux = _normalize_string(distutils.util.get_platform()) + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv7l" + manylinux_support = [] + _, arch = linux.split("_", 1) + if _have_compatible_manylinux_abi(arch): + if arch in {"x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le", "s390x"}: + manylinux_support.append( + ("manylinux2014", (2, 17)) + ) # CentOS 7 w/ glibc 2.17 (PEP 599) + if arch in {"x86_64", "i686"}: + manylinux_support.append( + ("manylinux2010", (2, 12)) + ) # CentOS 6 w/ glibc 2.12 (PEP 571) + manylinux_support.append( + ("manylinux1", (2, 5)) + ) # CentOS 5 w/ glibc 2.5 (PEP 513) + manylinux_support_iter = iter(manylinux_support) + for name, glibc_version in manylinux_support_iter: + if _is_manylinux_compatible(name, glibc_version): + yield linux.replace("linux", name) + break + # Support for a later manylinux implies support for an earlier version. + for name, _ in manylinux_support_iter: + yield linux.replace("linux", name) + yield linux + + +def _generic_platforms(): + # type: () -> Iterator[str] + yield _normalize_string(distutils.util.get_platform()) + + +def _platform_tags(): + # type: () -> Iterator[str] + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name(): + # type: () -> str + """ + Returns the name of the running interpreter. + """ + try: + name = sys.implementation.name # type: ignore + except AttributeError: # pragma: no cover + # Python 2.7 compatibility. + name = platform.python_implementation().lower() + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(**kwargs): + # type: (bool) -> str + """ + Returns the version of the running interpreter. + """ + warn = _warn_keyword_parameter("interpreter_version", kwargs) + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version): + # type: (PythonVersion) -> str + if any(v >= 10 for v in version): + sep = "_" + else: + sep = "" + return sep.join(map(str, version)) + + +def sys_tags(**kwargs): + # type: (bool) -> Iterator[Tag] + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + warn = _warn_keyword_parameter("sys_tags", kwargs) + + interp_name = interpreter_name() + if interp_name == "cp": + for tag in cpython_tags(warn=warn): + yield tag + else: + for tag in generic_tags(): + yield tag + + for tag in compatible_tags(): + yield tag diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/utils.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/utils.py new file mode 100644 index 0000000..19579c1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/utils.py @@ -0,0 +1,65 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import re + +from ._typing import TYPE_CHECKING, cast +from .version import InvalidVersion, Version + +if TYPE_CHECKING: # pragma: no cover + from typing import NewType, Union + + NormalizedName = NewType("NormalizedName", str) + +_canonicalize_regex = re.compile(r"[-_.]+") + + +def canonicalize_name(name): + # type: (str) -> NormalizedName + # This is taken from PEP 503. + value = _canonicalize_regex.sub("-", name).lower() + return cast("NormalizedName", value) + + +def canonicalize_version(_version): + # type: (str) -> Union[Version, str] + """ + This is very similar to Version.__str__, but has one subtle difference + with the way it handles the release segment. + """ + + try: + version = Version(_version) + except InvalidVersion: + # Legacy versions cannot be normalized + return _version + + parts = [] + + # Epoch + if version.epoch != 0: + parts.append("{0}!".format(version.epoch)) + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + + # Pre-release + if version.pre is not None: + parts.append("".join(str(x) for x in version.pre)) + + # Post-release + if version.post is not None: + parts.append(".post{0}".format(version.post)) + + # Development release + if version.dev is not None: + parts.append(".dev{0}".format(version.dev)) + + # Local version segment + if version.local is not None: + parts.append("+{0}".format(version.local)) + + return "".join(parts) diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/version.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/version.py new file mode 100644 index 0000000..00371e8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/version.py @@ -0,0 +1,535 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import collections +import itertools +import re + +from ._structures import Infinity, NegativeInfinity +from ._typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union + + from ._structures import InfinityType, NegativeInfinityType + + InfiniteTypes = Union[InfinityType, NegativeInfinityType] + PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] + SubLocalType = Union[InfiniteTypes, int, str] + LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], + ] + CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType + ] + LegacyCmpKey = Tuple[int, Tuple[str, ...]] + VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool + ] + +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] + + +_Version = collections.namedtuple( + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] +) + + +def parse(version): + # type: (str) -> Union[LegacyVersion, Version] + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion(object): + _key = None # type: Union[CmpKey, LegacyCmpKey] + + def __hash__(self): + # type: () -> int + return hash(self._key) + + def __lt__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s <= o) + + def __eq__(self, other): + # type: (object) -> bool + return self._compare(other, lambda s, o: s == o) + + def __ge__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + # type: (object) -> bool + return self._compare(other, lambda s, o: s != o) + + def _compare(self, other, method): + # type: (object, VersionComparisonMethod) -> Union[bool, NotImplemented] + if not isinstance(other, _BaseVersion): + return NotImplemented + + return method(self._key, other._key) + + +class LegacyVersion(_BaseVersion): + def __init__(self, version): + # type: (str) -> None + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + # type: () -> str + return self._version + + def __repr__(self): + # type: () -> str + return "".format(repr(str(self))) + + @property + def public(self): + # type: () -> str + return self._version + + @property + def base_version(self): + # type: () -> str + return self._version + + @property + def epoch(self): + # type: () -> int + return -1 + + @property + def release(self): + # type: () -> None + return None + + @property + def pre(self): + # type: () -> None + return None + + @property + def post(self): + # type: () -> None + return None + + @property + def dev(self): + # type: () -> None + return None + + @property + def local(self): + # type: () -> None + return None + + @property + def is_prerelease(self): + # type: () -> bool + return False + + @property + def is_postrelease(self): + # type: () -> bool + return False + + @property + def is_devrelease(self): + # type: () -> bool + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s): + # type: (str) -> Iterator[str] + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version): + # type: (str) -> LegacyCmpKey + + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts = [] # type: List[str] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + + return epoch, tuple(parts) + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+
+class Version(_BaseVersion):
+
+    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+
+    def __init__(self, version):
+        # type: (str) -> None
+
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion("Invalid version: '{0}'".format(version))
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+            post=_parse_letter_version(
+                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+            ),
+            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+            local=_parse_local_version(match.group("local")),
+        )
+
+        # Generate a key which will be used for sorting
+        self._key = _cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+    def __repr__(self):
+        # type: () -> str
+        return "".format(repr(str(self)))
+
+    def __str__(self):
+        # type: () -> str
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append("{0}!".format(self.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        # Pre-release
+        if self.pre is not None:
+            parts.append("".join(str(x) for x in self.pre))
+
+        # Post-release
+        if self.post is not None:
+            parts.append(".post{0}".format(self.post))
+
+        # Development release
+        if self.dev is not None:
+            parts.append(".dev{0}".format(self.dev))
+
+        # Local version segment
+        if self.local is not None:
+            parts.append("+{0}".format(self.local))
+
+        return "".join(parts)
+
+    @property
+    def epoch(self):
+        # type: () -> int
+        _epoch = self._version.epoch  # type: int
+        return _epoch
+
+    @property
+    def release(self):
+        # type: () -> Tuple[int, ...]
+        _release = self._version.release  # type: Tuple[int, ...]
+        return _release
+
+    @property
+    def pre(self):
+        # type: () -> Optional[Tuple[str, int]]
+        _pre = self._version.pre  # type: Optional[Tuple[str, int]]
+        return _pre
+
+    @property
+    def post(self):
+        # type: () -> Optional[Tuple[str, int]]
+        return self._version.post[1] if self._version.post else None
+
+    @property
+    def dev(self):
+        # type: () -> Optional[Tuple[str, int]]
+        return self._version.dev[1] if self._version.dev else None
+
+    @property
+    def local(self):
+        # type: () -> Optional[str]
+        if self._version.local:
+            return ".".join(str(x) for x in self._version.local)
+        else:
+            return None
+
+    @property
+    def public(self):
+        # type: () -> str
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self):
+        # type: () -> str
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append("{0}!".format(self.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        return "".join(parts)
+
+    @property
+    def is_prerelease(self):
+        # type: () -> bool
+        return self.dev is not None or self.pre is not None
+
+    @property
+    def is_postrelease(self):
+        # type: () -> bool
+        return self.post is not None
+
+    @property
+    def is_devrelease(self):
+        # type: () -> bool
+        return self.dev is not None
+
+    @property
+    def major(self):
+        # type: () -> int
+        return self.release[0] if len(self.release) >= 1 else 0
+
+    @property
+    def minor(self):
+        # type: () -> int
+        return self.release[1] if len(self.release) >= 2 else 0
+
+    @property
+    def micro(self):
+        # type: () -> int
+        return self.release[2] if len(self.release) >= 3 else 0
+
+
+def _parse_letter_version(
+    letter,  # type: str
+    number,  # type: Union[str, bytes, SupportsInt]
+):
+    # type: (...) -> Optional[Tuple[str, int]]
+
+    if letter:
+        # We consider there to be an implicit 0 in a pre-release if there is
+        # not a numeral associated with it.
+        if number is None:
+            number = 0
+
+        # We normalize any letters to their lower case form
+        letter = letter.lower()
+
+        # We consider some words to be alternate spellings of other words and
+        # in those cases we want to normalize the spellings to our preferred
+        # spelling.
+        if letter == "alpha":
+            letter = "a"
+        elif letter == "beta":
+            letter = "b"
+        elif letter in ["c", "pre", "preview"]:
+            letter = "rc"
+        elif letter in ["rev", "r"]:
+            letter = "post"
+
+        return letter, int(number)
+    if not letter and number:
+        # We assume if we are given a number, but we are not given a letter
+        # then this is using the implicit post release syntax (e.g. 1.0-1)
+        letter = "post"
+
+        return letter, int(number)
+
+    return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local):
+    # type: (str) -> Optional[LocalType]
+    """
+    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+    """
+    if local is not None:
+        return tuple(
+            part.lower() if not part.isdigit() else int(part)
+            for part in _local_version_separators.split(local)
+        )
+    return None
+
+
+def _cmpkey(
+    epoch,  # type: int
+    release,  # type: Tuple[int, ...]
+    pre,  # type: Optional[Tuple[str, int]]
+    post,  # type: Optional[Tuple[str, int]]
+    dev,  # type: Optional[Tuple[str, int]]
+    local,  # type: Optional[Tuple[SubLocalType]]
+):
+    # type: (...) -> CmpKey
+
+    # When we compare a release version, we want to compare it with all of the
+    # trailing zeros removed. So we'll use a reverse the list, drop all the now
+    # leading zeros until we come to something non zero, then take the rest
+    # re-reverse it back into the correct order and make it a tuple and use
+    # that for our sorting key.
+    _release = tuple(
+        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+    )
+
+    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+    # We'll do this by abusing the pre segment, but we _only_ want to do this
+    # if there is not a pre or a post segment. If we have one of those then
+    # the normal sorting rules will handle this case correctly.
+    if pre is None and post is None and dev is not None:
+        _pre = NegativeInfinity  # type: PrePostDevType
+    # Versions without a pre-release (except as noted above) should sort after
+    # those with one.
+    elif pre is None:
+        _pre = Infinity
+    else:
+        _pre = pre
+
+    # Versions without a post segment should sort before those with one.
+    if post is None:
+        _post = NegativeInfinity  # type: PrePostDevType
+
+    else:
+        _post = post
+
+    # Versions without a development segment should sort after those with one.
+    if dev is None:
+        _dev = Infinity  # type: PrePostDevType
+
+    else:
+        _dev = dev
+
+    if local is None:
+        # Versions without a local segment should sort before those with one.
+        _local = NegativeInfinity  # type: LocalType
+    else:
+        # Versions with a local segment need that segment parsed to implement
+        # the sorting rules in PEP440.
+        # - Alpha numeric segments sort before numeric segments
+        # - Alpha numeric segments sort lexicographically
+        # - Numeric segments sort numerically
+        # - Shorter versions sort before longer versions when the prefixes
+        #   match exactly
+        _local = tuple(
+            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+        )
+
+    return epoch, _release, _pre, _post, _dev, _local
diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/pyparsing.py b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/pyparsing.py
new file mode 100644
index 0000000..cf75e1e
--- /dev/null
+++ b/.venv/lib/python3.8/site-packages/pkg_resources/_vendor/pyparsing.py
@@ -0,0 +1,5742 @@
+# module pyparsing.py
+#
+# Copyright (c) 2003-2018  Paul T. McGuire
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__doc__ = \
+"""
+pyparsing module - Classes and methods to define and execute parsing grammars
+=============================================================================
+
+The pyparsing module is an alternative approach to creating and executing simple grammars,
+vs. the traditional lex/yacc approach, or the use of regular expressions.  With pyparsing, you
+don't need to learn a new syntax for defining grammars or matching expressions - the parsing module
+provides a library of classes that you use to construct the grammar directly in Python.
+
+Here is a program to parse "Hello, World!" (or any greeting of the form 
+C{", !"}), built up using L{Word}, L{Literal}, and L{And} elements 
+(L{'+'} operator gives L{And} expressions, strings are auto-converted to
+L{Literal} expressions)::
+
+    from pyparsing import Word, alphas
+
+    # define grammar of a greeting
+    greet = Word(alphas) + "," + Word(alphas) + "!"
+
+    hello = "Hello, World!"
+    print (hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+    Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the self-explanatory
+class names, and the use of '+', '|' and '^' operators.
+
+The L{ParseResults} object returned from L{ParserElement.parseString} can be accessed as a nested list, a dictionary, or an
+object with named attributes.
+
+The pyparsing module handles some of the problems that are typically vexing when writing text parsers:
+ - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello  ,  World  !", etc.)
+ - quoted strings
+ - embedded comments
+
+
+Getting Started -
+-----------------
+Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
+classes inherit from. Use the docstrings for examples of how to:
+ - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
+ - construct character word-group expressions using the L{Word} class
+ - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
+ - use L{'+'}, L{'|'}, L{'^'}, and L{'&'} operators to combine simple expressions into more complex ones
+ - associate names with your parsed results using L{ParserElement.setResultsName}
+ - find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
+ - find more useful common expressions in the L{pyparsing_common} namespace class
+"""
+
+__version__ = "2.2.1"
+__versionTime__ = "18 Sep 2018 00:49 UTC"
+__author__ = "Paul McGuire "
+
+import string
+from weakref import ref as wkref
+import copy
+import sys
+import warnings
+import re
+import sre_constants
+import collections
+import pprint
+import traceback
+import types
+from datetime import datetime
+
+try:
+    from _thread import RLock
+except ImportError:
+    from threading import RLock
+
+try:
+    # Python 3
+    from collections.abc import Iterable
+    from collections.abc import MutableMapping
+except ImportError:
+    # Python 2.7
+    from collections import Iterable
+    from collections import MutableMapping
+
+try:
+    from collections import OrderedDict as _OrderedDict
+except ImportError:
+    try:
+        from ordereddict import OrderedDict as _OrderedDict
+    except ImportError:
+        _OrderedDict = None
+
+#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
+
+__all__ = [
+'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty',
+'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal',
+'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or',
+'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException',
+'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException',
+'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 
+'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore',
+'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col',
+'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString',
+'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums',
+'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno',
+'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral',
+'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables',
+'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', 
+'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd',
+'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute',
+'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass',
+'CloseMatch', 'tokenMap', 'pyparsing_common',
+]
+
+system_version = tuple(sys.version_info)[:3]
+PY_3 = system_version[0] == 3
+if PY_3:
+    _MAX_INT = sys.maxsize
+    basestring = str
+    unichr = chr
+    _ustr = str
+
+    # build list of single arg builtins, that can be used as parse actions
+    singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max]
+
+else:
+    _MAX_INT = sys.maxint
+    range = xrange
+
+    def _ustr(obj):
+        """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries
+           str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It
+           then < returns the unicode object | encodes it with the default encoding | ... >.
+        """
+        if isinstance(obj,unicode):
+            return obj
+
+        try:
+            # If this works, then _ustr(obj) has the same behaviour as str(obj), so
+            # it won't break any existing code.
+            return str(obj)
+
+        except UnicodeEncodeError:
+            # Else encode it
+            ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
+            xmlcharref = Regex(r'&#\d+;')
+            xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
+            return xmlcharref.transformString(ret)
+
+    # build list of single arg builtins, tolerant of Python version, that can be used as parse actions
+    singleArgBuiltins = []
+    import __builtin__
+    for fname in "sum len sorted reversed list tuple set any all min max".split():
+        try:
+            singleArgBuiltins.append(getattr(__builtin__,fname))
+        except AttributeError:
+            continue
+            
+_generatorType = type((y for y in range(1)))
+ 
+def _xml_escape(data):
+    """Escape &, <, >, ", ', etc. in a string of data."""
+
+    # ampersand must be replaced first
+    from_symbols = '&><"\''
+    to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split())
+    for from_,to_ in zip(from_symbols, to_symbols):
+        data = data.replace(from_, to_)
+    return data
+
+class _Constants(object):
+    pass
+
+alphas     = string.ascii_uppercase + string.ascii_lowercase
+nums       = "0123456789"
+hexnums    = nums + "ABCDEFabcdef"
+alphanums  = alphas + nums
+_bslash    = chr(92)
+printables = "".join(c for c in string.printable if c not in string.whitespace)
+
+class ParseBaseException(Exception):
+    """base exception class for all parsing runtime exceptions"""
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, pstr, loc=0, msg=None, elem=None ):
+        self.loc = loc
+        if msg is None:
+            self.msg = pstr
+            self.pstr = ""
+        else:
+            self.msg = msg
+            self.pstr = pstr
+        self.parserElement = elem
+        self.args = (pstr, loc, msg)
+
+    @classmethod
+    def _from_exception(cls, pe):
+        """
+        internal factory method to simplify creating one type of ParseException 
+        from another - avoids having __init__ signature conflicts among subclasses
+        """
+        return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
+
+    def __getattr__( self, aname ):
+        """supported attributes by name are:
+            - lineno - returns the line number of the exception text
+            - col - returns the column number of the exception text
+            - line - returns the line containing the exception text
+        """
+        if( aname == "lineno" ):
+            return lineno( self.loc, self.pstr )
+        elif( aname in ("col", "column") ):
+            return col( self.loc, self.pstr )
+        elif( aname == "line" ):
+            return line( self.loc, self.pstr )
+        else:
+            raise AttributeError(aname)
+
+    def __str__( self ):
+        return "%s (at char %d), (line:%d, col:%d)" % \
+                ( self.msg, self.loc, self.lineno, self.column )
+    def __repr__( self ):
+        return _ustr(self)
+    def markInputline( self, markerString = ">!<" ):
+        """Extracts the exception line from the input string, and marks
+           the location of the exception with a special symbol.
+        """
+        line_str = self.line
+        line_column = self.column - 1
+        if markerString:
+            line_str = "".join((line_str[:line_column],
+                                markerString, line_str[line_column:]))
+        return line_str.strip()
+    def __dir__(self):
+        return "lineno col line".split() + dir(type(self))
+
+class ParseException(ParseBaseException):
+    """
+    Exception thrown when parse expressions don't match class;
+    supported attributes by name are:
+     - lineno - returns the line number of the exception text
+     - col - returns the column number of the exception text
+     - line - returns the line containing the exception text
+        
+    Example::
+        try:
+            Word(nums).setName("integer").parseString("ABC")
+        except ParseException as pe:
+            print(pe)
+            print("column: {}".format(pe.col))
+            
+    prints::
+       Expected integer (at char 0), (line:1, col:1)
+        column: 1
+    """
+    pass
+
+class ParseFatalException(ParseBaseException):
+    """user-throwable exception thrown when inconsistent parse content
+       is found; stops all parsing immediately"""
+    pass
+
+class ParseSyntaxException(ParseFatalException):
+    """just like L{ParseFatalException}, but thrown internally when an
+       L{ErrorStop} ('-' operator) indicates that parsing is to stop 
+       immediately because an unbacktrackable syntax error has been found"""
+    pass
+
+#~ class ReparseException(ParseBaseException):
+    #~ """Experimental class - parse actions can raise this exception to cause
+       #~ pyparsing to reparse the input string:
+        #~ - with a modified input string, and/or
+        #~ - with a modified start location
+       #~ Set the values of the ReparseException in the constructor, and raise the
+       #~ exception in a parse action to cause pyparsing to use the new string/location.
+       #~ Setting the values as None causes no change to be made.
+       #~ """
+    #~ def __init_( self, newstring, restartLoc ):
+        #~ self.newParseText = newstring
+        #~ self.reparseLoc = restartLoc
+
+class RecursiveGrammarException(Exception):
+    """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive"""
+    def __init__( self, parseElementList ):
+        self.parseElementTrace = parseElementList
+
+    def __str__( self ):
+        return "RecursiveGrammarException: %s" % self.parseElementTrace
+
+class _ParseResultsWithOffset(object):
+    def __init__(self,p1,p2):
+        self.tup = (p1,p2)
+    def __getitem__(self,i):
+        return self.tup[i]
+    def __repr__(self):
+        return repr(self.tup[0])
+    def setOffset(self,i):
+        self.tup = (self.tup[0],i)
+
+class ParseResults(object):
+    """
+    Structured parse results, to provide multiple means of access to the parsed data:
+       - as a list (C{len(results)})
+       - by list index (C{results[0], results[1]}, etc.)
+       - by attribute (C{results.} - see L{ParserElement.setResultsName})
+
+    Example::
+        integer = Word(nums)
+        date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+        # equivalent form:
+        # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+        # parseString returns a ParseResults object
+        result = date_str.parseString("1999/12/31")
+
+        def test(s, fn=repr):
+            print("%s -> %s" % (s, fn(eval(s))))
+        test("list(result)")
+        test("result[0]")
+        test("result['month']")
+        test("result.day")
+        test("'month' in result")
+        test("'minutes' in result")
+        test("result.dump()", str)
+    prints::
+        list(result) -> ['1999', '/', '12', '/', '31']
+        result[0] -> '1999'
+        result['month'] -> '12'
+        result.day -> '31'
+        'month' in result -> True
+        'minutes' in result -> False
+        result.dump() -> ['1999', '/', '12', '/', '31']
+        - day: 31
+        - month: 12
+        - year: 1999
+    """
+    def __new__(cls, toklist=None, name=None, asList=True, modal=True ):
+        if isinstance(toklist, cls):
+            return toklist
+        retobj = object.__new__(cls)
+        retobj.__doinit = True
+        return retobj
+
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ):
+        if self.__doinit:
+            self.__doinit = False
+            self.__name = None
+            self.__parent = None
+            self.__accumNames = {}
+            self.__asList = asList
+            self.__modal = modal
+            if toklist is None:
+                toklist = []
+            if isinstance(toklist, list):
+                self.__toklist = toklist[:]
+            elif isinstance(toklist, _generatorType):
+                self.__toklist = list(toklist)
+            else:
+                self.__toklist = [toklist]
+            self.__tokdict = dict()
+
+        if name is not None and name:
+            if not modal:
+                self.__accumNames[name] = 0
+            if isinstance(name,int):
+                name = _ustr(name) # will always return a str, but use _ustr for consistency
+            self.__name = name
+            if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])):
+                if isinstance(toklist,basestring):
+                    toklist = [ toklist ]
+                if asList:
+                    if isinstance(toklist,ParseResults):
+                        self[name] = _ParseResultsWithOffset(toklist.copy(),0)
+                    else:
+                        self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0)
+                    self[name].__name = name
+                else:
+                    try:
+                        self[name] = toklist[0]
+                    except (KeyError,TypeError,IndexError):
+                        self[name] = toklist
+
+    def __getitem__( self, i ):
+        if isinstance( i, (int,slice) ):
+            return self.__toklist[i]
+        else:
+            if i not in self.__accumNames:
+                return self.__tokdict[i][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[i] ])
+
+    def __setitem__( self, k, v, isinstance=isinstance ):
+        if isinstance(v,_ParseResultsWithOffset):
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [v]
+            sub = v[0]
+        elif isinstance(k,(int,slice)):
+            self.__toklist[k] = v
+            sub = v
+        else:
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)]
+            sub = v
+        if isinstance(sub,ParseResults):
+            sub.__parent = wkref(self)
+
+    def __delitem__( self, i ):
+        if isinstance(i,(int,slice)):
+            mylen = len( self.__toklist )
+            del self.__toklist[i]
+
+            # convert int to slice
+            if isinstance(i, int):
+                if i < 0:
+                    i += mylen
+                i = slice(i, i+1)
+            # get removed indices
+            removed = list(range(*i.indices(mylen)))
+            removed.reverse()
+            # fixup indices in token dictionary
+            for name,occurrences in self.__tokdict.items():
+                for j in removed:
+                    for k, (value, position) in enumerate(occurrences):
+                        occurrences[k] = _ParseResultsWithOffset(value, position - (position > j))
+        else:
+            del self.__tokdict[i]
+
+    def __contains__( self, k ):
+        return k in self.__tokdict
+
+    def __len__( self ): return len( self.__toklist )
+    def __bool__(self): return ( not not self.__toklist )
+    __nonzero__ = __bool__
+    def __iter__( self ): return iter( self.__toklist )
+    def __reversed__( self ): return iter( self.__toklist[::-1] )
+    def _iterkeys( self ):
+        if hasattr(self.__tokdict, "iterkeys"):
+            return self.__tokdict.iterkeys()
+        else:
+            return iter(self.__tokdict)
+
+    def _itervalues( self ):
+        return (self[k] for k in self._iterkeys())
+            
+    def _iteritems( self ):
+        return ((k, self[k]) for k in self._iterkeys())
+
+    if PY_3:
+        keys = _iterkeys       
+        """Returns an iterator of all named result keys (Python 3.x only)."""
+
+        values = _itervalues
+        """Returns an iterator of all named result values (Python 3.x only)."""
+
+        items = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 3.x only)."""
+
+    else:
+        iterkeys = _iterkeys
+        """Returns an iterator of all named result keys (Python 2.x only)."""
+
+        itervalues = _itervalues
+        """Returns an iterator of all named result values (Python 2.x only)."""
+
+        iteritems = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
+
+        def keys( self ):
+            """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iterkeys())
+
+        def values( self ):
+            """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.itervalues())
+                
+        def items( self ):
+            """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iteritems())
+
+    def haskeys( self ):
+        """Since keys() returns an iterator, this method is helpful in bypassing
+           code that looks for the existence of any defined results names."""
+        return bool(self.__tokdict)
+        
+    def pop( self, *args, **kwargs):
+        """
+        Removes and returns item at specified index (default=C{last}).
+        Supports both C{list} and C{dict} semantics for C{pop()}. If passed no
+        argument or an integer argument, it will use C{list} semantics
+        and pop tokens from the list of parsed tokens. If passed a 
+        non-integer argument (most likely a string), it will use C{dict}
+        semantics and pop the corresponding value from any defined 
+        results names. A second default return value argument is 
+        supported, just as in C{dict.pop()}.
+
+        Example::
+            def remove_first(tokens):
+                tokens.pop(0)
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
+
+            label = Word(alphas)
+            patt = label("LABEL") + OneOrMore(Word(nums))
+            print(patt.parseString("AAB 123 321").dump())
+
+            # Use pop() in a parse action to remove named result (note that corresponding value is not
+            # removed from list form of results)
+            def remove_LABEL(tokens):
+                tokens.pop("LABEL")
+                return tokens
+            patt.addParseAction(remove_LABEL)
+            print(patt.parseString("AAB 123 321").dump())
+        prints::
+            ['AAB', '123', '321']
+            - LABEL: AAB
+
+            ['AAB', '123', '321']
+        """
+        if not args:
+            args = [-1]
+        for k,v in kwargs.items():
+            if k == 'default':
+                args = (args[0], v)
+            else:
+                raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
+        if (isinstance(args[0], int) or 
+                        len(args) == 1 or 
+                        args[0] in self):
+            index = args[0]
+            ret = self[index]
+            del self[index]
+            return ret
+        else:
+            defaultvalue = args[1]
+            return defaultvalue
+
+    def get(self, key, defaultValue=None):
+        """
+        Returns named result matching the given key, or if there is no
+        such name, then returns the given C{defaultValue} or C{None} if no
+        C{defaultValue} is specified.
+
+        Similar to C{dict.get()}.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            result = date_str.parseString("1999/12/31")
+            print(result.get("year")) # -> '1999'
+            print(result.get("hour", "not specified")) # -> 'not specified'
+            print(result.get("hour")) # -> None
+        """
+        if key in self:
+            return self[key]
+        else:
+            return defaultValue
+
+    def insert( self, index, insStr ):
+        """
+        Inserts new element at location index in the list of parsed tokens.
+        
+        Similar to C{list.insert()}.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+
+            # use a parse action to insert the parse location in the front of the parsed results
+            def insert_locn(locn, tokens):
+                tokens.insert(0, locn)
+            print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
+        """
+        self.__toklist.insert(index, insStr)
+        # fixup indices in token dictionary
+        for name,occurrences in self.__tokdict.items():
+            for k, (value, position) in enumerate(occurrences):
+                occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
+
+    def append( self, item ):
+        """
+        Add single element to end of ParseResults list of elements.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            
+            # use a parse action to compute the sum of the parsed integers, and add it to the end
+            def append_sum(tokens):
+                tokens.append(sum(map(int, tokens)))
+            print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444]
+        """
+        self.__toklist.append(item)
+
+    def extend( self, itemseq ):
+        """
+        Add sequence of elements to end of ParseResults list of elements.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            
+            # use a parse action to append the reverse of the matched strings, to make a palindrome
+            def make_palindrome(tokens):
+                tokens.extend(reversed([t[::-1] for t in tokens]))
+                return ''.join(tokens)
+            print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
+        """
+        if isinstance(itemseq, ParseResults):
+            self += itemseq
+        else:
+            self.__toklist.extend(itemseq)
+
+    def clear( self ):
+        """
+        Clear all elements and results names.
+        """
+        del self.__toklist[:]
+        self.__tokdict.clear()
+
+    def __getattr__( self, name ):
+        try:
+            return self[name]
+        except KeyError:
+            return ""
+            
+        if name in self.__tokdict:
+            if name not in self.__accumNames:
+                return self.__tokdict[name][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[name] ])
+        else:
+            return ""
+
+    def __add__( self, other ):
+        ret = self.copy()
+        ret += other
+        return ret
+
+    def __iadd__( self, other ):
+        if other.__tokdict:
+            offset = len(self.__toklist)
+            addoffset = lambda a: offset if a<0 else a+offset
+            otheritems = other.__tokdict.items()
+            otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) )
+                                for (k,vlist) in otheritems for v in vlist]
+            for k,v in otherdictitems:
+                self[k] = v
+                if isinstance(v[0],ParseResults):
+                    v[0].__parent = wkref(self)
+            
+        self.__toklist += other.__toklist
+        self.__accumNames.update( other.__accumNames )
+        return self
+
+    def __radd__(self, other):
+        if isinstance(other,int) and other == 0:
+            # useful for merging many ParseResults using sum() builtin
+            return self.copy()
+        else:
+            # this may raise a TypeError - so be it
+            return other + self
+        
+    def __repr__( self ):
+        return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) )
+
+    def __str__( self ):
+        return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
+
+    def _asStringList( self, sep='' ):
+        out = []
+        for item in self.__toklist:
+            if out and sep:
+                out.append(sep)
+            if isinstance( item, ParseResults ):
+                out += item._asStringList()
+            else:
+                out.append( _ustr(item) )
+        return out
+
+    def asList( self ):
+        """
+        Returns the parse results as a nested list of matching tokens, all converted to strings.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            result = patt.parseString("sldkj lsdkj sldkj")
+            # even though the result prints in string-like form, it is actually a pyparsing ParseResults
+            print(type(result), result) # ->  ['sldkj', 'lsdkj', 'sldkj']
+            
+            # Use asList() to create an actual list
+            result_list = result.asList()
+            print(type(result_list), result_list) # ->  ['sldkj', 'lsdkj', 'sldkj']
+        """
+        return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist]
+
+    def asDict( self ):
+        """
+        Returns the named parse results as a nested dictionary.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(type(result), repr(result)) # ->  (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
+            
+            result_dict = result.asDict()
+            print(type(result_dict), repr(result_dict)) # ->  {'day': '1999', 'year': '12', 'month': '31'}
+
+            # even though a ParseResults supports dict-like access, sometime you just need to have a dict
+            import json
+            print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
+            print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
+        """
+        if PY_3:
+            item_fn = self.items
+        else:
+            item_fn = self.iteritems
+            
+        def toItem(obj):
+            if isinstance(obj, ParseResults):
+                if obj.haskeys():
+                    return obj.asDict()
+                else:
+                    return [toItem(v) for v in obj]
+            else:
+                return obj
+                
+        return dict((k,toItem(v)) for k,v in item_fn())
+
+    def copy( self ):
+        """
+        Returns a new copy of a C{ParseResults} object.
+        """
+        ret = ParseResults( self.__toklist )
+        ret.__tokdict = self.__tokdict.copy()
+        ret.__parent = self.__parent
+        ret.__accumNames.update( self.__accumNames )
+        ret.__name = self.__name
+        return ret
+
+    def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ):
+        """
+        (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.
+        """
+        nl = "\n"
+        out = []
+        namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items()
+                                                            for v in vlist)
+        nextLevelIndent = indent + "  "
+
+        # collapse out indents if formatting is not desired
+        if not formatted:
+            indent = ""
+            nextLevelIndent = ""
+            nl = ""
+
+        selfTag = None
+        if doctag is not None:
+            selfTag = doctag
+        else:
+            if self.__name:
+                selfTag = self.__name
+
+        if not selfTag:
+            if namedItemsOnly:
+                return ""
+            else:
+                selfTag = "ITEM"
+
+        out += [ nl, indent, "<", selfTag, ">" ]
+
+        for i,res in enumerate(self.__toklist):
+            if isinstance(res,ParseResults):
+                if i in namedItems:
+                    out += [ res.asXML(namedItems[i],
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+                else:
+                    out += [ res.asXML(None,
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+            else:
+                # individual token, see if there is a name for it
+                resTag = None
+                if i in namedItems:
+                    resTag = namedItems[i]
+                if not resTag:
+                    if namedItemsOnly:
+                        continue
+                    else:
+                        resTag = "ITEM"
+                xmlBodyText = _xml_escape(_ustr(res))
+                out += [ nl, nextLevelIndent, "<", resTag, ">",
+                                                xmlBodyText,
+                                                "" ]
+
+        out += [ nl, indent, "" ]
+        return "".join(out)
+
+    def __lookup(self,sub):
+        for k,vlist in self.__tokdict.items():
+            for v,loc in vlist:
+                if sub is v:
+                    return k
+        return None
+
+    def getName(self):
+        r"""
+        Returns the results name for this token expression. Useful when several 
+        different expressions might match at a particular location.
+
+        Example::
+            integer = Word(nums)
+            ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+            house_number_expr = Suppress('#') + Word(nums, alphanums)
+            user_data = (Group(house_number_expr)("house_number") 
+                        | Group(ssn_expr)("ssn")
+                        | Group(integer)("age"))
+            user_info = OneOrMore(user_data)
+            
+            result = user_info.parseString("22 111-22-3333 #221B")
+            for item in result:
+                print(item.getName(), ':', item[0])
+        prints::
+            age : 22
+            ssn : 111-22-3333
+            house_number : 221B
+        """
+        if self.__name:
+            return self.__name
+        elif self.__parent:
+            par = self.__parent()
+            if par:
+                return par.__lookup(self)
+            else:
+                return None
+        elif (len(self) == 1 and
+               len(self.__tokdict) == 1 and
+               next(iter(self.__tokdict.values()))[0][1] in (0,-1)):
+            return next(iter(self.__tokdict.keys()))
+        else:
+            return None
+
+    def dump(self, indent='', depth=0, full=True):
+        """
+        Diagnostic method for listing out the contents of a C{ParseResults}.
+        Accepts an optional C{indent} argument so that this string can be embedded
+        in a nested display of other data.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(result.dump())
+        prints::
+            ['12', '/', '31', '/', '1999']
+            - day: 1999
+            - month: 31
+            - year: 12
+        """
+        out = []
+        NL = '\n'
+        out.append( indent+_ustr(self.asList()) )
+        if full:
+            if self.haskeys():
+                items = sorted((str(k), v) for k,v in self.items())
+                for k,v in items:
+                    if out:
+                        out.append(NL)
+                    out.append( "%s%s- %s: " % (indent,('  '*depth), k) )
+                    if isinstance(v,ParseResults):
+                        if v:
+                            out.append( v.dump(indent,depth+1) )
+                        else:
+                            out.append(_ustr(v))
+                    else:
+                        out.append(repr(v))
+            elif any(isinstance(vv,ParseResults) for vv in self):
+                v = self
+                for i,vv in enumerate(v):
+                    if isinstance(vv,ParseResults):
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),vv.dump(indent,depth+1) ))
+                    else:
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),_ustr(vv)))
+            
+        return "".join(out)
+
+    def pprint(self, *args, **kwargs):
+        """
+        Pretty-printer for parsed results as a list, using the C{pprint} module.
+        Accepts additional positional or keyword args as defined for the 
+        C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint})
+
+        Example::
+            ident = Word(alphas, alphanums)
+            num = Word(nums)
+            func = Forward()
+            term = ident | num | Group('(' + func + ')')
+            func <<= ident + Group(Optional(delimitedList(term)))
+            result = func.parseString("fna a,b,(fnb c,d,200),100")
+            result.pprint(width=40)
+        prints::
+            ['fna',
+             ['a',
+              'b',
+              ['(', 'fnb', ['c', 'd', '200'], ')'],
+              '100']]
+        """
+        pprint.pprint(self.asList(), *args, **kwargs)
+
+    # add support for pickle protocol
+    def __getstate__(self):
+        return ( self.__toklist,
+                 ( self.__tokdict.copy(),
+                   self.__parent is not None and self.__parent() or None,
+                   self.__accumNames,
+                   self.__name ) )
+
+    def __setstate__(self,state):
+        self.__toklist = state[0]
+        (self.__tokdict,
+         par,
+         inAccumNames,
+         self.__name) = state[1]
+        self.__accumNames = {}
+        self.__accumNames.update(inAccumNames)
+        if par is not None:
+            self.__parent = wkref(par)
+        else:
+            self.__parent = None
+
+    def __getnewargs__(self):
+        return self.__toklist, self.__name, self.__asList, self.__modal
+
+    def __dir__(self):
+        return (dir(type(self)) + list(self.keys()))
+
+MutableMapping.register(ParseResults)
+
+def col (loc,strg):
+    """Returns current column within a string, counting newlines as line separators.
+   The first column is number 1.
+
+   Note: the default parsing behavior is to expand tabs in the input string
+   before starting the parsing process.  See L{I{ParserElement.parseString}} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    s = strg
+    return 1 if 0} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    return strg.count("\n",0,loc) + 1
+
+def line( loc, strg ):
+    """Returns the line of text containing loc within a string, counting newlines as line separators.
+       """
+    lastCR = strg.rfind("\n", 0, loc)
+    nextCR = strg.find("\n", loc)
+    if nextCR >= 0:
+        return strg[lastCR+1:nextCR]
+    else:
+        return strg[lastCR+1:]
+
+def _defaultStartDebugAction( instring, loc, expr ):
+    print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))
+
+def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ):
+    print ("Matched " + _ustr(expr) + " -> " + str(toks.asList()))
+
+def _defaultExceptionDebugAction( instring, loc, expr, exc ):
+    print ("Exception raised:" + _ustr(exc))
+
+def nullDebugAction(*args):
+    """'Do-nothing' debug action, to suppress debugging output during parsing."""
+    pass
+
+# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
+#~ 'decorator to trim function calls to match the arity of the target'
+#~ def _trim_arity(func, maxargs=3):
+    #~ if func in singleArgBuiltins:
+        #~ return lambda s,l,t: func(t)
+    #~ limit = 0
+    #~ foundArity = False
+    #~ def wrapper(*args):
+        #~ nonlocal limit,foundArity
+        #~ while 1:
+            #~ try:
+                #~ ret = func(*args[limit:])
+                #~ foundArity = True
+                #~ return ret
+            #~ except TypeError:
+                #~ if limit == maxargs or foundArity:
+                    #~ raise
+                #~ limit += 1
+                #~ continue
+    #~ return wrapper
+
+# this version is Python 2.x-3.x cross-compatible
+'decorator to trim function calls to match the arity of the target'
+def _trim_arity(func, maxargs=2):
+    if func in singleArgBuiltins:
+        return lambda s,l,t: func(t)
+    limit = [0]
+    foundArity = [False]
+    
+    # traceback return data structure changed in Py3.5 - normalize back to plain tuples
+    if system_version[:2] >= (3,5):
+        def extract_stack(limit=0):
+            # special handling for Python 3.5.0 - extra deep call stack by 1
+            offset = -3 if system_version == (3,5,0) else -2
+            frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
+            return [frame_summary[:2]]
+        def extract_tb(tb, limit=0):
+            frames = traceback.extract_tb(tb, limit=limit)
+            frame_summary = frames[-1]
+            return [frame_summary[:2]]
+    else:
+        extract_stack = traceback.extract_stack
+        extract_tb = traceback.extract_tb
+    
+    # synthesize what would be returned by traceback.extract_stack at the call to 
+    # user's parse action 'func', so that we don't incur call penalty at parse time
+    
+    LINE_DIFF = 6
+    # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND 
+    # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
+    this_line = extract_stack(limit=2)[-1]
+    pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF)
+
+    def wrapper(*args):
+        while 1:
+            try:
+                ret = func(*args[limit[0]:])
+                foundArity[0] = True
+                return ret
+            except TypeError:
+                # re-raise TypeErrors if they did not come from our arity testing
+                if foundArity[0]:
+                    raise
+                else:
+                    try:
+                        tb = sys.exc_info()[-1]
+                        if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth:
+                            raise
+                    finally:
+                        del tb
+
+                if limit[0] <= maxargs:
+                    limit[0] += 1
+                    continue
+                raise
+
+    # copy func name to wrapper for sensible debug output
+    func_name = ""
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    wrapper.__name__ = func_name
+
+    return wrapper
+
+class ParserElement(object):
+    """Abstract base level parser element class."""
+    DEFAULT_WHITE_CHARS = " \n\t\r"
+    verbose_stacktrace = False
+
+    @staticmethod
+    def setDefaultWhitespaceChars( chars ):
+        r"""
+        Overrides the default whitespace chars
+
+        Example::
+            # default whitespace chars are space,  and newline
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
+            
+            # change to just treat newline as significant
+            ParserElement.setDefaultWhitespaceChars(" \t")
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def']
+        """
+        ParserElement.DEFAULT_WHITE_CHARS = chars
+
+    @staticmethod
+    def inlineLiteralsUsing(cls):
+        """
+        Set class to be used for inclusion of string literals into a parser.
+        
+        Example::
+            # default literal class used is Literal
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+
+            # change to Suppress
+            ParserElement.inlineLiteralsUsing(Suppress)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '12', '31']
+        """
+        ParserElement._literalStringClass = cls
+
+    def __init__( self, savelist=False ):
+        self.parseAction = list()
+        self.failAction = None
+        #~ self.name = ""  # don't define self.name, let subclasses try/except upcall
+        self.strRepr = None
+        self.resultsName = None
+        self.saveAsList = savelist
+        self.skipWhitespace = True
+        self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        self.copyDefaultWhiteChars = True
+        self.mayReturnEmpty = False # used when checking for left-recursion
+        self.keepTabs = False
+        self.ignoreExprs = list()
+        self.debug = False
+        self.streamlined = False
+        self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index
+        self.errmsg = ""
+        self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all)
+        self.debugActions = ( None, None, None ) #custom debug actions
+        self.re = None
+        self.callPreparse = True # used to avoid redundant calls to preParse
+        self.callDuringTry = False
+
+    def copy( self ):
+        """
+        Make a copy of this C{ParserElement}.  Useful for defining different parse actions
+        for the same parsing pattern, using copies of the original parse element.
+        
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K")
+            integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+            
+            print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
+        prints::
+            [5120, 100, 655360, 268435456]
+        Equivalent form of C{expr.copy()} is just C{expr()}::
+            integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+        """
+        cpy = copy.copy( self )
+        cpy.parseAction = self.parseAction[:]
+        cpy.ignoreExprs = self.ignoreExprs[:]
+        if self.copyDefaultWhiteChars:
+            cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        return cpy
+
+    def setName( self, name ):
+        """
+        Define name for this expression, makes debugging and exception messages clearer.
+        
+        Example::
+            Word(nums).parseString("ABC")  # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
+            Word(nums).setName("integer").parseString("ABC")  # -> Exception: Expected integer (at char 0), (line:1, col:1)
+        """
+        self.name = name
+        self.errmsg = "Expected " + self.name
+        if hasattr(self,"exception"):
+            self.exception.msg = self.errmsg
+        return self
+
+    def setResultsName( self, name, listAllMatches=False ):
+        """
+        Define name for referencing matching tokens as a nested attribute
+        of the returned parse results.
+        NOTE: this returns a *copy* of the original C{ParserElement} object;
+        this is so that the client can define a basic element, such as an
+        integer, and reference it in multiple places with different names.
+
+        You can also set results names using the abbreviated syntax,
+        C{expr("name")} in place of C{expr.setResultsName("name")} - 
+        see L{I{__call__}<__call__>}.
+
+        Example::
+            date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+
+            # equivalent form:
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+        """
+        newself = self.copy()
+        if name.endswith("*"):
+            name = name[:-1]
+            listAllMatches=True
+        newself.resultsName = name
+        newself.modalResults = not listAllMatches
+        return newself
+
+    def setBreak(self,breakFlag = True):
+        """Method to invoke the Python pdb debugger when this element is
+           about to be parsed. Set C{breakFlag} to True to enable, False to
+           disable.
+        """
+        if breakFlag:
+            _parseMethod = self._parse
+            def breaker(instring, loc, doActions=True, callPreParse=True):
+                import pdb
+                pdb.set_trace()
+                return _parseMethod( instring, loc, doActions, callPreParse )
+            breaker._originalParseMethod = _parseMethod
+            self._parse = breaker
+        else:
+            if hasattr(self._parse,"_originalParseMethod"):
+                self._parse = self._parse._originalParseMethod
+        return self
+
+    def setParseAction( self, *fns, **kwargs ):
+        """
+        Define one or more actions to perform when successfully matching parse element definition.
+        Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)},
+        C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where:
+         - s   = the original string being parsed (see note below)
+         - loc = the location of the matching substring
+         - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object
+        If the functions in fns modify the tokens, they can return them as the return
+        value from fn, and the modified list of tokens will replace the original.
+        Otherwise, fn does not need to return any value.
+
+        Optional keyword arguments:
+         - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing
+
+        Note: the default parsing behavior is to expand tabs in the input string
+        before starting the parsing process.  See L{I{parseString}} for more information
+        on parsing strings containing C{}s, and suggested methods to maintain a
+        consistent view of the parsed string, the parse location, and line and column
+        positions within the parsed string.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer + '/' + integer + '/' + integer
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+            # use parse action to convert to ints at parse time
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            date_str = integer + '/' + integer + '/' + integer
+
+            # note that integer fields are now ints, not strings
+            date_str.parseString("1999/12/31")  # -> [1999, '/', 12, '/', 31]
+        """
+        self.parseAction = list(map(_trim_arity, list(fns)))
+        self.callDuringTry = kwargs.get("callDuringTry", False)
+        return self
+
+    def addParseAction( self, *fns, **kwargs ):
+        """
+        Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}.
+        
+        See examples in L{I{copy}}.
+        """
+        self.parseAction += list(map(_trim_arity, list(fns)))
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def addCondition(self, *fns, **kwargs):
+        """Add a boolean predicate function to expression's list of parse actions. See 
+        L{I{setParseAction}} for function call signatures. Unlike C{setParseAction}, 
+        functions passed to C{addCondition} need to return boolean success/fail of the condition.
+
+        Optional keyword arguments:
+         - message = define a custom message to be used in the raised exception
+         - fatal   = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
+         
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            year_int = integer.copy()
+            year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
+            date_str = year_int + '/' + integer + '/' + integer
+
+            result = date_str.parseString("1999/12/31")  # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
+        """
+        msg = kwargs.get("message", "failed user-defined condition")
+        exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
+        for fn in fns:
+            def pa(s,l,t):
+                if not bool(_trim_arity(fn)(s,l,t)):
+                    raise exc_type(s,l,msg)
+            self.parseAction.append(pa)
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def setFailAction( self, fn ):
+        """Define action to perform if parsing fails at this expression.
+           Fail acton fn is a callable function that takes the arguments
+           C{fn(s,loc,expr,err)} where:
+            - s = string being parsed
+            - loc = location where expression match was attempted and failed
+            - expr = the parse expression that failed
+            - err = the exception thrown
+           The function returns no value.  It may throw C{L{ParseFatalException}}
+           if it is desired to stop parsing immediately."""
+        self.failAction = fn
+        return self
+
+    def _skipIgnorables( self, instring, loc ):
+        exprsFound = True
+        while exprsFound:
+            exprsFound = False
+            for e in self.ignoreExprs:
+                try:
+                    while 1:
+                        loc,dummy = e._parse( instring, loc )
+                        exprsFound = True
+                except ParseException:
+                    pass
+        return loc
+
+    def preParse( self, instring, loc ):
+        if self.ignoreExprs:
+            loc = self._skipIgnorables( instring, loc )
+
+        if self.skipWhitespace:
+            wt = self.whiteChars
+            instrlen = len(instring)
+            while loc < instrlen and instring[loc] in wt:
+                loc += 1
+
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        return loc, []
+
+    def postParse( self, instring, loc, tokenlist ):
+        return tokenlist
+
+    #~ @profile
+    def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ):
+        debugging = ( self.debug ) #and doActions )
+
+        if debugging or self.failAction:
+            #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))
+            if (self.debugActions[0] ):
+                self.debugActions[0]( instring, loc, self )
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            try:
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            except ParseBaseException as err:
+                #~ print ("Exception raised:", err)
+                if self.debugActions[2]:
+                    self.debugActions[2]( instring, tokensStart, self, err )
+                if self.failAction:
+                    self.failAction( instring, tokensStart, self, err )
+                raise
+        else:
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            if self.mayIndexError or preloc >= len(instring):
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            else:
+                loc,tokens = self.parseImpl( instring, preloc, doActions )
+
+        tokens = self.postParse( instring, loc, tokens )
+
+        retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults )
+        if self.parseAction and (doActions or self.callDuringTry):
+            if debugging:
+                try:
+                    for fn in self.parseAction:
+                        tokens = fn( instring, tokensStart, retTokens )
+                        if tokens is not None:
+                            retTokens = ParseResults( tokens,
+                                                      self.resultsName,
+                                                      asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                      modal=self.modalResults )
+                except ParseBaseException as err:
+                    #~ print "Exception raised in user parse action:", err
+                    if (self.debugActions[2] ):
+                        self.debugActions[2]( instring, tokensStart, self, err )
+                    raise
+            else:
+                for fn in self.parseAction:
+                    tokens = fn( instring, tokensStart, retTokens )
+                    if tokens is not None:
+                        retTokens = ParseResults( tokens,
+                                                  self.resultsName,
+                                                  asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                  modal=self.modalResults )
+        if debugging:
+            #~ print ("Matched",self,"->",retTokens.asList())
+            if (self.debugActions[1] ):
+                self.debugActions[1]( instring, tokensStart, loc, self, retTokens )
+
+        return loc, retTokens
+
+    def tryParse( self, instring, loc ):
+        try:
+            return self._parse( instring, loc, doActions=False )[0]
+        except ParseFatalException:
+            raise ParseException( instring, loc, self.errmsg, self)
+    
+    def canParseNext(self, instring, loc):
+        try:
+            self.tryParse(instring, loc)
+        except (ParseException, IndexError):
+            return False
+        else:
+            return True
+
+    class _UnboundedCache(object):
+        def __init__(self):
+            cache = {}
+            self.not_in_cache = not_in_cache = object()
+
+            def get(self, key):
+                return cache.get(key, not_in_cache)
+
+            def set(self, key, value):
+                cache[key] = value
+
+            def clear(self):
+                cache.clear()
+                
+            def cache_len(self):
+                return len(cache)
+
+            self.get = types.MethodType(get, self)
+            self.set = types.MethodType(set, self)
+            self.clear = types.MethodType(clear, self)
+            self.__len__ = types.MethodType(cache_len, self)
+
+    if _OrderedDict is not None:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = _OrderedDict()
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(cache) > size:
+                        try:
+                            cache.popitem(False)
+                        except KeyError:
+                            pass
+
+                def clear(self):
+                    cache.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    else:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = {}
+                key_fifo = collections.deque([], size)
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(key_fifo) > size:
+                        cache.pop(key_fifo.popleft(), None)
+                    key_fifo.append(key)
+
+                def clear(self):
+                    cache.clear()
+                    key_fifo.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    # argument cache for optimizing repeated calls when backtracking through recursive expressions
+    packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
+    packrat_cache_lock = RLock()
+    packrat_cache_stats = [0, 0]
+
+    # this method gets repeatedly called during backtracking with the same arguments -
+    # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
+    def _parseCache( self, instring, loc, doActions=True, callPreParse=True ):
+        HIT, MISS = 0, 1
+        lookup = (self, instring, loc, callPreParse, doActions)
+        with ParserElement.packrat_cache_lock:
+            cache = ParserElement.packrat_cache
+            value = cache.get(lookup)
+            if value is cache.not_in_cache:
+                ParserElement.packrat_cache_stats[MISS] += 1
+                try:
+                    value = self._parseNoCache(instring, loc, doActions, callPreParse)
+                except ParseBaseException as pe:
+                    # cache a copy of the exception, without the traceback
+                    cache.set(lookup, pe.__class__(*pe.args))
+                    raise
+                else:
+                    cache.set(lookup, (value[0], value[1].copy()))
+                    return value
+            else:
+                ParserElement.packrat_cache_stats[HIT] += 1
+                if isinstance(value, Exception):
+                    raise value
+                return (value[0], value[1].copy())
+
+    _parse = _parseNoCache
+
+    @staticmethod
+    def resetCache():
+        ParserElement.packrat_cache.clear()
+        ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
+
+    _packratEnabled = False
+    @staticmethod
+    def enablePackrat(cache_size_limit=128):
+        """Enables "packrat" parsing, which adds memoizing to the parsing logic.
+           Repeated parse attempts at the same string location (which happens
+           often in many complex grammars) can immediately return a cached value,
+           instead of re-executing parsing/validating code.  Memoizing is done of
+           both valid results and parsing exceptions.
+           
+           Parameters:
+            - cache_size_limit - (default=C{128}) - if an integer value is provided
+              will limit the size of the packrat cache; if None is passed, then
+              the cache size will be unbounded; if 0 is passed, the cache will
+              be effectively disabled.
+            
+           This speedup may break existing programs that use parse actions that
+           have side-effects.  For this reason, packrat parsing is disabled when
+           you first import pyparsing.  To activate the packrat feature, your
+           program must call the class method C{ParserElement.enablePackrat()}.  If
+           your program uses C{psyco} to "compile as you go", you must call
+           C{enablePackrat} before calling C{psyco.full()}.  If you do not do this,
+           Python will crash.  For best results, call C{enablePackrat()} immediately
+           after importing pyparsing.
+           
+           Example::
+               import pyparsing
+               pyparsing.ParserElement.enablePackrat()
+        """
+        if not ParserElement._packratEnabled:
+            ParserElement._packratEnabled = True
+            if cache_size_limit is None:
+                ParserElement.packrat_cache = ParserElement._UnboundedCache()
+            else:
+                ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
+            ParserElement._parse = ParserElement._parseCache
+
+    def parseString( self, instring, parseAll=False ):
+        """
+        Execute the parse expression with the given string.
+        This is the main interface to the client code, once the complete
+        expression has been built.
+
+        If you want the grammar to require that the entire input string be
+        successfully parsed, then set C{parseAll} to True (equivalent to ending
+        the grammar with C{L{StringEnd()}}).
+
+        Note: C{parseString} implicitly calls C{expandtabs()} on the input string,
+        in order to report proper column numbers in parse actions.
+        If the input string contains tabs and
+        the grammar uses parse actions that use the C{loc} argument to index into the
+        string being parsed, you can ensure you have a consistent view of the input
+        string by:
+         - calling C{parseWithTabs} on your grammar before calling C{parseString}
+           (see L{I{parseWithTabs}})
+         - define your parse action using the full C{(s,loc,toks)} signature, and
+           reference the input string using the parse action's C{s} argument
+         - explictly expand the tabs in your input string before calling
+           C{parseString}
+        
+        Example::
+            Word('a').parseString('aaaaabaaa')  # -> ['aaaaa']
+            Word('a').parseString('aaaaabaaa', parseAll=True)  # -> Exception: Expected end of text
+        """
+        ParserElement.resetCache()
+        if not self.streamlined:
+            self.streamline()
+            #~ self.saveAsList = True
+        for e in self.ignoreExprs:
+            e.streamline()
+        if not self.keepTabs:
+            instring = instring.expandtabs()
+        try:
+            loc, tokens = self._parse( instring, 0 )
+            if parseAll:
+                loc = self.preParse( instring, loc )
+                se = Empty() + StringEnd()
+                se._parse( instring, loc )
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+        else:
+            return tokens
+
+    def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ):
+        """
+        Scan the input string for expression matches.  Each match will return the
+        matching tokens, start location, and end location.  May be called with optional
+        C{maxMatches} argument, to clip scanning after 'n' matches are found.  If
+        C{overlap} is specified, then overlapping matches will be reported.
+
+        Note that the start and end locations are reported relative to the string
+        being parsed.  See L{I{parseString}} for more information on parsing
+        strings with embedded tabs.
+
+        Example::
+            source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
+            print(source)
+            for tokens,start,end in Word(alphas).scanString(source):
+                print(' '*start + '^'*(end-start))
+                print(' '*start + tokens[0])
+        
+        prints::
+        
+            sldjf123lsdjjkf345sldkjf879lkjsfd987
+            ^^^^^
+            sldjf
+                    ^^^^^^^
+                    lsdjjkf
+                              ^^^^^^
+                              sldkjf
+                                       ^^^^^^
+                                       lkjsfd
+        """
+        if not self.streamlined:
+            self.streamline()
+        for e in self.ignoreExprs:
+            e.streamline()
+
+        if not self.keepTabs:
+            instring = _ustr(instring).expandtabs()
+        instrlen = len(instring)
+        loc = 0
+        preparseFn = self.preParse
+        parseFn = self._parse
+        ParserElement.resetCache()
+        matches = 0
+        try:
+            while loc <= instrlen and matches < maxMatches:
+                try:
+                    preloc = preparseFn( instring, loc )
+                    nextLoc,tokens = parseFn( instring, preloc, callPreParse=False )
+                except ParseException:
+                    loc = preloc+1
+                else:
+                    if nextLoc > loc:
+                        matches += 1
+                        yield tokens, preloc, nextLoc
+                        if overlap:
+                            nextloc = preparseFn( instring, loc )
+                            if nextloc > loc:
+                                loc = nextLoc
+                            else:
+                                loc += 1
+                        else:
+                            loc = nextLoc
+                    else:
+                        loc = preloc+1
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def transformString( self, instring ):
+        """
+        Extension to C{L{scanString}}, to modify matching text with modified tokens that may
+        be returned from a parse action.  To use C{transformString}, define a grammar and
+        attach a parse action to it that modifies the returned token list.
+        Invoking C{transformString()} on a target string will then scan for matches,
+        and replace the matched text patterns according to the logic in the parse
+        action.  C{transformString()} returns the resulting transformed string.
+        
+        Example::
+            wd = Word(alphas)
+            wd.setParseAction(lambda toks: toks[0].title())
+            
+            print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
+        Prints::
+            Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
+        """
+        out = []
+        lastE = 0
+        # force preservation of s, to minimize unwanted transformation of string, and to
+        # keep string locs straight between transformString and scanString
+        self.keepTabs = True
+        try:
+            for t,s,e in self.scanString( instring ):
+                out.append( instring[lastE:s] )
+                if t:
+                    if isinstance(t,ParseResults):
+                        out += t.asList()
+                    elif isinstance(t,list):
+                        out += t
+                    else:
+                        out.append(t)
+                lastE = e
+            out.append(instring[lastE:])
+            out = [o for o in out if o]
+            return "".join(map(_ustr,_flatten(out)))
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def searchString( self, instring, maxMatches=_MAX_INT ):
+        """
+        Another extension to C{L{scanString}}, simplifying the access to the tokens found
+        to match the given parse expression.  May be called with optional
+        C{maxMatches} argument, to clip searching after 'n' matches are found.
+        
+        Example::
+            # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
+            cap_word = Word(alphas.upper(), alphas.lower())
+            
+            print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
+
+            # the sum() builtin can be used to merge results into a single ParseResults object
+            print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")))
+        prints::
+            [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
+            ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
+        """
+        try:
+            return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ])
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
+        """
+        Generator method to split a string using the given expression as a separator.
+        May be called with optional C{maxsplit} argument, to limit the number of splits;
+        and the optional C{includeSeparators} argument (default=C{False}), if the separating
+        matching text should be included in the split results.
+        
+        Example::        
+            punc = oneOf(list(".,;:/-!?"))
+            print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
+        prints::
+            ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
+        """
+        splits = 0
+        last = 0
+        for t,s,e in self.scanString(instring, maxMatches=maxsplit):
+            yield instring[last:s]
+            if includeSeparators:
+                yield t[0]
+            last = e
+        yield instring[last:]
+
+    def __add__(self, other ):
+        """
+        Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement
+        converts them to L{Literal}s by default.
+        
+        Example::
+            greet = Word(alphas) + "," + Word(alphas) + "!"
+            hello = "Hello, World!"
+            print (hello, "->", greet.parseString(hello))
+        Prints::
+            Hello, World! -> ['Hello', ',', 'World', '!']
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return And( [ self, other ] )
+
+    def __radd__(self, other ):
+        """
+        Implementation of + operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other + self
+
+    def __sub__(self, other):
+        """
+        Implementation of - operator, returns C{L{And}} with error stop
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return self + And._ErrorStop() + other
+
+    def __rsub__(self, other ):
+        """
+        Implementation of - operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other - self
+
+    def __mul__(self,other):
+        """
+        Implementation of * operator, allows use of C{expr * 3} in place of
+        C{expr + expr + expr}.  Expressions may also me multiplied by a 2-integer
+        tuple, similar to C{{min,max}} multipliers in regular expressions.  Tuples
+        may also include C{None} as in:
+         - C{expr*(n,None)} or C{expr*(n,)} is equivalent
+              to C{expr*n + L{ZeroOrMore}(expr)}
+              (read as "at least n instances of C{expr}")
+         - C{expr*(None,n)} is equivalent to C{expr*(0,n)}
+              (read as "0 to n instances of C{expr}")
+         - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)}
+         - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)}
+
+        Note that C{expr*(None,n)} does not raise an exception if
+        more than n exprs exist in the input stream; that is,
+        C{expr*(None,n)} does not enforce a maximum number of expr
+        occurrences.  If this behavior is desired, then write
+        C{expr*(None,n) + ~expr}
+        """
+        if isinstance(other,int):
+            minElements, optElements = other,0
+        elif isinstance(other,tuple):
+            other = (other + (None, None))[:2]
+            if other[0] is None:
+                other = (0, other[1])
+            if isinstance(other[0],int) and other[1] is None:
+                if other[0] == 0:
+                    return ZeroOrMore(self)
+                if other[0] == 1:
+                    return OneOrMore(self)
+                else:
+                    return self*other[0] + ZeroOrMore(self)
+            elif isinstance(other[0],int) and isinstance(other[1],int):
+                minElements, optElements = other
+                optElements -= minElements
+            else:
+                raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1]))
+        else:
+            raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other))
+
+        if minElements < 0:
+            raise ValueError("cannot multiply ParserElement by negative value")
+        if optElements < 0:
+            raise ValueError("second tuple value must be greater or equal to first tuple value")
+        if minElements == optElements == 0:
+            raise ValueError("cannot multiply ParserElement by 0 or (0,0)")
+
+        if (optElements):
+            def makeOptionalList(n):
+                if n>1:
+                    return Optional(self + makeOptionalList(n-1))
+                else:
+                    return Optional(self)
+            if minElements:
+                if minElements == 1:
+                    ret = self + makeOptionalList(optElements)
+                else:
+                    ret = And([self]*minElements) + makeOptionalList(optElements)
+            else:
+                ret = makeOptionalList(optElements)
+        else:
+            if minElements == 1:
+                ret = self
+            else:
+                ret = And([self]*minElements)
+        return ret
+
+    def __rmul__(self, other):
+        return self.__mul__(other)
+
+    def __or__(self, other ):
+        """
+        Implementation of | operator - returns C{L{MatchFirst}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return MatchFirst( [ self, other ] )
+
+    def __ror__(self, other ):
+        """
+        Implementation of | operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other | self
+
+    def __xor__(self, other ):
+        """
+        Implementation of ^ operator - returns C{L{Or}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Or( [ self, other ] )
+
+    def __rxor__(self, other ):
+        """
+        Implementation of ^ operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other ^ self
+
+    def __and__(self, other ):
+        """
+        Implementation of & operator - returns C{L{Each}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Each( [ self, other ] )
+
+    def __rand__(self, other ):
+        """
+        Implementation of & operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other & self
+
+    def __invert__( self ):
+        """
+        Implementation of ~ operator - returns C{L{NotAny}}
+        """
+        return NotAny( self )
+
+    def __call__(self, name=None):
+        """
+        Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}.
+        
+        If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be
+        passed as C{True}.
+           
+        If C{name} is omitted, same as calling C{L{copy}}.
+
+        Example::
+            # these are equivalent
+            userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno")
+            userdata = Word(alphas)("name") + Word(nums+"-")("socsecno")             
+        """
+        if name is not None:
+            return self.setResultsName(name)
+        else:
+            return self.copy()
+
+    def suppress( self ):
+        """
+        Suppresses the output of this C{ParserElement}; useful to keep punctuation from
+        cluttering up returned output.
+        """
+        return Suppress( self )
+
+    def leaveWhitespace( self ):
+        """
+        Disables the skipping of whitespace before matching the characters in the
+        C{ParserElement}'s defined pattern.  This is normally only used internally by
+        the pyparsing module, but may be needed in some whitespace-sensitive grammars.
+        """
+        self.skipWhitespace = False
+        return self
+
+    def setWhitespaceChars( self, chars ):
+        """
+        Overrides the default whitespace chars
+        """
+        self.skipWhitespace = True
+        self.whiteChars = chars
+        self.copyDefaultWhiteChars = False
+        return self
+
+    def parseWithTabs( self ):
+        """
+        Overrides default behavior to expand C{}s to spaces before parsing the input string.
+        Must be called before C{parseString} when the input grammar contains elements that
+        match C{} characters.
+        """
+        self.keepTabs = True
+        return self
+
+    def ignore( self, other ):
+        """
+        Define expression to be ignored (e.g., comments) while doing pattern
+        matching; may be called repeatedly, to define multiple comment or other
+        ignorable patterns.
+        
+        Example::
+            patt = OneOrMore(Word(alphas))
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
+            
+            patt.ignore(cStyleComment)
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
+        """
+        if isinstance(other, basestring):
+            other = Suppress(other)
+
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                self.ignoreExprs.append(other)
+        else:
+            self.ignoreExprs.append( Suppress( other.copy() ) )
+        return self
+
+    def setDebugActions( self, startAction, successAction, exceptionAction ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        """
+        self.debugActions = (startAction or _defaultStartDebugAction,
+                             successAction or _defaultSuccessDebugAction,
+                             exceptionAction or _defaultExceptionDebugAction)
+        self.debug = True
+        return self
+
+    def setDebug( self, flag=True ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        Set C{flag} to True to enable, False to disable.
+
+        Example::
+            wd = Word(alphas).setName("alphaword")
+            integer = Word(nums).setName("numword")
+            term = wd | integer
+            
+            # turn on debugging for wd
+            wd.setDebug()
+
+            OneOrMore(term).parseString("abc 123 xyz 890")
+        
+        prints::
+            Match alphaword at loc 0(1,1)
+            Matched alphaword -> ['abc']
+            Match alphaword at loc 3(1,4)
+            Exception raised:Expected alphaword (at char 4), (line:1, col:5)
+            Match alphaword at loc 7(1,8)
+            Matched alphaword -> ['xyz']
+            Match alphaword at loc 11(1,12)
+            Exception raised:Expected alphaword (at char 12), (line:1, col:13)
+            Match alphaword at loc 15(1,16)
+            Exception raised:Expected alphaword (at char 15), (line:1, col:16)
+
+        The output shown is that produced by the default debug actions - custom debug actions can be
+        specified using L{setDebugActions}. Prior to attempting
+        to match the C{wd} expression, the debugging message C{"Match  at loc (,)"}
+        is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"}
+        message is shown. Also note the use of L{setName} to assign a human-readable name to the expression,
+        which makes debugging and exception messages easier to understand - for instance, the default
+        name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}.
+        """
+        if flag:
+            self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction )
+        else:
+            self.debug = False
+        return self
+
+    def __str__( self ):
+        return self.name
+
+    def __repr__( self ):
+        return _ustr(self)
+
+    def streamline( self ):
+        self.streamlined = True
+        self.strRepr = None
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        pass
+
+    def validate( self, validateTrace=[] ):
+        """
+        Check defined expressions for valid structure, check for infinite recursive definitions.
+        """
+        self.checkRecursion( [] )
+
+    def parseFile( self, file_or_filename, parseAll=False ):
+        """
+        Execute the parse expression on the given file or filename.
+        If a filename is specified (instead of a file object),
+        the entire file is opened, read, and closed before parsing.
+        """
+        try:
+            file_contents = file_or_filename.read()
+        except AttributeError:
+            with open(file_or_filename, "r") as f:
+                file_contents = f.read()
+        try:
+            return self.parseString(file_contents, parseAll)
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def __eq__(self,other):
+        if isinstance(other, ParserElement):
+            return self is other or vars(self) == vars(other)
+        elif isinstance(other, basestring):
+            return self.matches(other)
+        else:
+            return super(ParserElement,self)==other
+
+    def __ne__(self,other):
+        return not (self == other)
+
+    def __hash__(self):
+        return hash(id(self))
+
+    def __req__(self,other):
+        return self == other
+
+    def __rne__(self,other):
+        return not (self == other)
+
+    def matches(self, testString, parseAll=True):
+        """
+        Method for quick testing of a parser against a test string. Good for simple 
+        inline microtests of sub expressions while building up larger parser.
+           
+        Parameters:
+         - testString - to test against this expression for a match
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
+            
+        Example::
+            expr = Word(nums)
+            assert expr.matches("100")
+        """
+        try:
+            self.parseString(_ustr(testString), parseAll=parseAll)
+            return True
+        except ParseBaseException:
+            return False
+                
+    def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False):
+        """
+        Execute the parse expression on a series of test strings, showing each
+        test, the parsed results or where the parse failed. Quick and easy way to
+        run a parse expression against a list of sample strings.
+           
+        Parameters:
+         - tests - a list of separate test strings, or a multiline string of test strings
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests           
+         - comment - (default=C{'#'}) - expression for indicating embedded comments in the test 
+              string; pass None to disable comment filtering
+         - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline;
+              if False, only dump nested list
+         - printResults - (default=C{True}) prints test output to stdout
+         - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing
+
+        Returns: a (success, results) tuple, where success indicates that all tests succeeded
+        (or failed if C{failureTests} is True), and the results contain a list of lines of each 
+        test's output
+        
+        Example::
+            number_expr = pyparsing_common.number.copy()
+
+            result = number_expr.runTests('''
+                # unsigned integer
+                100
+                # negative integer
+                -100
+                # float with scientific notation
+                6.02e23
+                # integer with scientific notation
+                1e-12
+                ''')
+            print("Success" if result[0] else "Failed!")
+
+            result = number_expr.runTests('''
+                # stray character
+                100Z
+                # missing leading digit before '.'
+                -.100
+                # too many '.'
+                3.14.159
+                ''', failureTests=True)
+            print("Success" if result[0] else "Failed!")
+        prints::
+            # unsigned integer
+            100
+            [100]
+
+            # negative integer
+            -100
+            [-100]
+
+            # float with scientific notation
+            6.02e23
+            [6.02e+23]
+
+            # integer with scientific notation
+            1e-12
+            [1e-12]
+
+            Success
+            
+            # stray character
+            100Z
+               ^
+            FAIL: Expected end of text (at char 3), (line:1, col:4)
+
+            # missing leading digit before '.'
+            -.100
+            ^
+            FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
+
+            # too many '.'
+            3.14.159
+                ^
+            FAIL: Expected end of text (at char 4), (line:1, col:5)
+
+            Success
+
+        Each test string must be on a single line. If you want to test a string that spans multiple
+        lines, create a test like this::
+
+            expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines")
+        
+        (Note that this is a raw string literal, you must include the leading 'r'.)
+        """
+        if isinstance(tests, basestring):
+            tests = list(map(str.strip, tests.rstrip().splitlines()))
+        if isinstance(comment, basestring):
+            comment = Literal(comment)
+        allResults = []
+        comments = []
+        success = True
+        for t in tests:
+            if comment is not None and comment.matches(t, False) or comments and not t:
+                comments.append(t)
+                continue
+            if not t:
+                continue
+            out = ['\n'.join(comments), t]
+            comments = []
+            try:
+                t = t.replace(r'\n','\n')
+                result = self.parseString(t, parseAll=parseAll)
+                out.append(result.dump(full=fullDump))
+                success = success and not failureTests
+            except ParseBaseException as pe:
+                fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
+                if '\n' in t:
+                    out.append(line(pe.loc, t))
+                    out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
+                else:
+                    out.append(' '*pe.loc + '^' + fatal)
+                out.append("FAIL: " + str(pe))
+                success = success and failureTests
+                result = pe
+            except Exception as exc:
+                out.append("FAIL-EXCEPTION: " + str(exc))
+                success = success and failureTests
+                result = exc
+
+            if printResults:
+                if fullDump:
+                    out.append('')
+                print('\n'.join(out))
+
+            allResults.append((t, result))
+        
+        return success, allResults
+
+        
+class Token(ParserElement):
+    """
+    Abstract C{ParserElement} subclass, for defining atomic matching patterns.
+    """
+    def __init__( self ):
+        super(Token,self).__init__( savelist=False )
+
+
+class Empty(Token):
+    """
+    An empty token, will always match.
+    """
+    def __init__( self ):
+        super(Empty,self).__init__()
+        self.name = "Empty"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+
+class NoMatch(Token):
+    """
+    A token that will never match.
+    """
+    def __init__( self ):
+        super(NoMatch,self).__init__()
+        self.name = "NoMatch"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.errmsg = "Unmatchable token"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Literal(Token):
+    """
+    Token to exactly match a specified string.
+    
+    Example::
+        Literal('blah').parseString('blah')  # -> ['blah']
+        Literal('blah').parseString('blahfooblah')  # -> ['blah']
+        Literal('blah').parseString('bla')  # -> Exception: Expected "blah"
+    
+    For case-insensitive matching, use L{CaselessLiteral}.
+    
+    For keyword matching (force word break before and after the matched string),
+    use L{Keyword} or L{CaselessKeyword}.
+    """
+    def __init__( self, matchString ):
+        super(Literal,self).__init__()
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Literal; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+            self.__class__ = Empty
+        self.name = '"%s"' % _ustr(self.match)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+
+    # Performance tuning: this routine gets called a *lot*
+    # if this is a single character match string  and the first character matches,
+    # short-circuit as quickly as possible, and avoid calling startswith
+    #~ @profile
+    def parseImpl( self, instring, loc, doActions=True ):
+        if (instring[loc] == self.firstMatchChar and
+            (self.matchLen==1 or instring.startswith(self.match,loc)) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+_L = Literal
+ParserElement._literalStringClass = Literal
+
+class Keyword(Token):
+    """
+    Token to exactly match a specified string as a keyword, that is, it must be
+    immediately followed by a non-keyword character.  Compare with C{L{Literal}}:
+     - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}.
+     - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'}
+    Accepts two optional constructor arguments in addition to the keyword string:
+     - C{identChars} is a string of characters that would be valid identifier characters,
+          defaulting to all alphanumerics + "_" and "$"
+     - C{caseless} allows case-insensitive matching, default is C{False}.
+       
+    Example::
+        Keyword("start").parseString("start")  # -> ['start']
+        Keyword("start").parseString("starting")  # -> Exception
+
+    For case-insensitive matching, use L{CaselessKeyword}.
+    """
+    DEFAULT_KEYWORD_CHARS = alphanums+"_$"
+
+    def __init__( self, matchString, identChars=None, caseless=False ):
+        super(Keyword,self).__init__()
+        if identChars is None:
+            identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Keyword; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+        self.name = '"%s"' % self.match
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+        self.caseless = caseless
+        if caseless:
+            self.caselessmatch = matchString.upper()
+            identChars = identChars.upper()
+        self.identChars = set(identChars)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.caseless:
+            if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+                 (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and
+                 (loc == 0 or instring[loc-1].upper() not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        else:
+            if (instring[loc] == self.firstMatchChar and
+                (self.matchLen==1 or instring.startswith(self.match,loc)) and
+                (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and
+                (loc == 0 or instring[loc-1] not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+    def copy(self):
+        c = super(Keyword,self).copy()
+        c.identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        return c
+
+    @staticmethod
+    def setDefaultKeywordChars( chars ):
+        """Overrides the default Keyword chars
+        """
+        Keyword.DEFAULT_KEYWORD_CHARS = chars
+
+class CaselessLiteral(Literal):
+    """
+    Token to match a specified string, ignoring case of letters.
+    Note: the matched results will always be in the case of the given
+    match string, NOT the case of the input text.
+
+    Example::
+        OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessKeyword}.)
+    """
+    def __init__( self, matchString ):
+        super(CaselessLiteral,self).__init__( matchString.upper() )
+        # Preserve the defining literal.
+        self.returnString = matchString
+        self.name = "'%s'" % self.returnString
+        self.errmsg = "Expected " + self.name
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[ loc:loc+self.matchLen ].upper() == self.match:
+            return loc+self.matchLen, self.returnString
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CaselessKeyword(Keyword):
+    """
+    Caseless version of L{Keyword}.
+
+    Example::
+        OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessLiteral}.)
+    """
+    def __init__( self, matchString, identChars=None ):
+        super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True )
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+             (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CloseMatch(Token):
+    """
+    A variation on L{Literal} which matches "close" matches, that is, 
+    strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters:
+     - C{match_string} - string to be matched
+     - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match
+    
+    The results from a successful parse will contain the matched text from the input string and the following named results:
+     - C{mismatches} - a list of the positions within the match_string where mismatches were found
+     - C{original} - the original match_string used to compare against the input string
+    
+    If C{mismatches} is an empty list, then the match was an exact match.
+    
+    Example::
+        patt = CloseMatch("ATCATCGAATGGA")
+        patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
+        patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
+
+        # exact match
+        patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
+
+        # close match allowing up to 2 mismatches
+        patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2)
+        patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
+    """
+    def __init__(self, match_string, maxMismatches=1):
+        super(CloseMatch,self).__init__()
+        self.name = match_string
+        self.match_string = match_string
+        self.maxMismatches = maxMismatches
+        self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches)
+        self.mayIndexError = False
+        self.mayReturnEmpty = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        start = loc
+        instrlen = len(instring)
+        maxloc = start + len(self.match_string)
+
+        if maxloc <= instrlen:
+            match_string = self.match_string
+            match_stringloc = 0
+            mismatches = []
+            maxMismatches = self.maxMismatches
+
+            for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)):
+                src,mat = s_m
+                if src != mat:
+                    mismatches.append(match_stringloc)
+                    if len(mismatches) > maxMismatches:
+                        break
+            else:
+                loc = match_stringloc + 1
+                results = ParseResults([instring[start:loc]])
+                results['original'] = self.match_string
+                results['mismatches'] = mismatches
+                return loc, results
+
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Word(Token):
+    """
+    Token for matching words composed of allowed character sets.
+    Defined with string containing all allowed initial characters,
+    an optional string containing allowed body characters (if omitted,
+    defaults to the initial character set), and an optional minimum,
+    maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction. An optional
+    C{excludeChars} parameter can list characters that might be found in 
+    the input C{bodyChars} string; useful to define a word of all printables
+    except for one or two characters, for instance.
+    
+    L{srange} is useful for defining custom character set strings for defining 
+    C{Word} expressions, using range notation from regular expression character sets.
+    
+    A common mistake is to use C{Word} to match a specific literal string, as in 
+    C{Word("Address")}. Remember that C{Word} uses the string argument to define
+    I{sets} of matchable characters. This expression would match "Add", "AAA",
+    "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'.
+    To match an exact literal string, use L{Literal} or L{Keyword}.
+
+    pyparsing includes helper strings for building Words:
+     - L{alphas}
+     - L{nums}
+     - L{alphanums}
+     - L{hexnums}
+     - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.)
+     - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.)
+     - L{printables} (any non-whitespace character)
+
+    Example::
+        # a word composed of digits
+        integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
+        
+        # a word with a leading capital, and zero or more lowercase
+        capital_word = Word(alphas.upper(), alphas.lower())
+
+        # hostnames are alphanumeric, with leading alpha, and '-'
+        hostname = Word(alphas, alphanums+'-')
+        
+        # roman numeral (not a strict parser, accepts invalid mix of characters)
+        roman = Word("IVXLCDM")
+        
+        # any string of non-whitespace characters, except for ','
+        csv_value = Word(printables, excludeChars=",")
+    """
+    def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ):
+        super(Word,self).__init__()
+        if excludeChars:
+            initChars = ''.join(c for c in initChars if c not in excludeChars)
+            if bodyChars:
+                bodyChars = ''.join(c for c in bodyChars if c not in excludeChars)
+        self.initCharsOrig = initChars
+        self.initChars = set(initChars)
+        if bodyChars :
+            self.bodyCharsOrig = bodyChars
+            self.bodyChars = set(bodyChars)
+        else:
+            self.bodyCharsOrig = initChars
+            self.bodyChars = set(initChars)
+
+        self.maxSpecified = max > 0
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.asKeyword = asKeyword
+
+        if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0):
+            if self.bodyCharsOrig == self.initCharsOrig:
+                self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig)
+            elif len(self.initCharsOrig) == 1:
+                self.reString = "%s[%s]*" % \
+                                      (re.escape(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            else:
+                self.reString = "[%s][%s]*" % \
+                                      (_escapeRegexRangeChars(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            if self.asKeyword:
+                self.reString = r"\b"+self.reString+r"\b"
+            try:
+                self.re = re.compile( self.reString )
+            except Exception:
+                self.re = None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.re:
+            result = self.re.match(instring,loc)
+            if not result:
+                raise ParseException(instring, loc, self.errmsg, self)
+
+            loc = result.end()
+            return loc, result.group()
+
+        if not(instring[ loc ] in self.initChars):
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        instrlen = len(instring)
+        bodychars = self.bodyChars
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, instrlen )
+        while loc < maxloc and instring[loc] in bodychars:
+            loc += 1
+
+        throwException = False
+        if loc - start < self.minLen:
+            throwException = True
+        if self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
+            throwException = True
+        if self.asKeyword:
+            if (start>0 and instring[start-1] in bodychars) or (loc4:
+                    return s[:4]+"..."
+                else:
+                    return s
+
+            if ( self.initCharsOrig != self.bodyCharsOrig ):
+                self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) )
+            else:
+                self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig)
+
+        return self.strRepr
+
+
+class Regex(Token):
+    r"""
+    Token for matching strings that match a given regular expression.
+    Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module.
+    If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as 
+    named parse results.
+
+    Example::
+        realnum = Regex(r"[+-]?\d+\.\d*")
+        date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)')
+        # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
+        roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
+    """
+    compiledREtype = type(re.compile("[A-Z]"))
+    def __init__( self, pattern, flags=0):
+        """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags."""
+        super(Regex,self).__init__()
+
+        if isinstance(pattern, basestring):
+            if not pattern:
+                warnings.warn("null string passed to Regex; use Empty() instead",
+                        SyntaxWarning, stacklevel=2)
+
+            self.pattern = pattern
+            self.flags = flags
+
+            try:
+                self.re = re.compile(self.pattern, self.flags)
+                self.reString = self.pattern
+            except sre_constants.error:
+                warnings.warn("invalid pattern (%s) passed to Regex" % pattern,
+                    SyntaxWarning, stacklevel=2)
+                raise
+
+        elif isinstance(pattern, Regex.compiledREtype):
+            self.re = pattern
+            self.pattern = \
+            self.reString = str(pattern)
+            self.flags = flags
+            
+        else:
+            raise ValueError("Regex may only be constructed with a string or a compiled RE object")
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = self.re.match(instring,loc)
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        d = result.groupdict()
+        ret = ParseResults(result.group())
+        if d:
+            for k in d:
+                ret[k] = d[k]
+        return loc,ret
+
+    def __str__( self ):
+        try:
+            return super(Regex,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "Re:(%s)" % repr(self.pattern)
+
+        return self.strRepr
+
+
+class QuotedString(Token):
+    r"""
+    Token for matching strings that are delimited by quoting characters.
+    
+    Defined with the following parameters:
+        - quoteChar - string of one or more characters defining the quote delimiting string
+        - escChar - character to escape quotes, typically backslash (default=C{None})
+        - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None})
+        - multiline - boolean indicating whether quotes can span multiple lines (default=C{False})
+        - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True})
+        - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar)
+        - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True})
+
+    Example::
+        qs = QuotedString('"')
+        print(qs.searchString('lsjdf "This is the quote" sldjf'))
+        complex_qs = QuotedString('{{', endQuoteChar='}}')
+        print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
+        sql_qs = QuotedString('"', escQuote='""')
+        print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
+    prints::
+        [['This is the quote']]
+        [['This is the "quote"']]
+        [['This is the quote with "embedded" quotes']]
+    """
+    def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
+        super(QuotedString,self).__init__()
+
+        # remove white space from quote chars - wont work anyway
+        quoteChar = quoteChar.strip()
+        if not quoteChar:
+            warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+            raise SyntaxError()
+
+        if endQuoteChar is None:
+            endQuoteChar = quoteChar
+        else:
+            endQuoteChar = endQuoteChar.strip()
+            if not endQuoteChar:
+                warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+                raise SyntaxError()
+
+        self.quoteChar = quoteChar
+        self.quoteCharLen = len(quoteChar)
+        self.firstQuoteChar = quoteChar[0]
+        self.endQuoteChar = endQuoteChar
+        self.endQuoteCharLen = len(endQuoteChar)
+        self.escChar = escChar
+        self.escQuote = escQuote
+        self.unquoteResults = unquoteResults
+        self.convertWhitespaceEscapes = convertWhitespaceEscapes
+
+        if multiline:
+            self.flags = re.MULTILINE | re.DOTALL
+            self.pattern = r'%s(?:[^%s%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        else:
+            self.flags = 0
+            self.pattern = r'%s(?:[^%s\n\r%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        if len(self.endQuoteChar) > 1:
+            self.pattern += (
+                '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]),
+                                               _escapeRegexRangeChars(self.endQuoteChar[i]))
+                                    for i in range(len(self.endQuoteChar)-1,0,-1)) + ')'
+                )
+        if escQuote:
+            self.pattern += (r'|(?:%s)' % re.escape(escQuote))
+        if escChar:
+            self.pattern += (r'|(?:%s.)' % re.escape(escChar))
+            self.escCharReplacePattern = re.escape(self.escChar)+"(.)"
+        self.pattern += (r')*%s' % re.escape(self.endQuoteChar))
+
+        try:
+            self.re = re.compile(self.pattern, self.flags)
+            self.reString = self.pattern
+        except sre_constants.error:
+            warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern,
+                SyntaxWarning, stacklevel=2)
+            raise
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        ret = result.group()
+
+        if self.unquoteResults:
+
+            # strip off quotes
+            ret = ret[self.quoteCharLen:-self.endQuoteCharLen]
+
+            if isinstance(ret,basestring):
+                # replace escaped whitespace
+                if '\\' in ret and self.convertWhitespaceEscapes:
+                    ws_map = {
+                        r'\t' : '\t',
+                        r'\n' : '\n',
+                        r'\f' : '\f',
+                        r'\r' : '\r',
+                    }
+                    for wslit,wschar in ws_map.items():
+                        ret = ret.replace(wslit, wschar)
+
+                # replace escaped characters
+                if self.escChar:
+                    ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
+
+                # replace escaped quotes
+                if self.escQuote:
+                    ret = ret.replace(self.escQuote, self.endQuoteChar)
+
+        return loc, ret
+
+    def __str__( self ):
+        try:
+            return super(QuotedString,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar)
+
+        return self.strRepr
+
+
+class CharsNotIn(Token):
+    """
+    Token for matching words composed of characters I{not} in a given set (will
+    include whitespace in matched characters if not listed in the provided exclusion set - see example).
+    Defined with string containing all disallowed characters, and an optional
+    minimum, maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction.
+
+    Example::
+        # define a comma-separated-value as anything that is not a ','
+        csv_value = CharsNotIn(',')
+        print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
+    prints::
+        ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
+    """
+    def __init__( self, notChars, min=1, max=0, exact=0 ):
+        super(CharsNotIn,self).__init__()
+        self.skipWhitespace = False
+        self.notChars = notChars
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = ( self.minLen == 0 )
+        self.mayIndexError = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[loc] in self.notChars:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        notchars = self.notChars
+        maxlen = min( start+self.maxLen, len(instring) )
+        while loc < maxlen and \
+              (instring[loc] not in notchars):
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+    def __str__( self ):
+        try:
+            return super(CharsNotIn, self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            if len(self.notChars) > 4:
+                self.strRepr = "!W:(%s...)" % self.notChars[:4]
+            else:
+                self.strRepr = "!W:(%s)" % self.notChars
+
+        return self.strRepr
+
+class White(Token):
+    """
+    Special matching class for matching whitespace.  Normally, whitespace is ignored
+    by pyparsing grammars.  This class is included when some whitespace structures
+    are significant.  Define with a string containing the whitespace characters to be
+    matched; default is C{" \\t\\r\\n"}.  Also takes optional C{min}, C{max}, and C{exact} arguments,
+    as defined for the C{L{Word}} class.
+    """
+    whiteStrs = {
+        " " : "",
+        "\t": "",
+        "\n": "",
+        "\r": "",
+        "\f": "",
+        }
+    def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
+        super(White,self).__init__()
+        self.matchWhite = ws
+        self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) )
+        #~ self.leaveWhitespace()
+        self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite))
+        self.mayReturnEmpty = True
+        self.errmsg = "Expected " + self.name
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if not(instring[ loc ] in self.matchWhite):
+            raise ParseException(instring, loc, self.errmsg, self)
+        start = loc
+        loc += 1
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, len(instring) )
+        while loc < maxloc and instring[loc] in self.matchWhite:
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+
+class _PositionToken(Token):
+    def __init__( self ):
+        super(_PositionToken,self).__init__()
+        self.name=self.__class__.__name__
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+class GoToColumn(_PositionToken):
+    """
+    Token to advance to a specific column of input text; useful for tabular report scraping.
+    """
+    def __init__( self, colno ):
+        super(GoToColumn,self).__init__()
+        self.col = colno
+
+    def preParse( self, instring, loc ):
+        if col(loc,instring) != self.col:
+            instrlen = len(instring)
+            if self.ignoreExprs:
+                loc = self._skipIgnorables( instring, loc )
+            while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col :
+                loc += 1
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        thiscol = col( loc, instring )
+        if thiscol > self.col:
+            raise ParseException( instring, loc, "Text not in expected column", self )
+        newloc = loc + self.col - thiscol
+        ret = instring[ loc: newloc ]
+        return newloc, ret
+
+
+class LineStart(_PositionToken):
+    """
+    Matches if current position is at the beginning of a line within the parse string
+    
+    Example::
+    
+        test = '''\
+        AAA this line
+        AAA and this line
+          AAA but not this one
+        B AAA and definitely not this one
+        '''
+
+        for t in (LineStart() + 'AAA' + restOfLine).searchString(test):
+            print(t)
+    
+    Prints::
+        ['AAA', ' this line']
+        ['AAA', ' and this line']    
+
+    """
+    def __init__( self ):
+        super(LineStart,self).__init__()
+        self.errmsg = "Expected start of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if col(loc, instring) == 1:
+            return loc, []
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class LineEnd(_PositionToken):
+    """
+    Matches if current position is at the end of a line within the parse string
+    """
+    def __init__( self ):
+        super(LineEnd,self).__init__()
+        self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") )
+        self.errmsg = "Expected end of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if loc len(instring):
+            return loc, []
+        else:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+class WordStart(_PositionToken):
+    """
+    Matches if the current position is at the beginning of a Word, and
+    is not preceded by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of
+    the string being parsed, or at the beginning of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordStart,self).__init__()
+        self.wordChars = set(wordChars)
+        self.errmsg = "Not at the start of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        if loc != 0:
+            if (instring[loc-1] in self.wordChars or
+                instring[loc] not in self.wordChars):
+                raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+class WordEnd(_PositionToken):
+    """
+    Matches if the current position is at the end of a Word, and
+    is not followed by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of
+    the string being parsed, or at the end of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordEnd,self).__init__()
+        self.wordChars = set(wordChars)
+        self.skipWhitespace = False
+        self.errmsg = "Not at the end of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        instrlen = len(instring)
+        if instrlen>0 and loc maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+            else:
+                # save match among all matches, to retry longest to shortest
+                matches.append((loc2, e))
+
+        if matches:
+            matches.sort(key=lambda x: -x[0])
+            for _,e in matches:
+                try:
+                    return e._parse( instring, loc, doActions )
+                except ParseException as err:
+                    err.__traceback__ = None
+                    if err.loc > maxExcLoc:
+                        maxException = err
+                        maxExcLoc = err.loc
+
+        if maxException is not None:
+            maxException.msg = self.errmsg
+            raise maxException
+        else:
+            raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+
+    def __ixor__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #Or( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class MatchFirst(ParseExpression):
+    """
+    Requires that at least one C{ParseExpression} is found.
+    If two expressions match, the first one listed is the one that will match.
+    May be constructed using the C{'|'} operator.
+
+    Example::
+        # construct MatchFirst using '|' operator
+        
+        # watch the order of expressions to match
+        number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
+        print(number.searchString("123 3.1416 789")) #  Fail! -> [['123'], ['3'], ['1416'], ['789']]
+
+        # put more selective expression first
+        number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
+        print(number.searchString("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
+    """
+    def __init__( self, exprs, savelist = False ):
+        super(MatchFirst,self).__init__(exprs, savelist)
+        if self.exprs:
+            self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+        else:
+            self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        maxExcLoc = -1
+        maxException = None
+        for e in self.exprs:
+            try:
+                ret = e._parse( instring, loc, doActions )
+                return ret
+            except ParseException as err:
+                if err.loc > maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+
+        # only got here if no expression matched, raise exception for match that made it the furthest
+        else:
+            if maxException is not None:
+                maxException.msg = self.errmsg
+                raise maxException
+            else:
+                raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+    def __ior__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #MatchFirst( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class Each(ParseExpression):
+    """
+    Requires all given C{ParseExpression}s to be found, but in any order.
+    Expressions may be separated by whitespace.
+    May be constructed using the C{'&'} operator.
+
+    Example::
+        color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
+        shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
+        integer = Word(nums)
+        shape_attr = "shape:" + shape_type("shape")
+        posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
+        color_attr = "color:" + color("color")
+        size_attr = "size:" + integer("size")
+
+        # use Each (using operator '&') to accept attributes in any order 
+        # (shape and posn are required, color and size are optional)
+        shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
+
+        shape_spec.runTests('''
+            shape: SQUARE color: BLACK posn: 100, 120
+            shape: CIRCLE size: 50 color: BLUE posn: 50,80
+            color:GREEN size:20 shape:TRIANGLE posn:20,40
+            '''
+            )
+    prints::
+        shape: SQUARE color: BLACK posn: 100, 120
+        ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
+        - color: BLACK
+        - posn: ['100', ',', '120']
+          - x: 100
+          - y: 120
+        - shape: SQUARE
+
+
+        shape: CIRCLE size: 50 color: BLUE posn: 50,80
+        ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
+        - color: BLUE
+        - posn: ['50', ',', '80']
+          - x: 50
+          - y: 80
+        - shape: CIRCLE
+        - size: 50
+
+
+        color: GREEN size: 20 shape: TRIANGLE posn: 20,40
+        ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
+        - color: GREEN
+        - posn: ['20', ',', '40']
+          - x: 20
+          - y: 40
+        - shape: TRIANGLE
+        - size: 20
+    """
+    def __init__( self, exprs, savelist = True ):
+        super(Each,self).__init__(exprs, savelist)
+        self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+        self.skipWhitespace = True
+        self.initExprGroups = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.initExprGroups:
+            self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional))
+            opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ]
+            opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)]
+            self.optionals = opt1 + opt2
+            self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ]
+            self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ]
+            self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ]
+            self.required += self.multirequired
+            self.initExprGroups = False
+        tmpLoc = loc
+        tmpReqd = self.required[:]
+        tmpOpt  = self.optionals[:]
+        matchOrder = []
+
+        keepMatching = True
+        while keepMatching:
+            tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
+            failed = []
+            for e in tmpExprs:
+                try:
+                    tmpLoc = e.tryParse( instring, tmpLoc )
+                except ParseException:
+                    failed.append(e)
+                else:
+                    matchOrder.append(self.opt1map.get(id(e),e))
+                    if e in tmpReqd:
+                        tmpReqd.remove(e)
+                    elif e in tmpOpt:
+                        tmpOpt.remove(e)
+            if len(failed) == len(tmpExprs):
+                keepMatching = False
+
+        if tmpReqd:
+            missing = ", ".join(_ustr(e) for e in tmpReqd)
+            raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing )
+
+        # add any unmatched Optionals, in case they have default values defined
+        matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt]
+
+        resultlist = []
+        for e in matchOrder:
+            loc,results = e._parse(instring,loc,doActions)
+            resultlist.append(results)
+
+        finalResults = sum(resultlist, ParseResults([]))
+        return loc, finalResults
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class ParseElementEnhance(ParserElement):
+    """
+    Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(ParseElementEnhance,self).__init__(savelist)
+        if isinstance( expr, basestring ):
+            if issubclass(ParserElement._literalStringClass, Token):
+                expr = ParserElement._literalStringClass(expr)
+            else:
+                expr = ParserElement._literalStringClass(Literal(expr))
+        self.expr = expr
+        self.strRepr = None
+        if expr is not None:
+            self.mayIndexError = expr.mayIndexError
+            self.mayReturnEmpty = expr.mayReturnEmpty
+            self.setWhitespaceChars( expr.whiteChars )
+            self.skipWhitespace = expr.skipWhitespace
+            self.saveAsList = expr.saveAsList
+            self.callPreparse = expr.callPreparse
+            self.ignoreExprs.extend(expr.ignoreExprs)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr is not None:
+            return self.expr._parse( instring, loc, doActions, callPreParse=False )
+        else:
+            raise ParseException("",loc,self.errmsg,self)
+
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        self.expr = self.expr.copy()
+        if self.expr is not None:
+            self.expr.leaveWhitespace()
+        return self
+
+    def ignore( self, other ):
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                super( ParseElementEnhance, self).ignore( other )
+                if self.expr is not None:
+                    self.expr.ignore( self.ignoreExprs[-1] )
+        else:
+            super( ParseElementEnhance, self).ignore( other )
+            if self.expr is not None:
+                self.expr.ignore( self.ignoreExprs[-1] )
+        return self
+
+    def streamline( self ):
+        super(ParseElementEnhance,self).streamline()
+        if self.expr is not None:
+            self.expr.streamline()
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        if self in parseElementList:
+            raise RecursiveGrammarException( parseElementList+[self] )
+        subRecCheckList = parseElementList[:] + [ self ]
+        if self.expr is not None:
+            self.expr.checkRecursion( subRecCheckList )
+
+    def validate( self, validateTrace=[] ):
+        tmp = validateTrace[:]+[self]
+        if self.expr is not None:
+            self.expr.validate(tmp)
+        self.checkRecursion( [] )
+
+    def __str__( self ):
+        try:
+            return super(ParseElementEnhance,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None and self.expr is not None:
+            self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) )
+        return self.strRepr
+
+
+class FollowedBy(ParseElementEnhance):
+    """
+    Lookahead matching of the given parse expression.  C{FollowedBy}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression matches at the current
+    position.  C{FollowedBy} always returns a null token list.
+
+    Example::
+        # use FollowedBy to match a label only if it is followed by a ':'
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
+    prints::
+        [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
+    """
+    def __init__( self, expr ):
+        super(FollowedBy,self).__init__(expr)
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self.expr.tryParse( instring, loc )
+        return loc, []
+
+
+class NotAny(ParseElementEnhance):
+    """
+    Lookahead to disallow matching with the given parse expression.  C{NotAny}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression does I{not} match at the current
+    position.  Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny}
+    always returns a null token list.  May be constructed using the '~' operator.
+
+    Example::
+        
+    """
+    def __init__( self, expr ):
+        super(NotAny,self).__init__(expr)
+        #~ self.leaveWhitespace()
+        self.skipWhitespace = False  # do NOT use self.leaveWhitespace(), don't want to propagate to exprs
+        self.mayReturnEmpty = True
+        self.errmsg = "Found unwanted token, "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr.canParseNext(instring, loc):
+            raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "~{" + _ustr(self.expr) + "}"
+
+        return self.strRepr
+
+class _MultipleMatch(ParseElementEnhance):
+    def __init__( self, expr, stopOn=None):
+        super(_MultipleMatch, self).__init__(expr)
+        self.saveAsList = True
+        ender = stopOn
+        if isinstance(ender, basestring):
+            ender = ParserElement._literalStringClass(ender)
+        self.not_ender = ~ender if ender is not None else None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self_expr_parse = self.expr._parse
+        self_skip_ignorables = self._skipIgnorables
+        check_ender = self.not_ender is not None
+        if check_ender:
+            try_not_ender = self.not_ender.tryParse
+        
+        # must be at least one (but first see if we are the stopOn sentinel;
+        # if so, fail)
+        if check_ender:
+            try_not_ender(instring, loc)
+        loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
+        try:
+            hasIgnoreExprs = (not not self.ignoreExprs)
+            while 1:
+                if check_ender:
+                    try_not_ender(instring, loc)
+                if hasIgnoreExprs:
+                    preloc = self_skip_ignorables( instring, loc )
+                else:
+                    preloc = loc
+                loc, tmptokens = self_expr_parse( instring, preloc, doActions )
+                if tmptokens or tmptokens.haskeys():
+                    tokens += tmptokens
+        except (ParseException,IndexError):
+            pass
+
+        return loc, tokens
+        
+class OneOrMore(_MultipleMatch):
+    """
+    Repetition of one or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match one or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: BLACK"
+        OneOrMore(attr_expr).parseString(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+
+        # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
+        
+        # could also be written as
+        (attr_expr * (1,)).parseString(text).pprint()
+    """
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + _ustr(self.expr) + "}..."
+
+        return self.strRepr
+
+class ZeroOrMore(_MultipleMatch):
+    """
+    Optional repetition of zero or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match zero or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example: similar to L{OneOrMore}
+    """
+    def __init__( self, expr, stopOn=None):
+        super(ZeroOrMore,self).__init__(expr, stopOn=stopOn)
+        self.mayReturnEmpty = True
+        
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
+        except (ParseException,IndexError):
+            return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]..."
+
+        return self.strRepr
+
+class _NullToken(object):
+    def __bool__(self):
+        return False
+    __nonzero__ = __bool__
+    def __str__(self):
+        return ""
+
+_optionalNotMatched = _NullToken()
+class Optional(ParseElementEnhance):
+    """
+    Optional matching of the given expression.
+
+    Parameters:
+     - expr - expression that must match zero or more times
+     - default (optional) - value to be returned if the optional expression is not found.
+
+    Example::
+        # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
+        zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
+        zip.runTests('''
+            # traditional ZIP code
+            12345
+            
+            # ZIP+4 form
+            12101-0001
+            
+            # invalid ZIP
+            98765-
+            ''')
+    prints::
+        # traditional ZIP code
+        12345
+        ['12345']
+
+        # ZIP+4 form
+        12101-0001
+        ['12101-0001']
+
+        # invalid ZIP
+        98765-
+             ^
+        FAIL: Expected end of text (at char 5), (line:1, col:6)
+    """
+    def __init__( self, expr, default=_optionalNotMatched ):
+        super(Optional,self).__init__( expr, savelist=False )
+        self.saveAsList = self.expr.saveAsList
+        self.defaultValue = default
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False )
+        except (ParseException,IndexError):
+            if self.defaultValue is not _optionalNotMatched:
+                if self.expr.resultsName:
+                    tokens = ParseResults([ self.defaultValue ])
+                    tokens[self.expr.resultsName] = self.defaultValue
+                else:
+                    tokens = [ self.defaultValue ]
+            else:
+                tokens = []
+        return loc, tokens
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]"
+
+        return self.strRepr
+
+class SkipTo(ParseElementEnhance):
+    """
+    Token for skipping over all undefined text until the matched expression is found.
+
+    Parameters:
+     - expr - target expression marking the end of the data to be skipped
+     - include - (default=C{False}) if True, the target expression is also parsed 
+          (the skipped text and target expression are returned as a 2-element list).
+     - ignore - (default=C{None}) used to define grammars (typically quoted strings and 
+          comments) that might contain false matches to the target expression
+     - failOn - (default=C{None}) define expressions that are not allowed to be 
+          included in the skipped test; if found before the target expression is found, 
+          the SkipTo is not a match
+
+    Example::
+        report = '''
+            Outstanding Issues Report - 1 Jan 2000
+
+               # | Severity | Description                               |  Days Open
+            -----+----------+-------------------------------------------+-----------
+             101 | Critical | Intermittent system crash                 |          6
+              94 | Cosmetic | Spelling error on Login ('log|n')         |         14
+              79 | Minor    | System slow when running too many reports |         47
+            '''
+        integer = Word(nums)
+        SEP = Suppress('|')
+        # use SkipTo to simply match everything up until the next SEP
+        # - ignore quoted strings, so that a '|' character inside a quoted string does not match
+        # - parse action will call token.strip() for each matched token, i.e., the description body
+        string_data = SkipTo(SEP, ignore=quotedString)
+        string_data.setParseAction(tokenMap(str.strip))
+        ticket_expr = (integer("issue_num") + SEP 
+                      + string_data("sev") + SEP 
+                      + string_data("desc") + SEP 
+                      + integer("days_open"))
+        
+        for tkt in ticket_expr.searchString(report):
+            print tkt.dump()
+    prints::
+        ['101', 'Critical', 'Intermittent system crash', '6']
+        - days_open: 6
+        - desc: Intermittent system crash
+        - issue_num: 101
+        - sev: Critical
+        ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
+        - days_open: 14
+        - desc: Spelling error on Login ('log|n')
+        - issue_num: 94
+        - sev: Cosmetic
+        ['79', 'Minor', 'System slow when running too many reports', '47']
+        - days_open: 47
+        - desc: System slow when running too many reports
+        - issue_num: 79
+        - sev: Minor
+    """
+    def __init__( self, other, include=False, ignore=None, failOn=None ):
+        super( SkipTo, self ).__init__( other )
+        self.ignoreExpr = ignore
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.includeMatch = include
+        self.asList = False
+        if isinstance(failOn, basestring):
+            self.failOn = ParserElement._literalStringClass(failOn)
+        else:
+            self.failOn = failOn
+        self.errmsg = "No match found for "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        startloc = loc
+        instrlen = len(instring)
+        expr = self.expr
+        expr_parse = self.expr._parse
+        self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
+        self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
+        
+        tmploc = loc
+        while tmploc <= instrlen:
+            if self_failOn_canParseNext is not None:
+                # break if failOn expression matches
+                if self_failOn_canParseNext(instring, tmploc):
+                    break
+                    
+            if self_ignoreExpr_tryParse is not None:
+                # advance past ignore expressions
+                while 1:
+                    try:
+                        tmploc = self_ignoreExpr_tryParse(instring, tmploc)
+                    except ParseBaseException:
+                        break
+            
+            try:
+                expr_parse(instring, tmploc, doActions=False, callPreParse=False)
+            except (ParseException, IndexError):
+                # no match, advance loc in string
+                tmploc += 1
+            else:
+                # matched skipto expr, done
+                break
+
+        else:
+            # ran off the end of the input string without matching skipto expr, fail
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        # build up return values
+        loc = tmploc
+        skiptext = instring[startloc:loc]
+        skipresult = ParseResults(skiptext)
+        
+        if self.includeMatch:
+            loc, mat = expr_parse(instring,loc,doActions,callPreParse=False)
+            skipresult += mat
+
+        return loc, skipresult
+
+class Forward(ParseElementEnhance):
+    """
+    Forward declaration of an expression to be defined later -
+    used for recursive grammars, such as algebraic infix notation.
+    When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator.
+
+    Note: take care when assigning to C{Forward} not to overlook precedence of operators.
+    Specifically, '|' has a lower precedence than '<<', so that::
+        fwdExpr << a | b | c
+    will actually be evaluated as::
+        (fwdExpr << a) | b | c
+    thereby leaving b and c out as parseable alternatives.  It is recommended that you
+    explicitly group the values inserted into the C{Forward}::
+        fwdExpr << (a | b | c)
+    Converting to use the '<<=' operator instead will avoid this problem.
+
+    See L{ParseResults.pprint} for an example of a recursive parser created using
+    C{Forward}.
+    """
+    def __init__( self, other=None ):
+        super(Forward,self).__init__( other, savelist=False )
+
+    def __lshift__( self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass(other)
+        self.expr = other
+        self.strRepr = None
+        self.mayIndexError = self.expr.mayIndexError
+        self.mayReturnEmpty = self.expr.mayReturnEmpty
+        self.setWhitespaceChars( self.expr.whiteChars )
+        self.skipWhitespace = self.expr.skipWhitespace
+        self.saveAsList = self.expr.saveAsList
+        self.ignoreExprs.extend(self.expr.ignoreExprs)
+        return self
+        
+    def __ilshift__(self, other):
+        return self << other
+    
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        return self
+
+    def streamline( self ):
+        if not self.streamlined:
+            self.streamlined = True
+            if self.expr is not None:
+                self.expr.streamline()
+        return self
+
+    def validate( self, validateTrace=[] ):
+        if self not in validateTrace:
+            tmp = validateTrace[:]+[self]
+            if self.expr is not None:
+                self.expr.validate(tmp)
+        self.checkRecursion([])
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+        return self.__class__.__name__ + ": ..."
+
+        # stubbed out for now - creates awful memory and perf issues
+        self._revertClass = self.__class__
+        self.__class__ = _ForwardNoRecurse
+        try:
+            if self.expr is not None:
+                retString = _ustr(self.expr)
+            else:
+                retString = "None"
+        finally:
+            self.__class__ = self._revertClass
+        return self.__class__.__name__ + ": " + retString
+
+    def copy(self):
+        if self.expr is not None:
+            return super(Forward,self).copy()
+        else:
+            ret = Forward()
+            ret <<= self
+            return ret
+
+class _ForwardNoRecurse(Forward):
+    def __str__( self ):
+        return "..."
+
+class TokenConverter(ParseElementEnhance):
+    """
+    Abstract subclass of C{ParseExpression}, for converting parsed results.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(TokenConverter,self).__init__( expr )#, savelist )
+        self.saveAsList = False
+
+class Combine(TokenConverter):
+    """
+    Converter to concatenate all matching tokens to a single string.
+    By default, the matching patterns must also be contiguous in the input string;
+    this can be disabled by specifying C{'adjacent=False'} in the constructor.
+
+    Example::
+        real = Word(nums) + '.' + Word(nums)
+        print(real.parseString('3.1416')) # -> ['3', '.', '1416']
+        # will also erroneously match the following
+        print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
+
+        real = Combine(Word(nums) + '.' + Word(nums))
+        print(real.parseString('3.1416')) # -> ['3.1416']
+        # no match when there are internal spaces
+        print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
+    """
+    def __init__( self, expr, joinString="", adjacent=True ):
+        super(Combine,self).__init__( expr )
+        # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
+        if adjacent:
+            self.leaveWhitespace()
+        self.adjacent = adjacent
+        self.skipWhitespace = True
+        self.joinString = joinString
+        self.callPreparse = True
+
+    def ignore( self, other ):
+        if self.adjacent:
+            ParserElement.ignore(self, other)
+        else:
+            super( Combine, self).ignore( other )
+        return self
+
+    def postParse( self, instring, loc, tokenlist ):
+        retToks = tokenlist.copy()
+        del retToks[:]
+        retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults)
+
+        if self.resultsName and retToks.haskeys():
+            return [ retToks ]
+        else:
+            return retToks
+
+class Group(TokenConverter):
+    """
+    Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.
+
+    Example::
+        ident = Word(alphas)
+        num = Word(nums)
+        term = ident | num
+        func = ident + Optional(delimitedList(term))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', 'a', 'b', '100']
+
+        func = ident + Group(Optional(delimitedList(term)))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', ['a', 'b', '100']]
+    """
+    def __init__( self, expr ):
+        super(Group,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        return [ tokenlist ]
+
+class Dict(TokenConverter):
+    """
+    Converter to return a repetitive expression as a list, but also as a dictionary.
+    Each element can also be referenced using the first token in the expression as its key.
+    Useful for tabular report scraping when the first column can be used as a item key.
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        # print attributes as plain groups
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
+        result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
+        print(result.dump())
+        
+        # access named fields as dict entries, or output as dict
+        print(result['shape'])        
+        print(result.asDict())
+    prints::
+        ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
+
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
+    See more examples at L{ParseResults} of accessing fields by results name.
+    """
+    def __init__( self, expr ):
+        super(Dict,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        for i,tok in enumerate(tokenlist):
+            if len(tok) == 0:
+                continue
+            ikey = tok[0]
+            if isinstance(ikey,int):
+                ikey = _ustr(tok[0]).strip()
+            if len(tok)==1:
+                tokenlist[ikey] = _ParseResultsWithOffset("",i)
+            elif len(tok)==2 and not isinstance(tok[1],ParseResults):
+                tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i)
+            else:
+                dictvalue = tok.copy() #ParseResults(i)
+                del dictvalue[0]
+                if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()):
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i)
+                else:
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i)
+
+        if self.resultsName:
+            return [ tokenlist ]
+        else:
+            return tokenlist
+
+
+class Suppress(TokenConverter):
+    """
+    Converter for ignoring the results of a parsed expression.
+
+    Example::
+        source = "a, b, c,d"
+        wd = Word(alphas)
+        wd_list1 = wd + ZeroOrMore(',' + wd)
+        print(wd_list1.parseString(source))
+
+        # often, delimiters that are useful during parsing are just in the
+        # way afterward - use Suppress to keep them out of the parsed output
+        wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+        print(wd_list2.parseString(source))
+    prints::
+        ['a', ',', 'b', ',', 'c', ',', 'd']
+        ['a', 'b', 'c', 'd']
+    (See also L{delimitedList}.)
+    """
+    def postParse( self, instring, loc, tokenlist ):
+        return []
+
+    def suppress( self ):
+        return self
+
+
+class OnlyOnce(object):
+    """
+    Wrapper for parse actions, to ensure they are only called once.
+    """
+    def __init__(self, methodCall):
+        self.callable = _trim_arity(methodCall)
+        self.called = False
+    def __call__(self,s,l,t):
+        if not self.called:
+            results = self.callable(s,l,t)
+            self.called = True
+            return results
+        raise ParseException(s,l,"")
+    def reset(self):
+        self.called = False
+
+def traceParseAction(f):
+    """
+    Decorator for debugging parse actions. 
+    
+    When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".}
+    When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised.
+
+    Example::
+        wd = Word(alphas)
+
+        @traceParseAction
+        def remove_duplicate_chars(tokens):
+            return ''.join(sorted(set(''.join(tokens))))
+
+        wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
+        print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
+    prints::
+        >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
+        <3:
+            thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
+        sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) )
+        try:
+            ret = f(*paArgs)
+        except Exception as exc:
+            sys.stderr.write( "< ['aa', 'bb', 'cc']
+        delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
+    """
+    dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..."
+    if combine:
+        return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName)
+    else:
+        return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName)
+
+def countedArray( expr, intExpr=None ):
+    """
+    Helper to define a counted list of expressions.
+    This helper defines a pattern of the form::
+        integer expr expr expr...
+    where the leading integer tells how many expr expressions follow.
+    The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed.
+    
+    If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value.
+
+    Example::
+        countedArray(Word(alphas)).parseString('2 ab cd ef')  # -> ['ab', 'cd']
+
+        # in this parser, the leading integer value is given in binary,
+        # '10' indicating that 2 values are in the array
+        binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2))
+        countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef')  # -> ['ab', 'cd']
+    """
+    arrayExpr = Forward()
+    def countFieldParseAction(s,l,t):
+        n = t[0]
+        arrayExpr << (n and Group(And([expr]*n)) or Group(empty))
+        return []
+    if intExpr is None:
+        intExpr = Word(nums).setParseAction(lambda t:int(t[0]))
+    else:
+        intExpr = intExpr.copy()
+    intExpr.setName("arrayLen")
+    intExpr.addParseAction(countFieldParseAction, callDuringTry=True)
+    return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...')
+
+def _flatten(L):
+    ret = []
+    for i in L:
+        if isinstance(i,list):
+            ret.extend(_flatten(i))
+        else:
+            ret.append(i)
+    return ret
+
+def matchPreviousLiteral(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousLiteral(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches a
+    previous literal, will also match the leading C{"1:1"} in C{"1:10"}.
+    If this is not desired, use C{matchPreviousExpr}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    def copyTokenToRepeater(s,l,t):
+        if t:
+            if len(t) == 1:
+                rep << t[0]
+            else:
+                # flatten t tokens
+                tflat = _flatten(t.asList())
+                rep << And(Literal(tt) for tt in tflat)
+        else:
+            rep << Empty()
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def matchPreviousExpr(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousExpr(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches by
+    expressions, will I{not} match the leading C{"1:1"} in C{"1:10"};
+    the expressions are evaluated first, and then compared, so
+    C{"1"} is compared with C{"10"}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    e2 = expr.copy()
+    rep <<= e2
+    def copyTokenToRepeater(s,l,t):
+        matchTokens = _flatten(t.asList())
+        def mustMatchTheseTokens(s,l,t):
+            theseTokens = _flatten(t.asList())
+            if  theseTokens != matchTokens:
+                raise ParseException("",0,"")
+        rep.setParseAction( mustMatchTheseTokens, callDuringTry=True )
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def _escapeRegexRangeChars(s):
+    #~  escape these chars: ^-]
+    for c in r"\^-]":
+        s = s.replace(c,_bslash+c)
+    s = s.replace("\n",r"\n")
+    s = s.replace("\t",r"\t")
+    return _ustr(s)
+
+def oneOf( strs, caseless=False, useRegex=True ):
+    """
+    Helper to quickly define a set of alternative Literals, and makes sure to do
+    longest-first testing when there is a conflict, regardless of the input order,
+    but returns a C{L{MatchFirst}} for best performance.
+
+    Parameters:
+     - strs - a string of space-delimited literals, or a collection of string literals
+     - caseless - (default=C{False}) - treat all literals as caseless
+     - useRegex - (default=C{True}) - as an optimization, will generate a Regex
+          object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or
+          if creating a C{Regex} raises an exception)
+
+    Example::
+        comp_oper = oneOf("< = > <= >= !=")
+        var = Word(alphas)
+        number = Word(nums)
+        term = var | number
+        comparison_expr = term + comp_oper + term
+        print(comparison_expr.searchString("B = 12  AA=23 B<=AA AA>12"))
+    prints::
+        [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
+    """
+    if caseless:
+        isequal = ( lambda a,b: a.upper() == b.upper() )
+        masks = ( lambda a,b: b.upper().startswith(a.upper()) )
+        parseElementClass = CaselessLiteral
+    else:
+        isequal = ( lambda a,b: a == b )
+        masks = ( lambda a,b: b.startswith(a) )
+        parseElementClass = Literal
+
+    symbols = []
+    if isinstance(strs,basestring):
+        symbols = strs.split()
+    elif isinstance(strs, Iterable):
+        symbols = list(strs)
+    else:
+        warnings.warn("Invalid argument to oneOf, expected string or iterable",
+                SyntaxWarning, stacklevel=2)
+    if not symbols:
+        return NoMatch()
+
+    i = 0
+    while i < len(symbols)-1:
+        cur = symbols[i]
+        for j,other in enumerate(symbols[i+1:]):
+            if ( isequal(other, cur) ):
+                del symbols[i+j+1]
+                break
+            elif ( masks(cur, other) ):
+                del symbols[i+j+1]
+                symbols.insert(i,other)
+                cur = other
+                break
+        else:
+            i += 1
+
+    if not caseless and useRegex:
+        #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] ))
+        try:
+            if len(symbols)==len("".join(symbols)):
+                return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols))
+            else:
+                return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols))
+        except Exception:
+            warnings.warn("Exception creating Regex for oneOf, building MatchFirst",
+                    SyntaxWarning, stacklevel=2)
+
+
+    # last resort, just use MatchFirst
+    return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols))
+
+def dictOf( key, value ):
+    """
+    Helper to easily and clearly define a dictionary by specifying the respective patterns
+    for the key and value.  Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens
+    in the proper order.  The key pattern can include delimiting markers or punctuation,
+    as long as they are suppressed, thereby leaving the significant key text.  The value
+    pattern can include named results, so that the C{Dict} results can include named token
+    fields.
+
+    Example::
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        attr_label = label
+        attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
+
+        # similar to Dict, but simpler call format
+        result = dictOf(attr_label, attr_value).parseString(text)
+        print(result.dump())
+        print(result['shape'])
+        print(result.shape)  # object attribute access works too
+        print(result.asDict())
+    prints::
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        SQUARE
+        {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
+    """
+    return Dict( ZeroOrMore( Group ( key + value ) ) )
+
+def originalTextFor(expr, asString=True):
+    """
+    Helper to return the original, untokenized text for a given expression.  Useful to
+    restore the parsed fields of an HTML start tag into the raw tag text itself, or to
+    revert separate tokens with intervening whitespace back to the original matching
+    input text. By default, returns astring containing the original parsed text.  
+       
+    If the optional C{asString} argument is passed as C{False}, then the return value is a 
+    C{L{ParseResults}} containing any results names that were originally matched, and a 
+    single token containing the original matched text from the input string.  So if 
+    the expression passed to C{L{originalTextFor}} contains expressions with defined
+    results names, you must set C{asString} to C{False} if you want to preserve those
+    results name values.
+
+    Example::
+        src = "this is test  bold text  normal text "
+        for tag in ("b","i"):
+            opener,closer = makeHTMLTags(tag)
+            patt = originalTextFor(opener + SkipTo(closer) + closer)
+            print(patt.searchString(src)[0])
+    prints::
+        [' bold text ']
+        ['text']
+    """
+    locMarker = Empty().setParseAction(lambda s,loc,t: loc)
+    endlocMarker = locMarker.copy()
+    endlocMarker.callPreparse = False
+    matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
+    if asString:
+        extractText = lambda s,l,t: s[t._original_start:t._original_end]
+    else:
+        def extractText(s,l,t):
+            t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
+    matchExpr.setParseAction(extractText)
+    matchExpr.ignoreExprs = expr.ignoreExprs
+    return matchExpr
+
+def ungroup(expr): 
+    """
+    Helper to undo pyparsing's default grouping of And expressions, even
+    if all but one are non-empty.
+    """
+    return TokenConverter(expr).setParseAction(lambda t:t[0])
+
+def locatedExpr(expr):
+    """
+    Helper to decorate a returned token with its starting and ending locations in the input string.
+    This helper adds the following results names:
+     - locn_start = location where matched expression begins
+     - locn_end = location where matched expression ends
+     - value = the actual parsed results
+
+    Be careful if the input text contains C{} characters, you may want to call
+    C{L{ParserElement.parseWithTabs}}
+
+    Example::
+        wd = Word(alphas)
+        for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
+            print(match)
+    prints::
+        [[0, 'ljsdf', 5]]
+        [[8, 'lksdjjf', 15]]
+        [[18, 'lkkjj', 23]]
+    """
+    locator = Empty().setParseAction(lambda s,l,t: l)
+    return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
+
+
+# convenience constants for positional expressions
+empty       = Empty().setName("empty")
+lineStart   = LineStart().setName("lineStart")
+lineEnd     = LineEnd().setName("lineEnd")
+stringStart = StringStart().setName("stringStart")
+stringEnd   = StringEnd().setName("stringEnd")
+
+_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
+_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
+_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
+_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
+_charRange = Group(_singleChar + Suppress("-") + _singleChar)
+_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
+
+def srange(s):
+    r"""
+    Helper to easily define string ranges for use in Word construction.  Borrows
+    syntax from regexp '[]' string range definitions::
+        srange("[0-9]")   -> "0123456789"
+        srange("[a-z]")   -> "abcdefghijklmnopqrstuvwxyz"
+        srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
+    The input string must be enclosed in []'s, and the returned string is the expanded
+    character set joined into a single string.
+    The values enclosed in the []'s may be:
+     - a single character
+     - an escaped character with a leading backslash (such as C{\-} or C{\]})
+     - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) 
+         (C{\0x##} is also supported for backwards compatibility) 
+     - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character)
+     - a range of any of the above, separated by a dash (C{'a-z'}, etc.)
+     - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.)
+    """
+    _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1))
+    try:
+        return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body)
+    except Exception:
+        return ""
+
+def matchOnlyAtCol(n):
+    """
+    Helper method for defining parse actions that require matching at a specific
+    column in the input text.
+    """
+    def verifyCol(strg,locn,toks):
+        if col(locn,strg) != n:
+            raise ParseException(strg,locn,"matched token not at column %d" % n)
+    return verifyCol
+
+def replaceWith(replStr):
+    """
+    Helper method for common parse actions that simply return a literal value.  Especially
+    useful when used with C{L{transformString}()}.
+
+    Example::
+        num = Word(nums).setParseAction(lambda toks: int(toks[0]))
+        na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
+        term = na | num
+        
+        OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
+    """
+    return lambda s,l,t: [replStr]
+
+def removeQuotes(s,l,t):
+    """
+    Helper parse action for removing quotation marks from parsed quoted strings.
+
+    Example::
+        # by default, quotation marks are included in parsed results
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
+
+        # use removeQuotes to strip quotation marks from parsed results
+        quotedString.setParseAction(removeQuotes)
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
+    """
+    return t[0][1:-1]
+
+def tokenMap(func, *args):
+    """
+    Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional 
+    args are passed, they are forwarded to the given function as additional arguments after
+    the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the
+    parsed data to an integer using base 16.
+
+    Example (compare the last to example in L{ParserElement.transformString}::
+        hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
+        hex_ints.runTests('''
+            00 11 22 aa FF 0a 0d 1a
+            ''')
+        
+        upperword = Word(alphas).setParseAction(tokenMap(str.upper))
+        OneOrMore(upperword).runTests('''
+            my kingdom for a horse
+            ''')
+
+        wd = Word(alphas).setParseAction(tokenMap(str.title))
+        OneOrMore(wd).setParseAction(' '.join).runTests('''
+            now is the winter of our discontent made glorious summer by this sun of york
+            ''')
+    prints::
+        00 11 22 aa FF 0a 0d 1a
+        [0, 17, 34, 170, 255, 10, 13, 26]
+
+        my kingdom for a horse
+        ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
+
+        now is the winter of our discontent made glorious summer by this sun of york
+        ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
+    """
+    def pa(s,l,t):
+        return [func(tokn, *args) for tokn in t]
+
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    pa.__name__ = func_name
+
+    return pa
+
+upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
+"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}"""
+
+downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
+"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}"""
+    
+def _makeTags(tagStr, xml):
+    """Internal helper to construct opening and closing tag expressions, given a tag name"""
+    if isinstance(tagStr,basestring):
+        resname = tagStr
+        tagStr = Keyword(tagStr, caseless=not xml)
+    else:
+        resname = tagStr.name
+
+    tagAttrName = Word(alphas,alphanums+"_-:")
+    if (xml):
+        tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes )
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    else:
+        printablesLessRAbrack = "".join(c for c in printables if c not in ">")
+        tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack)
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \
+                Optional( Suppress("=") + tagAttrValue ) ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    closeTag = Combine(_L("")
+
+    openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname)
+    closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % resname)
+    openTag.tag = resname
+    closeTag.tag = resname
+    return openTag, closeTag
+
+def makeHTMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches
+    tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values.
+
+    Example::
+        text = 'More info at the pyparsing wiki page'
+        # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple
+        a,a_end = makeHTMLTags("A")
+        link_expr = a + SkipTo(a_end)("link_text") + a_end
+        
+        for link in link_expr.searchString(text):
+            # attributes in the  tag (like "href" shown here) are also accessible as named results
+            print(link.link_text, '->', link.href)
+    prints::
+        pyparsing -> http://pyparsing.wikispaces.com
+    """
+    return _makeTags( tagStr, False )
+
+def makeXMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for XML, given a tag name. Matches
+    tags only in the given upper/lower case.
+
+    Example: similar to L{makeHTMLTags}
+    """
+    return _makeTags( tagStr, True )
+
+def withAttribute(*args,**attrDict):
+    """
+    Helper to create a validating parse action to be used with start tags created
+    with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag
+    with a required attribute value, to avoid false matches on common tags such as
+    C{} or C{
}. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + If just testing for C{class} (with or without a namespace), use C{L{withClass}}. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this has no type
+
+ + ''' + div,div_end = makeHTMLTags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().setParseAction(withAttribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +def withClass(classname, namespace=''): + """ + Simplified version of C{L{withAttribute}} when matching on a div class - made + difficult because C{class} is a reserved word in Python. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this <div> has no class
+
+ + ''' + div,div_end = makeHTMLTags("div") + div_grid = div().setParseAction(withClass("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "%s:class" % namespace if namespace else "class" + return withAttribute(**{classattr : classname}) + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): + """ + Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. The generated parser will also recognize the use + of parentheses to override operator precedences (see example below). + + Note: if you define a deep operator list, you may see performance issues + when using infixNotation. See L{ParserElement.enablePackrat} for a + mechanism to potentially improve your parser performance. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) + - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) + - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) + + Example:: + # simple example of four-function arithmetic with ints and variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infixNotation(integer | varname, + [ + ('-', 1, opAssoc.RIGHT), + (oneOf('* /'), 2, opAssoc.LEFT), + (oneOf('+ -'), 2, opAssoc.LEFT), + ]) + + arith_expr.runTests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', fullDump=False) + prints:: + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + ret = Forward() + lastExpr = baseExpr | ( lpar + ret + rpar ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward().setName(termName) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) + thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + lastExpr = thisExpr + ret <<= lastExpr + return ret + +operatorPrecedence = infixNotation +"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" + +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """ + Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression + - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression + - content - expression for items within the nested lists (default=C{None}) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + + Example:: + data_type = oneOf("void int short long char float double") + decl_data_type = Combine(data_type + Optional(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR,RPAR = map(Suppress, "()") + + code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Optional(delimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(cStyleComment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.searchString(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + prints:: + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + ret.setName('nested %s%s expression' % (opener,closer)) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """ + Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=C{True}) + + A valid block must contain at least one C{blockStatement}. + + Example:: + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group( funcDecl + func_body ) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << ( funcDef | assignment | identifier ) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + prints:: + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.setName('indented block') + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +commonHTMLEntity = Regex('&(?P' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") +def replaceHTMLEntity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") +"Comment of the form C{/* ... */}" + +htmlComment = Regex(r"").setName("HTML comment") +"Comment of the form C{}" + +restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") +dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") +"Comment of the form C{// ... (to end of line)}" + +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" + +javaStyleComment = cppStyleComment +"Same as C{L{cppStyleComment}}" + +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +"Comment of the form C{# ... (to end of line)}" + +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. + This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """ + Here are some common low-level expressions that may be useful in jump-starting parser development: + - numeric forms (L{integers}, L{reals}, L{scientific notation}) + - common L{programming identifiers} + - network addresses (L{MAC}, L{IPv4}, L{IPv6}) + - ISO8601 L{dates} and L{datetime} + - L{UUID} + - L{comma-separated list} + Parse actions: + - C{L{convertToInteger}} + - C{L{convertToFloat}} + - C{L{convertToDate}} + - C{L{convertToDatetime}} + - C{L{stripHTMLTags}} + - C{L{upcaseTokens}} + - C{L{downcaseTokens}} + + Example:: + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + prints:: + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convertToInteger = tokenMap(int) + """ + Parse action for converting parsed integers to Python int + """ + + convertToFloat = tokenMap(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).setName("integer").setParseAction(convertToInteger) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.addParseAction(lambda t: t[0]/t[-1]) + + mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.addParseAction(sum) + + real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + """expression that parses a floating point number with optional scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) + """any int or real number, returned as float""" + + identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") + "IPv4 address (C{0.0.0.0 - 255.255.255.255})" + + _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") + ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convertToDate(fmt="%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) + + Example:: + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + prints:: + [datetime.date(1999, 12, 31)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt).date() + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + @staticmethod + def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): + """ + Helper to create a parse action for converting parsed datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) + + Example:: + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + prints:: + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + iso8601_date = Regex(r'(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?').setName("ISO8601 date") + "ISO8601 date (C{yyyy-mm-dd})" + + iso8601_datetime = Regex(r'(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") + "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" + + uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") + "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" + + _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() + @staticmethod + def stripHTMLTags(s, l, tokens): + """ + Parse action to remove HTML tags from web page HTML source + + Example:: + # strip HTML links from normal text + text = 'More info at the
pyparsing wiki page' + td,td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + + print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' + """ + return pyparsing_common._html_stripper.transformString(tokens[0]) + + _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') + + Optional( White(" \t") ) ) ).streamline().setName("commaItem") + comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) + """Parse action to convert tokens to upper case.""" + + downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) + """Parse action to convert tokens to lower case.""" + + +if __name__ == "__main__": + + selectToken = CaselessLiteral("select") + fromToken = CaselessLiteral("from") + + ident = Word(alphas, alphanums + "_$") + + columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + columnNameList = Group(delimitedList(columnName)).setName("columns") + columnSpec = ('*' | columnNameList) + + tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + tableNameList = Group(delimitedList(tableName)).setName("tables") + + simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") + + # demo runTests method, including embedded comments in test string + simpleSQL.runTests(""" + # '*' as column list and dotted table name + select * from SYS.XYZZY + + # caseless match on "SELECT", and casts back to "select" + SELECT * from XYZZY, ABC + + # list of column names, and mixed case SELECT keyword + Select AA,BB,CC from Sys.dual + + # multiple tables + Select A, B, C from Sys.dual, Table2 + + # invalid SELECT keyword - should fail + Xelect A, B, C from Sys.dual + + # incomplete command - should fail + Select + + # invalid column name - should fail + Select ^^^ frox Sys.dual + + """) + + pyparsing_common.number.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + # any int or real number, returned as float + pyparsing_common.fnumber.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + pyparsing_common.hex_integer.runTests(""" + 100 + FF + """) + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(""" + 12345678-1234-5678-1234-567812345678 + """) diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/extern/__init__.py b/.venv/lib/python3.8/site-packages/pkg_resources/extern/__init__.py new file mode 100644 index 0000000..fed5929 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/extern/__init__.py @@ -0,0 +1,73 @@ +import importlib.util +import sys + + +class VendorImporter: + """ + A PEP 302 meta path importer for finding optionally-vendored + or otherwise naturally-installed packages from root_name. + """ + + def __init__(self, root_name, vendored_names=(), vendor_pkg=None): + self.root_name = root_name + self.vendored_names = set(vendored_names) + self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') + + @property + def search_path(self): + """ + Search first the vendor package then as a natural package. + """ + yield self.vendor_pkg + '.' + yield '' + + def _module_matches_namespace(self, fullname): + """Figure out if the target module is vendored.""" + root, base, target = fullname.partition(self.root_name + '.') + return not root and any(map(target.startswith, self.vendored_names)) + + def load_module(self, fullname): + """ + Iterate over the search path to locate and load fullname. + """ + root, base, target = fullname.partition(self.root_name + '.') + for prefix in self.search_path: + try: + extant = prefix + target + __import__(extant) + mod = sys.modules[extant] + sys.modules[fullname] = mod + return mod + except ImportError: + pass + else: + raise ImportError( + "The '{target}' package is required; " + "normally this is bundled with this package so if you get " + "this warning, consult the packager of your " + "distribution.".format(**locals()) + ) + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + def find_spec(self, fullname, path=None, target=None): + """Return a module spec for vendored names.""" + return ( + importlib.util.spec_from_loader(fullname, self) + if self._module_matches_namespace(fullname) else None + ) + + def install(self): + """ + Install this importer into sys.meta_path if not already present. + """ + if self not in sys.meta_path: + sys.meta_path.append(self) + + +names = 'packaging', 'pyparsing', 'appdirs' +VendorImporter(__name__, names).install() diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ca0ee91 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/__pycache__/setup.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/__pycache__/setup.cpython-38.pyc new file mode 100644 index 0000000..7d48d80 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/__pycache__/setup.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py b/.venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py new file mode 100644 index 0000000..fe80d28 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py @@ -0,0 +1,6 @@ +import setuptools +setuptools.setup( + name="my-test-package", + version="1.0", + zip_safe=True, +) diff --git a/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/LICENSE.txt b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/LICENSE.txt new file mode 100644 index 0000000..f0bbd69 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/LICENSE.txt @@ -0,0 +1,22 @@ +# This is the MIT license + +Copyright (c) 2010 ActiveState Software Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/METADATA b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/METADATA new file mode 100644 index 0000000..02a5f5a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/METADATA @@ -0,0 +1,249 @@ +Metadata-Version: 2.1 +Name: platformdirs +Version: 2.5.1 +Summary: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". +Home-page: https://github.com/platformdirs/platformdirs +Maintainer: Bernát Gábor, Julian Berman, Ofek Lev, Ronny Pfannschmidt +Maintainer-email: gaborjbernat@gmail.com, Julian@GrayVines.com, oss@ofek.dev, opensource@ronnypfannschmidt.de +License: MIT +Project-URL: Source, https://github.com/platformdirs/platformdirs +Project-URL: Tracker, https://github.com/platformdirs/platformdirs/issues +Project-URL: Documentation, https://platformdirs.readthedocs.io/ +Keywords: application directory log cache user +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.txt +Provides-Extra: docs +Requires-Dist: Sphinx (>=4) ; extra == 'docs' +Requires-Dist: furo (>=2021.7.5b38) ; extra == 'docs' +Requires-Dist: proselint (>=0.10.2) ; extra == 'docs' +Requires-Dist: sphinx-autodoc-typehints (>=1.12) ; extra == 'docs' +Provides-Extra: test +Requires-Dist: appdirs (==1.4.4) ; extra == 'test' +Requires-Dist: pytest (>=6) ; extra == 'test' +Requires-Dist: pytest-cov (>=2.7) ; extra == 'test' +Requires-Dist: pytest-mock (>=3.6) ; extra == 'test' + +The problem +=========== + +.. image:: https://github.com/platformdirs/platformdirs/workflows/Test/badge.svg + :target: https://github.com/platformdirs/platformdirs/actions?query=workflow%3ATest + +When writing desktop application, finding the right location to store user data +and configuration varies per platform. Even for single-platform apps, there +may by plenty of nuances in figuring out the right location. + +For example, if running on macOS, you should use:: + + ~/Library/Application Support/ + +If on Windows (at least English Win XP) that should be:: + + C:\Documents and Settings\\Application Data\Local Settings\\ + +or possibly:: + + C:\Documents and Settings\\Application Data\\ + +for `roaming profiles `_ but that is another story. + +On Linux (and other Unices), according to the `XDG Basedir Spec`_, it should be:: + + ~/.local/share/ + +.. _XDG Basedir Spec: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + +``platformdirs`` to the rescue +============================== + +This kind of thing is what the ``platformdirs`` module is for. +``platformdirs`` will help you choose an appropriate: + +- user data dir (``user_data_dir``) +- user config dir (``user_config_dir``) +- user cache dir (``user_cache_dir``) +- site data dir (``site_data_dir``) +- site config dir (``site_config_dir``) +- user log dir (``user_log_dir``) +- user documents dir (``user_documents_dir``) +- user runtime dir (``user_runtime_dir``) + +And also: + +- Is a single module so other Python packages can vendor their own private copy. +- Is slightly opinionated on the directory names used. Look for "OPINION" in + documentation and code for when an opinion is being applied. + +Example output +============== + +On macOS: + +.. code-block:: pycon + + >>> from platformdirs import * + >>> appname = "SuperApp" + >>> appauthor = "Acme" + >>> user_data_dir(appname, appauthor) + '/Users/trentm/Library/Application Support/SuperApp' + >>> site_data_dir(appname, appauthor) + '/Library/Application Support/SuperApp' + >>> user_cache_dir(appname, appauthor) + '/Users/trentm/Library/Caches/SuperApp' + >>> user_log_dir(appname, appauthor) + '/Users/trentm/Library/Logs/SuperApp' + >>> user_documents_dir() + '/Users/trentm/Documents' + >>> user_runtime_dir(appname, appauthor) + '/Users/trentm/Library/Caches/TemporaryItems/SuperApp' + +On Windows 7: + +.. code-block:: pycon + + >>> from platformdirs import * + >>> appname = "SuperApp" + >>> appauthor = "Acme" + >>> user_data_dir(appname, appauthor) + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' + >>> user_data_dir(appname, appauthor, roaming=True) + 'C:\\Users\\trentm\\AppData\\Roaming\\Acme\\SuperApp' + >>> user_cache_dir(appname, appauthor) + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Cache' + >>> user_log_dir(appname, appauthor) + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Logs' + >>> user_documents_dir() + 'C:\\Users\\trentm\\Documents' + >>> user_runtime_dir(appname, appauthor) + 'C:\\Users\\trentm\\AppData\\Local\\Temp\\Acme\\SuperApp' + +On Linux: + +.. code-block:: pycon + + >>> from platformdirs import * + >>> appname = "SuperApp" + >>> appauthor = "Acme" + >>> user_data_dir(appname, appauthor) + '/home/trentm/.local/share/SuperApp' + >>> site_data_dir(appname, appauthor) + '/usr/local/share/SuperApp' + >>> site_data_dir(appname, appauthor, multipath=True) + '/usr/local/share/SuperApp:/usr/share/SuperApp' + >>> user_cache_dir(appname, appauthor) + '/home/trentm/.cache/SuperApp' + >>> user_log_dir(appname, appauthor) + '/home/trentm/.cache/SuperApp/log' + >>> user_config_dir(appname) + '/home/trentm/.config/SuperApp' + >>> user_documents_dir() + '/home/trentm/Documents' + >>> user_runtime_dir(appname, appauthor) + '/run/user/{os.getuid()}/SuperApp' + >>> site_config_dir(appname) + '/etc/xdg/SuperApp' + >>> os.environ["XDG_CONFIG_DIRS"] = "/etc:/usr/local/etc" + >>> site_config_dir(appname, multipath=True) + '/etc/SuperApp:/usr/local/etc/SuperApp' + +On Android:: + + >>> from platformdirs import * + >>> appname = "SuperApp" + >>> appauthor = "Acme" + >>> user_data_dir(appname, appauthor) + '/data/data/com.termux/files/SuperApp' + >>> user_cache_dir(appname, appauthor) + '/data/data/com.termux/cache/SuperApp' + >>> user_log_dir(appname, appauthor) + '/data/data/com.termux/cache/SuperApp/log' + >>> user_config_dir(appname) + '/data/data/com.termux/shared_prefs/SuperApp' + >>> user_documents_dir() + '/storage/emulated/0/Documents' + >>> user_runtime_dir(appname, appauthor) + '/data/data/com.termux/cache/SuperApp/tmp' + +``PlatformDirs`` for convenience +================================ + +.. code-block:: pycon + + >>> from platformdirs import PlatformDirs + >>> dirs = PlatformDirs("SuperApp", "Acme") + >>> dirs.user_data_dir + '/Users/trentm/Library/Application Support/SuperApp' + >>> dirs.site_data_dir + '/Library/Application Support/SuperApp' + >>> dirs.user_cache_dir + '/Users/trentm/Library/Caches/SuperApp' + >>> dirs.user_log_dir + '/Users/trentm/Library/Logs/SuperApp' + >>> dirs.user_documents_dir + '/Users/trentm/Documents' + >>> dirs.user_runtime_dir + '/Users/trentm/Library/Caches/TemporaryItems/SuperApp' + +Per-version isolation +===================== + +If you have multiple versions of your app in use that you want to be +able to run side-by-side, then you may want version-isolation for these +dirs:: + + >>> from platformdirs import PlatformDirs + >>> dirs = PlatformDirs("SuperApp", "Acme", version="1.0") + >>> dirs.user_data_dir + '/Users/trentm/Library/Application Support/SuperApp/1.0' + >>> dirs.site_data_dir + '/Library/Application Support/SuperApp/1.0' + >>> dirs.user_cache_dir + '/Users/trentm/Library/Caches/SuperApp/1.0' + >>> dirs.user_log_dir + '/Users/trentm/Library/Logs/SuperApp/1.0' + >>> dirs.user_documents_dir + '/Users/trentm/Documents' + >>> dirs.user_runtime_dir + '/Users/trentm/Library/Caches/TemporaryItems/SuperApp/1.0' + +Be wary of using this for configuration files though; you'll need to handle +migrating configuration files manually. + +Why this Fork? +============== + +This repository is a friendly fork of the wonderful work started by +`ActiveState `_ who created +``appdirs``, this package's ancestor. + +Maintaining an open source project is no easy task, particularly +from within an organization, and the Python community is indebted +to ``appdirs`` (and to Trent Mick and Jeff Rouse in particular) for +creating an incredibly useful simple module, as evidenced by the wide +number of users it has attracted over the years. + +Nonetheless, given the number of long-standing open issues +and pull requests, and no clear path towards `ensuring +that maintenance of the package would continue or grow +`_, this fork was +created. + +Contributions are most welcome. + + diff --git a/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/RECORD b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/RECORD new file mode 100644 index 0000000..2f51a03 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/RECORD @@ -0,0 +1,24 @@ +platformdirs-2.5.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +platformdirs-2.5.1.dist-info/LICENSE.txt,sha256=1l7lsqNqOR6Nau_OuArlAGMEml2xS8gk7g8eusmXDlA,1096 +platformdirs-2.5.1.dist-info/METADATA,sha256=MT26r5rQA2tJbP8D_wm-mtDZZeDx6vvUsD-DZODNHlg,9139 +platformdirs-2.5.1.dist-info/RECORD,, +platformdirs-2.5.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +platformdirs-2.5.1.dist-info/top_level.txt,sha256=i0Q-nUAcPabcNxrgdGr0mzOEavZ4cGml0zH0oxKEFQE,13 +platformdirs-2.5.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +platformdirs/__init__.py,sha256=n5barrjjLRd9At0RYY4pjKMb98bnpWBwwVc3ZOBkpuI,12688 +platformdirs/__main__.py,sha256=VsC0t5m-6f0YVr96PVks93G3EDF8MSNY4KpUMvPahDA,1164 +platformdirs/__pycache__/__init__.cpython-38.pyc,, +platformdirs/__pycache__/__main__.cpython-38.pyc,, +platformdirs/__pycache__/android.cpython-38.pyc,, +platformdirs/__pycache__/api.cpython-38.pyc,, +platformdirs/__pycache__/macos.cpython-38.pyc,, +platformdirs/__pycache__/unix.cpython-38.pyc,, +platformdirs/__pycache__/version.cpython-38.pyc,, +platformdirs/__pycache__/windows.cpython-38.pyc,, +platformdirs/android.py,sha256=GKizhyS7ESRiU67u8UnBJLm46goau9937EchXWbPBlk,4068 +platformdirs/api.py,sha256=MXKHXOL3eh_-trSok-JUTjAR_zjmmKF3rjREVABjP8s,4910 +platformdirs/macos.py,sha256=-3UXQewbT0yMhMdkzRXfXGAntmLIH7Qt4a9Hlf8I5_Y,2655 +platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +platformdirs/unix.py,sha256=b4aVYTz0qZ50HntwOXo8r6tp82jAa3qTjxw-WlnC2yc,6910 +platformdirs/version.py,sha256=vYD-L_Eg2ytekxltvPgbkHTNgLD8A56MZS7bJcEhjzI,80 +platformdirs/windows.py,sha256=ISruopR5UGBePC0BxCxXevkZYfjJsIZc49YWU5iYfQ4,6439 diff --git a/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/top_level.txt new file mode 100644 index 0000000..67fd014 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/top_level.txt @@ -0,0 +1 @@ +platformdirs diff --git a/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/zip-safe b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs-2.5.1.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__init__.py b/.venv/lib/python3.8/site-packages/platformdirs/__init__.py new file mode 100644 index 0000000..eb7d032 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/__init__.py @@ -0,0 +1,336 @@ +""" +Utilities for determining application-specific dirs. See for details and +usage. +""" +from __future__ import annotations + +import os +import sys +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing_extensions import Literal # pragma: no cover + +from .api import PlatformDirsABC +from .version import __version__, __version_info__ + + +def _set_platform_dir_class() -> type[PlatformDirsABC]: + if sys.platform == "win32": + from platformdirs.windows import Windows as Result + elif sys.platform == "darwin": + from platformdirs.macos import MacOS as Result + else: + from platformdirs.unix import Unix as Result + + if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system": + from platformdirs.android import _android_folder + + if _android_folder() is not None: + from platformdirs.android import Android + + return Android # return to avoid redefinition of result + + return Result + + +PlatformDirs = _set_platform_dir_class() #: Currently active platform +AppDirs = PlatformDirs #: Backwards compatibility with appdirs + + +def user_data_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: data directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_dir + + +def site_data_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + multipath: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `roaming `. + :returns: data directory shared by users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_dir + + +def user_config_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: config directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_dir + + +def site_config_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + multipath: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `roaming `. + :returns: config directory shared by the users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_dir + + +def user_cache_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: cache directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_dir + + +def user_state_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: state directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_dir + + +def user_log_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: log directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_dir + + +def user_documents_dir() -> str: + """ + :returns: documents directory tied to the user + """ + return PlatformDirs().user_documents_dir + + +def user_runtime_dir( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `opinion `. + :returns: runtime directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_dir + + +def user_data_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: data path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_path + + +def site_data_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + multipath: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `multipath `. + :returns: data path shared by users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_path + + +def user_config_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: config path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_path + + +def site_config_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + multipath: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param multipath: See `roaming `. + :returns: config path shared by the users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_path + + +def user_cache_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: cache path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_path + + +def user_state_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param roaming: See `roaming `. + :returns: state path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_path + + +def user_log_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `roaming `. + :returns: log path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_path + + +def user_documents_path() -> Path: + """ + :returns: documents path tied to the user + """ + return PlatformDirs().user_documents_path + + +def user_runtime_path( + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname `. + :param appauthor: See `appauthor `. + :param version: See `version `. + :param opinion: See `opinion `. + :returns: runtime path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_path + + +__all__ = [ + "__version__", + "__version_info__", + "PlatformDirs", + "AppDirs", + "PlatformDirsABC", + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "user_documents_dir", + "user_runtime_dir", + "site_data_dir", + "site_config_dir", + "user_data_path", + "user_config_path", + "user_cache_path", + "user_state_path", + "user_log_path", + "user_documents_path", + "user_runtime_path", + "site_data_path", + "site_config_path", +] diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__main__.py b/.venv/lib/python3.8/site-packages/platformdirs/__main__.py new file mode 100644 index 0000000..0fc1edd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/__main__.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from platformdirs import PlatformDirs, __version__ + +PROPS = ( + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "user_documents_dir", + "user_runtime_dir", + "site_data_dir", + "site_config_dir", +) + + +def main() -> None: + app_name = "MyApp" + app_author = "MyCompany" + + print(f"-- platformdirs {__version__} --") + + print("-- app dirs (with optional 'version')") + dirs = PlatformDirs(app_name, app_author, version="1.0") + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (without optional 'version')") + dirs = PlatformDirs(app_name, app_author) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (without optional 'appauthor')") + dirs = PlatformDirs(app_name) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = PlatformDirs(app_name, appauthor=False) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..6df91e6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..42dffbd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/android.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/android.cpython-38.pyc new file mode 100644 index 0000000..8c85385 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/android.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/api.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/api.cpython-38.pyc new file mode 100644 index 0000000..e6d65a4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/api.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/macos.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/macos.cpython-38.pyc new file mode 100644 index 0000000..9be4c1f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/macos.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/unix.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/unix.cpython-38.pyc new file mode 100644 index 0000000..4768c5a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/unix.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/version.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/version.cpython-38.pyc new file mode 100644 index 0000000..d711762 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/version.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/windows.cpython-38.pyc b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/windows.cpython-38.pyc new file mode 100644 index 0000000..7f7cd99 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/platformdirs/__pycache__/windows.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/platformdirs/android.py b/.venv/lib/python3.8/site-packages/platformdirs/android.py new file mode 100644 index 0000000..eda8093 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/android.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +import os +import re +import sys +from functools import lru_cache +from typing import cast + +from .api import PlatformDirsABC + + +class Android(PlatformDirsABC): + """ + Follows the guidance `from here `_. Makes use of the + `appname ` and + `version `. + """ + + @property + def user_data_dir(self) -> str: + """:return: data directory tied to the user, e.g. ``/data/user///files/``""" + return self._append_app_name_and_version(cast(str, _android_folder()), "files") + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_config_dir(self) -> str: + """ + :return: config directory tied to the user, e.g. ``/data/user///shared_prefs/`` + """ + return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs") + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, same as `user_config_dir`""" + return self.user_config_dir + + @property + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user, e.g. e.g. ``/data/user///cache/``""" + return self._append_app_name_and_version(cast(str, _android_folder()), "cache") + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it, + e.g. ``/data/user///cache//log`` + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "log") + return path + + @property + def user_documents_dir(self) -> str: + """ + :return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents`` + """ + return _android_documents_folder() + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it, + e.g. ``/data/user///cache//tmp`` + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "tmp") + return path + + +@lru_cache(maxsize=1) +def _android_folder() -> str | None: + """:return: base folder for the Android OS or None if cannot be found""" + try: + # First try to get path to android app via pyjnius + from jnius import autoclass + + Context = autoclass("android.content.Context") # noqa: N806 + result: str | None = Context.getFilesDir().getParentFile().getAbsolutePath() + except Exception: + # if fails find an android folder looking path on the sys.path + pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files") + for path in sys.path: + if pattern.match(path): + result = path.split("/files")[0] + break + else: + result = None + return result + + +@lru_cache(maxsize=1) +def _android_documents_folder() -> str: + """:return: documents folder for the Android OS""" + # Get directories with pyjnius + try: + from jnius import autoclass + + Context = autoclass("android.content.Context") # noqa: N806 + Environment = autoclass("android.os.Environment") # noqa: N806 + documents_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + except Exception: + documents_dir = "/storage/emulated/0/Documents" + + return documents_dir + + +__all__ = [ + "Android", +] diff --git a/.venv/lib/python3.8/site-packages/platformdirs/api.py b/.venv/lib/python3.8/site-packages/platformdirs/api.py new file mode 100644 index 0000000..6f6e2c2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/api.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +import os +import sys +from abc import ABC, abstractmethod +from pathlib import Path + +if sys.version_info >= (3, 8): # pragma: no branch + from typing import Literal # pragma: no cover + + +class PlatformDirsABC(ABC): + """ + Abstract base class for platform directories. + """ + + def __init__( + self, + appname: str | None = None, + appauthor: str | None | Literal[False] = None, + version: str | None = None, + roaming: bool = False, + multipath: bool = False, + opinion: bool = True, + ): + """ + Create a new platform directory. + + :param appname: See `appname`. + :param appauthor: See `appauthor`. + :param version: See `version`. + :param roaming: See `roaming`. + :param multipath: See `multipath`. + :param opinion: See `opinion`. + """ + self.appname = appname #: The name of application. + self.appauthor = appauthor + """ + The name of the app author or distributing body for this application. Typically, it is the owning company name. + Defaults to `appname`. You may pass ``False`` to disable it. + """ + self.version = version + """ + An optional version path element to append to the path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this would typically be ``.``. + """ + self.roaming = roaming + """ + Whether to use the roaming appdata directory on Windows. That means that for users on a Windows network setup + for roaming profiles, this user data will be synced on login (see + `here `_). + """ + self.multipath = multipath + """ + An optional parameter only applicable to Unix/Linux which indicates that the entire list of data dirs should be + returned. By default, the first item would only be returned. + """ + self.opinion = opinion #: A flag to indicating to use opinionated values. + + def _append_app_name_and_version(self, *base: str) -> str: + params = list(base[1:]) + if self.appname: + params.append(self.appname) + if self.version: + params.append(self.version) + return os.path.join(base[0], *params) + + @property + @abstractmethod + def user_data_dir(self) -> str: + """:return: data directory tied to the user""" + + @property + @abstractmethod + def site_data_dir(self) -> str: + """:return: data directory shared by users""" + + @property + @abstractmethod + def user_config_dir(self) -> str: + """:return: config directory tied to the user""" + + @property + @abstractmethod + def site_config_dir(self) -> str: + """:return: config directory shared by the users""" + + @property + @abstractmethod + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user""" + + @property + @abstractmethod + def user_state_dir(self) -> str: + """:return: state directory tied to the user""" + + @property + @abstractmethod + def user_log_dir(self) -> str: + """:return: log directory tied to the user""" + + @property + @abstractmethod + def user_documents_dir(self) -> str: + """:return: documents directory tied to the user""" + + @property + @abstractmethod + def user_runtime_dir(self) -> str: + """:return: runtime directory tied to the user""" + + @property + def user_data_path(self) -> Path: + """:return: data path tied to the user""" + return Path(self.user_data_dir) + + @property + def site_data_path(self) -> Path: + """:return: data path shared by users""" + return Path(self.site_data_dir) + + @property + def user_config_path(self) -> Path: + """:return: config path tied to the user""" + return Path(self.user_config_dir) + + @property + def site_config_path(self) -> Path: + """:return: config path shared by the users""" + return Path(self.site_config_dir) + + @property + def user_cache_path(self) -> Path: + """:return: cache path tied to the user""" + return Path(self.user_cache_dir) + + @property + def user_state_path(self) -> Path: + """:return: state path tied to the user""" + return Path(self.user_state_dir) + + @property + def user_log_path(self) -> Path: + """:return: log path tied to the user""" + return Path(self.user_log_dir) + + @property + def user_documents_path(self) -> Path: + """:return: documents path tied to the user""" + return Path(self.user_documents_dir) + + @property + def user_runtime_path(self) -> Path: + """:return: runtime path tied to the user""" + return Path(self.user_runtime_dir) diff --git a/.venv/lib/python3.8/site-packages/platformdirs/macos.py b/.venv/lib/python3.8/site-packages/platformdirs/macos.py new file mode 100644 index 0000000..a01337c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/macos.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import os + +from .api import PlatformDirsABC + + +class MacOS(PlatformDirsABC): + """ + Platform directories for the macOS operating system. Follows the guidance from `Apple documentation + `_. + Makes use of the `appname ` and + `version `. + """ + + @property + def user_data_dir(self) -> str: + """:return: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support/")) + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``""" + return self._append_app_name_and_version("/Library/Application Support") + + @property + def user_config_dir(self) -> str: + """:return: config directory tied to the user, e.g. ``~/Library/Preferences/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Preferences/")) + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, e.g. ``/Library/Preferences/$appname``""" + return self._append_app_name_and_version("/Library/Preferences") + + @property + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches")) + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """:return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs")) + + @property + def user_documents_dir(self) -> str: + """:return: documents directory tied to the user, e.g. ``~/Documents``""" + return os.path.expanduser("~/Documents") + + @property + def user_runtime_dir(self) -> str: + """:return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems")) + + +__all__ = [ + "MacOS", +] diff --git a/.venv/lib/python3.8/site-packages/platformdirs/py.typed b/.venv/lib/python3.8/site-packages/platformdirs/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/platformdirs/unix.py b/.venv/lib/python3.8/site-packages/platformdirs/unix.py new file mode 100644 index 0000000..2fbd4d4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/unix.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import os +import sys +from configparser import ConfigParser +from pathlib import Path + +from .api import PlatformDirsABC + +if sys.platform.startswith("linux"): # pragma: no branch # no op check, only to please the type checker + from os import getuid +else: + + def getuid() -> int: + raise RuntimeError("should only be used on Linux") + + +class Unix(PlatformDirsABC): + """ + On Unix/Linux, we follow the + `XDG Basedir Spec `_. The spec allows + overriding directories with environment variables. The examples show are the default values, alongside the name of + the environment variable that overrides them. Makes use of the + `appname `, + `version `, + `multipath `, + `opinion `. + """ + + @property + def user_data_dir(self) -> str: + """ + :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or + ``$XDG_DATA_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_DATA_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/share") + return self._append_app_name_and_version(path) + + @property + def site_data_dir(self) -> str: + """ + :return: data directories shared by users (if `multipath ` is + enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS + path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` + """ + # XDG default for $XDG_DATA_DIRS; only first, if multipath is False + path = os.environ.get("XDG_DATA_DIRS", "") + if not path.strip(): + path = f"/usr/local/share{os.pathsep}/usr/share" + return self._with_multi_path(path) + + def _with_multi_path(self, path: str) -> str: + path_list = path.split(os.pathsep) + if not self.multipath: + path_list = path_list[0:1] + path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list] + return os.pathsep.join(path_list) + + @property + def user_config_dir(self) -> str: + """ + :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or + ``$XDG_CONFIG_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_CONFIG_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.config") + return self._append_app_name_and_version(path) + + @property + def site_config_dir(self) -> str: + """ + :return: config directories shared by users (if `multipath ` + is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS + path separator), e.g. ``/etc/xdg/$appname/$version`` + """ + # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False + path = os.environ.get("XDG_CONFIG_DIRS", "") + if not path.strip(): + path = "/etc/xdg" + return self._with_multi_path(path) + + @property + def user_cache_dir(self) -> str: + """ + :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or + ``~/$XDG_CACHE_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_CACHE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.cache") + return self._append_app_name_and_version(path) + + @property + def user_state_dir(self) -> str: + """ + :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or + ``$XDG_STATE_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_STATE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/state") + return self._append_app_name_and_version(path) + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``log`` in it + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "log") + return path + + @property + def user_documents_dir(self) -> str: + """ + :return: documents directory tied to the user, e.g. ``~/Documents`` + """ + documents_dir = _get_user_dirs_folder("XDG_DOCUMENTS_DIR") + if documents_dir is None: + documents_dir = os.environ.get("XDG_DOCUMENTS_DIR", "").strip() + if not documents_dir: + documents_dir = os.path.expanduser("~/Documents") + + return documents_dir + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or + ``$XDG_RUNTIME_DIR/$appname/$version`` + """ + path = os.environ.get("XDG_RUNTIME_DIR", "") + if not path.strip(): + path = f"/run/user/{getuid()}" + return self._append_app_name_and_version(path) + + @property + def site_data_path(self) -> Path: + """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``""" + return self._first_item_as_path_if_multipath(self.site_data_dir) + + @property + def site_config_path(self) -> Path: + """:return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``""" + return self._first_item_as_path_if_multipath(self.site_config_dir) + + def _first_item_as_path_if_multipath(self, directory: str) -> Path: + if self.multipath: + # If multipath is True, the first path is returned. + directory = directory.split(os.pathsep)[0] + return Path(directory) + + +def _get_user_dirs_folder(key: str) -> str | None: + """Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/""" + user_dirs_config_path = os.path.join(Unix().user_config_dir, "user-dirs.dirs") + if os.path.exists(user_dirs_config_path): + parser = ConfigParser() + + with open(user_dirs_config_path) as stream: + # Add fake section header, so ConfigParser doesn't complain + parser.read_string(f"[top]\n{stream.read()}") + + if key not in parser["top"]: + return None + + path = parser["top"][key].strip('"') + # Handle relative home paths + path = path.replace("$HOME", os.path.expanduser("~")) + return path + + return None + + +__all__ = [ + "Unix", +] diff --git a/.venv/lib/python3.8/site-packages/platformdirs/version.py b/.venv/lib/python3.8/site-packages/platformdirs/version.py new file mode 100644 index 0000000..4be05f8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/version.py @@ -0,0 +1,4 @@ +""" Version information """ + +__version__ = "2.5.1" +__version_info__ = (2, 5, 1) diff --git a/.venv/lib/python3.8/site-packages/platformdirs/windows.py b/.venv/lib/python3.8/site-packages/platformdirs/windows.py new file mode 100644 index 0000000..ef972bd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/platformdirs/windows.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +import ctypes +import os +from functools import lru_cache +from typing import Callable + +from .api import PlatformDirsABC + + +class Windows(PlatformDirsABC): + """`MSDN on where to store app data files + `_. + Makes use of the + `appname `, + `appauthor `, + `version `, + `roaming `, + `opinion `.""" + + @property + def user_data_dir(self) -> str: + """ + :return: data directory tied to the user, e.g. + ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname`` (not roaming) or + ``%USERPROFILE%\\AppData\\Roaming\\$appauthor\\$appname`` (roaming) + """ + const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(get_win_folder(const)) + return self._append_parts(path) + + def _append_parts(self, path: str, *, opinion_value: str | None = None) -> str: + params = [] + if self.appname: + if self.appauthor is not False: + author = self.appauthor or self.appname + params.append(author) + params.append(self.appname) + if opinion_value is not None and self.opinion: + params.append(opinion_value) + if self.version: + params.append(self.version) + return os.path.join(path, *params) + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname``""" + path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA")) + return self._append_parts(path) + + @property + def user_config_dir(self) -> str: + """:return: config directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, same as `site_data_dir`""" + return self.site_data_dir + + @property + def user_cache_dir(self) -> str: + """ + :return: cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g. + ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname\\Cache\\$version`` + """ + path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA")) + return self._append_parts(path, opinion_value="Cache") + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it + """ + path = self.user_data_dir + if self.opinion: + path = os.path.join(path, "Logs") + return path + + @property + def user_documents_dir(self) -> str: + """ + :return: documents directory tied to the user e.g. ``%USERPROFILE%\\Documents`` + """ + return os.path.normpath(get_win_folder("CSIDL_PERSONAL")) + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, e.g. + ``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname`` + """ + path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp")) + return self._append_parts(path) + + +def get_win_folder_from_env_vars(csidl_name: str) -> str: + """Get folder from environment variables.""" + if csidl_name == "CSIDL_PERSONAL": # does not have an environment name + return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents") + + env_var_name = { + "CSIDL_APPDATA": "APPDATA", + "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE", + "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA", + }.get(csidl_name) + if env_var_name is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + result = os.environ.get(env_var_name) + if result is None: + raise ValueError(f"Unset environment variable: {env_var_name}") + return result + + +def get_win_folder_from_registry(csidl_name: str) -> str: + """Get folder from the registry. + + This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + "CSIDL_PERSONAL": "Personal", + }.get(csidl_name) + if shell_folder_name is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + + import winreg + + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") + directory, _ = winreg.QueryValueEx(key, shell_folder_name) + return str(directory) + + +def get_win_folder_via_ctypes(csidl_name: str) -> str: + """Get folder with ctypes.""" + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + "CSIDL_PERSONAL": 5, + }.get(csidl_name) + if csidl_const is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + + buf = ctypes.create_unicode_buffer(1024) + windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to avoid false positive with mypy type checker + windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if it has highbit chars. + if any(ord(c) > 255 for c in buf): + buf2 = ctypes.create_unicode_buffer(1024) + if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + + +def _pick_get_win_folder() -> Callable[[str], str]: + if hasattr(ctypes, "windll"): + return get_win_folder_via_ctypes + try: + import winreg # noqa: F401 + except ImportError: + return get_win_folder_from_env_vars + else: + return get_win_folder_from_registry + + +get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder()) + +__all__ = [ + "Windows", +] diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/LICENSE b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/LICENSE new file mode 100644 index 0000000..4a071fc --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 pre-commit dev team: Anthony Sottile, Ken Struys + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/METADATA b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/METADATA new file mode 100644 index 0000000..83ac388 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/METADATA @@ -0,0 +1,42 @@ +Metadata-Version: 2.1 +Name: pre-commit +Version: 2.17.0 +Summary: A framework for managing and maintaining multi-language pre-commit hooks. +Home-page: https://github.com/pre-commit/pre-commit +Author: Anthony Sottile +Author-email: asottile@umich.edu +License: MIT +Platform: UNKNOWN +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=3.6.1 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: cfgv (>=2.0.0) +Requires-Dist: identify (>=1.0.0) +Requires-Dist: nodeenv (>=0.11.1) +Requires-Dist: pyyaml (>=5.1) +Requires-Dist: toml +Requires-Dist: virtualenv (>=20.0.8) +Requires-Dist: importlib-resources (<5.3) ; python_version < "3.7" +Requires-Dist: importlib-metadata ; python_version < "3.8" + +[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/pre-commit.pre-commit?branchName=master)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/21/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=master) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit/master.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/master) + +## pre-commit + +A framework for managing and maintaining multi-language pre-commit hooks. + +For more information see: https://pre-commit.com/ + + diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/RECORD b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/RECORD new file mode 100644 index 0000000..32284ac --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/RECORD @@ -0,0 +1,149 @@ +../../../bin/pre-commit,sha256=r5vnfWR0uiILx3mc7L38_pMUkAh7E_rJzNcJytW9iR4,251 +../../../bin/pre-commit-validate-config,sha256=qaOiVDCxZUekZ8WGv6vmPQ6ESKzJFDPYCFPjArLRr64,288 +../../../bin/pre-commit-validate-manifest,sha256=mQh8KhgZSfsuQZJ-T5m2A_R2GDG6p6pxPM9pLExyHcs,292 +pre_commit-2.17.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pre_commit-2.17.0.dist-info/LICENSE,sha256=6iyifLp8w1gi2VpG1ZvNPMiOGWWS5jkNGUmjWf_JkOg,1092 +pre_commit-2.17.0.dist-info/METADATA,sha256=BK1_TIWdOdG4M22dX2GLTrb_mx1P0yMSvVVIq74AyqE,1945 +pre_commit-2.17.0.dist-info/RECORD,, +pre_commit-2.17.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pre_commit-2.17.0.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110 +pre_commit-2.17.0.dist-info/entry_points.txt,sha256=ZVtxB1oq0xody41RKm_fugEVJt3f5yxoTEdYXrMvsXw,199 +pre_commit-2.17.0.dist-info/top_level.txt,sha256=KMCc5TrDQcN3MCxwR72948Y2zIrILKmtnSAdqWBUa_I,11 +pre_commit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pre_commit/__main__.py,sha256=2Za352diu3FeRoU024ldSuE61gj0_HdR-90YHX_85a4,91 +pre_commit/__pycache__/__init__.cpython-38.pyc,, +pre_commit/__pycache__/__main__.cpython-38.pyc,, +pre_commit/__pycache__/clientlib.cpython-38.pyc,, +pre_commit/__pycache__/color.cpython-38.pyc,, +pre_commit/__pycache__/constants.cpython-38.pyc,, +pre_commit/__pycache__/envcontext.cpython-38.pyc,, +pre_commit/__pycache__/error_handler.cpython-38.pyc,, +pre_commit/__pycache__/errors.cpython-38.pyc,, +pre_commit/__pycache__/file_lock.cpython-38.pyc,, +pre_commit/__pycache__/git.cpython-38.pyc,, +pre_commit/__pycache__/hook.cpython-38.pyc,, +pre_commit/__pycache__/logging_handler.cpython-38.pyc,, +pre_commit/__pycache__/main.cpython-38.pyc,, +pre_commit/__pycache__/output.cpython-38.pyc,, +pre_commit/__pycache__/parse_shebang.cpython-38.pyc,, +pre_commit/__pycache__/prefix.cpython-38.pyc,, +pre_commit/__pycache__/repository.cpython-38.pyc,, +pre_commit/__pycache__/staged_files_only.cpython-38.pyc,, +pre_commit/__pycache__/store.cpython-38.pyc,, +pre_commit/__pycache__/util.cpython-38.pyc,, +pre_commit/__pycache__/xargs.cpython-38.pyc,, +pre_commit/clientlib.py,sha256=ZgQtZ-jRVcRNvroZgGmXiW0LnvbiRQvxw7-g9nb_qEA,13084 +pre_commit/color.py,sha256=j5Ta0H7Yz9aUX96DLjhA6Dy4tWZmgY6oXNm_Y_JjvG0,3183 +pre_commit/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pre_commit/commands/__pycache__/__init__.cpython-38.pyc,, +pre_commit/commands/__pycache__/autoupdate.cpython-38.pyc,, +pre_commit/commands/__pycache__/clean.cpython-38.pyc,, +pre_commit/commands/__pycache__/gc.cpython-38.pyc,, +pre_commit/commands/__pycache__/hook_impl.cpython-38.pyc,, +pre_commit/commands/__pycache__/init_templatedir.cpython-38.pyc,, +pre_commit/commands/__pycache__/install_uninstall.cpython-38.pyc,, +pre_commit/commands/__pycache__/migrate_config.cpython-38.pyc,, +pre_commit/commands/__pycache__/run.cpython-38.pyc,, +pre_commit/commands/__pycache__/sample_config.cpython-38.pyc,, +pre_commit/commands/__pycache__/try_repo.cpython-38.pyc,, +pre_commit/commands/autoupdate.py,sha256=9Di1wE7WOQxV9V7JIzoSZxetEMEst1b8FaHHwsuEk54,6185 +pre_commit/commands/clean.py,sha256=a5V5brwieLXAi0zdQYBAvVqKj0_M1NfDfr_UegVP8ik,393 +pre_commit/commands/gc.py,sha256=zK0W2OwdxxMIUi9_qvRT1Xq8Xkye73uVuDXnKCCNjH4,2840 +pre_commit/commands/hook_impl.py,sha256=kx2XIPtn4VKrTAJ18KrwxTcMieaH65ilhkD5aNrqI9U,7936 +pre_commit/commands/init_templatedir.py,sha256=RRjH9tIxwIpkTxxl587bVsWbGN5asAmtTIvI3uqu7cs,1124 +pre_commit/commands/install_uninstall.py,sha256=vqnkXpfpRHzdZ7W0lO_AwHNk7esgD_EUGdXA2FzoxoE,4986 +pre_commit/commands/migrate_config.py,sha256=773p4fgqePmt5ecWjbs1qpQkBZ5nFRInlH167EcaiNI,1536 +pre_commit/commands/run.py,sha256=xhUKN_UM1P9DkeN4VSz0xGrup1f9HoN5gC26gZZWFy4,13167 +pre_commit/commands/sample_config.py,sha256=K-B62-jf4i7t_Q97fOOBCtA9hnhwNBGP9JmwXr9MeDE,684 +pre_commit/commands/try_repo.py,sha256=XyX2qtW_fQrFOXt-_DjUH-ft3qnW9TLY1x5Fb4qg7jk,2596 +pre_commit/constants.py,sha256=V1iC3A0F6bqRxkrgwDbxPh0ll3eN4voNce-nhmsmEvc,733 +pre_commit/envcontext.py,sha256=kB4UyX3dgSgmHlwC0u7QJhpK6FzpQF8WiT0OfAA_NpY,1607 +pre_commit/error_handler.py,sha256=uQr5GYjv_H-eP60woRmB8fgpYw0Q-kLK0VAvTIBSgic,2556 +pre_commit/errors.py,sha256=lMci5-VfOsvVSIt2oshqdo2iM9KJHY_iqnh_4OiS2sA,41 +pre_commit/file_lock.py,sha256=SNpDgwLLBbAofiqi0plzvT4X_cMT2cUONWDe1EpH4EA,2536 +pre_commit/git.py,sha256=Qn2HqVhQy06-Wk3UTLRZjs9oPHDK6e8svusX7oNNrDY,8152 +pre_commit/hook.py,sha256=7lQ6S4BM3QpkPdPEJ1dSkvA-2dM436dfmoFC35_upA0,1639 +pre_commit/languages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pre_commit/languages/__pycache__/__init__.cpython-38.pyc,, +pre_commit/languages/__pycache__/all.cpython-38.pyc,, +pre_commit/languages/__pycache__/conda.cpython-38.pyc,, +pre_commit/languages/__pycache__/coursier.cpython-38.pyc,, +pre_commit/languages/__pycache__/dart.cpython-38.pyc,, +pre_commit/languages/__pycache__/docker.cpython-38.pyc,, +pre_commit/languages/__pycache__/docker_image.cpython-38.pyc,, +pre_commit/languages/__pycache__/dotnet.cpython-38.pyc,, +pre_commit/languages/__pycache__/fail.cpython-38.pyc,, +pre_commit/languages/__pycache__/golang.cpython-38.pyc,, +pre_commit/languages/__pycache__/helpers.cpython-38.pyc,, +pre_commit/languages/__pycache__/lua.cpython-38.pyc,, +pre_commit/languages/__pycache__/node.cpython-38.pyc,, +pre_commit/languages/__pycache__/perl.cpython-38.pyc,, +pre_commit/languages/__pycache__/pygrep.cpython-38.pyc,, +pre_commit/languages/__pycache__/python.cpython-38.pyc,, +pre_commit/languages/__pycache__/r.cpython-38.pyc,, +pre_commit/languages/__pycache__/ruby.cpython-38.pyc,, +pre_commit/languages/__pycache__/rust.cpython-38.pyc,, +pre_commit/languages/__pycache__/script.cpython-38.pyc,, +pre_commit/languages/__pycache__/swift.cpython-38.pyc,, +pre_commit/languages/__pycache__/system.cpython-38.pyc,, +pre_commit/languages/all.py,sha256=JBfy7hpzpXEVkOOf36lgzqMdRLIQyV8tCZezd-puBLk,6309 +pre_commit/languages/conda.py,sha256=2Tsr2nBsIACQgNWZyLVPB4yBoCRIgMgBuZ9LhKkYZPg,3084 +pre_commit/languages/coursier.py,sha256=fpilrvRagomBA0zjecH9MfZJMJX-HR0jSPUSCz8ZVZg,2146 +pre_commit/languages/dart.py,sha256=aZ1phCcjHGvW-Cav5YVaH6EHTJOfpgf5MX7rfcofBEw,3565 +pre_commit/languages/docker.py,sha256=EfX_O5iGVYZBqYWcTK-sVKYP-DzUzOw36nTVD2YknZQ,4552 +pre_commit/languages/docker_image.py,sha256=NcWvbjN02IJiaLuDELRQmAn_ZUS8CsUDFxndiy50_7E,571 +pre_commit/languages/dotnet.py,sha256=SP8-p-nNoQZH3471UzHhl3DkHJnQ4cKGJ6YPU4f6XkQ,2690 +pre_commit/languages/fail.py,sha256=9DtzrZGN8I7Yp-_CQ8G-t4Hdjx-CG_HAyW4qmD_vg-I,514 +pre_commit/languages/golang.py,sha256=MtqTDJQNV-XFjvs4mUysbnXddRXvmf97a1U0Lp0N6v0,3191 +pre_commit/languages/helpers.py,sha256=IPgbEnOnAKJ4Jp7wX15gT8Y8KLa9tzLAt3HC4pk1DHU,3635 +pre_commit/languages/lua.py,sha256=FGxY9LrVVBjCJ9pyZvt-N1zq3JP21HtKaVdutUaQO68,2953 +pre_commit/languages/node.py,sha256=-K7A5VxYkiI7HORN-WhRpfbHpCrb_J704cOJ_Dsk_kA,4310 +pre_commit/languages/perl.py,sha256=3XBj_vd2PwRmfFm6H0azI-LbSTRsGw0ebDsfUmG_z30,1964 +pre_commit/languages/pygrep.py,sha256=raxSJU3TIvDLfkM-RmbORFl_urXTJn4xfRtvC_yPW-M,3696 +pre_commit/languages/python.py,sha256=5zsHCm8iXtcN1n6rJrPTrBQpDwOxdzty1wIaKK_J4kQ,6574 +pre_commit/languages/r.py,sha256=eLRXDQjWJdfk4knV-Gs1_Ik0lZ2nkUDyPsB2ms2Sy1k,4485 +pre_commit/languages/ruby.py,sha256=q0l5rBX34lB0BOiAolVjetRC2VE8ogMurQkxllZfTCE,4853 +pre_commit/languages/rust.py,sha256=6gzwTZYbJ6xmiwXUE9brNJBIc9K3UUGN_FbIxv6HFE4,3324 +pre_commit/languages/script.py,sha256=5h_pSMuqbdrrCe13iyuwlQyiF1S4uNQgWdUGsWNUq5A,517 +pre_commit/languages/swift.py,sha256=Tr74uarY2H8JnVao7SJ-L1ASs6I7m8dG-TB-Lsk4Ni0,1995 +pre_commit/languages/system.py,sha256=8krWA1SgyefweWxzrFU8JsZcKdvO0mPM28Xf8dcHkd4,466 +pre_commit/logging_handler.py,sha256=vwqwLYjdbIVmNZTfqy5JNuMOQeLMBP4iY-Qd4aDYMJA,986 +pre_commit/main.py,sha256=XOap5CQaDONjCRzLX0qGA_B0cljolbHfBM5KfoKqONA,14606 +pre_commit/meta_hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pre_commit/meta_hooks/__pycache__/__init__.cpython-38.pyc,, +pre_commit/meta_hooks/__pycache__/check_hooks_apply.cpython-38.pyc,, +pre_commit/meta_hooks/__pycache__/check_useless_excludes.cpython-38.pyc,, +pre_commit/meta_hooks/__pycache__/identity.cpython-38.pyc,, +pre_commit/meta_hooks/check_hooks_apply.py,sha256=Cyxpt8nj0wjBESAbCqDpTrA3Y1z_MKWdXqBqIxzaWXc,1188 +pre_commit/meta_hooks/check_useless_excludes.py,sha256=yTAjMEm2OIQjeGkca_AOVEZ8ZV3fXwLbVP4GCHfhMEg,2549 +pre_commit/meta_hooks/identity.py,sha256=h9TBQBFZGzbsuS8OddBHqPjNTYIQZGe3S2Y6xuQ9eFs,332 +pre_commit/output.py,sha256=UKnjO5W7Py4ra5FXZHwDyw6_Bav0OMj4v5NdjGvMLw0,912 +pre_commit/parse_shebang.py,sha256=vXShsJLOMQyCC_pfsIVOay9owtutccu50txSduTByiU,2429 +pre_commit/prefix.py,sha256=esDqQ0ndFbnJpxZjTtzW2OwxF5g3FvwUiNZTf-FW5so,484 +pre_commit/repository.py,sha256=XpndS4e5h4wozBlTXqaZUGmZhUzMU3OrqyyBt8oyyzU,7700 +pre_commit/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pre_commit/resources/__pycache__/__init__.cpython-38.pyc,, +pre_commit/resources/__pycache__/empty_template_setup.cpython-38.pyc,, +pre_commit/resources/empty_template_.npmignore,sha256=zbyuFRBda3geYggTx5x-hodA1OnMU85vX8u8Ejh630s,2 +pre_commit/resources/empty_template_Cargo.toml,sha256=rjclCuspy_DoGro5K_Yx-cjKO88rvQU5qkkbAYIiatg,96 +pre_commit/resources/empty_template_LICENSE.renv,sha256=vJiU4spBSQJkgro6DzPpoTeFvYYGMdkKEhZQQh54scE,1052 +pre_commit/resources/empty_template_Makefile.PL,sha256=oNZjJpS1Ba38XJ4VmQArf7ZfaSFbD4vDFNZ8ERoAy2A,104 +pre_commit/resources/empty_template_activate.R,sha256=ydOQGgFscRyMU4bIqm1TzGQN8Dxfgc79eOXujP7uVjE,12037 +pre_commit/resources/empty_template_environment.yml,sha256=SBs7AN3M19I6EX_lrIDhFavgAaBRubazR6imzTbhBCI,302 +pre_commit/resources/empty_template_go.mod,sha256=35v54fbcB8Hs9TOPi6uHNiu9GTtuYs4M3ofWXpdG3j8,43 +pre_commit/resources/empty_template_main.go,sha256=VaYLuXFRsrS2gEYkR85g7DRRGxT6ENd0QMl7l3cQFWY,29 +pre_commit/resources/empty_template_main.rs,sha256=U25Qa7kJFMJDoSs5e5qZj4WuLL2boC39A6nhVcpcoPQ,13 +pre_commit/resources/empty_template_package.json,sha256=iBN43GUUn05DYKA7vpOcdU27H-nTsr6aVFCJVq4G3-E,73 +pre_commit/resources/empty_template_pre-commit-package-dev-1.rockspec,sha256=Jd8cePFPSzN0YO5T7vZLAVTX_KrXQiSNu5uFQXmETEU,212 +pre_commit/resources/empty_template_pre_commit_placeholder_package.gemspec,sha256=UCWGrtzSq_bUTvDgJuV94ZPQ6KHrGbFywWyppbR8ZIQ,195 +pre_commit/resources/empty_template_pubspec.yaml,sha256=cSn_jSXaYL5L2N9UtZblUumra-pehiTuJ-lwJwGxPQU,78 +pre_commit/resources/empty_template_renv.lock,sha256=Oa6eSaJs4lZn8A4qo1OkEvDC0H_g-2VrmzFuATmMhuM,351 +pre_commit/resources/empty_template_setup.py,sha256=yAcMxdYfxYLOEj6h1xcPC0MHX-LzTs7Eq7-sjL59aWo,93 +pre_commit/resources/hook-tmpl,sha256=OJybNgJvZYMlvJbUZkzzdGDlxBXVMFmiIb0DYCP3b_U,528 +pre_commit/resources/rbenv.tar.gz,sha256=w9_kJbnEJvuY0R_br0VC-pxpr-3xyDRR0UjuW67iX-A,32551 +pre_commit/resources/ruby-build.tar.gz,sha256=asgSWq5FRXVtHoAZyE2lmHM0FqlAMMlG9thk-tWZ-Bg,71151 +pre_commit/resources/ruby-download.tar.gz,sha256=n058b-WeiYtCeAU-I6TMlQbioI9Pw6_qsG3KbDxlCTg,5271 +pre_commit/staged_files_only.py,sha256=IbsK28pri_hrupMeAkeRbPsfgRebqJdoX19TuaBRrhg,3540 +pre_commit/store.py,sha256=dGaSiHzpDbWZtaiEp93X3Zphpda2X_9aU-XzJKE6pP0,9776 +pre_commit/util.py,sha256=bao4JGGdjUdfPM9N0zpPLP7d6ixO33yX8mK5RI0OdRA,8165 +pre_commit/xargs.py,sha256=hyNWjR1k_E7ydlu21qakP2XO4XqFRmtDW1jqhqP0QhU,5109 diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/REQUESTED b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/WHEEL new file mode 100644 index 0000000..0b18a28 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/entry_points.txt b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/entry_points.txt new file mode 100644 index 0000000..973a92a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +pre-commit = pre_commit.main:main +pre-commit-validate-config = pre_commit.clientlib:validate_config_main +pre-commit-validate-manifest = pre_commit.clientlib:validate_manifest_main + diff --git a/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/top_level.txt new file mode 100644 index 0000000..493444d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit-2.17.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pre_commit diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__init__.py b/.venv/lib/python3.8/site-packages/pre_commit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__main__.py b/.venv/lib/python3.8/site-packages/pre_commit/__main__.py new file mode 100644 index 0000000..83bd93c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/__main__.py @@ -0,0 +1,5 @@ +from pre_commit.main import main + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..6033bbb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..7ff5175 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/clientlib.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/clientlib.cpython-38.pyc new file mode 100644 index 0000000..729209f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/clientlib.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/color.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/color.cpython-38.pyc new file mode 100644 index 0000000..aa38e8b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/color.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/constants.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/constants.cpython-38.pyc new file mode 100644 index 0000000..db12c28 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/constants.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/envcontext.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/envcontext.cpython-38.pyc new file mode 100644 index 0000000..4b7f65c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/envcontext.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/error_handler.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/error_handler.cpython-38.pyc new file mode 100644 index 0000000..d0b86f7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/error_handler.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/errors.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/errors.cpython-38.pyc new file mode 100644 index 0000000..9af5b1a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/errors.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/file_lock.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/file_lock.cpython-38.pyc new file mode 100644 index 0000000..69632dd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/file_lock.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/git.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/git.cpython-38.pyc new file mode 100644 index 0000000..fb4a578 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/git.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/hook.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/hook.cpython-38.pyc new file mode 100644 index 0000000..717f57b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/hook.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/logging_handler.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/logging_handler.cpython-38.pyc new file mode 100644 index 0000000..9425285 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/logging_handler.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/main.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/main.cpython-38.pyc new file mode 100644 index 0000000..dedddd5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/main.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/output.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/output.cpython-38.pyc new file mode 100644 index 0000000..2a51c56 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/output.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/parse_shebang.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/parse_shebang.cpython-38.pyc new file mode 100644 index 0000000..b018059 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/parse_shebang.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/prefix.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/prefix.cpython-38.pyc new file mode 100644 index 0000000..0d6a876 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/prefix.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/repository.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/repository.cpython-38.pyc new file mode 100644 index 0000000..fd31a9b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/repository.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/staged_files_only.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/staged_files_only.cpython-38.pyc new file mode 100644 index 0000000..a90e7da Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/staged_files_only.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/store.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/store.cpython-38.pyc new file mode 100644 index 0000000..02aaf19 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/store.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/util.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/util.cpython-38.pyc new file mode 100644 index 0000000..535ee82 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/util.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/xargs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/xargs.cpython-38.pyc new file mode 100644 index 0000000..72584a1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/__pycache__/xargs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/clientlib.py b/.venv/lib/python3.8/site-packages/pre_commit/clientlib.py new file mode 100644 index 0000000..47ebd54 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/clientlib.py @@ -0,0 +1,413 @@ +import argparse +import functools +import logging +import re +import shlex +import sys +from typing import Any +from typing import Dict +from typing import Optional +from typing import Sequence + +import cfgv +from identify.identify import ALL_TAGS + +import pre_commit.constants as C +from pre_commit.color import add_color_option +from pre_commit.errors import FatalError +from pre_commit.languages.all import all_languages +from pre_commit.logging_handler import logging_handler +from pre_commit.util import parse_version +from pre_commit.util import yaml_load + +logger = logging.getLogger('pre_commit') + +check_string_regex = cfgv.check_and(cfgv.check_string, cfgv.check_regex) + + +def check_type_tag(tag: str) -> None: + if tag not in ALL_TAGS: + raise cfgv.ValidationError( + f'Type tag {tag!r} is not recognized. ' + f'Try upgrading identify and pre-commit?', + ) + + +def check_min_version(version: str) -> None: + if parse_version(version) > parse_version(C.VERSION): + raise cfgv.ValidationError( + f'pre-commit version {version} is required but version ' + f'{C.VERSION} is installed. ' + f'Perhaps run `pip install --upgrade pre-commit`.', + ) + + +def _make_argparser(filenames_help: str) -> argparse.ArgumentParser: + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*', help=filenames_help) + parser.add_argument('-V', '--version', action='version', version=C.VERSION) + add_color_option(parser) + return parser + + +MANIFEST_HOOK_DICT = cfgv.Map( + 'Hook', 'id', + + cfgv.Required('id', cfgv.check_string), + cfgv.Required('name', cfgv.check_string), + cfgv.Required('entry', cfgv.check_string), + cfgv.Required('language', cfgv.check_one_of(all_languages)), + cfgv.Optional('alias', cfgv.check_string, ''), + + cfgv.Optional('files', check_string_regex, ''), + cfgv.Optional('exclude', check_string_regex, '^$'), + cfgv.Optional('types', cfgv.check_array(check_type_tag), ['file']), + cfgv.Optional('types_or', cfgv.check_array(check_type_tag), []), + cfgv.Optional('exclude_types', cfgv.check_array(check_type_tag), []), + + cfgv.Optional( + 'additional_dependencies', cfgv.check_array(cfgv.check_string), [], + ), + cfgv.Optional('args', cfgv.check_array(cfgv.check_string), []), + cfgv.Optional('always_run', cfgv.check_bool, False), + cfgv.Optional('fail_fast', cfgv.check_bool, False), + cfgv.Optional('pass_filenames', cfgv.check_bool, True), + cfgv.Optional('description', cfgv.check_string, ''), + cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT), + cfgv.Optional('log_file', cfgv.check_string, ''), + cfgv.Optional('minimum_pre_commit_version', cfgv.check_string, '0'), + cfgv.Optional('require_serial', cfgv.check_bool, False), + cfgv.Optional('stages', cfgv.check_array(cfgv.check_one_of(C.STAGES)), []), + cfgv.Optional('verbose', cfgv.check_bool, False), +) +MANIFEST_SCHEMA = cfgv.Array(MANIFEST_HOOK_DICT) + + +class InvalidManifestError(FatalError): + pass + + +load_manifest = functools.partial( + cfgv.load_from_filename, + schema=MANIFEST_SCHEMA, + load_strategy=yaml_load, + exc_tp=InvalidManifestError, +) + + +def validate_manifest_main(argv: Optional[Sequence[str]] = None) -> int: + parser = _make_argparser('Manifest filenames.') + args = parser.parse_args(argv) + + with logging_handler(args.color): + ret = 0 + for filename in args.filenames: + try: + load_manifest(filename) + except InvalidManifestError as e: + print(e) + ret = 1 + return ret + + +LOCAL = 'local' +META = 'meta' + + +# should inherit from cfgv.Conditional if sha support is dropped +class WarnMutableRev(cfgv.ConditionalOptional): + def check(self, dct: Dict[str, Any]) -> None: + super().check(dct) + + if self.key in dct: + rev = dct[self.key] + + if '.' not in rev and not re.match(r'^[a-fA-F0-9]+$', rev): + logger.warning( + f'The {self.key!r} field of repo {dct["repo"]!r} ' + f'appears to be a mutable reference ' + f'(moving tag / branch). Mutable references are never ' + f'updated after first install and are not supported. ' + f'See https://pre-commit.com/#using-the-latest-version-for-a-repository ' # noqa: E501 + f'for more details. ' + f'Hint: `pre-commit autoupdate` often fixes this.', + ) + + +class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault): + def check(self, dct: Dict[str, Any]) -> None: + super().check(dct) + + if '/*' in dct.get(self.key, ''): + logger.warning( + f'The {self.key!r} field in hook {dct.get("id")!r} is a ' + f"regex, not a glob -- matching '/*' probably isn't what you " + f'want here', + ) + for fwd_slash_re in (r'[\\/]', r'[\/]', r'[/\\]'): + if fwd_slash_re in dct.get(self.key, ''): + logger.warning( + fr'pre-commit normalizes slashes in the {self.key!r} ' + fr'field in hook {dct.get("id")!r} to forward slashes, ' + fr'so you can use / instead of {fwd_slash_re}', + ) + + +class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault): + def check(self, dct: Dict[str, Any]) -> None: + super().check(dct) + + if '/*' in dct.get(self.key, ''): + logger.warning( + f'The top-level {self.key!r} field is a regex, not a glob -- ' + f"matching '/*' probably isn't what you want here", + ) + for fwd_slash_re in (r'[\\/]', r'[\/]', r'[/\\]'): + if fwd_slash_re in dct.get(self.key, ''): + logger.warning( + fr'pre-commit normalizes the slashes in the top-level ' + fr'{self.key!r} field to forward slashes, so you ' + fr'can use / instead of {fwd_slash_re}', + ) + + +class MigrateShaToRev: + key = 'rev' + + @staticmethod + def _cond(key: str) -> cfgv.Conditional: + return cfgv.Conditional( + key, cfgv.check_string, + condition_key='repo', + condition_value=cfgv.NotIn(LOCAL, META), + ensure_absent=True, + ) + + def check(self, dct: Dict[str, Any]) -> None: + if dct.get('repo') in {LOCAL, META}: + self._cond('rev').check(dct) + self._cond('sha').check(dct) + elif 'sha' in dct and 'rev' in dct: + raise cfgv.ValidationError('Cannot specify both sha and rev') + elif 'sha' in dct: + self._cond('sha').check(dct) + else: + self._cond('rev').check(dct) + + def apply_default(self, dct: Dict[str, Any]) -> None: + if 'sha' in dct: + dct['rev'] = dct.pop('sha') + + remove_default = cfgv.Required.remove_default + + +def _entry(modname: str) -> str: + """the hook `entry` is passed through `shlex.split()` by the command + runner, so to prevent issues with spaces and backslashes (on Windows) + it must be quoted here. + """ + return f'{shlex.quote(sys.executable)} -m pre_commit.meta_hooks.{modname}' + + +def warn_unknown_keys_root( + extra: Sequence[str], + orig_keys: Sequence[str], + dct: Dict[str, str], +) -> None: + logger.warning(f'Unexpected key(s) present at root: {", ".join(extra)}') + + +def warn_unknown_keys_repo( + extra: Sequence[str], + orig_keys: Sequence[str], + dct: Dict[str, str], +) -> None: + logger.warning( + f'Unexpected key(s) present on {dct["repo"]}: {", ".join(extra)}', + ) + + +_meta = ( + ( + 'check-hooks-apply', ( + ('name', 'Check hooks apply to the repository'), + ('files', f'^{re.escape(C.CONFIG_FILE)}$'), + ('entry', _entry('check_hooks_apply')), + ), + ), + ( + 'check-useless-excludes', ( + ('name', 'Check for useless excludes'), + ('files', f'^{re.escape(C.CONFIG_FILE)}$'), + ('entry', _entry('check_useless_excludes')), + ), + ), + ( + 'identity', ( + ('name', 'identity'), + ('verbose', True), + ('entry', _entry('identity')), + ), + ), +) + + +class NotAllowed(cfgv.OptionalNoDefault): + def check(self, dct: Dict[str, Any]) -> None: + if self.key in dct: + raise cfgv.ValidationError(f'{self.key!r} cannot be overridden') + + +META_HOOK_DICT = cfgv.Map( + 'Hook', 'id', + cfgv.Required('id', cfgv.check_string), + cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))), + # language must be system + cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'), + # entry cannot be overridden + NotAllowed('entry', cfgv.check_any), + *( + # default to the hook definition for the meta hooks + cfgv.ConditionalOptional(key, cfgv.check_any, value, 'id', hook_id) + for hook_id, values in _meta + for key, value in values + ), + *( + # default to the "manifest" parsing + cfgv.OptionalNoDefault(item.key, item.check_fn) + # these will always be defaulted above + if item.key in {'name', 'language', 'entry'} else + item + for item in MANIFEST_HOOK_DICT.items + ), +) +CONFIG_HOOK_DICT = cfgv.Map( + 'Hook', 'id', + + cfgv.Required('id', cfgv.check_string), + + # All keys in manifest hook dict are valid in a config hook dict, but + # are optional. + # No defaults are provided here as the config is merged on top of the + # manifest. + *( + cfgv.OptionalNoDefault(item.key, item.check_fn) + for item in MANIFEST_HOOK_DICT.items + if item.key != 'id' + ), + OptionalSensibleRegexAtHook('files', cfgv.check_string), + OptionalSensibleRegexAtHook('exclude', cfgv.check_string), +) +CONFIG_REPO_DICT = cfgv.Map( + 'Repository', 'repo', + + cfgv.Required('repo', cfgv.check_string), + + cfgv.ConditionalRecurse( + 'hooks', cfgv.Array(CONFIG_HOOK_DICT), + 'repo', cfgv.NotIn(LOCAL, META), + ), + cfgv.ConditionalRecurse( + 'hooks', cfgv.Array(MANIFEST_HOOK_DICT), + 'repo', LOCAL, + ), + cfgv.ConditionalRecurse( + 'hooks', cfgv.Array(META_HOOK_DICT), + 'repo', META, + ), + + MigrateShaToRev(), + WarnMutableRev( + 'rev', + cfgv.check_string, + '', + 'repo', + cfgv.NotIn(LOCAL, META), + True, + ), + cfgv.WarnAdditionalKeys(('repo', 'rev', 'hooks'), warn_unknown_keys_repo), +) +DEFAULT_LANGUAGE_VERSION = cfgv.Map( + 'DefaultLanguageVersion', None, + cfgv.NoAdditionalKeys(all_languages), + *(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in all_languages), +) +CONFIG_SCHEMA = cfgv.Map( + 'Config', None, + + cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)), + cfgv.OptionalRecurse( + 'default_language_version', DEFAULT_LANGUAGE_VERSION, {}, + ), + cfgv.Optional( + 'default_stages', + cfgv.check_array(cfgv.check_one_of(C.STAGES)), + C.STAGES, + ), + cfgv.Optional('files', check_string_regex, ''), + cfgv.Optional('exclude', check_string_regex, '^$'), + cfgv.Optional('fail_fast', cfgv.check_bool, False), + cfgv.Optional( + 'minimum_pre_commit_version', + cfgv.check_and(cfgv.check_string, check_min_version), + '0', + ), + cfgv.WarnAdditionalKeys( + ( + 'repos', + 'default_language_version', + 'default_stages', + 'files', + 'exclude', + 'fail_fast', + 'minimum_pre_commit_version', + 'ci', + ), + warn_unknown_keys_root, + ), + OptionalSensibleRegexAtTop('files', cfgv.check_string), + OptionalSensibleRegexAtTop('exclude', cfgv.check_string), + + # do not warn about configuration for pre-commit.ci + cfgv.OptionalNoDefault('ci', cfgv.check_type(dict)), +) + + +class InvalidConfigError(FatalError): + pass + + +def ordered_load_normalize_legacy_config(contents: str) -> Dict[str, Any]: + data = yaml_load(contents) + if isinstance(data, list): + logger.warning( + 'normalizing pre-commit configuration to a top-level map. ' + 'support for top level list will be removed in a future version. ' + 'run: `pre-commit migrate-config` to automatically fix this.', + ) + return {'repos': data} + else: + return data + + +load_config = functools.partial( + cfgv.load_from_filename, + schema=CONFIG_SCHEMA, + load_strategy=ordered_load_normalize_legacy_config, + exc_tp=InvalidConfigError, +) + + +def validate_config_main(argv: Optional[Sequence[str]] = None) -> int: + parser = _make_argparser('Config filenames.') + args = parser.parse_args(argv) + + with logging_handler(args.color): + ret = 0 + for filename in args.filenames: + try: + load_config(filename) + except InvalidConfigError as e: + print(e) + ret = 1 + return ret diff --git a/.venv/lib/python3.8/site-packages/pre_commit/color.py b/.venv/lib/python3.8/site-packages/pre_commit/color.py new file mode 100644 index 0000000..4ddfdf5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/color.py @@ -0,0 +1,107 @@ +import argparse +import os +import sys + +if sys.platform == 'win32': # pragma: no cover (windows) + def _enable() -> None: + from ctypes import POINTER + from ctypes import windll + from ctypes import WinError + from ctypes import WINFUNCTYPE + from ctypes.wintypes import BOOL + from ctypes.wintypes import DWORD + from ctypes.wintypes import HANDLE + + STD_ERROR_HANDLE = -12 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + + def bool_errcheck(result, func, args): + if not result: + raise WinError() + return args + + GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)( + ('GetStdHandle', windll.kernel32), ((1, 'nStdHandle'),), + ) + + GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))( + ('GetConsoleMode', windll.kernel32), + ((1, 'hConsoleHandle'), (2, 'lpMode')), + ) + GetConsoleMode.errcheck = bool_errcheck + + SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)( + ('SetConsoleMode', windll.kernel32), + ((1, 'hConsoleHandle'), (1, 'dwMode')), + ) + SetConsoleMode.errcheck = bool_errcheck + + # As of Windows 10, the Windows console supports (some) ANSI escape + # sequences, but it needs to be enabled using `SetConsoleMode` first. + # + # More info on the escape sequences supported: + # https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx + stderr = GetStdHandle(STD_ERROR_HANDLE) + flags = GetConsoleMode(stderr) + SetConsoleMode(stderr, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + + try: + _enable() + except OSError: + terminal_supports_color = False + else: + terminal_supports_color = True +else: # pragma: win32 no cover + terminal_supports_color = True + +RED = '\033[41m' +GREEN = '\033[42m' +YELLOW = '\033[43;30m' +TURQUOISE = '\033[46;30m' +SUBTLE = '\033[2m' +NORMAL = '\033[m' + + +def format_color(text: str, color: str, use_color_setting: bool) -> str: + """Format text with color. + + Args: + text - Text to be formatted with color if `use_color` + color - The color start string + use_color_setting - Whether or not to color + """ + if use_color_setting: + return f'{color}{text}{NORMAL}' + else: + return text + + +COLOR_CHOICES = ('auto', 'always', 'never') + + +def use_color(setting: str) -> bool: + """Choose whether to use color based on the command argument. + + Args: + setting - Either `auto`, `always`, or `never` + """ + if setting not in COLOR_CHOICES: + raise ValueError(setting) + + return ( + setting == 'always' or ( + setting == 'auto' and + sys.stderr.isatty() and + terminal_supports_color and + os.getenv('TERM') != 'dumb' + ) + ) + + +def add_color_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '--color', default=os.environ.get('PRE_COMMIT_COLOR', 'auto'), + type=use_color, + metavar='{' + ','.join(COLOR_CHOICES) + '}', + help='Whether to use color in output. Defaults to `%(default)s`.', + ) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__init__.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..7dca152 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/autoupdate.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/autoupdate.cpython-38.pyc new file mode 100644 index 0000000..ef3caf7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/autoupdate.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/clean.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/clean.cpython-38.pyc new file mode 100644 index 0000000..5a34492 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/clean.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/gc.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/gc.cpython-38.pyc new file mode 100644 index 0000000..3dab9f5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/gc.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/hook_impl.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/hook_impl.cpython-38.pyc new file mode 100644 index 0000000..131e39a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/hook_impl.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/init_templatedir.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/init_templatedir.cpython-38.pyc new file mode 100644 index 0000000..90d51e7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/init_templatedir.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/install_uninstall.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/install_uninstall.cpython-38.pyc new file mode 100644 index 0000000..0373d6d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/install_uninstall.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/migrate_config.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/migrate_config.cpython-38.pyc new file mode 100644 index 0000000..f1a80ec Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/migrate_config.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/run.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/run.cpython-38.pyc new file mode 100644 index 0000000..ec82d37 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/run.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/sample_config.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/sample_config.cpython-38.pyc new file mode 100644 index 0000000..a52e290 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/sample_config.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/try_repo.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/try_repo.cpython-38.pyc new file mode 100644 index 0000000..aa7b6c0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/commands/__pycache__/try_repo.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/autoupdate.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/autoupdate.py new file mode 100644 index 0000000..5cb978e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/autoupdate.py @@ -0,0 +1,194 @@ +import os.path +import re +from typing import Any +from typing import Dict +from typing import List +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit import git +from pre_commit import output +from pre_commit.clientlib import InvalidManifestError +from pre_commit.clientlib import load_config +from pre_commit.clientlib import load_manifest +from pre_commit.clientlib import LOCAL +from pre_commit.clientlib import META +from pre_commit.commands.migrate_config import migrate_config +from pre_commit.store import Store +from pre_commit.util import CalledProcessError +from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b +from pre_commit.util import tmpdir +from pre_commit.util import yaml_dump +from pre_commit.util import yaml_load + + +class RevInfo(NamedTuple): + repo: str + rev: str + frozen: Optional[str] + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> 'RevInfo': + return cls(config['repo'], config['rev'], None) + + def update(self, tags_only: bool, freeze: bool) -> 'RevInfo': + git_cmd = ('git', *git.NO_FS_MONITOR) + + if tags_only: + tag_cmd = ( + *git_cmd, 'describe', + 'FETCH_HEAD', '--tags', '--abbrev=0', + ) + else: + tag_cmd = ( + *git_cmd, 'describe', + 'FETCH_HEAD', '--tags', '--exact', + ) + + with tmpdir() as tmp: + git.init_repo(tmp, self.repo) + cmd_output_b( + *git_cmd, 'fetch', 'origin', 'HEAD', '--tags', + cwd=tmp, + ) + + try: + rev = cmd_output(*tag_cmd, cwd=tmp)[1].strip() + except CalledProcessError: + cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD') + rev = cmd_output(*cmd, cwd=tmp)[1].strip() + + frozen = None + if freeze: + exact_rev_cmd = (*git_cmd, 'rev-parse', rev) + exact = cmd_output(*exact_rev_cmd, cwd=tmp)[1].strip() + if exact != rev: + rev, frozen = exact, rev + return self._replace(rev=rev, frozen=frozen) + + +class RepositoryCannotBeUpdatedError(RuntimeError): + pass + + +def _check_hooks_still_exist_at_rev( + repo_config: Dict[str, Any], + info: RevInfo, + store: Store, +) -> None: + try: + path = store.clone(repo_config['repo'], info.rev) + manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE)) + except InvalidManifestError as e: + raise RepositoryCannotBeUpdatedError(str(e)) + + # See if any of our hooks were deleted with the new commits + hooks = {hook['id'] for hook in repo_config['hooks']} + hooks_missing = hooks - {hook['id'] for hook in manifest} + if hooks_missing: + raise RepositoryCannotBeUpdatedError( + f'Cannot update because the update target is missing these ' + f'hooks:\n{", ".join(sorted(hooks_missing))}', + ) + + +REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$') + + +def _original_lines( + path: str, + rev_infos: List[Optional[RevInfo]], + retry: bool = False, +) -> Tuple[List[str], List[int]]: + """detect `rev:` lines or reformat the file""" + with open(path, newline='') as f: + original = f.read() + + lines = original.splitlines(True) + idxs = [i for i, line in enumerate(lines) if REV_LINE_RE.match(line)] + if len(idxs) == len(rev_infos): + return lines, idxs + elif retry: + raise AssertionError('could not find rev lines') + else: + with open(path, 'w') as f: + f.write(yaml_dump(yaml_load(original))) + return _original_lines(path, rev_infos, retry=True) + + +def _write_new_config(path: str, rev_infos: List[Optional[RevInfo]]) -> None: + lines, idxs = _original_lines(path, rev_infos) + + for idx, rev_info in zip(idxs, rev_infos): + if rev_info is None: + continue + match = REV_LINE_RE.match(lines[idx]) + assert match is not None + new_rev_s = yaml_dump({'rev': rev_info.rev}, default_style=match[3]) + new_rev = new_rev_s.split(':', 1)[1].strip() + if rev_info.frozen is not None: + comment = f' # frozen: {rev_info.frozen}' + elif match[5].strip().startswith('# frozen:'): + comment = '' + else: + comment = match[5] + lines[idx] = f'{match[1]}rev:{match[2]}{new_rev}{comment}{match[6]}' + + with open(path, 'w', newline='') as f: + f.write(''.join(lines)) + + +def autoupdate( + config_file: str, + store: Store, + tags_only: bool, + freeze: bool, + repos: Sequence[str] = (), +) -> int: + """Auto-update the pre-commit config to the latest versions of repos.""" + migrate_config(config_file, quiet=True) + retv = 0 + rev_infos: List[Optional[RevInfo]] = [] + changed = False + + config = load_config(config_file) + for repo_config in config['repos']: + if repo_config['repo'] in {LOCAL, META}: + continue + + info = RevInfo.from_config(repo_config) + if repos and info.repo not in repos: + rev_infos.append(None) + continue + + output.write(f'Updating {info.repo} ... ') + new_info = info.update(tags_only=tags_only, freeze=freeze) + try: + _check_hooks_still_exist_at_rev(repo_config, new_info, store) + except RepositoryCannotBeUpdatedError as error: + output.write_line(error.args[0]) + rev_infos.append(None) + retv = 1 + continue + + if new_info.rev != info.rev: + changed = True + if new_info.frozen: + updated_to = f'{new_info.frozen} (frozen)' + else: + updated_to = new_info.rev + msg = f'updating {info.rev} -> {updated_to}.' + output.write_line(msg) + rev_infos.append(new_info) + else: + output.write_line('already up to date.') + rev_infos.append(None) + + if changed: + _write_new_config(config_file, rev_infos) + + return retv diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/clean.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/clean.py new file mode 100644 index 0000000..2be6c16 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/clean.py @@ -0,0 +1,14 @@ +import os.path + +from pre_commit import output +from pre_commit.store import Store +from pre_commit.util import rmtree + + +def clean(store: Store) -> int: + legacy_path = os.path.expanduser('~/.pre-commit') + for directory in (store.directory, legacy_path): + if os.path.exists(directory): + rmtree(directory) + output.write_line(f'Cleaned {directory}.') + return 0 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/gc.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/gc.py new file mode 100644 index 0000000..7f6d311 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/gc.py @@ -0,0 +1,90 @@ +import os.path +from typing import Any +from typing import Dict +from typing import Set +from typing import Tuple + +import pre_commit.constants as C +from pre_commit import output +from pre_commit.clientlib import InvalidConfigError +from pre_commit.clientlib import InvalidManifestError +from pre_commit.clientlib import load_config +from pre_commit.clientlib import load_manifest +from pre_commit.clientlib import LOCAL +from pre_commit.clientlib import META +from pre_commit.store import Store + + +def _mark_used_repos( + store: Store, + all_repos: Dict[Tuple[str, str], str], + unused_repos: Set[Tuple[str, str]], + repo: Dict[str, Any], +) -> None: + if repo['repo'] == META: + return + elif repo['repo'] == LOCAL: + for hook in repo['hooks']: + deps = hook.get('additional_dependencies') + unused_repos.discard(( + store.db_repo_name(repo['repo'], deps), C.LOCAL_REPO_VERSION, + )) + else: + key = (repo['repo'], repo['rev']) + path = all_repos.get(key) + # can't inspect manifest if it isn't cloned + if path is None: + return + + try: + manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE)) + except InvalidManifestError: + return + else: + unused_repos.discard(key) + by_id = {hook['id']: hook for hook in manifest} + + for hook in repo['hooks']: + if hook['id'] not in by_id: + continue + + deps = hook.get( + 'additional_dependencies', + by_id[hook['id']]['additional_dependencies'], + ) + unused_repos.discard(( + store.db_repo_name(repo['repo'], deps), repo['rev'], + )) + + +def _gc_repos(store: Store) -> int: + configs = store.select_all_configs() + repos = store.select_all_repos() + + # delete config paths which do not exist + dead_configs = [p for p in configs if not os.path.exists(p)] + live_configs = [p for p in configs if os.path.exists(p)] + + all_repos = {(repo, ref): path for repo, ref, path in repos} + unused_repos = set(all_repos) + for config_path in live_configs: + try: + config = load_config(config_path) + except InvalidConfigError: + dead_configs.append(config_path) + continue + else: + for repo in config['repos']: + _mark_used_repos(store, all_repos, unused_repos, repo) + + store.delete_configs(dead_configs) + for db_repo_name, ref in unused_repos: + store.delete_repo(db_repo_name, ref, all_repos[(db_repo_name, ref)]) + return len(unused_repos) + + +def gc(store: Store) -> int: + with store.exclusive_lock(): + repos_removed = _gc_repos(store) + output.write_line(f'{repos_removed} repo(s) removed.') + return 0 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py new file mode 100644 index 0000000..90bb33b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py @@ -0,0 +1,237 @@ +import argparse +import os.path +import subprocess +import sys +from typing import Optional +from typing import Sequence +from typing import Tuple + +from pre_commit.commands.run import run +from pre_commit.envcontext import envcontext +from pre_commit.parse_shebang import normalize_cmd +from pre_commit.store import Store + +Z40 = '0' * 40 + + +def _run_legacy( + hook_type: str, + hook_dir: str, + args: Sequence[str], +) -> Tuple[int, bytes]: + if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'): + raise SystemExit( + f"bug: pre-commit's script is installed in migration mode\n" + f'run `pre-commit install -f --hook-type {hook_type}` to fix ' + f'this\n\n' + f'Please report this bug at ' + f'https://github.com/pre-commit/pre-commit/issues', + ) + + if hook_type == 'pre-push': + stdin = sys.stdin.buffer.read() + else: + stdin = b'' + + # not running in legacy mode + legacy_hook = os.path.join(hook_dir, f'{hook_type}.legacy') + if not os.access(legacy_hook, os.X_OK): + return 0, stdin + + with envcontext((('PRE_COMMIT_RUNNING_LEGACY', '1'),)): + cmd = normalize_cmd((legacy_hook, *args)) + return subprocess.run(cmd, input=stdin).returncode, stdin + + +def _validate_config( + retv: int, + config: str, + skip_on_missing_config: bool, +) -> None: + if not os.path.isfile(config): + if skip_on_missing_config or os.getenv('PRE_COMMIT_ALLOW_NO_CONFIG'): + print(f'`{config}` config file not found. Skipping `pre-commit`.') + raise SystemExit(retv) + else: + print( + f'No {config} file was found\n' + f'- To temporarily silence this, run ' + f'`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n' + f'- To permanently silence this, install pre-commit with the ' + f'--allow-missing-config option\n' + f'- To uninstall pre-commit run `pre-commit uninstall`', + ) + raise SystemExit(1) + + +def _ns( + hook_type: str, + color: bool, + *, + all_files: bool = False, + remote_branch: Optional[str] = None, + local_branch: Optional[str] = None, + from_ref: Optional[str] = None, + to_ref: Optional[str] = None, + remote_name: Optional[str] = None, + remote_url: Optional[str] = None, + commit_msg_filename: Optional[str] = None, + checkout_type: Optional[str] = None, + is_squash_merge: Optional[str] = None, + rewrite_command: Optional[str] = None, +) -> argparse.Namespace: + return argparse.Namespace( + color=color, + hook_stage=hook_type.replace('pre-', ''), + remote_branch=remote_branch, + local_branch=local_branch, + from_ref=from_ref, + to_ref=to_ref, + remote_name=remote_name, + remote_url=remote_url, + commit_msg_filename=commit_msg_filename, + all_files=all_files, + checkout_type=checkout_type, + is_squash_merge=is_squash_merge, + rewrite_command=rewrite_command, + files=(), + hook=None, + verbose=False, + show_diff_on_failure=False, + ) + + +def _rev_exists(rev: str) -> bool: + return not subprocess.call(('git', 'rev-list', '--quiet', rev)) + + +def _pre_push_ns( + color: bool, + args: Sequence[str], + stdin: bytes, +) -> Optional[argparse.Namespace]: + remote_name = args[0] + remote_url = args[1] + + for line in stdin.decode().splitlines(): + local_branch, local_sha, remote_branch, remote_sha = line.split() + if local_sha == Z40: + continue + elif remote_sha != Z40 and _rev_exists(remote_sha): + return _ns( + 'pre-push', color, + from_ref=remote_sha, to_ref=local_sha, + remote_branch=remote_branch, + local_branch=local_branch, + remote_name=remote_name, remote_url=remote_url, + ) + else: + # ancestors not found in remote + ancestors = subprocess.check_output(( + 'git', 'rev-list', local_sha, '--topo-order', '--reverse', + '--not', f'--remotes={remote_name}', + )).decode().strip() + if not ancestors: + continue + else: + first_ancestor = ancestors.splitlines()[0] + cmd = ('git', 'rev-list', '--max-parents=0', local_sha) + roots = set(subprocess.check_output(cmd).decode().splitlines()) + if first_ancestor in roots: + # pushing the whole tree including root commit + return _ns( + 'pre-push', color, + all_files=True, + remote_name=remote_name, remote_url=remote_url, + remote_branch=remote_branch, + local_branch=local_branch, + ) + else: + rev_cmd = ('git', 'rev-parse', f'{first_ancestor}^') + source = subprocess.check_output(rev_cmd).decode().strip() + return _ns( + 'pre-push', color, + from_ref=source, to_ref=local_sha, + remote_name=remote_name, remote_url=remote_url, + remote_branch=remote_branch, + local_branch=local_branch, + ) + + # nothing to push + return None + + +_EXPECTED_ARG_LENGTH_BY_HOOK = { + 'commit-msg': 1, + 'post-checkout': 3, + 'post-commit': 0, + 'pre-commit': 0, + 'pre-merge-commit': 0, + 'post-merge': 1, + 'post-rewrite': 1, + 'pre-push': 2, +} + + +def _check_args_length(hook_type: str, args: Sequence[str]) -> None: + if hook_type == 'prepare-commit-msg': + if len(args) < 1 or len(args) > 3: + raise SystemExit( + f'hook-impl for {hook_type} expected 1, 2, or 3 arguments ' + f'but got {len(args)}: {args}', + ) + elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK: + expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type] + if len(args) != expected: + arguments_s = 'argument' if expected == 1 else 'arguments' + raise SystemExit( + f'hook-impl for {hook_type} expected {expected} {arguments_s} ' + f'but got {len(args)}: {args}', + ) + else: + raise AssertionError(f'unexpected hook type: {hook_type}') + + +def _run_ns( + hook_type: str, + color: bool, + args: Sequence[str], + stdin: bytes, +) -> Optional[argparse.Namespace]: + _check_args_length(hook_type, args) + if hook_type == 'pre-push': + return _pre_push_ns(color, args, stdin) + elif hook_type in {'commit-msg', 'prepare-commit-msg'}: + return _ns(hook_type, color, commit_msg_filename=args[0]) + elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}: + return _ns(hook_type, color) + elif hook_type == 'post-checkout': + return _ns( + hook_type, color, + from_ref=args[0], to_ref=args[1], checkout_type=args[2], + ) + elif hook_type == 'post-merge': + return _ns(hook_type, color, is_squash_merge=args[0]) + elif hook_type == 'post-rewrite': + return _ns(hook_type, color, rewrite_command=args[0]) + else: + raise AssertionError(f'unexpected hook type: {hook_type}') + + +def hook_impl( + store: Store, + *, + config: str, + color: bool, + hook_type: str, + hook_dir: str, + skip_on_missing_config: bool, + args: Sequence[str], +) -> int: + retv, stdin = _run_legacy(hook_type, hook_dir, args) + _validate_config(retv, config, skip_on_missing_config) + ns = _run_ns(hook_type, color, args, stdin) + if ns is None: + return retv + else: + return retv | run(config, store, ns) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/init_templatedir.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/init_templatedir.py new file mode 100644 index 0000000..5f17d9c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/init_templatedir.py @@ -0,0 +1,38 @@ +import logging +import os.path +from typing import Sequence + +from pre_commit.commands.install_uninstall import install +from pre_commit.store import Store +from pre_commit.util import CalledProcessError +from pre_commit.util import cmd_output + +logger = logging.getLogger('pre_commit') + + +def init_templatedir( + config_file: str, + store: Store, + directory: str, + hook_types: Sequence[str], + skip_on_missing_config: bool = True, +) -> int: + install( + config_file, + store, + hook_types=hook_types, + overwrite=True, + skip_on_missing_config=skip_on_missing_config, + git_dir=directory, + ) + try: + _, out, _ = cmd_output('git', 'config', 'init.templateDir') + except CalledProcessError: + configured_path = None + else: + configured_path = os.path.realpath(os.path.expanduser(out.strip())) + dest = os.path.realpath(directory) + if configured_path != dest: + logger.warning('`init.templateDir` not set to the target directory') + logger.warning(f'maybe `git config --global init.templateDir {dest}`?') + return 0 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/install_uninstall.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/install_uninstall.py new file mode 100644 index 0000000..50c6443 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/install_uninstall.py @@ -0,0 +1,156 @@ +import logging +import os.path +import shlex +import shutil +import sys +from typing import Optional +from typing import Sequence +from typing import Tuple + +from pre_commit import git +from pre_commit import output +from pre_commit.clientlib import load_config +from pre_commit.repository import all_hooks +from pre_commit.repository import install_hook_envs +from pre_commit.store import Store +from pre_commit.util import make_executable +from pre_commit.util import resource_text + + +logger = logging.getLogger(__name__) + +# This is used to identify the hook file we install +PRIOR_HASHES = ( + b'4d9958c90bc262f47553e2c073f14cfe', + b'd8ee923c46731b42cd95cc869add4062', + b'49fd668cb42069aa1b6048464be5d395', + b'79f09a650522a87b0da915d0d983b2de', + b'e358c9dae00eac5d06b38dfdb1e33a8c', +) +CURRENT_HASH = b'138fd403232d2ddd5efb44317e38bf03' +TEMPLATE_START = '# start templated\n' +TEMPLATE_END = '# end templated\n' + + +def _hook_paths( + hook_type: str, + git_dir: Optional[str] = None, +) -> Tuple[str, str]: + git_dir = git_dir if git_dir is not None else git.get_git_dir() + pth = os.path.join(git_dir, 'hooks', hook_type) + return pth, f'{pth}.legacy' + + +def is_our_script(filename: str) -> bool: + if not os.path.exists(filename): # pragma: win32 no cover (symlink) + return False + with open(filename, 'rb') as f: + contents = f.read() + return any(h in contents for h in (CURRENT_HASH,) + PRIOR_HASHES) + + +def _install_hook_script( + config_file: str, + hook_type: str, + overwrite: bool = False, + skip_on_missing_config: bool = False, + git_dir: Optional[str] = None, +) -> None: + hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir) + + os.makedirs(os.path.dirname(hook_path), exist_ok=True) + + # If we have an existing hook, move it to pre-commit.legacy + if os.path.lexists(hook_path) and not is_our_script(hook_path): + shutil.move(hook_path, legacy_path) + + # If we specify overwrite, we simply delete the legacy file + if overwrite and os.path.exists(legacy_path): + os.remove(legacy_path) + elif os.path.exists(legacy_path): + output.write_line( + f'Running in migration mode with existing hooks at {legacy_path}\n' + f'Use -f to use only pre-commit.', + ) + + args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}'] + if skip_on_missing_config: + args.append('--skip-on-missing-config') + + with open(hook_path, 'w') as hook_file: + contents = resource_text('hook-tmpl') + before, rest = contents.split(TEMPLATE_START) + _, after = rest.split(TEMPLATE_END) + + # on windows always use `/bin/sh` since `bash` might not be on PATH + # though we use bash-specific features `sh` on windows is actually + # bash in "POSIXLY_CORRECT" mode which still supports the features we + # use: subshells / arrays + if sys.platform == 'win32': # pragma: win32 cover + hook_file.write('#!/bin/sh\n') + + hook_file.write(before + TEMPLATE_START) + hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n') + # TODO: python3.8+: shlex.join + args_s = ' '.join(shlex.quote(part) for part in args) + hook_file.write(f'ARGS=({args_s})\n') + hook_file.write(TEMPLATE_END + after) + make_executable(hook_path) + + output.write_line(f'pre-commit installed at {hook_path}') + + +def install( + config_file: str, + store: Store, + hook_types: Sequence[str], + overwrite: bool = False, + hooks: bool = False, + skip_on_missing_config: bool = False, + git_dir: Optional[str] = None, +) -> int: + if git_dir is None and git.has_core_hookpaths_set(): + logger.error( + 'Cowardly refusing to install hooks with `core.hooksPath` set.\n' + 'hint: `git config --unset-all core.hooksPath`', + ) + return 1 + + for hook_type in hook_types: + _install_hook_script( + config_file, hook_type, + overwrite=overwrite, + skip_on_missing_config=skip_on_missing_config, + git_dir=git_dir, + ) + + if hooks: + install_hooks(config_file, store) + + return 0 + + +def install_hooks(config_file: str, store: Store) -> int: + install_hook_envs(all_hooks(load_config(config_file), store), store) + return 0 + + +def _uninstall_hook_script(hook_type: str) -> None: + hook_path, legacy_path = _hook_paths(hook_type) + + # If our file doesn't exist or it isn't ours, gtfo. + if not os.path.exists(hook_path) or not is_our_script(hook_path): + return + + os.remove(hook_path) + output.write_line(f'{hook_type} uninstalled') + + if os.path.exists(legacy_path): + os.replace(legacy_path, hook_path) + output.write_line(f'Restored previous hooks to {hook_path}') + + +def uninstall(hook_types: Sequence[str]) -> int: + for hook_type in hook_types: + _uninstall_hook_script(hook_type) + return 0 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/migrate_config.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/migrate_config.py new file mode 100644 index 0000000..fef14cd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/migrate_config.py @@ -0,0 +1,55 @@ +import re +import textwrap + +import yaml + +from pre_commit.util import yaml_load + + +def _is_header_line(line: str) -> bool: + return line.startswith(('#', '---')) or not line.strip() + + +def _migrate_map(contents: str) -> str: + if isinstance(yaml_load(contents), list): + # Find the first non-header line + lines = contents.splitlines(True) + i = 0 + # Only loop on non empty configuration file + while i < len(lines) and _is_header_line(lines[i]): + i += 1 + + header = ''.join(lines[:i]) + rest = ''.join(lines[i:]) + + # If they are using the "default" flow style of yaml, this operation + # will yield a valid configuration + try: + trial_contents = f'{header}repos:\n{rest}' + yaml_load(trial_contents) + contents = trial_contents + except yaml.YAMLError: + contents = f'{header}repos:\n{textwrap.indent(rest, " " * 4)}' + + return contents + + +def _migrate_sha_to_rev(contents: str) -> str: + return re.sub(r'(\n\s+)sha:', r'\1rev:', contents) + + +def migrate_config(config_file: str, quiet: bool = False) -> int: + with open(config_file) as f: + orig_contents = contents = f.read() + + contents = _migrate_map(contents) + contents = _migrate_sha_to_rev(contents) + + if contents != orig_contents: + with open(config_file, 'w') as f: + f.write(contents) + + print('Configuration has been migrated.') + elif not quiet: + print('Configuration is already migrated.') + return 0 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/run.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/run.py new file mode 100644 index 0000000..f8ced0f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/run.py @@ -0,0 +1,421 @@ +import argparse +import contextlib +import functools +import logging +import os +import re +import subprocess +import time +import unicodedata +from typing import Any +from typing import Collection +from typing import Dict +from typing import List +from typing import MutableMapping +from typing import Sequence +from typing import Set +from typing import Tuple + +from identify.identify import tags_from_path + +from pre_commit import color +from pre_commit import git +from pre_commit import output +from pre_commit.clientlib import load_config +from pre_commit.hook import Hook +from pre_commit.languages.all import languages +from pre_commit.repository import all_hooks +from pre_commit.repository import install_hook_envs +from pre_commit.staged_files_only import staged_files_only +from pre_commit.store import Store +from pre_commit.util import cmd_output_b + + +logger = logging.getLogger('pre_commit') + + +def _len_cjk(msg: str) -> int: + widths = {'A': 1, 'F': 2, 'H': 1, 'N': 1, 'Na': 1, 'W': 2} + return sum(widths[unicodedata.east_asian_width(c)] for c in msg) + + +def _start_msg(*, start: str, cols: int, end_len: int) -> str: + dots = '.' * (cols - _len_cjk(start) - end_len - 1) + return f'{start}{dots}' + + +def _full_msg( + *, + start: str, + cols: int, + end_msg: str, + end_color: str, + use_color: bool, + postfix: str = '', +) -> str: + dots = '.' * (cols - _len_cjk(start) - len(postfix) - len(end_msg) - 1) + end = color.format_color(end_msg, end_color, use_color) + return f'{start}{dots}{postfix}{end}\n' + + +def filter_by_include_exclude( + names: Collection[str], + include: str, + exclude: str, +) -> List[str]: + include_re, exclude_re = re.compile(include), re.compile(exclude) + return [ + filename for filename in names + if include_re.search(filename) + if not exclude_re.search(filename) + ] + + +class Classifier: + def __init__(self, filenames: Collection[str]) -> None: + self.filenames = [f for f in filenames if os.path.lexists(f)] + + @functools.lru_cache(maxsize=None) + def _types_for_file(self, filename: str) -> Set[str]: + return tags_from_path(filename) + + def by_types( + self, + names: Sequence[str], + types: Collection[str], + types_or: Collection[str], + exclude_types: Collection[str], + ) -> List[str]: + types = frozenset(types) + types_or = frozenset(types_or) + exclude_types = frozenset(exclude_types) + ret = [] + for filename in names: + tags = self._types_for_file(filename) + if ( + tags >= types and + (not types_or or tags & types_or) and + not tags & exclude_types + ): + ret.append(filename) + return ret + + def filenames_for_hook(self, hook: Hook) -> Tuple[str, ...]: + names = self.filenames + names = filter_by_include_exclude(names, hook.files, hook.exclude) + names = self.by_types( + names, + hook.types, + hook.types_or, + hook.exclude_types, + ) + return tuple(names) + + @classmethod + def from_config( + cls, + filenames: Collection[str], + include: str, + exclude: str, + ) -> 'Classifier': + # on windows we normalize all filenames to use forward slashes + # this makes it easier to filter using the `files:` regex + # this also makes improperly quoted shell-based hooks work better + # see #1173 + if os.altsep == '/' and os.sep == '\\': + filenames = [f.replace(os.sep, os.altsep) for f in filenames] + filenames = filter_by_include_exclude(filenames, include, exclude) + return Classifier(filenames) + + +def _get_skips(environ: MutableMapping[str, str]) -> Set[str]: + skips = environ.get('SKIP', '') + return {skip.strip() for skip in skips.split(',') if skip.strip()} + + +SKIPPED = 'Skipped' +NO_FILES = '(no files to check)' + + +def _subtle_line(s: str, use_color: bool) -> None: + output.write_line(color.format_color(s, color.SUBTLE, use_color)) + + +def _run_single_hook( + classifier: Classifier, + hook: Hook, + skips: Set[str], + cols: int, + diff_before: bytes, + verbose: bool, + use_color: bool, +) -> Tuple[bool, bytes]: + filenames = classifier.filenames_for_hook(hook) + + if hook.id in skips or hook.alias in skips: + output.write( + _full_msg( + start=hook.name, + end_msg=SKIPPED, + end_color=color.YELLOW, + use_color=use_color, + cols=cols, + ), + ) + duration = None + retcode = 0 + diff_after = diff_before + files_modified = False + out = b'' + elif not filenames and not hook.always_run: + output.write( + _full_msg( + start=hook.name, + postfix=NO_FILES, + end_msg=SKIPPED, + end_color=color.TURQUOISE, + use_color=use_color, + cols=cols, + ), + ) + duration = None + retcode = 0 + diff_after = diff_before + files_modified = False + out = b'' + else: + # print hook and dots first in case the hook takes a while to run + output.write(_start_msg(start=hook.name, end_len=6, cols=cols)) + + if not hook.pass_filenames: + filenames = () + time_before = time.time() + language = languages[hook.language] + retcode, out = language.run_hook(hook, filenames, use_color) + duration = round(time.time() - time_before, 2) or 0 + diff_after = _get_diff() + + # if the hook makes changes, fail the commit + files_modified = diff_before != diff_after + + if retcode or files_modified: + print_color = color.RED + status = 'Failed' + else: + print_color = color.GREEN + status = 'Passed' + + output.write_line(color.format_color(status, print_color, use_color)) + + if verbose or hook.verbose or retcode or files_modified: + _subtle_line(f'- hook id: {hook.id}', use_color) + + if (verbose or hook.verbose) and duration is not None: + _subtle_line(f'- duration: {duration}s', use_color) + + if retcode: + _subtle_line(f'- exit code: {retcode}', use_color) + + # Print a message if failing due to file modifications + if files_modified: + _subtle_line('- files were modified by this hook', use_color) + + if out.strip(): + output.write_line() + output.write_line_b(out.strip(), logfile_name=hook.log_file) + output.write_line() + + return files_modified or bool(retcode), diff_after + + +def _compute_cols(hooks: Sequence[Hook]) -> int: + """Compute the number of columns to display hook messages. The widest + that will be displayed is in the no files skipped case: + + Hook name...(no files to check) Skipped + """ + if hooks: + name_len = max(_len_cjk(hook.name) for hook in hooks) + else: + name_len = 0 + + cols = name_len + 3 + len(NO_FILES) + 1 + len(SKIPPED) + return max(cols, 80) + + +def _all_filenames(args: argparse.Namespace) -> Collection[str]: + # these hooks do not operate on files + if args.hook_stage in { + 'post-checkout', 'post-commit', 'post-merge', 'post-rewrite', + }: + return () + elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}: + return (args.commit_msg_filename,) + elif args.from_ref and args.to_ref: + return git.get_changed_files(args.from_ref, args.to_ref) + elif args.files: + return args.files + elif args.all_files: + return git.get_all_files() + elif git.is_in_merge_conflict(): + return git.get_conflicted_files() + else: + return git.get_staged_files() + + +def _get_diff() -> bytes: + _, out, _ = cmd_output_b( + 'git', 'diff', '--no-ext-diff', '--ignore-submodules', retcode=None, + ) + return out + + +def _run_hooks( + config: Dict[str, Any], + hooks: Sequence[Hook], + skips: Set[str], + args: argparse.Namespace, +) -> int: + """Actually run the hooks.""" + cols = _compute_cols(hooks) + classifier = Classifier.from_config( + _all_filenames(args), config['files'], config['exclude'], + ) + retval = 0 + prior_diff = _get_diff() + for hook in hooks: + current_retval, prior_diff = _run_single_hook( + classifier, hook, skips, cols, prior_diff, + verbose=args.verbose, use_color=args.color, + ) + retval |= current_retval + if retval and (config['fail_fast'] or hook.fail_fast): + break + if retval and args.show_diff_on_failure and prior_diff: + if args.all_files: + output.write_line( + 'pre-commit hook(s) made changes.\n' + 'If you are seeing this message in CI, ' + 'reproduce locally with: `pre-commit run --all-files`.\n' + 'To run `pre-commit` as part of git workflow, use ' + '`pre-commit install`.', + ) + output.write_line('All changes made by hooks:') + # args.color is a boolean. + # See user_color function in color.py + git_color_opt = 'always' if args.color else 'never' + subprocess.call(( + 'git', '--no-pager', 'diff', '--no-ext-diff', + f'--color={git_color_opt}', + )) + + return retval + + +def _has_unmerged_paths() -> bool: + _, stdout, _ = cmd_output_b('git', 'ls-files', '--unmerged') + return bool(stdout.strip()) + + +def _has_unstaged_config(config_file: str) -> bool: + retcode, _, _ = cmd_output_b( + 'git', 'diff', '--no-ext-diff', '--exit-code', config_file, + retcode=None, + ) + # be explicit, other git errors don't mean it has an unstaged config. + return retcode == 1 + + +def run( + config_file: str, + store: Store, + args: argparse.Namespace, + environ: MutableMapping[str, str] = os.environ, +) -> int: + stash = not args.all_files and not args.files + + # Check if we have unresolved merge conflict files and fail fast. + if _has_unmerged_paths(): + logger.error('Unmerged files. Resolve before committing.') + return 1 + if bool(args.from_ref) != bool(args.to_ref): + logger.error('Specify both --from-ref and --to-ref.') + return 1 + if stash and _has_unstaged_config(config_file): + logger.error( + f'Your pre-commit configuration is unstaged.\n' + f'`git add {config_file}` to fix this.', + ) + return 1 + if ( + args.hook_stage in {'prepare-commit-msg', 'commit-msg'} and + not args.commit_msg_filename + ): + logger.error( + f'`--commit-msg-filename` is required for ' + f'`--hook-stage {args.hook_stage}`', + ) + return 1 + # prevent recursive post-checkout hooks (#1418) + if ( + args.hook_stage == 'post-checkout' and + environ.get('_PRE_COMMIT_SKIP_POST_CHECKOUT') + ): + return 0 + + # Expose from-ref / to-ref as environment variables for hooks to consume + if args.from_ref and args.to_ref: + # legacy names + environ['PRE_COMMIT_ORIGIN'] = args.from_ref + environ['PRE_COMMIT_SOURCE'] = args.to_ref + # new names + environ['PRE_COMMIT_FROM_REF'] = args.from_ref + environ['PRE_COMMIT_TO_REF'] = args.to_ref + + if ( + args.remote_name and args.remote_url and + args.remote_branch and args.local_branch + ): + environ['PRE_COMMIT_LOCAL_BRANCH'] = args.local_branch + environ['PRE_COMMIT_REMOTE_BRANCH'] = args.remote_branch + environ['PRE_COMMIT_REMOTE_NAME'] = args.remote_name + environ['PRE_COMMIT_REMOTE_URL'] = args.remote_url + + if args.checkout_type: + environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type + + if args.is_squash_merge: + environ['PRE_COMMIT_IS_SQUASH_MERGE'] = args.is_squash_merge + + if args.rewrite_command: + environ['PRE_COMMIT_REWRITE_COMMAND'] = args.rewrite_command + + # Set pre_commit flag + environ['PRE_COMMIT'] = '1' + + with contextlib.ExitStack() as exit_stack: + if stash: + exit_stack.enter_context(staged_files_only(store.directory)) + + config = load_config(config_file) + hooks = [ + hook + for hook in all_hooks(config, store) + if not args.hook or hook.id == args.hook or hook.alias == args.hook + if args.hook_stage in hook.stages + ] + + if args.hook and not hooks: + output.write_line( + f'No hook with id `{args.hook}` in stage `{args.hook_stage}`', + ) + return 1 + + skips = _get_skips(environ) + to_install = [hook for hook in hooks if hook.id not in skips] + install_hook_envs(to_install, store) + + return _run_hooks(config, hooks, skips, args) + + # https://github.com/python/mypy/issues/7726 + raise AssertionError('unreachable') diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/sample_config.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/sample_config.py new file mode 100644 index 0000000..64617c3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/sample_config.py @@ -0,0 +1,21 @@ +# TODO: maybe `git ls-remote git://github.com/pre-commit/pre-commit-hooks` to +# determine the latest revision? This adds ~200ms from my tests (and is +# significantly faster than https:// or http://). For now, periodically +# manually updating the revision is fine. +SAMPLE_CONFIG = '''\ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +''' + + +def sample_config() -> int: + print(SAMPLE_CONFIG, end='') + return 0 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/commands/try_repo.py b/.venv/lib/python3.8/site-packages/pre_commit/commands/try_repo.py new file mode 100644 index 0000000..4aee209 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/commands/try_repo.py @@ -0,0 +1,77 @@ +import argparse +import logging +import os.path +from typing import Optional +from typing import Tuple + +import pre_commit.constants as C +from pre_commit import git +from pre_commit import output +from pre_commit.clientlib import load_manifest +from pre_commit.commands.run import run +from pre_commit.store import Store +from pre_commit.util import cmd_output_b +from pre_commit.util import tmpdir +from pre_commit.util import yaml_dump +from pre_commit.xargs import xargs + +logger = logging.getLogger(__name__) + + +def _repo_ref(tmpdir: str, repo: str, ref: Optional[str]) -> Tuple[str, str]: + # if `ref` is explicitly passed, use it + if ref is not None: + return repo, ref + + ref = git.head_rev(repo) + # if it exists on disk, we'll try and clone it with the local changes + if os.path.exists(repo) and git.has_diff('HEAD', repo=repo): + logger.warning('Creating temporary repo with uncommitted changes...') + + shadow = os.path.join(tmpdir, 'shadow-repo') + cmd_output_b('git', 'clone', repo, shadow) + cmd_output_b('git', 'checkout', ref, '-b', '_pc_tmp', cwd=shadow) + + idx = git.git_path('index', repo=shadow) + objs = git.git_path('objects', repo=shadow) + env = dict(os.environ, GIT_INDEX_FILE=idx, GIT_OBJECT_DIRECTORY=objs) + + staged_files = git.get_staged_files(cwd=repo) + if staged_files: + xargs(('git', 'add', '--'), staged_files, cwd=repo, env=env) + + cmd_output_b('git', 'add', '-u', cwd=repo, env=env) + git.commit(repo=shadow) + + return shadow, git.head_rev(shadow) + else: + return repo, ref + + +def try_repo(args: argparse.Namespace) -> int: + with tmpdir() as tempdir: + repo, ref = _repo_ref(tempdir, args.repo, args.ref) + + store = Store(tempdir) + if args.hook: + hooks = [{'id': args.hook}] + else: + repo_path = store.clone(repo, ref) + manifest = load_manifest(os.path.join(repo_path, C.MANIFEST_FILE)) + manifest = sorted(manifest, key=lambda hook: hook['id']) + hooks = [{'id': hook['id']} for hook in manifest] + + config = {'repos': [{'repo': repo, 'rev': ref, 'hooks': hooks}]} + config_s = yaml_dump(config) + + config_filename = os.path.join(tempdir, C.CONFIG_FILE) + with open(config_filename, 'w') as cfg: + cfg.write(config_s) + + output.write_line('=' * 79) + output.write_line('Using config:') + output.write_line('=' * 79) + output.write(config_s) + output.write_line('=' * 79) + + return run(config_filename, store, args) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/constants.py b/.venv/lib/python3.8/site-packages/pre_commit/constants.py new file mode 100644 index 0000000..d2f9363 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/constants.py @@ -0,0 +1,25 @@ +import sys + +if sys.version_info >= (3, 8): # pragma: >=3.8 cover + import importlib.metadata as importlib_metadata +else: # pragma: <3.8 cover + import importlib_metadata + +CONFIG_FILE = '.pre-commit-config.yaml' +MANIFEST_FILE = '.pre-commit-hooks.yaml' + +# Bump when installation changes in a backwards / forwards incompatible way +INSTALLED_STATE_VERSION = '1' +# Bump when modifying `empty_template` +LOCAL_REPO_VERSION = '1' + +VERSION = importlib_metadata.version('pre_commit') + +# `manual` is not invoked by any installed git hook. See #719 +STAGES = ( + 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', + 'post-commit', 'manual', 'post-checkout', 'push', 'post-merge', + 'post-rewrite', +) + +DEFAULT = 'default' diff --git a/.venv/lib/python3.8/site-packages/pre_commit/envcontext.py b/.venv/lib/python3.8/site-packages/pre_commit/envcontext.py new file mode 100644 index 0000000..92d975d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/envcontext.py @@ -0,0 +1,62 @@ +import contextlib +import enum +import os +from typing import Generator +from typing import MutableMapping +from typing import NamedTuple +from typing import Optional +from typing import Tuple +from typing import Union + +_Unset = enum.Enum('_Unset', 'UNSET') +UNSET = _Unset.UNSET + + +class Var(NamedTuple): + name: str + default: str = '' + + +SubstitutionT = Tuple[Union[str, Var], ...] +ValueT = Union[str, _Unset, SubstitutionT] +PatchesT = Tuple[Tuple[str, ValueT], ...] + + +def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str: + return ''.join( + env.get(part.name, part.default) if isinstance(part, Var) else part + for part in parts + ) + + +@contextlib.contextmanager +def envcontext( + patch: PatchesT, + _env: Optional[MutableMapping[str, str]] = None, +) -> Generator[None, None, None]: + """In this context, `os.environ` is modified according to `patch`. + + `patch` is an iterable of 2-tuples (key, value): + `key`: string + `value`: + - string: `environ[key] == value` inside the context. + - UNSET: `key not in environ` inside the context. + - template: A template is a tuple of strings and Var which will be + replaced with the previous environment + """ + env = os.environ if _env is None else _env + before = dict(env) + + for k, v in patch: + if v is UNSET: + env.pop(k, None) + elif isinstance(v, tuple): + env[k] = format_env(v, before) + else: + env[k] = v + + try: + yield + finally: + env.clear() + env.update(before) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/error_handler.py b/.venv/lib/python3.8/site-packages/pre_commit/error_handler.py new file mode 100644 index 0000000..7e74b95 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/error_handler.py @@ -0,0 +1,78 @@ +import contextlib +import functools +import os.path +import sys +import traceback +from typing import Generator + +import pre_commit.constants as C +from pre_commit import output +from pre_commit.errors import FatalError +from pre_commit.store import Store +from pre_commit.util import cmd_output_b +from pre_commit.util import force_bytes + + +def _log_and_exit( + msg: str, + ret_code: int, + exc: BaseException, + formatted: str, +) -> None: + error_msg = f'{msg}: {type(exc).__name__}: '.encode() + force_bytes(exc) + output.write_line_b(error_msg) + + _, git_version_b, _ = cmd_output_b('git', '--version', retcode=None) + git_version = git_version_b.decode(errors='backslashreplace').rstrip() + + storedir = Store().directory + log_path = os.path.join(storedir, 'pre-commit.log') + with contextlib.ExitStack() as ctx: + if os.access(storedir, os.W_OK): + output.write_line(f'Check the log at {log_path}') + log = ctx.enter_context(open(log_path, 'wb')) + else: # pragma: win32 no cover + output.write_line(f'Failed to write to log at {log_path}') + log = sys.stdout.buffer + + _log_line = functools.partial(output.write_line, stream=log) + _log_line_b = functools.partial(output.write_line_b, stream=log) + + _log_line('### version information') + _log_line() + _log_line('```') + _log_line(f'pre-commit version: {C.VERSION}') + _log_line(f'git --version: {git_version}') + _log_line('sys.version:') + for line in sys.version.splitlines(): + _log_line(f' {line}') + _log_line(f'sys.executable: {sys.executable}') + _log_line(f'os.name: {os.name}') + _log_line(f'sys.platform: {sys.platform}') + _log_line('```') + _log_line() + + _log_line('### error information') + _log_line() + _log_line('```') + _log_line_b(error_msg) + _log_line('```') + _log_line() + _log_line('```') + _log_line(formatted.rstrip()) + _log_line('```') + raise SystemExit(ret_code) + + +@contextlib.contextmanager +def error_handler() -> Generator[None, None, None]: + try: + yield + except (Exception, KeyboardInterrupt) as e: + if isinstance(e, FatalError): + msg, ret_code = 'An error has occurred', 1 + elif isinstance(e, KeyboardInterrupt): + msg, ret_code = 'Interrupted (^C)', 130 + else: + msg, ret_code = 'An unexpected error has occurred', 3 + _log_and_exit(msg, ret_code, e, traceback.format_exc()) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/errors.py b/.venv/lib/python3.8/site-packages/pre_commit/errors.py new file mode 100644 index 0000000..f84d3f1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/errors.py @@ -0,0 +1,2 @@ +class FatalError(RuntimeError): + pass diff --git a/.venv/lib/python3.8/site-packages/pre_commit/file_lock.py b/.venv/lib/python3.8/site-packages/pre_commit/file_lock.py new file mode 100644 index 0000000..55a8eb2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/file_lock.py @@ -0,0 +1,76 @@ +import contextlib +import errno +import sys +from typing import Callable +from typing import Generator + + +if sys.platform == 'win32': # pragma: no cover (windows) + import msvcrt + + # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/locking + + # on windows we lock "regions" of files, we don't care about the actual + # byte region so we'll just pick *some* number here. + _region = 0xffff + + @contextlib.contextmanager + def _locked( + fileno: int, + blocked_cb: Callable[[], None], + ) -> Generator[None, None, None]: + try: + # TODO: https://github.com/python/typeshed/pull/3607 + msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region) + except OSError: + blocked_cb() + while True: + try: + # TODO: https://github.com/python/typeshed/pull/3607 + msvcrt.locking(fileno, msvcrt.LK_LOCK, _region) + except OSError as e: + # Locking violation. Returned when the _LK_LOCK or _LK_RLCK + # flag is specified and the file cannot be locked after 10 + # attempts. + if e.errno != errno.EDEADLOCK: + raise + else: + break + + try: + yield + finally: + # From cursory testing, it seems to get unlocked when the file is + # closed so this may not be necessary. + # The documentation however states: + # "Regions should be locked only briefly and should be unlocked + # before closing a file or exiting the program." + # TODO: https://github.com/python/typeshed/pull/3607 + msvcrt.locking(fileno, msvcrt.LK_UNLCK, _region) +else: # pragma: win32 no cover + import fcntl + + @contextlib.contextmanager + def _locked( + fileno: int, + blocked_cb: Callable[[], None], + ) -> Generator[None, None, None]: + try: + fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB) + except OSError: # pragma: no cover (tests are single-threaded) + blocked_cb() + fcntl.flock(fileno, fcntl.LOCK_EX) + try: + yield + finally: + fcntl.flock(fileno, fcntl.LOCK_UN) + + +@contextlib.contextmanager +def lock( + path: str, + blocked_cb: Callable[[], None], +) -> Generator[None, None, None]: + with open(path, 'a+') as f: + with _locked(f.fileno(), blocked_cb): + yield diff --git a/.venv/lib/python3.8/site-packages/pre_commit/git.py b/.venv/lib/python3.8/site-packages/pre_commit/git.py new file mode 100644 index 0000000..e9ec601 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/git.py @@ -0,0 +1,232 @@ +import logging +import os.path +import sys +from typing import Dict +from typing import List +from typing import MutableMapping +from typing import Optional +from typing import Set + +from pre_commit.errors import FatalError +from pre_commit.util import CalledProcessError +from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b + +logger = logging.getLogger(__name__) + +# see #2046 +NO_FS_MONITOR = ('-c', 'core.useBuiltinFSMonitor=false') + + +def zsplit(s: str) -> List[str]: + s = s.strip('\0') + if s: + return s.split('\0') + else: + return [] + + +def no_git_env( + _env: Optional[MutableMapping[str, str]] = None, +) -> Dict[str, str]: + # Too many bugs dealing with environment variables and GIT: + # https://github.com/pre-commit/pre-commit/issues/300 + # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running + # pre-commit hooks + # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE + # while running pre-commit hooks in submodules. + # GIT_DIR: Causes git clone to clone wrong thing + # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit + _env = _env if _env is not None else os.environ + return { + k: v for k, v in _env.items() + if not k.startswith('GIT_') or + k.startswith(('GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_')) or + k in { + 'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO', + 'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT', + } + } + + +def get_root() -> str: + # Git 2.25 introduced a change to "rev-parse --show-toplevel" that exposed + # underlying volumes for Windows drives mapped with SUBST. We use + # "rev-parse --show-cdup" to get the appropriate path, but must perform + # an extra check to see if we are in the .git directory. + try: + root = os.path.abspath( + cmd_output('git', 'rev-parse', '--show-cdup')[1].strip(), + ) + git_dir = os.path.abspath(get_git_dir()) + except CalledProcessError: + raise FatalError( + 'git failed. Is it installed, and are you in a Git repository ' + 'directory?', + ) + if os.path.samefile(root, git_dir): + raise FatalError( + 'git toplevel unexpectedly empty! make sure you are not ' + 'inside the `.git` directory of your repository.', + ) + return root + + +def get_git_dir(git_root: str = '.') -> str: + opts = ('--git-common-dir', '--git-dir') + _, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root) + for line, opt in zip(out.splitlines(), opts): + if line != opt: # pragma: no branch (git < 2.5) + return os.path.normpath(os.path.join(git_root, line)) + else: + raise AssertionError('unreachable: no git dir') + + +def get_remote_url(git_root: str) -> str: + _, out, _ = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root) + return out.strip() + + +def is_in_merge_conflict() -> bool: + git_dir = get_git_dir('.') + return ( + os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and + os.path.exists(os.path.join(git_dir, 'MERGE_HEAD')) + ) + + +def parse_merge_msg_for_conflicts(merge_msg: bytes) -> List[str]: + # Conflicted files start with tabs + return [ + line.lstrip(b'#').strip().decode() + for line in merge_msg.splitlines() + # '#\t' for git 2.4.1 + if line.startswith((b'\t', b'#\t')) + ] + + +def get_conflicted_files() -> Set[str]: + logger.info('Checking merge-conflict files only.') + # Need to get the conflicted files from the MERGE_MSG because they could + # have resolved the conflict by choosing one side or the other + with open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb') as f: + merge_msg = f.read() + merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg) + + # This will get the rest of the changes made after the merge. + # If they resolved the merge conflict by choosing a mesh of both sides + # this will also include the conflicted files + tree_hash = cmd_output('git', 'write-tree')[1].strip() + merge_diff_filenames = zsplit( + cmd_output( + 'git', 'diff', '--name-only', '--no-ext-diff', '-z', + '-m', tree_hash, 'HEAD', 'MERGE_HEAD', + )[1], + ) + return set(merge_conflict_filenames) | set(merge_diff_filenames) + + +def get_staged_files(cwd: Optional[str] = None) -> List[str]: + return zsplit( + cmd_output( + 'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z', + # Everything except for D + '--diff-filter=ACMRTUXB', + cwd=cwd, + )[1], + ) + + +def intent_to_add_files() -> List[str]: + _, stdout, _ = cmd_output( + 'git', 'status', '--ignore-submodules', '--porcelain', '-z', + ) + parts = list(reversed(zsplit(stdout))) + intent_to_add = [] + while parts: + line = parts.pop() + status, filename = line[:3], line[3:] + if status[0] in {'C', 'R'}: # renames / moves have an additional arg + parts.pop() + if status[1] == 'A': + intent_to_add.append(filename) + return intent_to_add + + +def get_all_files() -> List[str]: + return zsplit(cmd_output('git', 'ls-files', '-z')[1]) + + +def get_changed_files(old: str, new: str) -> List[str]: + diff_cmd = ('git', 'diff', '--name-only', '--no-ext-diff', '-z') + try: + _, out, _ = cmd_output(*diff_cmd, f'{old}...{new}') + except CalledProcessError: # pragma: no cover (new git) + # on newer git where old and new do not have a merge base git fails + # so we try a full diff (this is what old git did for us!) + _, out, _ = cmd_output(*diff_cmd, f'{old}..{new}') + + return zsplit(out) + + +def head_rev(remote: str) -> str: + _, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD') + return out.split()[0] + + +def has_diff(*args: str, repo: str = '.') -> bool: + cmd = ('git', 'diff', '--quiet', '--no-ext-diff', *args) + return cmd_output_b(*cmd, cwd=repo, retcode=None)[0] == 1 + + +def has_core_hookpaths_set() -> bool: + _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', retcode=None) + return bool(out.strip()) + + +def init_repo(path: str, remote: str) -> None: + if os.path.isdir(remote): + remote = os.path.abspath(remote) + + git = ('git', *NO_FS_MONITOR) + env = no_git_env() + # avoid the user's template so that hooks do not recurse + cmd_output_b(*git, 'init', '--template=', path, env=env) + cmd_output_b(*git, 'remote', 'add', 'origin', remote, cwd=path, env=env) + + +def commit(repo: str = '.') -> None: + env = no_git_env() + name, email = 'pre-commit', 'asottile+pre-commit@umich.edu' + env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = name + env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = email + cmd = ('git', 'commit', '--no-edit', '--no-gpg-sign', '-n', '-minit') + cmd_output_b(*cmd, cwd=repo, env=env) + + +def git_path(name: str, repo: str = '.') -> str: + _, out, _ = cmd_output('git', 'rev-parse', '--git-path', name, cwd=repo) + return os.path.join(repo, out.strip()) + + +def check_for_cygwin_mismatch() -> None: + """See https://github.com/pre-commit/pre-commit/issues/354""" + if sys.platform in ('cygwin', 'win32'): # pragma: no cover (windows) + is_cygwin_python = sys.platform == 'cygwin' + try: + toplevel = get_root() + except FatalError: # skip the check if we're not in a git repo + return + is_cygwin_git = toplevel.startswith('/') + + if is_cygwin_python ^ is_cygwin_git: + exe_type = {True: '(cygwin)', False: '(windows)'} + logger.warn( + f'pre-commit has detected a mix of cygwin python / git\n' + f'This combination is not supported, it is likely you will ' + f'receive an error later in the program.\n' + f'Make sure to use cygwin git+python while using cygwin\n' + f'These can be installed through the cygwin installer.\n' + f' - python {exe_type[is_cygwin_python]}\n' + f' - git {exe_type[is_cygwin_git]}\n', + ) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/hook.py b/.venv/lib/python3.8/site-packages/pre_commit/hook.py new file mode 100644 index 0000000..82e99c5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/hook.py @@ -0,0 +1,65 @@ +import logging +import shlex +from typing import Any +from typing import Dict +from typing import NamedTuple +from typing import Sequence +from typing import Tuple + +from pre_commit.prefix import Prefix + +logger = logging.getLogger('pre_commit') + + +class Hook(NamedTuple): + src: str + prefix: Prefix + id: str + name: str + entry: str + language: str + alias: str + files: str + exclude: str + types: Sequence[str] + types_or: Sequence[str] + exclude_types: Sequence[str] + additional_dependencies: Sequence[str] + args: Sequence[str] + always_run: bool + fail_fast: bool + pass_filenames: bool + description: str + language_version: str + log_file: str + minimum_pre_commit_version: str + require_serial: bool + stages: Sequence[str] + verbose: bool + + @property + def cmd(self) -> Tuple[str, ...]: + return (*shlex.split(self.entry), *self.args) + + @property + def install_key(self) -> Tuple[Prefix, str, str, Tuple[str, ...]]: + return ( + self.prefix, + self.language, + self.language_version, + tuple(self.additional_dependencies), + ) + + @classmethod + def create(cls, src: str, prefix: Prefix, dct: Dict[str, Any]) -> 'Hook': + # TODO: have cfgv do this (?) + extra_keys = set(dct) - _KEYS + if extra_keys: + logger.warning( + f'Unexpected key(s) present on {src} => {dct["id"]}: ' + f'{", ".join(sorted(extra_keys))}', + ) + return cls(src=src, prefix=prefix, **{k: dct[k] for k in _KEYS}) + + +_KEYS = frozenset(set(Hook._fields) - {'src', 'prefix'}) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__init__.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d14f0f1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/all.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/all.cpython-38.pyc new file mode 100644 index 0000000..789ec68 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/all.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/conda.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/conda.cpython-38.pyc new file mode 100644 index 0000000..064034c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/conda.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/coursier.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/coursier.cpython-38.pyc new file mode 100644 index 0000000..cfc2249 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/coursier.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/dart.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/dart.cpython-38.pyc new file mode 100644 index 0000000..d69ba15 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/dart.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/docker.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/docker.cpython-38.pyc new file mode 100644 index 0000000..b2de4f7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/docker.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/docker_image.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/docker_image.cpython-38.pyc new file mode 100644 index 0000000..7a57c92 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/docker_image.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/dotnet.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/dotnet.cpython-38.pyc new file mode 100644 index 0000000..98c92a5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/dotnet.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/fail.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/fail.cpython-38.pyc new file mode 100644 index 0000000..585b010 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/fail.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/golang.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/golang.cpython-38.pyc new file mode 100644 index 0000000..e02aa62 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/golang.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/helpers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/helpers.cpython-38.pyc new file mode 100644 index 0000000..9abdefd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/helpers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/lua.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/lua.cpython-38.pyc new file mode 100644 index 0000000..39f1bde Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/lua.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/node.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/node.cpython-38.pyc new file mode 100644 index 0000000..0fef08f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/node.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/perl.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/perl.cpython-38.pyc new file mode 100644 index 0000000..4258a6b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/perl.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/pygrep.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/pygrep.cpython-38.pyc new file mode 100644 index 0000000..dbd5ef2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/pygrep.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/python.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/python.cpython-38.pyc new file mode 100644 index 0000000..19b5e97 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/python.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/r.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/r.cpython-38.pyc new file mode 100644 index 0000000..8f27fb6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/r.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/ruby.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/ruby.cpython-38.pyc new file mode 100644 index 0000000..791648e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/ruby.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/rust.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/rust.cpython-38.pyc new file mode 100644 index 0000000..dfd3c3d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/rust.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/script.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/script.cpython-38.pyc new file mode 100644 index 0000000..348dd0a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/script.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/swift.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/swift.cpython-38.pyc new file mode 100644 index 0000000..6d1f3c4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/swift.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/system.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/system.cpython-38.pyc new file mode 100644 index 0000000..a74fcba Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/languages/__pycache__/system.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/all.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/all.py new file mode 100644 index 0000000..0bcedd6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/all.py @@ -0,0 +1,70 @@ +from typing import Callable +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Tuple + +from pre_commit.hook import Hook +from pre_commit.languages import conda +from pre_commit.languages import coursier +from pre_commit.languages import dart +from pre_commit.languages import docker +from pre_commit.languages import docker_image +from pre_commit.languages import dotnet +from pre_commit.languages import fail +from pre_commit.languages import golang +from pre_commit.languages import lua +from pre_commit.languages import node +from pre_commit.languages import perl +from pre_commit.languages import pygrep +from pre_commit.languages import python +from pre_commit.languages import r +from pre_commit.languages import ruby +from pre_commit.languages import rust +from pre_commit.languages import script +from pre_commit.languages import swift +from pre_commit.languages import system +from pre_commit.prefix import Prefix + + +class Language(NamedTuple): + name: str + # Use `None` for no installation / environment + ENVIRONMENT_DIR: Optional[str] + # return a value to replace `'default` for `language_version` + get_default_version: Callable[[], str] + # return whether the environment is healthy (or should be rebuilt) + healthy: Callable[[Prefix, str], bool] + # install a repository for the given language and language_version + install_environment: Callable[[Prefix, str, Sequence[str]], None] + # execute a hook and return the exit code and output + run_hook: 'Callable[[Hook, Sequence[str], bool], Tuple[int, bytes]]' + + +# TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018 +languages = { + # BEGIN GENERATED (testing/gen-languages-all) + 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, healthy=conda.healthy, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 + 'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, healthy=coursier.healthy, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501 + 'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, healthy=dart.healthy, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501 + 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, healthy=docker.healthy, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 + 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, healthy=docker_image.healthy, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 + 'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, healthy=dotnet.healthy, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501 + 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, healthy=fail.healthy, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 + 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, healthy=golang.healthy, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 + 'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, healthy=lua.healthy, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501 + 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, healthy=node.healthy, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 + 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, healthy=perl.healthy, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 + 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 + 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 + 'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, healthy=r.healthy, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501 + 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 + 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 + 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 + 'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, healthy=swift.healthy, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501 + 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, healthy=system.healthy, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 + # END GENERATED +} +# TODO: fully deprecate `python_venv` +languages['python_venv'] = languages['python'] +all_languages = sorted(languages) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/conda.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/conda.py new file mode 100644 index 0000000..97e2f69 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/conda.py @@ -0,0 +1,95 @@ +import contextlib +import os +from typing import Generator +from typing import Sequence +from typing import Tuple + +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import SubstitutionT +from pre_commit.envcontext import UNSET +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output_b + +ENVIRONMENT_DIR = 'conda' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(env: str) -> PatchesT: + # On non-windows systems executable live in $CONDA_PREFIX/bin, on Windows + # they can be in $CONDA_PREFIX/bin, $CONDA_PREFIX/Library/bin, + # $CONDA_PREFIX/Scripts and $CONDA_PREFIX. Whereas the latter only + # seems to be used for python.exe. + path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH')) + if os.name == 'nt': # pragma: no cover (platform specific) + path = (env, os.pathsep, *path) + path = (os.path.join(env, 'Scripts'), os.pathsep, *path) + path = (os.path.join(env, 'Library', 'bin'), os.pathsep, *path) + + return ( + ('PYTHONHOME', UNSET), + ('VIRTUAL_ENV', UNSET), + ('CONDA_PREFIX', env), + ('PATH', path), + ) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) + envdir = prefix.path(directory) + with envcontext(get_env_patch(envdir)): + yield + + +def _conda_exe() -> str: + if os.environ.get('PRE_COMMIT_USE_MICROMAMBA'): + return 'micromamba' + elif os.environ.get('PRE_COMMIT_USE_MAMBA'): + return 'mamba' + else: + return 'conda' + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + helpers.assert_version_default('conda', version) + directory = helpers.environment_dir(ENVIRONMENT_DIR, version) + + conda_exe = _conda_exe() + + env_dir = prefix.path(directory) + with clean_path_on_failure(env_dir): + cmd_output_b( + conda_exe, 'env', 'create', '-p', env_dir, '--file', + 'environment.yml', cwd=prefix.prefix_dir, + ) + if additional_dependencies: + cmd_output_b( + conda_exe, 'install', '-p', env_dir, *additional_dependencies, + cwd=prefix.prefix_dir, + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + # TODO: Some rare commands need to be run using `conda run` but mostly we + # can run them without which is much quicker and produces a better + # output. + # cmd = ('conda', 'run', '-p', env_dir) + hook.cmd + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/coursier.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/coursier.py new file mode 100644 index 0000000..2841467 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/coursier.py @@ -0,0 +1,71 @@ +import contextlib +import os +from typing import Generator +from typing import Sequence +from typing import Tuple + +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure + +ENVIRONMENT_DIR = 'coursier' + +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: # pragma: win32 no cover + helpers.assert_version_default('coursier', version) + helpers.assert_no_additional_deps('coursier', additional_dependencies) + + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + channel = prefix.path('.pre-commit-channel') + with clean_path_on_failure(envdir): + for app_descriptor in os.listdir(channel): + _, app_file = os.path.split(app_descriptor) + app, _ = os.path.splitext(app_file) + helpers.run_setup_cmd( + prefix, + ( + 'cs', + 'install', + '--default-channels=false', + f'--channel={channel}', + app, + f'--dir={envdir}', + ), + ) + + +def get_env_patch(target_dir: str) -> PatchesT: # pragma: win32 no cover + return ( + ('PATH', (target_dir, os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, +) -> Generator[None, None, None]: # pragma: win32 no cover + target_dir = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, get_default_version()), + ) + with envcontext(get_env_patch(target_dir)): + yield + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: # pragma: win32 no cover + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/dart.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/dart.py new file mode 100644 index 0000000..16e7554 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/dart.py @@ -0,0 +1,109 @@ +import contextlib +import os.path +import shutil +import tempfile +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import win_exe +from pre_commit.util import yaml_load + +ENVIRONMENT_DIR = 'dartenv' + +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env(prefix: Prefix) -> Generator[None, None, None]: + directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT) + envdir = prefix.path(directory) + with envcontext(get_env_patch(envdir)): + yield + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + helpers.assert_version_default('dart', version) + + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + bin_dir = os.path.join(envdir, 'bin') + + def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: + dart_env = {**os.environ, 'PUB_CACHE': pub_cache} + + with open(prefix_p.path('pubspec.yaml')) as f: + pubspec_contents = yaml_load(f) + + helpers.run_setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env) + + for executable in pubspec_contents['executables']: + helpers.run_setup_cmd( + prefix_p, + ( + 'dart', 'compile', 'exe', + '--output', os.path.join(bin_dir, win_exe(executable)), + prefix_p.path('bin', f'{executable}.dart'), + ), + env=dart_env, + ) + + with clean_path_on_failure(envdir): + os.makedirs(bin_dir) + + with tempfile.TemporaryDirectory() as tmp: + _install_dir(prefix, tmp) + + for dep_s in additional_dependencies: + with tempfile.TemporaryDirectory() as dep_tmp: + dep, _, version = dep_s.partition(':') + if version: + dep_cmd: Tuple[str, ...] = (dep, '--version', version) + else: + dep_cmd = (dep,) + + helpers.run_setup_cmd( + prefix, + ('dart', 'pub', 'cache', 'add', *dep_cmd), + env={**os.environ, 'PUB_CACHE': dep_tmp}, + ) + + # try and find the 'pubspec.yaml' that just got added + for root, _, filenames in os.walk(dep_tmp): + if 'pubspec.yaml' in filenames: + with tempfile.TemporaryDirectory() as copied: + pkg = os.path.join(copied, 'pkg') + shutil.copytree(root, pkg) + _install_dir(Prefix(pkg), dep_tmp) + break + else: + raise AssertionError( + f'could not find pubspec.yaml for {dep_s}', + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/docker.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/docker.py new file mode 100644 index 0000000..644d8d2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/docker.py @@ -0,0 +1,141 @@ +import hashlib +import json +import os +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import CalledProcessError +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output_b + +ENVIRONMENT_DIR = 'docker' +PRE_COMMIT_LABEL = 'PRE_COMMIT' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def _is_in_docker() -> bool: + try: + with open('/proc/1/cgroup', 'rb') as f: + return b'docker' in f.read() + except FileNotFoundError: + return False + + +def _get_container_id() -> str: + # It's assumed that we already check /proc/1/cgroup in _is_in_docker. The + # cpuset cgroup controller existed since cgroups were introduced so this + # way of getting the container ID is pretty reliable. + with open('/proc/1/cgroup', 'rb') as f: + for line in f.readlines(): + if line.split(b':')[1] == b'cpuset': + return os.path.basename(line.split(b':')[2]).strip().decode() + raise RuntimeError('Failed to find the container ID in /proc/1/cgroup.') + + +def _get_docker_path(path: str) -> str: + if not _is_in_docker(): + return path + + container_id = _get_container_id() + + try: + _, out, _ = cmd_output_b('docker', 'inspect', container_id) + except CalledProcessError: + # self-container was not visible from here (perhaps docker-in-docker) + return path + + container, = json.loads(out) + for mount in container['Mounts']: + src_path = mount['Source'] + to_path = mount['Destination'] + if os.path.commonpath((path, to_path)) == to_path: + # So there is something in common, + # and we can proceed remapping it + return path.replace(to_path, src_path) + # we're in Docker, but the path is not mounted, cannot really do anything, + # so fall back to original path + return path + + +def md5(s: str) -> str: # pragma: win32 no cover + return hashlib.md5(s.encode()).hexdigest() + + +def docker_tag(prefix: Prefix) -> str: # pragma: win32 no cover + md5sum = md5(os.path.basename(prefix.prefix_dir)).lower() + return f'pre-commit-{md5sum}' + + +def build_docker_image( + prefix: Prefix, + *, + pull: bool, +) -> None: # pragma: win32 no cover + cmd: Tuple[str, ...] = ( + 'docker', 'build', + '--tag', docker_tag(prefix), + '--label', PRE_COMMIT_LABEL, + ) + if pull: + cmd += ('--pull',) + # This must come last for old versions of docker. See #477 + cmd += ('.',) + helpers.run_setup_cmd(prefix, cmd) + + +def install_environment( + prefix: Prefix, version: str, additional_dependencies: Sequence[str], +) -> None: # pragma: win32 no cover + helpers.assert_version_default('docker', version) + helpers.assert_no_additional_deps('docker', additional_dependencies) + + directory = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), + ) + + # Docker doesn't really have relevant disk environment, but pre-commit + # still needs to cleanup its state files on failure + with clean_path_on_failure(directory): + build_docker_image(prefix, pull=True) + os.mkdir(directory) + + +def get_docker_user() -> Tuple[str, ...]: # pragma: win32 no cover + try: + return ('-u', f'{os.getuid()}:{os.getgid()}') + except AttributeError: + return () + + +def docker_cmd() -> Tuple[str, ...]: # pragma: win32 no cover + return ( + 'docker', 'run', + '--rm', + *get_docker_user(), + # https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from + # The `Z` option tells Docker to label the content with a private + # unshared label. Only the current container can use a private volume. + '-v', f'{_get_docker_path(os.getcwd())}:/src:rw,Z', + '--workdir', '/src', + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: # pragma: win32 no cover + # Rebuild the docker image in case it has gone missing, as many people do + # automated cleanup of docker images. + build_docker_image(hook.prefix, pull=False) + + entry_exe, *cmd_rest = hook.cmd + + entry_tag = ('--entrypoint', entry_exe, docker_tag(hook.prefix)) + cmd = (*docker_cmd(), *entry_tag, *cmd_rest) + return helpers.run_xargs(hook, cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/docker_image.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/docker_image.py new file mode 100644 index 0000000..311d127 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/docker_image.py @@ -0,0 +1,20 @@ +from typing import Sequence +from typing import Tuple + +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.languages.docker import docker_cmd + +ENVIRONMENT_DIR = None +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy +install_environment = helpers.no_install + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: # pragma: win32 no cover + cmd = docker_cmd() + hook.cmd + return helpers.run_xargs(hook, cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/dotnet.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/dotnet.py new file mode 100644 index 0000000..094d2f1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/dotnet.py @@ -0,0 +1,89 @@ +import contextlib +import os.path +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure + +ENVIRONMENT_DIR = 'dotnetenv' +BIN_DIR = 'bin' + +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('PATH', (os.path.join(venv, BIN_DIR), os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env(prefix: Prefix) -> Generator[None, None, None]: + directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT) + envdir = prefix.path(directory) + with envcontext(get_env_patch(envdir)): + yield + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + helpers.assert_version_default('dotnet', version) + helpers.assert_no_additional_deps('dotnet', additional_dependencies) + + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + with clean_path_on_failure(envdir): + build_dir = 'pre-commit-build' + + # Build & pack nupkg file + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'pack', + '--configuration', 'Release', + '--output', build_dir, + ), + ) + + # Determine tool from the packaged file ..nupkg + build_outputs = os.listdir(os.path.join(prefix.prefix_dir, build_dir)) + if len(build_outputs) != 1: + raise NotImplementedError( + f"Can't handle multiple build outputs. Got {build_outputs}", + ) + tool_name = build_outputs[0].split('.')[0] + + # Install to bin dir + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'tool', 'install', + '--tool-path', os.path.join(envdir, BIN_DIR), + '--add-source', build_dir, + tool_name, + ), + ) + + # Clean the git dir, ignoring the environment dir + clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*') + helpers.run_setup_cmd(prefix, clean_cmd) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/fail.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/fail.py new file mode 100644 index 0000000..d2b02d2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/fail.py @@ -0,0 +1,20 @@ +from typing import Sequence +from typing import Tuple + +from pre_commit.hook import Hook +from pre_commit.languages import helpers + +ENVIRONMENT_DIR = None +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy +install_environment = helpers.no_install + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + out = f'{hook.entry}\n\n'.encode() + out += b'\n'.join(f.encode() for f in file_args) + b'\n' + return 1, out diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/golang.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/golang.py new file mode 100644 index 0000000..10ebc62 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/golang.py @@ -0,0 +1,100 @@ +import contextlib +import os.path +import sys +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit import git +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b +from pre_commit.util import rmtree + +ENVIRONMENT_DIR = 'golangenv' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env(prefix: Prefix) -> Generator[None, None, None]: + envdir = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), + ) + with envcontext(get_env_patch(envdir)): + yield + + +def guess_go_dir(remote_url: str) -> str: + if remote_url.endswith('.git'): + remote_url = remote_url[:-1 * len('.git')] + looks_like_url = ( + not remote_url.startswith('file://') and + ('//' in remote_url or '@' in remote_url) + ) + remote_url = remote_url.replace(':', '/') + if looks_like_url: + _, _, remote_url = remote_url.rpartition('//') + _, _, remote_url = remote_url.rpartition('@') + return remote_url + else: + return 'unknown_src_dir' + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + helpers.assert_version_default('golang', version) + directory = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), + ) + + with clean_path_on_failure(directory): + remote = git.get_remote_url(prefix.prefix_dir) + repo_src_dir = os.path.join(directory, 'src', guess_go_dir(remote)) + + # Clone into the goenv we'll create + cmd = ('git', 'clone', '--recursive', '.', repo_src_dir) + helpers.run_setup_cmd(prefix, cmd) + + if sys.platform == 'cygwin': # pragma: no cover + _, gopath, _ = cmd_output('cygpath', '-w', directory) + gopath = gopath.strip() + else: + gopath = directory + env = dict(os.environ, GOPATH=gopath) + env.pop('GOBIN', None) + cmd_output_b('go', 'install', './...', cwd=repo_src_dir, env=env) + for dependency in additional_dependencies: + cmd_output_b( + 'go', 'install', dependency, cwd=repo_src_dir, env=env, + ) + # Same some disk space, we don't need these after installation + rmtree(prefix.path(directory, 'src')) + pkgdir = prefix.path(directory, 'pkg') + if os.path.exists(pkgdir): # pragma: no cover (go<1.10) + rmtree(pkgdir) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/helpers.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/helpers.py new file mode 100644 index 0000000..276ce16 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/helpers.py @@ -0,0 +1,136 @@ +import multiprocessing +import os +import random +import re +from typing import Any +from typing import List +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING + +import pre_commit.constants as C +from pre_commit import parse_shebang +from pre_commit.hook import Hook +from pre_commit.prefix import Prefix +from pre_commit.util import cmd_output_b +from pre_commit.xargs import xargs + +if TYPE_CHECKING: + from typing import NoReturn + +FIXED_RANDOM_SEED = 1542676187 + +SHIMS_RE = re.compile(r'[/\\]shims[/\\]') + + +def exe_exists(exe: str) -> bool: + found = parse_shebang.find_executable(exe) + if found is None: # exe exists + return False + + homedir = os.path.expanduser('~') + try: + common: Optional[str] = os.path.commonpath((found, homedir)) + except ValueError: # on windows, different drives raises ValueError + common = None + + return ( + # it is not in a /shims/ directory + not SHIMS_RE.search(found) and + ( + # the homedir is / (docker, service user, etc.) + os.path.dirname(homedir) == homedir or + # the exe is not contained in the home directory + common != homedir + ) + ) + + +def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...], **kwargs: Any) -> None: + cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs) + + +@overload +def environment_dir(d: None, language_version: str) -> None: ... +@overload +def environment_dir(d: str, language_version: str) -> str: ... + + +def environment_dir(d: Optional[str], language_version: str) -> Optional[str]: + if d is None: + return None + else: + return f'{d}-{language_version}' + + +def assert_version_default(binary: str, version: str) -> None: + if version != C.DEFAULT: + raise AssertionError( + f'For now, pre-commit requires system-installed {binary}', + ) + + +def assert_no_additional_deps( + lang: str, + additional_deps: Sequence[str], +) -> None: + if additional_deps: + raise AssertionError( + f'For now, pre-commit does not support ' + f'additional_dependencies for {lang}', + ) + + +def basic_get_default_version() -> str: + return C.DEFAULT + + +def basic_healthy(prefix: Prefix, language_version: str) -> bool: + return True + + +def no_install( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> 'NoReturn': + raise AssertionError('This type is not installable') + + +def target_concurrency(hook: Hook) -> int: + if hook.require_serial or 'PRE_COMMIT_NO_CONCURRENCY' in os.environ: + return 1 + else: + # Travis appears to have a bunch of CPUs, but we can't use them all. + if 'TRAVIS' in os.environ: + return 2 + else: + try: + return multiprocessing.cpu_count() + except NotImplementedError: + return 1 + + +def _shuffled(seq: Sequence[str]) -> List[str]: + """Deterministically shuffle""" + fixed_random = random.Random() + fixed_random.seed(FIXED_RANDOM_SEED, version=1) + + seq = list(seq) + fixed_random.shuffle(seq) + return seq + + +def run_xargs( + hook: Hook, + cmd: Tuple[str, ...], + file_args: Sequence[str], + **kwargs: Any, +) -> Tuple[int, bytes]: + # Shuffle the files so that they more evenly fill out the xargs partitions, + # but do it deterministically in case a hook cares about ordering. + file_args = _shuffled(file_args) + kwargs['target_concurrency'] = target_concurrency(hook) + return xargs(cmd, file_args, **kwargs) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/lua.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/lua.py new file mode 100644 index 0000000..f699937 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/lua.py @@ -0,0 +1,90 @@ +import contextlib +import os +import sys +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output + +ENVIRONMENT_DIR = 'lua_env' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def _get_lua_version() -> str: # pragma: win32 no cover + """Get the Lua version used in file paths.""" + _, stdout, _ = cmd_output('luarocks', 'config', '--lua-ver') + return stdout.strip() + + +def get_env_patch(d: str) -> PatchesT: # pragma: win32 no cover + version = _get_lua_version() + so_ext = 'dll' if sys.platform == 'win32' else 'so' + return ( + ('PATH', (os.path.join(d, 'bin'), os.pathsep, Var('PATH'))), + ( + 'LUA_PATH', ( + os.path.join(d, 'share', 'lua', version, '?.lua;'), + os.path.join(d, 'share', 'lua', version, '?', 'init.lua;;'), + ), + ), + ( + 'LUA_CPATH', + (os.path.join(d, 'lib', 'lua', version, f'?.{so_ext};;'),), + ), + ) + + +def _envdir(prefix: Prefix) -> str: # pragma: win32 no cover + directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT) + return prefix.path(directory) + + +@contextlib.contextmanager # pragma: win32 no cover +def in_env(prefix: Prefix) -> Generator[None, None, None]: + with envcontext(get_env_patch(_envdir(prefix))): + yield + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: # pragma: win32 no cover + helpers.assert_version_default('lua', version) + + envdir = _envdir(prefix) + with clean_path_on_failure(envdir): + with in_env(prefix): + # luarocks doesn't bootstrap a tree prior to installing + # so ensure the directory exists. + os.makedirs(envdir, exist_ok=True) + + # Older luarocks (e.g., 2.4.2) expect the rockspec as an arg + for rockspec in prefix.star('.rockspec'): + make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec) + helpers.run_setup_cmd(prefix, make_cmd) + + # luarocks can't install multiple packages at once + # so install them individually. + for dependency in additional_dependencies: + cmd = ('luarocks', '--tree', envdir, 'install', dependency) + helpers.run_setup_cmd(prefix, cmd) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: # pragma: win32 no cover + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/node.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/node.py new file mode 100644 index 0000000..8dc4e8b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/node.py @@ -0,0 +1,127 @@ +import contextlib +import functools +import os +import sys +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import UNSET +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.languages.python import bin_dir +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b +from pre_commit.util import rmtree + +ENVIRONMENT_DIR = 'node_env' + + +@functools.lru_cache(maxsize=1) +def get_default_version() -> str: + # nodeenv does not yet support `-n system` on windows + if sys.platform == 'win32': + return C.DEFAULT + # if node is already installed, we can save a bunch of setup time by + # using the installed version + elif all(helpers.exe_exists(exe) for exe in ('node', 'npm')): + return 'system' + else: + return C.DEFAULT + + +def _envdir(prefix: Prefix, version: str) -> str: + directory = helpers.environment_dir(ENVIRONMENT_DIR, version) + return prefix.path(directory) + + +def get_env_patch(venv: str) -> PatchesT: + if sys.platform == 'cygwin': # pragma: no cover + _, win_venv, _ = cmd_output('cygpath', '-w', venv) + install_prefix = fr'{win_venv.strip()}\bin' + lib_dir = 'lib' + elif sys.platform == 'win32': # pragma: no cover + install_prefix = bin_dir(venv) + lib_dir = 'Scripts' + else: # pragma: win32 no cover + install_prefix = venv + lib_dir = 'lib' + return ( + ('NODE_VIRTUAL_ENV', venv), + ('NPM_CONFIG_PREFIX', install_prefix), + ('npm_config_prefix', install_prefix), + ('NPM_CONFIG_USERCONFIG', UNSET), + ('npm_config_userconfig', UNSET), + ('NODE_PATH', os.path.join(venv, lib_dir, 'node_modules')), + ('PATH', (bin_dir(venv), os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + with envcontext(get_env_patch(_envdir(prefix, language_version))): + yield + + +def healthy(prefix: Prefix, language_version: str) -> bool: + with in_env(prefix, language_version): + retcode, _, _ = cmd_output_b('node', '--version', retcode=None) + return retcode == 0 + + +def install_environment( + prefix: Prefix, version: str, additional_dependencies: Sequence[str], +) -> None: + additional_dependencies = tuple(additional_dependencies) + assert prefix.exists('package.json') + envdir = _envdir(prefix, version) + + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath + if sys.platform == 'win32': # pragma: no cover + envdir = fr'\\?\{os.path.normpath(envdir)}' + with clean_path_on_failure(envdir): + cmd = [ + sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir, + ] + if version != C.DEFAULT: + cmd.extend(['-n', version]) + cmd_output_b(*cmd) + + with in_env(prefix, version): + # https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449 + # install as if we installed from git + + local_install_cmd = ( + 'npm', 'install', '--dev', '--prod', + '--ignore-prepublish', '--no-progress', '--no-save', + ) + helpers.run_setup_cmd(prefix, local_install_cmd) + + _, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir) + pkg = prefix.path(pkg.strip()) + + install = ('npm', 'install', '-g', pkg, *additional_dependencies) + helpers.run_setup_cmd(prefix, install) + + # clean these up after installation + if prefix.exists('node_modules'): # pragma: win32 no cover + rmtree(prefix.path('node_modules')) + os.remove(pkg) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/perl.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/perl.py new file mode 100644 index 0000000..bbf5504 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/perl.py @@ -0,0 +1,67 @@ +import contextlib +import os +import shlex +from typing import Generator +from typing import Sequence +from typing import Tuple + +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure + +ENVIRONMENT_DIR = 'perl_env' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def _envdir(prefix: Prefix, version: str) -> str: + directory = helpers.environment_dir(ENVIRONMENT_DIR, version) + return prefix.path(directory) + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), + ('PERL5LIB', os.path.join(venv, 'lib', 'perl5')), + ('PERL_MB_OPT', f'--install_base {shlex.quote(venv)}'), + ( + 'PERL_MM_OPT', ( + f'INSTALL_BASE={shlex.quote(venv)} ' + f'INSTALLSITEMAN1DIR=none INSTALLSITEMAN3DIR=none' + ), + ), + ) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + with envcontext(get_env_patch(_envdir(prefix, language_version))): + yield + + +def install_environment( + prefix: Prefix, version: str, additional_dependencies: Sequence[str], +) -> None: + helpers.assert_version_default('perl', version) + + with clean_path_on_failure(_envdir(prefix, version)): + with in_env(prefix, version): + helpers.run_setup_cmd( + prefix, ('cpan', '-T', '.', *additional_dependencies), + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/pygrep.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/pygrep.py new file mode 100644 index 0000000..a713c3f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/pygrep.py @@ -0,0 +1,127 @@ +import argparse +import re +import sys +from typing import NamedTuple +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Tuple + +from pre_commit import output +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.xargs import xargs + +ENVIRONMENT_DIR = None +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy +install_environment = helpers.no_install + + +def _process_filename_by_line(pattern: Pattern[bytes], filename: str) -> int: + retv = 0 + with open(filename, 'rb') as f: + for line_no, line in enumerate(f, start=1): + if pattern.search(line): + retv = 1 + output.write(f'{filename}:{line_no}:') + output.write_line_b(line.rstrip(b'\r\n')) + return retv + + +def _process_filename_at_once(pattern: Pattern[bytes], filename: str) -> int: + retv = 0 + with open(filename, 'rb') as f: + contents = f.read() + match = pattern.search(contents) + if match: + retv = 1 + line_no = contents[:match.start()].count(b'\n') + output.write(f'{filename}:{line_no + 1}:') + + matched_lines = match[0].split(b'\n') + matched_lines[0] = contents.split(b'\n')[line_no] + + output.write_line_b(b'\n'.join(matched_lines)) + return retv + + +def _process_filename_by_line_negated( + pattern: Pattern[bytes], + filename: str, +) -> int: + with open(filename, 'rb') as f: + for line in f: + if pattern.search(line): + return 0 + else: + output.write_line(filename) + return 1 + + +def _process_filename_at_once_negated( + pattern: Pattern[bytes], + filename: str, +) -> int: + with open(filename, 'rb') as f: + contents = f.read() + match = pattern.search(contents) + if match: + return 0 + else: + output.write_line(filename) + return 1 + + +class Choice(NamedTuple): + multiline: bool + negate: bool + + +FNS = { + Choice(multiline=True, negate=True): _process_filename_at_once_negated, + Choice(multiline=True, negate=False): _process_filename_at_once, + Choice(multiline=False, negate=True): _process_filename_by_line_negated, + Choice(multiline=False, negate=False): _process_filename_by_line, +} + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + exe = (sys.executable, '-m', __name__) + tuple(hook.args) + (hook.entry,) + return xargs(exe, file_args, color=color) + + +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser( + description=( + 'grep-like finder using python regexes. Unlike grep, this tool ' + 'returns nonzero when it finds a match and zero otherwise. The ' + 'idea here being that matches are "problems".' + ), + ) + parser.add_argument('-i', '--ignore-case', action='store_true') + parser.add_argument('--multiline', action='store_true') + parser.add_argument('--negate', action='store_true') + parser.add_argument('pattern', help='python regex pattern.') + parser.add_argument('filenames', nargs='*') + args = parser.parse_args(argv) + + flags = re.IGNORECASE if args.ignore_case else 0 + if args.multiline: + flags |= re.MULTILINE | re.DOTALL + + pattern = re.compile(args.pattern.encode(), flags) + + retv = 0 + process_fn = FNS[Choice(multiline=args.multiline, negate=args.negate)] + for filename in args.filenames: + retv |= process_fn(pattern, filename) + return retv + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/python.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/python.py new file mode 100644 index 0000000..faa6029 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/python.py @@ -0,0 +1,214 @@ +import contextlib +import functools +import os +import sys +from typing import Dict +from typing import Generator +from typing import Optional +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import UNSET +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.parse_shebang import find_executable +from pre_commit.prefix import Prefix +from pre_commit.util import CalledProcessError +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b +from pre_commit.util import win_exe + +ENVIRONMENT_DIR = 'py_env' + + +@functools.lru_cache(maxsize=None) +def _version_info(exe: str) -> str: + prog = 'import sys;print(".".join(str(p) for p in sys.version_info))' + try: + return cmd_output(exe, '-S', '-c', prog)[1].strip() + except CalledProcessError: + return f'<>' + + +def _read_pyvenv_cfg(filename: str) -> Dict[str, str]: + ret = {} + with open(filename, encoding='UTF-8') as f: + for line in f: + try: + k, v = line.split('=') + except ValueError: # blank line / comment / etc. + continue + else: + ret[k.strip()] = v.strip() + return ret + + +def bin_dir(venv: str) -> str: + """On windows there's a different directory for the virtualenv""" + bin_part = 'Scripts' if os.name == 'nt' else 'bin' + return os.path.join(venv, bin_part) + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('PIP_DISABLE_PIP_VERSION_CHECK', '1'), + ('PYTHONHOME', UNSET), + ('VIRTUAL_ENV', venv), + ('PATH', (bin_dir(venv), os.pathsep, Var('PATH'))), + ) + + +def _find_by_py_launcher( + version: str, +) -> Optional[str]: # pragma: no cover (windows only) + if version.startswith('python'): + num = version[len('python'):] + cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)') + env = dict(os.environ, PYTHONIOENCODING='UTF-8') + try: + return cmd_output(*cmd, env=env)[1].strip() + except CalledProcessError: + pass + return None + + +def _find_by_sys_executable() -> Optional[str]: + def _norm(path: str) -> Optional[str]: + _, exe = os.path.split(path.lower()) + exe, _, _ = exe.partition('.exe') + if exe not in {'python', 'pythonw'} and find_executable(exe): + return exe + return None + + # On linux, I see these common sys.executables: + # + # system `python`: /usr/bin/python -> python2.7 + # system `python2`: /usr/bin/python2 -> python2.7 + # virtualenv v: v/bin/python (will not return from this loop) + # virtualenv v -ppython2: v/bin/python -> python2 + # virtualenv v -ppython2.7: v/bin/python -> python2.7 + # virtualenv v -ppypy: v/bin/python -> v/bin/pypy + for path in (sys.executable, os.path.realpath(sys.executable)): + exe = _norm(path) + if exe: + return exe + return None + + +@functools.lru_cache(maxsize=1) +def get_default_version() -> str: # pragma: no cover (platform dependent) + # First attempt from `sys.executable` (or the realpath) + exe = _find_by_sys_executable() + if exe: + return exe + + # Next try the `pythonX.X` executable + exe = f'python{sys.version_info[0]}.{sys.version_info[1]}' + if find_executable(exe): + return exe + + if _find_by_py_launcher(exe): + return exe + + # We tried! + return C.DEFAULT + + +def _sys_executable_matches(version: str) -> bool: + if version == 'python': + return True + elif not version.startswith('python'): + return False + + try: + info = tuple(int(p) for p in version[len('python'):].split('.')) + except ValueError: + return False + + return sys.version_info[:len(info)] == info + + +def norm_version(version: str) -> Optional[str]: + if version == C.DEFAULT: # use virtualenv's default + return None + elif _sys_executable_matches(version): # virtualenv defaults to our exe + return None + + if os.name == 'nt': # pragma: no cover (windows) + version_exec = _find_by_py_launcher(version) + if version_exec: + return version_exec + + # Try looking up by name + version_exec = find_executable(version) + if version_exec and version_exec != version: + return version_exec + + # Otherwise assume it is a path + return os.path.expanduser(version) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) + envdir = prefix.path(directory) + with envcontext(get_env_patch(envdir)): + yield + + +def healthy(prefix: Prefix, language_version: str) -> bool: + directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) + envdir = prefix.path(directory) + pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg') + + # created with "old" virtualenv + if not os.path.exists(pyvenv_cfg): + return False + + exe_name = win_exe('python') + py_exe = prefix.path(bin_dir(envdir), exe_name) + cfg = _read_pyvenv_cfg(pyvenv_cfg) + + return ( + 'version_info' in cfg and + # always use uncached lookup here in case we replaced an unhealthy env + _version_info.__wrapped__(py_exe) == cfg['version_info'] and ( + 'base-executable' not in cfg or + _version_info(cfg['base-executable']) == cfg['version_info'] + ) + ) + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + venv_cmd = [sys.executable, '-mvirtualenv', envdir] + python = norm_version(version) + if python is not None: + venv_cmd.extend(('-p', python)) + install_cmd = ('python', '-mpip', 'install', '.', *additional_dependencies) + + with clean_path_on_failure(envdir): + cmd_output_b(*venv_cmd, cwd='/') + with in_env(prefix, version): + helpers.run_setup_cmd(prefix, install_cmd) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/r.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/r.py new file mode 100644 index 0000000..e034e39 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/r.py @@ -0,0 +1,155 @@ +import contextlib +import os +import shlex +import shutil +from typing import Generator +from typing import Sequence +from typing import Tuple + +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import UNSET +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output_b + +ENVIRONMENT_DIR = 'renv' +RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('R_PROFILE_USER', os.path.join(venv, 'activate.R')), + ('RENV_PROJECT', UNSET), + ) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + envdir = _get_env_dir(prefix, language_version) + with envcontext(get_env_patch(envdir)): + yield + + +def _get_env_dir(prefix: Prefix, version: str) -> str: + return prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + + +def _prefix_if_non_local_file_entry( + entry: Sequence[str], + prefix: Prefix, + src: str, +) -> Sequence[str]: + if entry[1] == '-e': + return entry[1:] + else: + if src == 'local': + path = entry[1] + else: + path = prefix.path(entry[1]) + return (path,) + + +def _rscript_exec() -> str: + return os.path.join(os.getenv('R_HOME', ''), 'Rscript') + + +def _entry_validate(entry: Sequence[str]) -> None: + """ + Allowed entries: + # Rscript -e expr + # Rscript path/to/file + """ + if entry[0] != 'Rscript': + raise ValueError('entry must start with `Rscript`.') + + if entry[1] == '-e': + if len(entry) > 3: + raise ValueError('You can supply at most one expression.') + elif len(entry) > 2: + raise ValueError( + 'The only valid syntax is `Rscript -e {expr}`', + 'or `Rscript path/to/hook/script`', + ) + + +def _cmd_from_hook(hook: Hook) -> Tuple[str, ...]: + entry = shlex.split(hook.entry) + _entry_validate(entry) + + return ( + *entry[:1], *RSCRIPT_OPTS, + *_prefix_if_non_local_file_entry(entry, hook.prefix, hook.src), + *hook.args, + ) + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + env_dir = _get_env_dir(prefix, version) + with clean_path_on_failure(env_dir): + os.makedirs(env_dir, exist_ok=True) + shutil.copy(prefix.path('renv.lock'), env_dir) + shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv')) + + cmd_output_b( + _rscript_exec(), '--vanilla', '-e', + f"""\ + prefix_dir <- {prefix.prefix_dir!r} + options( + repos = c(CRAN = "https://cran.rstudio.com"), + renv.consent = TRUE + ) + source("renv/activate.R") + renv::restore() + activate_statement <- paste0( + 'suppressWarnings({{', + 'old <- setwd("', getwd(), '"); ', + 'source("renv/activate.R"); ', + 'setwd(old); ', + 'renv::load("', getwd(), '");}})' + ) + writeLines(activate_statement, 'activate.R') + is_package <- tryCatch( + {{ + path_desc <- file.path(prefix_dir, 'DESCRIPTION') + suppressWarnings(desc <- read.dcf(path_desc)) + "Package" %in% colnames(desc) + }}, + error = function(...) FALSE + ) + if (is_package) {{ + renv::install(prefix_dir) + }} + """, + cwd=env_dir, + ) + if additional_dependencies: + with in_env(prefix, version): + cmd_output_b( + _rscript_exec(), *RSCRIPT_OPTS, '-e', + 'renv::install(commandArgs(trailingOnly = TRUE))', + *additional_dependencies, + cwd=env_dir, + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs( + hook, _cmd_from_hook(hook), file_args, color=color, + ) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/ruby.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/ruby.py new file mode 100644 index 0000000..81bc954 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/ruby.py @@ -0,0 +1,151 @@ +import contextlib +import functools +import os.path +import shutil +import tarfile +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import UNSET +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import CalledProcessError +from pre_commit.util import clean_path_on_failure +from pre_commit.util import resource_bytesio + +ENVIRONMENT_DIR = 'rbenv' +healthy = helpers.basic_healthy + + +@functools.lru_cache(maxsize=1) +def get_default_version() -> str: + if all(helpers.exe_exists(exe) for exe in ('ruby', 'gem')): + return 'system' + else: + return C.DEFAULT + + +def get_env_patch( + venv: str, + language_version: str, +) -> PatchesT: + patches: PatchesT = ( + ('GEM_HOME', os.path.join(venv, 'gems')), + ('GEM_PATH', UNSET), + ('BUNDLE_IGNORE_CONFIG', '1'), + ) + if language_version == 'system': + patches += ( + ( + 'PATH', ( + os.path.join(venv, 'gems', 'bin'), os.pathsep, + Var('PATH'), + ), + ), + ) + else: # pragma: win32 no cover + patches += ( + ('RBENV_ROOT', venv), + ( + 'PATH', ( + os.path.join(venv, 'gems', 'bin'), os.pathsep, + os.path.join(venv, 'shims'), os.pathsep, + os.path.join(venv, 'bin'), os.pathsep, Var('PATH'), + ), + ), + ) + if language_version not in {'system', 'default'}: # pragma: win32 no cover + patches += (('RBENV_VERSION', language_version),) + + return patches + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + envdir = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, language_version), + ) + with envcontext(get_env_patch(envdir, language_version)): + yield + + +def _extract_resource(filename: str, dest: str) -> None: + with resource_bytesio(filename) as bio: + with tarfile.open(fileobj=bio) as tf: + tf.extractall(dest) + + +def _install_rbenv( + prefix: Prefix, + version: str, +) -> None: # pragma: win32 no cover + directory = helpers.environment_dir(ENVIRONMENT_DIR, version) + + _extract_resource('rbenv.tar.gz', prefix.path('.')) + shutil.move(prefix.path('rbenv'), prefix.path(directory)) + + # Only install ruby-build if the version is specified + if version != C.DEFAULT: + plugins_dir = prefix.path(directory, 'plugins') + _extract_resource('ruby-download.tar.gz', plugins_dir) + _extract_resource('ruby-build.tar.gz', plugins_dir) + + +def _install_ruby( + prefix: Prefix, + version: str, +) -> None: # pragma: win32 no cover + try: + helpers.run_setup_cmd(prefix, ('rbenv', 'download', version)) + except CalledProcessError: # pragma: no cover (usually find with download) + # Failed to download from mirror for some reason, build it instead + helpers.run_setup_cmd(prefix, ('rbenv', 'install', version)) + + +def install_environment( + prefix: Prefix, version: str, additional_dependencies: Sequence[str], +) -> None: + additional_dependencies = tuple(additional_dependencies) + directory = helpers.environment_dir(ENVIRONMENT_DIR, version) + with clean_path_on_failure(prefix.path(directory)): + if version != 'system': # pragma: win32 no cover + _install_rbenv(prefix, version) + with in_env(prefix, version): + # Need to call this before installing so rbenv's directories + # are set up + helpers.run_setup_cmd(prefix, ('rbenv', 'init', '-')) + if version != C.DEFAULT: + _install_ruby(prefix, version) + # Need to call this after installing to set up the shims + helpers.run_setup_cmd(prefix, ('rbenv', 'rehash')) + + with in_env(prefix, version): + helpers.run_setup_cmd( + prefix, ('gem', 'build', *prefix.star('.gemspec')), + ) + helpers.run_setup_cmd( + prefix, + ( + 'gem', 'install', + '--no-document', '--no-format-executable', + *prefix.star('.gem'), *additional_dependencies, + ), + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/rust.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/rust.py new file mode 100644 index 0000000..7ea3f54 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/rust.py @@ -0,0 +1,106 @@ +import contextlib +import os.path +from typing import Generator +from typing import Sequence +from typing import Set +from typing import Tuple + +import toml + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output_b + +ENVIRONMENT_DIR = 'rustenv' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(target_dir: str) -> PatchesT: + return ( + ('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env(prefix: Prefix) -> Generator[None, None, None]: + target_dir = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), + ) + with envcontext(get_env_patch(target_dir)): + yield + + +def _add_dependencies( + cargo_toml_path: str, + additional_dependencies: Set[str], +) -> None: + with open(cargo_toml_path, 'r+') as f: + cargo_toml = toml.load(f) + cargo_toml.setdefault('dependencies', {}) + for dep in additional_dependencies: + name, _, spec = dep.partition(':') + cargo_toml['dependencies'][name] = spec or '*' + f.seek(0) + toml.dump(cargo_toml, f) + f.truncate() + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + helpers.assert_version_default('rust', version) + directory = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), + ) + + # There are two cases where we might want to specify more dependencies: + # as dependencies for the library being built, and as binary packages + # to be `cargo install`'d. + # + # Unlike e.g. Python, if we just `cargo install` a library, it won't be + # used for compilation. And if we add a crate providing a binary to the + # `Cargo.toml`, the binary won't be built. + # + # Because of this, we allow specifying "cli" dependencies by prefixing + # with 'cli:'. + cli_deps = { + dep for dep in additional_dependencies if dep.startswith('cli:') + } + lib_deps = set(additional_dependencies) - cli_deps + + if len(lib_deps) > 0: + _add_dependencies(prefix.path('Cargo.toml'), lib_deps) + + with clean_path_on_failure(directory): + packages_to_install: Set[Tuple[str, ...]] = {('--path', '.')} + for cli_dep in cli_deps: + cli_dep = cli_dep[len('cli:'):] + package, _, version = cli_dep.partition(':') + if version != '': + packages_to_install.add((package, '--version', version)) + else: + packages_to_install.add((package,)) + + for args in packages_to_install: + cmd_output_b( + 'cargo', 'install', '--bins', '--root', directory, *args, + cwd=prefix.prefix_dir, + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/script.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/script.py new file mode 100644 index 0000000..a5e1365 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/script.py @@ -0,0 +1,19 @@ +from typing import Sequence +from typing import Tuple + +from pre_commit.hook import Hook +from pre_commit.languages import helpers + +ENVIRONMENT_DIR = None +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy +install_environment = helpers.no_install + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:]) + return helpers.run_xargs(hook, cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/swift.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/swift.py new file mode 100644 index 0000000..66aadc8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/swift.py @@ -0,0 +1,64 @@ +import contextlib +import os +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output_b + +ENVIRONMENT_DIR = 'swift_env' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy +BUILD_DIR = '.build' +BUILD_CONFIG = 'release' + + +def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover + bin_path = os.path.join(venv, BUILD_DIR, BUILD_CONFIG) + return (('PATH', (bin_path, os.pathsep, Var('PATH'))),) + + +@contextlib.contextmanager # pragma: win32 no cover +def in_env(prefix: Prefix) -> Generator[None, None, None]: + envdir = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), + ) + with envcontext(get_env_patch(envdir)): + yield + + +def install_environment( + prefix: Prefix, version: str, additional_dependencies: Sequence[str], +) -> None: # pragma: win32 no cover + helpers.assert_version_default('swift', version) + helpers.assert_no_additional_deps('swift', additional_dependencies) + directory = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), + ) + + # Build the swift package + with clean_path_on_failure(directory): + os.mkdir(directory) + cmd_output_b( + 'swift', 'build', + '-C', prefix.prefix_dir, + '-c', BUILD_CONFIG, + '--build-path', os.path.join(directory, BUILD_DIR), + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: # pragma: win32 no cover + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/languages/system.py b/.venv/lib/python3.8/site-packages/pre_commit/languages/system.py new file mode 100644 index 0000000..139f45d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/languages/system.py @@ -0,0 +1,19 @@ +from typing import Sequence +from typing import Tuple + +from pre_commit.hook import Hook +from pre_commit.languages import helpers + + +ENVIRONMENT_DIR = None +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy +install_environment = helpers.no_install + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/logging_handler.py b/.venv/lib/python3.8/site-packages/pre_commit/logging_handler.py new file mode 100644 index 0000000..ba05295 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/logging_handler.py @@ -0,0 +1,40 @@ +import contextlib +import logging +from typing import Generator + +from pre_commit import color +from pre_commit import output + +logger = logging.getLogger('pre_commit') + +LOG_LEVEL_COLORS = { + 'DEBUG': '', + 'INFO': '', + 'WARNING': color.YELLOW, + 'ERROR': color.RED, +} + + +class LoggingHandler(logging.Handler): + def __init__(self, use_color: bool) -> None: + super().__init__() + self.use_color = use_color + + def emit(self, record: logging.LogRecord) -> None: + level_msg = color.format_color( + f'[{record.levelname}]', + LOG_LEVEL_COLORS[record.levelname], + self.use_color, + ) + output.write_line(f'{level_msg} {record.getMessage()}') + + +@contextlib.contextmanager +def logging_handler(use_color: bool) -> Generator[None, None, None]: + handler = LoggingHandler(use_color) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + try: + yield + finally: + logger.removeHandler(handler) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/main.py b/.venv/lib/python3.8/site-packages/pre_commit/main.py new file mode 100644 index 0000000..f1e8d03 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/main.py @@ -0,0 +1,414 @@ +import argparse +import logging +import os +import sys +from typing import Any +from typing import Optional +from typing import Sequence +from typing import Union + +import pre_commit.constants as C +from pre_commit import git +from pre_commit.color import add_color_option +from pre_commit.commands.autoupdate import autoupdate +from pre_commit.commands.clean import clean +from pre_commit.commands.gc import gc +from pre_commit.commands.hook_impl import hook_impl +from pre_commit.commands.init_templatedir import init_templatedir +from pre_commit.commands.install_uninstall import install +from pre_commit.commands.install_uninstall import install_hooks +from pre_commit.commands.install_uninstall import uninstall +from pre_commit.commands.migrate_config import migrate_config +from pre_commit.commands.run import run +from pre_commit.commands.sample_config import sample_config +from pre_commit.commands.try_repo import try_repo +from pre_commit.error_handler import error_handler +from pre_commit.logging_handler import logging_handler +from pre_commit.store import Store + + +logger = logging.getLogger('pre_commit') + +# https://github.com/pre-commit/pre-commit/issues/217 +# On OSX, making a virtualenv using pyvenv at . causes `virtualenv` and `pip` +# to install packages to the wrong place. We don't want anything to deal with +# pyvenv +os.environ.pop('__PYVENV_LAUNCHER__', None) + + +COMMANDS_NO_GIT = {'clean', 'gc', 'init-templatedir', 'sample-config'} + + +def _add_config_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '-c', '--config', default=C.CONFIG_FILE, + help='Path to alternate config file', + ) + + +class AppendReplaceDefault(argparse.Action): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.appended = False + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[str], None], + option_string: Optional[str] = None, + ) -> None: + if not self.appended: + setattr(namespace, self.dest, []) + self.appended = True + getattr(namespace, self.dest).append(values) + + +def _add_hook_type_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '-t', '--hook-type', choices=( + 'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg', + 'commit-msg', 'post-commit', 'post-checkout', 'post-merge', + 'post-rewrite', + ), + action=AppendReplaceDefault, + default=['pre-commit'], + dest='hook_types', + ) + + +def _add_run_options(parser: argparse.ArgumentParser) -> None: + parser.add_argument('hook', nargs='?', help='A single hook-id to run') + parser.add_argument('--verbose', '-v', action='store_true', default=False) + mutex_group = parser.add_mutually_exclusive_group(required=False) + mutex_group.add_argument( + '--all-files', '-a', action='store_true', default=False, + help='Run on all the files in the repo.', + ) + mutex_group.add_argument( + '--files', nargs='*', default=[], + help='Specific filenames to run hooks on.', + ) + parser.add_argument( + '--show-diff-on-failure', action='store_true', + help='When hooks fail, run `git diff` directly afterward.', + ) + parser.add_argument( + '--hook-stage', choices=C.STAGES, default='commit', + help='The stage during which the hook is fired. One of %(choices)s', + ) + parser.add_argument( + '--remote-branch', help='Remote branch ref used by `git push`.', + ) + parser.add_argument( + '--local-branch', help='Local branch ref used by `git push`.', + ) + parser.add_argument( + '--from-ref', '--source', '-s', + help=( + '(for usage with `--from-ref`) -- this option represents the ' + 'original ref in a `from_ref...to_ref` diff expression. ' + 'For `pre-push` hooks, this represents the branch you are pushing ' + 'to. ' + 'For `post-checkout` hooks, this represents the branch that was ' + 'previously checked out.' + ), + ) + parser.add_argument( + '--to-ref', '--origin', '-o', + help=( + '(for usage with `--to-ref`) -- this option represents the ' + 'destination ref in a `from_ref...to_ref` diff expression. ' + 'For `pre-push` hooks, this represents the branch being pushed. ' + 'For `post-checkout` hooks, this represents the branch that is ' + 'now checked out.' + ), + ) + parser.add_argument( + '--commit-msg-filename', + help='Filename to check when running during `commit-msg`', + ) + parser.add_argument( + '--remote-name', help='Remote name used by `git push`.', + ) + parser.add_argument('--remote-url', help='Remote url used by `git push`.') + parser.add_argument( + '--checkout-type', + help=( + 'Indicates whether the checkout was a branch checkout ' + '(changing branches, flag=1) or a file checkout (retrieving a ' + 'file from the index, flag=0).' + ), + ) + parser.add_argument( + '--is-squash-merge', + help=( + 'During a post-merge hook, indicates whether the merge was a ' + 'squash merge' + ), + ) + parser.add_argument( + '--rewrite-command', + help=( + 'During a post-rewrite hook, specifies the command that invoked ' + 'the rewrite' + ), + ) + + +def _adjust_args_and_chdir(args: argparse.Namespace) -> None: + # `--config` was specified relative to the non-root working directory + if os.path.exists(args.config): + args.config = os.path.abspath(args.config) + if args.command in {'run', 'try-repo'}: + args.files = [os.path.abspath(filename) for filename in args.files] + if args.command == 'try-repo' and os.path.exists(args.repo): + args.repo = os.path.abspath(args.repo) + + toplevel = git.get_root() + os.chdir(toplevel) + + args.config = os.path.relpath(args.config) + if args.command in {'run', 'try-repo'}: + args.files = [os.path.relpath(filename) for filename in args.files] + if args.command == 'try-repo' and os.path.exists(args.repo): + args.repo = os.path.relpath(args.repo) + + +def main(argv: Optional[Sequence[str]] = None) -> int: + argv = argv if argv is not None else sys.argv[1:] + parser = argparse.ArgumentParser(prog='pre-commit') + + # https://stackoverflow.com/a/8521644/812183 + parser.add_argument( + '-V', '--version', + action='version', + version=f'%(prog)s {C.VERSION}', + ) + + subparsers = parser.add_subparsers(dest='command') + + autoupdate_parser = subparsers.add_parser( + 'autoupdate', + help="Auto-update pre-commit config to the latest repos' versions.", + ) + add_color_option(autoupdate_parser) + _add_config_option(autoupdate_parser) + autoupdate_parser.add_argument( + '--bleeding-edge', action='store_true', + help=( + 'Update to the bleeding edge of `master` instead of the latest ' + 'tagged version (the default behavior).' + ), + ) + autoupdate_parser.add_argument( + '--freeze', action='store_true', + help='Store "frozen" hashes in `rev` instead of tag names', + ) + autoupdate_parser.add_argument( + '--repo', dest='repos', action='append', metavar='REPO', + help='Only update this repository -- may be specified multiple times.', + ) + + clean_parser = subparsers.add_parser( + 'clean', help='Clean out pre-commit files.', + ) + add_color_option(clean_parser) + _add_config_option(clean_parser) + + hook_impl_parser = subparsers.add_parser('hook-impl') + add_color_option(hook_impl_parser) + _add_config_option(hook_impl_parser) + hook_impl_parser.add_argument('--hook-type') + hook_impl_parser.add_argument('--hook-dir') + hook_impl_parser.add_argument( + '--skip-on-missing-config', action='store_true', + ) + hook_impl_parser.add_argument(dest='rest', nargs=argparse.REMAINDER) + + gc_parser = subparsers.add_parser('gc', help='Clean unused cached repos.') + add_color_option(gc_parser) + _add_config_option(gc_parser) + + init_templatedir_parser = subparsers.add_parser( + 'init-templatedir', + help=( + 'Install hook script in a directory intended for use with ' + '`git config init.templateDir`.' + ), + ) + add_color_option(init_templatedir_parser) + _add_config_option(init_templatedir_parser) + init_templatedir_parser.add_argument( + 'directory', help='The directory in which to write the hook script.', + ) + init_templatedir_parser.add_argument( + '--no-allow-missing-config', + action='store_false', + dest='allow_missing_config', + help='Assume cloned repos should have a `pre-commit` config.', + ) + _add_hook_type_option(init_templatedir_parser) + + install_parser = subparsers.add_parser( + 'install', help='Install the pre-commit script.', + ) + add_color_option(install_parser) + _add_config_option(install_parser) + install_parser.add_argument( + '-f', '--overwrite', action='store_true', + help='Overwrite existing hooks / remove migration mode.', + ) + install_parser.add_argument( + '--install-hooks', action='store_true', + help=( + 'Whether to install hook environments for all environments ' + 'in the config file.' + ), + ) + _add_hook_type_option(install_parser) + install_parser.add_argument( + '--allow-missing-config', action='store_true', default=False, + help=( + 'Whether to allow a missing `pre-commit` configuration file ' + 'or exit with a failure code.' + ), + ) + + install_hooks_parser = subparsers.add_parser( + 'install-hooks', + help=( + 'Install hook environments for all environments in the config ' + 'file. You may find `pre-commit install --install-hooks` more ' + 'useful.' + ), + ) + add_color_option(install_hooks_parser) + _add_config_option(install_hooks_parser) + + migrate_config_parser = subparsers.add_parser( + 'migrate-config', + help='Migrate list configuration to new map configuration.', + ) + add_color_option(migrate_config_parser) + _add_config_option(migrate_config_parser) + + run_parser = subparsers.add_parser('run', help='Run hooks.') + add_color_option(run_parser) + _add_config_option(run_parser) + _add_run_options(run_parser) + + sample_config_parser = subparsers.add_parser( + 'sample-config', help=f'Produce a sample {C.CONFIG_FILE} file', + ) + add_color_option(sample_config_parser) + _add_config_option(sample_config_parser) + + try_repo_parser = subparsers.add_parser( + 'try-repo', + help='Try the hooks in a repository, useful for developing new hooks.', + ) + add_color_option(try_repo_parser) + _add_config_option(try_repo_parser) + try_repo_parser.add_argument( + 'repo', help='Repository to source hooks from.', + ) + try_repo_parser.add_argument( + '--ref', '--rev', + help=( + 'Manually select a rev to run against, otherwise the `HEAD` ' + 'revision will be used.' + ), + ) + _add_run_options(try_repo_parser) + + uninstall_parser = subparsers.add_parser( + 'uninstall', help='Uninstall the pre-commit script.', + ) + add_color_option(uninstall_parser) + _add_config_option(uninstall_parser) + _add_hook_type_option(uninstall_parser) + + help = subparsers.add_parser( + 'help', help='Show help for a specific command.', + ) + help.add_argument('help_cmd', nargs='?', help='Command to show help for.') + + # argparse doesn't really provide a way to use a `default` subparser + if len(argv) == 0: + argv = ['run'] + args = parser.parse_args(argv) + + if args.command == 'help' and args.help_cmd: + parser.parse_args([args.help_cmd, '--help']) + elif args.command == 'help': + parser.parse_args(['--help']) + + with error_handler(), logging_handler(args.color): + git.check_for_cygwin_mismatch() + + if args.command not in COMMANDS_NO_GIT: + _adjust_args_and_chdir(args) + + store = Store() + store.mark_config_used(args.config) + + if args.command == 'autoupdate': + return autoupdate( + args.config, store, + tags_only=not args.bleeding_edge, + freeze=args.freeze, + repos=args.repos, + ) + elif args.command == 'clean': + return clean(store) + elif args.command == 'gc': + return gc(store) + elif args.command == 'hook-impl': + return hook_impl( + store, + config=args.config, + color=args.color, + hook_type=args.hook_type, + hook_dir=args.hook_dir, + skip_on_missing_config=args.skip_on_missing_config, + args=args.rest[1:], + ) + elif args.command == 'install': + return install( + args.config, store, + hook_types=args.hook_types, + overwrite=args.overwrite, + hooks=args.install_hooks, + skip_on_missing_config=args.allow_missing_config, + ) + elif args.command == 'init-templatedir': + return init_templatedir( + args.config, store, args.directory, + hook_types=args.hook_types, + skip_on_missing_config=args.allow_missing_config, + ) + elif args.command == 'install-hooks': + return install_hooks(args.config, store) + elif args.command == 'migrate-config': + return migrate_config(args.config) + elif args.command == 'run': + return run(args.config, store, args) + elif args.command == 'sample-config': + return sample_config() + elif args.command == 'try-repo': + return try_repo(args) + elif args.command == 'uninstall': + return uninstall(hook_types=args.hook_types) + else: + raise NotImplementedError( + f'Command {args.command} not implemented.', + ) + + raise AssertionError( + f'Command {args.command} failed to exit with a returncode', + ) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__init__.py b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..7dceee9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/check_hooks_apply.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/check_hooks_apply.cpython-38.pyc new file mode 100644 index 0000000..5430ead Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/check_hooks_apply.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/check_useless_excludes.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/check_useless_excludes.cpython-38.pyc new file mode 100644 index 0000000..3a445d1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/check_useless_excludes.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/identity.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/identity.cpython-38.pyc new file mode 100644 index 0000000..7f4b261 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/__pycache__/identity.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/check_hooks_apply.py b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/check_hooks_apply.py new file mode 100644 index 0000000..a6eb0e0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/check_hooks_apply.py @@ -0,0 +1,42 @@ +import argparse +from typing import Optional +from typing import Sequence + +import pre_commit.constants as C +from pre_commit import git +from pre_commit.clientlib import load_config +from pre_commit.commands.run import Classifier +from pre_commit.repository import all_hooks +from pre_commit.store import Store + + +def check_all_hooks_match_files(config_file: str) -> int: + config = load_config(config_file) + classifier = Classifier.from_config( + git.get_all_files(), config['files'], config['exclude'], + ) + retv = 0 + + for hook in all_hooks(config, Store()): + if hook.always_run or hook.language == 'fail': + continue + elif not classifier.filenames_for_hook(hook): + print(f'{hook.id} does not apply to this repository') + retv = 1 + + return retv + + +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE]) + args = parser.parse_args(argv) + + retv = 0 + for filename in args.filenames: + retv |= check_all_hooks_match_files(filename) + return retv + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/check_useless_excludes.py b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/check_useless_excludes.py new file mode 100644 index 0000000..60870f8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/check_useless_excludes.py @@ -0,0 +1,80 @@ +import argparse +import re +from typing import Optional +from typing import Sequence + +from cfgv import apply_defaults + +import pre_commit.constants as C +from pre_commit import git +from pre_commit.clientlib import load_config +from pre_commit.clientlib import MANIFEST_HOOK_DICT +from pre_commit.commands.run import Classifier + + +def exclude_matches_any( + filenames: Sequence[str], + include: str, + exclude: str, +) -> bool: + if exclude == '^$': + return True + include_re, exclude_re = re.compile(include), re.compile(exclude) + for filename in filenames: + if include_re.search(filename) and exclude_re.search(filename): + return True + return False + + +def check_useless_excludes(config_file: str) -> int: + config = load_config(config_file) + filenames = git.get_all_files() + classifier = Classifier.from_config( + filenames, config['files'], config['exclude'], + ) + retv = 0 + + exclude = config['exclude'] + if not exclude_matches_any(filenames, '', exclude): + print( + f'The global exclude pattern {exclude!r} does not match any files', + ) + retv = 1 + + for repo in config['repos']: + for hook in repo['hooks']: + # the default of manifest hooks is `types: [file]` but we may + # be configuring a symlink hook while there's a broken symlink + hook.setdefault('types', []) + # Not actually a manifest dict, but this more accurately reflects + # the defaults applied during runtime + hook = apply_defaults(hook, MANIFEST_HOOK_DICT) + names = classifier.filenames + types = hook['types'] + types_or = hook['types_or'] + exclude_types = hook['exclude_types'] + names = classifier.by_types(names, types, types_or, exclude_types) + include, exclude = hook['files'], hook['exclude'] + if not exclude_matches_any(names, include, exclude): + print( + f'The exclude pattern {exclude!r} for {hook["id"]} does ' + f'not match any files', + ) + retv = 1 + + return retv + + +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE]) + args = parser.parse_args(argv) + + retv = 0 + for filename in args.filenames: + retv |= check_useless_excludes(filename) + return retv + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/identity.py b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/identity.py new file mode 100644 index 0000000..12eb02f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/meta_hooks/identity.py @@ -0,0 +1,16 @@ +import sys +from typing import Optional +from typing import Sequence + +from pre_commit import output + + +def main(argv: Optional[Sequence[str]] = None) -> int: + argv = argv if argv is not None else sys.argv[1:] + for arg in argv: + output.write_line(arg) + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/output.py b/.venv/lib/python3.8/site-packages/pre_commit/output.py new file mode 100644 index 0000000..24f9d84 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/output.py @@ -0,0 +1,32 @@ +import contextlib +import sys +from typing import Any +from typing import IO +from typing import Optional + + +def write(s: str, stream: IO[bytes] = sys.stdout.buffer) -> None: + stream.write(s.encode()) + stream.flush() + + +def write_line_b( + s: Optional[bytes] = None, + stream: IO[bytes] = sys.stdout.buffer, + logfile_name: Optional[str] = None, +) -> None: + with contextlib.ExitStack() as exit_stack: + output_streams = [stream] + if logfile_name: + stream = exit_stack.enter_context(open(logfile_name, 'ab')) + output_streams.append(stream) + + for output_stream in output_streams: + if s is not None: + output_stream.write(s) + output_stream.write(b'\n') + output_stream.flush() + + +def write_line(s: Optional[str] = None, **kwargs: Any) -> None: + write_line_b(s.encode() if s is not None else s, **kwargs) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/parse_shebang.py b/.venv/lib/python3.8/site-packages/pre_commit/parse_shebang.py new file mode 100644 index 0000000..d344a1d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/parse_shebang.py @@ -0,0 +1,84 @@ +import os.path +from typing import Mapping +from typing import Optional +from typing import Tuple +from typing import TYPE_CHECKING + +from identify.identify import parse_shebang_from_file + +if TYPE_CHECKING: + from typing import NoReturn + + +class ExecutableNotFoundError(OSError): + def to_output(self) -> Tuple[int, bytes, None]: + return (1, self.args[0].encode(), None) + + +def parse_filename(filename: str) -> Tuple[str, ...]: + if not os.path.exists(filename): + return () + else: + return parse_shebang_from_file(filename) + + +def find_executable( + exe: str, _environ: Optional[Mapping[str, str]] = None, +) -> Optional[str]: + exe = os.path.normpath(exe) + if os.sep in exe: + return exe + + environ = _environ if _environ is not None else os.environ + + if 'PATHEXT' in environ: + exts = environ['PATHEXT'].split(os.pathsep) + possible_exe_names = tuple(f'{exe}{ext}' for ext in exts) + (exe,) + else: + possible_exe_names = (exe,) + + for path in environ.get('PATH', '').split(os.pathsep): + for possible_exe_name in possible_exe_names: + joined = os.path.join(path, possible_exe_name) + if os.path.isfile(joined) and os.access(joined, os.X_OK): + return joined + else: + return None + + +def normexe(orig: str) -> str: + def _error(msg: str) -> 'NoReturn': + raise ExecutableNotFoundError(f'Executable `{orig}` {msg}') + + if os.sep not in orig and (not os.altsep or os.altsep not in orig): + exe = find_executable(orig) + if exe is None: + _error('not found') + return exe + elif os.path.isdir(orig): + _error('is a directory') + elif not os.path.isfile(orig): + _error('not found') + elif not os.access(orig, os.X_OK): # pragma: win32 no cover + _error('is not executable') + else: + return orig + + +def normalize_cmd(cmd: Tuple[str, ...]) -> Tuple[str, ...]: + """Fixes for the following issues on windows + - https://bugs.python.org/issue8557 + - windows does not parse shebangs + + This function also makes deep-path shebangs work just fine + """ + # Use PATH to determine the executable + exe = normexe(cmd[0]) + + # Figure out the shebang from the resulting command + cmd = parse_filename(exe) + (exe,) + cmd[1:] + + # This could have given us back another bare executable + exe = normexe(cmd[0]) + + return (exe,) + cmd[1:] diff --git a/.venv/lib/python3.8/site-packages/pre_commit/prefix.py b/.venv/lib/python3.8/site-packages/pre_commit/prefix.py new file mode 100644 index 0000000..0e3ebbd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/prefix.py @@ -0,0 +1,17 @@ +import os.path +from typing import NamedTuple +from typing import Tuple + + +class Prefix(NamedTuple): + prefix_dir: str + + def path(self, *parts: str) -> str: + return os.path.normpath(os.path.join(self.prefix_dir, *parts)) + + def exists(self, *parts: str) -> bool: + return os.path.exists(self.path(*parts)) + + def star(self, end: str) -> Tuple[str, ...]: + paths = os.listdir(self.prefix_dir) + return tuple(path for path in paths if path.endswith(end)) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/repository.py b/.venv/lib/python3.8/site-packages/pre_commit/repository.py new file mode 100644 index 0000000..15827dd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/repository.py @@ -0,0 +1,232 @@ +import json +import logging +import os +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.clientlib import load_manifest +from pre_commit.clientlib import LOCAL +from pre_commit.clientlib import META +from pre_commit.hook import Hook +from pre_commit.languages.all import languages +from pre_commit.languages.helpers import environment_dir +from pre_commit.prefix import Prefix +from pre_commit.store import Store +from pre_commit.util import parse_version +from pre_commit.util import rmtree + + +logger = logging.getLogger('pre_commit') + + +def _state(additional_deps: Sequence[str]) -> object: + return {'additional_dependencies': sorted(additional_deps)} + + +def _state_filename(prefix: Prefix, venv: str) -> str: + return prefix.path(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}') + + +def _read_state(prefix: Prefix, venv: str) -> Optional[object]: + filename = _state_filename(prefix, venv) + if not os.path.exists(filename): + return None + else: + with open(filename) as f: + return json.load(f) + + +def _write_state(prefix: Prefix, venv: str, state: object) -> None: + state_filename = _state_filename(prefix, venv) + staging = f'{state_filename}staging' + with open(staging, 'w') as state_file: + state_file.write(json.dumps(state)) + # Move the file into place atomically to indicate we've installed + os.replace(staging, state_filename) + + +def _hook_installed(hook: Hook) -> bool: + lang = languages[hook.language] + venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version) + return ( + venv is None or ( + ( + _read_state(hook.prefix, venv) == + _state(hook.additional_dependencies) + ) and + lang.healthy(hook.prefix, hook.language_version) + ) + ) + + +def _hook_install(hook: Hook) -> None: + logger.info(f'Installing environment for {hook.src}.') + logger.info('Once installed this environment will be reused.') + logger.info('This may take a few minutes...') + + lang = languages[hook.language] + assert lang.ENVIRONMENT_DIR is not None + venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version) + + # There's potentially incomplete cleanup from previous runs + # Clean it up! + if hook.prefix.exists(venv): + rmtree(hook.prefix.path(venv)) + + lang.install_environment( + hook.prefix, hook.language_version, hook.additional_dependencies, + ) + if not lang.healthy(hook.prefix, hook.language_version): + raise AssertionError( + f'BUG: expected environment for {hook.language} to be healthy() ' + f'immediately after install, please open an issue describing ' + f'your environment', + ) + # Write our state to indicate we're installed + _write_state(hook.prefix, venv, _state(hook.additional_dependencies)) + + +def _hook( + *hook_dicts: Dict[str, Any], + root_config: Dict[str, Any], +) -> Dict[str, Any]: + ret, rest = dict(hook_dicts[0]), hook_dicts[1:] + for dct in rest: + ret.update(dct) + + version = ret['minimum_pre_commit_version'] + if parse_version(version) > parse_version(C.VERSION): + logger.error( + f'The hook `{ret["id"]}` requires pre-commit version {version} ' + f'but version {C.VERSION} is installed. ' + f'Perhaps run `pip install --upgrade pre-commit`.', + ) + exit(1) + + lang = ret['language'] + if ret['language_version'] == C.DEFAULT: + ret['language_version'] = root_config['default_language_version'][lang] + if ret['language_version'] == C.DEFAULT: + ret['language_version'] = languages[lang].get_default_version() + + if not ret['stages']: + ret['stages'] = root_config['default_stages'] + + if languages[lang].ENVIRONMENT_DIR is None: + if ret['language_version'] != C.DEFAULT: + logger.error( + f'The hook `{ret["id"]}` specifies `language_version` but is ' + f'using language `{lang}` which does not install an ' + f'environment. ' + f'Perhaps you meant to use a specific language?', + ) + exit(1) + if ret['additional_dependencies']: + logger.error( + f'The hook `{ret["id"]}` specifies `additional_dependencies` ' + f'but is using language `{lang}` which does not install an ' + f'environment. ' + f'Perhaps you meant to use a specific language?', + ) + exit(1) + + return ret + + +def _non_cloned_repository_hooks( + repo_config: Dict[str, Any], + store: Store, + root_config: Dict[str, Any], +) -> Tuple[Hook, ...]: + def _prefix(language_name: str, deps: Sequence[str]) -> Prefix: + language = languages[language_name] + # pygrep / script / system / docker_image do not have + # environments so they work out of the current directory + if language.ENVIRONMENT_DIR is None: + return Prefix(os.getcwd()) + else: + return Prefix(store.make_local(deps)) + + return tuple( + Hook.create( + repo_config['repo'], + _prefix(hook['language'], hook['additional_dependencies']), + _hook(hook, root_config=root_config), + ) + for hook in repo_config['hooks'] + ) + + +def _cloned_repository_hooks( + repo_config: Dict[str, Any], + store: Store, + root_config: Dict[str, Any], +) -> Tuple[Hook, ...]: + repo, rev = repo_config['repo'], repo_config['rev'] + manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE) + by_id = {hook['id']: hook for hook in load_manifest(manifest_path)} + + for hook in repo_config['hooks']: + if hook['id'] not in by_id: + logger.error( + f'`{hook["id"]}` is not present in repository {repo}. ' + f'Typo? Perhaps it is introduced in a newer version? ' + f'Often `pre-commit autoupdate` fixes this.', + ) + exit(1) + + hook_dcts = [ + _hook(by_id[hook['id']], hook, root_config=root_config) + for hook in repo_config['hooks'] + ] + return tuple( + Hook.create( + repo_config['repo'], + Prefix(store.clone(repo, rev, hook['additional_dependencies'])), + hook, + ) + for hook in hook_dcts + ) + + +def _repository_hooks( + repo_config: Dict[str, Any], + store: Store, + root_config: Dict[str, Any], +) -> Tuple[Hook, ...]: + if repo_config['repo'] in {LOCAL, META}: + return _non_cloned_repository_hooks(repo_config, store, root_config) + else: + return _cloned_repository_hooks(repo_config, store, root_config) + + +def install_hook_envs(hooks: Sequence[Hook], store: Store) -> None: + def _need_installed() -> List[Hook]: + seen: Set[Tuple[Prefix, str, str, Tuple[str, ...]]] = set() + ret = [] + for hook in hooks: + if hook.install_key not in seen and not _hook_installed(hook): + ret.append(hook) + seen.add(hook.install_key) + return ret + + if not _need_installed(): + return + with store.exclusive_lock(): + # Another process may have already completed this work + for hook in _need_installed(): + _hook_install(hook) + + +def all_hooks(root_config: Dict[str, Any], store: Store) -> Tuple[Hook, ...]: + return tuple( + hook + for repo in root_config['repos'] + for hook in _repository_hooks(repo, store, root_config) + ) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/__init__.py b/.venv/lib/python3.8/site-packages/pre_commit/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/resources/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..72665b3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/resources/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/__pycache__/empty_template_setup.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pre_commit/resources/__pycache__/empty_template_setup.cpython-38.pyc new file mode 100644 index 0000000..23d0632 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/resources/__pycache__/empty_template_setup.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_.npmignore b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_.npmignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_.npmignore @@ -0,0 +1 @@ +* diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_Cargo.toml b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_Cargo.toml new file mode 100644 index 0000000..3dfeffa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "__fake_crate" +version = "0.0.0" + +[[bin]] +name = "__fake_cmd" +path = "main.rs" diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_LICENSE.renv b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_LICENSE.renv new file mode 100644 index 0000000..253c5d1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_LICENSE.renv @@ -0,0 +1,7 @@ +Copyright 2021 RStudio, PBC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_Makefile.PL b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_Makefile.PL new file mode 100644 index 0000000..45a0ba3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_Makefile.PL @@ -0,0 +1,6 @@ +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => "PreCommitPlaceholder", + VERSION => "0.0.1", +); diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_activate.R b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_activate.R new file mode 100644 index 0000000..d8d092c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_activate.R @@ -0,0 +1,440 @@ + +local({ + + # the requested version of renv + version <- "0.12.5" + + # the project directory + project <- getwd() + + # avoid recursion + if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA))) + return(invisible(TRUE)) + + # signal that we're loading renv during R startup + Sys.setenv("RENV_R_INITIALIZING" = "true") + on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) + + # signal that we've consented to use renv + options(renv.consent = TRUE) + + # load the 'utils' package eagerly -- this ensures that renv shims, which + # mask 'utils' packages, will come first on the search path + library(utils, lib.loc = .Library) + + # check to see if renv has already been loaded + if ("renv" %in% loadedNamespaces()) { + + # if renv has already been loaded, and it's the requested version of renv, + # nothing to do + spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") + if (identical(spec[["version"]], version)) + return(invisible(TRUE)) + + # otherwise, unload and attempt to load the correct version of renv + unloadNamespace("renv") + + } + + # load bootstrap tools + bootstrap <- function(version, library) { + + # attempt to download renv + tarball <- tryCatch(renv_bootstrap_download(version), error = identity) + if (inherits(tarball, "error")) + stop("failed to download renv ", version) + + # now attempt to install + status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) + if (inherits(status, "error")) + stop("failed to install renv ", version) + + } + + renv_bootstrap_tests_running <- function() { + getOption("renv.tests.running", default = FALSE) + } + + renv_bootstrap_repos <- function() { + + # check for repos override + repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) + if (!is.na(repos)) + return(repos) + + # if we're testing, re-use the test repositories + if (renv_bootstrap_tests_running()) + return(getOption("renv.tests.repos")) + + # retrieve current repos + repos <- getOption("repos") + + # ensure @CRAN@ entries are resolved + repos[repos == "@CRAN@"] <- "https://cloud.r-project.org" + + # add in renv.bootstrap.repos if set + default <- c(CRAN = "https://cloud.r-project.org") + extra <- getOption("renv.bootstrap.repos", default = default) + repos <- c(repos, extra) + + # remove duplicates that might've snuck in + dupes <- duplicated(repos) | duplicated(names(repos)) + repos[!dupes] + + } + + renv_bootstrap_download <- function(version) { + + # if the renv version number has 4 components, assume it must + # be retrieved via github + nv <- numeric_version(version) + components <- unclass(nv)[[1]] + + methods <- if (length(components) == 4L) { + list( + renv_bootstrap_download_github + ) + } else { + list( + renv_bootstrap_download_cran_latest, + renv_bootstrap_download_cran_archive + ) + } + + for (method in methods) { + path <- tryCatch(method(version), error = identity) + if (is.character(path) && file.exists(path)) + return(path) + } + + stop("failed to download renv ", version) + + } + + renv_bootstrap_download_impl <- function(url, destfile) { + + mode <- "wb" + + # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 + fixup <- + Sys.info()[["sysname"]] == "Windows" && + substring(url, 1L, 5L) == "file:" + + if (fixup) + mode <- "w+b" + + utils::download.file( + url = url, + destfile = destfile, + mode = mode, + quiet = TRUE + ) + + } + + renv_bootstrap_download_cran_latest <- function(version) { + + repos <- renv_bootstrap_download_cran_latest_find(version) + + message("* Downloading renv ", version, " from CRAN ... ", appendLF = FALSE) + + info <- tryCatch( + utils::download.packages( + pkgs = "renv", + repos = repos, + destdir = tempdir(), + quiet = TRUE + ), + condition = identity + ) + + if (inherits(info, "condition")) { + message("FAILED") + return(FALSE) + } + + message("OK") + info[1, 2] + + } + + renv_bootstrap_download_cran_latest_find <- function(version) { + + all <- renv_bootstrap_repos() + + for (repos in all) { + + db <- tryCatch( + as.data.frame( + x = utils::available.packages(repos = repos), + stringsAsFactors = FALSE + ), + error = identity + ) + + if (inherits(db, "error")) + next + + entry <- db[db$Package %in% "renv" & db$Version %in% version, ] + if (nrow(entry) == 0) + next + + return(repos) + + } + + fmt <- "renv %s is not available from your declared package repositories" + stop(sprintf(fmt, version)) + + } + + renv_bootstrap_download_cran_archive <- function(version) { + + name <- sprintf("renv_%s.tar.gz", version) + repos <- renv_bootstrap_repos() + urls <- file.path(repos, "src/contrib/Archive/renv", name) + destfile <- file.path(tempdir(), name) + + message("* Downloading renv ", version, " from CRAN archive ... ", appendLF = FALSE) + + for (url in urls) { + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (identical(status, 0L)) { + message("OK") + return(destfile) + } + + } + + message("FAILED") + return(FALSE) + + } + + renv_bootstrap_download_github <- function(version) { + + enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") + if (!identical(enabled, "TRUE")) + return(FALSE) + + # prepare download options + pat <- Sys.getenv("GITHUB_PAT") + if (nzchar(Sys.which("curl")) && nzchar(pat)) { + fmt <- "--location --fail --header \"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "curl", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { + fmt <- "--header=\"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "wget", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } + + message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) + + url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) + name <- sprintf("renv_%s.tar.gz", version) + destfile <- file.path(tempdir(), name) + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (!identical(status, 0L)) { + message("FAILED") + return(FALSE) + } + + message("OK") + return(destfile) + + } + + renv_bootstrap_install <- function(version, tarball, library) { + + # attempt to install it into project library + message("* Installing renv ", version, " ... ", appendLF = FALSE) + dir.create(library, showWarnings = FALSE, recursive = TRUE) + + # invoke using system2 so we can capture and report output + bin <- R.home("bin") + exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" + r <- file.path(bin, exe) + args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball)) + output <- system2(r, args, stdout = TRUE, stderr = TRUE) + message("Done!") + + # check for successful install + status <- attr(output, "status") + if (is.numeric(status) && !identical(status, 0L)) { + header <- "Error installing renv:" + lines <- paste(rep.int("=", nchar(header)), collapse = "") + text <- c(header, lines, output) + writeLines(text, con = stderr()) + } + + status + + } + + renv_bootstrap_prefix <- function() { + + # construct version prefix + version <- paste(R.version$major, R.version$minor, sep = ".") + prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") + + # include SVN revision for development versions of R + # (to avoid sharing platform-specific artefacts with released versions of R) + devel <- + identical(R.version[["status"]], "Under development (unstable)") || + identical(R.version[["nickname"]], "Unsuffered Consequences") + + if (devel) + prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") + + # build list of path components + components <- c(prefix, R.version$platform) + + # include prefix if provided by user + prefix <- Sys.getenv("RENV_PATHS_PREFIX") + if (nzchar(prefix)) + components <- c(prefix, components) + + # build prefix + paste(components, collapse = "/") + + } + + renv_bootstrap_library_root_name <- function(project) { + + # use project name as-is if requested + asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") + if (asis) + return(basename(project)) + + # otherwise, disambiguate based on project's path + id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) + paste(basename(project), id, sep = "-") + + } + + renv_bootstrap_library_root <- function(project) { + + path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) + if (!is.na(path)) + return(path) + + path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) + if (!is.na(path)) { + name <- renv_bootstrap_library_root_name(project) + return(file.path(path, name)) + } + + file.path(project, "renv/library") + + } + + renv_bootstrap_validate_version <- function(version) { + + loadedversion <- utils::packageDescription("renv", fields = "Version") + if (version == loadedversion) + return(TRUE) + + # assume four-component versions are from GitHub; three-component + # versions are from CRAN + components <- strsplit(loadedversion, "[.-]")[[1]] + remote <- if (length(components) == 4L) + paste("rstudio/renv", loadedversion, sep = "@") + else + paste("renv", loadedversion, sep = "@") + + fmt <- paste( + "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", + "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", + "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", + sep = "\n" + ) + + msg <- sprintf(fmt, loadedversion, version, remote) + warning(msg, call. = FALSE) + + FALSE + + } + + renv_bootstrap_hash_text <- function(text) { + + hashfile <- tempfile("renv-hash-") + on.exit(unlink(hashfile), add = TRUE) + + writeLines(text, con = hashfile) + tools::md5sum(hashfile) + + } + + renv_bootstrap_load <- function(project, libpath, version) { + + # try to load renv from the project library + if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) + return(FALSE) + + # warn if the version of renv loaded does not match + renv_bootstrap_validate_version(version) + + # load the project + renv::load(project) + + TRUE + + } + + # construct path to library root + root <- renv_bootstrap_library_root(project) + + # construct library prefix for platform + prefix <- renv_bootstrap_prefix() + + # construct full libpath + libpath <- file.path(root, prefix) + + # attempt to load + if (renv_bootstrap_load(project, libpath, version)) + return(TRUE) + + # load failed; inform user we're about to bootstrap + prefix <- paste("# Bootstrapping renv", version) + postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") + header <- paste(prefix, postfix) + message(header) + + # perform bootstrap + bootstrap(version, libpath) + + # exit early if we're just testing bootstrap + if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) + return(TRUE) + + # try again to load + if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { + message("* Successfully installed and loaded renv ", version, ".") + return(renv::load()) + } + + # failed to download or load renv; warn the user + msg <- c( + "Failed to find an renv installation: the project will not be loaded.", + "Use `renv::activate()` to re-initialize the project." + ) + + warning(paste(msg, collapse = "\n"), call. = FALSE) + +}) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_environment.yml b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_environment.yml new file mode 100644 index 0000000..0f29f0c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_environment.yml @@ -0,0 +1,9 @@ +channels: + - conda-forge + - defaults +dependencies: + # This cannot be empty as otherwise no environment will be created. + # We're using openssl here as it is available on all system and will + # most likely be always installed anyways. + # See https://github.com/conda/conda/issues/9487 + - openssl diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_go.mod b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_go.mod new file mode 100644 index 0000000..892c4e5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_go.mod @@ -0,0 +1 @@ +module pre-commit-placeholder-empty-module diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_main.go b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_main.go new file mode 100644 index 0000000..38dd16d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_main.go @@ -0,0 +1,3 @@ +package main + +func main() {} diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_main.rs b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_package.json b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_package.json new file mode 100644 index 0000000..042e958 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_package.json @@ -0,0 +1,4 @@ +{ + "name": "pre_commit_placeholder_package", + "version": "0.0.0" +} diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pre-commit-package-dev-1.rockspec b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pre-commit-package-dev-1.rockspec new file mode 100644 index 0000000..f063c8e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pre-commit-package-dev-1.rockspec @@ -0,0 +1,12 @@ +package = "pre-commit-package" +version = "dev-1" + +source = { + url = "git+ssh://git@github.com/pre-commit/pre-commit.git" +} +description = {} +dependencies = {} +build = { + type = "builtin", + modules = {}, +} diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pre_commit_placeholder_package.gemspec b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pre_commit_placeholder_package.gemspec new file mode 100644 index 0000000..630f0d4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pre_commit_placeholder_package.gemspec @@ -0,0 +1,6 @@ +Gem::Specification.new do |s| + s.name = 'pre_commit_placeholder_package' + s.version = '0.0.0' + s.summary = 'placeholder gem for pre-commit hooks' + s.authors = ['Anthony Sottile'] +end diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pubspec.yaml b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pubspec.yaml new file mode 100644 index 0000000..3be6ffe --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_pubspec.yaml @@ -0,0 +1,4 @@ +name: pre_commit_empty_pubspec +environment: + sdk: '>=2.10.0' +executables: {} diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_renv.lock b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_renv.lock new file mode 100644 index 0000000..d6e31f8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_renv.lock @@ -0,0 +1,20 @@ +{ + "R": { + "Version": "4.0.3", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cran.rstudio.com" + } + ] + }, + "Packages": { + "renv": { + "Package": "renv", + "Version": "0.12.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5c0cdb37f063c58cdab3c7e9fbb8bd2c" + } + } +} diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_setup.py b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_setup.py new file mode 100644 index 0000000..ef05eef --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/empty_template_setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + + +setup(name='pre-commit-placeholder-package', version='0.0.0') diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/hook-tmpl b/.venv/lib/python3.8/site-packages/pre_commit/resources/hook-tmpl new file mode 100755 index 0000000..53d29f9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/resources/hook-tmpl @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# File generated by pre-commit: https://pre-commit.com +# ID: 138fd403232d2ddd5efb44317e38bf03 + +# start templated +INSTALL_PYTHON='' +ARGS=(hook-impl) +# end templated + +HERE="$(cd "$(dirname "$0")" && pwd)" +ARGS+=(--hook-dir "$HERE" -- "$@") + +if [ -x "$INSTALL_PYTHON" ]; then + exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}" +elif command -v pre-commit > /dev/null; then + exec pre-commit "${ARGS[@]}" +else + echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2 + exit 1 +fi diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/rbenv.tar.gz b/.venv/lib/python3.8/site-packages/pre_commit/resources/rbenv.tar.gz new file mode 100644 index 0000000..da2514e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/resources/rbenv.tar.gz differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/ruby-build.tar.gz b/.venv/lib/python3.8/site-packages/pre_commit/resources/ruby-build.tar.gz new file mode 100644 index 0000000..01867be Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/resources/ruby-build.tar.gz differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/resources/ruby-download.tar.gz b/.venv/lib/python3.8/site-packages/pre_commit/resources/ruby-download.tar.gz new file mode 100644 index 0000000..92502a7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pre_commit/resources/ruby-download.tar.gz differ diff --git a/.venv/lib/python3.8/site-packages/pre_commit/staged_files_only.py b/.venv/lib/python3.8/site-packages/pre_commit/staged_files_only.py new file mode 100644 index 0000000..bad004c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/staged_files_only.py @@ -0,0 +1,99 @@ +import contextlib +import logging +import os.path +import time +from typing import Generator + +from pre_commit import git +from pre_commit.util import CalledProcessError +from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b +from pre_commit.xargs import xargs + + +logger = logging.getLogger('pre_commit') + +# without forcing submodule.recurse=0, changes in nested submodules will be +# discarded if `submodule.recurse=1` is configured +# we choose this instead of `--no-recurse-submodules` because it works on +# versions of git before that option was added to `git checkout` +_CHECKOUT_CMD = ('git', '-c', 'submodule.recurse=0', 'checkout', '--', '.') + + +def _git_apply(patch: str) -> None: + args = ('apply', '--whitespace=nowarn', patch) + try: + cmd_output_b('git', *args) + except CalledProcessError: + # Retry with autocrlf=false -- see #570 + cmd_output_b('git', '-c', 'core.autocrlf=false', *args) + + +@contextlib.contextmanager +def _intent_to_add_cleared() -> Generator[None, None, None]: + intent_to_add = git.intent_to_add_files() + if intent_to_add: + logger.warning('Unstaged intent-to-add files detected.') + + xargs(('git', 'rm', '--cached', '--'), intent_to_add) + try: + yield + finally: + xargs(('git', 'add', '--intent-to-add', '--'), intent_to_add) + else: + yield + + +@contextlib.contextmanager +def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: + tree = cmd_output('git', 'write-tree')[1].strip() + retcode, diff_stdout_binary, _ = cmd_output_b( + 'git', 'diff-index', '--ignore-submodules', '--binary', + '--exit-code', '--no-color', '--no-ext-diff', tree, '--', + retcode=None, + ) + if retcode and diff_stdout_binary.strip(): + patch_filename = f'patch{int(time.time())}-{os.getpid()}' + patch_filename = os.path.join(patch_dir, patch_filename) + logger.warning('Unstaged files detected.') + logger.info(f'Stashing unstaged files to {patch_filename}.') + # Save the current unstaged changes as a patch + os.makedirs(patch_dir, exist_ok=True) + with open(patch_filename, 'wb') as patch_file: + patch_file.write(diff_stdout_binary) + + # prevent recursive post-checkout hooks (#1418) + no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1') + cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env) + + try: + yield + finally: + # Try to apply the patch we saved + try: + _git_apply(patch_filename) + except CalledProcessError: + logger.warning( + 'Stashed changes conflicted with hook auto-fixes... ' + 'Rolling back fixes...', + ) + # We failed to apply the patch, presumably due to fixes made + # by hooks. + # Roll back the changes made by hooks. + cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env) + _git_apply(patch_filename) + + logger.info(f'Restored changes from {patch_filename}.') + else: + # There weren't any staged files so we don't need to do anything + # special + yield + + +@contextlib.contextmanager +def staged_files_only(patch_dir: str) -> Generator[None, None, None]: + """Clear any unstaged changes from the git working directory inside this + context. + """ + with _intent_to_add_cleared(), _unstaged_changes_cleared(patch_dir): + yield diff --git a/.venv/lib/python3.8/site-packages/pre_commit/store.py b/.venv/lib/python3.8/site-packages/pre_commit/store.py new file mode 100644 index 0000000..27d8553 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/store.py @@ -0,0 +1,263 @@ +import contextlib +import logging +import os.path +import sqlite3 +import tempfile +from typing import Callable +from typing import Generator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit import file_lock +from pre_commit import git +from pre_commit.util import CalledProcessError +from pre_commit.util import clean_path_on_failure +from pre_commit.util import cmd_output_b +from pre_commit.util import resource_text +from pre_commit.util import rmtree + + +logger = logging.getLogger('pre_commit') + + +def _get_default_directory() -> str: + """Returns the default directory for the Store. This is intentionally + underscored to indicate that `Store.get_default_directory` is the intended + way to get this information. This is also done so + `Store.get_default_directory` can be mocked in tests and + `_get_default_directory` can be tested. + """ + ret = os.environ.get('PRE_COMMIT_HOME') or os.path.join( + os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'), + 'pre-commit', + ) + return os.path.realpath(ret) + + +class Store: + get_default_directory = staticmethod(_get_default_directory) + + def __init__(self, directory: Optional[str] = None) -> None: + self.directory = directory or Store.get_default_directory() + self.db_path = os.path.join(self.directory, 'db.db') + self.readonly = ( + os.path.exists(self.directory) and + not os.access(self.directory, os.W_OK) + ) + + if not os.path.exists(self.directory): + os.makedirs(self.directory, exist_ok=True) + with open(os.path.join(self.directory, 'README'), 'w') as f: + f.write( + 'This directory is maintained by the pre-commit project.\n' + 'Learn more: https://github.com/pre-commit/pre-commit\n', + ) + + if os.path.exists(self.db_path): + return + with self.exclusive_lock(): + # Another process may have already completed this work + if os.path.exists(self.db_path): # pragma: no cover (race) + return + # To avoid a race where someone ^Cs between db creation and + # execution of the CREATE TABLE statement + fd, tmpfile = tempfile.mkstemp(dir=self.directory) + # We'll be managing this file ourselves + os.close(fd) + with self.connect(db_path=tmpfile) as db: + db.executescript( + 'CREATE TABLE repos (' + ' repo TEXT NOT NULL,' + ' ref TEXT NOT NULL,' + ' path TEXT NOT NULL,' + ' PRIMARY KEY (repo, ref)' + ');', + ) + self._create_config_table(db) + + # Atomic file move + os.replace(tmpfile, self.db_path) + + @contextlib.contextmanager + def exclusive_lock(self) -> Generator[None, None, None]: + def blocked_cb() -> None: # pragma: no cover (tests are in-process) + logger.info('Locking pre-commit directory') + + with file_lock.lock(os.path.join(self.directory, '.lock'), blocked_cb): + yield + + @contextlib.contextmanager + def connect( + self, + db_path: Optional[str] = None, + ) -> Generator[sqlite3.Connection, None, None]: + db_path = db_path or self.db_path + # sqlite doesn't close its fd with its contextmanager >.< + # contextlib.closing fixes this. + # See: https://stackoverflow.com/a/28032829/812183 + with contextlib.closing(sqlite3.connect(db_path)) as db: + # this creates a transaction + with db: + yield db + + @classmethod + def db_repo_name(cls, repo: str, deps: Sequence[str]) -> str: + if deps: + return f'{repo}:{",".join(sorted(deps))}' + else: + return repo + + def _new_repo( + self, + repo: str, + ref: str, + deps: Sequence[str], + make_strategy: Callable[[str], None], + ) -> str: + repo = self.db_repo_name(repo, deps) + + def _get_result() -> Optional[str]: + # Check if we already exist + with self.connect() as db: + result = db.execute( + 'SELECT path FROM repos WHERE repo = ? AND ref = ?', + (repo, ref), + ).fetchone() + return result[0] if result else None + + result = _get_result() + if result: + return result + with self.exclusive_lock(): + # Another process may have already completed this work + result = _get_result() + if result: # pragma: no cover (race) + return result + + logger.info(f'Initializing environment for {repo}.') + + directory = tempfile.mkdtemp(prefix='repo', dir=self.directory) + with clean_path_on_failure(directory): + make_strategy(directory) + + # Update our db with the created repo + with self.connect() as db: + db.execute( + 'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)', + [repo, ref, directory], + ) + return directory + + def _complete_clone(self, ref: str, git_cmd: Callable[..., None]) -> None: + """Perform a complete clone of a repository and its submodules """ + + git_cmd('fetch', 'origin', '--tags') + git_cmd('checkout', ref) + git_cmd('submodule', 'update', '--init', '--recursive') + + def _shallow_clone(self, ref: str, git_cmd: Callable[..., None]) -> None: + """Perform a shallow clone of a repository and its submodules """ + + git_config = 'protocol.version=2' + git_cmd('-c', git_config, 'fetch', 'origin', ref, '--depth=1') + git_cmd('checkout', 'FETCH_HEAD') + git_cmd( + '-c', git_config, 'submodule', 'update', '--init', '--recursive', + '--depth=1', + ) + + def clone(self, repo: str, ref: str, deps: Sequence[str] = ()) -> str: + """Clone the given url and checkout the specific ref.""" + + def clone_strategy(directory: str) -> None: + git.init_repo(directory, repo) + env = git.no_git_env() + + def _git_cmd(*args: str) -> None: + cmd_output_b('git', *args, cwd=directory, env=env) + + try: + self._shallow_clone(ref, _git_cmd) + except CalledProcessError: + self._complete_clone(ref, _git_cmd) + + return self._new_repo(repo, ref, deps, clone_strategy) + + LOCAL_RESOURCES = ( + 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore', + 'package.json', 'pre-commit-package-dev-1.rockspec', + 'pre_commit_placeholder_package.gemspec', 'setup.py', + 'environment.yml', 'Makefile.PL', 'pubspec.yaml', + 'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv', + ) + + def make_local(self, deps: Sequence[str]) -> str: + def make_local_strategy(directory: str) -> None: + for resource in self.LOCAL_RESOURCES: + resource_dirname, resource_basename = os.path.split(resource) + contents = resource_text(f'empty_template_{resource_basename}') + target_dir = os.path.join(directory, resource_dirname) + target_file = os.path.join(target_dir, resource_basename) + os.makedirs(target_dir, exist_ok=True) + with open(target_file, 'w') as f: + f.write(contents) + + env = git.no_git_env() + + # initialize the git repository so it looks more like cloned repos + def _git_cmd(*args: str) -> None: + cmd_output_b('git', *args, cwd=directory, env=env) + + git.init_repo(directory, '<>') + _git_cmd('add', '.') + git.commit(repo=directory) + + return self._new_repo( + 'local', C.LOCAL_REPO_VERSION, deps, make_local_strategy, + ) + + def _create_config_table(self, db: sqlite3.Connection) -> None: + db.executescript( + 'CREATE TABLE IF NOT EXISTS configs (' + ' path TEXT NOT NULL,' + ' PRIMARY KEY (path)' + ');', + ) + + def mark_config_used(self, path: str) -> None: + if self.readonly: # pragma: win32 no cover + return + path = os.path.realpath(path) + # don't insert config files that do not exist + if not os.path.exists(path): + return + with self.connect() as db: + # TODO: eventually remove this and only create in _create + self._create_config_table(db) + db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,)) + + def select_all_configs(self) -> List[str]: + with self.connect() as db: + self._create_config_table(db) + rows = db.execute('SELECT path FROM configs').fetchall() + return [path for path, in rows] + + def delete_configs(self, configs: List[str]) -> None: + with self.connect() as db: + rows = [(path,) for path in configs] + db.executemany('DELETE FROM configs WHERE path = ?', rows) + + def select_all_repos(self) -> List[Tuple[str, str, str]]: + with self.connect() as db: + return db.execute('SELECT repo, ref, path from repos').fetchall() + + def delete_repo(self, db_repo_name: str, ref: str, path: str) -> None: + with self.connect() as db: + db.execute( + 'DELETE FROM repos WHERE repo = ? and ref = ?', + (db_repo_name, ref), + ) + rmtree(path) diff --git a/.venv/lib/python3.8/site-packages/pre_commit/util.py b/.venv/lib/python3.8/site-packages/pre_commit/util.py new file mode 100644 index 0000000..6977acb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/util.py @@ -0,0 +1,274 @@ +import contextlib +import errno +import functools +import os.path +import shutil +import stat +import subprocess +import sys +import tempfile +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import IO +from typing import Optional +from typing import Tuple +from typing import Type + +import yaml + +from pre_commit import parse_shebang + +if sys.version_info >= (3, 7): # pragma: >=3.7 cover + from importlib.resources import open_binary + from importlib.resources import read_text +else: # pragma: <3.7 cover + from importlib_resources import open_binary + from importlib_resources import read_text + +Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader) +yaml_load = functools.partial(yaml.load, Loader=Loader) +Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper) + + +def yaml_dump(o: Any, **kwargs: Any) -> str: + # when python/mypy#1484 is solved, this can be `functools.partial` + return yaml.dump( + o, Dumper=Dumper, default_flow_style=False, indent=4, sort_keys=False, + **kwargs, + ) + + +def force_bytes(exc: Any) -> bytes: + with contextlib.suppress(TypeError): + return bytes(exc) + with contextlib.suppress(Exception): + return str(exc).encode() + return f''.encode() + + +@contextlib.contextmanager +def clean_path_on_failure(path: str) -> Generator[None, None, None]: + """Cleans up the directory on an exceptional failure.""" + try: + yield + except BaseException: + if os.path.exists(path): + rmtree(path) + raise + + +@contextlib.contextmanager +def tmpdir() -> Generator[str, None, None]: + """Contextmanager to create a temporary directory. It will be cleaned up + afterwards. + """ + tempdir = tempfile.mkdtemp() + try: + yield tempdir + finally: + rmtree(tempdir) + + +def resource_bytesio(filename: str) -> IO[bytes]: + return open_binary('pre_commit.resources', filename) + + +def resource_text(filename: str) -> str: + return read_text('pre_commit.resources', filename) + + +def make_executable(filename: str) -> None: + original_mode = os.stat(filename).st_mode + new_mode = original_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + os.chmod(filename, new_mode) + + +class CalledProcessError(RuntimeError): + def __init__( + self, + returncode: int, + cmd: Tuple[str, ...], + expected_returncode: int, + stdout: bytes, + stderr: Optional[bytes], + ) -> None: + super().__init__(returncode, cmd, expected_returncode, stdout, stderr) + self.returncode = returncode + self.cmd = cmd + self.expected_returncode = expected_returncode + self.stdout = stdout + self.stderr = stderr + + def __bytes__(self) -> bytes: + def _indent_or_none(part: Optional[bytes]) -> bytes: + if part: + return b'\n ' + part.replace(b'\n', b'\n ') + else: + return b' (none)' + + return b''.join(( + f'command: {self.cmd!r}\n'.encode(), + f'return code: {self.returncode}\n'.encode(), + f'expected return code: {self.expected_returncode}\n'.encode(), + b'stdout:', _indent_or_none(self.stdout), b'\n', + b'stderr:', _indent_or_none(self.stderr), + )) + + def __str__(self) -> str: + return self.__bytes__().decode() + + +def _setdefault_kwargs(kwargs: Dict[str, Any]) -> None: + for arg in ('stdin', 'stdout', 'stderr'): + kwargs.setdefault(arg, subprocess.PIPE) + + +def _oserror_to_output(e: OSError) -> Tuple[int, bytes, None]: + return 1, force_bytes(e).rstrip(b'\n') + b'\n', None + + +def cmd_output_b( + *cmd: str, + retcode: Optional[int] = 0, + **kwargs: Any, +) -> Tuple[int, bytes, Optional[bytes]]: + _setdefault_kwargs(kwargs) + + try: + cmd = parse_shebang.normalize_cmd(cmd) + except parse_shebang.ExecutableNotFoundError as e: + returncode, stdout_b, stderr_b = e.to_output() + else: + try: + proc = subprocess.Popen(cmd, **kwargs) + except OSError as e: + returncode, stdout_b, stderr_b = _oserror_to_output(e) + else: + stdout_b, stderr_b = proc.communicate() + returncode = proc.returncode + + if retcode is not None and retcode != returncode: + raise CalledProcessError(returncode, cmd, retcode, stdout_b, stderr_b) + + return returncode, stdout_b, stderr_b + + +def cmd_output(*cmd: str, **kwargs: Any) -> Tuple[int, str, Optional[str]]: + returncode, stdout_b, stderr_b = cmd_output_b(*cmd, **kwargs) + stdout = stdout_b.decode() if stdout_b is not None else None + stderr = stderr_b.decode() if stderr_b is not None else None + return returncode, stdout, stderr + + +if os.name != 'nt': # pragma: win32 no cover + from os import openpty + import termios + + class Pty: + def __init__(self) -> None: + self.r: Optional[int] = None + self.w: Optional[int] = None + + def __enter__(self) -> 'Pty': + self.r, self.w = openpty() + + # tty flags normally change \n to \r\n + attrs = termios.tcgetattr(self.r) + assert isinstance(attrs[1], int) + attrs[1] &= ~(termios.ONLCR | termios.OPOST) + termios.tcsetattr(self.r, termios.TCSANOW, attrs) + + return self + + def close_w(self) -> None: + if self.w is not None: + os.close(self.w) + self.w = None + + def close_r(self) -> None: + assert self.r is not None + os.close(self.r) + self.r = None + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + self.close_w() + self.close_r() + + def cmd_output_p( + *cmd: str, + retcode: Optional[int] = 0, + **kwargs: Any, + ) -> Tuple[int, bytes, Optional[bytes]]: + assert retcode is None + assert kwargs['stderr'] == subprocess.STDOUT, kwargs['stderr'] + _setdefault_kwargs(kwargs) + + try: + cmd = parse_shebang.normalize_cmd(cmd) + except parse_shebang.ExecutableNotFoundError as e: + return e.to_output() + + with open(os.devnull) as devnull, Pty() as pty: + assert pty.r is not None + kwargs.update({'stdin': devnull, 'stdout': pty.w, 'stderr': pty.w}) + try: + proc = subprocess.Popen(cmd, **kwargs) + except OSError as e: + return _oserror_to_output(e) + + pty.close_w() + + buf = b'' + while True: + try: + bts = os.read(pty.r, 4096) + except OSError as e: + if e.errno == errno.EIO: + bts = b'' + else: + raise + else: + buf += bts + if not bts: + break + + return proc.wait(), buf, None +else: # pragma: no cover + cmd_output_p = cmd_output_b + + +def rmtree(path: str) -> None: + """On windows, rmtree fails for readonly dirs.""" + def handle_remove_readonly( + func: Callable[..., Any], + path: str, + exc: Tuple[Type[OSError], OSError, TracebackType], + ) -> None: + excvalue = exc[1] + if ( + func in (os.rmdir, os.remove, os.unlink) and + excvalue.errno in {errno.EACCES, errno.EPERM} + ): + for p in (path, os.path.dirname(path)): + os.chmod(p, os.stat(p).st_mode | stat.S_IWUSR) + func(path) + else: + raise + shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly) + + +def parse_version(s: str) -> Tuple[int, ...]: + """poor man's version comparison""" + return tuple(int(p) for p in s.split('.')) + + +def win_exe(s: str) -> str: + return s if sys.platform != 'win32' else f'{s}.exe' diff --git a/.venv/lib/python3.8/site-packages/pre_commit/xargs.py b/.venv/lib/python3.8/site-packages/pre_commit/xargs.py new file mode 100644 index 0000000..6b0fa20 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pre_commit/xargs.py @@ -0,0 +1,169 @@ +import concurrent.futures +import contextlib +import math +import os +import subprocess +import sys +from typing import Any +from typing import Callable +from typing import Generator +from typing import Iterable +from typing import List +from typing import MutableMapping +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TypeVar + +from pre_commit import parse_shebang +from pre_commit.util import cmd_output_b +from pre_commit.util import cmd_output_p + +TArg = TypeVar('TArg') +TRet = TypeVar('TRet') + + +def _environ_size(_env: Optional[MutableMapping[str, str]] = None) -> int: + environ = _env if _env is not None else getattr(os, 'environb', os.environ) + size = 8 * len(environ) # number of pointers in `envp` + for k, v in environ.items(): + size += len(k) + len(v) + 2 # c strings in `envp` + return size + + +def _get_platform_max_length() -> int: # pragma: no cover (platform specific) + if os.name == 'posix': + maximum = os.sysconf('SC_ARG_MAX') - 2048 - _environ_size() + maximum = max(min(maximum, 2 ** 17), 2 ** 12) + return maximum + elif os.name == 'nt': + return 2 ** 15 - 2048 # UNICODE_STRING max - headroom + else: + # posix minimum + return 2 ** 12 + + +def _command_length(*cmd: str) -> int: + full_cmd = ' '.join(cmd) + + # win32 uses the amount of characters, more details at: + # https://github.com/pre-commit/pre-commit/pull/839 + if sys.platform == 'win32': + return len(full_cmd.encode('utf-16le')) // 2 + else: + return len(full_cmd.encode(sys.getfilesystemencoding())) + + +class ArgumentTooLongError(RuntimeError): + pass + + +def partition( + cmd: Sequence[str], + varargs: Sequence[str], + target_concurrency: int, + _max_length: Optional[int] = None, +) -> Tuple[Tuple[str, ...], ...]: + _max_length = _max_length or _get_platform_max_length() + + # Generally, we try to partition evenly into at least `target_concurrency` + # partitions, but we don't want a bunch of tiny partitions. + max_args = max(4, math.ceil(len(varargs) / target_concurrency)) + + cmd = tuple(cmd) + ret = [] + + ret_cmd: List[str] = [] + # Reversed so arguments are in order + varargs = list(reversed(varargs)) + + total_length = _command_length(*cmd) + 1 + while varargs: + arg = varargs.pop() + + arg_length = _command_length(arg) + 1 + if ( + total_length + arg_length <= _max_length and + len(ret_cmd) < max_args + ): + ret_cmd.append(arg) + total_length += arg_length + elif not ret_cmd: + raise ArgumentTooLongError(arg) + else: + # We've exceeded the length, yield a command + ret.append(cmd + tuple(ret_cmd)) + ret_cmd = [] + total_length = _command_length(*cmd) + 1 + varargs.append(arg) + + ret.append(cmd + tuple(ret_cmd)) + + return tuple(ret) + + +@contextlib.contextmanager +def _thread_mapper(maxsize: int) -> Generator[ + Callable[[Callable[[TArg], TRet], Iterable[TArg]], Iterable[TRet]], + None, None, +]: + if maxsize == 1: + yield map + else: + with concurrent.futures.ThreadPoolExecutor(maxsize) as ex: + yield ex.map + + +def xargs( + cmd: Tuple[str, ...], + varargs: Sequence[str], + *, + color: bool = False, + target_concurrency: int = 1, + _max_length: int = _get_platform_max_length(), + **kwargs: Any, +) -> Tuple[int, bytes]: + """A simplified implementation of xargs. + + color: Make a pty if on a platform that supports it + target_concurrency: Target number of partitions to run concurrently + """ + cmd_fn = cmd_output_p if color else cmd_output_b + retcode = 0 + stdout = b'' + + try: + cmd = parse_shebang.normalize_cmd(cmd) + except parse_shebang.ExecutableNotFoundError as e: + return e.to_output()[:2] + + # on windows, batch files have a separate length limit than windows itself + if ( + sys.platform == 'win32' and + cmd[0].lower().endswith(('.bat', '.cmd')) + ): # pragma: win32 cover + # this is implementation details but the command gets translated into + # full/path/to/cmd.exe /c *cmd + cmd_exe = parse_shebang.find_executable('cmd.exe') + # 1024 is additionally subtracted to give headroom for further + # expansion inside the batch file + _max_length = 8192 - len(cmd_exe) - len(' /c ') - 1024 + + partitions = partition(cmd, varargs, target_concurrency, _max_length) + + def run_cmd_partition( + run_cmd: Tuple[str, ...], + ) -> Tuple[int, bytes, Optional[bytes]]: + return cmd_fn( + *run_cmd, retcode=None, stderr=subprocess.STDOUT, **kwargs, + ) + + threads = min(len(partitions), target_concurrency) + with _thread_mapper(threads) as thread_map: + results = thread_map(run_cmd_partition, partitions) + + for proc_retcode, proc_out, _ in results: + retcode = max(retcode, proc_retcode) + stdout += proc_out + + return retcode, stdout diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/AUTHORS.rst b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/AUTHORS.rst new file mode 100644 index 0000000..f7c8f60 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/AUTHORS.rst @@ -0,0 +1,11 @@ +Authors +======= + +Creator +------- +Jonathan Slenders + +Contributors +------------ + +- Amjith Ramanujam diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/LICENSE b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/LICENSE new file mode 100644 index 0000000..e1720e0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014, Jonathan Slenders +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/METADATA b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/METADATA new file mode 100644 index 0000000..cdf27c9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/METADATA @@ -0,0 +1,184 @@ +Metadata-Version: 2.1 +Name: prompt-toolkit +Version: 3.0.28 +Summary: Library for building powerful interactive command lines in Python +Home-page: https://github.com/prompt-toolkit/python-prompt-toolkit +Author: Jonathan Slenders +License: UNKNOWN +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 :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development +Requires-Python: >=3.6.2 +Description-Content-Type: text/x-rst +Requires-Dist: wcwidth + +Python Prompt Toolkit +===================== + +|Build Status| |AppVeyor| |PyPI| |RTD| |License| |Codecov| + +.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png + +``prompt_toolkit`` *is a library for building powerful interactive command line applications in Python.* + +Read the `documentation on readthedocs +`_. + +NOTICE: prompt_toolkit 3.0 +************************** + +Please notice that this branch is the ``prompt_toolkit`` **3.0** branch. For most +users, it should be compatible with ``prompt_toolkit`` **2.0**, but it requires at +least **Python 3.6**. On the plus side, ``prompt_toolkit`` **3.0** is completely type +annotated and uses asyncio natively. + + +Gallery +******* + +`ptpython `_ is an interactive +Python Shell, build on top of ``prompt_toolkit``. + +.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/ptpython.png + +`More examples `_ + + +prompt_toolkit features +*********************** + +``prompt_toolkit`` could be a replacement for `GNU readline +`_, but it can be much +more than that. + +Some features: + +- **Pure Python**. +- Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) +- Multi-line input editing. +- Advanced code completion. +- Both Emacs and Vi key bindings. (Similar to readline.) +- Even some advanced Vi functionality, like named registers and digraphs. +- Reverse and forward incremental search. +- Works well with Unicode double width characters. (Chinese input.) +- Selecting text for copy/paste. (Both Emacs and Vi style.) +- Support for `bracketed paste `_. +- Mouse support for cursor positioning and scrolling. +- Auto suggestions. (Like `fish shell `_.) +- Multiple input buffers. +- No global state. +- Lightweight, the only dependencies are Pygments and wcwidth. +- Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. +- And much more... + +Feel free to create tickets for bugs and feature requests, and create pull +requests if you have nice patches that you would like to share with others. + + +Installation +************ + +:: + + pip install prompt_toolkit + +For Conda, do: + +:: + + conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit + + +About Windows support +********************* + +``prompt_toolkit`` is cross platform, and everything that you build on top +should run fine on both Unix and Windows systems. Windows support is best on +recent Windows 10 builds, for which the command line window supports vt100 +escape sequences. (If not supported, we fall back to using Win32 APIs for color +and cursor movements). + +It's worth noting that the implementation is a "best effort of what is +possible". Both Unix and Windows terminals have their limitations. But in +general, the Unix experience will still be a little better. + +For Windows, it's recommended to use either `cmder +`_ or `conemu `_. + +Getting started +*************** + +The most simple example of the library would look like this: + +.. code:: python + + from prompt_toolkit import prompt + + if __name__ == '__main__': + answer = prompt('Give me some input: ') + print('You said: %s' % answer) + +For more complex examples, have a look in the ``examples`` directory. All +examples are chosen to demonstrate only one thing. Also, don't be afraid to +look at the source code. The implementation of the ``prompt`` function could be +a good start. + +Philosophy +********** + +The source code of ``prompt_toolkit`` should be **readable**, **concise** and +**efficient**. We prefer short functions focusing each on one task and for which +the input and output types are clearly specified. We mostly prefer composition +over inheritance, because inheritance can result in too much functionality in +the same object. We prefer immutable objects where possible (objects don't +change after initialization). Reusability is important. We absolutely refrain +from having a changing global state, it should be possible to have multiple +independent instances of the same code in the same process. The architecture +should be layered: the lower levels operate on primitive operations and data +structures giving -- when correctly combined -- all the possible flexibility; +while at the higher level, there should be a simpler API, ready-to-use and +sufficient for most use cases. Thinking about algorithms and efficiency is +important, but avoid premature optimization. + + +`Projects using prompt_toolkit `_ +*********************************************** + +Special thanks to +***************** + +- `Pygments `_: Syntax highlighter. +- `wcwidth `_: Determine columns needed for a wide characters. + +.. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/python-prompt-toolkit.svg?branch=master + :target: https://travis-ci.org/prompt-toolkit/python-prompt-toolkit# + +.. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg + :target: https://pypi.python.org/pypi/prompt-toolkit/ + :alt: Latest Version + +.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true + :target: https://ci.appveyor.com/project/prompt-toolkit/python-prompt-toolkit/ + +.. |RTD| image:: https://readthedocs.org/projects/python-prompt-toolkit/badge/ + :target: https://python-prompt-toolkit.readthedocs.io/en/master/ + +.. |License| image:: https://img.shields.io/github/license/prompt-toolkit/python-prompt-toolkit.svg + :target: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/LICENSE + +.. |Codecov| image:: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/branch/master/graphs/badge.svg?style=flat + :target: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/ + + + diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/RECORD b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/RECORD new file mode 100644 index 0000000..ba9f1dc --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/RECORD @@ -0,0 +1,300 @@ +prompt_toolkit-3.0.28.dist-info/AUTHORS.rst,sha256=09xixryENmWElauJrqN1Eef6k5HSgmVyOcnPuA29QuU,148 +prompt_toolkit-3.0.28.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +prompt_toolkit-3.0.28.dist-info/LICENSE,sha256=MDV02b3YXHV9YCUBeUK_F7ru3yd49ivX9CXQfYgPTEo,1493 +prompt_toolkit-3.0.28.dist-info/METADATA,sha256=kXapVoOKBcCGxRPW52G_8zlm9oqYwMXY2F30LpEn_iE,7022 +prompt_toolkit-3.0.28.dist-info/RECORD,, +prompt_toolkit-3.0.28.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +prompt_toolkit-3.0.28.dist-info/top_level.txt,sha256=5rJXrEGx6st4KkmhOPR6l0ITDbV53x_Xy6MurOukXfA,15 +prompt_toolkit/__init__.py,sha256=PsETDPPOb0QbYbgBtEbkF42DqcoTkKPfhl1iLvv2zKI,927 +prompt_toolkit/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/__pycache__/auto_suggest.cpython-38.pyc,, +prompt_toolkit/__pycache__/buffer.cpython-38.pyc,, +prompt_toolkit/__pycache__/cache.cpython-38.pyc,, +prompt_toolkit/__pycache__/cursor_shapes.cpython-38.pyc,, +prompt_toolkit/__pycache__/data_structures.cpython-38.pyc,, +prompt_toolkit/__pycache__/document.cpython-38.pyc,, +prompt_toolkit/__pycache__/enums.cpython-38.pyc,, +prompt_toolkit/__pycache__/history.cpython-38.pyc,, +prompt_toolkit/__pycache__/keys.cpython-38.pyc,, +prompt_toolkit/__pycache__/log.cpython-38.pyc,, +prompt_toolkit/__pycache__/mouse_events.cpython-38.pyc,, +prompt_toolkit/__pycache__/patch_stdout.cpython-38.pyc,, +prompt_toolkit/__pycache__/renderer.cpython-38.pyc,, +prompt_toolkit/__pycache__/search.cpython-38.pyc,, +prompt_toolkit/__pycache__/selection.cpython-38.pyc,, +prompt_toolkit/__pycache__/token.cpython-38.pyc,, +prompt_toolkit/__pycache__/utils.cpython-38.pyc,, +prompt_toolkit/__pycache__/validation.cpython-38.pyc,, +prompt_toolkit/__pycache__/win32_types.cpython-38.pyc,, +prompt_toolkit/application/__init__.py,sha256=Vmaq4QcT50k7-1kXHJeuTxYBsI1gqLMOqINymkDM4k0,621 +prompt_toolkit/application/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/application/__pycache__/application.cpython-38.pyc,, +prompt_toolkit/application/__pycache__/current.cpython-38.pyc,, +prompt_toolkit/application/__pycache__/dummy.cpython-38.pyc,, +prompt_toolkit/application/__pycache__/run_in_terminal.cpython-38.pyc,, +prompt_toolkit/application/application.py,sha256=9Jyw28GukmxPqopbAAuRYhsmtDKslczyWU5cTU5NK8A,55146 +prompt_toolkit/application/current.py,sha256=lyp97PBzHDMnof3Qa2Bv6nf_IrCOsL3I42kixYrtH3E,6054 +prompt_toolkit/application/dummy.py,sha256=eeoFtj3QWXL3cVo-ieyg9UN2e-qs3B3aF7nZZPJFzsA,1508 +prompt_toolkit/application/run_in_terminal.py,sha256=ppou72uDfdiACOQ4uwp_Vgn33A0u5QOs3BQGYQgjEFM,3713 +prompt_toolkit/auto_suggest.py,sha256=BWfu_h2h7D9D0qj0438OBOmjOxaeMaGwS0JSrRIcLZg,5922 +prompt_toolkit/buffer.py,sha256=VU0U0N2Hghe8c60_fZS6QTjasVbjtjCkHV1kDws8raI,72857 +prompt_toolkit/cache.py,sha256=yZ5M3tki7PNR26vGji3_9dPljqAxVPX2imA8tVQ3V-E,3794 +prompt_toolkit/clipboard/__init__.py,sha256=7ceVnHo8jP-4j-_rLubuJmf51pQzcY0wsNj_OLvvAX0,403 +prompt_toolkit/clipboard/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/clipboard/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/clipboard/__pycache__/in_memory.cpython-38.pyc,, +prompt_toolkit/clipboard/__pycache__/pyperclip.cpython-38.pyc,, +prompt_toolkit/clipboard/base.py,sha256=3xbjTaYoTxdsdtqXwsxwp75lo2DZKIOjRSwiHQb0wbY,2492 +prompt_toolkit/clipboard/in_memory.py,sha256=IGkT9pA9H50y3u5lI7ASu_2S7JzBM-TaQbfNn_mEptA,1077 +prompt_toolkit/clipboard/pyperclip.py,sha256=U3AJ4Nb3ugq-ZDCSJ0ZaQCMfw_1YQpeXd0Y91E38THA,1156 +prompt_toolkit/completion/__init__.py,sha256=p6eAas03IrRtHMdjy5j_NkWAmSQhHVA1F9SJUoS3x1U,956 +prompt_toolkit/completion/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/completion/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/completion/__pycache__/deduplicate.cpython-38.pyc,, +prompt_toolkit/completion/__pycache__/filesystem.cpython-38.pyc,, +prompt_toolkit/completion/__pycache__/fuzzy_completer.cpython-38.pyc,, +prompt_toolkit/completion/__pycache__/nested.cpython-38.pyc,, +prompt_toolkit/completion/__pycache__/word_completer.cpython-38.pyc,, +prompt_toolkit/completion/base.py,sha256=OBjtbBC69bbBzGJhUZkVEjkyGJqiBWboWNSQ5JCaUBs,13155 +prompt_toolkit/completion/deduplicate.py,sha256=sPCdcKZ5_YZNXABdEl3HSb-NZu0S9NRAwF0EIA8l1EM,1405 +prompt_toolkit/completion/filesystem.py,sha256=jea6w3qqmr1PoJ6fn7EAa_TviqqXgS5xLds6PN2Xyuw,3937 +prompt_toolkit/completion/fuzzy_completer.py,sha256=e5omOJpNcNdZkhjfPDrVfMFWL6uyD_E3C6TAp5Z0Umo,7065 +prompt_toolkit/completion/nested.py,sha256=mAvBXt_JqnRLwydeL_OS8Qudd3SqRaS5YIsK3iowUFs,3842 +prompt_toolkit/completion/word_completer.py,sha256=TlUqPx9ngPh9T3-U4wACFbDBo3uCpG-y4WKU_B0NG0U,3420 +prompt_toolkit/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +prompt_toolkit/contrib/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/contrib/completers/__init__.py,sha256=jVK7dpUuyjDI5A_C2bVd3vLbQkl-xcV1gmnAg6DZ-Vg,67 +prompt_toolkit/contrib/completers/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/contrib/completers/__pycache__/system.cpython-38.pyc,, +prompt_toolkit/contrib/completers/system.py,sha256=aYN5i1d0dHPvjjQ4fRsWte1khl4S9Evqn2JckhYKBvw,2021 +prompt_toolkit/contrib/regular_languages/__init__.py,sha256=8Mo6qb-BWDggqYOw0ZI1KrgSq3w2eXrR0lrzuUekcgE,3243 +prompt_toolkit/contrib/regular_languages/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/contrib/regular_languages/__pycache__/compiler.cpython-38.pyc,, +prompt_toolkit/contrib/regular_languages/__pycache__/completion.cpython-38.pyc,, +prompt_toolkit/contrib/regular_languages/__pycache__/lexer.cpython-38.pyc,, +prompt_toolkit/contrib/regular_languages/__pycache__/regex_parser.cpython-38.pyc,, +prompt_toolkit/contrib/regular_languages/__pycache__/validation.cpython-38.pyc,, +prompt_toolkit/contrib/regular_languages/compiler.py,sha256=rIvSoad6jhtCvgdSbrlvBQ8CV1Esqntds3-tG2cArOo,21868 +prompt_toolkit/contrib/regular_languages/completion.py,sha256=C2X7-JPn8aACwSJgZN8WKWW7Uo3ElSTR0Ykf32Tyomo,3222 +prompt_toolkit/contrib/regular_languages/lexer.py,sha256=TBfU0nSBv-p_h-5CCPpgmn5ub-Z34sAEgAJcTmLuJWM,3398 +prompt_toolkit/contrib/regular_languages/regex_parser.py,sha256=MifqfniMiZaF5Q_1bxPvV4Jk6prnX5vRr88detP6s2o,7803 +prompt_toolkit/contrib/regular_languages/validation.py,sha256=UQkC3nXedAPVM37qJ4LiBZA8JfoScMal_0SSwSwvRt4,2048 +prompt_toolkit/contrib/ssh/__init__.py,sha256=RXy1t24dQ3a0oqLBy23ezhknwA6-kl9PlJ9P91anFgQ,144 +prompt_toolkit/contrib/ssh/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/contrib/ssh/__pycache__/server.cpython-38.pyc,, +prompt_toolkit/contrib/ssh/server.py,sha256=D_ymHnb6utqaEJ5DEeTXHoEq6x8zfVxF-HRAHFB2fEA,5426 +prompt_toolkit/contrib/telnet/__init__.py,sha256=4Gka9wB9X2k7mLy9fQg2aP-9Tud-3fk9DvMaBgno4ZU,68 +prompt_toolkit/contrib/telnet/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/contrib/telnet/__pycache__/log.cpython-38.pyc,, +prompt_toolkit/contrib/telnet/__pycache__/protocol.cpython-38.pyc,, +prompt_toolkit/contrib/telnet/__pycache__/server.cpython-38.pyc,, +prompt_toolkit/contrib/telnet/log.py,sha256=9Gx9ifabhiFgoIfhAclkDUkdeGCB9B0e63x07JvULgA,130 +prompt_toolkit/contrib/telnet/protocol.py,sha256=MBFZXRgM-TEEtKLrQoCcQW3yj7LRJytYTrUsIKXaI8c,5548 +prompt_toolkit/contrib/telnet/server.py,sha256=IWGJOz8-bnBVl1huB4EOAcYiOanuxUjtgE7bP2f3yIA,10707 +prompt_toolkit/cursor_shapes.py,sha256=gTQKFrAseTRR7vjtMqocKc5YQxC_CZthHag6446BHus,3269 +prompt_toolkit/data_structures.py,sha256=6mATpY1S2RUzM02qNWgvGnTfoJnCKSM1U_4xEID4ufo,176 +prompt_toolkit/document.py,sha256=t5wP3iE1hNYFM6bLTZgpGUyHRFlOlYRAfWkNVgPzB6w,40689 +prompt_toolkit/enums.py,sha256=T54v5C2Adz1qcWIXJLWqrrTnOyKouJ1i5wwNuY4YV7E,322 +prompt_toolkit/eventloop/__init__.py,sha256=jYlepkTiGGhHCEEXlc5ad4QlPe_zmyVWYxANfCUjoEQ,678 +prompt_toolkit/eventloop/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/eventloop/__pycache__/async_context_manager.cpython-38.pyc,, +prompt_toolkit/eventloop/__pycache__/async_generator.cpython-38.pyc,, +prompt_toolkit/eventloop/__pycache__/dummy_contextvars.cpython-38.pyc,, +prompt_toolkit/eventloop/__pycache__/inputhook.cpython-38.pyc,, +prompt_toolkit/eventloop/__pycache__/utils.cpython-38.pyc,, +prompt_toolkit/eventloop/__pycache__/win32.cpython-38.pyc,, +prompt_toolkit/eventloop/async_context_manager.py,sha256=O8kvy2b8xogOLdqaMjQgTT2ot3EzAq4Pc2tT7PBK86o,4429 +prompt_toolkit/eventloop/async_generator.py,sha256=r9XFZlrK4BoTonI7Shgu1MWARhT7iJmJdt4q4VAJQ58,2075 +prompt_toolkit/eventloop/dummy_contextvars.py,sha256=IQ-PkGILLCWIP-qAjwubdEJtQrTwoxZ5GkNMVgDHsww,1171 +prompt_toolkit/eventloop/inputhook.py,sha256=0xy_O4HNMyeqa0Jo237fXKK2pByG0cm47WSknl72pa0,6049 +prompt_toolkit/eventloop/utils.py,sha256=SMC5GnSjamJq8wTWjRxCWzoILalG3307oTjVso5giWE,3723 +prompt_toolkit/eventloop/win32.py,sha256=4NCx8KV6m8jZ_Cw3BZwU_TKCcVT0Ta8fCRAIlIFDGpQ,2249 +prompt_toolkit/filters/__init__.py,sha256=dmIst7EUX2A_6pQHdVEVgLHOLAXGU8DiEhqjBHfkUPQ,1953 +prompt_toolkit/filters/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/filters/__pycache__/app.cpython-38.pyc,, +prompt_toolkit/filters/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/filters/__pycache__/cli.cpython-38.pyc,, +prompt_toolkit/filters/__pycache__/utils.cpython-38.pyc,, +prompt_toolkit/filters/app.py,sha256=jmyIqAEfOTqfe6EnPoKqjjNVMjQtkBkk4IWytYRl3_o,9799 +prompt_toolkit/filters/base.py,sha256=cJqc02EBSEdXL8E4GsceDiz7Thm4FxLvolw3WjYwXzk,5704 +prompt_toolkit/filters/cli.py,sha256=FuQMa4dCAmUIHV7aJEIsJXqAfJxlW4OeyqbaQKsY_eA,1830 +prompt_toolkit/filters/utils.py,sha256=l_cn8dAcZ4GpUa1xtBWKNvcuPhJHfg76YkjVjyjPClI,848 +prompt_toolkit/formatted_text/__init__.py,sha256=EkYZbOikE51A01oKtB1Ciodj80E7n3XPey39Yz73esk,1418 +prompt_toolkit/formatted_text/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/formatted_text/__pycache__/ansi.cpython-38.pyc,, +prompt_toolkit/formatted_text/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/formatted_text/__pycache__/html.cpython-38.pyc,, +prompt_toolkit/formatted_text/__pycache__/pygments.cpython-38.pyc,, +prompt_toolkit/formatted_text/__pycache__/utils.cpython-38.pyc,, +prompt_toolkit/formatted_text/ansi.py,sha256=z1Bt8R62vlvxBeo4s0wmsSo2Dij5ZbNdnf4O2469k7M,9750 +prompt_toolkit/formatted_text/base.py,sha256=yJz35j6hMYiY5_A-GhNHRAZ37083WBHG045Be4m3vT8,5057 +prompt_toolkit/formatted_text/html.py,sha256=FTz7rdMPNGHulFP2AAB-92MFj1PHYVh3m8-H6HOArZA,4362 +prompt_toolkit/formatted_text/pygments.py,sha256=hG1bM5iP8Gl2yc92occjeoODsbkENP1NTfJPAPUTrbg,759 +prompt_toolkit/formatted_text/utils.py,sha256=F3X6QC7sw5F9xcN238VaICUdNP30IZLUiYQYOtsIrWA,3015 +prompt_toolkit/history.py,sha256=jJhBoYb7amERSn6h7GxQ4I-fkUlT6P95UNgLZDU-cWI,9352 +prompt_toolkit/input/__init__.py,sha256=YwzIjXrGzROGZf4Uf0fiYrfD69bREXqH5goa4-s-UfE,209 +prompt_toolkit/input/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/ansi_escape_sequences.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/defaults.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/posix_pipe.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/posix_utils.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/typeahead.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/vt100.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/vt100_parser.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/win32.cpython-38.pyc,, +prompt_toolkit/input/__pycache__/win32_pipe.cpython-38.pyc,, +prompt_toolkit/input/ansi_escape_sequences.py,sha256=d4xOsJNe62jn1rYGqX00uyfrbr7S5Rz5q6cLynIGu9I,13671 +prompt_toolkit/input/base.py,sha256=20kVHqd5zDshbzNty4O5J7SAeBtyPmMO2dGnDAkwM3o,3729 +prompt_toolkit/input/defaults.py,sha256=_AioXVSd18B8gCXAUwt0zazSgwqWC6LDutu9Tyi_8sE,1788 +prompt_toolkit/input/posix_pipe.py,sha256=w9vvR7L5OtEAeEOmMIWzuz7lnBD7lGOe6FA6jDF4X4A,1822 +prompt_toolkit/input/posix_utils.py,sha256=yVx4Jq-x0w5j38SaM_yJKcl_tanlHZONe0UQQYBpbpg,3937 +prompt_toolkit/input/typeahead.py,sha256=mNwYoBagxNS73tYpXZEPI4-uQa_MR-DEWa16PGpdc2M,2538 +prompt_toolkit/input/vt100.py,sha256=4XUr4KEvtyU5PJmP3zTyqN7MjucDfJXRD9AtekQEd5c,10580 +prompt_toolkit/input/vt100_parser.py,sha256=00MW0PfbDWzXuSk-500RfklXlXIvv4O_aTQ4xMgVIm4,8400 +prompt_toolkit/input/win32.py,sha256=QvL04vewwHWxrthCZKv2tbU3ngkayZB7SCHBBIIYVAA,25689 +prompt_toolkit/input/win32_pipe.py,sha256=ttMMtZyY-52R-R7lFQxhwtR-wGmPzjOxjQ1nIF5X9bs,4125 +prompt_toolkit/key_binding/__init__.py,sha256=ZqHB3R548NETt6D6NiwWCjrxFgVRYmytC9lwY49CIMA,411 +prompt_toolkit/key_binding/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/key_binding/__pycache__/defaults.cpython-38.pyc,, +prompt_toolkit/key_binding/__pycache__/digraphs.cpython-38.pyc,, +prompt_toolkit/key_binding/__pycache__/emacs_state.cpython-38.pyc,, +prompt_toolkit/key_binding/__pycache__/key_bindings.cpython-38.pyc,, +prompt_toolkit/key_binding/__pycache__/key_processor.cpython-38.pyc,, +prompt_toolkit/key_binding/__pycache__/vi_state.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +prompt_toolkit/key_binding/bindings/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/auto_suggest.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/basic.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/completion.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/cpr.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/emacs.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/focus.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/mouse.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/named_commands.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/open_in_editor.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/page_navigation.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/scroll.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/search.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/__pycache__/vi.cpython-38.pyc,, +prompt_toolkit/key_binding/bindings/auto_suggest.py,sha256=ucar3OEvUSMJUqK1ks3LBNBCybvjIqson0mYcisPLAc,1808 +prompt_toolkit/key_binding/bindings/basic.py,sha256=-vYYqiB7lmA4PKcPJbgKEr2vT9lruCaM0NA6OzQ9jek,7215 +prompt_toolkit/key_binding/bindings/completion.py,sha256=iVQA1RiSgBq-JjrEHIFGwP2hcn5mtv0cg7Cd6jhSw-g,6878 +prompt_toolkit/key_binding/bindings/cpr.py,sha256=O5iNE9POKF9_l-eOuc0bgLFi466bmX2RthWBEsAO5Zc,750 +prompt_toolkit/key_binding/bindings/emacs.py,sha256=C_UoIDgrKwIYxJmnvyKxUiQxLh9do0Tr-19as1wuJ8E,19613 +prompt_toolkit/key_binding/bindings/focus.py,sha256=s2_w73r-oUSMv6LbiNHcZBGe9REOoER9nHzRgaiu8_U,471 +prompt_toolkit/key_binding/bindings/mouse.py,sha256=Dfax7oFV5wTK_zQAHIxZE9pjvnedyuzh4hT0o0aGw_8,18475 +prompt_toolkit/key_binding/bindings/named_commands.py,sha256=oY3G765YilI12DKZllwxxQDGY0Pwwqd_RLJaUsw6bpE,18375 +prompt_toolkit/key_binding/bindings/open_in_editor.py,sha256=URpsxHfjB_BgJpTa8dd0esoshbOyrGx0cp2YqWyHkBM,1319 +prompt_toolkit/key_binding/bindings/page_navigation.py,sha256=fGbcbiP39qCHBq5K1OfgZRaxGCUE77D0XhZSxYGLp7A,2355 +prompt_toolkit/key_binding/bindings/scroll.py,sha256=XN05lfh9U6wdD1_fuNqb6AkxEhBbRwzPgsnzq1grrjs,5576 +prompt_toolkit/key_binding/bindings/search.py,sha256=nlanphOqCwz787C3bxPKWK-e5-taxKqWbPyCWbnqMoE,2595 +prompt_toolkit/key_binding/bindings/vi.py,sha256=mJWlxOQeu9chlc9b22oZQeeQci7oO9U3lN_p3Un-lIo,75521 +prompt_toolkit/key_binding/defaults.py,sha256=p5AviNKVJsYzOTKOVwKFMei1RVyqiiHuU-AkTUKUPMw,1938 +prompt_toolkit/key_binding/digraphs.py,sha256=aBtRvgSO13UlBv9rgc5nMhItgm6KcjVw3NK5wDKZrD0,32780 +prompt_toolkit/key_binding/emacs_state.py,sha256=GNKQEDHii1wNIlbG82L95ib3UUC9KmF-qiu0bjnM05c,889 +prompt_toolkit/key_binding/key_bindings.py,sha256=XWX_zdcQdmZpg-dXJ3ulJWEQugjHgjpsSpmBe-QUIBo,20967 +prompt_toolkit/key_binding/key_processor.py,sha256=zfrsdfER2WDZZV2QK96tflh4S5iJFtPR9zmjgGptBb4,17641 +prompt_toolkit/key_binding/vi_state.py,sha256=6LVK_dDuYdjS3ON1nCb66IVDVaUo4KVWc6UQjn2B3xQ,3360 +prompt_toolkit/keys.py,sha256=4fMQJ2TLpyUjeKERxkjc0f4wRO9SiAHWb9eIcQ4_VZQ,4910 +prompt_toolkit/layout/__init__.py,sha256=xX1hmV1blE2JireJX_9jMtO-7wDmPNj9z1yFq7Ai3H4,3566 +prompt_toolkit/layout/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/containers.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/controls.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/dimension.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/dummy.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/layout.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/margins.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/menus.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/mouse_handlers.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/processors.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/screen.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/scrollable_pane.cpython-38.pyc,, +prompt_toolkit/layout/__pycache__/utils.cpython-38.pyc,, +prompt_toolkit/layout/containers.py,sha256=tMf2KoYAqJ9GbvACm8vqJv8sFYwAJxnELHOI9HCUbyI,99601 +prompt_toolkit/layout/controls.py,sha256=XY1oi8Nk3SvnnbNcpWCGngjT6RXJtTxD2IHk-nvfG5Q,35957 +prompt_toolkit/layout/dimension.py,sha256=lBzx48D7djEq69TOosmZbYcIyyYzIr1yMQaMZRSWHnA,7043 +prompt_toolkit/layout/dummy.py,sha256=OUqbukgJtpzP8Oiqq5tJm1CDezvpqfr_X6o7atsEd_0,1010 +prompt_toolkit/layout/layout.py,sha256=P7Z9FoI0BUUt-OdaDD8ONU-FjN18xuKMtszlmDbIngE,13988 +prompt_toolkit/layout/margins.py,sha256=In3rqV2UsLP2wlKKqw4HOD4ntXXirpDiQR34KB5lBaE,10378 +prompt_toolkit/layout/menus.py,sha256=0N5-v05ME3weTfS6PW9lTPRQFKjanUK8VoUZqvyLvmI,25404 +prompt_toolkit/layout/mouse_handlers.py,sha256=sRHlAkwYfKS4haejuPszOb6HHYJfVAt-2RydGcmiZuY,1568 +prompt_toolkit/layout/processors.py,sha256=o4LQ_dww-unVfCi4FhEK64Vo0I5iaMgbmifrih3LkHs,34134 +prompt_toolkit/layout/screen.py,sha256=oCArHHj4q_L55i9oqmubF4lJYXClYS5fSFL8f4DJhiY,10220 +prompt_toolkit/layout/scrollable_pane.py,sha256=ChOrxN1Cnh4L2AfVHEOA_UpeLeqscHyoi5jDKba_pEI,19230 +prompt_toolkit/layout/utils.py,sha256=1SDuikXiu_Huzjizq8vcHVbs3ZFJ-s3TCfh0gU869ug,2430 +prompt_toolkit/lexers/__init__.py,sha256=KHqcFmNnehEcLINMN8X52iMvT9I21NNJ5vl4i8NGEGQ,372 +prompt_toolkit/lexers/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/lexers/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/lexers/__pycache__/pygments.cpython-38.pyc,, +prompt_toolkit/lexers/base.py,sha256=6dBWaxJs9qQ5qgLbFDR8_MlKu_wYjjJISYMIervn-lI,2326 +prompt_toolkit/lexers/pygments.py,sha256=VhXla8sgzQ5kM0Aup_Zm9Xm50EDbmD-H2Rt4xvRA5BY,11952 +prompt_toolkit/log.py,sha256=mM3nJZ0GvsDSyr4awNl7ywzKXAu3Cc_o5lIoQTpC1bs,116 +prompt_toolkit/mouse_events.py,sha256=9KAU2ZKU8f_h7ScjccHQ8bEbPTFdZ_5o4QxLmiRUYpU,2538 +prompt_toolkit/output/__init__.py,sha256=077-T8XZ4fD4pFeo7FtbUYnoYoIZMriD6CI6ynsJa5E,244 +prompt_toolkit/output/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/color_depth.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/conemu.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/defaults.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/flush_stdout.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/plain_text.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/vt100.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/win32.cpython-38.pyc,, +prompt_toolkit/output/__pycache__/windows10.cpython-38.pyc,, +prompt_toolkit/output/base.py,sha256=erJ_QyPozt3c2s7DSF3W2fBnvuEa6Yj4tQImDGqLXsk,8326 +prompt_toolkit/output/color_depth.py,sha256=EtFC46oJS3FprQh3L_r1_rLf2fxpwvzu_r1E0uQdggI,1387 +prompt_toolkit/output/conemu.py,sha256=34W9W1qxeO1RJGKbawvwZpMu3R51GSGG3L_hzaqjqbo,1798 +prompt_toolkit/output/defaults.py,sha256=lOxmB8vXVVvX3Z-SE7g5XNxtfGdvpAeJAn8QWBT0w2s,3566 +prompt_toolkit/output/flush_stdout.py,sha256=VpFSNv93iNomcseOVTrV5ntrtn_qOjmnbq5KU1_zKLA,3028 +prompt_toolkit/output/plain_text.py,sha256=dIn9GWl2qjd_ZrYRhOhfzEOF4wnXL79q_Gp7K1Y6DBk,3439 +prompt_toolkit/output/vt100.py,sha256=5w1OdEMDGcPJ-S0Zn78bTYnVhD9haMJz4enJAR64w9s,22769 +prompt_toolkit/output/win32.py,sha256=w0LhapiiozCi3FuN1RKd9RPMf4t6Aj_oDf_Yr_jweDM,22904 +prompt_toolkit/output/windows10.py,sha256=U6rJGTXZHw1xamHwdgF_ohRQmHLHmXalkJZBX_4Rjwg,3155 +prompt_toolkit/patch_stdout.py,sha256=hcFYdYiOt-_dnHTW_0pQ6xdlFw74Pghh3K4qTgtclDk,9058 +prompt_toolkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +prompt_toolkit/renderer.py,sha256=ie4nVD3yRR18_9WYX_pYcdcwKlbT4SfWc5Rg0ftIEMk,29047 +prompt_toolkit/search.py,sha256=9mUgXsUKN9dl82j7L6H6ORsuf3LpXgaENOFVdN_1I3U,7022 +prompt_toolkit/selection.py,sha256=vDA1DNZXrUuUCjtdus_3YocFD-c-qup-T2APIp1AMzc,1297 +prompt_toolkit/shortcuts/__init__.py,sha256=qmbaI4bpzC9K-gcivcAfKaw_0IxB3SvIZ00iZZH28Do,913 +prompt_toolkit/shortcuts/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/shortcuts/__pycache__/dialogs.cpython-38.pyc,, +prompt_toolkit/shortcuts/__pycache__/prompt.cpython-38.pyc,, +prompt_toolkit/shortcuts/__pycache__/utils.cpython-38.pyc,, +prompt_toolkit/shortcuts/dialogs.py,sha256=LOzT1kvGyMR5Qzv5Kgl-5J7cYd11OecyXZ0fSWooltk,8968 +prompt_toolkit/shortcuts/progress_bar/__init__.py,sha256=JZ6nHSonthtbWKxg51XqUIQflJFeAEvyDyk0_LCrjyo,504 +prompt_toolkit/shortcuts/progress_bar/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/shortcuts/progress_bar/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/shortcuts/progress_bar/__pycache__/formatters.cpython-38.pyc,, +prompt_toolkit/shortcuts/progress_bar/base.py,sha256=trOTc83dzh-Scp022Ykcx0kk3USZCHvtDuH-NoRG1Lc,13725 +prompt_toolkit/shortcuts/progress_bar/formatters.py,sha256=Q0HF-o58935fjy2gAcqAQCVVeXrz2OKAVnhqJAPY5Vc,11763 +prompt_toolkit/shortcuts/prompt.py,sha256=RpL7IcDOdR1JyUceDrCFokxdbSm9koy1n80jQNB0RJA,59579 +prompt_toolkit/shortcuts/utils.py,sha256=lDR2lqufH8XWgghErNhhhJVjt2FKjgy9-mP5Sqy-Nok,7135 +prompt_toolkit/styles/__init__.py,sha256=5unzwZ5xM75TIOz3uwr6nsvSTaFMC9AeEMF4y_GZeNA,1603 +prompt_toolkit/styles/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/styles/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/styles/__pycache__/defaults.cpython-38.pyc,, +prompt_toolkit/styles/__pycache__/named_colors.cpython-38.pyc,, +prompt_toolkit/styles/__pycache__/pygments.cpython-38.pyc,, +prompt_toolkit/styles/__pycache__/style.cpython-38.pyc,, +prompt_toolkit/styles/__pycache__/style_transformation.cpython-38.pyc,, +prompt_toolkit/styles/base.py,sha256=J5Johh2OOH78N96zJZAwx0YwXtAFMEkLeJ7BD6UHEEo,5036 +prompt_toolkit/styles/defaults.py,sha256=WLzmO7RJSdSWCBv4T72GGP5jXeeGXWIOtZkwI89xd8o,8512 +prompt_toolkit/styles/named_colors.py,sha256=UPxQnEm8gxROOwO3V-8GNAUMaBiQoaUCBi4Mr9VZTL8,4355 +prompt_toolkit/styles/pygments.py,sha256=BH0tmECRVRXQXJkBc5CJvDzd7XSLHYkJg2KwA1mK6lw,1955 +prompt_toolkit/styles/style.py,sha256=yeKi4LVVAzHfdBrD7DqPfnQzeqw3KQsNN6-nDaGzXx0,13045 +prompt_toolkit/styles/style_transformation.py,sha256=Ba6Tzi59XdTYY-v1MWuNnSDGnDN_DseR3OxjjFGNb_8,12439 +prompt_toolkit/token.py,sha256=4_osqr_9234wZax-5eMba3osY4wCpowL4nyBiI5Y02E,85 +prompt_toolkit/utils.py,sha256=o478yPi1s-3bBwsBG6G5Lb1CKqvsE1-_LWll5gU8_oQ,8617 +prompt_toolkit/validation.py,sha256=px26dJKq_Zf85aYhbb_AQMF_jlln4yWl1uTLJOzuCc4,5845 +prompt_toolkit/widgets/__init__.py,sha256=hlL7bmjGESgHh7A_AnmiOjzokDZbqf_wjV_y-CUacNY,1181 +prompt_toolkit/widgets/__pycache__/__init__.cpython-38.pyc,, +prompt_toolkit/widgets/__pycache__/base.cpython-38.pyc,, +prompt_toolkit/widgets/__pycache__/dialogs.cpython-38.pyc,, +prompt_toolkit/widgets/__pycache__/menus.cpython-38.pyc,, +prompt_toolkit/widgets/__pycache__/toolbars.cpython-38.pyc,, +prompt_toolkit/widgets/base.py,sha256=Mbk-z54U4fqpHr5BYP13G89IGLFQnaFglBLYyU02ig4,31874 +prompt_toolkit/widgets/dialogs.py,sha256=7sOdcweXSB8qdfuYcL7rgfd2uqvIF6f-9dJcdUmbmwk,3370 +prompt_toolkit/widgets/menus.py,sha256=0LIpF2mYQ2AdGxzqjdtDKUotzou2DOYOLjROohqdbSo,13438 +prompt_toolkit/widgets/toolbars.py,sha256=IV3lsN7I5eOUw6chCnQZy4PEKv4ru8eReOVny5IK6Ko,12258 +prompt_toolkit/win32_types.py,sha256=q-K1rNmdnlgfcRHemJJZYI7wbCSR315ipTpDXZJ8AW0,5515 diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/top_level.txt new file mode 100644 index 0000000..29392df --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit-3.0.28.dist-info/top_level.txt @@ -0,0 +1 @@ +prompt_toolkit diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/__init__.py new file mode 100644 index 0000000..f3423df --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/__init__.py @@ -0,0 +1,40 @@ +""" +prompt_toolkit +============== + +Author: Jonathan Slenders + +Description: prompt_toolkit is a Library for building powerful interactive + command lines in Python. It can be a replacement for GNU + Readline, but it can be much more than that. + +See the examples directory to learn about the usage. + +Probably, to get started, you might also want to have a look at +`prompt_toolkit.shortcuts.prompt`. +""" +from .application import Application +from .formatted_text import ANSI, HTML +from .shortcuts import PromptSession, print_formatted_text, prompt + +# Don't forget to update in `docs/conf.py`! +__version__ = "3.0.28" + +# Version tuple. +VERSION = tuple(__version__.split(".")) + + +__all__ = [ + # Application. + "Application", + # Shortcuts. + "prompt", + "PromptSession", + "print_formatted_text", + # Formatted text. + "HTML", + "ANSI", + # Version info. + "__version__", + "VERSION", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f4c2268 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/auto_suggest.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/auto_suggest.cpython-38.pyc new file mode 100644 index 0000000..f53e995 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/auto_suggest.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/buffer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/buffer.cpython-38.pyc new file mode 100644 index 0000000..1623dc3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/buffer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/cache.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/cache.cpython-38.pyc new file mode 100644 index 0000000..4251678 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/cache.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/cursor_shapes.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/cursor_shapes.cpython-38.pyc new file mode 100644 index 0000000..b4be601 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/cursor_shapes.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/data_structures.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/data_structures.cpython-38.pyc new file mode 100644 index 0000000..39ce4aa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/data_structures.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/document.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/document.cpython-38.pyc new file mode 100644 index 0000000..978009a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/document.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/enums.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/enums.cpython-38.pyc new file mode 100644 index 0000000..d11cfd6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/enums.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/history.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/history.cpython-38.pyc new file mode 100644 index 0000000..0b2a93c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/history.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/keys.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/keys.cpython-38.pyc new file mode 100644 index 0000000..71a40ae Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/keys.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/log.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/log.cpython-38.pyc new file mode 100644 index 0000000..50857af Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/log.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/mouse_events.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/mouse_events.cpython-38.pyc new file mode 100644 index 0000000..bc55b5e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/mouse_events.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/patch_stdout.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/patch_stdout.cpython-38.pyc new file mode 100644 index 0000000..51972a3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/patch_stdout.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/renderer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/renderer.cpython-38.pyc new file mode 100644 index 0000000..2a93286 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/renderer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/search.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/search.cpython-38.pyc new file mode 100644 index 0000000..0021e8f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/search.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/selection.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/selection.cpython-38.pyc new file mode 100644 index 0000000..9215de1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/selection.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/token.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/token.cpython-38.pyc new file mode 100644 index 0000000..6167fac Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/token.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..bdd3272 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/validation.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/validation.cpython-38.pyc new file mode 100644 index 0000000..3fb500e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/validation.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/win32_types.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/win32_types.cpython-38.pyc new file mode 100644 index 0000000..f315342 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/__pycache__/win32_types.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__init__.py new file mode 100644 index 0000000..dc61ca7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__init__.py @@ -0,0 +1,30 @@ +from .application import Application +from .current import ( + AppSession, + create_app_session, + create_app_session_from_tty, + get_app, + get_app_or_none, + get_app_session, + set_app, +) +from .dummy import DummyApplication +from .run_in_terminal import in_terminal, run_in_terminal + +__all__ = [ + # Application. + "Application", + # Current. + "AppSession", + "get_app_session", + "create_app_session", + "create_app_session_from_tty", + "get_app", + "get_app_or_none", + "set_app", + # Dummy. + "DummyApplication", + # Run_in_terminal + "in_terminal", + "run_in_terminal", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..686780e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/application.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/application.cpython-38.pyc new file mode 100644 index 0000000..bb0f462 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/application.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/current.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/current.cpython-38.pyc new file mode 100644 index 0000000..8bcf0f4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/current.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/dummy.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/dummy.cpython-38.pyc new file mode 100644 index 0000000..58a21ff Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/dummy.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/run_in_terminal.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/run_in_terminal.cpython-38.pyc new file mode 100644 index 0000000..4e947cc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/__pycache__/run_in_terminal.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/application.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/application.py new file mode 100644 index 0000000..07b81d5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/application.py @@ -0,0 +1,1428 @@ +import asyncio +import os +import re +import signal +import sys +import threading +import time +from asyncio import ( + AbstractEventLoop, + CancelledError, + Future, + Task, + ensure_future, + new_event_loop, + set_event_loop, + sleep, +) +from contextlib import contextmanager +from subprocess import Popen +from traceback import format_tb +from typing import ( + Any, + Awaitable, + Callable, + Dict, + FrozenSet, + Generator, + Generic, + Hashable, + Iterable, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, + overload, +) + +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.cache import SimpleCache +from prompt_toolkit.clipboard import Clipboard, InMemoryClipboard +from prompt_toolkit.cursor_shapes import AnyCursorShapeConfig, to_cursor_shape_config +from prompt_toolkit.data_structures import Size +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.eventloop import ( + get_traceback_from_context, + run_in_executor_with_context, +) +from prompt_toolkit.eventloop.utils import call_soon_threadsafe, get_event_loop +from prompt_toolkit.filters import Condition, Filter, FilterOrBool, to_filter +from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.input.base import Input +from prompt_toolkit.input.typeahead import get_typeahead, store_typeahead +from prompt_toolkit.key_binding.bindings.page_navigation import ( + load_page_navigation_bindings, +) +from prompt_toolkit.key_binding.defaults import load_key_bindings +from prompt_toolkit.key_binding.emacs_state import EmacsState +from prompt_toolkit.key_binding.key_bindings import ( + Binding, + ConditionalKeyBindings, + GlobalOnlyKeyBindings, + KeyBindings, + KeyBindingsBase, + KeysTuple, + merge_key_bindings, +) +from prompt_toolkit.key_binding.key_processor import KeyPressEvent, KeyProcessor +from prompt_toolkit.key_binding.vi_state import ViState +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout.containers import Container, Window +from prompt_toolkit.layout.controls import BufferControl, UIControl +from prompt_toolkit.layout.dummy import create_dummy_layout +from prompt_toolkit.layout.layout import Layout, walk +from prompt_toolkit.output import ColorDepth, Output +from prompt_toolkit.renderer import Renderer, print_formatted_text +from prompt_toolkit.search import SearchState +from prompt_toolkit.styles import ( + BaseStyle, + DummyStyle, + DummyStyleTransformation, + DynamicStyle, + StyleTransformation, + default_pygments_style, + default_ui_style, + merge_styles, +) +from prompt_toolkit.utils import Event, in_main_thread + +from ..utils import is_windows +from .current import get_app_session, set_app +from .run_in_terminal import in_terminal, run_in_terminal + +try: + import contextvars +except ImportError: + import prompt_toolkit.eventloop.dummy_contextvars as contextvars # type: ignore + + +__all__ = [ + "Application", +] + + +E = KeyPressEvent +_AppResult = TypeVar("_AppResult") +ApplicationEventHandler = Callable[["Application[_AppResult]"], None] + +_SIGWINCH = getattr(signal, "SIGWINCH", None) +_SIGTSTP = getattr(signal, "SIGTSTP", None) + + +class Application(Generic[_AppResult]): + """ + The main Application class! + This glues everything together. + + :param layout: A :class:`~prompt_toolkit.layout.Layout` instance. + :param key_bindings: + :class:`~prompt_toolkit.key_binding.KeyBindingsBase` instance for + the key bindings. + :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` to use. + :param full_screen: When True, run the application on the alternate screen buffer. + :param color_depth: Any :class:`~.ColorDepth` value, a callable that + returns a :class:`~.ColorDepth` or `None` for default. + :param erase_when_done: (bool) Clear the application output when it finishes. + :param reverse_vi_search_direction: Normally, in Vi mode, a '/' searches + forward and a '?' searches backward. In Readline mode, this is usually + reversed. + :param min_redraw_interval: Number of seconds to wait between redraws. Use + this for applications where `invalidate` is called a lot. This could cause + a lot of terminal output, which some terminals are not able to process. + + `None` means that every `invalidate` will be scheduled right away + (which is usually fine). + + When one `invalidate` is called, but a scheduled redraw of a previous + `invalidate` call has not been executed yet, nothing will happen in any + case. + + :param max_render_postpone_time: When there is high CPU (a lot of other + scheduled calls), postpone the rendering max x seconds. '0' means: + don't postpone. '.5' means: try to draw at least twice a second. + + :param refresh_interval: Automatically invalidate the UI every so many + seconds. When `None` (the default), only invalidate when `invalidate` + has been called. + + :param terminal_size_polling_interval: Poll the terminal size every so many + seconds. Useful if the applications runs in a thread other then then + main thread where SIGWINCH can't be handled, or on Windows. + + Filters: + + :param mouse_support: (:class:`~prompt_toolkit.filters.Filter` or + boolean). When True, enable mouse support. + :param paste_mode: :class:`~prompt_toolkit.filters.Filter` or boolean. + :param editing_mode: :class:`~prompt_toolkit.enums.EditingMode`. + + :param enable_page_navigation_bindings: When `True`, enable the page + navigation key bindings. These include both Emacs and Vi bindings like + page-up, page-down and so on to scroll through pages. Mostly useful for + creating an editor or other full screen applications. Probably, you + don't want this for the implementation of a REPL. By default, this is + enabled if `full_screen` is set. + + Callbacks (all of these should accept an + :class:`~prompt_toolkit.application.Application` object as input.) + + :param on_reset: Called during reset. + :param on_invalidate: Called when the UI has been invalidated. + :param before_render: Called right before rendering. + :param after_render: Called right after rendering. + + I/O: + (Note that the preferred way to change the input/output is by creating an + `AppSession` with the required input/output objects. If you need multiple + applications running at the same time, you have to create a separate + `AppSession` using a `with create_app_session():` block. + + :param input: :class:`~prompt_toolkit.input.Input` instance. + :param output: :class:`~prompt_toolkit.output.Output` instance. (Probably + Vt100_Output or Win32Output.) + + Usage: + + app = Application(...) + app.run() + + # Or + await app.run_async() + """ + + def __init__( + self, + layout: Optional[Layout] = None, + style: Optional[BaseStyle] = None, + include_default_pygments_style: FilterOrBool = True, + style_transformation: Optional[StyleTransformation] = None, + key_bindings: Optional[KeyBindingsBase] = None, + clipboard: Optional[Clipboard] = None, + full_screen: bool = False, + color_depth: Union[ + ColorDepth, Callable[[], Union[ColorDepth, None]], None + ] = None, + mouse_support: FilterOrBool = False, + enable_page_navigation_bindings: Optional[ + FilterOrBool + ] = None, # Can be None, True or False. + paste_mode: FilterOrBool = False, + editing_mode: EditingMode = EditingMode.EMACS, + erase_when_done: bool = False, + reverse_vi_search_direction: FilterOrBool = False, + min_redraw_interval: Union[float, int, None] = None, + max_render_postpone_time: Union[float, int, None] = 0.01, + refresh_interval: Optional[float] = None, + terminal_size_polling_interval: Optional[float] = 0.5, + cursor: AnyCursorShapeConfig = None, + on_reset: Optional["ApplicationEventHandler[_AppResult]"] = None, + on_invalidate: Optional["ApplicationEventHandler[_AppResult]"] = None, + before_render: Optional["ApplicationEventHandler[_AppResult]"] = None, + after_render: Optional["ApplicationEventHandler[_AppResult]"] = None, + # I/O. + input: Optional[Input] = None, + output: Optional[Output] = None, + ) -> None: + + # If `enable_page_navigation_bindings` is not specified, enable it in + # case of full screen applications only. This can be overridden by the user. + if enable_page_navigation_bindings is None: + enable_page_navigation_bindings = Condition(lambda: self.full_screen) + + paste_mode = to_filter(paste_mode) + mouse_support = to_filter(mouse_support) + reverse_vi_search_direction = to_filter(reverse_vi_search_direction) + enable_page_navigation_bindings = to_filter(enable_page_navigation_bindings) + include_default_pygments_style = to_filter(include_default_pygments_style) + + if layout is None: + layout = create_dummy_layout() + + if style_transformation is None: + style_transformation = DummyStyleTransformation() + + self.style = style + self.style_transformation = style_transformation + + # Key bindings. + self.key_bindings = key_bindings + self._default_bindings = load_key_bindings() + self._page_navigation_bindings = load_page_navigation_bindings() + + self.layout = layout + self.clipboard = clipboard or InMemoryClipboard() + self.full_screen: bool = full_screen + self._color_depth = color_depth + self.mouse_support = mouse_support + + self.paste_mode = paste_mode + self.editing_mode = editing_mode + self.erase_when_done = erase_when_done + self.reverse_vi_search_direction = reverse_vi_search_direction + self.enable_page_navigation_bindings = enable_page_navigation_bindings + self.min_redraw_interval = min_redraw_interval + self.max_render_postpone_time = max_render_postpone_time + self.refresh_interval = refresh_interval + self.terminal_size_polling_interval = terminal_size_polling_interval + + self.cursor = to_cursor_shape_config(cursor) + + # Events. + self.on_invalidate = Event(self, on_invalidate) + self.on_reset = Event(self, on_reset) + self.before_render = Event(self, before_render) + self.after_render = Event(self, after_render) + + # I/O. + session = get_app_session() + self.output = output or session.output + self.input = input or session.input + + # List of 'extra' functions to execute before a Application.run. + self.pre_run_callables: List[Callable[[], None]] = [] + + self._is_running = False + self.future: Optional[Future[_AppResult]] = None + self.loop: Optional[AbstractEventLoop] = None + self.context: Optional[contextvars.Context] = None + + #: Quoted insert. This flag is set if we go into quoted insert mode. + self.quoted_insert = False + + #: Vi state. (For Vi key bindings.) + self.vi_state = ViState() + self.emacs_state = EmacsState() + + #: When to flush the input (For flushing escape keys.) This is important + #: on terminals that use vt100 input. We can't distinguish the escape + #: key from for instance the left-arrow key, if we don't know what follows + #: after "\x1b". This little timer will consider "\x1b" to be escape if + #: nothing did follow in this time span. + #: This seems to work like the `ttimeoutlen` option in Vim. + self.ttimeoutlen = 0.5 # Seconds. + + #: Like Vim's `timeoutlen` option. This can be `None` or a float. For + #: instance, suppose that we have a key binding AB and a second key + #: binding A. If the uses presses A and then waits, we don't handle + #: this binding yet (unless it was marked 'eager'), because we don't + #: know what will follow. This timeout is the maximum amount of time + #: that we wait until we call the handlers anyway. Pass `None` to + #: disable this timeout. + self.timeoutlen = 1.0 + + #: The `Renderer` instance. + # Make sure that the same stdout is used, when a custom renderer has been passed. + self._merged_style = self._create_merged_style(include_default_pygments_style) + + self.renderer = Renderer( + self._merged_style, + self.output, + full_screen=full_screen, + mouse_support=mouse_support, + cpr_not_supported_callback=self.cpr_not_supported_callback, + ) + + #: Render counter. This one is increased every time the UI is rendered. + #: It can be used as a key for caching certain information during one + #: rendering. + self.render_counter = 0 + + # Invalidate flag. When 'True', a repaint has been scheduled. + self._invalidated = False + self._invalidate_events: List[ + Event[object] + ] = [] # Collection of 'invalidate' Event objects. + self._last_redraw_time = 0.0 # Unix timestamp of last redraw. Used when + # `min_redraw_interval` is given. + + #: The `InputProcessor` instance. + self.key_processor = KeyProcessor(_CombinedRegistry(self)) + + # If `run_in_terminal` was called. This will point to a `Future` what will be + # set at the point when the previous run finishes. + self._running_in_terminal = False + self._running_in_terminal_f: Optional[Future[None]] = None + + # Trigger initialize callback. + self.reset() + + def _create_merged_style(self, include_default_pygments_style: Filter) -> BaseStyle: + """ + Create a `Style` object that merges the default UI style, the default + pygments style, and the custom user style. + """ + dummy_style = DummyStyle() + pygments_style = default_pygments_style() + + @DynamicStyle + def conditional_pygments_style() -> BaseStyle: + if include_default_pygments_style(): + return pygments_style + else: + return dummy_style + + return merge_styles( + [ + default_ui_style(), + conditional_pygments_style, + DynamicStyle(lambda: self.style), + ] + ) + + @property + def color_depth(self) -> ColorDepth: + """ + The active :class:`.ColorDepth`. + + The current value is determined as follows: + + - If a color depth was given explicitly to this application, use that + value. + - Otherwise, fall back to the color depth that is reported by the + :class:`.Output` implementation. If the :class:`.Output` class was + created using `output.defaults.create_output`, then this value is + coming from the $PROMPT_TOOLKIT_COLOR_DEPTH environment variable. + """ + depth = self._color_depth + + if callable(depth): + depth = depth() + + if depth is None: + depth = self.output.get_default_color_depth() + + return depth + + @property + def current_buffer(self) -> Buffer: + """ + The currently focused :class:`~.Buffer`. + + (This returns a dummy :class:`.Buffer` when none of the actual buffers + has the focus. In this case, it's really not practical to check for + `None` values or catch exceptions every time.) + """ + return self.layout.current_buffer or Buffer( + name="dummy-buffer" + ) # Dummy buffer. + + @property + def current_search_state(self) -> SearchState: + """ + Return the current :class:`.SearchState`. (The one for the focused + :class:`.BufferControl`.) + """ + ui_control = self.layout.current_control + if isinstance(ui_control, BufferControl): + return ui_control.search_state + else: + return SearchState() # Dummy search state. (Don't return None!) + + def reset(self) -> None: + """ + Reset everything, for reading the next input. + """ + # Notice that we don't reset the buffers. (This happens just before + # returning, and when we have multiple buffers, we clearly want the + # content in the other buffers to remain unchanged between several + # calls of `run`. (And the same is true for the focus stack.) + + self.exit_style = "" + + self.background_tasks: List[Task[None]] = [] + + self.renderer.reset() + self.key_processor.reset() + self.layout.reset() + self.vi_state.reset() + self.emacs_state.reset() + + # Trigger reset event. + self.on_reset.fire() + + # Make sure that we have a 'focusable' widget focused. + # (The `Layout` class can't determine this.) + layout = self.layout + + if not layout.current_control.is_focusable(): + for w in layout.find_all_windows(): + if w.content.is_focusable(): + layout.current_window = w + break + + def invalidate(self) -> None: + """ + Thread safe way of sending a repaint trigger to the input event loop. + """ + if not self._is_running: + # Don't schedule a redraw if we're not running. + # Otherwise, `get_event_loop()` in `call_soon_threadsafe` can fail. + # See: https://github.com/dbcli/mycli/issues/797 + return + + # `invalidate()` called if we don't have a loop yet (not running?), or + # after the event loop was closed. + if self.loop is None or self.loop.is_closed(): + return + + # Never schedule a second redraw, when a previous one has not yet been + # executed. (This should protect against other threads calling + # 'invalidate' many times, resulting in 100% CPU.) + if self._invalidated: + return + else: + self._invalidated = True + + # Trigger event. + self.loop.call_soon_threadsafe(self.on_invalidate.fire) + + def redraw() -> None: + self._invalidated = False + self._redraw() + + def schedule_redraw() -> None: + call_soon_threadsafe( + redraw, max_postpone_time=self.max_render_postpone_time, loop=self.loop + ) + + if self.min_redraw_interval: + # When a minimum redraw interval is set, wait minimum this amount + # of time between redraws. + diff = time.time() - self._last_redraw_time + if diff < self.min_redraw_interval: + + async def redraw_in_future() -> None: + await sleep(cast(float, self.min_redraw_interval) - diff) + schedule_redraw() + + self.loop.call_soon_threadsafe( + lambda: self.create_background_task(redraw_in_future()) + ) + else: + schedule_redraw() + else: + schedule_redraw() + + @property + def invalidated(self) -> bool: + "True when a redraw operation has been scheduled." + return self._invalidated + + def _redraw(self, render_as_done: bool = False) -> None: + """ + Render the command line again. (Not thread safe!) (From other threads, + or if unsure, use :meth:`.Application.invalidate`.) + + :param render_as_done: make sure to put the cursor after the UI. + """ + + def run_in_context() -> None: + # Only draw when no sub application was started. + if self._is_running and not self._running_in_terminal: + if self.min_redraw_interval: + self._last_redraw_time = time.time() + + # Render + self.render_counter += 1 + self.before_render.fire() + + if render_as_done: + if self.erase_when_done: + self.renderer.erase() + else: + # Draw in 'done' state and reset renderer. + self.renderer.render(self, self.layout, is_done=render_as_done) + else: + self.renderer.render(self, self.layout) + + self.layout.update_parents_relations() + + # Fire render event. + self.after_render.fire() + + self._update_invalidate_events() + + # NOTE: We want to make sure this Application is the active one. The + # invalidate function is often called from a context where this + # application is not the active one. (Like the + # `PromptSession._auto_refresh_context`). + # We copy the context in case the context was already active, to + # prevent RuntimeErrors. (The rendering is not supposed to change + # any context variables.) + if self.context is not None: + self.context.copy().run(run_in_context) + + def _start_auto_refresh_task(self) -> None: + """ + Start a while/true loop in the background for automatic invalidation of + the UI. + """ + if self.refresh_interval is not None and self.refresh_interval != 0: + + async def auto_refresh(refresh_interval: float) -> None: + while True: + await sleep(refresh_interval) + self.invalidate() + + self.create_background_task(auto_refresh(self.refresh_interval)) + + def _update_invalidate_events(self) -> None: + """ + Make sure to attach 'invalidate' handlers to all invalidate events in + the UI. + """ + # Remove all the original event handlers. (Components can be removed + # from the UI.) + for ev in self._invalidate_events: + ev -= self._invalidate_handler + + # Gather all new events. + # (All controls are able to invalidate themselves.) + def gather_events() -> Iterable[Event[object]]: + for c in self.layout.find_all_controls(): + yield from c.get_invalidate_events() + + self._invalidate_events = list(gather_events()) + + for ev in self._invalidate_events: + ev += self._invalidate_handler + + def _invalidate_handler(self, sender: object) -> None: + """ + Handler for invalidate events coming from UIControls. + + (This handles the difference in signature between event handler and + `self.invalidate`. It also needs to be a method -not a nested + function-, so that we can remove it again .) + """ + self.invalidate() + + def _on_resize(self) -> None: + """ + When the window size changes, we erase the current output and request + again the cursor position. When the CPR answer arrives, the output is + drawn again. + """ + # Erase, request position (when cursor is at the start position) + # and redraw again. -- The order is important. + self.renderer.erase(leave_alternate_screen=False) + self._request_absolute_cursor_position() + self._redraw() + + def _pre_run(self, pre_run: Optional[Callable[[], None]] = None) -> None: + """ + Called during `run`. + + `self.future` should be set to the new future at the point where this + is called in order to avoid data races. `pre_run` can be used to set a + `threading.Event` to synchronize with UI termination code, running in + another thread that would call `Application.exit`. (See the progress + bar code for an example.) + """ + if pre_run: + pre_run() + + # Process registered "pre_run_callables" and clear list. + for c in self.pre_run_callables: + c() + del self.pre_run_callables[:] + + async def run_async( + self, + pre_run: Optional[Callable[[], None]] = None, + set_exception_handler: bool = True, + handle_sigint: bool = True, + slow_callback_duration: float = 0.5, + ) -> _AppResult: + """ + Run the prompt_toolkit :class:`~prompt_toolkit.application.Application` + until :meth:`~prompt_toolkit.application.Application.exit` has been + called. Return the value that was passed to + :meth:`~prompt_toolkit.application.Application.exit`. + + This is the main entry point for a prompt_toolkit + :class:`~prompt_toolkit.application.Application` and usually the only + place where the event loop is actually running. + + :param pre_run: Optional callable, which is called right after the + "reset" of the application. + :param set_exception_handler: When set, in case of an exception, go out + of the alternate screen and hide the application, display the + exception, and wait for the user to press ENTER. + :param handle_sigint: Handle SIGINT signal if possible. This will call + the `` key binding when a SIGINT is received. (This only + works in the main thread.) + :param slow_callback_duration: Display warnings if code scheduled in + the asyncio event loop takes more time than this. The asyncio + default of `0.1` is sometimes not sufficient on a slow system, + because exceptionally, the drawing of the app, which happens in the + event loop, can take a bit longer from time to time. + """ + assert not self._is_running, "Application is already running." + + if not in_main_thread() or is_windows(): + # Handling signals in other threads is not supported. + # Also on Windows, `add_signal_handler(signal.SIGINT, ...)` raises + # `NotImplementedError`. + # See: https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1553 + handle_sigint = False + + async def _run_async() -> _AppResult: + "Coroutine." + loop = get_event_loop() + f = loop.create_future() + self.future = f # XXX: make sure to set this before calling '_redraw'. + self.loop = loop + self.context = contextvars.copy_context() + + # Counter for cancelling 'flush' timeouts. Every time when a key is + # pressed, we start a 'flush' timer for flushing our escape key. But + # when any subsequent input is received, a new timer is started and + # the current timer will be ignored. + flush_task: Optional[asyncio.Task[None]] = None + + # Reset. + # (`self.future` needs to be set when `pre_run` is called.) + self.reset() + self._pre_run(pre_run) + + # Feed type ahead input first. + self.key_processor.feed_multiple(get_typeahead(self.input)) + self.key_processor.process_keys() + + def read_from_input() -> None: + nonlocal flush_task + + # Ignore when we aren't running anymore. This callback will + # removed from the loop next time. (It could be that it was + # still in the 'tasks' list of the loop.) + # Except: if we need to process incoming CPRs. + if not self._is_running and not self.renderer.waiting_for_cpr: + return + + # Get keys from the input object. + keys = self.input.read_keys() + + # Feed to key processor. + self.key_processor.feed_multiple(keys) + self.key_processor.process_keys() + + # Quit when the input stream was closed. + if self.input.closed: + if not f.done(): + f.set_exception(EOFError) + else: + # Automatically flush keys. + if flush_task: + flush_task.cancel() + flush_task = self.create_background_task(auto_flush_input()) + + async def auto_flush_input() -> None: + # Flush input after timeout. + # (Used for flushing the enter key.) + # This sleep can be cancelled, in that case we won't flush yet. + await sleep(self.ttimeoutlen) + flush_input() + + def flush_input() -> None: + if not self.is_done: + # Get keys, and feed to key processor. + keys = self.input.flush_keys() + self.key_processor.feed_multiple(keys) + self.key_processor.process_keys() + + if self.input.closed: + f.set_exception(EOFError) + + # Enter raw mode, attach input and attach WINCH event handler. + with self.input.raw_mode(), self.input.attach( + read_from_input + ), attach_winch_signal_handler(self._on_resize): + + # Draw UI. + self._request_absolute_cursor_position() + self._redraw() + self._start_auto_refresh_task() + + self.create_background_task(self._poll_output_size()) + + # Wait for UI to finish. + try: + result = await f + finally: + # In any case, when the application finishes. + # (Successful, or because of an error.) + try: + self._redraw(render_as_done=True) + finally: + # _redraw has a good chance to fail if it calls widgets + # with bad code. Make sure to reset the renderer + # anyway. + self.renderer.reset() + + # Unset `is_running`, this ensures that possibly + # scheduled draws won't paint during the following + # yield. + self._is_running = False + + # Detach event handlers for invalidate events. + # (Important when a UIControl is embedded in multiple + # applications, like ptterm in pymux. An invalidate + # should not trigger a repaint in terminated + # applications.) + for ev in self._invalidate_events: + ev -= self._invalidate_handler + self._invalidate_events = [] + + # Wait for CPR responses. + if self.output.responds_to_cpr: + await self.renderer.wait_for_cpr_responses() + + # Wait for the run-in-terminals to terminate. + previous_run_in_terminal_f = self._running_in_terminal_f + + if previous_run_in_terminal_f: + await previous_run_in_terminal_f + + # Store unprocessed input as typeahead for next time. + store_typeahead(self.input, self.key_processor.empty_queue()) + + return cast(_AppResult, result) + + async def _run_async2() -> _AppResult: + self._is_running = True + try: + # Make sure to set `_invalidated` to `False` to begin with, + # otherwise we're not going to paint anything. This can happen if + # this application had run before on a different event loop, and a + # paint was scheduled using `call_soon_threadsafe` with + # `max_postpone_time`. + self._invalidated = False + + loop = get_event_loop() + + if handle_sigint: + loop.add_signal_handler( + signal.SIGINT, + lambda *_: loop.call_soon_threadsafe( + self.key_processor.send_sigint + ), + ) + + if set_exception_handler: + previous_exc_handler = loop.get_exception_handler() + loop.set_exception_handler(self._handle_exception) + + # Set slow_callback_duration. + original_slow_callback_duration = loop.slow_callback_duration + loop.slow_callback_duration = slow_callback_duration + + try: + with set_app(self), self._enable_breakpointhook(): + try: + result = await _run_async() + finally: + # Wait for the background tasks to be done. This needs to + # go in the finally! If `_run_async` raises + # `KeyboardInterrupt`, we still want to wait for the + # background tasks. + await self.cancel_and_wait_for_background_tasks() + + # Also remove the Future again. (This brings the + # application back to its initial state, where it also + # doesn't have a Future.) + self.future = None + + return result + finally: + if set_exception_handler: + loop.set_exception_handler(previous_exc_handler) + + if handle_sigint: + loop.remove_signal_handler(signal.SIGINT) + + # Reset slow_callback_duration. + loop.slow_callback_duration = original_slow_callback_duration + + finally: + # Set the `_is_running` flag to `False`. Normally this happened + # already in the finally block in `run_async` above, but in + # case of exceptions, that's not always the case. + self._is_running = False + + return await _run_async2() + + def run( + self, + pre_run: Optional[Callable[[], None]] = None, + set_exception_handler: bool = True, + handle_sigint: bool = True, + in_thread: bool = False, + ) -> _AppResult: + """ + A blocking 'run' call that waits until the UI is finished. + + This will start the current asyncio event loop. If no loop is set for + the current thread, then it will create a new loop. If a new loop was + created, this won't close the new loop (if `in_thread=False`). + + :param pre_run: Optional callable, which is called right after the + "reset" of the application. + :param set_exception_handler: When set, in case of an exception, go out + of the alternate screen and hide the application, display the + exception, and wait for the user to press ENTER. + :param in_thread: When true, run the application in a background + thread, and block the current thread until the application + terminates. This is useful if we need to be sure the application + won't use the current event loop (asyncio does not support nested + event loops). A new event loop will be created in this background + thread, and that loop will also be closed when the background + thread terminates. When this is used, it's especially important to + make sure that all asyncio background tasks are managed through + `get_appp().create_background_task()`, so that unfinished tasks are + properly cancelled before the event loop is closed. This is used + for instance in ptpython. + :param handle_sigint: Handle SIGINT signal. Call the key binding for + `Keys.SIGINT`. (This only works in the main thread.) + """ + if in_thread: + result: _AppResult + exception: Optional[BaseException] = None + + def run_in_thread() -> None: + nonlocal result, exception + try: + result = self.run( + pre_run=pre_run, + set_exception_handler=set_exception_handler, + # Signal handling only works in the main thread. + handle_sigint=False, + ) + except BaseException as e: + exception = e + finally: + # Make sure to close the event loop in this thread. Running + # the application creates a new loop (because we're in + # another thread), but it doesn't get closed automatically + # (also not by the garbage collector). + loop = get_event_loop() + loop.run_until_complete(loop.shutdown_asyncgens()) + loop.close() + + thread = threading.Thread(target=run_in_thread) + thread.start() + thread.join() + + if exception is not None: + raise exception + return result + + # We don't create a new event loop by default, because we want to be + # sure that when this is called multiple times, each call of `run()` + # goes through the same event loop. This way, users can schedule + # background-tasks that keep running across multiple prompts. + try: + loop = get_event_loop() + except RuntimeError: + # Possibly we are not running in the main thread, where no event + # loop is set by default. Or somebody called `asyncio.run()` + # before, which closes the existing event loop. We can create a new + # loop. + loop = new_event_loop() + set_event_loop(loop) + + return loop.run_until_complete( + self.run_async(pre_run=pre_run, set_exception_handler=set_exception_handler) + ) + + def _handle_exception( + self, loop: AbstractEventLoop, context: Dict[str, Any] + ) -> None: + """ + Handler for event loop exceptions. + This will print the exception, using run_in_terminal. + """ + # For Python 2: we have to get traceback at this point, because + # we're still in the 'except:' block of the event loop where the + # traceback is still available. Moving this code in the + # 'print_exception' coroutine will loose the exception. + tb = get_traceback_from_context(context) + formatted_tb = "".join(format_tb(tb)) + + async def in_term() -> None: + async with in_terminal(): + # Print output. Similar to 'loop.default_exception_handler', + # but don't use logger. (This works better on Python 2.) + print("\nUnhandled exception in event loop:") + print(formatted_tb) + print("Exception {}".format(context.get("exception"))) + + await _do_wait_for_enter("Press ENTER to continue...") + + ensure_future(in_term()) + + @contextmanager + def _enable_breakpointhook(self) -> Generator[None, None, None]: + """ + Install our custom breakpointhook for the duration of this context + manager. (We will only install the hook if no other custom hook was + set.) + """ + if sys.version_info >= (3, 7) and sys.breakpointhook == sys.__breakpointhook__: + sys.breakpointhook = self._breakpointhook + + try: + yield + finally: + sys.breakpointhook = sys.__breakpointhook__ + else: + yield + + def _breakpointhook(self, *a: object, **kw: object) -> None: + """ + Breakpointhook which uses PDB, but ensures that the application is + hidden and input echoing is restored during each debugger dispatch. + """ + app = self + # Inline import on purpose. We don't want to import pdb, if not needed. + import pdb + from types import FrameType + + TraceDispatch = Callable[[FrameType, str, Any], Any] + + class CustomPdb(pdb.Pdb): + def trace_dispatch( + self, frame: FrameType, event: str, arg: Any + ) -> TraceDispatch: + # Hide application. + app.renderer.erase() + + # Detach input and dispatch to debugger. + with app.input.detach(): + with app.input.cooked_mode(): + return super().trace_dispatch(frame, event, arg) + + # Note: we don't render the application again here, because + # there's a good chance that there's a breakpoint on the next + # line. This paint/erase cycle would move the PDB prompt back + # to the middle of the screen. + + frame = sys._getframe().f_back + CustomPdb(stdout=sys.__stdout__).set_trace(frame) + + def create_background_task( + self, coroutine: Awaitable[None] + ) -> "asyncio.Task[None]": + """ + Start a background task (coroutine) for the running application. When + the `Application` terminates, unfinished background tasks will be + cancelled. + + If asyncio had nurseries like Trio, we would create a nursery in + `Application.run_async`, and run the given coroutine in that nursery. + + Not threadsafe. + """ + task = get_event_loop().create_task(coroutine) + self.background_tasks.append(task) + return task + + async def cancel_and_wait_for_background_tasks(self) -> None: + """ + Cancel all background tasks, and wait for the cancellation to be done. + If any of the background tasks raised an exception, this will also + propagate the exception. + + (If we had nurseries like Trio, this would be the `__aexit__` of a + nursery.) + """ + for task in self.background_tasks: + task.cancel() + + for task in self.background_tasks: + try: + await task + except CancelledError: + pass + + async def _poll_output_size(self) -> None: + """ + Coroutine for polling the terminal dimensions. + + Useful for situations where `attach_winch_signal_handler` is not sufficient: + - If we are not running in the main thread. + - On Windows. + """ + size: Optional[Size] = None + interval = self.terminal_size_polling_interval + + if interval is None: + return + + while True: + await asyncio.sleep(interval) + new_size = self.output.get_size() + + if size is not None and new_size != size: + self._on_resize() + size = new_size + + def cpr_not_supported_callback(self) -> None: + """ + Called when we don't receive the cursor position response in time. + """ + if not self.output.responds_to_cpr: + return # We know about this already. + + def in_terminal() -> None: + self.output.write( + "WARNING: your terminal doesn't support cursor position requests (CPR).\r\n" + ) + self.output.flush() + + run_in_terminal(in_terminal) + + @overload + def exit(self) -> None: + "Exit without arguments." + + @overload + def exit(self, *, result: _AppResult, style: str = "") -> None: + "Exit with `_AppResult`." + + @overload + def exit( + self, *, exception: Union[BaseException, Type[BaseException]], style: str = "" + ) -> None: + "Exit with exception." + + def exit( + self, + result: Optional[_AppResult] = None, + exception: Optional[Union[BaseException, Type[BaseException]]] = None, + style: str = "", + ) -> None: + """ + Exit application. + + .. note:: + + If `Application.exit` is called before `Application.run()` is + called, then the `Application` won't exit (because the + `Application.future` doesn't correspond to the current run). Use a + `pre_run` hook and an event to synchronize the closing if there's a + chance this can happen. + + :param result: Set this result for the application. + :param exception: Set this exception as the result for an application. For + a prompt, this is often `EOFError` or `KeyboardInterrupt`. + :param style: Apply this style on the whole content when quitting, + often this is 'class:exiting' for a prompt. (Used when + `erase_when_done` is not set.) + """ + assert result is None or exception is None + + if self.future is None: + raise Exception("Application is not running. Application.exit() failed.") + + if self.future.done(): + raise Exception("Return value already set. Application.exit() failed.") + + self.exit_style = style + + if exception is not None: + self.future.set_exception(exception) + else: + self.future.set_result(cast(_AppResult, result)) + + def _request_absolute_cursor_position(self) -> None: + """ + Send CPR request. + """ + # Note: only do this if the input queue is not empty, and a return + # value has not been set. Otherwise, we won't be able to read the + # response anyway. + if not self.key_processor.input_queue and not self.is_done: + self.renderer.request_absolute_cursor_position() + + async def run_system_command( + self, + command: str, + wait_for_enter: bool = True, + display_before_text: AnyFormattedText = "", + wait_text: str = "Press ENTER to continue...", + ) -> None: + """ + Run system command (While hiding the prompt. When finished, all the + output will scroll above the prompt.) + + :param command: Shell command to be executed. + :param wait_for_enter: FWait for the user to press enter, when the + command is finished. + :param display_before_text: If given, text to be displayed before the + command executes. + :return: A `Future` object. + """ + async with in_terminal(): + # Try to use the same input/output file descriptors as the one, + # used to run this application. + try: + input_fd = self.input.fileno() + except AttributeError: + input_fd = sys.stdin.fileno() + try: + output_fd = self.output.fileno() + except AttributeError: + output_fd = sys.stdout.fileno() + + # Run sub process. + def run_command() -> None: + self.print_text(display_before_text) + p = Popen(command, shell=True, stdin=input_fd, stdout=output_fd) + p.wait() + + await run_in_executor_with_context(run_command) + + # Wait for the user to press enter. + if wait_for_enter: + await _do_wait_for_enter(wait_text) + + def suspend_to_background(self, suspend_group: bool = True) -> None: + """ + (Not thread safe -- to be called from inside the key bindings.) + Suspend process. + + :param suspend_group: When true, suspend the whole process group. + (This is the default, and probably what you want.) + """ + # Only suspend when the operating system supports it. + # (Not on Windows.) + if _SIGTSTP is not None: + + def run() -> None: + signal = cast(int, _SIGTSTP) + # Send `SIGTSTP` to own process. + # This will cause it to suspend. + + # Usually we want the whole process group to be suspended. This + # handles the case when input is piped from another process. + if suspend_group: + os.kill(0, signal) + else: + os.kill(os.getpid(), signal) + + run_in_terminal(run) + + def print_text( + self, text: AnyFormattedText, style: Optional[BaseStyle] = None + ) -> None: + """ + Print a list of (style_str, text) tuples to the output. + (When the UI is running, this method has to be called through + `run_in_terminal`, otherwise it will destroy the UI.) + + :param text: List of ``(style_str, text)`` tuples. + :param style: Style class to use. Defaults to the active style in the CLI. + """ + print_formatted_text( + output=self.output, + formatted_text=text, + style=style or self._merged_style, + color_depth=self.color_depth, + style_transformation=self.style_transformation, + ) + + @property + def is_running(self) -> bool: + "`True` when the application is currently active/running." + return self._is_running + + @property + def is_done(self) -> bool: + if self.future: + return self.future.done() + return False + + def get_used_style_strings(self) -> List[str]: + """ + Return a list of used style strings. This is helpful for debugging, and + for writing a new `Style`. + """ + attrs_for_style = self.renderer._attrs_for_style + + if attrs_for_style: + return sorted( + re.sub(r"\s+", " ", style_str).strip() + for style_str in attrs_for_style.keys() + ) + + return [] + + +class _CombinedRegistry(KeyBindingsBase): + """ + The `KeyBindings` of key bindings for a `Application`. + This merges the global key bindings with the one of the current user + control. + """ + + def __init__(self, app: Application[_AppResult]) -> None: + self.app = app + self._cache: SimpleCache[ + Tuple[Window, FrozenSet[UIControl]], KeyBindingsBase + ] = SimpleCache() + + @property + def _version(self) -> Hashable: + """Not needed - this object is not going to be wrapped in another + KeyBindings object.""" + raise NotImplementedError + + def bindings(self) -> List[Binding]: + """Not needed - this object is not going to be wrapped in another + KeyBindings object.""" + raise NotImplementedError + + def _create_key_bindings( + self, current_window: Window, other_controls: List[UIControl] + ) -> KeyBindingsBase: + """ + Create a `KeyBindings` object that merges the `KeyBindings` from the + `UIControl` with all the parent controls and the global key bindings. + """ + key_bindings = [] + collected_containers = set() + + # Collect key bindings from currently focused control and all parent + # controls. Don't include key bindings of container parent controls. + container: Container = current_window + while True: + collected_containers.add(container) + kb = container.get_key_bindings() + if kb is not None: + key_bindings.append(kb) + + if container.is_modal(): + break + + parent = self.app.layout.get_parent(container) + if parent is None: + break + else: + container = parent + + # Include global bindings (starting at the top-model container). + for c in walk(container): + if c not in collected_containers: + kb = c.get_key_bindings() + if kb is not None: + key_bindings.append(GlobalOnlyKeyBindings(kb)) + + # Add App key bindings + if self.app.key_bindings: + key_bindings.append(self.app.key_bindings) + + # Add mouse bindings. + key_bindings.append( + ConditionalKeyBindings( + self.app._page_navigation_bindings, + self.app.enable_page_navigation_bindings, + ) + ) + key_bindings.append(self.app._default_bindings) + + # Reverse this list. The current control's key bindings should come + # last. They need priority. + key_bindings = key_bindings[::-1] + + return merge_key_bindings(key_bindings) + + @property + def _key_bindings(self) -> KeyBindingsBase: + current_window = self.app.layout.current_window + other_controls = list(self.app.layout.find_all_controls()) + key = current_window, frozenset(other_controls) + + return self._cache.get( + key, lambda: self._create_key_bindings(current_window, other_controls) + ) + + def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + return self._key_bindings.get_bindings_for_keys(keys) + + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + return self._key_bindings.get_bindings_starting_with_keys(keys) + + +async def _do_wait_for_enter(wait_text: AnyFormattedText) -> None: + """ + Create a sub application to wait for the enter key press. + This has two advantages over using 'input'/'raw_input': + - This will share the same input/output I/O. + - This doesn't block the event loop. + """ + from prompt_toolkit.shortcuts import PromptSession + + key_bindings = KeyBindings() + + @key_bindings.add("enter") + def _ok(event: E) -> None: + event.app.exit() + + @key_bindings.add(Keys.Any) + def _ignore(event: E) -> None: + "Disallow typing." + pass + + session: PromptSession[None] = PromptSession( + message=wait_text, key_bindings=key_bindings + ) + await session.app.run_async() + + +@contextmanager +def attach_winch_signal_handler( + handler: Callable[[], None] +) -> Generator[None, None, None]: + """ + Attach the given callback as a WINCH signal handler within the context + manager. Restore the original signal handler when done. + + The `Application.run` method will register SIGWINCH, so that it will + properly repaint when the terminal window resizes. However, using + `run_in_terminal`, we can temporarily send an application to the + background, and run an other app in between, which will then overwrite the + SIGWINCH. This is why it's important to restore the handler when the app + terminates. + """ + # The tricky part here is that signals are registered in the Unix event + # loop with a wakeup fd, but another application could have registered + # signals using signal.signal directly. For now, the implementation is + # hard-coded for the `asyncio.unix_events._UnixSelectorEventLoop`. + + # No WINCH? Then don't do anything. + sigwinch = getattr(signal, "SIGWINCH", None) + if sigwinch is None or not in_main_thread(): + yield + return + + # Keep track of the previous handler. + # (Only UnixSelectorEventloop has `_signal_handlers`.) + loop = get_event_loop() + previous_winch_handler = getattr(loop, "_signal_handlers", {}).get(sigwinch) + + try: + loop.add_signal_handler(sigwinch, handler) + yield + finally: + # Restore the previous signal handler. + loop.remove_signal_handler(sigwinch) + if previous_winch_handler is not None: + loop.add_signal_handler( + sigwinch, + previous_winch_handler._callback, + *previous_winch_handler._args, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/current.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/current.py new file mode 100644 index 0000000..ae69bfd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/current.py @@ -0,0 +1,197 @@ +import sys +from contextlib import contextmanager +from typing import TYPE_CHECKING, Any, Generator, Optional + +try: + from contextvars import ContextVar +except ImportError: + from prompt_toolkit.eventloop.dummy_contextvars import ContextVar # type: ignore + +if TYPE_CHECKING: + from prompt_toolkit.input.base import Input + from prompt_toolkit.output.base import Output + + from .application import Application + +__all__ = [ + "AppSession", + "get_app_session", + "get_app", + "get_app_or_none", + "set_app", + "create_app_session", + "create_app_session_from_tty", +] + + +class AppSession: + """ + An AppSession is an interactive session, usually connected to one terminal. + Within one such session, interaction with many applications can happen, one + after the other. + + The input/output device is not supposed to change during one session. + + Warning: Always use the `create_app_session` function to create an + instance, so that it gets activated correctly. + + :param input: Use this as a default input for all applications + running in this session, unless an input is passed to the `Application` + explicitely. + :param output: Use this as a default output. + """ + + def __init__( + self, input: Optional["Input"] = None, output: Optional["Output"] = None + ) -> None: + + self._input = input + self._output = output + + # The application will be set dynamically by the `set_app` context + # manager. This is called in the application itself. + self.app: Optional["Application[Any]"] = None + + def __repr__(self) -> str: + return f"AppSession(app={self.app!r})" + + @property + def input(self) -> "Input": + if self._input is None: + from prompt_toolkit.input.defaults import create_input + + self._input = create_input() + return self._input + + @property + def output(self) -> "Output": + if self._output is None: + from prompt_toolkit.output.defaults import create_output + + self._output = create_output() + return self._output + + +_current_app_session: ContextVar["AppSession"] = ContextVar( + "_current_app_session", default=AppSession() +) + + +def get_app_session() -> AppSession: + return _current_app_session.get() + + +def get_app() -> "Application[Any]": + """ + Get the current active (running) Application. + An :class:`.Application` is active during the + :meth:`.Application.run_async` call. + + We assume that there can only be one :class:`.Application` active at the + same time. There is only one terminal window, with only one stdin and + stdout. This makes the code significantly easier than passing around the + :class:`.Application` everywhere. + + If no :class:`.Application` is running, then return by default a + :class:`.DummyApplication`. For practical reasons, we prefer to not raise + an exception. This way, we don't have to check all over the place whether + an actual `Application` was returned. + + (For applications like pymux where we can have more than one `Application`, + we'll use a work-around to handle that.) + """ + session = _current_app_session.get() + if session.app is not None: + return session.app + + from .dummy import DummyApplication + + return DummyApplication() + + +def get_app_or_none() -> Optional["Application[Any]"]: + """ + Get the current active (running) Application, or return `None` if no + application is running. + """ + session = _current_app_session.get() + return session.app + + +@contextmanager +def set_app(app: "Application[Any]") -> Generator[None, None, None]: + """ + Context manager that sets the given :class:`.Application` active in an + `AppSession`. + + This should only be called by the `Application` itself. + The application will automatically be active while its running. If you want + the application to be active in other threads/coroutines, where that's not + the case, use `contextvars.copy_context()`, or use `Application.context` to + run it in the appropriate context. + """ + session = _current_app_session.get() + + previous_app = session.app + session.app = app + try: + yield + finally: + session.app = previous_app + + +@contextmanager +def create_app_session( + input: Optional["Input"] = None, output: Optional["Output"] = None +) -> Generator[AppSession, None, None]: + """ + Create a separate AppSession. + + This is useful if there can be multiple individual `AppSession`s going on. + Like in the case of an Telnet/SSH server. This functionality uses + contextvars and requires at least Python 3.7. + """ + if sys.version_info <= (3, 6): + raise RuntimeError("Application sessions require Python 3.7.") + + # If no input/output is specified, fall back to the current input/output, + # whatever that is. + if input is None: + input = get_app_session().input + if output is None: + output = get_app_session().output + + # Create new `AppSession` and activate. + session = AppSession(input=input, output=output) + + token = _current_app_session.set(session) + try: + yield session + finally: + _current_app_session.reset(token) + + +@contextmanager +def create_app_session_from_tty() -> Generator[AppSession, None, None]: + """ + Create `AppSession` that always prefers the TTY input/output. + + Even if `sys.stdin` and `sys.stdout` are connected to input/output pipes, + this will still use the terminal for interaction (because `sys.stderr` is + still connected to the terminal). + + Usage:: + + from prompt_toolkit.shortcuts import prompt + + with create_app_session_from_tty(): + prompt('>') + """ + from prompt_toolkit.input.defaults import create_input + from prompt_toolkit.output.defaults import create_output + + input = create_input(always_prefer_tty=True) + output = create_output(always_prefer_tty=True) + + with create_app_session(input=input, output=output) as app_session: + yield app_session diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/dummy.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/dummy.py new file mode 100644 index 0000000..4e5e4aa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/dummy.py @@ -0,0 +1,51 @@ +from typing import Callable, Optional + +from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.input import DummyInput +from prompt_toolkit.output import DummyOutput + +from .application import Application + +__all__ = [ + "DummyApplication", +] + + +class DummyApplication(Application[None]): + """ + When no :class:`.Application` is running, + :func:`.get_app` will run an instance of this :class:`.DummyApplication` instead. + """ + + def __init__(self) -> None: + super().__init__(output=DummyOutput(), input=DummyInput()) + + def run( + self, + pre_run: Optional[Callable[[], None]] = None, + set_exception_handler: bool = True, + handle_sigint: bool = True, + in_thread: bool = False, + ) -> None: + raise NotImplementedError("A DummyApplication is not supposed to run.") + + async def run_async( + self, + pre_run: Optional[Callable[[], None]] = None, + set_exception_handler: bool = True, + handle_sigint: bool = True, + slow_callback_duration: float = 0.5, + ) -> None: + raise NotImplementedError("A DummyApplication is not supposed to run.") + + async def run_system_command( + self, + command: str, + wait_for_enter: bool = True, + display_before_text: AnyFormattedText = "", + wait_text: str = "", + ) -> None: + raise NotImplementedError + + def suspend_to_background(self, suspend_group: bool = True) -> None: + raise NotImplementedError diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/application/run_in_terminal.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/run_in_terminal.py new file mode 100644 index 0000000..d5ef8aa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/application/run_in_terminal.py @@ -0,0 +1,117 @@ +""" +Tools for running functions on the terminal above the current application or prompt. +""" +import sys +from asyncio import Future, ensure_future +from typing import AsyncGenerator, Awaitable, Callable, TypeVar + +from prompt_toolkit.eventloop import run_in_executor_with_context + +from .current import get_app_or_none + +if sys.version_info >= (3, 7): + from contextlib import asynccontextmanager +else: + from prompt_toolkit.eventloop.async_context_manager import asynccontextmanager + + +__all__ = [ + "run_in_terminal", + "in_terminal", +] + +_T = TypeVar("_T") + + +def run_in_terminal( + func: Callable[[], _T], render_cli_done: bool = False, in_executor: bool = False +) -> Awaitable[_T]: + """ + Run function on the terminal above the current application or prompt. + + What this does is first hiding the prompt, then running this callable + (which can safely output to the terminal), and then again rendering the + prompt which causes the output of this function to scroll above the + prompt. + + ``func`` is supposed to be a synchronous function. If you need an + asynchronous version of this function, use the ``in_terminal`` context + manager directly. + + :param func: The callable to execute. + :param render_cli_done: When True, render the interface in the + 'Done' state first, then execute the function. If False, + erase the interface first. + :param in_executor: When True, run in executor. (Use this for long + blocking functions, when you don't want to block the event loop.) + + :returns: A `Future`. + """ + + async def run() -> _T: + async with in_terminal(render_cli_done=render_cli_done): + if in_executor: + return await run_in_executor_with_context(func) + else: + return func() + + return ensure_future(run()) + + +@asynccontextmanager +async def in_terminal(render_cli_done: bool = False) -> AsyncGenerator[None, None]: + """ + Asynchronous context manager that suspends the current application and runs + the body in the terminal. + + .. code:: + + async def f(): + async with in_terminal(): + call_some_function() + await call_some_async_function() + """ + app = get_app_or_none() + if app is None or not app._is_running: + yield + return + + # When a previous `run_in_terminal` call was in progress. Wait for that + # to finish, before starting this one. Chain to previous call. + previous_run_in_terminal_f = app._running_in_terminal_f + new_run_in_terminal_f: Future[None] = Future() + app._running_in_terminal_f = new_run_in_terminal_f + + # Wait for the previous `run_in_terminal` to finish. + if previous_run_in_terminal_f is not None: + await previous_run_in_terminal_f + + # Wait for all CPRs to arrive. We don't want to detach the input until + # all cursor position responses have been arrived. Otherwise, the tty + # will echo its input and can show stuff like ^[[39;1R. + if app.output.responds_to_cpr: + await app.renderer.wait_for_cpr_responses() + + # Draw interface in 'done' state, or erase. + if render_cli_done: + app._redraw(render_as_done=True) + else: + app.renderer.erase() + + # Disable rendering. + app._running_in_terminal = True + + # Detach input. + try: + with app.input.detach(): + with app.input.cooked_mode(): + yield + finally: + # Redraw interface again. + try: + app._running_in_terminal = False + app.renderer.reset() + app._request_absolute_cursor_position() + app._redraw() + finally: + new_run_in_terminal_f.set_result(None) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/auto_suggest.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/auto_suggest.py new file mode 100644 index 0000000..7099b8e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/auto_suggest.py @@ -0,0 +1,187 @@ +""" +`Fish-style `_ like auto-suggestion. + +While a user types input in a certain buffer, suggestions are generated +(asynchronously.) Usually, they are displayed after the input. When the cursor +presses the right arrow and the cursor is at the end of the input, the +suggestion will be inserted. + +If you want the auto suggestions to be asynchronous (in a background thread), +because they take too much time, and could potentially block the event loop, +then wrap the :class:`.AutoSuggest` instance into a +:class:`.ThreadedAutoSuggest`. +""" +from abc import ABCMeta, abstractmethod +from typing import TYPE_CHECKING, Callable, Optional, Union + +from prompt_toolkit.eventloop import run_in_executor_with_context + +from .document import Document +from .filters import Filter, to_filter + +if TYPE_CHECKING: + from .buffer import Buffer + +__all__ = [ + "Suggestion", + "AutoSuggest", + "ThreadedAutoSuggest", + "DummyAutoSuggest", + "AutoSuggestFromHistory", + "ConditionalAutoSuggest", + "DynamicAutoSuggest", +] + + +class Suggestion: + """ + Suggestion returned by an auto-suggest algorithm. + + :param text: The suggestion text. + """ + + def __init__(self, text: str) -> None: + self.text = text + + def __repr__(self) -> str: + return "Suggestion(%s)" % self.text + + +class AutoSuggest(metaclass=ABCMeta): + """ + Base class for auto suggestion implementations. + """ + + @abstractmethod + def get_suggestion( + self, buffer: "Buffer", document: Document + ) -> Optional[Suggestion]: + """ + Return `None` or a :class:`.Suggestion` instance. + + We receive both :class:`~prompt_toolkit.buffer.Buffer` and + :class:`~prompt_toolkit.document.Document`. The reason is that auto + suggestions are retrieved asynchronously. (Like completions.) The + buffer text could be changed in the meantime, but ``document`` contains + the buffer document like it was at the start of the auto suggestion + call. So, from here, don't access ``buffer.text``, but use + ``document.text`` instead. + + :param buffer: The :class:`~prompt_toolkit.buffer.Buffer` instance. + :param document: The :class:`~prompt_toolkit.document.Document` instance. + """ + + async def get_suggestion_async( + self, buff: "Buffer", document: Document + ) -> Optional[Suggestion]: + """ + Return a :class:`.Future` which is set when the suggestions are ready. + This function can be overloaded in order to provide an asynchronous + implementation. + """ + return self.get_suggestion(buff, document) + + +class ThreadedAutoSuggest(AutoSuggest): + """ + Wrapper that runs auto suggestions in a thread. + (Use this to prevent the user interface from becoming unresponsive if the + generation of suggestions takes too much time.) + """ + + def __init__(self, auto_suggest: AutoSuggest) -> None: + self.auto_suggest = auto_suggest + + def get_suggestion( + self, buff: "Buffer", document: Document + ) -> Optional[Suggestion]: + return self.auto_suggest.get_suggestion(buff, document) + + async def get_suggestion_async( + self, buff: "Buffer", document: Document + ) -> Optional[Suggestion]: + """ + Run the `get_suggestion` function in a thread. + """ + + def run_get_suggestion_thread() -> Optional[Suggestion]: + return self.get_suggestion(buff, document) + + return await run_in_executor_with_context(run_get_suggestion_thread) + + +class DummyAutoSuggest(AutoSuggest): + """ + AutoSuggest class that doesn't return any suggestion. + """ + + def get_suggestion( + self, buffer: "Buffer", document: Document + ) -> Optional[Suggestion]: + return None # No suggestion + + +class AutoSuggestFromHistory(AutoSuggest): + """ + Give suggestions based on the lines in the history. + """ + + def get_suggestion( + self, buffer: "Buffer", document: Document + ) -> Optional[Suggestion]: + history = buffer.history + + # Consider only the last line for the suggestion. + text = document.text.rsplit("\n", 1)[-1] + + # Only create a suggestion when this is not an empty line. + if text.strip(): + # Find first matching line in history. + for string in reversed(list(history.get_strings())): + for line in reversed(string.splitlines()): + if line.startswith(text): + return Suggestion(line[len(text) :]) + + return None + + +class ConditionalAutoSuggest(AutoSuggest): + """ + Auto suggest that can be turned on and of according to a certain condition. + """ + + def __init__(self, auto_suggest: AutoSuggest, filter: Union[bool, Filter]) -> None: + + self.auto_suggest = auto_suggest + self.filter = to_filter(filter) + + def get_suggestion( + self, buffer: "Buffer", document: Document + ) -> Optional[Suggestion]: + if self.filter(): + return self.auto_suggest.get_suggestion(buffer, document) + + return None + + +class DynamicAutoSuggest(AutoSuggest): + """ + Validator class that can dynamically returns any Validator. + + :param get_validator: Callable that returns a :class:`.Validator` instance. + """ + + def __init__(self, get_auto_suggest: Callable[[], Optional[AutoSuggest]]) -> None: + self.get_auto_suggest = get_auto_suggest + + def get_suggestion( + self, buff: "Buffer", document: Document + ) -> Optional[Suggestion]: + auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() + return auto_suggest.get_suggestion(buff, document) + + async def get_suggestion_async( + self, buff: "Buffer", document: Document + ) -> Optional[Suggestion]: + auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() + return await auto_suggest.get_suggestion_async(buff, document) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/buffer.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/buffer.py new file mode 100644 index 0000000..ec2d3ba --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/buffer.py @@ -0,0 +1,2014 @@ +""" +Data structures for the Buffer. +It holds the text, cursor position, history, etc... +""" +import asyncio +import logging +import os +import re +import shlex +import shutil +import subprocess +import tempfile +from collections import deque +from enum import Enum +from functools import wraps +from typing import ( + Any, + Awaitable, + Callable, + Deque, + Iterable, + List, + Optional, + Set, + Tuple, + TypeVar, + Union, + cast, +) + +from .application.current import get_app +from .application.run_in_terminal import run_in_terminal +from .auto_suggest import AutoSuggest, Suggestion +from .cache import FastDictCache +from .clipboard import ClipboardData +from .completion import ( + CompleteEvent, + Completer, + Completion, + DummyCompleter, + get_common_complete_suffix, +) +from .document import Document +from .filters import FilterOrBool, to_filter +from .history import History, InMemoryHistory +from .search import SearchDirection, SearchState +from .selection import PasteMode, SelectionState, SelectionType +from .utils import Event, to_str +from .validation import ValidationError, Validator + +__all__ = [ + "EditReadOnlyBuffer", + "Buffer", + "CompletionState", + "indent", + "unindent", + "reshape_text", +] + +logger = logging.getLogger(__name__) + + +class EditReadOnlyBuffer(Exception): + "Attempt editing of read-only :class:`.Buffer`." + + +class ValidationState(Enum): + "The validation state of a buffer. This is set after the validation." + VALID = "VALID" + INVALID = "INVALID" + UNKNOWN = "UNKNOWN" + + +class CompletionState: + """ + Immutable class that contains a completion state. + """ + + def __init__( + self, + original_document: "Document", + completions: Optional[List["Completion"]] = None, + complete_index: Optional[int] = None, + ) -> None: + + #: Document as it was when the completion started. + self.original_document = original_document + + #: List of all the current Completion instances which are possible at + #: this point. + self.completions = completions or [] + + #: Position in the `completions` array. + #: This can be `None` to indicate "no completion", the original text. + self.complete_index = complete_index # Position in the `_completions` array. + + def __repr__(self) -> str: + return "{}({!r}, <{!r}> completions, index={!r})".format( + self.__class__.__name__, + self.original_document, + len(self.completions), + self.complete_index, + ) + + def go_to_index(self, index: Optional[int]) -> None: + """ + Create a new :class:`.CompletionState` object with the new index. + + When `index` is `None` deselect the completion. + """ + if self.completions: + assert index is None or 0 <= index < len(self.completions) + self.complete_index = index + + def new_text_and_position(self) -> Tuple[str, int]: + """ + Return (new_text, new_cursor_position) for this completion. + """ + if self.complete_index is None: + return self.original_document.text, self.original_document.cursor_position + else: + original_text_before_cursor = self.original_document.text_before_cursor + original_text_after_cursor = self.original_document.text_after_cursor + + c = self.completions[self.complete_index] + if c.start_position == 0: + before = original_text_before_cursor + else: + before = original_text_before_cursor[: c.start_position] + + new_text = before + c.text + original_text_after_cursor + new_cursor_position = len(before) + len(c.text) + return new_text, new_cursor_position + + @property + def current_completion(self) -> Optional["Completion"]: + """ + Return the current completion, or return `None` when no completion is + selected. + """ + if self.complete_index is not None: + return self.completions[self.complete_index] + return None + + +_QUOTED_WORDS_RE = re.compile(r"""(\s+|".*?"|'.*?')""") + + +class YankNthArgState: + """ + For yank-last-arg/yank-nth-arg: Keep track of where we are in the history. + """ + + def __init__( + self, history_position: int = 0, n: int = -1, previous_inserted_word: str = "" + ) -> None: + + self.history_position = history_position + self.previous_inserted_word = previous_inserted_word + self.n = n + + def __repr__(self) -> str: + return "{}(history_position={!r}, n={!r}, previous_inserted_word={!r})".format( + self.__class__.__name__, + self.history_position, + self.n, + self.previous_inserted_word, + ) + + +BufferEventHandler = Callable[["Buffer"], None] +BufferAcceptHandler = Callable[["Buffer"], bool] + + +class Buffer: + """ + The core data structure that holds the text and cursor position of the + current input line and implements all text manipulations on top of it. It + also implements the history, undo stack and the completion state. + + :param completer: :class:`~prompt_toolkit.completion.Completer` instance. + :param history: :class:`~prompt_toolkit.history.History` instance. + :param tempfile_suffix: The tempfile suffix (extension) to be used for the + "open in editor" function. For a Python REPL, this would be ".py", so + that the editor knows the syntax highlighting to use. This can also be + a callable that returns a string. + :param tempfile: For more advanced tempfile situations where you need + control over the subdirectories and filename. For a Git Commit Message, + this would be ".git/COMMIT_EDITMSG", so that the editor knows the syntax + highlighting to use. This can also be a callable that returns a string. + :param name: Name for this buffer. E.g. DEFAULT_BUFFER. This is mostly + useful for key bindings where we sometimes prefer to refer to a buffer + by their name instead of by reference. + :param accept_handler: Called when the buffer input is accepted. (Usually + when the user presses `enter`.) The accept handler receives this + `Buffer` as input and should return True when the buffer text should be + kept instead of calling reset. + + In case of a `PromptSession` for instance, we want to keep the text, + because we will exit the application, and only reset it during the next + run. + + Events: + + :param on_text_changed: When the buffer text changes. (Callable or None.) + :param on_text_insert: When new text is inserted. (Callable or None.) + :param on_cursor_position_changed: When the cursor moves. (Callable or None.) + :param on_completions_changed: When the completions were changed. (Callable or None.) + :param on_suggestion_set: When an auto-suggestion text has been set. (Callable or None.) + + Filters: + + :param complete_while_typing: :class:`~prompt_toolkit.filters.Filter` + or `bool`. Decide whether or not to do asynchronous autocompleting while + typing. + :param validate_while_typing: :class:`~prompt_toolkit.filters.Filter` + or `bool`. Decide whether or not to do asynchronous validation while + typing. + :param enable_history_search: :class:`~prompt_toolkit.filters.Filter` or + `bool` to indicate when up-arrow partial string matching is enabled. It + is advised to not enable this at the same time as + `complete_while_typing`, because when there is an autocompletion found, + the up arrows usually browse through the completions, rather than + through the history. + :param read_only: :class:`~prompt_toolkit.filters.Filter`. When True, + changes will not be allowed. + :param multiline: :class:`~prompt_toolkit.filters.Filter` or `bool`. When + not set, pressing `Enter` will call the `accept_handler`. Otherwise, + pressing `Esc-Enter` is required. + """ + + def __init__( + self, + completer: Optional[Completer] = None, + auto_suggest: Optional[AutoSuggest] = None, + history: Optional[History] = None, + validator: Optional[Validator] = None, + tempfile_suffix: Union[str, Callable[[], str]] = "", + tempfile: Union[str, Callable[[], str]] = "", + name: str = "", + complete_while_typing: FilterOrBool = False, + validate_while_typing: FilterOrBool = False, + enable_history_search: FilterOrBool = False, + document: Optional[Document] = None, + accept_handler: Optional[BufferAcceptHandler] = None, + read_only: FilterOrBool = False, + multiline: FilterOrBool = True, + on_text_changed: Optional[BufferEventHandler] = None, + on_text_insert: Optional[BufferEventHandler] = None, + on_cursor_position_changed: Optional[BufferEventHandler] = None, + on_completions_changed: Optional[BufferEventHandler] = None, + on_suggestion_set: Optional[BufferEventHandler] = None, + ): + + # Accept both filters and booleans as input. + enable_history_search = to_filter(enable_history_search) + complete_while_typing = to_filter(complete_while_typing) + validate_while_typing = to_filter(validate_while_typing) + read_only = to_filter(read_only) + multiline = to_filter(multiline) + + self.completer = completer or DummyCompleter() + self.auto_suggest = auto_suggest + self.validator = validator + self.tempfile_suffix = tempfile_suffix + self.tempfile = tempfile + self.name = name + self.accept_handler = accept_handler + + # Filters. (Usually, used by the key bindings to drive the buffer.) + self.complete_while_typing = complete_while_typing + self.validate_while_typing = validate_while_typing + self.enable_history_search = enable_history_search + self.read_only = read_only + self.multiline = multiline + + # Text width. (For wrapping, used by the Vi 'gq' operator.) + self.text_width = 0 + + #: The command buffer history. + # Note that we shouldn't use a lazy 'or' here. bool(history) could be + # False when empty. + self.history = InMemoryHistory() if history is None else history + + self.__cursor_position = 0 + + # Events + self.on_text_changed: Event["Buffer"] = Event(self, on_text_changed) + self.on_text_insert: Event["Buffer"] = Event(self, on_text_insert) + self.on_cursor_position_changed: Event["Buffer"] = Event( + self, on_cursor_position_changed + ) + self.on_completions_changed: Event["Buffer"] = Event( + self, on_completions_changed + ) + self.on_suggestion_set: Event["Buffer"] = Event(self, on_suggestion_set) + + # Document cache. (Avoid creating new Document instances.) + self._document_cache: FastDictCache[ + Tuple[str, int, Optional[SelectionState]], Document + ] = FastDictCache(Document, size=10) + + # Create completer / auto suggestion / validation coroutines. + self._async_suggester = self._create_auto_suggest_coroutine() + self._async_completer = self._create_completer_coroutine() + self._async_validator = self._create_auto_validate_coroutine() + + # Asyncio task for populating the history. + self._load_history_task: Optional[asyncio.Future[None]] = None + + # Reset other attributes. + self.reset(document=document) + + def __repr__(self) -> str: + if len(self.text) < 15: + text = self.text + else: + text = self.text[:12] + "..." + + return f"" + + def reset( + self, document: Optional[Document] = None, append_to_history: bool = False + ) -> None: + """ + :param append_to_history: Append current input to history first. + """ + if append_to_history: + self.append_to_history() + + document = document or Document() + + self.__cursor_position = document.cursor_position + + # `ValidationError` instance. (Will be set when the input is wrong.) + self.validation_error: Optional[ValidationError] = None + self.validation_state: Optional[ValidationState] = ValidationState.UNKNOWN + + # State of the selection. + self.selection_state: Optional[SelectionState] = None + + # Multiple cursor mode. (When we press 'I' or 'A' in visual-block mode, + # we can insert text on multiple lines at once. This is implemented by + # using multiple cursors.) + self.multiple_cursor_positions: List[int] = [] + + # When doing consecutive up/down movements, prefer to stay at this column. + self.preferred_column: Optional[int] = None + + # State of complete browser + # For interactive completion through Ctrl-N/Ctrl-P. + self.complete_state: Optional[CompletionState] = None + + # State of Emacs yank-nth-arg completion. + self.yank_nth_arg_state: Optional[YankNthArgState] = None # for yank-nth-arg. + + # Remember the document that we had *right before* the last paste + # operation. This is used for rotating through the kill ring. + self.document_before_paste: Optional[Document] = None + + # Current suggestion. + self.suggestion: Optional[Suggestion] = None + + # The history search text. (Used for filtering the history when we + # browse through it.) + self.history_search_text: Optional[str] = None + + # Undo/redo stacks (stack of `(text, cursor_position)`). + self._undo_stack: List[Tuple[str, int]] = [] + self._redo_stack: List[Tuple[str, int]] = [] + + # Cancel history loader. If history loading was still ongoing. + # Cancel the `_load_history_task`, so that next repaint of the + # `BufferControl` we will repopulate it. + if self._load_history_task is not None: + self._load_history_task.cancel() + self._load_history_task = None + + #: The working lines. Similar to history, except that this can be + #: modified. The user can press arrow_up and edit previous entries. + #: Ctrl-C should reset this, and copy the whole history back in here. + #: Enter should process the current command and append to the real + #: history. + self._working_lines: Deque[str] = deque([document.text]) + self.__working_index = 0 + + def load_history_if_not_yet_loaded(self) -> None: + """ + Create task for populating the buffer history (if not yet done). + + Note:: + + This needs to be called from within the event loop of the + application, because history loading is async, and we need to be + sure the right event loop is active. Therefor, we call this method + in the `BufferControl.create_content`. + + There are situations where prompt_toolkit applications are created + in one thread, but will later run in a different thread (Ptpython + is one example. The REPL runs in a separate thread, in order to + prevent interfering with a potential different event loop in the + main thread. The REPL UI however is still created in the main + thread.) We could decide to not support creating prompt_toolkit + objects in one thread and running the application in a different + thread, but history loading is the only place where it matters, and + this solves it. + """ + if self._load_history_task is None: + + async def load_history() -> None: + async for item in self.history.load(): + self._working_lines.appendleft(item) + self.__working_index += 1 + + self._load_history_task = get_app().create_background_task(load_history()) + + def load_history_done(f: "asyncio.Future[None]") -> None: + """ + Handle `load_history` result when either done, cancelled, or + when an exception was raised. + """ + try: + f.result() + except asyncio.CancelledError: + # Ignore cancellation. But handle it, so that we don't get + # this traceback. + pass + except GeneratorExit: + # Probably not needed, but we had situations where + # `GeneratorExit` was raised in `load_history` during + # cancellation. + pass + except BaseException: + # Log error if something goes wrong. (We don't have a + # caller to which we can propagate this exception.) + logger.exception("Loading history failed") + + self._load_history_task.add_done_callback(load_history_done) + + # + + def _set_text(self, value: str) -> bool: + """set text at current working_index. Return whether it changed.""" + working_index = self.working_index + working_lines = self._working_lines + + original_value = working_lines[working_index] + working_lines[working_index] = value + + # Return True when this text has been changed. + if len(value) != len(original_value): + # For Python 2, it seems that when two strings have a different + # length and one is a prefix of the other, Python still scans + # character by character to see whether the strings are different. + # (Some benchmarking showed significant differences for big + # documents. >100,000 of lines.) + return True + elif value != original_value: + return True + return False + + def _set_cursor_position(self, value: int) -> bool: + """Set cursor position. Return whether it changed.""" + original_position = self.__cursor_position + self.__cursor_position = max(0, value) + + return self.__cursor_position != original_position + + @property + def text(self) -> str: + return self._working_lines[self.working_index] + + @text.setter + def text(self, value: str) -> None: + """ + Setting text. (When doing this, make sure that the cursor_position is + valid for this text. text/cursor_position should be consistent at any time, + otherwise set a Document instead.) + """ + # Ensure cursor position remains within the size of the text. + if self.cursor_position > len(value): + self.cursor_position = len(value) + + # Don't allow editing of read-only buffers. + if self.read_only(): + raise EditReadOnlyBuffer() + + changed = self._set_text(value) + + if changed: + self._text_changed() + + # Reset history search text. + # (Note that this doesn't need to happen when working_index + # changes, which is when we traverse the history. That's why we + # don't do this in `self._text_changed`.) + self.history_search_text = None + + @property + def cursor_position(self) -> int: + return self.__cursor_position + + @cursor_position.setter + def cursor_position(self, value: int) -> None: + """ + Setting cursor position. + """ + assert isinstance(value, int) + + # Ensure cursor position is within the size of the text. + if value > len(self.text): + value = len(self.text) + if value < 0: + value = 0 + + changed = self._set_cursor_position(value) + + if changed: + self._cursor_position_changed() + + @property + def working_index(self) -> int: + return self.__working_index + + @working_index.setter + def working_index(self, value: int) -> None: + if self.__working_index != value: + self.__working_index = value + # Make sure to reset the cursor position, otherwise we end up in + # situations where the cursor position is out of the bounds of the + # text. + self.cursor_position = 0 + self._text_changed() + + def _text_changed(self) -> None: + # Remove any validation errors and complete state. + self.validation_error = None + self.validation_state = ValidationState.UNKNOWN + self.complete_state = None + self.yank_nth_arg_state = None + self.document_before_paste = None + self.selection_state = None + self.suggestion = None + self.preferred_column = None + + # fire 'on_text_changed' event. + self.on_text_changed.fire() + + # Input validation. + # (This happens on all change events, unlike auto completion, also when + # deleting text.) + if self.validator and self.validate_while_typing(): + get_app().create_background_task(self._async_validator()) + + def _cursor_position_changed(self) -> None: + # Remove any complete state. + # (Input validation should only be undone when the cursor position + # changes.) + self.complete_state = None + self.yank_nth_arg_state = None + self.document_before_paste = None + + # Unset preferred_column. (Will be set after the cursor movement, if + # required.) + self.preferred_column = None + + # Note that the cursor position can change if we have a selection the + # new position of the cursor determines the end of the selection. + + # fire 'on_cursor_position_changed' event. + self.on_cursor_position_changed.fire() + + @property + def document(self) -> Document: + """ + Return :class:`~prompt_toolkit.document.Document` instance from the + current text, cursor position and selection state. + """ + return self._document_cache[ + self.text, self.cursor_position, self.selection_state + ] + + @document.setter + def document(self, value: Document) -> None: + """ + Set :class:`~prompt_toolkit.document.Document` instance. + + This will set both the text and cursor position at the same time, but + atomically. (Change events will be triggered only after both have been set.) + """ + self.set_document(value) + + def set_document(self, value: Document, bypass_readonly: bool = False) -> None: + """ + Set :class:`~prompt_toolkit.document.Document` instance. Like the + ``document`` property, but accept an ``bypass_readonly`` argument. + + :param bypass_readonly: When True, don't raise an + :class:`.EditReadOnlyBuffer` exception, even + when the buffer is read-only. + + .. warning:: + + When this buffer is read-only and `bypass_readonly` was not passed, + the `EditReadOnlyBuffer` exception will be caught by the + `KeyProcessor` and is silently suppressed. This is important to + keep in mind when writing key bindings, because it won't do what + you expect, and there won't be a stack trace. Use try/finally + around this function if you need some cleanup code. + """ + # Don't allow editing of read-only buffers. + if not bypass_readonly and self.read_only(): + raise EditReadOnlyBuffer() + + # Set text and cursor position first. + text_changed = self._set_text(value.text) + cursor_position_changed = self._set_cursor_position(value.cursor_position) + + # Now handle change events. (We do this when text/cursor position is + # both set and consistent.) + if text_changed: + self._text_changed() + self.history_search_text = None + + if cursor_position_changed: + self._cursor_position_changed() + + @property + def is_returnable(self) -> bool: + """ + True when there is something handling accept. + """ + return bool(self.accept_handler) + + # End of + + def save_to_undo_stack(self, clear_redo_stack: bool = True) -> None: + """ + Safe current state (input text and cursor position), so that we can + restore it by calling undo. + """ + # Safe if the text is different from the text at the top of the stack + # is different. If the text is the same, just update the cursor position. + if self._undo_stack and self._undo_stack[-1][0] == self.text: + self._undo_stack[-1] = (self._undo_stack[-1][0], self.cursor_position) + else: + self._undo_stack.append((self.text, self.cursor_position)) + + # Saving anything to the undo stack, clears the redo stack. + if clear_redo_stack: + self._redo_stack = [] + + def transform_lines( + self, + line_index_iterator: Iterable[int], + transform_callback: Callable[[str], str], + ) -> str: + """ + Transforms the text on a range of lines. + When the iterator yield an index not in the range of lines that the + document contains, it skips them silently. + + To uppercase some lines:: + + new_text = transform_lines(range(5,10), lambda text: text.upper()) + + :param line_index_iterator: Iterator of line numbers (int) + :param transform_callback: callable that takes the original text of a + line, and return the new text for this line. + + :returns: The new text. + """ + # Split lines + lines = self.text.split("\n") + + # Apply transformation + for index in line_index_iterator: + try: + lines[index] = transform_callback(lines[index]) + except IndexError: + pass + + return "\n".join(lines) + + def transform_current_line(self, transform_callback: Callable[[str], str]) -> None: + """ + Apply the given transformation function to the current line. + + :param transform_callback: callable that takes a string and return a new string. + """ + document = self.document + a = document.cursor_position + document.get_start_of_line_position() + b = document.cursor_position + document.get_end_of_line_position() + self.text = ( + document.text[:a] + + transform_callback(document.text[a:b]) + + document.text[b:] + ) + + def transform_region( + self, from_: int, to: int, transform_callback: Callable[[str], str] + ) -> None: + """ + Transform a part of the input string. + + :param from_: (int) start position. + :param to: (int) end position. + :param transform_callback: Callable which accepts a string and returns + the transformed string. + """ + assert from_ < to + + self.text = "".join( + [ + self.text[:from_] + + transform_callback(self.text[from_:to]) + + self.text[to:] + ] + ) + + def cursor_left(self, count: int = 1) -> None: + self.cursor_position += self.document.get_cursor_left_position(count=count) + + def cursor_right(self, count: int = 1) -> None: + self.cursor_position += self.document.get_cursor_right_position(count=count) + + def cursor_up(self, count: int = 1) -> None: + """(for multiline edit). Move cursor to the previous line.""" + original_column = self.preferred_column or self.document.cursor_position_col + self.cursor_position += self.document.get_cursor_up_position( + count=count, preferred_column=original_column + ) + + # Remember the original column for the next up/down movement. + self.preferred_column = original_column + + def cursor_down(self, count: int = 1) -> None: + """(for multiline edit). Move cursor to the next line.""" + original_column = self.preferred_column or self.document.cursor_position_col + self.cursor_position += self.document.get_cursor_down_position( + count=count, preferred_column=original_column + ) + + # Remember the original column for the next up/down movement. + self.preferred_column = original_column + + def auto_up( + self, count: int = 1, go_to_start_of_line_if_history_changes: bool = False + ) -> None: + """ + If we're not on the first line (of a multiline input) go a line up, + otherwise go back in history. (If nothing is selected.) + """ + if self.complete_state: + self.complete_previous(count=count) + elif self.document.cursor_position_row > 0: + self.cursor_up(count=count) + elif not self.selection_state: + self.history_backward(count=count) + + # Go to the start of the line? + if go_to_start_of_line_if_history_changes: + self.cursor_position += self.document.get_start_of_line_position() + + def auto_down( + self, count: int = 1, go_to_start_of_line_if_history_changes: bool = False + ) -> None: + """ + If we're not on the last line (of a multiline input) go a line down, + otherwise go forward in history. (If nothing is selected.) + """ + if self.complete_state: + self.complete_next(count=count) + elif self.document.cursor_position_row < self.document.line_count - 1: + self.cursor_down(count=count) + elif not self.selection_state: + self.history_forward(count=count) + + # Go to the start of the line? + if go_to_start_of_line_if_history_changes: + self.cursor_position += self.document.get_start_of_line_position() + + def delete_before_cursor(self, count: int = 1) -> str: + """ + Delete specified number of characters before cursor and return the + deleted text. + """ + assert count >= 0 + deleted = "" + + if self.cursor_position > 0: + deleted = self.text[self.cursor_position - count : self.cursor_position] + + new_text = ( + self.text[: self.cursor_position - count] + + self.text[self.cursor_position :] + ) + new_cursor_position = self.cursor_position - len(deleted) + + # Set new Document atomically. + self.document = Document(new_text, new_cursor_position) + + return deleted + + def delete(self, count: int = 1) -> str: + """ + Delete specified number of characters and Return the deleted text. + """ + if self.cursor_position < len(self.text): + deleted = self.document.text_after_cursor[:count] + self.text = ( + self.text[: self.cursor_position] + + self.text[self.cursor_position + len(deleted) :] + ) + return deleted + else: + return "" + + def join_next_line(self, separator: str = " ") -> None: + """ + Join the next line to the current one by deleting the line ending after + the current line. + """ + if not self.document.on_last_line: + self.cursor_position += self.document.get_end_of_line_position() + self.delete() + + # Remove spaces. + self.text = ( + self.document.text_before_cursor + + separator + + self.document.text_after_cursor.lstrip(" ") + ) + + def join_selected_lines(self, separator: str = " ") -> None: + """ + Join the selected lines. + """ + assert self.selection_state + + # Get lines. + from_, to = sorted( + [self.cursor_position, self.selection_state.original_cursor_position] + ) + + before = self.text[:from_] + lines = self.text[from_:to].splitlines() + after = self.text[to:] + + # Replace leading spaces with just one space. + lines = [l.lstrip(" ") + separator for l in lines] + + # Set new document. + self.document = Document( + text=before + "".join(lines) + after, + cursor_position=len(before + "".join(lines[:-1])) - 1, + ) + + def swap_characters_before_cursor(self) -> None: + """ + Swap the last two characters before the cursor. + """ + pos = self.cursor_position + + if pos >= 2: + a = self.text[pos - 2] + b = self.text[pos - 1] + + self.text = self.text[: pos - 2] + b + a + self.text[pos:] + + def go_to_history(self, index: int) -> None: + """ + Go to this item in the history. + """ + if index < len(self._working_lines): + self.working_index = index + self.cursor_position = len(self.text) + + def complete_next(self, count: int = 1, disable_wrap_around: bool = False) -> None: + """ + Browse to the next completions. + (Does nothing if there are no completion.) + """ + index: Optional[int] + + if self.complete_state: + completions_count = len(self.complete_state.completions) + + if self.complete_state.complete_index is None: + index = 0 + elif self.complete_state.complete_index == completions_count - 1: + index = None + + if disable_wrap_around: + return + else: + index = min( + completions_count - 1, self.complete_state.complete_index + count + ) + self.go_to_completion(index) + + def complete_previous( + self, count: int = 1, disable_wrap_around: bool = False + ) -> None: + """ + Browse to the previous completions. + (Does nothing if there are no completion.) + """ + index: Optional[int] + + if self.complete_state: + if self.complete_state.complete_index == 0: + index = None + + if disable_wrap_around: + return + elif self.complete_state.complete_index is None: + index = len(self.complete_state.completions) - 1 + else: + index = max(0, self.complete_state.complete_index - count) + + self.go_to_completion(index) + + def cancel_completion(self) -> None: + """ + Cancel completion, go back to the original text. + """ + if self.complete_state: + self.go_to_completion(None) + self.complete_state = None + + def _set_completions(self, completions: List[Completion]) -> CompletionState: + """ + Start completions. (Generate list of completions and initialize.) + + By default, no completion will be selected. + """ + self.complete_state = CompletionState( + original_document=self.document, completions=completions + ) + + # Trigger event. This should eventually invalidate the layout. + self.on_completions_changed.fire() + + return self.complete_state + + def start_history_lines_completion(self) -> None: + """ + Start a completion based on all the other lines in the document and the + history. + """ + found_completions: Set[str] = set() + completions = [] + + # For every line of the whole history, find matches with the current line. + current_line = self.document.current_line_before_cursor.lstrip() + + for i, string in enumerate(self._working_lines): + for j, l in enumerate(string.split("\n")): + l = l.strip() + if l and l.startswith(current_line): + # When a new line has been found. + if l not in found_completions: + found_completions.add(l) + + # Create completion. + if i == self.working_index: + display_meta = "Current, line %s" % (j + 1) + else: + display_meta = f"History {i + 1}, line {j + 1}" + + completions.append( + Completion( + text=l, + start_position=-len(current_line), + display_meta=display_meta, + ) + ) + + self._set_completions(completions=completions[::-1]) + self.go_to_completion(0) + + def go_to_completion(self, index: Optional[int]) -> None: + """ + Select a completion from the list of current completions. + """ + assert self.complete_state + + # Set new completion + state = self.complete_state + state.go_to_index(index) + + # Set text/cursor position + new_text, new_cursor_position = state.new_text_and_position() + self.document = Document(new_text, new_cursor_position) + + # (changing text/cursor position will unset complete_state.) + self.complete_state = state + + def apply_completion(self, completion: Completion) -> None: + """ + Insert a given completion. + """ + # If there was already a completion active, cancel that one. + if self.complete_state: + self.go_to_completion(None) + self.complete_state = None + + # Insert text from the given completion. + self.delete_before_cursor(-completion.start_position) + self.insert_text(completion.text) + + def _set_history_search(self) -> None: + """ + Set `history_search_text`. + (The text before the cursor will be used for filtering the history.) + """ + if self.enable_history_search(): + if self.history_search_text is None: + self.history_search_text = self.document.text_before_cursor + else: + self.history_search_text = None + + def _history_matches(self, i: int) -> bool: + """ + True when the current entry matches the history search. + (when we don't have history search, it's also True.) + """ + return self.history_search_text is None or self._working_lines[i].startswith( + self.history_search_text + ) + + def history_forward(self, count: int = 1) -> None: + """ + Move forwards through the history. + + :param count: Amount of items to move forward. + """ + self._set_history_search() + + # Go forward in history. + found_something = False + + for i in range(self.working_index + 1, len(self._working_lines)): + if self._history_matches(i): + self.working_index = i + count -= 1 + found_something = True + if count == 0: + break + + # If we found an entry, move cursor to the end of the first line. + if found_something: + self.cursor_position = 0 + self.cursor_position += self.document.get_end_of_line_position() + + def history_backward(self, count: int = 1) -> None: + """ + Move backwards through history. + """ + self._set_history_search() + + # Go back in history. + found_something = False + + for i in range(self.working_index - 1, -1, -1): + if self._history_matches(i): + self.working_index = i + count -= 1 + found_something = True + if count == 0: + break + + # If we move to another entry, move cursor to the end of the line. + if found_something: + self.cursor_position = len(self.text) + + def yank_nth_arg( + self, n: Optional[int] = None, _yank_last_arg: bool = False + ) -> None: + """ + Pick nth word from previous history entry (depending on current + `yank_nth_arg_state`) and insert it at current position. Rotate through + history if called repeatedly. If no `n` has been given, take the first + argument. (The second word.) + + :param n: (None or int), The index of the word from the previous line + to take. + """ + assert n is None or isinstance(n, int) + history_strings = self.history.get_strings() + + if not len(history_strings): + return + + # Make sure we have a `YankNthArgState`. + if self.yank_nth_arg_state is None: + state = YankNthArgState(n=-1 if _yank_last_arg else 1) + else: + state = self.yank_nth_arg_state + + if n is not None: + state.n = n + + # Get new history position. + new_pos = state.history_position - 1 + if -new_pos > len(history_strings): + new_pos = -1 + + # Take argument from line. + line = history_strings[new_pos] + + words = [w.strip() for w in _QUOTED_WORDS_RE.split(line)] + words = [w for w in words if w] + try: + word = words[state.n] + except IndexError: + word = "" + + # Insert new argument. + if state.previous_inserted_word: + self.delete_before_cursor(len(state.previous_inserted_word)) + self.insert_text(word) + + # Save state again for next completion. (Note that the 'insert' + # operation from above clears `self.yank_nth_arg_state`.) + state.previous_inserted_word = word + state.history_position = new_pos + self.yank_nth_arg_state = state + + def yank_last_arg(self, n: Optional[int] = None) -> None: + """ + Like `yank_nth_arg`, but if no argument has been given, yank the last + word by default. + """ + self.yank_nth_arg(n=n, _yank_last_arg=True) + + def start_selection( + self, selection_type: SelectionType = SelectionType.CHARACTERS + ) -> None: + """ + Take the current cursor position as the start of this selection. + """ + self.selection_state = SelectionState(self.cursor_position, selection_type) + + def copy_selection(self, _cut: bool = False) -> ClipboardData: + """ + Copy selected text and return :class:`.ClipboardData` instance. + + Notice that this doesn't store the copied data on the clipboard yet. + You can store it like this: + + .. code:: python + + data = buffer.copy_selection() + get_app().clipboard.set_data(data) + """ + new_document, clipboard_data = self.document.cut_selection() + if _cut: + self.document = new_document + + self.selection_state = None + return clipboard_data + + def cut_selection(self) -> ClipboardData: + """ + Delete selected text and return :class:`.ClipboardData` instance. + """ + return self.copy_selection(_cut=True) + + def paste_clipboard_data( + self, + data: ClipboardData, + paste_mode: PasteMode = PasteMode.EMACS, + count: int = 1, + ) -> None: + """ + Insert the data from the clipboard. + """ + assert isinstance(data, ClipboardData) + assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) + + original_document = self.document + self.document = self.document.paste_clipboard_data( + data, paste_mode=paste_mode, count=count + ) + + # Remember original document. This assignment should come at the end, + # because assigning to 'document' will erase it. + self.document_before_paste = original_document + + def newline(self, copy_margin: bool = True) -> None: + """ + Insert a line ending at the current position. + """ + if copy_margin: + self.insert_text("\n" + self.document.leading_whitespace_in_current_line) + else: + self.insert_text("\n") + + def insert_line_above(self, copy_margin: bool = True) -> None: + """ + Insert a new line above the current one. + """ + if copy_margin: + insert = self.document.leading_whitespace_in_current_line + "\n" + else: + insert = "\n" + + self.cursor_position += self.document.get_start_of_line_position() + self.insert_text(insert) + self.cursor_position -= 1 + + def insert_line_below(self, copy_margin: bool = True) -> None: + """ + Insert a new line below the current one. + """ + if copy_margin: + insert = "\n" + self.document.leading_whitespace_in_current_line + else: + insert = "\n" + + self.cursor_position += self.document.get_end_of_line_position() + self.insert_text(insert) + + def insert_text( + self, + data: str, + overwrite: bool = False, + move_cursor: bool = True, + fire_event: bool = True, + ) -> None: + """ + Insert characters at cursor position. + + :param fire_event: Fire `on_text_insert` event. This is mainly used to + trigger autocompletion while typing. + """ + # Original text & cursor position. + otext = self.text + ocpos = self.cursor_position + + # In insert/text mode. + if overwrite: + # Don't overwrite the newline itself. Just before the line ending, + # it should act like insert mode. + overwritten_text = otext[ocpos : ocpos + len(data)] + if "\n" in overwritten_text: + overwritten_text = overwritten_text[: overwritten_text.find("\n")] + + text = otext[:ocpos] + data + otext[ocpos + len(overwritten_text) :] + else: + text = otext[:ocpos] + data + otext[ocpos:] + + if move_cursor: + cpos = self.cursor_position + len(data) + else: + cpos = self.cursor_position + + # Set new document. + # (Set text and cursor position at the same time. Otherwise, setting + # the text will fire a change event before the cursor position has been + # set. It works better to have this atomic.) + self.document = Document(text, cpos) + + # Fire 'on_text_insert' event. + if fire_event: # XXX: rename to `start_complete`. + self.on_text_insert.fire() + + # Only complete when "complete_while_typing" is enabled. + if self.completer and self.complete_while_typing(): + get_app().create_background_task(self._async_completer()) + + # Call auto_suggest. + if self.auto_suggest: + get_app().create_background_task(self._async_suggester()) + + def undo(self) -> None: + # Pop from the undo-stack until we find a text that if different from + # the current text. (The current logic of `save_to_undo_stack` will + # cause that the top of the undo stack is usually the same as the + # current text, so in that case we have to pop twice.) + while self._undo_stack: + text, pos = self._undo_stack.pop() + + if text != self.text: + # Push current text to redo stack. + self._redo_stack.append((self.text, self.cursor_position)) + + # Set new text/cursor_position. + self.document = Document(text, cursor_position=pos) + break + + def redo(self) -> None: + if self._redo_stack: + # Copy current state on undo stack. + self.save_to_undo_stack(clear_redo_stack=False) + + # Pop state from redo stack. + text, pos = self._redo_stack.pop() + self.document = Document(text, cursor_position=pos) + + def validate(self, set_cursor: bool = False) -> bool: + """ + Returns `True` if valid. + + :param set_cursor: Set the cursor position, if an error was found. + """ + # Don't call the validator again, if it was already called for the + # current input. + if self.validation_state != ValidationState.UNKNOWN: + return self.validation_state == ValidationState.VALID + + # Call validator. + if self.validator: + try: + self.validator.validate(self.document) + except ValidationError as e: + # Set cursor position (don't allow invalid values.) + if set_cursor: + self.cursor_position = min( + max(0, e.cursor_position), len(self.text) + ) + + self.validation_state = ValidationState.INVALID + self.validation_error = e + return False + + # Handle validation result. + self.validation_state = ValidationState.VALID + self.validation_error = None + return True + + async def _validate_async(self) -> None: + """ + Asynchronous version of `validate()`. + This one doesn't set the cursor position. + + We have both variants, because a synchronous version is required. + Handling the ENTER key needs to be completely synchronous, otherwise + stuff like type-ahead is going to give very weird results. (People + could type input while the ENTER key is still processed.) + + An asynchronous version is required if we have `validate_while_typing` + enabled. + """ + while True: + # Don't call the validator again, if it was already called for the + # current input. + if self.validation_state != ValidationState.UNKNOWN: + return + + # Call validator. + error = None + document = self.document + + if self.validator: + try: + await self.validator.validate_async(self.document) + except ValidationError as e: + error = e + + # If the document changed during the validation, try again. + if self.document != document: + continue + + # Handle validation result. + if error: + self.validation_state = ValidationState.INVALID + else: + self.validation_state = ValidationState.VALID + + self.validation_error = error + get_app().invalidate() # Trigger redraw (display error). + + def append_to_history(self) -> None: + """ + Append the current input to the history. + """ + # Save at the tail of the history. (But don't if the last entry the + # history is already the same.) + if self.text: + history_strings = self.history.get_strings() + if not len(history_strings) or history_strings[-1] != self.text: + self.history.append_string(self.text) + + def _search( + self, + search_state: SearchState, + include_current_position: bool = False, + count: int = 1, + ) -> Optional[Tuple[int, int]]: + """ + Execute search. Return (working_index, cursor_position) tuple when this + search is applied. Returns `None` when this text cannot be found. + """ + assert count > 0 + + text = search_state.text + direction = search_state.direction + ignore_case = search_state.ignore_case() + + def search_once( + working_index: int, document: Document + ) -> Optional[Tuple[int, Document]]: + """ + Do search one time. + Return (working_index, document) or `None` + """ + if direction == SearchDirection.FORWARD: + # Try find at the current input. + new_index = document.find( + text, + include_current_position=include_current_position, + ignore_case=ignore_case, + ) + + if new_index is not None: + return ( + working_index, + Document(document.text, document.cursor_position + new_index), + ) + else: + # No match, go forward in the history. (Include len+1 to wrap around.) + # (Here we should always include all cursor positions, because + # it's a different line.) + for i in range(working_index + 1, len(self._working_lines) + 1): + i %= len(self._working_lines) + + document = Document(self._working_lines[i], 0) + new_index = document.find( + text, include_current_position=True, ignore_case=ignore_case + ) + if new_index is not None: + return (i, Document(document.text, new_index)) + else: + # Try find at the current input. + new_index = document.find_backwards(text, ignore_case=ignore_case) + + if new_index is not None: + return ( + working_index, + Document(document.text, document.cursor_position + new_index), + ) + else: + # No match, go back in the history. (Include -1 to wrap around.) + for i in range(working_index - 1, -2, -1): + i %= len(self._working_lines) + + document = Document( + self._working_lines[i], len(self._working_lines[i]) + ) + new_index = document.find_backwards( + text, ignore_case=ignore_case + ) + if new_index is not None: + return ( + i, + Document(document.text, len(document.text) + new_index), + ) + return None + + # Do 'count' search iterations. + working_index = self.working_index + document = self.document + for _ in range(count): + result = search_once(working_index, document) + if result is None: + return None # Nothing found. + else: + working_index, document = result + + return (working_index, document.cursor_position) + + def document_for_search(self, search_state: SearchState) -> Document: + """ + Return a :class:`~prompt_toolkit.document.Document` instance that has + the text/cursor position for this search, if we would apply it. This + will be used in the + :class:`~prompt_toolkit.layout.BufferControl` to display feedback while + searching. + """ + search_result = self._search(search_state, include_current_position=True) + + if search_result is None: + return self.document + else: + working_index, cursor_position = search_result + + # Keep selection, when `working_index` was not changed. + if working_index == self.working_index: + selection = self.selection_state + else: + selection = None + + return Document( + self._working_lines[working_index], cursor_position, selection=selection + ) + + def get_search_position( + self, + search_state: SearchState, + include_current_position: bool = True, + count: int = 1, + ) -> int: + """ + Get the cursor position for this search. + (This operation won't change the `working_index`. It's won't go through + the history. Vi text objects can't span multiple items.) + """ + search_result = self._search( + search_state, include_current_position=include_current_position, count=count + ) + + if search_result is None: + return self.cursor_position + else: + working_index, cursor_position = search_result + return cursor_position + + def apply_search( + self, + search_state: SearchState, + include_current_position: bool = True, + count: int = 1, + ) -> None: + """ + Apply search. If something is found, set `working_index` and + `cursor_position`. + """ + search_result = self._search( + search_state, include_current_position=include_current_position, count=count + ) + + if search_result is not None: + working_index, cursor_position = search_result + self.working_index = working_index + self.cursor_position = cursor_position + + def exit_selection(self) -> None: + self.selection_state = None + + def _editor_simple_tempfile(self) -> Tuple[str, Callable[[], None]]: + """ + Simple (file) tempfile implementation. + Return (tempfile, cleanup_func). + """ + suffix = to_str(self.tempfile_suffix) + descriptor, filename = tempfile.mkstemp(suffix) + + os.write(descriptor, self.text.encode("utf-8")) + os.close(descriptor) + + def cleanup() -> None: + os.unlink(filename) + + return filename, cleanup + + def _editor_complex_tempfile(self) -> Tuple[str, Callable[[], None]]: + # Complex (directory) tempfile implementation. + headtail = to_str(self.tempfile) + if not headtail: + # Revert to simple case. + return self._editor_simple_tempfile() + headtail = str(headtail) + + # Try to make according to tempfile logic. + head, tail = os.path.split(headtail) + if os.path.isabs(head): + head = head[1:] + + dirpath = tempfile.mkdtemp() + if head: + dirpath = os.path.join(dirpath, head) + # Assume there is no issue creating dirs in this temp dir. + os.makedirs(dirpath) + + # Open the filename and write current text. + filename = os.path.join(dirpath, tail) + with open(filename, "w", encoding="utf-8") as fh: + fh.write(self.text) + + def cleanup() -> None: + shutil.rmtree(dirpath) + + return filename, cleanup + + def open_in_editor(self, validate_and_handle: bool = False) -> "asyncio.Task[None]": + """ + Open code in editor. + + This returns a future, and runs in a thread executor. + """ + if self.read_only(): + raise EditReadOnlyBuffer() + + # Write current text to temporary file + if self.tempfile: + filename, cleanup_func = self._editor_complex_tempfile() + else: + filename, cleanup_func = self._editor_simple_tempfile() + + async def run() -> None: + try: + # Open in editor + # (We need to use `run_in_terminal`, because not all editors go to + # the alternate screen buffer, and some could influence the cursor + # position.) + succes = await run_in_terminal( + lambda: self._open_file_in_editor(filename), in_executor=True + ) + + # Read content again. + if succes: + with open(filename, "rb") as f: + text = f.read().decode("utf-8") + + # Drop trailing newline. (Editors are supposed to add it at the + # end, but we don't need it.) + if text.endswith("\n"): + text = text[:-1] + + self.document = Document(text=text, cursor_position=len(text)) + + # Accept the input. + if validate_and_handle: + self.validate_and_handle() + + finally: + # Clean up temp dir/file. + cleanup_func() + + return get_app().create_background_task(run()) + + def _open_file_in_editor(self, filename: str) -> bool: + """ + Call editor executable. + + Return True when we received a zero return code. + """ + # If the 'VISUAL' or 'EDITOR' environment variable has been set, use that. + # Otherwise, fall back to the first available editor that we can find. + visual = os.environ.get("VISUAL") + editor = os.environ.get("EDITOR") + + editors = [ + visual, + editor, + # Order of preference. + "/usr/bin/editor", + "/usr/bin/nano", + "/usr/bin/pico", + "/usr/bin/vi", + "/usr/bin/emacs", + ] + + for e in editors: + if e: + try: + # Use 'shlex.split()', because $VISUAL can contain spaces + # and quotes. + returncode = subprocess.call(shlex.split(e) + [filename]) + return returncode == 0 + + except OSError: + # Executable does not exist, try the next one. + pass + + return False + + def start_completion( + self, + select_first: bool = False, + select_last: bool = False, + insert_common_part: bool = False, + complete_event: Optional[CompleteEvent] = None, + ) -> None: + """ + Start asynchronous autocompletion of this buffer. + (This will do nothing if a previous completion was still in progress.) + """ + # Only one of these options can be selected. + assert select_first + select_last + insert_common_part <= 1 + + get_app().create_background_task( + self._async_completer( + select_first=select_first, + select_last=select_last, + insert_common_part=insert_common_part, + complete_event=complete_event + or CompleteEvent(completion_requested=True), + ) + ) + + def _create_completer_coroutine(self) -> Callable[..., Awaitable[None]]: + """ + Create function for asynchronous autocompletion. + + (This consumes the asynchronous completer generator, which possibly + runs the completion algorithm in another thread.) + """ + + def completion_does_nothing(document: Document, completion: Completion) -> bool: + """ + Return `True` if applying this completion doesn't have any effect. + (When it doesn't insert any new text. + """ + text_before_cursor = document.text_before_cursor + replaced_text = text_before_cursor[ + len(text_before_cursor) + completion.start_position : + ] + return replaced_text == completion.text + + @_only_one_at_a_time + async def async_completer( + select_first: bool = False, + select_last: bool = False, + insert_common_part: bool = False, + complete_event: Optional[CompleteEvent] = None, + ) -> None: + + document = self.document + complete_event = complete_event or CompleteEvent(text_inserted=True) + + # Don't complete when we already have completions. + if self.complete_state or not self.completer: + return + + # Create an empty CompletionState. + complete_state = CompletionState(original_document=self.document) + self.complete_state = complete_state + + def proceed() -> bool: + """Keep retrieving completions. Input text has not yet changed + while generating completions.""" + return self.complete_state == complete_state + + async for completion in self.completer.get_completions_async( + document, complete_event + ): + complete_state.completions.append(completion) + self.on_completions_changed.fire() + + # If the input text changes, abort. + if not proceed(): + break + + completions = complete_state.completions + + # When there is only one completion, which has nothing to add, ignore it. + if len(completions) == 1 and completion_does_nothing( + document, completions[0] + ): + del completions[:] + + # Set completions if the text was not yet changed. + if proceed(): + # When no completions were found, or when the user selected + # already a completion by using the arrow keys, don't do anything. + if ( + not self.complete_state + or self.complete_state.complete_index is not None + ): + return + + # When there are no completions, reset completion state anyway. + if not completions: + self.complete_state = None + # Render the ui if the completion menu was shown + # it is needed especially if there is one completion and it was deleted. + self.on_completions_changed.fire() + return + + # Select first/last or insert common part, depending on the key + # binding. (For this we have to wait until all completions are + # loaded.) + + if select_first: + self.go_to_completion(0) + + elif select_last: + self.go_to_completion(len(completions) - 1) + + elif insert_common_part: + common_part = get_common_complete_suffix(document, completions) + if common_part: + # Insert the common part, update completions. + self.insert_text(common_part) + if len(completions) > 1: + # (Don't call `async_completer` again, but + # recalculate completions. See: + # https://github.com/ipython/ipython/issues/9658) + completions[:] = [ + c.new_completion_from_position(len(common_part)) + for c in completions + ] + + self._set_completions(completions=completions) + else: + self.complete_state = None + else: + # When we were asked to insert the "common" + # prefix, but there was no common suffix but + # still exactly one match, then select the + # first. (It could be that we have a completion + # which does * expansion, like '*.py', with + # exactly one match.) + if len(completions) == 1: + self.go_to_completion(0) + + else: + # If the last operation was an insert, (not a delete), restart + # the completion coroutine. + + if self.document.text_before_cursor == document.text_before_cursor: + return # Nothing changed. + + if self.document.text_before_cursor.startswith( + document.text_before_cursor + ): + raise _Retry + + return async_completer + + def _create_auto_suggest_coroutine(self) -> Callable[[], Awaitable[None]]: + """ + Create function for asynchronous auto suggestion. + (This can be in another thread.) + """ + + @_only_one_at_a_time + async def async_suggestor() -> None: + document = self.document + + # Don't suggest when we already have a suggestion. + if self.suggestion or not self.auto_suggest: + return + + suggestion = await self.auto_suggest.get_suggestion_async(self, document) + + # Set suggestion only if the text was not yet changed. + if self.document == document: + # Set suggestion and redraw interface. + self.suggestion = suggestion + self.on_suggestion_set.fire() + else: + # Otherwise, restart thread. + raise _Retry + + return async_suggestor + + def _create_auto_validate_coroutine(self) -> Callable[[], Awaitable[None]]: + """ + Create a function for asynchronous validation while typing. + (This can be in another thread.) + """ + + @_only_one_at_a_time + async def async_validator() -> None: + await self._validate_async() + + return async_validator + + def validate_and_handle(self) -> None: + """ + Validate buffer and handle the accept action. + """ + valid = self.validate(set_cursor=True) + + # When the validation succeeded, accept the input. + if valid: + if self.accept_handler: + keep_text = self.accept_handler(self) + else: + keep_text = False + + self.append_to_history() + + if not keep_text: + self.reset() + + +_T = TypeVar("_T", bound=Callable[..., Awaitable[None]]) + + +def _only_one_at_a_time(coroutine: _T) -> _T: + """ + Decorator that only starts the coroutine only if the previous call has + finished. (Used to make sure that we have only one autocompleter, auto + suggestor and validator running at a time.) + + When the coroutine raises `_Retry`, it is restarted. + """ + running = False + + @wraps(coroutine) + async def new_coroutine(*a: Any, **kw: Any) -> Any: + nonlocal running + + # Don't start a new function, if the previous is still in progress. + if running: + return + + running = True + + try: + while True: + try: + await coroutine(*a, **kw) + except _Retry: + continue + else: + return None + finally: + running = False + + return cast(_T, new_coroutine) + + +class _Retry(Exception): + "Retry in `_only_one_at_a_time`." + + +def indent(buffer: Buffer, from_row: int, to_row: int, count: int = 1) -> None: + """ + Indent text of a :class:`.Buffer` object. + """ + current_row = buffer.document.cursor_position_row + line_range = range(from_row, to_row) + + # Apply transformation. + new_text = buffer.transform_lines(line_range, lambda l: " " * count + l) + buffer.document = Document( + new_text, Document(new_text).translate_row_col_to_index(current_row, 0) + ) + + # Go to the start of the line. + buffer.cursor_position += buffer.document.get_start_of_line_position( + after_whitespace=True + ) + + +def unindent(buffer: Buffer, from_row: int, to_row: int, count: int = 1) -> None: + """ + Unindent text of a :class:`.Buffer` object. + """ + current_row = buffer.document.cursor_position_row + line_range = range(from_row, to_row) + + def transform(text: str) -> str: + remove = " " * count + if text.startswith(remove): + return text[len(remove) :] + else: + return text.lstrip() + + # Apply transformation. + new_text = buffer.transform_lines(line_range, transform) + buffer.document = Document( + new_text, Document(new_text).translate_row_col_to_index(current_row, 0) + ) + + # Go to the start of the line. + buffer.cursor_position += buffer.document.get_start_of_line_position( + after_whitespace=True + ) + + +def reshape_text(buffer: Buffer, from_row: int, to_row: int) -> None: + """ + Reformat text, taking the width into account. + `to_row` is included. + (Vi 'gq' operator.) + """ + lines = buffer.text.splitlines(True) + lines_before = lines[:from_row] + lines_after = lines[to_row + 1 :] + lines_to_reformat = lines[from_row : to_row + 1] + + if lines_to_reformat: + # Take indentation from the first line. + match = re.search(r"^\s*", lines_to_reformat[0]) + length = match.end() if match else 0 # `match` can't be None, actually. + + indent = lines_to_reformat[0][:length].replace("\n", "") + + # Now, take all the 'words' from the lines to be reshaped. + words = "".join(lines_to_reformat).split() + + # And reshape. + width = (buffer.text_width or 80) - len(indent) + reshaped_text = [indent] + current_width = 0 + for w in words: + if current_width: + if len(w) + current_width + 1 > width: + reshaped_text.append("\n") + reshaped_text.append(indent) + current_width = 0 + else: + reshaped_text.append(" ") + current_width += 1 + + reshaped_text.append(w) + current_width += len(w) + + if reshaped_text[-1] != "\n": + reshaped_text.append("\n") + + # Apply result. + buffer.document = Document( + text="".join(lines_before + reshaped_text + lines_after), + cursor_position=len("".join(lines_before + reshaped_text)), + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/cache.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/cache.py new file mode 100644 index 0000000..e5e9d70 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/cache.py @@ -0,0 +1,125 @@ +from collections import deque +from functools import wraps +from typing import Any, Callable, Deque, Dict, Generic, Hashable, Tuple, TypeVar, cast + +__all__ = [ + "SimpleCache", + "FastDictCache", + "memoized", +] + +_T = TypeVar("_T", bound=Hashable) +_U = TypeVar("_U") + + +class SimpleCache(Generic[_T, _U]): + """ + Very simple cache that discards the oldest item when the cache size is + exceeded. + + :param maxsize: Maximum size of the cache. (Don't make it too big.) + """ + + def __init__(self, maxsize: int = 8) -> None: + assert maxsize > 0 + + self._data: Dict[_T, _U] = {} + self._keys: Deque[_T] = deque() + self.maxsize: int = maxsize + + def get(self, key: _T, getter_func: Callable[[], _U]) -> _U: + """ + Get object from the cache. + If not found, call `getter_func` to resolve it, and put that on the top + of the cache instead. + """ + # Look in cache first. + try: + return self._data[key] + except KeyError: + # Not found? Get it. + value = getter_func() + self._data[key] = value + self._keys.append(key) + + # Remove the oldest key when the size is exceeded. + if len(self._data) > self.maxsize: + key_to_remove = self._keys.popleft() + if key_to_remove in self._data: + del self._data[key_to_remove] + + return value + + def clear(self) -> None: + "Clear cache." + self._data = {} + self._keys = deque() + + +_K = TypeVar("_K", bound=Tuple[Hashable, ...]) +_V = TypeVar("_V") + + +class FastDictCache(Dict[_K, _V]): + """ + Fast, lightweight cache which keeps at most `size` items. + It will discard the oldest items in the cache first. + + The cache is a dictionary, which doesn't keep track of access counts. + It is perfect to cache little immutable objects which are not expensive to + create, but where a dictionary lookup is still much faster than an object + instantiation. + + :param get_value: Callable that's called in case of a missing key. + """ + + # NOTE: This cache is used to cache `prompt_toolkit.layout.screen.Char` and + # `prompt_toolkit.Document`. Make sure to keep this really lightweight. + # Accessing the cache should stay faster than instantiating new + # objects. + # (Dictionary lookups are really fast.) + # SimpleCache is still required for cases where the cache key is not + # the same as the arguments given to the function that creates the + # value.) + def __init__(self, get_value: Callable[..., _V], size: int = 1000000) -> None: + assert size > 0 + + self._keys: Deque[_K] = deque() + self.get_value = get_value + self.size = size + + def __missing__(self, key: _K) -> _V: + # Remove the oldest key when the size is exceeded. + if len(self) > self.size: + key_to_remove = self._keys.popleft() + if key_to_remove in self: + del self[key_to_remove] + + result = self.get_value(*key) + self[key] = result + self._keys.append(key) + return result + + +_F = TypeVar("_F", bound=Callable[..., object]) + + +def memoized(maxsize: int = 1024) -> Callable[[_F], _F]: + """ + Memoization decorator for immutable classes and pure functions. + """ + + def decorator(obj: _F) -> _F: + cache: SimpleCache[Hashable, Any] = SimpleCache(maxsize=maxsize) + + @wraps(obj) + def new_callable(*a: Any, **kw: Any) -> Any: + def create_new() -> Any: + return obj(*a, **kw) + + key = (a, tuple(sorted(kw.items()))) + return cache.get(key, create_new) + + return cast(_F, new_callable) + + return decorator diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__init__.py new file mode 100644 index 0000000..160b50a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__init__.py @@ -0,0 +1,15 @@ +from .base import Clipboard, ClipboardData, DummyClipboard, DynamicClipboard +from .in_memory import InMemoryClipboard + +# We are not importing `PyperclipClipboard` here, because it would require the +# `pyperclip` module to be present. + +# from .pyperclip import PyperclipClipboard + +__all__ = [ + "Clipboard", + "ClipboardData", + "DummyClipboard", + "DynamicClipboard", + "InMemoryClipboard", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ed2f9b3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..5d2e037 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/in_memory.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/in_memory.cpython-38.pyc new file mode 100644 index 0000000..3c1e6e7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/in_memory.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/pyperclip.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/pyperclip.cpython-38.pyc new file mode 100644 index 0000000..85af65d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/__pycache__/pyperclip.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/base.py new file mode 100644 index 0000000..c24623d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/base.py @@ -0,0 +1,107 @@ +""" +Clipboard for command line interface. +""" +from abc import ABCMeta, abstractmethod +from typing import Callable, Optional + +from prompt_toolkit.selection import SelectionType + +__all__ = [ + "Clipboard", + "ClipboardData", + "DummyClipboard", + "DynamicClipboard", +] + + +class ClipboardData: + """ + Text on the clipboard. + + :param text: string + :param type: :class:`~prompt_toolkit.selection.SelectionType` + """ + + def __init__( + self, text: str = "", type: SelectionType = SelectionType.CHARACTERS + ) -> None: + + self.text = text + self.type = type + + +class Clipboard(metaclass=ABCMeta): + """ + Abstract baseclass for clipboards. + (An implementation can be in memory, it can share the X11 or Windows + keyboard, or can be persistent.) + """ + + @abstractmethod + def set_data(self, data: ClipboardData) -> None: + """ + Set data to the clipboard. + + :param data: :class:`~.ClipboardData` instance. + """ + + def set_text(self, text: str) -> None: # Not abstract. + """ + Shortcut for setting plain text on clipboard. + """ + self.set_data(ClipboardData(text)) + + def rotate(self) -> None: + """ + For Emacs mode, rotate the kill ring. + """ + + @abstractmethod + def get_data(self) -> ClipboardData: + """ + Return clipboard data. + """ + + +class DummyClipboard(Clipboard): + """ + Clipboard implementation that doesn't remember anything. + """ + + def set_data(self, data: ClipboardData) -> None: + pass + + def set_text(self, text: str) -> None: + pass + + def rotate(self) -> None: + pass + + def get_data(self) -> ClipboardData: + return ClipboardData() + + +class DynamicClipboard(Clipboard): + """ + Clipboard class that can dynamically returns any Clipboard. + + :param get_clipboard: Callable that returns a :class:`.Clipboard` instance. + """ + + def __init__(self, get_clipboard: Callable[[], Optional[Clipboard]]) -> None: + self.get_clipboard = get_clipboard + + def _clipboard(self) -> Clipboard: + return self.get_clipboard() or DummyClipboard() + + def set_data(self, data: ClipboardData) -> None: + self._clipboard().set_data(data) + + def set_text(self, text: str) -> None: + self._clipboard().set_text(text) + + def rotate(self) -> None: + self._clipboard().rotate() + + def get_data(self) -> ClipboardData: + return self._clipboard().get_data() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/in_memory.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/in_memory.py new file mode 100644 index 0000000..1902e36 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/in_memory.py @@ -0,0 +1,46 @@ +from collections import deque +from typing import Deque, Optional + +from .base import Clipboard, ClipboardData + +__all__ = [ + "InMemoryClipboard", +] + + +class InMemoryClipboard(Clipboard): + """ + Default clipboard implementation. + Just keep the data in memory. + + This implements a kill-ring, for Emacs mode. + """ + + def __init__( + self, data: Optional[ClipboardData] = None, max_size: int = 60 + ) -> None: + + assert max_size >= 1 + + self.max_size = max_size + self._ring: Deque[ClipboardData] = deque() + + if data is not None: + self.set_data(data) + + def set_data(self, data: ClipboardData) -> None: + self._ring.appendleft(data) + + while len(self._ring) > self.max_size: + self._ring.pop() + + def get_data(self) -> ClipboardData: + if self._ring: + return self._ring[0] + else: + return ClipboardData() + + def rotate(self) -> None: + if self._ring: + # Add the very first item at the end. + self._ring.append(self._ring.popleft()) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/pyperclip.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/pyperclip.py new file mode 100644 index 0000000..c8cb7af --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/clipboard/pyperclip.py @@ -0,0 +1,42 @@ +from typing import Optional + +import pyperclip + +from prompt_toolkit.selection import SelectionType + +from .base import Clipboard, ClipboardData + +__all__ = [ + "PyperclipClipboard", +] + + +class PyperclipClipboard(Clipboard): + """ + Clipboard that synchronizes with the Windows/Mac/Linux system clipboard, + using the pyperclip module. + """ + + def __init__(self) -> None: + self._data: Optional[ClipboardData] = None + + def set_data(self, data: ClipboardData) -> None: + self._data = data + pyperclip.copy(data.text) + + def get_data(self) -> ClipboardData: + text = pyperclip.paste() + + # When the clipboard data is equal to what we copied last time, reuse + # the `ClipboardData` instance. That way we're sure to keep the same + # `SelectionType`. + if self._data and self._data.text == text: + return self._data + + # Pyperclip returned something else. Create a new `ClipboardData` + # instance. + else: + return ClipboardData( + text=text, + type=SelectionType.LINES if "\n" in text else SelectionType.CHARACTERS, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__init__.py new file mode 100644 index 0000000..270c743 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__init__.py @@ -0,0 +1,41 @@ +from .base import ( + CompleteEvent, + Completer, + Completion, + ConditionalCompleter, + DummyCompleter, + DynamicCompleter, + ThreadedCompleter, + get_common_complete_suffix, + merge_completers, +) +from .deduplicate import DeduplicateCompleter +from .filesystem import ExecutableCompleter, PathCompleter +from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter +from .nested import NestedCompleter +from .word_completer import WordCompleter + +__all__ = [ + # Base. + "Completion", + "Completer", + "ThreadedCompleter", + "DummyCompleter", + "DynamicCompleter", + "CompleteEvent", + "ConditionalCompleter", + "merge_completers", + "get_common_complete_suffix", + # Filesystem. + "PathCompleter", + "ExecutableCompleter", + # Fuzzy + "FuzzyCompleter", + "FuzzyWordCompleter", + # Nested. + "NestedCompleter", + # Word completer. + "WordCompleter", + # Deduplicate + "DeduplicateCompleter", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3560983 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..0a0d168 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/deduplicate.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/deduplicate.cpython-38.pyc new file mode 100644 index 0000000..6975dd5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/deduplicate.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/filesystem.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/filesystem.cpython-38.pyc new file mode 100644 index 0000000..ceb1846 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/filesystem.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/fuzzy_completer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/fuzzy_completer.cpython-38.pyc new file mode 100644 index 0000000..05190bd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/fuzzy_completer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/nested.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/nested.cpython-38.pyc new file mode 100644 index 0000000..e0457fa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/nested.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/word_completer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/word_completer.cpython-38.pyc new file mode 100644 index 0000000..3795647 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/__pycache__/word_completer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/base.py new file mode 100644 index 0000000..371c0ea --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/base.py @@ -0,0 +1,396 @@ +""" +""" +from abc import ABCMeta, abstractmethod +from typing import AsyncGenerator, Callable, Iterable, Optional, Sequence + +from prompt_toolkit.document import Document +from prompt_toolkit.eventloop import generator_to_async_generator +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples + +__all__ = [ + "Completion", + "Completer", + "ThreadedCompleter", + "DummyCompleter", + "DynamicCompleter", + "CompleteEvent", + "ConditionalCompleter", + "merge_completers", + "get_common_complete_suffix", +] + + +class Completion: + """ + :param text: The new string that will be inserted into the document. + :param start_position: Position relative to the cursor_position where the + new text will start. The text will be inserted between the + start_position and the original cursor position. + :param display: (optional string or formatted text) If the completion has + to be displayed differently in the completion menu. + :param display_meta: (Optional string or formatted text) Meta information + about the completion, e.g. the path or source where it's coming from. + This can also be a callable that returns a string. + :param style: Style string. + :param selected_style: Style string, used for a selected completion. + This can override the `style` parameter. + """ + + def __init__( + self, + text: str, + start_position: int = 0, + display: Optional[AnyFormattedText] = None, + display_meta: Optional[AnyFormattedText] = None, + style: str = "", + selected_style: str = "", + ) -> None: + + from prompt_toolkit.formatted_text import to_formatted_text + + self.text = text + self.start_position = start_position + self._display_meta = display_meta + + if display is None: + display = text + + self.display = to_formatted_text(display) + + self.style = style + self.selected_style = selected_style + + assert self.start_position <= 0 + + def __repr__(self) -> str: + if isinstance(self.display, str) and self.display == self.text: + return "{}(text={!r}, start_position={!r})".format( + self.__class__.__name__, + self.text, + self.start_position, + ) + else: + return "{}(text={!r}, start_position={!r}, display={!r})".format( + self.__class__.__name__, + self.text, + self.start_position, + self.display, + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Completion): + return False + return ( + self.text == other.text + and self.start_position == other.start_position + and self.display == other.display + and self._display_meta == other._display_meta + ) + + def __hash__(self) -> int: + return hash((self.text, self.start_position, self.display, self._display_meta)) + + @property + def display_text(self) -> str: + "The 'display' field as plain text." + from prompt_toolkit.formatted_text import fragment_list_to_text + + return fragment_list_to_text(self.display) + + @property + def display_meta(self) -> StyleAndTextTuples: + "Return meta-text. (This is lazy when using a callable)." + from prompt_toolkit.formatted_text import to_formatted_text + + return to_formatted_text(self._display_meta or "") + + @property + def display_meta_text(self) -> str: + "The 'meta' field as plain text." + from prompt_toolkit.formatted_text import fragment_list_to_text + + return fragment_list_to_text(self.display_meta) + + def new_completion_from_position(self, position: int) -> "Completion": + """ + (Only for internal use!) + Get a new completion by splitting this one. Used by `Application` when + it needs to have a list of new completions after inserting the common + prefix. + """ + assert position - self.start_position >= 0 + + return Completion( + text=self.text[position - self.start_position :], + display=self.display, + display_meta=self._display_meta, + ) + + +class CompleteEvent: + """ + Event that called the completer. + + :param text_inserted: When True, it means that completions are requested + because of a text insert. (`Buffer.complete_while_typing`.) + :param completion_requested: When True, it means that the user explicitly + pressed the `Tab` key in order to view the completions. + + These two flags can be used for instance to implement a completer that + shows some completions when ``Tab`` has been pressed, but not + automatically when the user presses a space. (Because of + `complete_while_typing`.) + """ + + def __init__( + self, text_inserted: bool = False, completion_requested: bool = False + ) -> None: + assert not (text_inserted and completion_requested) + + #: Automatic completion while typing. + self.text_inserted = text_inserted + + #: Used explicitly requested completion by pressing 'tab'. + self.completion_requested = completion_requested + + def __repr__(self) -> str: + return "{}(text_inserted={!r}, completion_requested={!r})".format( + self.__class__.__name__, + self.text_inserted, + self.completion_requested, + ) + + +class Completer(metaclass=ABCMeta): + """ + Base class for completer implementations. + """ + + @abstractmethod + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + """ + This should be a generator that yields :class:`.Completion` instances. + + If the generation of completions is something expensive (that takes a + lot of time), consider wrapping this `Completer` class in a + `ThreadedCompleter`. In that case, the completer algorithm runs in a + background thread and completions will be displayed as soon as they + arrive. + + :param document: :class:`~prompt_toolkit.document.Document` instance. + :param complete_event: :class:`.CompleteEvent` instance. + """ + while False: + yield + + async def get_completions_async( + self, document: Document, complete_event: CompleteEvent + ) -> AsyncGenerator[Completion, None]: + """ + Asynchronous generator for completions. (Probably, you won't have to + override this.) + + Asynchronous generator of :class:`.Completion` objects. + """ + for item in self.get_completions(document, complete_event): + yield item + + +class ThreadedCompleter(Completer): + """ + Wrapper that runs the `get_completions` generator in a thread. + + (Use this to prevent the user interface from becoming unresponsive if the + generation of completions takes too much time.) + + The completions will be displayed as soon as they are produced. The user + can already select a completion, even if not all completions are displayed. + """ + + def __init__(self, completer: Completer) -> None: + self.completer = completer + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + return self.completer.get_completions(document, complete_event) + + async def get_completions_async( + self, document: Document, complete_event: CompleteEvent + ) -> AsyncGenerator[Completion, None]: + """ + Asynchronous generator of completions. + """ + async for completion in generator_to_async_generator( + lambda: self.completer.get_completions(document, complete_event) + ): + yield completion + + def __repr__(self) -> str: + return f"ThreadedCompleter({self.completer!r})" + + +class DummyCompleter(Completer): + """ + A completer that doesn't return any completion. + """ + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + return [] + + def __repr__(self) -> str: + return "DummyCompleter()" + + +class DynamicCompleter(Completer): + """ + Completer class that can dynamically returns any Completer. + + :param get_completer: Callable that returns a :class:`.Completer` instance. + """ + + def __init__(self, get_completer: Callable[[], Optional[Completer]]) -> None: + self.get_completer = get_completer + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + completer = self.get_completer() or DummyCompleter() + return completer.get_completions(document, complete_event) + + async def get_completions_async( + self, document: Document, complete_event: CompleteEvent + ) -> AsyncGenerator[Completion, None]: + completer = self.get_completer() or DummyCompleter() + + async for completion in completer.get_completions_async( + document, complete_event + ): + yield completion + + def __repr__(self) -> str: + return f"DynamicCompleter({self.get_completer!r} -> {self.get_completer()!r})" + + +class ConditionalCompleter(Completer): + """ + Wrapper around any other completer that will enable/disable the completions + depending on whether the received condition is satisfied. + + :param completer: :class:`.Completer` instance. + :param filter: :class:`.Filter` instance. + """ + + def __init__(self, completer: Completer, filter: FilterOrBool) -> None: + self.completer = completer + self.filter = to_filter(filter) + + def __repr__(self) -> str: + return f"ConditionalCompleter({self.completer!r}, filter={self.filter!r})" + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + # Get all completions in a blocking way. + if self.filter(): + yield from self.completer.get_completions(document, complete_event) + + async def get_completions_async( + self, document: Document, complete_event: CompleteEvent + ) -> AsyncGenerator[Completion, None]: + + # Get all completions in a non-blocking way. + if self.filter(): + async for item in self.completer.get_completions_async( + document, complete_event + ): + yield item + + +class _MergedCompleter(Completer): + """ + Combine several completers into one. + """ + + def __init__(self, completers: Sequence[Completer]) -> None: + self.completers = completers + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + # Get all completions from the other completers in a blocking way. + for completer in self.completers: + yield from completer.get_completions(document, complete_event) + + async def get_completions_async( + self, document: Document, complete_event: CompleteEvent + ) -> AsyncGenerator[Completion, None]: + + # Get all completions from the other completers in a non-blocking way. + for completer in self.completers: + async for item in completer.get_completions_async(document, complete_event): + yield item + + +def merge_completers( + completers: Sequence[Completer], deduplicate: bool = False +) -> Completer: + """ + Combine several completers into one. + + :param deduplicate: If `True`, wrap the result in a `DeduplicateCompleter` + so that completions that would result in the same text will be + deduplicated. + """ + if deduplicate: + from .deduplicate import DeduplicateCompleter + + return DeduplicateCompleter(_MergedCompleter(completers)) + + return _MergedCompleter(completers) + + +def get_common_complete_suffix( + document: Document, completions: Sequence[Completion] +) -> str: + """ + Return the common prefix for all completions. + """ + # Take only completions that don't change the text before the cursor. + def doesnt_change_before_cursor(completion: Completion) -> bool: + end = completion.text[: -completion.start_position] + return document.text_before_cursor.endswith(end) + + completions2 = [c for c in completions if doesnt_change_before_cursor(c)] + + # When there is at least one completion that changes the text before the + # cursor, don't return any common part. + if len(completions2) != len(completions): + return "" + + # Return the common prefix. + def get_suffix(completion: Completion) -> str: + return completion.text[-completion.start_position :] + + return _commonprefix([get_suffix(c) for c in completions2]) + + +def _commonprefix(strings: Iterable[str]) -> str: + # Similar to os.path.commonprefix + if not strings: + return "" + + else: + s1 = min(strings) + s2 = max(strings) + + for i, c in enumerate(s1): + if c != s2[i]: + return s1[:i] + + return s1 diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/deduplicate.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/deduplicate.py new file mode 100644 index 0000000..6ef9522 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/deduplicate.py @@ -0,0 +1,43 @@ +from typing import Iterable, Set + +from prompt_toolkit.document import Document + +from .base import CompleteEvent, Completer, Completion + +__all__ = ["DeduplicateCompleter"] + + +class DeduplicateCompleter(Completer): + """ + Wrapper around a completer that removes duplicates. Only the first unique + completions are kept. + + Completions are considered to be a duplicate if they result in the same + document text when they would be applied. + """ + + def __init__(self, completer: Completer) -> None: + self.completer = completer + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + # Keep track of the document strings we'd get after applying any completion. + found_so_far: Set[str] = set() + + for completion in self.completer.get_completions(document, complete_event): + text_if_applied = ( + document.text[: document.cursor_position + completion.start_position] + + completion.text + + document.text[document.cursor_position :] + ) + + if text_if_applied == document.text: + # Don't include completions that don't have any effect at all. + continue + + if text_if_applied in found_so_far: + continue + + found_so_far.add(text_if_applied) + yield completion diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/filesystem.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/filesystem.py new file mode 100644 index 0000000..719eac6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/filesystem.py @@ -0,0 +1,117 @@ +import os +from typing import Callable, Iterable, List, Optional + +from prompt_toolkit.completion import CompleteEvent, Completer, Completion +from prompt_toolkit.document import Document + +__all__ = [ + "PathCompleter", + "ExecutableCompleter", +] + + +class PathCompleter(Completer): + """ + Complete for Path variables. + + :param get_paths: Callable which returns a list of directories to look into + when the user enters a relative path. + :param file_filter: Callable which takes a filename and returns whether + this file should show up in the completion. ``None`` + when no filtering has to be done. + :param min_input_len: Don't do autocompletion when the input string is shorter. + """ + + def __init__( + self, + only_directories: bool = False, + get_paths: Optional[Callable[[], List[str]]] = None, + file_filter: Optional[Callable[[str], bool]] = None, + min_input_len: int = 0, + expanduser: bool = False, + ) -> None: + + self.only_directories = only_directories + self.get_paths = get_paths or (lambda: ["."]) + self.file_filter = file_filter or (lambda _: True) + self.min_input_len = min_input_len + self.expanduser = expanduser + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + text = document.text_before_cursor + + # Complete only when we have at least the minimal input length, + # otherwise, we can too many results and autocompletion will become too + # heavy. + if len(text) < self.min_input_len: + return + + try: + # Do tilde expansion. + if self.expanduser: + text = os.path.expanduser(text) + + # Directories where to look. + dirname = os.path.dirname(text) + if dirname: + directories = [ + os.path.dirname(os.path.join(p, text)) for p in self.get_paths() + ] + else: + directories = self.get_paths() + + # Start of current file. + prefix = os.path.basename(text) + + # Get all filenames. + filenames = [] + for directory in directories: + # Look for matches in this directory. + if os.path.isdir(directory): + for filename in os.listdir(directory): + if filename.startswith(prefix): + filenames.append((directory, filename)) + + # Sort + filenames = sorted(filenames, key=lambda k: k[1]) + + # Yield them. + for directory, filename in filenames: + completion = filename[len(prefix) :] + full_name = os.path.join(directory, filename) + + if os.path.isdir(full_name): + # For directories, add a slash to the filename. + # (We don't add them to the `completion`. Users can type it + # to trigger the autocompletion themselves.) + filename += "/" + elif self.only_directories: + continue + + if not self.file_filter(full_name): + continue + + yield Completion( + text=completion, + start_position=0, + display=filename, + ) + except OSError: + pass + + +class ExecutableCompleter(PathCompleter): + """ + Complete only executable files in the current path. + """ + + def __init__(self) -> None: + super().__init__( + only_directories=False, + min_input_len=1, + get_paths=lambda: os.environ.get("PATH", "").split(os.pathsep), + file_filter=lambda name: os.access(name, os.X_OK), + expanduser=True, + ), diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/fuzzy_completer.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/fuzzy_completer.py new file mode 100644 index 0000000..627adc8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/fuzzy_completer.py @@ -0,0 +1,201 @@ +import re +from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union + +from prompt_toolkit.document import Document +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples + +from .base import CompleteEvent, Completer, Completion +from .word_completer import WordCompleter + +__all__ = [ + "FuzzyCompleter", + "FuzzyWordCompleter", +] + + +class FuzzyCompleter(Completer): + """ + Fuzzy completion. + This wraps any other completer and turns it into a fuzzy completer. + + If the list of words is: ["leopard" , "gorilla", "dinosaur", "cat", "bee"] + Then trying to complete "oar" would yield "leopard" and "dinosaur", but not + the others, because they match the regular expression 'o.*a.*r'. + Similar, in another application "djm" could expand to "django_migrations". + + The results are sorted by relevance, which is defined as the start position + and the length of the match. + + Notice that this is not really a tool to work around spelling mistakes, + like what would be possible with difflib. The purpose is rather to have a + quicker or more intuitive way to filter the given completions, especially + when many completions have a common prefix. + + Fuzzy algorithm is based on this post: + https://blog.amjith.com/fuzzyfinder-in-10-lines-of-python + + :param completer: A :class:`~.Completer` instance. + :param WORD: When True, use WORD characters. + :param pattern: Regex pattern which selects the characters before the + cursor that are considered for the fuzzy matching. + :param enable_fuzzy: (bool or `Filter`) Enabled the fuzzy behavior. For + easily turning fuzzyness on or off according to a certain condition. + """ + + def __init__( + self, + completer: Completer, + WORD: bool = False, + pattern: Optional[str] = None, + enable_fuzzy: FilterOrBool = True, + ): + + assert pattern is None or pattern.startswith("^") + + self.completer = completer + self.pattern = pattern + self.WORD = WORD + self.pattern = pattern + self.enable_fuzzy = to_filter(enable_fuzzy) + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + if self.enable_fuzzy(): + return self._get_fuzzy_completions(document, complete_event) + else: + return self.completer.get_completions(document, complete_event) + + def _get_pattern(self) -> str: + if self.pattern: + return self.pattern + if self.WORD: + return r"[^\s]+" + return "^[a-zA-Z0-9_]*" + + def _get_fuzzy_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + + word_before_cursor = document.get_word_before_cursor( + pattern=re.compile(self._get_pattern()) + ) + + # Get completions + document2 = Document( + text=document.text[: document.cursor_position - len(word_before_cursor)], + cursor_position=document.cursor_position - len(word_before_cursor), + ) + + completions = list(self.completer.get_completions(document2, complete_event)) + + fuzzy_matches: List[_FuzzyMatch] = [] + + pat = ".*?".join(map(re.escape, word_before_cursor)) + pat = f"(?=({pat}))" # lookahead regex to manage overlapping matches + regex = re.compile(pat, re.IGNORECASE) + for compl in completions: + matches = list(regex.finditer(compl.text)) + if matches: + # Prefer the match, closest to the left, then shortest. + best = min(matches, key=lambda m: (m.start(), len(m.group(1)))) + fuzzy_matches.append( + _FuzzyMatch(len(best.group(1)), best.start(), compl) + ) + + def sort_key(fuzzy_match: "_FuzzyMatch") -> Tuple[int, int]: + "Sort by start position, then by the length of the match." + return fuzzy_match.start_pos, fuzzy_match.match_length + + fuzzy_matches = sorted(fuzzy_matches, key=sort_key) + + for match in fuzzy_matches: + # Include these completions, but set the correct `display` + # attribute and `start_position`. + yield Completion( + text=match.completion.text, + start_position=match.completion.start_position + - len(word_before_cursor), + display_meta=match.completion.display_meta, + display=self._get_display(match, word_before_cursor), + style=match.completion.style, + ) + + def _get_display( + self, fuzzy_match: "_FuzzyMatch", word_before_cursor: str + ) -> AnyFormattedText: + """ + Generate formatted text for the display label. + """ + m = fuzzy_match + word = m.completion.text + + if m.match_length == 0: + # No highlighting when we have zero length matches (no input text). + # In this case, use the original display text (which can include + # additional styling or characters). + return m.completion.display + + result: StyleAndTextTuples = [] + + # Text before match. + result.append(("class:fuzzymatch.outside", word[: m.start_pos])) + + # The match itself. + characters = list(word_before_cursor) + + for c in word[m.start_pos : m.start_pos + m.match_length]: + classname = "class:fuzzymatch.inside" + if characters and c.lower() == characters[0].lower(): + classname += ".character" + del characters[0] + + result.append((classname, c)) + + # Text after match. + result.append( + ("class:fuzzymatch.outside", word[m.start_pos + m.match_length :]) + ) + + return result + + +class FuzzyWordCompleter(Completer): + """ + Fuzzy completion on a list of words. + + (This is basically a `WordCompleter` wrapped in a `FuzzyCompleter`.) + + :param words: List of words or callable that returns a list of words. + :param meta_dict: Optional dict mapping words to their meta-information. + :param WORD: When True, use WORD characters. + """ + + def __init__( + self, + words: Union[List[str], Callable[[], List[str]]], + meta_dict: Optional[Dict[str, str]] = None, + WORD: bool = False, + ) -> None: + + self.words = words + self.meta_dict = meta_dict or {} + self.WORD = WORD + + self.word_completer = WordCompleter( + words=self.words, WORD=self.WORD, meta_dict=self.meta_dict + ) + + self.fuzzy_completer = FuzzyCompleter(self.word_completer, WORD=self.WORD) + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + return self.fuzzy_completer.get_completions(document, complete_event) + + +class _FuzzyMatch(NamedTuple): + match_length: int + start_pos: int + completion: Completion diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/nested.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/nested.py new file mode 100644 index 0000000..f8656b2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/nested.py @@ -0,0 +1,107 @@ +""" +Nestedcompleter for completion of hierarchical data structures. +""" +from typing import Any, Dict, Iterable, Mapping, Optional, Set, Union + +from prompt_toolkit.completion import CompleteEvent, Completer, Completion +from prompt_toolkit.completion.word_completer import WordCompleter +from prompt_toolkit.document import Document + +__all__ = ["NestedCompleter"] + +# NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]] +NestedDict = Mapping[str, Union[Any, Set[str], None, Completer]] + + +class NestedCompleter(Completer): + """ + Completer which wraps around several other completers, and calls any the + one that corresponds with the first word of the input. + + By combining multiple `NestedCompleter` instances, we can achieve multiple + hierarchical levels of autocompletion. This is useful when `WordCompleter` + is not sufficient. + + If you need multiple levels, check out the `from_nested_dict` classmethod. + """ + + def __init__( + self, options: Dict[str, Optional[Completer]], ignore_case: bool = True + ) -> None: + + self.options = options + self.ignore_case = ignore_case + + def __repr__(self) -> str: + return f"NestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})" + + @classmethod + def from_nested_dict(cls, data: NestedDict) -> "NestedCompleter": + """ + Create a `NestedCompleter`, starting from a nested dictionary data + structure, like this: + + .. code:: + + data = { + 'show': { + 'version': None, + 'interfaces': None, + 'clock': None, + 'ip': {'interface': {'brief'}} + }, + 'exit': None + 'enable': None + } + + The value should be `None` if there is no further completion at some + point. If all values in the dictionary are None, it is also possible to + use a set instead. + + Values in this data structure can be a completers as well. + """ + options: Dict[str, Optional[Completer]] = {} + for key, value in data.items(): + if isinstance(value, Completer): + options[key] = value + elif isinstance(value, dict): + options[key] = cls.from_nested_dict(value) + elif isinstance(value, set): + options[key] = cls.from_nested_dict({item: None for item in value}) + else: + assert value is None + options[key] = None + + return cls(options) + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + # Split document. + text = document.text_before_cursor.lstrip() + stripped_len = len(document.text_before_cursor) - len(text) + + # If there is a space, check for the first term, and use a + # subcompleter. + if " " in text: + first_term = text.split()[0] + completer = self.options.get(first_term) + + # If we have a sub completer, use this for the completions. + if completer is not None: + remaining_text = text[len(first_term) :].lstrip() + move_cursor = len(text) - len(remaining_text) + stripped_len + + new_document = Document( + remaining_text, + cursor_position=document.cursor_position - move_cursor, + ) + + yield from completer.get_completions(new_document, complete_event) + + # No space in the input: behave exactly like `WordCompleter`. + else: + completer = WordCompleter( + list(self.options.keys()), ignore_case=self.ignore_case + ) + yield from completer.get_completions(document, complete_event) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/word_completer.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/word_completer.py new file mode 100644 index 0000000..28c133e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/completion/word_completer.py @@ -0,0 +1,93 @@ +from typing import Callable, Iterable, List, Mapping, Optional, Pattern, Union + +from prompt_toolkit.completion import CompleteEvent, Completer, Completion +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text import AnyFormattedText + +__all__ = [ + "WordCompleter", +] + + +class WordCompleter(Completer): + """ + Simple autocompletion on a list of words. + + :param words: List of words or callable that returns a list of words. + :param ignore_case: If True, case-insensitive completion. + :param meta_dict: Optional dict mapping words to their meta-text. (This + should map strings to strings or formatted text.) + :param WORD: When True, use WORD characters. + :param sentence: When True, don't complete by comparing the word before the + cursor, but by comparing all the text before the cursor. In this case, + the list of words is just a list of strings, where each string can + contain spaces. (Can not be used together with the WORD option.) + :param match_middle: When True, match not only the start, but also in the + middle of the word. + :param pattern: Optional compiled regex for finding the word before + the cursor to complete. When given, use this regex pattern instead of + default one (see document._FIND_WORD_RE) + """ + + def __init__( + self, + words: Union[List[str], Callable[[], List[str]]], + ignore_case: bool = False, + display_dict: Optional[Mapping[str, AnyFormattedText]] = None, + meta_dict: Optional[Mapping[str, AnyFormattedText]] = None, + WORD: bool = False, + sentence: bool = False, + match_middle: bool = False, + pattern: Optional[Pattern[str]] = None, + ) -> None: + + assert not (WORD and sentence) + + self.words = words + self.ignore_case = ignore_case + self.display_dict = display_dict or {} + self.meta_dict = meta_dict or {} + self.WORD = WORD + self.sentence = sentence + self.match_middle = match_middle + self.pattern = pattern + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + # Get list of words. + words = self.words + if callable(words): + words = words() + + # Get word/text before cursor. + if self.sentence: + word_before_cursor = document.text_before_cursor + else: + word_before_cursor = document.get_word_before_cursor( + WORD=self.WORD, pattern=self.pattern + ) + + if self.ignore_case: + word_before_cursor = word_before_cursor.lower() + + def word_matches(word: str) -> bool: + """True when the word before the cursor matches.""" + if self.ignore_case: + word = word.lower() + + if self.match_middle: + return word_before_cursor in word + else: + return word.startswith(word_before_cursor) + + for a in words: + if word_matches(a): + display = self.display_dict.get(a, a) + display_meta = self.meta_dict.get(a, "") + yield Completion( + text=a, + start_position=-len(word_before_cursor), + display=display, + display_meta=display_meta, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..c9a467a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__init__.py new file mode 100644 index 0000000..50ca2f9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__init__.py @@ -0,0 +1,3 @@ +from .system import SystemCompleter + +__all__ = ["SystemCompleter"] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..a219a0a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__pycache__/system.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__pycache__/system.cpython-38.pyc new file mode 100644 index 0000000..8a54a15 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/__pycache__/system.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/system.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/system.py new file mode 100644 index 0000000..9e63268 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/completers/system.py @@ -0,0 +1,62 @@ +from prompt_toolkit.completion.filesystem import ExecutableCompleter, PathCompleter +from prompt_toolkit.contrib.regular_languages.compiler import compile +from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter + +__all__ = [ + "SystemCompleter", +] + + +class SystemCompleter(GrammarCompleter): + """ + Completer for system commands. + """ + + def __init__(self) -> None: + # Compile grammar. + g = compile( + r""" + # First we have an executable. + (?P[^\s]+) + + # Ignore literals in between. + ( + \s+ + ("[^"]*" | '[^']*' | [^'"]+ ) + )* + + \s+ + + # Filename as parameters. + ( + (?P[^\s]+) | + "(?P[^\s]+)" | + '(?P[^\s]+)' + ) + """, + escape_funcs={ + "double_quoted_filename": (lambda string: string.replace('"', '\\"')), + "single_quoted_filename": (lambda string: string.replace("'", "\\'")), + }, + unescape_funcs={ + "double_quoted_filename": ( + lambda string: string.replace('\\"', '"') + ), # XXX: not entirely correct. + "single_quoted_filename": (lambda string: string.replace("\\'", "'")), + }, + ) + + # Create GrammarCompleter + super().__init__( + g, + { + "executable": ExecutableCompleter(), + "filename": PathCompleter(only_directories=False, expanduser=True), + "double_quoted_filename": PathCompleter( + only_directories=False, expanduser=True + ), + "single_quoted_filename": PathCompleter( + only_directories=False, expanduser=True + ), + }, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__init__.py new file mode 100644 index 0000000..3f30614 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__init__.py @@ -0,0 +1,77 @@ +r""" +Tool for expressing the grammar of an input as a regular language. +================================================================== + +The grammar for the input of many simple command line interfaces can be +expressed by a regular language. Examples are PDB (the Python debugger); a +simple (bash-like) shell with "pwd", "cd", "cat" and "ls" commands; arguments +that you can pass to an executable; etc. It is possible to use regular +expressions for validation and parsing of such a grammar. (More about regular +languages: http://en.wikipedia.org/wiki/Regular_language) + +Example +------- + +Let's take the pwd/cd/cat/ls example. We want to have a shell that accepts +these three commands. "cd" is followed by a quoted directory name and "cat" is +followed by a quoted file name. (We allow quotes inside the filename when +they're escaped with a backslash.) We could define the grammar using the +following regular expression:: + + grammar = \s* ( + pwd | + ls | + (cd \s+ " ([^"]|\.)+ ") | + (cat \s+ " ([^"]|\.)+ ") + ) \s* + + +What can we do with this grammar? +--------------------------------- + +- Syntax highlighting: We could use this for instance to give file names + different colour. +- Parse the result: .. We can extract the file names and commands by using a + regular expression with named groups. +- Input validation: .. Don't accept anything that does not match this grammar. + When combined with a parser, we can also recursively do + filename validation (and accept only existing files.) +- Autocompletion: .... Each part of the grammar can have its own autocompleter. + "cat" has to be completed using file names, while "cd" + has to be completed using directory names. + +How does it work? +----------------- + +As a user of this library, you have to define the grammar of the input as a +regular expression. The parts of this grammar where autocompletion, validation +or any other processing is required need to be marked using a regex named +group. Like ``(?P...)`` for instance. + +When the input is processed for validation (for instance), the regex will +execute, the named group is captured, and the validator associated with this +named group will test the captured string. + +There is one tricky bit: + + Often we operate on incomplete input (this is by definition the case for + autocompletion) and we have to decide for the cursor position in which + possible state the grammar it could be and in which way variables could be + matched up to that point. + +To solve this problem, the compiler takes the original regular expression and +translates it into a set of other regular expressions which each match certain +prefixes of the original regular expression. We generate one prefix regular +expression for every named variable (with this variable being the end of that +expression). + + +TODO: some examples of: + - How to create a highlighter from this grammar. + - How to create a validator from this grammar. + - How to create an autocompleter from this grammar. + - How to create a parser from this grammar. +""" +from .compiler import compile + +__all__ = ["compile"] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..fae482f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/compiler.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/compiler.cpython-38.pyc new file mode 100644 index 0000000..97889dd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/compiler.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/completion.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/completion.cpython-38.pyc new file mode 100644 index 0000000..b9dd024 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/completion.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/lexer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/lexer.cpython-38.pyc new file mode 100644 index 0000000..d5c4b68 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/lexer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/regex_parser.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/regex_parser.cpython-38.pyc new file mode 100644 index 0000000..db78162 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/regex_parser.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/validation.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/validation.cpython-38.pyc new file mode 100644 index 0000000..2efcf98 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/__pycache__/validation.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/compiler.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/compiler.py new file mode 100644 index 0000000..fed7849 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/compiler.py @@ -0,0 +1,574 @@ +r""" +Compiler for a regular grammar. + +Example usage:: + + # Create and compile grammar. + p = compile('add \s+ (?P[^\s]+) \s+ (?P[^\s]+)') + + # Match input string. + m = p.match('add 23 432') + + # Get variables. + m.variables().get('var1') # Returns "23" + m.variables().get('var2') # Returns "432" + + +Partial matches are possible:: + + # Create and compile grammar. + p = compile(''' + # Operators with two arguments. + ((?P[^\s]+) \s+ (?P[^\s]+) \s+ (?P[^\s]+)) | + + # Operators with only one arguments. + ((?P[^\s]+) \s+ (?P[^\s]+)) + ''') + + # Match partial input string. + m = p.match_prefix('add 23') + + # Get variables. (Notice that both operator1 and operator2 contain the + # value "add".) This is because our input is incomplete, and we don't know + # yet in which rule of the regex we we'll end up. It could also be that + # `operator1` and `operator2` have a different autocompleter and we want to + # call all possible autocompleters that would result in valid input.) + m.variables().get('var1') # Returns "23" + m.variables().get('operator1') # Returns "add" + m.variables().get('operator2') # Returns "add" + +""" +import re +from typing import Callable, Dict, Iterable, Iterator, List +from typing import Match as RegexMatch +from typing import Optional, Pattern, Tuple + +from .regex_parser import ( + AnyNode, + Lookahead, + Node, + NodeSequence, + Regex, + Repeat, + Variable, + parse_regex, + tokenize_regex, +) + +__all__ = [ + "compile", +] + + +# Name of the named group in the regex, matching trailing input. +# (Trailing input is when the input contains characters after the end of the +# expression has been matched.) +_INVALID_TRAILING_INPUT = "invalid_trailing" + +EscapeFuncDict = Dict[str, Callable[[str], str]] + + +class _CompiledGrammar: + """ + Compiles a grammar. This will take the parse tree of a regular expression + and compile the grammar. + + :param root_node: :class~`.regex_parser.Node` instance. + :param escape_funcs: `dict` mapping variable names to escape callables. + :param unescape_funcs: `dict` mapping variable names to unescape callables. + """ + + def __init__( + self, + root_node: Node, + escape_funcs: Optional[EscapeFuncDict] = None, + unescape_funcs: Optional[EscapeFuncDict] = None, + ) -> None: + + self.root_node = root_node + self.escape_funcs = escape_funcs or {} + self.unescape_funcs = unescape_funcs or {} + + #: Dictionary that will map the regex names to Node instances. + self._group_names_to_nodes: Dict[ + str, str + ] = {} # Maps regex group names to varnames. + counter = [0] + + def create_group_func(node: Variable) -> str: + name = "n%s" % counter[0] + self._group_names_to_nodes[name] = node.varname + counter[0] += 1 + return name + + # Compile regex strings. + self._re_pattern = "^%s$" % self._transform(root_node, create_group_func) + self._re_prefix_patterns = list( + self._transform_prefix(root_node, create_group_func) + ) + + # Compile the regex itself. + flags = re.DOTALL # Note that we don't need re.MULTILINE! (^ and $ + # still represent the start and end of input text.) + self._re = re.compile(self._re_pattern, flags) + self._re_prefix = [re.compile(t, flags) for t in self._re_prefix_patterns] + + # We compile one more set of regexes, similar to `_re_prefix`, but accept any trailing + # input. This will ensure that we can still highlight the input correctly, even when the + # input contains some additional characters at the end that don't match the grammar.) + self._re_prefix_with_trailing_input = [ + re.compile( + r"(?:{})(?P<{}>.*?)$".format(t.rstrip("$"), _INVALID_TRAILING_INPUT), + flags, + ) + for t in self._re_prefix_patterns + ] + + def escape(self, varname: str, value: str) -> str: + """ + Escape `value` to fit in the place of this variable into the grammar. + """ + f = self.escape_funcs.get(varname) + return f(value) if f else value + + def unescape(self, varname: str, value: str) -> str: + """ + Unescape `value`. + """ + f = self.unescape_funcs.get(varname) + return f(value) if f else value + + @classmethod + def _transform( + cls, root_node: Node, create_group_func: Callable[[Variable], str] + ) -> str: + """ + Turn a :class:`Node` object into a regular expression. + + :param root_node: The :class:`Node` instance for which we generate the grammar. + :param create_group_func: A callable which takes a `Node` and returns the next + free name for this node. + """ + + def transform(node: Node) -> str: + # Turn `AnyNode` into an OR. + if isinstance(node, AnyNode): + return "(?:%s)" % "|".join(transform(c) for c in node.children) + + # Concatenate a `NodeSequence` + elif isinstance(node, NodeSequence): + return "".join(transform(c) for c in node.children) + + # For Regex and Lookahead nodes, just insert them literally. + elif isinstance(node, Regex): + return node.regex + + elif isinstance(node, Lookahead): + before = "(?!" if node.negative else "(=" + return before + transform(node.childnode) + ")" + + # A `Variable` wraps the children into a named group. + elif isinstance(node, Variable): + return "(?P<{}>{})".format( + create_group_func(node), + transform(node.childnode), + ) + + # `Repeat`. + elif isinstance(node, Repeat): + if node.max_repeat is None: + if node.min_repeat == 0: + repeat_sign = "*" + elif node.min_repeat == 1: + repeat_sign = "+" + else: + repeat_sign = "{%i,%s}" % ( + node.min_repeat, + ("" if node.max_repeat is None else str(node.max_repeat)), + ) + + return "(?:{}){}{}".format( + transform(node.childnode), + repeat_sign, + ("" if node.greedy else "?"), + ) + else: + raise TypeError(f"Got {node!r}") + + return transform(root_node) + + @classmethod + def _transform_prefix( + cls, root_node: Node, create_group_func: Callable[[Variable], str] + ) -> Iterable[str]: + """ + Yield all the regular expressions matching a prefix of the grammar + defined by the `Node` instance. + + For each `Variable`, one regex pattern will be generated, with this + named group at the end. This is required because a regex engine will + terminate once a match is found. For autocompletion however, we need + the matches for all possible paths, so that we can provide completions + for each `Variable`. + + - So, in the case of an `Any` (`A|B|C)', we generate a pattern for each + clause. This is one for `A`, one for `B` and one for `C`. Unless some + groups don't contain a `Variable`, then these can be merged together. + - In the case of a `NodeSequence` (`ABC`), we generate a pattern for + each prefix that ends with a variable, and one pattern for the whole + sequence. So, that's one for `A`, one for `AB` and one for `ABC`. + + :param root_node: The :class:`Node` instance for which we generate the grammar. + :param create_group_func: A callable which takes a `Node` and returns the next + free name for this node. + """ + + def contains_variable(node: Node) -> bool: + if isinstance(node, Regex): + return False + elif isinstance(node, Variable): + return True + elif isinstance(node, (Lookahead, Repeat)): + return contains_variable(node.childnode) + elif isinstance(node, (NodeSequence, AnyNode)): + return any(contains_variable(child) for child in node.children) + + return False + + def transform(node: Node) -> Iterable[str]: + # Generate separate pattern for all terms that contain variables + # within this OR. Terms that don't contain a variable can be merged + # together in one pattern. + if isinstance(node, AnyNode): + # If we have a definition like: + # (?P .*) | (?P .*) + # Then we want to be able to generate completions for both the + # name as well as the city. We do this by yielding two + # different regular expressions, because the engine won't + # follow multiple paths, if multiple are possible. + children_with_variable = [] + children_without_variable = [] + for c in node.children: + if contains_variable(c): + children_with_variable.append(c) + else: + children_without_variable.append(c) + + for c in children_with_variable: + yield from transform(c) + + # Merge options without variable together. + if children_without_variable: + yield "|".join( + r for c in children_without_variable for r in transform(c) + ) + + # For a sequence, generate a pattern for each prefix that ends with + # a variable + one pattern of the complete sequence. + # (This is because, for autocompletion, we match the text before + # the cursor, and completions are given for the variable that we + # match right before the cursor.) + elif isinstance(node, NodeSequence): + # For all components in the sequence, compute prefix patterns, + # as well as full patterns. + complete = [cls._transform(c, create_group_func) for c in node.children] + prefixes = [list(transform(c)) for c in node.children] + variable_nodes = [contains_variable(c) for c in node.children] + + # If any child is contains a variable, we should yield a + # pattern up to that point, so that we are sure this will be + # matched. + for i in range(len(node.children)): + if variable_nodes[i]: + for c_str in prefixes[i]: + yield "".join(complete[:i]) + c_str + + # If there are non-variable nodes, merge all the prefixes into + # one pattern. If the input is: "[part1] [part2] [part3]", then + # this gets compiled into: + # (complete1 + (complete2 + (complete3 | partial3) | partial2) | partial1 ) + # For nodes that contain a variable, we skip the "|partial" + # part here, because thees are matched with the previous + # patterns. + if not all(variable_nodes): + result = [] + + # Start with complete patterns. + for i in range(len(node.children)): + result.append("(?:") + result.append(complete[i]) + + # Add prefix patterns. + for i in range(len(node.children) - 1, -1, -1): + if variable_nodes[i]: + # No need to yield a prefix for this one, we did + # the variable prefixes earlier. + result.append(")") + else: + result.append("|(?:") + # If this yields multiple, we should yield all combinations. + assert len(prefixes[i]) == 1 + result.append(prefixes[i][0]) + result.append("))") + + yield "".join(result) + + elif isinstance(node, Regex): + yield "(?:%s)?" % node.regex + + elif isinstance(node, Lookahead): + if node.negative: + yield "(?!%s)" % cls._transform(node.childnode, create_group_func) + else: + # Not sure what the correct semantics are in this case. + # (Probably it's not worth implementing this.) + raise Exception("Positive lookahead not yet supported.") + + elif isinstance(node, Variable): + # (Note that we should not append a '?' here. the 'transform' + # method will already recursively do that.) + for c_str in transform(node.childnode): + yield f"(?P<{create_group_func(node)}>{c_str})" + + elif isinstance(node, Repeat): + # If we have a repetition of 8 times. That would mean that the + # current input could have for instance 7 times a complete + # match, followed by a partial match. + prefix = cls._transform(node.childnode, create_group_func) + + if node.max_repeat == 1: + yield from transform(node.childnode) + else: + for c_str in transform(node.childnode): + if node.max_repeat: + repeat_sign = "{,%i}" % (node.max_repeat - 1) + else: + repeat_sign = "*" + yield "(?:{}){}{}{}".format( + prefix, + repeat_sign, + ("" if node.greedy else "?"), + c_str, + ) + + else: + raise TypeError("Got %r" % node) + + for r in transform(root_node): + yield "^(?:%s)$" % r + + def match(self, string: str) -> Optional["Match"]: + """ + Match the string with the grammar. + Returns a :class:`Match` instance or `None` when the input doesn't match the grammar. + + :param string: The input string. + """ + m = self._re.match(string) + + if m: + return Match( + string, [(self._re, m)], self._group_names_to_nodes, self.unescape_funcs + ) + return None + + def match_prefix(self, string: str) -> Optional["Match"]: + """ + Do a partial match of the string with the grammar. The returned + :class:`Match` instance can contain multiple representations of the + match. This will never return `None`. If it doesn't match at all, the "trailing input" + part will capture all of the input. + + :param string: The input string. + """ + # First try to match using `_re_prefix`. If nothing is found, use the patterns that + # also accept trailing characters. + for patterns in [self._re_prefix, self._re_prefix_with_trailing_input]: + matches = [(r, r.match(string)) for r in patterns] + matches2 = [(r, m) for r, m in matches if m] + + if matches2 != []: + return Match( + string, matches2, self._group_names_to_nodes, self.unescape_funcs + ) + + return None + + +class Match: + """ + :param string: The input string. + :param re_matches: List of (compiled_re_pattern, re_match) tuples. + :param group_names_to_nodes: Dictionary mapping all the re group names to the matching Node instances. + """ + + def __init__( + self, + string: str, + re_matches: List[Tuple[Pattern[str], RegexMatch[str]]], + group_names_to_nodes: Dict[str, str], + unescape_funcs: Dict[str, Callable[[str], str]], + ): + self.string = string + self._re_matches = re_matches + self._group_names_to_nodes = group_names_to_nodes + self._unescape_funcs = unescape_funcs + + def _nodes_to_regs(self) -> List[Tuple[str, Tuple[int, int]]]: + """ + Return a list of (varname, reg) tuples. + """ + + def get_tuples() -> Iterable[Tuple[str, Tuple[int, int]]]: + for r, re_match in self._re_matches: + for group_name, group_index in r.groupindex.items(): + if group_name != _INVALID_TRAILING_INPUT: + regs = re_match.regs + reg = regs[group_index] + node = self._group_names_to_nodes[group_name] + yield (node, reg) + + return list(get_tuples()) + + def _nodes_to_values(self) -> List[Tuple[str, str, Tuple[int, int]]]: + """ + Returns list of (Node, string_value) tuples. + """ + + def is_none(sl: Tuple[int, int]) -> bool: + return sl[0] == -1 and sl[1] == -1 + + def get(sl: Tuple[int, int]) -> str: + return self.string[sl[0] : sl[1]] + + return [ + (varname, get(slice), slice) + for varname, slice in self._nodes_to_regs() + if not is_none(slice) + ] + + def _unescape(self, varname: str, value: str) -> str: + unwrapper = self._unescape_funcs.get(varname) + return unwrapper(value) if unwrapper else value + + def variables(self) -> "Variables": + """ + Returns :class:`Variables` instance. + """ + return Variables( + [(k, self._unescape(k, v), sl) for k, v, sl in self._nodes_to_values()] + ) + + def trailing_input(self) -> Optional["MatchVariable"]: + """ + Get the `MatchVariable` instance, representing trailing input, if there is any. + "Trailing input" is input at the end that does not match the grammar anymore, but + when this is removed from the end of the input, the input would be a valid string. + """ + slices: List[Tuple[int, int]] = [] + + # Find all regex group for the name _INVALID_TRAILING_INPUT. + for r, re_match in self._re_matches: + for group_name, group_index in r.groupindex.items(): + if group_name == _INVALID_TRAILING_INPUT: + slices.append(re_match.regs[group_index]) + + # Take the smallest part. (Smaller trailing text means that a larger input has + # been matched, so that is better.) + if slices: + slice = (max(i[0] for i in slices), max(i[1] for i in slices)) + value = self.string[slice[0] : slice[1]] + return MatchVariable("", value, slice) + return None + + def end_nodes(self) -> Iterable["MatchVariable"]: + """ + Yields `MatchVariable` instances for all the nodes having their end + position at the end of the input string. + """ + for varname, reg in self._nodes_to_regs(): + # If this part goes until the end of the input string. + if reg[1] == len(self.string): + value = self._unescape(varname, self.string[reg[0] : reg[1]]) + yield MatchVariable(varname, value, (reg[0], reg[1])) + + +class Variables: + def __init__(self, tuples: List[Tuple[str, str, Tuple[int, int]]]) -> None: + #: List of (varname, value, slice) tuples. + self._tuples = tuples + + def __repr__(self) -> str: + return "{}({})".format( + self.__class__.__name__, + ", ".join(f"{k}={v!r}" for k, v, _ in self._tuples), + ) + + def get(self, key: str, default: Optional[str] = None) -> Optional[str]: + items = self.getall(key) + return items[0] if items else default + + def getall(self, key: str) -> List[str]: + return [v for k, v, _ in self._tuples if k == key] + + def __getitem__(self, key: str) -> Optional[str]: + return self.get(key) + + def __iter__(self) -> Iterator["MatchVariable"]: + """ + Yield `MatchVariable` instances. + """ + for varname, value, slice in self._tuples: + yield MatchVariable(varname, value, slice) + + +class MatchVariable: + """ + Represents a match of a variable in the grammar. + + :param varname: (string) Name of the variable. + :param value: (string) Value of this variable. + :param slice: (start, stop) tuple, indicating the position of this variable + in the input string. + """ + + def __init__(self, varname: str, value: str, slice: Tuple[int, int]) -> None: + self.varname = varname + self.value = value + self.slice = slice + + self.start = self.slice[0] + self.stop = self.slice[1] + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.varname!r}, {self.value!r})" + + +def compile( + expression: str, + escape_funcs: Optional[EscapeFuncDict] = None, + unescape_funcs: Optional[EscapeFuncDict] = None, +) -> _CompiledGrammar: + """ + Compile grammar (given as regex string), returning a `CompiledGrammar` + instance. + """ + return _compile_from_parse_tree( + parse_regex(tokenize_regex(expression)), + escape_funcs=escape_funcs, + unescape_funcs=unescape_funcs, + ) + + +def _compile_from_parse_tree( + root_node: Node, + escape_funcs: Optional[EscapeFuncDict] = None, + unescape_funcs: Optional[EscapeFuncDict] = None, +) -> _CompiledGrammar: + """ + Compile grammar (given as parse tree), returning a `CompiledGrammar` + instance. + """ + return _CompiledGrammar( + root_node, escape_funcs=escape_funcs, unescape_funcs=unescape_funcs + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/completion.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/completion.py new file mode 100644 index 0000000..3cebcc0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/completion.py @@ -0,0 +1,93 @@ +""" +Completer for a regular grammar. +""" +from typing import Dict, Iterable, List + +from prompt_toolkit.completion import CompleteEvent, Completer, Completion +from prompt_toolkit.document import Document + +from .compiler import Match, _CompiledGrammar + +__all__ = [ + "GrammarCompleter", +] + + +class GrammarCompleter(Completer): + """ + Completer which can be used for autocompletion according to variables in + the grammar. Each variable can have a different autocompleter. + + :param compiled_grammar: `GrammarCompleter` instance. + :param completers: `dict` mapping variable names of the grammar to the + `Completer` instances to be used for each variable. + """ + + def __init__( + self, compiled_grammar: _CompiledGrammar, completers: Dict[str, Completer] + ) -> None: + + self.compiled_grammar = compiled_grammar + self.completers = completers + + def get_completions( + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + m = self.compiled_grammar.match_prefix(document.text_before_cursor) + + if m: + completions = self._remove_duplicates( + self._get_completions_for_match(m, complete_event) + ) + + yield from completions + + def _get_completions_for_match( + self, match: Match, complete_event: CompleteEvent + ) -> Iterable[Completion]: + """ + Yield all the possible completions for this input string. + (The completer assumes that the cursor position was at the end of the + input string.) + """ + for match_variable in match.end_nodes(): + varname = match_variable.varname + start = match_variable.start + + completer = self.completers.get(varname) + + if completer: + text = match_variable.value + + # Unwrap text. + unwrapped_text = self.compiled_grammar.unescape(varname, text) + + # Create a document, for the completions API (text/cursor_position) + document = Document(unwrapped_text, len(unwrapped_text)) + + # Call completer + for completion in completer.get_completions(document, complete_event): + new_text = ( + unwrapped_text[: len(text) + completion.start_position] + + completion.text + ) + + # Wrap again. + yield Completion( + text=self.compiled_grammar.escape(varname, new_text), + start_position=start - len(match.string), + display=completion.display, + display_meta=completion.display_meta, + ) + + def _remove_duplicates(self, items: Iterable[Completion]) -> List[Completion]: + """ + Remove duplicates, while keeping the order. + (Sometimes we have duplicates, because the there several matches of the + same grammar, each yielding similar completions.) + """ + result: List[Completion] = [] + for i in items: + if i not in result: + result.append(i) + return result diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/lexer.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/lexer.py new file mode 100644 index 0000000..1ddeede --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/lexer.py @@ -0,0 +1,92 @@ +""" +`GrammarLexer` is compatible with other lexers and can be used to highlight +the input using a regular grammar with annotations. +""" +from typing import Callable, Dict, Optional + +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text.base import StyleAndTextTuples +from prompt_toolkit.formatted_text.utils import split_lines +from prompt_toolkit.lexers import Lexer + +from .compiler import _CompiledGrammar + +__all__ = [ + "GrammarLexer", +] + + +class GrammarLexer(Lexer): + """ + Lexer which can be used for highlighting of fragments according to variables in the grammar. + + (It does not actual lexing of the string, but it exposes an API, compatible + with the Pygments lexer class.) + + :param compiled_grammar: Grammar as returned by the `compile()` function. + :param lexers: Dictionary mapping variable names of the regular grammar to + the lexers that should be used for this part. (This can + call other lexers recursively.) If you wish a part of the + grammar to just get one fragment, use a + `prompt_toolkit.lexers.SimpleLexer`. + """ + + def __init__( + self, + compiled_grammar: _CompiledGrammar, + default_style: str = "", + lexers: Optional[Dict[str, Lexer]] = None, + ) -> None: + + self.compiled_grammar = compiled_grammar + self.default_style = default_style + self.lexers = lexers or {} + + def _get_text_fragments(self, text: str) -> StyleAndTextTuples: + m = self.compiled_grammar.match_prefix(text) + + if m: + characters: StyleAndTextTuples = [(self.default_style, c) for c in text] + + for v in m.variables(): + # If we have a `Lexer` instance for this part of the input. + # Tokenize recursively and apply tokens. + lexer = self.lexers.get(v.varname) + + if lexer: + document = Document(text[v.start : v.stop]) + lexer_tokens_for_line = lexer.lex_document(document) + text_fragments: StyleAndTextTuples = [] + for i in range(len(document.lines)): + text_fragments.extend(lexer_tokens_for_line(i)) + text_fragments.append(("", "\n")) + if text_fragments: + text_fragments.pop() + + i = v.start + for t, s, *_ in text_fragments: + for c in s: + if characters[i][0] == self.default_style: + characters[i] = (t, characters[i][1]) + i += 1 + + # Highlight trailing input. + trailing_input = m.trailing_input() + if trailing_input: + for i in range(trailing_input.start, trailing_input.stop): + characters[i] = ("class:trailing-input", characters[i][1]) + + return characters + else: + return [("", text)] + + def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: + lines = list(split_lines(self._get_text_fragments(document.text))) + + def get_line(lineno: int) -> StyleAndTextTuples: + try: + return lines[lineno] + except IndexError: + return [] + + return get_line diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/regex_parser.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/regex_parser.py new file mode 100644 index 0000000..87e39d4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/regex_parser.py @@ -0,0 +1,281 @@ +""" +Parser for parsing a regular expression. +Take a string representing a regular expression and return the root node of its +parse tree. + +usage:: + + root_node = parse_regex('(hello|world)') + +Remarks: +- The regex parser processes multiline, it ignores all whitespace and supports + multiple named groups with the same name and #-style comments. + +Limitations: +- Lookahead is not supported. +""" +import re +from typing import List, Optional + +__all__ = [ + "Repeat", + "Variable", + "Regex", + "Lookahead", + "tokenize_regex", + "parse_regex", +] + + +class Node: + """ + Base class for all the grammar nodes. + (You don't initialize this one.) + """ + + def __add__(self, other_node: "Node") -> "NodeSequence": + return NodeSequence([self, other_node]) + + def __or__(self, other_node: "Node") -> "AnyNode": + return AnyNode([self, other_node]) + + +class AnyNode(Node): + """ + Union operation (OR operation) between several grammars. You don't + initialize this yourself, but it's a result of a "Grammar1 | Grammar2" + operation. + """ + + def __init__(self, children: List[Node]) -> None: + self.children = children + + def __or__(self, other_node: Node) -> "AnyNode": + return AnyNode(self.children + [other_node]) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.children!r})" + + +class NodeSequence(Node): + """ + Concatenation operation of several grammars. You don't initialize this + yourself, but it's a result of a "Grammar1 + Grammar2" operation. + """ + + def __init__(self, children: List[Node]) -> None: + self.children = children + + def __add__(self, other_node: Node) -> "NodeSequence": + return NodeSequence(self.children + [other_node]) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.children!r})" + + +class Regex(Node): + """ + Regular expression. + """ + + def __init__(self, regex: str) -> None: + re.compile(regex) # Validate + + self.regex = regex + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(/{self.regex}/)" + + +class Lookahead(Node): + """ + Lookahead expression. + """ + + def __init__(self, childnode: Node, negative: bool = False) -> None: + self.childnode = childnode + self.negative = negative + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.childnode!r})" + + +class Variable(Node): + """ + Mark a variable in the regular grammar. This will be translated into a + named group. Each variable can have his own completer, validator, etc.. + + :param childnode: The grammar which is wrapped inside this variable. + :param varname: String. + """ + + def __init__(self, childnode: Node, varname: str = "") -> None: + self.childnode = childnode + self.varname = varname + + def __repr__(self) -> str: + return "{}(childnode={!r}, varname={!r})".format( + self.__class__.__name__, + self.childnode, + self.varname, + ) + + +class Repeat(Node): + def __init__( + self, + childnode: Node, + min_repeat: int = 0, + max_repeat: Optional[int] = None, + greedy: bool = True, + ) -> None: + self.childnode = childnode + self.min_repeat = min_repeat + self.max_repeat = max_repeat + self.greedy = greedy + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(childnode={self.childnode!r})" + + +def tokenize_regex(input: str) -> List[str]: + """ + Takes a string, representing a regular expression as input, and tokenizes + it. + + :param input: string, representing a regular expression. + :returns: List of tokens. + """ + # Regular expression for tokenizing other regular expressions. + p = re.compile( + r"""^( + \(\?P\<[a-zA-Z0-9_-]+\> | # Start of named group. + \(\?#[^)]*\) | # Comment + \(\?= | # Start of lookahead assertion + \(\?! | # Start of negative lookahead assertion + \(\?<= | # If preceded by. + \(\?< | # If not preceded by. + \(?: | # Start of group. (non capturing.) + \( | # Start of group. + \(?[iLmsux] | # Flags. + \(?P=[a-zA-Z]+\) | # Back reference to named group + \) | # End of group. + \{[^{}]*\} | # Repetition + \*\? | \+\? | \?\?\ | # Non greedy repetition. + \* | \+ | \? | # Repetition + \#.*\n | # Comment + \\. | + + # Character group. + \[ + ( [^\]\\] | \\.)* + \] | + + [^(){}] | + . + )""", + re.VERBOSE, + ) + + tokens = [] + + while input: + m = p.match(input) + if m: + token, input = input[: m.end()], input[m.end() :] + if not token.isspace(): + tokens.append(token) + else: + raise Exception("Could not tokenize input regex.") + + return tokens + + +def parse_regex(regex_tokens: List[str]) -> Node: + """ + Takes a list of tokens from the tokenizer, and returns a parse tree. + """ + # We add a closing brace because that represents the final pop of the stack. + tokens: List[str] = [")"] + regex_tokens[::-1] + + def wrap(lst: List[Node]) -> Node: + """Turn list into sequence when it contains several items.""" + if len(lst) == 1: + return lst[0] + else: + return NodeSequence(lst) + + def _parse() -> Node: + or_list: List[List[Node]] = [] + result: List[Node] = [] + + def wrapped_result() -> Node: + if or_list == []: + return wrap(result) + else: + or_list.append(result) + return AnyNode([wrap(i) for i in or_list]) + + while tokens: + t = tokens.pop() + + if t.startswith("(?P<"): + variable = Variable(_parse(), varname=t[4:-1]) + result.append(variable) + + elif t in ("*", "*?"): + greedy = t == "*" + result[-1] = Repeat(result[-1], greedy=greedy) + + elif t in ("+", "+?"): + greedy = t == "+" + result[-1] = Repeat(result[-1], min_repeat=1, greedy=greedy) + + elif t in ("?", "??"): + if result == []: + raise Exception("Nothing to repeat." + repr(tokens)) + else: + greedy = t == "?" + result[-1] = Repeat( + result[-1], min_repeat=0, max_repeat=1, greedy=greedy + ) + + elif t == "|": + or_list.append(result) + result = [] + + elif t in ("(", "(?:"): + result.append(_parse()) + + elif t == "(?!": + result.append(Lookahead(_parse(), negative=True)) + + elif t == "(?=": + result.append(Lookahead(_parse(), negative=False)) + + elif t == ")": + return wrapped_result() + + elif t.startswith("#"): + pass + + elif t.startswith("{"): + # TODO: implement! + raise Exception(f"{t}-style repetition not yet supported") + + elif t.startswith("(?"): + raise Exception("%r not supported" % t) + + elif t.isspace(): + pass + else: + result.append(Regex(t)) + + raise Exception("Expecting ')' token") + + result = _parse() + + if len(tokens) != 0: + raise Exception("Unmatched parentheses.") + else: + return result diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/validation.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/validation.py new file mode 100644 index 0000000..71d3434 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/regular_languages/validation.py @@ -0,0 +1,60 @@ +""" +Validator for a regular language. +""" +from typing import Dict + +from prompt_toolkit.document import Document +from prompt_toolkit.validation import ValidationError, Validator + +from .compiler import _CompiledGrammar + +__all__ = [ + "GrammarValidator", +] + + +class GrammarValidator(Validator): + """ + Validator which can be used for validation according to variables in + the grammar. Each variable can have its own validator. + + :param compiled_grammar: `GrammarCompleter` instance. + :param validators: `dict` mapping variable names of the grammar to the + `Validator` instances to be used for each variable. + """ + + def __init__( + self, compiled_grammar: _CompiledGrammar, validators: Dict[str, Validator] + ) -> None: + + self.compiled_grammar = compiled_grammar + self.validators = validators + + def validate(self, document: Document) -> None: + # Parse input document. + # We use `match`, not `match_prefix`, because for validation, we want + # the actual, unambiguous interpretation of the input. + m = self.compiled_grammar.match(document.text) + + if m: + for v in m.variables(): + validator = self.validators.get(v.varname) + + if validator: + # Unescape text. + unwrapped_text = self.compiled_grammar.unescape(v.varname, v.value) + + # Create a document, for the completions API (text/cursor_position) + inner_document = Document(unwrapped_text, len(unwrapped_text)) + + try: + validator.validate(inner_document) + except ValidationError as e: + raise ValidationError( + cursor_position=v.start + e.cursor_position, + message=e.message, + ) from e + else: + raise ValidationError( + cursor_position=len(document.text), message="Invalid command" + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__init__.py new file mode 100644 index 0000000..a895ca2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__init__.py @@ -0,0 +1,6 @@ +from .server import PromptToolkitSSHServer, PromptToolkitSSHSession + +__all__ = [ + "PromptToolkitSSHSession", + "PromptToolkitSSHServer", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b97b395 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__pycache__/server.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__pycache__/server.cpython-38.pyc new file mode 100644 index 0000000..a0a4da2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/__pycache__/server.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/server.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/server.py new file mode 100644 index 0000000..ba11036 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/ssh/server.py @@ -0,0 +1,156 @@ +""" +Utility for running a prompt_toolkit application in an asyncssh server. +""" +import asyncio +import traceback +from typing import Any, Awaitable, Callable, Optional, TextIO, cast + +import asyncssh + +from prompt_toolkit.application.current import AppSession, create_app_session +from prompt_toolkit.data_structures import Size +from prompt_toolkit.eventloop import get_event_loop +from prompt_toolkit.input import create_pipe_input +from prompt_toolkit.output.vt100 import Vt100_Output + +__all__ = ["PromptToolkitSSHSession", "PromptToolkitSSHServer"] + + +class PromptToolkitSSHSession(asyncssh.SSHServerSession): # type: ignore + def __init__( + self, interact: Callable[["PromptToolkitSSHSession"], Awaitable[None]] + ) -> None: + self.interact = interact + self.interact_task: Optional[asyncio.Task[None]] = None + self._chan: Optional[Any] = None + self.app_session: Optional[AppSession] = None + + # PipInput object, for sending input in the CLI. + # (This is something that we can use in the prompt_toolkit event loop, + # but still write date in manually.) + self._input = create_pipe_input() + self._output: Optional[Vt100_Output] = None + + # Output object. Don't render to the real stdout, but write everything + # in the SSH channel. + class Stdout: + def write(s, data: str) -> None: + try: + if self._chan is not None: + self._chan.write(data.replace("\n", "\r\n")) + except BrokenPipeError: + pass # Channel not open for sending. + + def isatty(s) -> bool: + return True + + def flush(s) -> None: + pass + + @property + def encoding(s) -> str: + assert self._chan is not None + return str(self._chan._orig_chan.get_encoding()[0]) + + self.stdout = cast(TextIO, Stdout()) + + def _get_size(self) -> Size: + """ + Callable that returns the current `Size`, required by Vt100_Output. + """ + if self._chan is None: + return Size(rows=20, columns=79) + else: + width, height, pixwidth, pixheight = self._chan.get_terminal_size() + return Size(rows=height, columns=width) + + def connection_made(self, chan: Any) -> None: + self._chan = chan + + def shell_requested(self) -> bool: + return True + + def session_started(self) -> None: + self.interact_task = get_event_loop().create_task(self._interact()) + + async def _interact(self) -> None: + if self._chan is None: + # Should not happen. + raise Exception("`_interact` called before `connection_made`.") + + if hasattr(self._chan, "set_line_mode") and self._chan._editor is not None: + # Disable the line editing provided by asyncssh. Prompt_toolkit + # provides the line editing. + self._chan.set_line_mode(False) + + term = self._chan.get_terminal_type() + + self._output = Vt100_Output( + self.stdout, self._get_size, term=term, write_binary=False + ) + with create_app_session(input=self._input, output=self._output) as session: + self.app_session = session + try: + await self.interact(self) + except BaseException: + traceback.print_exc() + finally: + # Close the connection. + self._chan.close() + self._input.close() + + def terminal_size_changed( + self, width: int, height: int, pixwidth: object, pixheight: object + ) -> None: + # Send resize event to the current application. + if self.app_session and self.app_session.app: + self.app_session.app._on_resize() + + def data_received(self, data: str, datatype: object) -> None: + self._input.send_text(data) + + +class PromptToolkitSSHServer(asyncssh.SSHServer): # type: ignore + """ + Run a prompt_toolkit application over an asyncssh server. + + This takes one argument, an `interact` function, which is called for each + connection. This should be an asynchronous function that runs the + prompt_toolkit applications. This function runs in an `AppSession`, which + means that we can have multiple UI interactions concurrently. + + Example usage: + + .. code:: python + + async def interact(ssh_session: PromptToolkitSSHSession) -> None: + await yes_no_dialog("my title", "my text").run_async() + + prompt_session = PromptSession() + text = await prompt_session.prompt_async("Type something: ") + print_formatted_text('You said: ', text) + + server = PromptToolkitSSHServer(interact=interact) + loop = get_event_loop() + loop.run_until_complete( + asyncssh.create_server( + lambda: MySSHServer(interact), + "", + port, + server_host_keys=["/etc/ssh/..."], + ) + ) + loop.run_forever() + """ + + def __init__( + self, interact: Callable[[PromptToolkitSSHSession], Awaitable[None]] + ) -> None: + self.interact = interact + + def begin_auth(self, username: str) -> bool: + # No authentication. + return False + + def session_requested(self) -> PromptToolkitSSHSession: + return PromptToolkitSSHSession(self.interact) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__init__.py new file mode 100644 index 0000000..b29f7d2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__init__.py @@ -0,0 +1,5 @@ +from .server import TelnetServer + +__all__ = [ + "TelnetServer", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..8e8dd84 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/log.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/log.cpython-38.pyc new file mode 100644 index 0000000..e1a29dd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/log.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/protocol.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/protocol.cpython-38.pyc new file mode 100644 index 0000000..698e81b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/protocol.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/server.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/server.cpython-38.pyc new file mode 100644 index 0000000..b3c6941 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/__pycache__/server.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/log.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/log.py new file mode 100644 index 0000000..24487ab --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/log.py @@ -0,0 +1,10 @@ +""" +Python logger for the telnet server. +""" +import logging + +logger = logging.getLogger(__package__) + +__all__ = [ + "logger", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/protocol.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/protocol.py new file mode 100644 index 0000000..68f3639 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/protocol.py @@ -0,0 +1,207 @@ +""" +Parser for the Telnet protocol. (Not a complete implementation of the telnet +specification, but sufficient for a command line interface.) + +Inspired by `Twisted.conch.telnet`. +""" +import struct +from typing import Callable, Generator + +from .log import logger + +__all__ = [ + "TelnetProtocolParser", +] + + +def int2byte(number: int) -> bytes: + return bytes((number,)) + + +# Telnet constants. +NOP = int2byte(0) +SGA = int2byte(3) + +IAC = int2byte(255) +DO = int2byte(253) +DONT = int2byte(254) +LINEMODE = int2byte(34) +SB = int2byte(250) +WILL = int2byte(251) +WONT = int2byte(252) +MODE = int2byte(1) +SE = int2byte(240) +ECHO = int2byte(1) +NAWS = int2byte(31) +LINEMODE = int2byte(34) +SUPPRESS_GO_AHEAD = int2byte(3) + +TTYPE = int2byte(24) +SEND = int2byte(1) +IS = int2byte(0) + +DM = int2byte(242) +BRK = int2byte(243) +IP = int2byte(244) +AO = int2byte(245) +AYT = int2byte(246) +EC = int2byte(247) +EL = int2byte(248) +GA = int2byte(249) + + +class TelnetProtocolParser: + """ + Parser for the Telnet protocol. + Usage:: + + def data_received(data): + print(data) + + def size_received(rows, columns): + print(rows, columns) + + p = TelnetProtocolParser(data_received, size_received) + p.feed(binary_data) + """ + + def __init__( + self, + data_received_callback: Callable[[bytes], None], + size_received_callback: Callable[[int, int], None], + ttype_received_callback: Callable[[str], None], + ) -> None: + + self.data_received_callback = data_received_callback + self.size_received_callback = size_received_callback + self.ttype_received_callback = ttype_received_callback + + self._parser = self._parse_coroutine() + self._parser.send(None) # type: ignore + + def received_data(self, data: bytes) -> None: + self.data_received_callback(data) + + def do_received(self, data: bytes) -> None: + """Received telnet DO command.""" + logger.info("DO %r", data) + + def dont_received(self, data: bytes) -> None: + """Received telnet DONT command.""" + logger.info("DONT %r", data) + + def will_received(self, data: bytes) -> None: + """Received telnet WILL command.""" + logger.info("WILL %r", data) + + def wont_received(self, data: bytes) -> None: + """Received telnet WONT command.""" + logger.info("WONT %r", data) + + def command_received(self, command: bytes, data: bytes) -> None: + if command == DO: + self.do_received(data) + + elif command == DONT: + self.dont_received(data) + + elif command == WILL: + self.will_received(data) + + elif command == WONT: + self.wont_received(data) + + else: + logger.info("command received %r %r", command, data) + + def naws(self, data: bytes) -> None: + """ + Received NAWS. (Window dimensions.) + """ + if len(data) == 4: + # NOTE: the first parameter of struct.unpack should be + # a 'str' object. Both on Py2/py3. This crashes on OSX + # otherwise. + columns, rows = struct.unpack("!HH", data) + self.size_received_callback(rows, columns) + else: + logger.warning("Wrong number of NAWS bytes") + + def ttype(self, data: bytes) -> None: + """ + Received terminal type. + """ + subcmd, data = data[0:1], data[1:] + if subcmd == IS: + ttype = data.decode("ascii") + self.ttype_received_callback(ttype) + else: + logger.warning("Received a non-IS terminal type Subnegotiation") + + def negotiate(self, data: bytes) -> None: + """ + Got negotiate data. + """ + command, payload = data[0:1], data[1:] + + if command == NAWS: + self.naws(payload) + elif command == TTYPE: + self.ttype(payload) + else: + logger.info("Negotiate (%r got bytes)", len(data)) + + def _parse_coroutine(self) -> Generator[None, bytes, None]: + """ + Parser state machine. + Every 'yield' expression returns the next byte. + """ + while True: + d = yield + + if d == int2byte(0): + pass # NOP + + # Go to state escaped. + elif d == IAC: + d2 = yield + + if d2 == IAC: + self.received_data(d2) + + # Handle simple commands. + elif d2 in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA): + self.command_received(d2, b"") + + # Handle IAC-[DO/DONT/WILL/WONT] commands. + elif d2 in (DO, DONT, WILL, WONT): + d3 = yield + self.command_received(d2, d3) + + # Subnegotiation + elif d2 == SB: + # Consume everything until next IAC-SE + data = [] + + while True: + d3 = yield + + if d3 == IAC: + d4 = yield + if d4 == SE: + break + else: + data.append(d4) + else: + data.append(d3) + + self.negotiate(b"".join(data)) + else: + self.received_data(d) + + def feed(self, data: bytes) -> None: + """ + Feed data to the parser. + """ + for b in data: + self._parser.send(int2byte(b)) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/server.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/server.py new file mode 100644 index 0000000..2e042c9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/contrib/telnet/server.py @@ -0,0 +1,357 @@ +""" +Telnet server. +""" +import asyncio +import socket +import sys +from typing import Any, Awaitable, Callable, List, Optional, Set, TextIO, Tuple, cast + +from prompt_toolkit.application.current import create_app_session, get_app +from prompt_toolkit.application.run_in_terminal import run_in_terminal +from prompt_toolkit.data_structures import Size +from prompt_toolkit.eventloop import get_event_loop +from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text +from prompt_toolkit.input import create_pipe_input +from prompt_toolkit.output.vt100 import Vt100_Output +from prompt_toolkit.renderer import print_formatted_text as print_formatted_text +from prompt_toolkit.styles import BaseStyle, DummyStyle + +from .log import logger +from .protocol import ( + DO, + ECHO, + IAC, + LINEMODE, + MODE, + NAWS, + SB, + SE, + SEND, + SUPPRESS_GO_AHEAD, + TTYPE, + WILL, + TelnetProtocolParser, +) + +if sys.version_info >= (3, 7): + import contextvars # Requires Python3.7! +else: + contextvars: Any = None + +__all__ = [ + "TelnetServer", +] + + +def int2byte(number: int) -> bytes: + return bytes((number,)) + + +def _initialize_telnet(connection: socket.socket) -> None: + logger.info("Initializing telnet connection") + + # Iac Do Linemode + connection.send(IAC + DO + LINEMODE) + + # Suppress Go Ahead. (This seems important for Putty to do correct echoing.) + # This will allow bi-directional operation. + connection.send(IAC + WILL + SUPPRESS_GO_AHEAD) + + # Iac sb + connection.send(IAC + SB + LINEMODE + MODE + int2byte(0) + IAC + SE) + + # IAC Will Echo + connection.send(IAC + WILL + ECHO) + + # Negotiate window size + connection.send(IAC + DO + NAWS) + + # Negotiate terminal type + # Assume the client will accept the negociation with `IAC + WILL + TTYPE` + connection.send(IAC + DO + TTYPE) + + # We can then select the first terminal type supported by the client, + # which is generally the best type the client supports + # The client should reply with a `IAC + SB + TTYPE + IS + ttype + IAC + SE` + connection.send(IAC + SB + TTYPE + SEND + IAC + SE) + + +class _ConnectionStdout: + """ + Wrapper around socket which provides `write` and `flush` methods for the + Vt100_Output output. + """ + + def __init__(self, connection: socket.socket, encoding: str) -> None: + self._encoding = encoding + self._connection = connection + self._errors = "strict" + self._buffer: List[bytes] = [] + + def write(self, data: str) -> None: + data = data.replace("\n", "\r\n") + self._buffer.append(data.encode(self._encoding, errors=self._errors)) + self.flush() + + def isatty(self) -> bool: + return True + + def flush(self) -> None: + try: + self._connection.send(b"".join(self._buffer)) + except OSError as e: + logger.warning("Couldn't send data over socket: %s" % e) + + self._buffer = [] + + @property + def encoding(self) -> str: + return self._encoding + + @property + def errors(self) -> str: + return self._errors + + +class TelnetConnection: + """ + Class that represents one Telnet connection. + """ + + def __init__( + self, + conn: socket.socket, + addr: Tuple[str, int], + interact: Callable[["TelnetConnection"], Awaitable[None]], + server: "TelnetServer", + encoding: str, + style: Optional[BaseStyle], + ) -> None: + + self.conn = conn + self.addr = addr + self.interact = interact + self.server = server + self.encoding = encoding + self.style = style + self._closed = False + self._ready = asyncio.Event() + self.vt100_output = None + + # Create "Output" object. + self.size = Size(rows=40, columns=79) + + # Initialize. + _initialize_telnet(conn) + + # Create input. + self.vt100_input = create_pipe_input() + + # Create output. + def get_size() -> Size: + return self.size + + self.stdout = cast(TextIO, _ConnectionStdout(conn, encoding=encoding)) + + def data_received(data: bytes) -> None: + """TelnetProtocolParser 'data_received' callback""" + self.vt100_input.send_bytes(data) + + def size_received(rows: int, columns: int) -> None: + """TelnetProtocolParser 'size_received' callback""" + self.size = Size(rows=rows, columns=columns) + if self.vt100_output is not None: + get_app()._on_resize() + + def ttype_received(ttype: str) -> None: + """TelnetProtocolParser 'ttype_received' callback""" + self.vt100_output = Vt100_Output( + self.stdout, get_size, term=ttype, write_binary=False + ) + self._ready.set() + + self.parser = TelnetProtocolParser(data_received, size_received, ttype_received) + self.context: Optional[contextvars.Context] = None + + async def run_application(self) -> None: + """ + Run application. + """ + + def handle_incoming_data() -> None: + data = self.conn.recv(1024) + if data: + self.feed(data) + else: + # Connection closed by client. + logger.info("Connection closed by client. %r %r" % self.addr) + self.close() + + # Add reader. + loop = get_event_loop() + loop.add_reader(self.conn, handle_incoming_data) + + try: + # Wait for v100_output to be properly instantiated + await self._ready.wait() + with create_app_session(input=self.vt100_input, output=self.vt100_output): + self.context = contextvars.copy_context() + await self.interact(self) + except Exception as e: + print("Got %s" % type(e).__name__, e) + import traceback + + traceback.print_exc() + raise + finally: + self.close() + + def feed(self, data: bytes) -> None: + """ + Handler for incoming data. (Called by TelnetServer.) + """ + self.parser.feed(data) + + def close(self) -> None: + """ + Closed by client. + """ + if not self._closed: + self._closed = True + + self.vt100_input.close() + get_event_loop().remove_reader(self.conn) + self.conn.close() + + def send(self, formatted_text: AnyFormattedText) -> None: + """ + Send text to the client. + """ + if self.vt100_output is None: + return + formatted_text = to_formatted_text(formatted_text) + print_formatted_text( + self.vt100_output, formatted_text, self.style or DummyStyle() + ) + + def send_above_prompt(self, formatted_text: AnyFormattedText) -> None: + """ + Send text to the client. + This is asynchronous, returns a `Future`. + """ + formatted_text = to_formatted_text(formatted_text) + return self._run_in_terminal(lambda: self.send(formatted_text)) + + def _run_in_terminal(self, func: Callable[[], None]) -> None: + # Make sure that when an application was active for this connection, + # that we print the text above the application. + if self.context: + self.context.run(run_in_terminal, func) + else: + raise RuntimeError("Called _run_in_terminal outside `run_application`.") + + def erase_screen(self) -> None: + """ + Erase the screen and move the cursor to the top. + """ + if self.vt100_output is None: + return + self.vt100_output.erase_screen() + self.vt100_output.cursor_goto(0, 0) + self.vt100_output.flush() + + +async def _dummy_interact(connection: TelnetConnection) -> None: + pass + + +class TelnetServer: + """ + Telnet server implementation. + """ + + def __init__( + self, + host: str = "127.0.0.1", + port: int = 23, + interact: Callable[[TelnetConnection], Awaitable[None]] = _dummy_interact, + encoding: str = "utf-8", + style: Optional[BaseStyle] = None, + ) -> None: + + self.host = host + self.port = port + self.interact = interact + self.encoding = encoding + self.style = style + self._application_tasks: List[asyncio.Task[None]] = [] + + self.connections: Set[TelnetConnection] = set() + self._listen_socket: Optional[socket.socket] = None + + @classmethod + def _create_socket(cls, host: str, port: int) -> socket.socket: + # Create and bind socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((host, port)) + + s.listen(4) + return s + + def start(self) -> None: + """ + Start the telnet server. + Don't forget to call `loop.run_forever()` after doing this. + """ + self._listen_socket = self._create_socket(self.host, self.port) + logger.info( + "Listening for telnet connections on %s port %r", self.host, self.port + ) + + get_event_loop().add_reader(self._listen_socket, self._accept) + + async def stop(self) -> None: + if self._listen_socket: + get_event_loop().remove_reader(self._listen_socket) + self._listen_socket.close() + + # Wait for all applications to finish. + for t in self._application_tasks: + t.cancel() + + for t in self._application_tasks: + try: + await t + except asyncio.CancelledError: + logger.debug("Task %s cancelled", str(t)) + + def _accept(self) -> None: + """ + Accept new incoming connection. + """ + if self._listen_socket is None: + return # Should not happen. `_accept` is called after `start`. + + conn, addr = self._listen_socket.accept() + logger.info("New connection %r %r", *addr) + + connection = TelnetConnection( + conn, addr, self.interact, self, encoding=self.encoding, style=self.style + ) + self.connections.add(connection) + + # Run application for this connection. + async def run() -> None: + logger.info("Starting interaction %r %r", *addr) + try: + await connection.run_application() + except Exception as e: + print(e) + finally: + self.connections.remove(connection) + self._application_tasks.remove(task) + logger.info("Stopping interaction %r %r", *addr) + + task = get_event_loop().create_task(run()) + self._application_tasks.append(task) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/cursor_shapes.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/cursor_shapes.py new file mode 100644 index 0000000..d38b350 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/cursor_shapes.py @@ -0,0 +1,102 @@ +from abc import ABC, abstractmethod +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, Union + +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.key_binding.vi_state import InputMode + +if TYPE_CHECKING: + from .application import Application + +__all__ = [ + "CursorShape", + "CursorShapeConfig", + "SimpleCursorShapeConfig", + "ModalCursorShapeConfig", + "DynamicCursorShapeConfig", + "to_cursor_shape_config", +] + + +class CursorShape(Enum): + # Default value that should tell the output implementation to never send + # cursor shape escape sequences. This is the default right now, because + # before this `CursorShape` functionality was introduced into + # prompt_toolkit itself, people had workarounds to send cursor shapes + # escapes into the terminal, by monkey patching some of prompt_toolkit's + # internals. We don't want the default prompt_toolkit implemetation to + # interefere with that. E.g., IPython patches the `ViState.input_mode` + # property. See: https://github.com/ipython/ipython/pull/13501/files + _NEVER_CHANGE = "_NEVER_CHANGE" + + BLOCK = "BLOCK" + BEAM = "BEAM" + UNDERLINE = "UNDERLINE" + BLINKING_BLOCK = "BLINKING_BLOCK" + BLINKING_BEAM = "BLINKING_BEAM" + BLINKING_UNDERLINE = "BLINKING_UNDERLINE" + + +class CursorShapeConfig(ABC): + @abstractmethod + def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + """ + Return the cursor shape to be used in the current state. + """ + + +AnyCursorShapeConfig = Union[CursorShape, CursorShapeConfig, None] + + +class SimpleCursorShapeConfig(CursorShapeConfig): + """ + Always show the given cursor shape. + """ + + def __init__(self, cursor_shape: CursorShape = CursorShape._NEVER_CHANGE) -> None: + self.cursor_shape = cursor_shape + + def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + return self.cursor_shape + + +class ModalCursorShapeConfig(CursorShapeConfig): + """ + Show cursor shape according to the current input mode. + """ + + def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + if application.editing_mode == EditingMode.VI: + if application.vi_state.input_mode == InputMode.INSERT: + return CursorShape.BEAM + if application.vi_state.input_mode == InputMode.REPLACE: + return CursorShape.UNDERLINE + + # Default + return CursorShape.BLOCK + + +class DynamicCursorShapeConfig(CursorShapeConfig): + def __init__( + self, get_cursor_shape_config: Callable[[], AnyCursorShapeConfig] + ) -> None: + self.get_cursor_shape_config = get_cursor_shape_config + + def get_cursor_shape(self, application: "Application[Any]") -> CursorShape: + return to_cursor_shape_config(self.get_cursor_shape_config()).get_cursor_shape( + application + ) + + +def to_cursor_shape_config(value: AnyCursorShapeConfig) -> CursorShapeConfig: + """ + Take a `CursorShape` instance or `CursorShapeConfig` and turn it into a + `CursorShapeConfig`. + """ + if value is None: + return SimpleCursorShapeConfig() + + if isinstance(value, CursorShape): + return SimpleCursorShapeConfig(value) + + return value diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/data_structures.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/data_structures.py new file mode 100644 index 0000000..d031acf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/data_structures.py @@ -0,0 +1,16 @@ +from typing import NamedTuple + +__all__ = [ + "Point", + "Size", +] + + +class Point(NamedTuple): + x: int + y: int + + +class Size(NamedTuple): + rows: int + columns: int diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/document.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/document.py new file mode 100644 index 0000000..1984155 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/document.py @@ -0,0 +1,1190 @@ +""" +The `Document` that implements all the text operations/querying. +""" +import bisect +import re +import string +import weakref +from typing import ( + Callable, + Dict, + Iterable, + List, + NoReturn, + Optional, + Pattern, + Tuple, + cast, +) + +from .clipboard import ClipboardData +from .filters import vi_mode +from .selection import PasteMode, SelectionState, SelectionType + +__all__ = [ + "Document", +] + + +# Regex for finding "words" in documents. (We consider a group of alnum +# characters a word, but also a group of special characters a word, as long as +# it doesn't contain a space.) +# (This is a 'word' in Vi.) +_FIND_WORD_RE = re.compile(r"([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)") +_FIND_CURRENT_WORD_RE = re.compile(r"^([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)") +_FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile( + r"^(([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)\s*)" +) + +# Regex for finding "WORDS" in documents. +# (This is a 'WORD in Vi.) +_FIND_BIG_WORD_RE = re.compile(r"([^\s]+)") +_FIND_CURRENT_BIG_WORD_RE = re.compile(r"^([^\s]+)") +_FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r"^([^\s]+\s*)") + +# Share the Document._cache between all Document instances. +# (Document instances are considered immutable. That means that if another +# `Document` is constructed with the same text, it should have the same +# `_DocumentCache`.) +_text_to_document_cache: Dict[str, "_DocumentCache"] = cast( + Dict[str, "_DocumentCache"], + weakref.WeakValueDictionary(), # Maps document.text to DocumentCache instance. +) + + +class _ImmutableLineList(List[str]): + """ + Some protection for our 'lines' list, which is assumed to be immutable in the cache. + (Useful for detecting obvious bugs.) + """ + + def _error(self, *a: object, **kw: object) -> NoReturn: + raise NotImplementedError("Attempt to modify an immutable list.") + + __setitem__ = _error # type: ignore + append = _error + clear = _error + extend = _error + insert = _error + pop = _error + remove = _error + reverse = _error + sort = _error # type: ignore + + +class _DocumentCache: + def __init__(self) -> None: + #: List of lines for the Document text. + self.lines: Optional[_ImmutableLineList] = None + + #: List of index positions, pointing to the start of all the lines. + self.line_indexes: Optional[List[int]] = None + + +class Document: + """ + This is a immutable class around the text and cursor position, and contains + methods for querying this data, e.g. to give the text before the cursor. + + This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer` + object, and accessed as the `document` property of that class. + + :param text: string + :param cursor_position: int + :param selection: :class:`.SelectionState` + """ + + __slots__ = ("_text", "_cursor_position", "_selection", "_cache") + + def __init__( + self, + text: str = "", + cursor_position: Optional[int] = None, + selection: Optional[SelectionState] = None, + ) -> None: + + # Check cursor position. It can also be right after the end. (Where we + # insert text.) + assert cursor_position is None or cursor_position <= len(text), AssertionError( + f"cursor_position={cursor_position!r}, len_text={len(text)!r}" + ) + + # By default, if no cursor position was given, make sure to put the + # cursor position is at the end of the document. This is what makes + # sense in most places. + if cursor_position is None: + cursor_position = len(text) + + # Keep these attributes private. A `Document` really has to be + # considered to be immutable, because otherwise the caching will break + # things. Because of that, we wrap these into read-only properties. + self._text = text + self._cursor_position = cursor_position + self._selection = selection + + # Cache for lines/indexes. (Shared with other Document instances that + # contain the same text. + try: + self._cache = _text_to_document_cache[self.text] + except KeyError: + self._cache = _DocumentCache() + _text_to_document_cache[self.text] = self._cache + + # XX: For some reason, above, we can't use 'WeakValueDictionary.setdefault'. + # This fails in Pypy3. `self._cache` becomes None, because that's what + # 'setdefault' returns. + # self._cache = _text_to_document_cache.setdefault(self.text, _DocumentCache()) + # assert self._cache + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.text!r}, {self.cursor_position!r})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Document): + return False + + return ( + self.text == other.text + and self.cursor_position == other.cursor_position + and self.selection == other.selection + ) + + @property + def text(self) -> str: + "The document text." + return self._text + + @property + def cursor_position(self) -> int: + "The document cursor position." + return self._cursor_position + + @property + def selection(self) -> Optional[SelectionState]: + ":class:`.SelectionState` object." + return self._selection + + @property + def current_char(self) -> str: + """Return character under cursor or an empty string.""" + return self._get_char_relative_to_cursor(0) or "" + + @property + def char_before_cursor(self) -> str: + """Return character before the cursor or an empty string.""" + return self._get_char_relative_to_cursor(-1) or "" + + @property + def text_before_cursor(self) -> str: + return self.text[: self.cursor_position :] + + @property + def text_after_cursor(self) -> str: + return self.text[self.cursor_position :] + + @property + def current_line_before_cursor(self) -> str: + """Text from the start of the line until the cursor.""" + _, _, text = self.text_before_cursor.rpartition("\n") + return text + + @property + def current_line_after_cursor(self) -> str: + """Text from the cursor until the end of the line.""" + text, _, _ = self.text_after_cursor.partition("\n") + return text + + @property + def lines(self) -> List[str]: + """ + Array of all the lines. + """ + # Cache, because this one is reused very often. + if self._cache.lines is None: + self._cache.lines = _ImmutableLineList(self.text.split("\n")) + + return self._cache.lines + + @property + def _line_start_indexes(self) -> List[int]: + """ + Array pointing to the start indexes of all the lines. + """ + # Cache, because this is often reused. (If it is used, it's often used + # many times. And this has to be fast for editing big documents!) + if self._cache.line_indexes is None: + # Create list of line lengths. + line_lengths = map(len, self.lines) + + # Calculate cumulative sums. + indexes = [0] + append = indexes.append + pos = 0 + + for line_length in line_lengths: + pos += line_length + 1 + append(pos) + + # Remove the last item. (This is not a new line.) + if len(indexes) > 1: + indexes.pop() + + self._cache.line_indexes = indexes + + return self._cache.line_indexes + + @property + def lines_from_current(self) -> List[str]: + """ + Array of the lines starting from the current line, until the last line. + """ + return self.lines[self.cursor_position_row :] + + @property + def line_count(self) -> int: + r"""Return the number of lines in this document. If the document ends + with a trailing \n, that counts as the beginning of a new line.""" + return len(self.lines) + + @property + def current_line(self) -> str: + """Return the text on the line where the cursor is. (when the input + consists of just one line, it equals `text`.""" + return self.current_line_before_cursor + self.current_line_after_cursor + + @property + def leading_whitespace_in_current_line(self) -> str: + """The leading whitespace in the left margin of the current line.""" + current_line = self.current_line + length = len(current_line) - len(current_line.lstrip()) + return current_line[:length] + + def _get_char_relative_to_cursor(self, offset: int = 0) -> str: + """ + Return character relative to cursor position, or empty string + """ + try: + return self.text[self.cursor_position + offset] + except IndexError: + return "" + + @property + def on_first_line(self) -> bool: + """ + True when we are at the first line. + """ + return self.cursor_position_row == 0 + + @property + def on_last_line(self) -> bool: + """ + True when we are at the last line. + """ + return self.cursor_position_row == self.line_count - 1 + + @property + def cursor_position_row(self) -> int: + """ + Current row. (0-based.) + """ + row, _ = self._find_line_start_index(self.cursor_position) + return row + + @property + def cursor_position_col(self) -> int: + """ + Current column. (0-based.) + """ + # (Don't use self.text_before_cursor to calculate this. Creating + # substrings and doing rsplit is too expensive for getting the cursor + # position.) + _, line_start_index = self._find_line_start_index(self.cursor_position) + return self.cursor_position - line_start_index + + def _find_line_start_index(self, index: int) -> Tuple[int, int]: + """ + For the index of a character at a certain line, calculate the index of + the first character on that line. + + Return (row, index) tuple. + """ + indexes = self._line_start_indexes + + pos = bisect.bisect_right(indexes, index) - 1 + return pos, indexes[pos] + + def translate_index_to_position(self, index: int) -> Tuple[int, int]: + """ + Given an index for the text, return the corresponding (row, col) tuple. + (0-based. Returns (0, 0) for index=0.) + """ + # Find start of this line. + row, row_index = self._find_line_start_index(index) + col = index - row_index + + return row, col + + def translate_row_col_to_index(self, row: int, col: int) -> int: + """ + Given a (row, col) tuple, return the corresponding index. + (Row and col params are 0-based.) + + Negative row/col values are turned into zero. + """ + try: + result = self._line_start_indexes[row] + line = self.lines[row] + except IndexError: + if row < 0: + result = self._line_start_indexes[0] + line = self.lines[0] + else: + result = self._line_start_indexes[-1] + line = self.lines[-1] + + result += max(0, min(col, len(line))) + + # Keep in range. (len(self.text) is included, because the cursor can be + # right after the end of the text as well.) + result = max(0, min(result, len(self.text))) + return result + + @property + def is_cursor_at_the_end(self) -> bool: + """True when the cursor is at the end of the text.""" + return self.cursor_position == len(self.text) + + @property + def is_cursor_at_the_end_of_line(self) -> bool: + """True when the cursor is at the end of this line.""" + return self.current_char in ("\n", "") + + def has_match_at_current_position(self, sub: str) -> bool: + """ + `True` when this substring is found at the cursor position. + """ + return self.text.find(sub, self.cursor_position) == self.cursor_position + + def find( + self, + sub: str, + in_current_line: bool = False, + include_current_position: bool = False, + ignore_case: bool = False, + count: int = 1, + ) -> Optional[int]: + """ + Find `text` after the cursor, return position relative to the cursor + position. Return `None` if nothing was found. + + :param count: Find the n-th occurrence. + """ + assert isinstance(ignore_case, bool) + + if in_current_line: + text = self.current_line_after_cursor + else: + text = self.text_after_cursor + + if not include_current_position: + if len(text) == 0: + return None # (Otherwise, we always get a match for the empty string.) + else: + text = text[1:] + + flags = re.IGNORECASE if ignore_case else 0 + iterator = re.finditer(re.escape(sub), text, flags) + + try: + for i, match in enumerate(iterator): + if i + 1 == count: + if include_current_position: + return match.start(0) + else: + return match.start(0) + 1 + except StopIteration: + pass + return None + + def find_all(self, sub: str, ignore_case: bool = False) -> List[int]: + """ + Find all occurrences of the substring. Return a list of absolute + positions in the document. + """ + flags = re.IGNORECASE if ignore_case else 0 + return [a.start() for a in re.finditer(re.escape(sub), self.text, flags)] + + def find_backwards( + self, + sub: str, + in_current_line: bool = False, + ignore_case: bool = False, + count: int = 1, + ) -> Optional[int]: + """ + Find `text` before the cursor, return position relative to the cursor + position. Return `None` if nothing was found. + + :param count: Find the n-th occurrence. + """ + if in_current_line: + before_cursor = self.current_line_before_cursor[::-1] + else: + before_cursor = self.text_before_cursor[::-1] + + flags = re.IGNORECASE if ignore_case else 0 + iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags) + + try: + for i, match in enumerate(iterator): + if i + 1 == count: + return -match.start(0) - len(sub) + except StopIteration: + pass + return None + + def get_word_before_cursor( + self, WORD: bool = False, pattern: Optional[Pattern[str]] = None + ) -> str: + """ + Give the word before the cursor. + If we have whitespace before the cursor this returns an empty string. + + :param pattern: (None or compiled regex). When given, use this regex + pattern. + """ + if self._is_word_before_cursor_complete(WORD=WORD, pattern=pattern): + # Space before the cursor or no text before cursor. + return "" + + text_before_cursor = self.text_before_cursor + start = self.find_start_of_previous_word(WORD=WORD, pattern=pattern) or 0 + + return text_before_cursor[len(text_before_cursor) + start :] + + def _is_word_before_cursor_complete( + self, WORD: bool = False, pattern: Optional[Pattern[str]] = None + ) -> bool: + if pattern: + return self.find_start_of_previous_word(WORD=WORD, pattern=pattern) is None + else: + return ( + self.text_before_cursor == "" or self.text_before_cursor[-1:].isspace() + ) + + def find_start_of_previous_word( + self, count: int = 1, WORD: bool = False, pattern: Optional[Pattern[str]] = None + ) -> Optional[int]: + """ + Return an index relative to the cursor position pointing to the start + of the previous word. Return `None` if nothing was found. + + :param pattern: (None or compiled regex). When given, use this regex + pattern. + """ + assert not (WORD and pattern) + + # Reverse the text before the cursor, in order to do an efficient + # backwards search. + text_before_cursor = self.text_before_cursor[::-1] + + if pattern: + regex = pattern + elif WORD: + regex = _FIND_BIG_WORD_RE + else: + regex = _FIND_WORD_RE + + iterator = regex.finditer(text_before_cursor) + + try: + for i, match in enumerate(iterator): + if i + 1 == count: + return -match.end(0) + except StopIteration: + pass + return None + + def find_boundaries_of_current_word( + self, + WORD: bool = False, + include_leading_whitespace: bool = False, + include_trailing_whitespace: bool = False, + ) -> Tuple[int, int]: + """ + Return the relative boundaries (startpos, endpos) of the current word under the + cursor. (This is at the current line, because line boundaries obviously + don't belong to any word.) + If not on a word, this returns (0,0) + """ + text_before_cursor = self.current_line_before_cursor[::-1] + text_after_cursor = self.current_line_after_cursor + + def get_regex(include_whitespace: bool) -> Pattern[str]: + return { + (False, False): _FIND_CURRENT_WORD_RE, + (False, True): _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE, + (True, False): _FIND_CURRENT_BIG_WORD_RE, + (True, True): _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE, + }[(WORD, include_whitespace)] + + match_before = get_regex(include_leading_whitespace).search(text_before_cursor) + match_after = get_regex(include_trailing_whitespace).search(text_after_cursor) + + # When there is a match before and after, and we're not looking for + # WORDs, make sure that both the part before and after the cursor are + # either in the [a-zA-Z_] alphabet or not. Otherwise, drop the part + # before the cursor. + if not WORD and match_before and match_after: + c1 = self.text[self.cursor_position - 1] + c2 = self.text[self.cursor_position] + alphabet = string.ascii_letters + "0123456789_" + + if (c1 in alphabet) != (c2 in alphabet): + match_before = None + + return ( + -match_before.end(1) if match_before else 0, + match_after.end(1) if match_after else 0, + ) + + def get_word_under_cursor(self, WORD: bool = False) -> str: + """ + Return the word, currently below the cursor. + This returns an empty string when the cursor is on a whitespace region. + """ + start, end = self.find_boundaries_of_current_word(WORD=WORD) + return self.text[self.cursor_position + start : self.cursor_position + end] + + def find_next_word_beginning( + self, count: int = 1, WORD: bool = False + ) -> Optional[int]: + """ + Return an index relative to the cursor position pointing to the start + of the next word. Return `None` if nothing was found. + """ + if count < 0: + return self.find_previous_word_beginning(count=-count, WORD=WORD) + + regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE + iterator = regex.finditer(self.text_after_cursor) + + try: + for i, match in enumerate(iterator): + # Take first match, unless it's the word on which we're right now. + if i == 0 and match.start(1) == 0: + count += 1 + + if i + 1 == count: + return match.start(1) + except StopIteration: + pass + return None + + def find_next_word_ending( + self, include_current_position: bool = False, count: int = 1, WORD: bool = False + ) -> Optional[int]: + """ + Return an index relative to the cursor position pointing to the end + of the next word. Return `None` if nothing was found. + """ + if count < 0: + return self.find_previous_word_ending(count=-count, WORD=WORD) + + if include_current_position: + text = self.text_after_cursor + else: + text = self.text_after_cursor[1:] + + regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE + iterable = regex.finditer(text) + + try: + for i, match in enumerate(iterable): + if i + 1 == count: + value = match.end(1) + + if include_current_position: + return value + else: + return value + 1 + + except StopIteration: + pass + return None + + def find_previous_word_beginning( + self, count: int = 1, WORD: bool = False + ) -> Optional[int]: + """ + Return an index relative to the cursor position pointing to the start + of the previous word. Return `None` if nothing was found. + """ + if count < 0: + return self.find_next_word_beginning(count=-count, WORD=WORD) + + regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE + iterator = regex.finditer(self.text_before_cursor[::-1]) + + try: + for i, match in enumerate(iterator): + if i + 1 == count: + return -match.end(1) + except StopIteration: + pass + return None + + def find_previous_word_ending( + self, count: int = 1, WORD: bool = False + ) -> Optional[int]: + """ + Return an index relative to the cursor position pointing to the end + of the previous word. Return `None` if nothing was found. + """ + if count < 0: + return self.find_next_word_ending(count=-count, WORD=WORD) + + text_before_cursor = self.text_after_cursor[:1] + self.text_before_cursor[::-1] + + regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE + iterator = regex.finditer(text_before_cursor) + + try: + for i, match in enumerate(iterator): + # Take first match, unless it's the word on which we're right now. + if i == 0 and match.start(1) == 0: + count += 1 + + if i + 1 == count: + return -match.start(1) + 1 + except StopIteration: + pass + return None + + def find_next_matching_line( + self, match_func: Callable[[str], bool], count: int = 1 + ) -> Optional[int]: + """ + Look downwards for empty lines. + Return the line index, relative to the current line. + """ + result = None + + for index, line in enumerate(self.lines[self.cursor_position_row + 1 :]): + if match_func(line): + result = 1 + index + count -= 1 + + if count == 0: + break + + return result + + def find_previous_matching_line( + self, match_func: Callable[[str], bool], count: int = 1 + ) -> Optional[int]: + """ + Look upwards for empty lines. + Return the line index, relative to the current line. + """ + result = None + + for index, line in enumerate(self.lines[: self.cursor_position_row][::-1]): + if match_func(line): + result = -1 - index + count -= 1 + + if count == 0: + break + + return result + + def get_cursor_left_position(self, count: int = 1) -> int: + """ + Relative position for cursor left. + """ + if count < 0: + return self.get_cursor_right_position(-count) + + return -min(self.cursor_position_col, count) + + def get_cursor_right_position(self, count: int = 1) -> int: + """ + Relative position for cursor_right. + """ + if count < 0: + return self.get_cursor_left_position(-count) + + return min(count, len(self.current_line_after_cursor)) + + def get_cursor_up_position( + self, count: int = 1, preferred_column: Optional[int] = None + ) -> int: + """ + Return the relative cursor position (character index) where we would be if the + user pressed the arrow-up button. + + :param preferred_column: When given, go to this column instead of + staying at the current column. + """ + assert count >= 1 + column = ( + self.cursor_position_col if preferred_column is None else preferred_column + ) + + return ( + self.translate_row_col_to_index( + max(0, self.cursor_position_row - count), column + ) + - self.cursor_position + ) + + def get_cursor_down_position( + self, count: int = 1, preferred_column: Optional[int] = None + ) -> int: + """ + Return the relative cursor position (character index) where we would be if the + user pressed the arrow-down button. + + :param preferred_column: When given, go to this column instead of + staying at the current column. + """ + assert count >= 1 + column = ( + self.cursor_position_col if preferred_column is None else preferred_column + ) + + return ( + self.translate_row_col_to_index(self.cursor_position_row + count, column) + - self.cursor_position + ) + + def find_enclosing_bracket_right( + self, left_ch: str, right_ch: str, end_pos: Optional[int] = None + ) -> Optional[int]: + """ + Find the right bracket enclosing current position. Return the relative + position to the cursor position. + + When `end_pos` is given, don't look past the position. + """ + if self.current_char == right_ch: + return 0 + + if end_pos is None: + end_pos = len(self.text) + else: + end_pos = min(len(self.text), end_pos) + + stack = 1 + + # Look forward. + for i in range(self.cursor_position + 1, end_pos): + c = self.text[i] + + if c == left_ch: + stack += 1 + elif c == right_ch: + stack -= 1 + + if stack == 0: + return i - self.cursor_position + + return None + + def find_enclosing_bracket_left( + self, left_ch: str, right_ch: str, start_pos: Optional[int] = None + ) -> Optional[int]: + """ + Find the left bracket enclosing current position. Return the relative + position to the cursor position. + + When `start_pos` is given, don't look past the position. + """ + if self.current_char == left_ch: + return 0 + + if start_pos is None: + start_pos = 0 + else: + start_pos = max(0, start_pos) + + stack = 1 + + # Look backward. + for i in range(self.cursor_position - 1, start_pos - 1, -1): + c = self.text[i] + + if c == right_ch: + stack += 1 + elif c == left_ch: + stack -= 1 + + if stack == 0: + return i - self.cursor_position + + return None + + def find_matching_bracket_position( + self, start_pos: Optional[int] = None, end_pos: Optional[int] = None + ) -> int: + """ + Return relative cursor position of matching [, (, { or < bracket. + + When `start_pos` or `end_pos` are given. Don't look past the positions. + """ + + # Look for a match. + for pair in "()", "[]", "{}", "<>": + A = pair[0] + B = pair[1] + if self.current_char == A: + return self.find_enclosing_bracket_right(A, B, end_pos=end_pos) or 0 + elif self.current_char == B: + return self.find_enclosing_bracket_left(A, B, start_pos=start_pos) or 0 + + return 0 + + def get_start_of_document_position(self) -> int: + """Relative position for the start of the document.""" + return -self.cursor_position + + def get_end_of_document_position(self) -> int: + """Relative position for the end of the document.""" + return len(self.text) - self.cursor_position + + def get_start_of_line_position(self, after_whitespace: bool = False) -> int: + """Relative position for the start of this line.""" + if after_whitespace: + current_line = self.current_line + return ( + len(current_line) + - len(current_line.lstrip()) + - self.cursor_position_col + ) + else: + return -len(self.current_line_before_cursor) + + def get_end_of_line_position(self) -> int: + """Relative position for the end of this line.""" + return len(self.current_line_after_cursor) + + def last_non_blank_of_current_line_position(self) -> int: + """ + Relative position for the last non blank character of this line. + """ + return len(self.current_line.rstrip()) - self.cursor_position_col - 1 + + def get_column_cursor_position(self, column: int) -> int: + """ + Return the relative cursor position for this column at the current + line. (It will stay between the boundaries of the line in case of a + larger number.) + """ + line_length = len(self.current_line) + current_column = self.cursor_position_col + column = max(0, min(line_length, column)) + + return column - current_column + + def selection_range( + self, + ) -> Tuple[ + int, int + ]: # XXX: shouldn't this return `None` if there is no selection??? + """ + Return (from, to) tuple of the selection. + start and end position are included. + + This doesn't take the selection type into account. Use + `selection_ranges` instead. + """ + if self.selection: + from_, to = sorted( + [self.cursor_position, self.selection.original_cursor_position] + ) + else: + from_, to = self.cursor_position, self.cursor_position + + return from_, to + + def selection_ranges(self) -> Iterable[Tuple[int, int]]: + """ + Return a list of `(from, to)` tuples for the selection or none if + nothing was selected. The upper boundary is not included. + + This will yield several (from, to) tuples in case of a BLOCK selection. + This will return zero ranges, like (8,8) for empty lines in a block + selection. + """ + if self.selection: + from_, to = sorted( + [self.cursor_position, self.selection.original_cursor_position] + ) + + if self.selection.type == SelectionType.BLOCK: + from_line, from_column = self.translate_index_to_position(from_) + to_line, to_column = self.translate_index_to_position(to) + from_column, to_column = sorted([from_column, to_column]) + lines = self.lines + + if vi_mode(): + to_column += 1 + + for l in range(from_line, to_line + 1): + line_length = len(lines[l]) + + if from_column <= line_length: + yield ( + self.translate_row_col_to_index(l, from_column), + self.translate_row_col_to_index( + l, min(line_length, to_column) + ), + ) + else: + # In case of a LINES selection, go to the start/end of the lines. + if self.selection.type == SelectionType.LINES: + from_ = max(0, self.text.rfind("\n", 0, from_) + 1) + + if self.text.find("\n", to) >= 0: + to = self.text.find("\n", to) + else: + to = len(self.text) - 1 + + # In Vi mode, the upper boundary is always included. For Emacs, + # that's not the case. + if vi_mode(): + to += 1 + + yield from_, to + + def selection_range_at_line(self, row: int) -> Optional[Tuple[int, int]]: + """ + If the selection spans a portion of the given line, return a (from, to) tuple. + + The returned upper boundary is not included in the selection, so + `(0, 0)` is an empty selection. `(0, 1)`, is a one character selection. + + Returns None if the selection doesn't cover this line at all. + """ + if self.selection: + line = self.lines[row] + + row_start = self.translate_row_col_to_index(row, 0) + row_end = self.translate_row_col_to_index(row, len(line)) + + from_, to = sorted( + [self.cursor_position, self.selection.original_cursor_position] + ) + + # Take the intersection of the current line and the selection. + intersection_start = max(row_start, from_) + intersection_end = min(row_end, to) + + if intersection_start <= intersection_end: + if self.selection.type == SelectionType.LINES: + intersection_start = row_start + intersection_end = row_end + + elif self.selection.type == SelectionType.BLOCK: + _, col1 = self.translate_index_to_position(from_) + _, col2 = self.translate_index_to_position(to) + col1, col2 = sorted([col1, col2]) + + if col1 > len(line): + return None # Block selection doesn't cross this line. + + intersection_start = self.translate_row_col_to_index(row, col1) + intersection_end = self.translate_row_col_to_index(row, col2) + + _, from_column = self.translate_index_to_position(intersection_start) + _, to_column = self.translate_index_to_position(intersection_end) + + # In Vi mode, the upper boundary is always included. For Emacs + # mode, that's not the case. + if vi_mode(): + to_column += 1 + + return from_column, to_column + return None + + def cut_selection(self) -> Tuple["Document", ClipboardData]: + """ + Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the + document represents the new document when the selection is cut, and the + clipboard data, represents whatever has to be put on the clipboard. + """ + if self.selection: + cut_parts = [] + remaining_parts = [] + new_cursor_position = self.cursor_position + + last_to = 0 + for from_, to in self.selection_ranges(): + if last_to == 0: + new_cursor_position = from_ + + remaining_parts.append(self.text[last_to:from_]) + cut_parts.append(self.text[from_:to]) + last_to = to + + remaining_parts.append(self.text[last_to:]) + + cut_text = "\n".join(cut_parts) + remaining_text = "".join(remaining_parts) + + # In case of a LINES selection, don't include the trailing newline. + if self.selection.type == SelectionType.LINES and cut_text.endswith("\n"): + cut_text = cut_text[:-1] + + return ( + Document(text=remaining_text, cursor_position=new_cursor_position), + ClipboardData(cut_text, self.selection.type), + ) + else: + return self, ClipboardData("") + + def paste_clipboard_data( + self, + data: ClipboardData, + paste_mode: PasteMode = PasteMode.EMACS, + count: int = 1, + ) -> "Document": + """ + Return a new :class:`.Document` instance which contains the result if + we would paste this data at the current cursor position. + + :param paste_mode: Where to paste. (Before/after/emacs.) + :param count: When >1, Paste multiple times. + """ + before = paste_mode == PasteMode.VI_BEFORE + after = paste_mode == PasteMode.VI_AFTER + + if data.type == SelectionType.CHARACTERS: + if after: + new_text = ( + self.text[: self.cursor_position + 1] + + data.text * count + + self.text[self.cursor_position + 1 :] + ) + else: + new_text = ( + self.text_before_cursor + data.text * count + self.text_after_cursor + ) + + new_cursor_position = self.cursor_position + len(data.text) * count + if before: + new_cursor_position -= 1 + + elif data.type == SelectionType.LINES: + l = self.cursor_position_row + if before: + lines = self.lines[:l] + [data.text] * count + self.lines[l:] + new_text = "\n".join(lines) + new_cursor_position = len("".join(self.lines[:l])) + l + else: + lines = self.lines[: l + 1] + [data.text] * count + self.lines[l + 1 :] + new_cursor_position = len("".join(self.lines[: l + 1])) + l + 1 + new_text = "\n".join(lines) + + elif data.type == SelectionType.BLOCK: + lines = self.lines[:] + start_line = self.cursor_position_row + start_column = self.cursor_position_col + (0 if before else 1) + + for i, line in enumerate(data.text.split("\n")): + index = i + start_line + if index >= len(lines): + lines.append("") + + lines[index] = lines[index].ljust(start_column) + lines[index] = ( + lines[index][:start_column] + + line * count + + lines[index][start_column:] + ) + + new_text = "\n".join(lines) + new_cursor_position = self.cursor_position + (0 if before else 1) + + return Document(text=new_text, cursor_position=new_cursor_position) + + def empty_line_count_at_the_end(self) -> int: + """ + Return number of empty lines at the end of the document. + """ + count = 0 + for line in self.lines[::-1]: + if not line or line.isspace(): + count += 1 + else: + break + + return count + + def start_of_paragraph(self, count: int = 1, before: bool = False) -> int: + """ + Return the start of the current paragraph. (Relative cursor position.) + """ + + def match_func(text: str) -> bool: + return not text or text.isspace() + + line_index = self.find_previous_matching_line( + match_func=match_func, count=count + ) + + if line_index: + add = 0 if before else 1 + return min(0, self.get_cursor_up_position(count=-line_index) + add) + else: + return -self.cursor_position + + def end_of_paragraph(self, count: int = 1, after: bool = False) -> int: + """ + Return the end of the current paragraph. (Relative cursor position.) + """ + + def match_func(text: str) -> bool: + return not text or text.isspace() + + line_index = self.find_next_matching_line(match_func=match_func, count=count) + + if line_index: + add = 0 if after else 1 + return max(0, self.get_cursor_down_position(count=line_index) - add) + else: + return len(self.text_after_cursor) + + # Modifiers. + + def insert_after(self, text: str) -> "Document": + """ + Create a new document, with this text inserted after the buffer. + It keeps selection ranges and cursor position in sync. + """ + return Document( + text=self.text + text, + cursor_position=self.cursor_position, + selection=self.selection, + ) + + def insert_before(self, text: str) -> "Document": + """ + Create a new document, with this text inserted before the buffer. + It keeps selection ranges and cursor position in sync. + """ + selection_state = self.selection + + if selection_state: + selection_state = SelectionState( + original_cursor_position=selection_state.original_cursor_position + + len(text), + type=selection_state.type, + ) + + return Document( + text=text + self.text, + cursor_position=self.cursor_position + len(text), + selection=selection_state, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/enums.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/enums.py new file mode 100644 index 0000000..4f496e6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/enums.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class EditingMode(Enum): + # The set of key bindings that is active. + VI = "VI" + EMACS = "EMACS" + + +#: Name of the search buffer. +SEARCH_BUFFER = "SEARCH_BUFFER" + +#: Name of the default buffer. +DEFAULT_BUFFER = "DEFAULT_BUFFER" + +#: Name of the system buffer. +SYSTEM_BUFFER = "SYSTEM_BUFFER" diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__init__.py new file mode 100644 index 0000000..3a3e9ac --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__init__.py @@ -0,0 +1,28 @@ +from .async_generator import generator_to_async_generator +from .inputhook import ( + InputHookContext, + InputHookSelector, + new_eventloop_with_inputhook, + set_eventloop_with_inputhook, +) +from .utils import ( + call_soon_threadsafe, + get_event_loop, + get_traceback_from_context, + run_in_executor_with_context, +) + +__all__ = [ + # Async generator + "generator_to_async_generator", + # Utils. + "run_in_executor_with_context", + "call_soon_threadsafe", + "get_traceback_from_context", + "get_event_loop", + # Inputhooks. + "new_eventloop_with_inputhook", + "set_eventloop_with_inputhook", + "InputHookSelector", + "InputHookContext", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..0b50572 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/async_context_manager.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/async_context_manager.cpython-38.pyc new file mode 100644 index 0000000..1ae0689 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/async_context_manager.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/async_generator.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/async_generator.cpython-38.pyc new file mode 100644 index 0000000..68c8709 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/async_generator.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/dummy_contextvars.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/dummy_contextvars.cpython-38.pyc new file mode 100644 index 0000000..c0b0b76 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/dummy_contextvars.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/inputhook.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/inputhook.cpython-38.pyc new file mode 100644 index 0000000..75fa866 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/inputhook.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..817d4ae Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/win32.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/win32.cpython-38.pyc new file mode 100644 index 0000000..d9a9f0e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/__pycache__/win32.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/async_context_manager.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/async_context_manager.py new file mode 100644 index 0000000..3914616 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/async_context_manager.py @@ -0,0 +1,132 @@ +""" +@asynccontextmanager code, copied from Python 3.7's contextlib. +For usage in Python 3.6. +Types have been added to this file, just enough to make Mypy happy. +""" +# mypy: allow-untyped-defs +import abc +from functools import wraps +from typing import AsyncContextManager, AsyncIterator, Callable, TypeVar + +import _collections_abc + +__all__ = ["asynccontextmanager"] + + +class AbstractAsyncContextManager(abc.ABC): + + """An abstract base class for asynchronous context managers.""" + + async def __aenter__(self): + """Return `self` upon entering the runtime context.""" + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + """Raise any exception triggered within the runtime context.""" + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AbstractAsyncContextManager: + return _collections_abc._check_methods(C, "__aenter__", "__aexit__") # type: ignore + return NotImplemented + + +class _GeneratorContextManagerBase: + """Shared functionality for @contextmanager and @asynccontextmanager.""" + + def __init__(self, func, args, kwds): + self.gen = func(*args, **kwds) + self.func, self.args, self.kwds = func, args, kwds + # Issue 19330: ensure context manager instances have good docstrings + doc = getattr(func, "__doc__", None) + if doc is None: + doc = type(self).__doc__ + self.__doc__ = doc + # Unfortunately, this still doesn't provide good help output when + # inspecting the created context manager instances, since pydoc + # currently bypasses the instance docstring and shows the docstring + # for the class instead. + # See http://bugs.python.org/issue19404 for more details. + + +class _AsyncGeneratorContextManager( + _GeneratorContextManagerBase, AbstractAsyncContextManager +): + """Helper for @asynccontextmanager.""" + + async def __aenter__(self): + try: + return await self.gen.__anext__() + except StopAsyncIteration: + raise RuntimeError("generator didn't yield") from None + + async def __aexit__(self, typ, value, traceback): + if typ is None: + try: + await self.gen.__anext__() + except StopAsyncIteration: + return + else: + raise RuntimeError("generator didn't stop") + else: + if value is None: + value = typ() + # See _GeneratorContextManager.__exit__ for comments on subtleties + # in this implementation + try: + await self.gen.athrow(typ, value, traceback) + raise RuntimeError("generator didn't stop after athrow()") + except StopAsyncIteration as exc: + return exc is not value + except RuntimeError as exc: + if exc is value: + return False + # Avoid suppressing if a StopIteration exception + # was passed to throw() and later wrapped into a RuntimeError + # (see PEP 479 for sync generators; async generators also + # have this behavior). But do this only if the exception wrapped + # by the RuntimeError is actully Stop(Async)Iteration (see + # issue29692). + if isinstance(value, (StopIteration, StopAsyncIteration)): + if exc.__cause__ is value: + return False + raise + except BaseException as exc: + if exc is not value: + raise + + +_T = TypeVar("_T") + + +def asynccontextmanager( + func: Callable[..., AsyncIterator[_T]] +) -> Callable[..., AsyncContextManager[_T]]: + """@asynccontextmanager decorator. + Typical usage: + @asynccontextmanager + async def some_async_generator(): + + try: + yield + finally: + + This makes this: + async with some_async_generator() as : + + equivalent to this: + + try: + = + + finally: + + """ + + @wraps(func) + def helper(*args, **kwds): + return _AsyncGeneratorContextManager(func, args, kwds) # type: ignore + + return helper diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/async_generator.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/async_generator.py new file mode 100644 index 0000000..7ab3c28 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/async_generator.py @@ -0,0 +1,74 @@ +""" +Implementation for async generators. +""" +from asyncio import Queue +from typing import AsyncGenerator, Callable, Iterable, TypeVar, Union + +from .utils import get_event_loop, run_in_executor_with_context + +__all__ = [ + "generator_to_async_generator", +] + + +_T = TypeVar("_T") + + +class _Done: + pass + + +async def generator_to_async_generator( + get_iterable: Callable[[], Iterable[_T]] +) -> AsyncGenerator[_T, None]: + """ + Turn a generator or iterable into an async generator. + + This works by running the generator in a background thread. + + :param get_iterable: Function that returns a generator or iterable when + called. + """ + quitting = False + _done = _Done() + q: Queue[Union[_T, _Done]] = Queue() + loop = get_event_loop() + + def runner() -> None: + """ + Consume the generator in background thread. + When items are received, they'll be pushed to the queue. + """ + try: + for item in get_iterable(): + # When this async generator was cancelled (closed), stop this + # thread. + if quitting: + break + + loop.call_soon_threadsafe(q.put_nowait, item) + + finally: + loop.call_soon_threadsafe(q.put_nowait, _done) + + # Start background thread. + runner_f = run_in_executor_with_context(runner) + + try: + while True: + item = await q.get() + if isinstance(item, _Done): + break + else: + yield item + finally: + # When this async generator is closed (GeneratorExit exception, stop + # the background thread as well. - we don't need that anymore.) + quitting = True + + # Wait for the background thread to finish. (should happen right after + # the next item is yielded). If we don't do this, and the event loop + # gets closed before the runner is done, then we'll get a + # `RuntimeError: Event loop is closed` exception printed to stdout that + # we can't handle. + await runner_f diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/dummy_contextvars.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/dummy_contextvars.py new file mode 100644 index 0000000..2b20d69 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/dummy_contextvars.py @@ -0,0 +1,49 @@ +""" +Dummy contextvars implementation, to make prompt_toolkit work on Python 3.6. + +As long as there is only one application running at a time, we don't need the +real contextvars. So, stuff like the telnet-server and so on requires 3.7. +""" +from typing import Any, Callable, Generic, Optional, TypeVar + + +def copy_context() -> "Context": + return Context() + + +_T = TypeVar("_T") + + +class Context: + def run(self, callable: Callable[..., _T], *args: Any, **kwargs: Any) -> _T: + return callable(*args, **kwargs) + + def copy(self) -> "Context": + return self + + +class Token(Generic[_T]): + pass + + +class ContextVar(Generic[_T]): + def __init__(self, name: str, *, default: Optional[_T] = None) -> None: + self._name = name + self._value = default + + @property + def name(self) -> str: + return self._name + + def get(self, default: Optional[_T] = None) -> _T: + result = self._value or default + if result is None: + raise LookupError + return result + + def set(self, value: _T) -> Token[_T]: + self._value = value + return Token() + + def reset(self, token: Token[_T]) -> None: + pass diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/inputhook.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/inputhook.py new file mode 100644 index 0000000..26228a2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/inputhook.py @@ -0,0 +1,184 @@ +""" +Similar to `PyOS_InputHook` of the Python API, we can plug in an input hook in +the asyncio event loop. + +The way this works is by using a custom 'selector' that runs the other event +loop until the real selector is ready. + +It's the responsibility of this event hook to return when there is input ready. +There are two ways to detect when input is ready: + +The inputhook itself is a callable that receives an `InputHookContext`. This +callable should run the other event loop, and return when the main loop has +stuff to do. There are two ways to detect when to return: + +- Call the `input_is_ready` method periodically. Quit when this returns `True`. + +- Add the `fileno` as a watch to the external eventloop. Quit when file descriptor + becomes readable. (But don't read from it.) + + Note that this is not the same as checking for `sys.stdin.fileno()`. The + eventloop of prompt-toolkit allows thread-based executors, for example for + asynchronous autocompletion. When the completion for instance is ready, we + also want prompt-toolkit to gain control again in order to display that. +""" +import asyncio +import os +import select +import selectors +import threading +from asyncio import AbstractEventLoop +from selectors import BaseSelector, SelectorKey +from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple + +from prompt_toolkit.utils import is_windows + +from .utils import get_event_loop + +__all__ = [ + "new_eventloop_with_inputhook", + "set_eventloop_with_inputhook", + "InputHookSelector", + "InputHookContext", +] + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + _EventMask = int + + +def new_eventloop_with_inputhook( + inputhook: Callable[["InputHookContext"], None] +) -> AbstractEventLoop: + """ + Create a new event loop with the given inputhook. + """ + selector = InputHookSelector(selectors.DefaultSelector(), inputhook) + loop = asyncio.SelectorEventLoop(selector) + return loop + + +def set_eventloop_with_inputhook( + inputhook: Callable[["InputHookContext"], None] +) -> AbstractEventLoop: + """ + Create a new event loop with the given inputhook, and activate it. + """ + loop = new_eventloop_with_inputhook(inputhook) + asyncio.set_event_loop(loop) + return loop + + +class InputHookSelector(BaseSelector): + """ + Usage: + + selector = selectors.SelectSelector() + loop = asyncio.SelectorEventLoop(InputHookSelector(selector, inputhook)) + asyncio.set_event_loop(loop) + """ + + def __init__( + self, selector: BaseSelector, inputhook: Callable[["InputHookContext"], None] + ) -> None: + self.selector = selector + self.inputhook = inputhook + self._r, self._w = os.pipe() + + def register( + self, fileobj: "FileDescriptorLike", events: "_EventMask", data: Any = None + ) -> "SelectorKey": + return self.selector.register(fileobj, events, data=data) + + def unregister(self, fileobj: "FileDescriptorLike") -> "SelectorKey": + return self.selector.unregister(fileobj) + + def modify( + self, fileobj: "FileDescriptorLike", events: "_EventMask", data: Any = None + ) -> "SelectorKey": + return self.selector.modify(fileobj, events, data=None) + + def select( + self, timeout: Optional[float] = None + ) -> List[Tuple["SelectorKey", "_EventMask"]]: + # If there are tasks in the current event loop, + # don't run the input hook. + if len(getattr(get_event_loop(), "_ready", [])) > 0: + return self.selector.select(timeout=timeout) + + ready = False + result = None + + # Run selector in other thread. + def run_selector() -> None: + nonlocal ready, result + result = self.selector.select(timeout=timeout) + os.write(self._w, b"x") + ready = True + + th = threading.Thread(target=run_selector) + th.start() + + def input_is_ready() -> bool: + return ready + + # Call inputhook. + # The inputhook function is supposed to return when our selector + # becomes ready. The inputhook can do that by registering the fd in its + # own loop, or by checking the `input_is_ready` function regularly. + self.inputhook(InputHookContext(self._r, input_is_ready)) + + # Flush the read end of the pipe. + try: + # Before calling 'os.read', call select.select. This is required + # when the gevent monkey patch has been applied. 'os.read' is never + # monkey patched and won't be cooperative, so that would block all + # other select() calls otherwise. + # See: http://www.gevent.org/gevent.os.html + + # Note: On Windows, this is apparently not an issue. + # However, if we would ever want to add a select call, it + # should use `windll.kernel32.WaitForMultipleObjects`, + # because `select.select` can't wait for a pipe on Windows. + if not is_windows(): + select.select([self._r], [], [], None) + + os.read(self._r, 1024) + except OSError: + # This happens when the window resizes and a SIGWINCH was received. + # We get 'Error: [Errno 4] Interrupted system call' + # Just ignore. + pass + + # Wait for the real selector to be done. + th.join() + assert result is not None + return result + + def close(self) -> None: + """ + Clean up resources. + """ + if self._r: + os.close(self._r) + os.close(self._w) + + self._r = self._w = -1 + self.selector.close() + + def get_map(self) -> Mapping["FileDescriptorLike", "SelectorKey"]: + return self.selector.get_map() + + +class InputHookContext: + """ + Given as a parameter to the inputhook. + """ + + def __init__(self, fileno: int, input_is_ready: Callable[[], bool]) -> None: + self._fileno = fileno + self.input_is_ready = input_is_ready + + def fileno(self) -> int: + return self._fileno diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/utils.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/utils.py new file mode 100644 index 0000000..2e5a05e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/utils.py @@ -0,0 +1,118 @@ +import asyncio +import sys +import time +from types import TracebackType +from typing import Any, Awaitable, Callable, Dict, Optional, TypeVar, cast + +try: + import contextvars +except ImportError: + from . import dummy_contextvars as contextvars # type: ignore + +__all__ = [ + "run_in_executor_with_context", + "call_soon_threadsafe", + "get_traceback_from_context", + "get_event_loop", +] + +_T = TypeVar("_T") + + +def run_in_executor_with_context( + func: Callable[..., _T], + *args: Any, + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> Awaitable[_T]: + """ + Run a function in an executor, but make sure it uses the same contextvars. + This is required so that the function will see the right application. + + See also: https://bugs.python.org/issue34014 + """ + loop = loop or get_event_loop() + ctx: contextvars.Context = contextvars.copy_context() + + return loop.run_in_executor(None, ctx.run, func, *args) + + +def call_soon_threadsafe( + func: Callable[[], None], + max_postpone_time: Optional[float] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> None: + """ + Wrapper around asyncio's `call_soon_threadsafe`. + + This takes a `max_postpone_time` which can be used to tune the urgency of + the method. + + Asyncio runs tasks in first-in-first-out. However, this is not what we + want for the render function of the prompt_toolkit UI. Rendering is + expensive, but since the UI is invalidated very often, in some situations + we render the UI too often, so much that the rendering CPU usage slows down + the rest of the processing of the application. (Pymux is an example where + we have to balance the CPU time spend on rendering the UI, and parsing + process output.) + However, we want to set a deadline value, for when the rendering should + happen. (The UI should stay responsive). + """ + loop2 = loop or get_event_loop() + + # If no `max_postpone_time` has been given, schedule right now. + if max_postpone_time is None: + loop2.call_soon_threadsafe(func) + return + + max_postpone_until = time.time() + max_postpone_time + + def schedule() -> None: + # When there are no other tasks scheduled in the event loop. Run it + # now. + # Notice: uvloop doesn't have this _ready attribute. In that case, + # always call immediately. + if not getattr(loop2, "_ready", []): + func() + return + + # If the timeout expired, run this now. + if time.time() > max_postpone_until: + func() + return + + # Schedule again for later. + loop2.call_soon_threadsafe(schedule) + + loop2.call_soon_threadsafe(schedule) + + +def get_traceback_from_context(context: Dict[str, Any]) -> Optional[TracebackType]: + """ + Get the traceback object from the context. + """ + exception = context.get("exception") + if exception: + if hasattr(exception, "__traceback__"): + return cast(TracebackType, exception.__traceback__) + else: + # call_exception_handler() is usually called indirectly + # from an except block. If it's not the case, the traceback + # is undefined... + return sys.exc_info()[2] + + return None + + +def get_event_loop() -> asyncio.AbstractEventLoop: + """Backward compatible way to get the event loop""" + # Python 3.6 doesn't have get_running_loop + # Python 3.10 deprecated get_event_loop + if sys.version_info >= (3, 7): + getloop = asyncio.get_running_loop + else: + getloop = asyncio.get_event_loop + + try: + return getloop() + except RuntimeError: + return asyncio.get_event_loop_policy().get_event_loop() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/win32.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/win32.py new file mode 100644 index 0000000..a53632e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/eventloop/win32.py @@ -0,0 +1,69 @@ +from ctypes import pointer + +from ..utils import SPHINX_AUTODOC_RUNNING + +# Do not import win32-specific stuff when generating documentation. +# Otherwise RTD would be unable to generate docs for this module. +if not SPHINX_AUTODOC_RUNNING: + from ctypes import windll + +from ctypes.wintypes import BOOL, DWORD, HANDLE +from typing import List, Optional + +from prompt_toolkit.win32_types import SECURITY_ATTRIBUTES + +__all__ = ["wait_for_handles", "create_win32_event"] + + +WAIT_TIMEOUT = 0x00000102 +INFINITE = -1 + + +def wait_for_handles( + handles: List[HANDLE], timeout: int = INFINITE +) -> Optional[HANDLE]: + """ + Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. + Returns `None` on timeout. + http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx + + Note that handles should be a list of `HANDLE` objects, not integers. See + this comment in the patch by @quark-zju for the reason why: + + ''' Make sure HANDLE on Windows has a correct size + + Previously, the type of various HANDLEs are native Python integer + types. The ctypes library will treat them as 4-byte integer when used + in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually + a small integer. Depending on whether the extra 4 bytes are zero-ed out + or not, things can happen to work, or break. ''' + + This function returns either `None` or one of the given `HANDLE` objects. + (The return value can be tested with the `is` operator.) + """ + arrtype = HANDLE * len(handles) + handle_array = arrtype(*handles) + + ret: int = windll.kernel32.WaitForMultipleObjects( + len(handle_array), handle_array, BOOL(False), DWORD(timeout) + ) + + if ret == WAIT_TIMEOUT: + return None + else: + return handles[ret] + + +def create_win32_event() -> HANDLE: + """ + Creates a Win32 unnamed Event . + http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx + """ + return HANDLE( + windll.kernel32.CreateEventA( + pointer(SECURITY_ATTRIBUTES()), + BOOL(True), # Manual reset event. + BOOL(False), # Initial state. + None, # Unnamed event object. + ) + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__init__.py new file mode 100644 index 0000000..d97195f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__init__.py @@ -0,0 +1,68 @@ +""" +Filters decide whether something is active or not (they decide about a boolean +state). This is used to enable/disable features, like key bindings, parts of +the layout and other stuff. For instance, we could have a `HasSearch` filter +attached to some part of the layout, in order to show that part of the user +interface only while the user is searching. + +Filters are made to avoid having to attach callbacks to all event in order to +propagate state. However, they are lazy, they don't automatically propagate the +state of what they are observing. Only when a filter is called (it's actually a +callable), it will calculate its value. So, its not really reactive +programming, but it's made to fit for this framework. + +Filters can be chained using ``&`` and ``|`` operations, and inverted using the +``~`` operator, for instance:: + + filter = has_focus('default') & ~ has_selection +""" +from .app import * +from .base import Always, Condition, Filter, FilterOrBool, Never +from .cli import * +from .utils import is_true, to_filter + +__all__ = [ + # app + "has_arg", + "has_completions", + "completion_is_selected", + "has_focus", + "buffer_has_focus", + "has_selection", + "has_validation_error", + "is_done", + "is_read_only", + "is_multiline", + "renderer_height_is_known", + "in_editing_mode", + "in_paste_mode", + "vi_mode", + "vi_navigation_mode", + "vi_insert_mode", + "vi_insert_multiple_mode", + "vi_replace_mode", + "vi_selection_mode", + "vi_waiting_for_text_object_mode", + "vi_digraph_mode", + "vi_recording_macro", + "emacs_mode", + "emacs_insert_mode", + "emacs_selection_mode", + "shift_selection_mode", + "is_searching", + "control_is_searchable", + "vi_search_direction_reversed", + # base. + "Filter", + "Never", + "Always", + "Condition", + "FilterOrBool", + # utils. + "is_true", + "to_filter", +] + +from .cli import __all__ as cli_all + +__all__.extend(cli_all) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..414fad9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/app.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/app.cpython-38.pyc new file mode 100644 index 0000000..65fd731 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/app.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..ac0dce6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/cli.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/cli.cpython-38.pyc new file mode 100644 index 0000000..7add85c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/cli.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..80a92fb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/app.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/app.py new file mode 100644 index 0000000..767ec49 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/app.py @@ -0,0 +1,402 @@ +""" +Filters that accept a `Application` as argument. +""" +from typing import TYPE_CHECKING, cast + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.cache import memoized +from prompt_toolkit.enums import EditingMode + +from .base import Condition + +if TYPE_CHECKING: + from prompt_toolkit.layout.layout import FocusableElement + + +__all__ = [ + "has_arg", + "has_completions", + "completion_is_selected", + "has_focus", + "buffer_has_focus", + "has_selection", + "has_validation_error", + "is_done", + "is_read_only", + "is_multiline", + "renderer_height_is_known", + "in_editing_mode", + "in_paste_mode", + "vi_mode", + "vi_navigation_mode", + "vi_insert_mode", + "vi_insert_multiple_mode", + "vi_replace_mode", + "vi_selection_mode", + "vi_waiting_for_text_object_mode", + "vi_digraph_mode", + "vi_recording_macro", + "emacs_mode", + "emacs_insert_mode", + "emacs_selection_mode", + "shift_selection_mode", + "is_searching", + "control_is_searchable", + "vi_search_direction_reversed", +] + + +@memoized() +def has_focus(value: "FocusableElement") -> Condition: + """ + Enable when this buffer has the focus. + """ + from prompt_toolkit.buffer import Buffer + from prompt_toolkit.layout import walk + from prompt_toolkit.layout.containers import Container, Window, to_container + from prompt_toolkit.layout.controls import UIControl + + if isinstance(value, str): + + def test() -> bool: + return get_app().current_buffer.name == value + + elif isinstance(value, Buffer): + + def test() -> bool: + return get_app().current_buffer == value + + elif isinstance(value, UIControl): + + def test() -> bool: + return get_app().layout.current_control == value + + else: + value = to_container(value) + + if isinstance(value, Window): + + def test() -> bool: + return get_app().layout.current_window == value + + else: + + def test() -> bool: + # Consider focused when any window inside this container is + # focused. + current_window = get_app().layout.current_window + + for c in walk(cast(Container, value)): + if isinstance(c, Window) and c == current_window: + return True + return False + + @Condition + def has_focus_filter() -> bool: + return test() + + return has_focus_filter + + +@Condition +def buffer_has_focus() -> bool: + """ + Enabled when the currently focused control is a `BufferControl`. + """ + return get_app().layout.buffer_has_focus + + +@Condition +def has_selection() -> bool: + """ + Enable when the current buffer has a selection. + """ + return bool(get_app().current_buffer.selection_state) + + +@Condition +def has_completions() -> bool: + """ + Enable when the current buffer has completions. + """ + state = get_app().current_buffer.complete_state + return state is not None and len(state.completions) > 0 + + +@Condition +def completion_is_selected() -> bool: + """ + True when the user selected a completion. + """ + complete_state = get_app().current_buffer.complete_state + return complete_state is not None and complete_state.current_completion is not None + + +@Condition +def is_read_only() -> bool: + """ + True when the current buffer is read only. + """ + return get_app().current_buffer.read_only() + + +@Condition +def is_multiline() -> bool: + """ + True when the current buffer has been marked as multiline. + """ + return get_app().current_buffer.multiline() + + +@Condition +def has_validation_error() -> bool: + "Current buffer has validation error." + return get_app().current_buffer.validation_error is not None + + +@Condition +def has_arg() -> bool: + "Enable when the input processor has an 'arg'." + return get_app().key_processor.arg is not None + + +@Condition +def is_done() -> bool: + """ + True when the CLI is returning, aborting or exiting. + """ + return get_app().is_done + + +@Condition +def renderer_height_is_known() -> bool: + """ + Only True when the renderer knows it's real height. + + (On VT100 terminals, we have to wait for a CPR response, before we can be + sure of the available height between the cursor position and the bottom of + the terminal. And usually it's nicer to wait with drawing bottom toolbars + until we receive the height, in order to avoid flickering -- first drawing + somewhere in the middle, and then again at the bottom.) + """ + return get_app().renderer.height_is_known + + +@memoized() +def in_editing_mode(editing_mode: EditingMode) -> Condition: + """ + Check whether a given editing mode is active. (Vi or Emacs.) + """ + + @Condition + def in_editing_mode_filter() -> bool: + return get_app().editing_mode == editing_mode + + return in_editing_mode_filter + + +@Condition +def in_paste_mode() -> bool: + return get_app().paste_mode() + + +@Condition +def vi_mode() -> bool: + return get_app().editing_mode == EditingMode.VI + + +@Condition +def vi_navigation_mode() -> bool: + """ + Active when the set for Vi navigation key bindings are active. + """ + from prompt_toolkit.key_binding.vi_state import InputMode + + app = get_app() + + if ( + app.editing_mode != EditingMode.VI + or app.vi_state.operator_func + or app.vi_state.waiting_for_digraph + or app.current_buffer.selection_state + ): + return False + + return ( + app.vi_state.input_mode == InputMode.NAVIGATION + or app.vi_state.temporary_navigation_mode + or app.current_buffer.read_only() + ) + + +@Condition +def vi_insert_mode() -> bool: + from prompt_toolkit.key_binding.vi_state import InputMode + + app = get_app() + + if ( + app.editing_mode != EditingMode.VI + or app.vi_state.operator_func + or app.vi_state.waiting_for_digraph + or app.current_buffer.selection_state + or app.vi_state.temporary_navigation_mode + or app.current_buffer.read_only() + ): + return False + + return app.vi_state.input_mode == InputMode.INSERT + + +@Condition +def vi_insert_multiple_mode() -> bool: + from prompt_toolkit.key_binding.vi_state import InputMode + + app = get_app() + + if ( + app.editing_mode != EditingMode.VI + or app.vi_state.operator_func + or app.vi_state.waiting_for_digraph + or app.current_buffer.selection_state + or app.vi_state.temporary_navigation_mode + or app.current_buffer.read_only() + ): + return False + + return app.vi_state.input_mode == InputMode.INSERT_MULTIPLE + + +@Condition +def vi_replace_mode() -> bool: + from prompt_toolkit.key_binding.vi_state import InputMode + + app = get_app() + + if ( + app.editing_mode != EditingMode.VI + or app.vi_state.operator_func + or app.vi_state.waiting_for_digraph + or app.current_buffer.selection_state + or app.vi_state.temporary_navigation_mode + or app.current_buffer.read_only() + ): + return False + + return app.vi_state.input_mode == InputMode.REPLACE + + +@Condition +def vi_replace_single_mode() -> bool: + from prompt_toolkit.key_binding.vi_state import InputMode + + app = get_app() + + if ( + app.editing_mode != EditingMode.VI + or app.vi_state.operator_func + or app.vi_state.waiting_for_digraph + or app.current_buffer.selection_state + or app.vi_state.temporary_navigation_mode + or app.current_buffer.read_only() + ): + return False + + return app.vi_state.input_mode == InputMode.REPLACE_SINGLE + + +@Condition +def vi_selection_mode() -> bool: + app = get_app() + if app.editing_mode != EditingMode.VI: + return False + + return bool(app.current_buffer.selection_state) + + +@Condition +def vi_waiting_for_text_object_mode() -> bool: + app = get_app() + if app.editing_mode != EditingMode.VI: + return False + + return app.vi_state.operator_func is not None + + +@Condition +def vi_digraph_mode() -> bool: + app = get_app() + if app.editing_mode != EditingMode.VI: + return False + + return app.vi_state.waiting_for_digraph + + +@Condition +def vi_recording_macro() -> bool: + "When recording a Vi macro." + app = get_app() + if app.editing_mode != EditingMode.VI: + return False + + return app.vi_state.recording_register is not None + + +@Condition +def emacs_mode() -> bool: + "When the Emacs bindings are active." + return get_app().editing_mode == EditingMode.EMACS + + +@Condition +def emacs_insert_mode() -> bool: + app = get_app() + if ( + app.editing_mode != EditingMode.EMACS + or app.current_buffer.selection_state + or app.current_buffer.read_only() + ): + return False + return True + + +@Condition +def emacs_selection_mode() -> bool: + app = get_app() + return bool( + app.editing_mode == EditingMode.EMACS and app.current_buffer.selection_state + ) + + +@Condition +def shift_selection_mode() -> bool: + app = get_app() + return bool( + app.current_buffer.selection_state + and app.current_buffer.selection_state.shift_mode + ) + + +@Condition +def is_searching() -> bool: + "When we are searching." + app = get_app() + return app.layout.is_searching + + +@Condition +def control_is_searchable() -> bool: + "When the current UIControl is searchable." + from prompt_toolkit.layout.controls import BufferControl + + control = get_app().layout.current_control + + return ( + isinstance(control, BufferControl) and control.search_buffer_control is not None + ) + + +@Condition +def vi_search_direction_reversed() -> bool: + "When the '/' and '?' key bindings for Vi-style searching have been reversed." + return get_app().reverse_vi_search_direction() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/base.py new file mode 100644 index 0000000..fd57cca --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/base.py @@ -0,0 +1,217 @@ +from abc import ABCMeta, abstractmethod +from typing import Callable, Dict, Iterable, List, Tuple, Union + +__all__ = ["Filter", "Never", "Always", "Condition", "FilterOrBool"] + + +class Filter(metaclass=ABCMeta): + """ + Base class for any filter to activate/deactivate a feature, depending on a + condition. + + The return value of ``__call__`` will tell if the feature should be active. + """ + + @abstractmethod + def __call__(self) -> bool: + """ + The actual call to evaluate the filter. + """ + return True + + def __and__(self, other: "Filter") -> "Filter": + """ + Chaining of filters using the & operator. + """ + return _and_cache[self, other] + + def __or__(self, other: "Filter") -> "Filter": + """ + Chaining of filters using the | operator. + """ + return _or_cache[self, other] + + def __invert__(self) -> "Filter": + """ + Inverting of filters using the ~ operator. + """ + return _invert_cache[self] + + def __bool__(self) -> None: + """ + By purpose, we don't allow bool(...) operations directly on a filter, + because the meaning is ambiguous. + + Executing a filter has to be done always by calling it. Providing + defaults for `None` values should be done through an `is None` check + instead of for instance ``filter1 or Always()``. + """ + raise ValueError( + "The truth value of a Filter is ambiguous. " + "Instead, call it as a function." + ) + + +class _AndCache(Dict[Tuple[Filter, Filter], "_AndList"]): + """ + Cache for And operation between filters. + (Filter classes are stateless, so we can reuse them.) + + Note: This could be a memory leak if we keep creating filters at runtime. + If that is True, the filters should be weakreffed (not the tuple of + filters), and tuples should be removed when one of these filters is + removed. In practise however, there is a finite amount of filters. + """ + + def __missing__(self, filters: Tuple[Filter, Filter]) -> Filter: + a, b = filters + assert isinstance(b, Filter), "Expecting filter, got %r" % b + + if isinstance(b, Always) or isinstance(a, Never): + return a + elif isinstance(b, Never) or isinstance(a, Always): + return b + + result = _AndList(filters) + self[filters] = result + return result + + +class _OrCache(Dict[Tuple[Filter, Filter], "_OrList"]): + """Cache for Or operation between filters.""" + + def __missing__(self, filters: Tuple[Filter, Filter]) -> Filter: + a, b = filters + assert isinstance(b, Filter), "Expecting filter, got %r" % b + + if isinstance(b, Always) or isinstance(a, Never): + return b + elif isinstance(b, Never) or isinstance(a, Always): + return a + + result = _OrList(filters) + self[filters] = result + return result + + +class _InvertCache(Dict[Filter, "_Invert"]): + """Cache for inversion operator.""" + + def __missing__(self, filter: Filter) -> Filter: + result = _Invert(filter) + self[filter] = result + return result + + +_and_cache = _AndCache() +_or_cache = _OrCache() +_invert_cache = _InvertCache() + + +class _AndList(Filter): + """ + Result of &-operation between several filters. + """ + + def __init__(self, filters: Iterable[Filter]) -> None: + self.filters: List[Filter] = [] + + for f in filters: + if isinstance(f, _AndList): # Turn nested _AndLists into one. + self.filters.extend(f.filters) + else: + self.filters.append(f) + + def __call__(self) -> bool: + return all(f() for f in self.filters) + + def __repr__(self) -> str: + return "&".join(repr(f) for f in self.filters) + + +class _OrList(Filter): + """ + Result of |-operation between several filters. + """ + + def __init__(self, filters: Iterable[Filter]) -> None: + self.filters: List[Filter] = [] + + for f in filters: + if isinstance(f, _OrList): # Turn nested _OrLists into one. + self.filters.extend(f.filters) + else: + self.filters.append(f) + + def __call__(self) -> bool: + return any(f() for f in self.filters) + + def __repr__(self) -> str: + return "|".join(repr(f) for f in self.filters) + + +class _Invert(Filter): + """ + Negation of another filter. + """ + + def __init__(self, filter: Filter) -> None: + self.filter = filter + + def __call__(self) -> bool: + return not self.filter() + + def __repr__(self) -> str: + return "~%r" % self.filter + + +class Always(Filter): + """ + Always enable feature. + """ + + def __call__(self) -> bool: + return True + + def __invert__(self) -> "Never": + return Never() + + +class Never(Filter): + """ + Never enable feature. + """ + + def __call__(self) -> bool: + return False + + def __invert__(self) -> Always: + return Always() + + +class Condition(Filter): + """ + Turn any callable into a Filter. The callable is supposed to not take any + arguments. + + This can be used as a decorator:: + + @Condition + def feature_is_active(): # `feature_is_active` becomes a Filter. + return True + + :param func: Callable which takes no inputs and returns a boolean. + """ + + def __init__(self, func: Callable[[], bool]) -> None: + self.func = func + + def __call__(self) -> bool: + return self.func() + + def __repr__(self) -> str: + return "Condition(%r)" % self.func + + +# Often used as type annotation. +FilterOrBool = Union[Filter, bool] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/cli.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/cli.py new file mode 100644 index 0000000..7135196 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/cli.py @@ -0,0 +1,62 @@ +""" +For backwards-compatibility. keep this file. +(Many people are going to have key bindings that rely on this file.) +""" +from .app import * + +__all__ = [ + # Old names. + "HasArg", + "HasCompletions", + "HasFocus", + "HasSelection", + "HasValidationError", + "IsDone", + "IsReadOnly", + "IsMultiline", + "RendererHeightIsKnown", + "InEditingMode", + "InPasteMode", + "ViMode", + "ViNavigationMode", + "ViInsertMode", + "ViInsertMultipleMode", + "ViReplaceMode", + "ViSelectionMode", + "ViWaitingForTextObjectMode", + "ViDigraphMode", + "EmacsMode", + "EmacsInsertMode", + "EmacsSelectionMode", + "IsSearching", + "HasSearch", + "ControlIsSearchable", +] + +# Keep the original classnames for backwards compatibility. +HasValidationError = lambda: has_validation_error +HasArg = lambda: has_arg +IsDone = lambda: is_done +RendererHeightIsKnown = lambda: renderer_height_is_known +ViNavigationMode = lambda: vi_navigation_mode +InPasteMode = lambda: in_paste_mode +EmacsMode = lambda: emacs_mode +EmacsInsertMode = lambda: emacs_insert_mode +ViMode = lambda: vi_mode +IsSearching = lambda: is_searching +HasSearch = lambda: is_searching +ControlIsSearchable = lambda: control_is_searchable +EmacsSelectionMode = lambda: emacs_selection_mode +ViDigraphMode = lambda: vi_digraph_mode +ViWaitingForTextObjectMode = lambda: vi_waiting_for_text_object_mode +ViSelectionMode = lambda: vi_selection_mode +ViReplaceMode = lambda: vi_replace_mode +ViInsertMultipleMode = lambda: vi_insert_multiple_mode +ViInsertMode = lambda: vi_insert_mode +HasSelection = lambda: has_selection +HasCompletions = lambda: has_completions +IsReadOnly = lambda: is_read_only +IsMultiline = lambda: is_multiline + +HasFocus = has_focus # No lambda here! (Has_focus is callable that returns a callable.) +InEditingMode = in_editing_mode diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/utils.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/utils.py new file mode 100644 index 0000000..aaf44aa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/filters/utils.py @@ -0,0 +1,41 @@ +from typing import Dict + +from .base import Always, Filter, FilterOrBool, Never + +__all__ = [ + "to_filter", + "is_true", +] + + +_always = Always() +_never = Never() + + +_bool_to_filter: Dict[bool, Filter] = { + True: _always, + False: _never, +} + + +def to_filter(bool_or_filter: FilterOrBool) -> Filter: + """ + Accept both booleans and Filters as input and + turn it into a Filter. + """ + if isinstance(bool_or_filter, bool): + return _bool_to_filter[bool_or_filter] + + if isinstance(bool_or_filter, Filter): + return bool_or_filter + + raise TypeError("Expecting a bool or a Filter instance. Got %r" % bool_or_filter) + + +def is_true(value: FilterOrBool) -> bool: + """ + Test whether `value` is True. In case of a Filter, call it. + + :param value: Boolean or `Filter` instance. + """ + return to_filter(value)() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__init__.py new file mode 100644 index 0000000..f0c92c9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__init__.py @@ -0,0 +1,54 @@ +""" +Many places in prompt_toolkit can take either plain text, or formatted text. +For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either +plain text or formatted text for the prompt. The +:class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain +text or formatted text. + +In any case, there is an input that can either be just plain text (a string), +an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of +`(style_string, text)` tuples. The :func:`.to_formatted_text` conversion +function takes any of these and turns all of them into such a tuple sequence. +""" +from .ansi import ANSI +from .base import ( + AnyFormattedText, + FormattedText, + StyleAndTextTuples, + Template, + is_formatted_text, + merge_formatted_text, + to_formatted_text, +) +from .html import HTML +from .pygments import PygmentsTokens +from .utils import ( + fragment_list_len, + fragment_list_to_text, + fragment_list_width, + split_lines, + to_plain_text, +) + +__all__ = [ + # Base. + "AnyFormattedText", + "to_formatted_text", + "is_formatted_text", + "Template", + "merge_formatted_text", + "FormattedText", + "StyleAndTextTuples", + # HTML. + "HTML", + # ANSI. + "ANSI", + # Pygments. + "PygmentsTokens", + # Utils. + "fragment_list_len", + "fragment_list_width", + "fragment_list_to_text", + "split_lines", + "to_plain_text", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..80341ee Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/ansi.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/ansi.cpython-38.pyc new file mode 100644 index 0000000..3549866 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/ansi.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..1a3d635 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/html.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/html.cpython-38.pyc new file mode 100644 index 0000000..13f8196 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/html.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/pygments.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/pygments.cpython-38.pyc new file mode 100644 index 0000000..d83ab74 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/pygments.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..5c6b700 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/ansi.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/ansi.py new file mode 100644 index 0000000..2a30b09 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/ansi.py @@ -0,0 +1,297 @@ +from string import Formatter +from typing import Generator, List, Optional, Tuple, Union + +from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS +from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table + +from .base import StyleAndTextTuples + +__all__ = [ + "ANSI", + "ansi_escape", +] + + +class ANSI: + """ + ANSI formatted text. + Take something ANSI escaped text, for use as a formatted string. E.g. + + :: + + ANSI('\\x1b[31mhello \\x1b[32mworld') + + Characters between ``\\001`` and ``\\002`` are supposed to have a zero width + when printed, but these are literally sent to the terminal output. This can + be used for instance, for inserting Final Term prompt commands. They will + be translated into a prompt_toolkit '[ZeroWidthEscape]' fragment. + """ + + def __init__(self, value: str) -> None: + self.value = value + self._formatted_text: StyleAndTextTuples = [] + + # Default style attributes. + self._color: Optional[str] = None + self._bgcolor: Optional[str] = None + self._bold = False + self._underline = False + self._strike = False + self._italic = False + self._blink = False + self._reverse = False + self._hidden = False + + # Process received text. + parser = self._parse_corot() + parser.send(None) # type: ignore + for c in value: + parser.send(c) + + def _parse_corot(self) -> Generator[None, str, None]: + """ + Coroutine that parses the ANSI escape sequences. + """ + style = "" + formatted_text = self._formatted_text + + while True: + # NOTE: CSI is a special token within a stream of characters that + # introduces an ANSI control sequence used to set the + # style attributes of the following characters. + csi = False + + c = yield + + # Everything between \001 and \002 should become a ZeroWidthEscape. + if c == "\001": + escaped_text = "" + while c != "\002": + c = yield + if c == "\002": + formatted_text.append(("[ZeroWidthEscape]", escaped_text)) + c = yield + break + else: + escaped_text += c + + # Check for CSI + if c == "\x1b": + # Start of color escape sequence. + square_bracket = yield + if square_bracket == "[": + csi = True + else: + continue + elif c == "\x9b": + csi = True + + if csi: + # Got a CSI sequence. Color codes are following. + current = "" + params = [] + + while True: + char = yield + + # Construct number + if char.isdigit(): + current += char + + # Eval number + else: + # Limit and save number value + params.append(min(int(current or 0), 9999)) + + # Get delimiter token if present + if char == ";": + current = "" + + # Check and evaluate color codes + elif char == "m": + # Set attributes and token. + self._select_graphic_rendition(params) + style = self._create_style_string() + break + + # Check and evaluate cursor forward + elif char == "C": + for i in range(params[0]): + # add using current style + formatted_text.append((style, " ")) + break + + else: + # Ignore unsupported sequence. + break + else: + # Add current character. + # NOTE: At this point, we could merge the current character + # into the previous tuple if the style did not change, + # however, it's not worth the effort given that it will + # be "Exploded" once again when it's rendered to the + # output. + formatted_text.append((style, c)) + + def _select_graphic_rendition(self, attrs: List[int]) -> None: + """ + Taken a list of graphics attributes and apply changes. + """ + if not attrs: + attrs = [0] + else: + attrs = list(attrs[::-1]) + + while attrs: + attr = attrs.pop() + + if attr in _fg_colors: + self._color = _fg_colors[attr] + elif attr in _bg_colors: + self._bgcolor = _bg_colors[attr] + elif attr == 1: + self._bold = True + # elif attr == 2: + # self._faint = True + elif attr == 3: + self._italic = True + elif attr == 4: + self._underline = True + elif attr == 5: + self._blink = True # Slow blink + elif attr == 6: + self._blink = True # Fast blink + elif attr == 7: + self._reverse = True + elif attr == 8: + self._hidden = True + elif attr == 9: + self._strike = True + elif attr == 22: + self._bold = False # Normal intensity + elif attr == 23: + self._italic = False + elif attr == 24: + self._underline = False + elif attr == 25: + self._blink = False + elif attr == 27: + self._reverse = False + elif attr == 28: + self._hidden = False + elif attr == 29: + self._strike = False + elif not attr: + # Reset all style attributes + self._color = None + self._bgcolor = None + self._bold = False + self._underline = False + self._strike = False + self._italic = False + self._blink = False + self._reverse = False + self._hidden = False + + elif attr in (38, 48) and len(attrs) > 1: + n = attrs.pop() + + # 256 colors. + if n == 5 and len(attrs) >= 1: + if attr == 38: + m = attrs.pop() + self._color = _256_colors.get(m) + elif attr == 48: + m = attrs.pop() + self._bgcolor = _256_colors.get(m) + + # True colors. + if n == 2 and len(attrs) >= 3: + try: + color_str = "#{:02x}{:02x}{:02x}".format( + attrs.pop(), + attrs.pop(), + attrs.pop(), + ) + except IndexError: + pass + else: + if attr == 38: + self._color = color_str + elif attr == 48: + self._bgcolor = color_str + + def _create_style_string(self) -> str: + """ + Turn current style flags into a string for usage in a formatted text. + """ + result = [] + if self._color: + result.append(self._color) + if self._bgcolor: + result.append("bg:" + self._bgcolor) + if self._bold: + result.append("bold") + if self._underline: + result.append("underline") + if self._strike: + result.append("strike") + if self._italic: + result.append("italic") + if self._blink: + result.append("blink") + if self._reverse: + result.append("reverse") + if self._hidden: + result.append("hidden") + + return " ".join(result) + + def __repr__(self) -> str: + return f"ANSI({self.value!r})" + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + return self._formatted_text + + def format(self, *args: str, **kwargs: str) -> "ANSI": + """ + Like `str.format`, but make sure that the arguments are properly + escaped. (No ANSI escapes can be injected.) + """ + return ANSI(FORMATTER.vformat(self.value, args, kwargs)) + + def __mod__(self, value: object) -> "ANSI": + """ + ANSI('%s') % value + """ + if not isinstance(value, tuple): + value = (value,) + + value = tuple(ansi_escape(i) for i in value) + return ANSI(self.value % value) + + +# Mapping of the ANSI color codes to their names. +_fg_colors = {v: k for k, v in FG_ANSI_COLORS.items()} +_bg_colors = {v: k for k, v in BG_ANSI_COLORS.items()} + +# Mapping of the escape codes for 256colors to their 'ffffff' value. +_256_colors = {} + +for i, (r, g, b) in enumerate(_256_colors_table.colors): + _256_colors[i] = f"#{r:02x}{g:02x}{b:02x}" + + +def ansi_escape(text: object) -> str: + """ + Replace characters with a special meaning. + """ + return str(text).replace("\x1b", "?").replace("\b", "?") + + +class ANSIFormatter(Formatter): + def format_field(self, value: object, format_spec: str) -> str: + return ansi_escape(format(value, format_spec)) + + +FORMATTER = ANSIFormatter() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/base.py new file mode 100644 index 0000000..e88c593 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/base.py @@ -0,0 +1,176 @@ +from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Tuple, Union, cast + +from prompt_toolkit.mouse_events import MouseEvent + +if TYPE_CHECKING: + from typing_extensions import Protocol + +__all__ = [ + "OneStyleAndTextTuple", + "StyleAndTextTuples", + "MagicFormattedText", + "AnyFormattedText", + "to_formatted_text", + "is_formatted_text", + "Template", + "merge_formatted_text", + "FormattedText", +] + +OneStyleAndTextTuple = Union[ + Tuple[str, str], Tuple[str, str, Callable[[MouseEvent], None]] +] + +# List of (style, text) tuples. +StyleAndTextTuples = List[OneStyleAndTextTuple] + + +if TYPE_CHECKING: + from typing_extensions import TypeGuard + + class MagicFormattedText(Protocol): + """ + Any object that implements ``__pt_formatted_text__`` represents formatted + text. + """ + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + ... + + +AnyFormattedText = Union[ + str, + "MagicFormattedText", + StyleAndTextTuples, + # Callable[[], 'AnyFormattedText'] # Recursive definition not supported by mypy. + Callable[[], Any], + None, +] + + +def to_formatted_text( + value: AnyFormattedText, style: str = "", auto_convert: bool = False +) -> "FormattedText": + """ + Convert the given value (which can be formatted text) into a list of text + fragments. (Which is the canonical form of formatted text.) The outcome is + always a `FormattedText` instance, which is a list of (style, text) tuples. + + It can take a plain text string, an `HTML` or `ANSI` object, anything that + implements `__pt_formatted_text__` or a callable that takes no arguments and + returns one of those. + + :param style: An additional style string which is applied to all text + fragments. + :param auto_convert: If `True`, also accept other types, and convert them + to a string first. + """ + result: Union[FormattedText, StyleAndTextTuples] + + if value is None: + result = [] + elif isinstance(value, str): + result = [("", value)] + elif isinstance(value, list): + result = value # StyleAndTextTuples + elif hasattr(value, "__pt_formatted_text__"): + result = cast("MagicFormattedText", value).__pt_formatted_text__() + elif callable(value): + return to_formatted_text(value(), style=style) + elif auto_convert: + result = [("", f"{value}")] + else: + raise ValueError( + "No formatted text. Expecting a unicode object, " + "HTML, ANSI or a FormattedText instance. Got %r" % (value,) + ) + + # Apply extra style. + if style: + result = cast( + StyleAndTextTuples, + [(style + " " + item_style, *rest) for item_style, *rest in result], + ) + + # Make sure the result is wrapped in a `FormattedText`. Among other + # reasons, this is important for `print_formatted_text` to work correctly + # and distinguish between lists and formatted text. + if isinstance(result, FormattedText): + return result + else: + return FormattedText(result) + + +def is_formatted_text(value: object) -> "TypeGuard[AnyFormattedText]": + """ + Check whether the input is valid formatted text (for use in assert + statements). + In case of a callable, it doesn't check the return type. + """ + if callable(value): + return True + if isinstance(value, (str, list)): + return True + if hasattr(value, "__pt_formatted_text__"): + return True + return False + + +class FormattedText(StyleAndTextTuples): + """ + A list of ``(style, text)`` tuples. + + (In some situations, this can also be ``(style, text, mouse_handler)`` + tuples.) + """ + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + return self + + def __repr__(self) -> str: + return "FormattedText(%s)" % super().__repr__() + + +class Template: + """ + Template for string interpolation with formatted text. + + Example:: + + Template(' ... {} ... ').format(HTML(...)) + + :param text: Plain text. + """ + + def __init__(self, text: str) -> None: + assert "{0}" not in text + self.text = text + + def format(self, *values: AnyFormattedText) -> AnyFormattedText: + def get_result() -> AnyFormattedText: + # Split the template in parts. + parts = self.text.split("{}") + assert len(parts) - 1 == len(values) + + result = FormattedText() + for part, val in zip(parts, values): + result.append(("", part)) + result.extend(to_formatted_text(val)) + result.append(("", parts[-1])) + return result + + return get_result + + +def merge_formatted_text(items: Iterable[AnyFormattedText]) -> AnyFormattedText: + """ + Merge (Concatenate) several pieces of formatted text together. + """ + + def _merge_formatted_text() -> AnyFormattedText: + result = FormattedText() + for i in items: + result.extend(to_formatted_text(i)) + return result + + return _merge_formatted_text diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/html.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/html.py new file mode 100644 index 0000000..0af2b18 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/html.py @@ -0,0 +1,143 @@ +import xml.dom.minidom as minidom +from string import Formatter +from typing import Any, List, Tuple, Union + +from .base import FormattedText, StyleAndTextTuples + +__all__ = ["HTML"] + + +class HTML: + """ + HTML formatted text. + Take something HTML-like, for use as a formatted string. + + :: + + # Turn something into red. + HTML('') + + # Italic, bold, underline and strike. + HTML('...') + HTML('...') + HTML('...') + HTML('...') + + All HTML elements become available as a "class" in the style sheet. + E.g. ``...`` can be styled, by setting a style for + ``username``. + """ + + def __init__(self, value: str) -> None: + self.value = value + document = minidom.parseString(f"{value}") + + result: StyleAndTextTuples = [] + name_stack: List[str] = [] + fg_stack: List[str] = [] + bg_stack: List[str] = [] + + def get_current_style() -> str: + "Build style string for current node." + parts = [] + if name_stack: + parts.append("class:" + ",".join(name_stack)) + + if fg_stack: + parts.append("fg:" + fg_stack[-1]) + if bg_stack: + parts.append("bg:" + bg_stack[-1]) + return " ".join(parts) + + def process_node(node: Any) -> None: + "Process node recursively." + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE: + result.append((get_current_style(), child.data)) + else: + add_to_name_stack = child.nodeName not in ( + "#document", + "html-root", + "style", + ) + fg = bg = "" + + for k, v in child.attributes.items(): + if k == "fg": + fg = v + if k == "bg": + bg = v + if k == "color": + fg = v # Alias for 'fg'. + + # Check for spaces in attributes. This would result in + # invalid style strings otherwise. + if " " in fg: + raise ValueError('"fg" attribute contains a space.') + if " " in bg: + raise ValueError('"bg" attribute contains a space.') + + if add_to_name_stack: + name_stack.append(child.nodeName) + if fg: + fg_stack.append(fg) + if bg: + bg_stack.append(bg) + + process_node(child) + + if add_to_name_stack: + name_stack.pop() + if fg: + fg_stack.pop() + if bg: + bg_stack.pop() + + process_node(document) + + self.formatted_text = FormattedText(result) + + def __repr__(self) -> str: + return f"HTML({self.value!r})" + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + return self.formatted_text + + def format(self, *args: object, **kwargs: object) -> "HTML": + """ + Like `str.format`, but make sure that the arguments are properly + escaped. + """ + return HTML(FORMATTER.vformat(self.value, args, kwargs)) + + def __mod__(self, value: object) -> "HTML": + """ + HTML('%s') % value + """ + if not isinstance(value, tuple): + value = (value,) + + value = tuple(html_escape(i) for i in value) + return HTML(self.value % value) + + +class HTMLFormatter(Formatter): + def format_field(self, value: object, format_spec: str) -> str: + return html_escape(format(value, format_spec)) + + +def html_escape(text: object) -> str: + # The string interpolation functions also take integers and other types. + # Convert to string first. + if not isinstance(text, str): + text = f"{text}" + + return ( + text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + ) + + +FORMATTER = HTMLFormatter() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/pygments.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/pygments.py new file mode 100644 index 0000000..dd16f0e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/pygments.py @@ -0,0 +1,30 @@ +from typing import TYPE_CHECKING, List, Tuple + +from prompt_toolkit.styles.pygments import pygments_token_to_classname + +from .base import StyleAndTextTuples + +if TYPE_CHECKING: + from pygments.token import Token + +__all__ = [ + "PygmentsTokens", +] + + +class PygmentsTokens: + """ + Turn a pygments token list into a list of prompt_toolkit text fragments + (``(style_str, text)`` tuples). + """ + + def __init__(self, token_list: List[Tuple["Token", str]]) -> None: + self.token_list = token_list + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + result: StyleAndTextTuples = [] + + for token, text in self.token_list: + result.append(("class:" + pygments_token_to_classname(token), text)) + + return result diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/utils.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/utils.py new file mode 100644 index 0000000..cda4233 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/formatted_text/utils.py @@ -0,0 +1,98 @@ +""" +Utilities for manipulating formatted text. + +When ``to_formatted_text`` has been called, we get a list of ``(style, text)`` +tuples. This file contains functions for manipulating such a list. +""" +from typing import Iterable, cast + +from prompt_toolkit.utils import get_cwidth + +from .base import ( + AnyFormattedText, + OneStyleAndTextTuple, + StyleAndTextTuples, + to_formatted_text, +) + +__all__ = [ + "to_plain_text", + "fragment_list_len", + "fragment_list_width", + "fragment_list_to_text", + "split_lines", +] + + +def to_plain_text(value: AnyFormattedText) -> str: + """ + Turn any kind of formatted text back into plain text. + """ + return fragment_list_to_text(to_formatted_text(value)) + + +def fragment_list_len(fragments: StyleAndTextTuples) -> int: + """ + Return the amount of characters in this text fragment list. + + :param fragments: List of ``(style_str, text)`` or + ``(style_str, text, mouse_handler)`` tuples. + """ + ZeroWidthEscape = "[ZeroWidthEscape]" + return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0]) + + +def fragment_list_width(fragments: StyleAndTextTuples) -> int: + """ + Return the character width of this text fragment list. + (Take double width characters into account.) + + :param fragments: List of ``(style_str, text)`` or + ``(style_str, text, mouse_handler)`` tuples. + """ + ZeroWidthEscape = "[ZeroWidthEscape]" + return sum( + get_cwidth(c) + for item in fragments + for c in item[1] + if ZeroWidthEscape not in item[0] + ) + + +def fragment_list_to_text(fragments: StyleAndTextTuples) -> str: + """ + Concatenate all the text parts again. + + :param fragments: List of ``(style_str, text)`` or + ``(style_str, text, mouse_handler)`` tuples. + """ + ZeroWidthEscape = "[ZeroWidthEscape]" + return "".join(item[1] for item in fragments if ZeroWidthEscape not in item[0]) + + +def split_lines(fragments: StyleAndTextTuples) -> Iterable[StyleAndTextTuples]: + """ + Take a single list of (style_str, text) tuples and yield one such list for each + line. Just like str.split, this will yield at least one item. + + :param fragments: List of (style_str, text) or (style_str, text, mouse_handler) + tuples. + """ + line: StyleAndTextTuples = [] + + for style, string, *mouse_handler in fragments: + parts = string.split("\n") + + for part in parts[:-1]: + if part: + line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler))) + yield line + line = [] + + line.append(cast(OneStyleAndTextTuple, (style, parts[-1], *mouse_handler))) + + # Always yield the last line, even when this is an empty line. This ensures + # that when `fragments` ends with a newline character, an additional empty + # line is yielded. (Otherwise, there's no way to differentiate between the + # cases where `fragments` does and doesn't end with a newline.) + yield line diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/history.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/history.py new file mode 100644 index 0000000..987d717 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/history.py @@ -0,0 +1,301 @@ +""" +Implementations for the history of a `Buffer`. + +NOTE: There is no `DynamicHistory`: + This doesn't work well, because the `Buffer` needs to be able to attach + an event handler to the event when a history entry is loaded. This + loading can be done asynchronously and making the history swappable would + probably break this. +""" +import datetime +import os +import threading +from abc import ABCMeta, abstractmethod +from typing import AsyncGenerator, Iterable, List, Optional, Sequence, Tuple + +from prompt_toolkit.eventloop import get_event_loop + +__all__ = [ + "History", + "ThreadedHistory", + "DummyHistory", + "FileHistory", + "InMemoryHistory", +] + + +class History(metaclass=ABCMeta): + """ + Base ``History`` class. + + This also includes abstract methods for loading/storing history. + """ + + def __init__(self) -> None: + # In memory storage for strings. + self._loaded = False + + # History that's loaded already, in reverse order. Latest, most recent + # item first. + self._loaded_strings: List[str] = [] + + # + # Methods expected by `Buffer`. + # + + async def load(self) -> AsyncGenerator[str, None]: + """ + Load the history and yield all the entries in reverse order (latest, + most recent history entry first). + + This method can be called multiple times from the `Buffer` to + repopulate the history when prompting for a new input. So we are + responsible here for both caching, and making sure that strings that + were were appended to the history will be incorporated next time this + method is called. + """ + if not self._loaded: + self._loaded_strings = list(self.load_history_strings()) + self._loaded = True + + for item in self._loaded_strings: + yield item + + def get_strings(self) -> List[str]: + """ + Get the strings from the history that are loaded so far. + (In order. Oldest item first.) + """ + return self._loaded_strings[::-1] + + def append_string(self, string: str) -> None: + "Add string to the history." + self._loaded_strings.insert(0, string) + self.store_string(string) + + # + # Implementation for specific backends. + # + + @abstractmethod + def load_history_strings(self) -> Iterable[str]: + """ + This should be a generator that yields `str` instances. + + It should yield the most recent items first, because they are the most + important. (The history can already be used, even when it's only + partially loaded.) + """ + while False: + yield + + @abstractmethod + def store_string(self, string: str) -> None: + """ + Store the string in persistent storage. + """ + + +class ThreadedHistory(History): + """ + Wrapper around `History` implementations that run the `load()` generator in + a thread. + + Use this to increase the start-up time of prompt_toolkit applications. + History entries are available as soon as they are loaded. We don't have to + wait for everything to be loaded. + """ + + def __init__(self, history: History) -> None: + super().__init__() + + self.history = history + + self._load_thread: Optional[threading.Thread] = None + + # Lock for accessing/manipulating `_loaded_strings` and `_loaded` + # together in a consistent state. + self._lock = threading.Lock() + + # Events created by each `load()` call. Used to wait for new history + # entries from the loader thread. + self._string_load_events: List[threading.Event] = [] + + async def load(self) -> AsyncGenerator[str, None]: + """ + Like `History.load(), but call `self.load_history_strings()` in a + background thread. + """ + # Start the load thread, if this is called for the first time. + if not self._load_thread: + self._load_thread = threading.Thread( + target=self._in_load_thread, + daemon=True, + ) + self._load_thread.start() + + # Consume the `_loaded_strings` list, using asyncio. + loop = get_event_loop() + + # Create threading Event so that we can wait for new items. + event = threading.Event() + event.set() + self._string_load_events.append(event) + + items_yielded = 0 + + try: + while True: + # Wait for new items to be available. + # (Use a timeout, because the executor thread is not a daemon + # thread. The "slow-history.py" example would otherwise hang if + # Control-C is pressed before the history is fully loaded, + # because there's still this non-daemon executor thread waiting + # for this event.) + got_timeout = await loop.run_in_executor( + None, lambda: event.wait(timeout=0.5) + ) + if not got_timeout: + continue + + # Read new items (in lock). + def in_executor() -> Tuple[List[str], bool]: + with self._lock: + new_items = self._loaded_strings[items_yielded:] + done = self._loaded + event.clear() + return new_items, done + + new_items, done = await loop.run_in_executor(None, in_executor) + + items_yielded += len(new_items) + + for item in new_items: + yield item + + if done: + break + finally: + self._string_load_events.remove(event) + + def _in_load_thread(self) -> None: + try: + # Start with an empty list. In case `append_string()` was called + # before `load()` happened. Then `.store_string()` will have + # written these entries back to disk and we will reload it. + self._loaded_strings = [] + + for item in self.history.load_history_strings(): + with self._lock: + self._loaded_strings.append(item) + + for event in self._string_load_events: + event.set() + finally: + with self._lock: + self._loaded = True + for event in self._string_load_events: + event.set() + + def append_string(self, string: str) -> None: + with self._lock: + self._loaded_strings.insert(0, string) + self.store_string(string) + + # All of the following are proxied to `self.history`. + + def load_history_strings(self) -> Iterable[str]: + return self.history.load_history_strings() + + def store_string(self, string: str) -> None: + self.history.store_string(string) + + def __repr__(self) -> str: + return f"ThreadedHistory({self.history!r})" + + +class InMemoryHistory(History): + """ + :class:`.History` class that keeps a list of all strings in memory. + + In order to prepopulate the history, it's possible to call either + `append_string` for all items or pass a list of strings to `__init__` here. + """ + + def __init__(self, history_strings: Optional[Sequence[str]] = None) -> None: + super().__init__() + # Emulating disk storage. + if history_strings is None: + self._storage = [] + else: + self._storage = list(history_strings) + + def load_history_strings(self) -> Iterable[str]: + yield from self._storage[::-1] + + def store_string(self, string: str) -> None: + self._storage.append(string) + + +class DummyHistory(History): + """ + :class:`.History` object that doesn't remember anything. + """ + + def load_history_strings(self) -> Iterable[str]: + return [] + + def store_string(self, string: str) -> None: + pass + + def append_string(self, string: str) -> None: + # Don't remember this. + pass + + +class FileHistory(History): + """ + :class:`.History` class that stores all strings in a file. + """ + + def __init__(self, filename: str) -> None: + self.filename = filename + super().__init__() + + def load_history_strings(self) -> Iterable[str]: + strings: List[str] = [] + lines: List[str] = [] + + def add() -> None: + if lines: + # Join and drop trailing newline. + string = "".join(lines)[:-1] + + strings.append(string) + + if os.path.exists(self.filename): + with open(self.filename, "rb") as f: + for line_bytes in f: + line = line_bytes.decode("utf-8", errors="replace") + + if line.startswith("+"): + lines.append(line[1:]) + else: + add() + lines = [] + + add() + + # Reverse the order, because newest items have to go first. + return reversed(strings) + + def store_string(self, string: str) -> None: + # Save to file. + with open(self.filename, "ab") as f: + + def write(t: str) -> None: + f.write(t.encode("utf-8")) + + write("\n# %s\n" % datetime.datetime.now()) + for line in string.split("\n"): + write("+%s\n" % line) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__init__.py new file mode 100644 index 0000000..421d4cc --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__init__.py @@ -0,0 +1,11 @@ +from .base import DummyInput, Input +from .defaults import create_input, create_pipe_input + +__all__ = [ + # Base. + "Input", + "DummyInput", + # Defaults. + "create_input", + "create_pipe_input", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..a3bb300 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/ansi_escape_sequences.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/ansi_escape_sequences.cpython-38.pyc new file mode 100644 index 0000000..27abde8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/ansi_escape_sequences.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..d3fd824 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/defaults.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/defaults.cpython-38.pyc new file mode 100644 index 0000000..0211c06 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/defaults.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/posix_pipe.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/posix_pipe.cpython-38.pyc new file mode 100644 index 0000000..643d4d1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/posix_pipe.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/posix_utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/posix_utils.cpython-38.pyc new file mode 100644 index 0000000..777fef4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/posix_utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/typeahead.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/typeahead.cpython-38.pyc new file mode 100644 index 0000000..80408c8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/typeahead.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/vt100.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/vt100.cpython-38.pyc new file mode 100644 index 0000000..626d50d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/vt100.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/vt100_parser.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/vt100_parser.cpython-38.pyc new file mode 100644 index 0000000..4d2550f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/vt100_parser.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/win32.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/win32.cpython-38.pyc new file mode 100644 index 0000000..4ef7d24 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/win32.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/win32_pipe.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/win32_pipe.cpython-38.pyc new file mode 100644 index 0000000..be6ae5f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/__pycache__/win32_pipe.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/ansi_escape_sequences.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/ansi_escape_sequences.py new file mode 100644 index 0000000..2e6c5b9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/ansi_escape_sequences.py @@ -0,0 +1,343 @@ +""" +Mappings from VT100 (ANSI) escape sequences to the corresponding prompt_toolkit +keys. + +We are not using the terminfo/termcap databases to detect the ANSI escape +sequences for the input. Instead, we recognize 99% of the most common +sequences. This works well, because in practice, every modern terminal is +mostly Xterm compatible. + +Some useful docs: +- Mintty: https://github.com/mintty/mintty/blob/master/wiki/Keycodes.md +""" +from typing import Dict, Tuple, Union + +from ..keys import Keys + +__all__ = [ + "ANSI_SEQUENCES", + "REVERSE_ANSI_SEQUENCES", +] + +# Mapping of vt100 escape codes to Keys. +ANSI_SEQUENCES: Dict[str, Union[Keys, Tuple[Keys, ...]]] = { + # Control keys. + "\x00": Keys.ControlAt, # Control-At (Also for Ctrl-Space) + "\x01": Keys.ControlA, # Control-A (home) + "\x02": Keys.ControlB, # Control-B (emacs cursor left) + "\x03": Keys.ControlC, # Control-C (interrupt) + "\x04": Keys.ControlD, # Control-D (exit) + "\x05": Keys.ControlE, # Control-E (end) + "\x06": Keys.ControlF, # Control-F (cursor forward) + "\x07": Keys.ControlG, # Control-G + "\x08": Keys.ControlH, # Control-H (8) (Identical to '\b') + "\x09": Keys.ControlI, # Control-I (9) (Identical to '\t') + "\x0a": Keys.ControlJ, # Control-J (10) (Identical to '\n') + "\x0b": Keys.ControlK, # Control-K (delete until end of line; vertical tab) + "\x0c": Keys.ControlL, # Control-L (clear; form feed) + "\x0d": Keys.ControlM, # Control-M (13) (Identical to '\r') + "\x0e": Keys.ControlN, # Control-N (14) (history forward) + "\x0f": Keys.ControlO, # Control-O (15) + "\x10": Keys.ControlP, # Control-P (16) (history back) + "\x11": Keys.ControlQ, # Control-Q + "\x12": Keys.ControlR, # Control-R (18) (reverse search) + "\x13": Keys.ControlS, # Control-S (19) (forward search) + "\x14": Keys.ControlT, # Control-T + "\x15": Keys.ControlU, # Control-U + "\x16": Keys.ControlV, # Control-V + "\x17": Keys.ControlW, # Control-W + "\x18": Keys.ControlX, # Control-X + "\x19": Keys.ControlY, # Control-Y (25) + "\x1a": Keys.ControlZ, # Control-Z + "\x1b": Keys.Escape, # Also Control-[ + "\x9b": Keys.ShiftEscape, + "\x1c": Keys.ControlBackslash, # Both Control-\ (also Ctrl-| ) + "\x1d": Keys.ControlSquareClose, # Control-] + "\x1e": Keys.ControlCircumflex, # Control-^ + "\x1f": Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hyphen.) + # ASCII Delete (0x7f) + # Vt220 (and Linux terminal) send this when pressing backspace. We map this + # to ControlH, because that will make it easier to create key bindings that + # work everywhere, with the trade-off that it's no longer possible to + # handle backspace and control-h individually for the few terminals that + # support it. (Most terminals send ControlH when backspace is pressed.) + # See: http://www.ibb.net/~anne/keyboard.html + "\x7f": Keys.ControlH, + # -- + # Various + "\x1b[1~": Keys.Home, # tmux + "\x1b[2~": Keys.Insert, + "\x1b[3~": Keys.Delete, + "\x1b[4~": Keys.End, # tmux + "\x1b[5~": Keys.PageUp, + "\x1b[6~": Keys.PageDown, + "\x1b[7~": Keys.Home, # xrvt + "\x1b[8~": Keys.End, # xrvt + "\x1b[Z": Keys.BackTab, # shift + tab + "\x1b\x09": Keys.BackTab, # Linux console + "\x1b[~": Keys.BackTab, # Windows console + # -- + # Function keys. + "\x1bOP": Keys.F1, + "\x1bOQ": Keys.F2, + "\x1bOR": Keys.F3, + "\x1bOS": Keys.F4, + "\x1b[[A": Keys.F1, # Linux console. + "\x1b[[B": Keys.F2, # Linux console. + "\x1b[[C": Keys.F3, # Linux console. + "\x1b[[D": Keys.F4, # Linux console. + "\x1b[[E": Keys.F5, # Linux console. + "\x1b[11~": Keys.F1, # rxvt-unicode + "\x1b[12~": Keys.F2, # rxvt-unicode + "\x1b[13~": Keys.F3, # rxvt-unicode + "\x1b[14~": Keys.F4, # rxvt-unicode + "\x1b[15~": Keys.F5, + "\x1b[17~": Keys.F6, + "\x1b[18~": Keys.F7, + "\x1b[19~": Keys.F8, + "\x1b[20~": Keys.F9, + "\x1b[21~": Keys.F10, + "\x1b[23~": Keys.F11, + "\x1b[24~": Keys.F12, + "\x1b[25~": Keys.F13, + "\x1b[26~": Keys.F14, + "\x1b[28~": Keys.F15, + "\x1b[29~": Keys.F16, + "\x1b[31~": Keys.F17, + "\x1b[32~": Keys.F18, + "\x1b[33~": Keys.F19, + "\x1b[34~": Keys.F20, + # Xterm + "\x1b[1;2P": Keys.F13, + "\x1b[1;2Q": Keys.F14, + # '\x1b[1;2R': Keys.F15, # Conflicts with CPR response. + "\x1b[1;2S": Keys.F16, + "\x1b[15;2~": Keys.F17, + "\x1b[17;2~": Keys.F18, + "\x1b[18;2~": Keys.F19, + "\x1b[19;2~": Keys.F20, + "\x1b[20;2~": Keys.F21, + "\x1b[21;2~": Keys.F22, + "\x1b[23;2~": Keys.F23, + "\x1b[24;2~": Keys.F24, + # -- + # CSI 27 disambiguated modified "other" keys (xterm) + # Ref: https://invisible-island.net/xterm/modified-keys.html + # These are currently unsupported, so just re-map some common ones to the + # unmodified versions + "\x1b[27;2;13~": Keys.ControlM, # Shift + Enter + "\x1b[27;5;13~": Keys.ControlM, # Ctrl + Enter + "\x1b[27;6;13~": Keys.ControlM, # Ctrl + Shift + Enter + # -- + # Control + function keys. + "\x1b[1;5P": Keys.ControlF1, + "\x1b[1;5Q": Keys.ControlF2, + # "\x1b[1;5R": Keys.ControlF3, # Conflicts with CPR response. + "\x1b[1;5S": Keys.ControlF4, + "\x1b[15;5~": Keys.ControlF5, + "\x1b[17;5~": Keys.ControlF6, + "\x1b[18;5~": Keys.ControlF7, + "\x1b[19;5~": Keys.ControlF8, + "\x1b[20;5~": Keys.ControlF9, + "\x1b[21;5~": Keys.ControlF10, + "\x1b[23;5~": Keys.ControlF11, + "\x1b[24;5~": Keys.ControlF12, + "\x1b[1;6P": Keys.ControlF13, + "\x1b[1;6Q": Keys.ControlF14, + # "\x1b[1;6R": Keys.ControlF15, # Conflicts with CPR response. + "\x1b[1;6S": Keys.ControlF16, + "\x1b[15;6~": Keys.ControlF17, + "\x1b[17;6~": Keys.ControlF18, + "\x1b[18;6~": Keys.ControlF19, + "\x1b[19;6~": Keys.ControlF20, + "\x1b[20;6~": Keys.ControlF21, + "\x1b[21;6~": Keys.ControlF22, + "\x1b[23;6~": Keys.ControlF23, + "\x1b[24;6~": Keys.ControlF24, + # -- + # Tmux (Win32 subsystem) sends the following scroll events. + "\x1b[62~": Keys.ScrollUp, + "\x1b[63~": Keys.ScrollDown, + "\x1b[200~": Keys.BracketedPaste, # Start of bracketed paste. + # -- + # Sequences generated by numpad 5. Not sure what it means. (It doesn't + # appear in 'infocmp'. Just ignore. + "\x1b[E": Keys.Ignore, # Xterm. + "\x1b[G": Keys.Ignore, # Linux console. + # -- + # Meta/control/escape + pageup/pagedown/insert/delete. + "\x1b[3;2~": Keys.ShiftDelete, # xterm, gnome-terminal. + "\x1b[5;2~": Keys.ShiftPageUp, + "\x1b[6;2~": Keys.ShiftPageDown, + "\x1b[2;3~": (Keys.Escape, Keys.Insert), + "\x1b[3;3~": (Keys.Escape, Keys.Delete), + "\x1b[5;3~": (Keys.Escape, Keys.PageUp), + "\x1b[6;3~": (Keys.Escape, Keys.PageDown), + "\x1b[2;4~": (Keys.Escape, Keys.ShiftInsert), + "\x1b[3;4~": (Keys.Escape, Keys.ShiftDelete), + "\x1b[5;4~": (Keys.Escape, Keys.ShiftPageUp), + "\x1b[6;4~": (Keys.Escape, Keys.ShiftPageDown), + "\x1b[3;5~": Keys.ControlDelete, # xterm, gnome-terminal. + "\x1b[5;5~": Keys.ControlPageUp, + "\x1b[6;5~": Keys.ControlPageDown, + "\x1b[3;6~": Keys.ControlShiftDelete, + "\x1b[5;6~": Keys.ControlShiftPageUp, + "\x1b[6;6~": Keys.ControlShiftPageDown, + "\x1b[2;7~": (Keys.Escape, Keys.ControlInsert), + "\x1b[5;7~": (Keys.Escape, Keys.ControlPageDown), + "\x1b[6;7~": (Keys.Escape, Keys.ControlPageDown), + "\x1b[2;8~": (Keys.Escape, Keys.ControlShiftInsert), + "\x1b[5;8~": (Keys.Escape, Keys.ControlShiftPageDown), + "\x1b[6;8~": (Keys.Escape, Keys.ControlShiftPageDown), + # -- + # Arrows. + # (Normal cursor mode). + "\x1b[A": Keys.Up, + "\x1b[B": Keys.Down, + "\x1b[C": Keys.Right, + "\x1b[D": Keys.Left, + "\x1b[H": Keys.Home, + "\x1b[F": Keys.End, + # Tmux sends following keystrokes when control+arrow is pressed, but for + # Emacs ansi-term sends the same sequences for normal arrow keys. Consider + # it a normal arrow press, because that's more important. + # (Application cursor mode). + "\x1bOA": Keys.Up, + "\x1bOB": Keys.Down, + "\x1bOC": Keys.Right, + "\x1bOD": Keys.Left, + "\x1bOF": Keys.End, + "\x1bOH": Keys.Home, + # Shift + arrows. + "\x1b[1;2A": Keys.ShiftUp, + "\x1b[1;2B": Keys.ShiftDown, + "\x1b[1;2C": Keys.ShiftRight, + "\x1b[1;2D": Keys.ShiftLeft, + "\x1b[1;2F": Keys.ShiftEnd, + "\x1b[1;2H": Keys.ShiftHome, + # Meta + arrow keys. Several terminals handle this differently. + # The following sequences are for xterm and gnome-terminal. + # (Iterm sends ESC followed by the normal arrow_up/down/left/right + # sequences, and the OSX Terminal sends ESCb and ESCf for "alt + # arrow_left" and "alt arrow_right." We don't handle these + # explicitly, in here, because would could not distinguish between + # pressing ESC (to go to Vi navigation mode), followed by just the + # 'b' or 'f' key. These combinations are handled in + # the input processor.) + "\x1b[1;3A": (Keys.Escape, Keys.Up), + "\x1b[1;3B": (Keys.Escape, Keys.Down), + "\x1b[1;3C": (Keys.Escape, Keys.Right), + "\x1b[1;3D": (Keys.Escape, Keys.Left), + "\x1b[1;3F": (Keys.Escape, Keys.End), + "\x1b[1;3H": (Keys.Escape, Keys.Home), + # Alt+shift+number. + "\x1b[1;4A": (Keys.Escape, Keys.ShiftDown), + "\x1b[1;4B": (Keys.Escape, Keys.ShiftUp), + "\x1b[1;4C": (Keys.Escape, Keys.ShiftRight), + "\x1b[1;4D": (Keys.Escape, Keys.ShiftLeft), + "\x1b[1;4F": (Keys.Escape, Keys.ShiftEnd), + "\x1b[1;4H": (Keys.Escape, Keys.ShiftHome), + # Control + arrows. + "\x1b[1;5A": Keys.ControlUp, # Cursor Mode + "\x1b[1;5B": Keys.ControlDown, # Cursor Mode + "\x1b[1;5C": Keys.ControlRight, # Cursor Mode + "\x1b[1;5D": Keys.ControlLeft, # Cursor Mode + "\x1b[1;5F": Keys.ControlEnd, + "\x1b[1;5H": Keys.ControlHome, + # Tmux sends following keystrokes when control+arrow is pressed, but for + # Emacs ansi-term sends the same sequences for normal arrow keys. Consider + # it a normal arrow press, because that's more important. + "\x1b[5A": Keys.ControlUp, + "\x1b[5B": Keys.ControlDown, + "\x1b[5C": Keys.ControlRight, + "\x1b[5D": Keys.ControlLeft, + "\x1bOc": Keys.ControlRight, # rxvt + "\x1bOd": Keys.ControlLeft, # rxvt + # Control + shift + arrows. + "\x1b[1;6A": Keys.ControlShiftDown, + "\x1b[1;6B": Keys.ControlShiftUp, + "\x1b[1;6C": Keys.ControlShiftRight, + "\x1b[1;6D": Keys.ControlShiftLeft, + "\x1b[1;6F": Keys.ControlShiftEnd, + "\x1b[1;6H": Keys.ControlShiftHome, + # Control + Meta + arrows. + "\x1b[1;7A": (Keys.Escape, Keys.ControlDown), + "\x1b[1;7B": (Keys.Escape, Keys.ControlUp), + "\x1b[1;7C": (Keys.Escape, Keys.ControlRight), + "\x1b[1;7D": (Keys.Escape, Keys.ControlLeft), + "\x1b[1;7F": (Keys.Escape, Keys.ControlEnd), + "\x1b[1;7H": (Keys.Escape, Keys.ControlHome), + # Meta + Shift + arrows. + "\x1b[1;8A": (Keys.Escape, Keys.ControlShiftDown), + "\x1b[1;8B": (Keys.Escape, Keys.ControlShiftUp), + "\x1b[1;8C": (Keys.Escape, Keys.ControlShiftRight), + "\x1b[1;8D": (Keys.Escape, Keys.ControlShiftLeft), + "\x1b[1;8F": (Keys.Escape, Keys.ControlShiftEnd), + "\x1b[1;8H": (Keys.Escape, Keys.ControlShiftHome), + # Meta + arrow on (some?) Macs when using iTerm defaults (see issue #483). + "\x1b[1;9A": (Keys.Escape, Keys.Up), + "\x1b[1;9B": (Keys.Escape, Keys.Down), + "\x1b[1;9C": (Keys.Escape, Keys.Right), + "\x1b[1;9D": (Keys.Escape, Keys.Left), + # -- + # Control/shift/meta + number in mintty. + # (c-2 will actually send c-@ and c-6 will send c-^.) + "\x1b[1;5p": Keys.Control0, + "\x1b[1;5q": Keys.Control1, + "\x1b[1;5r": Keys.Control2, + "\x1b[1;5s": Keys.Control3, + "\x1b[1;5t": Keys.Control4, + "\x1b[1;5u": Keys.Control5, + "\x1b[1;5v": Keys.Control6, + "\x1b[1;5w": Keys.Control7, + "\x1b[1;5x": Keys.Control8, + "\x1b[1;5y": Keys.Control9, + "\x1b[1;6p": Keys.ControlShift0, + "\x1b[1;6q": Keys.ControlShift1, + "\x1b[1;6r": Keys.ControlShift2, + "\x1b[1;6s": Keys.ControlShift3, + "\x1b[1;6t": Keys.ControlShift4, + "\x1b[1;6u": Keys.ControlShift5, + "\x1b[1;6v": Keys.ControlShift6, + "\x1b[1;6w": Keys.ControlShift7, + "\x1b[1;6x": Keys.ControlShift8, + "\x1b[1;6y": Keys.ControlShift9, + "\x1b[1;7p": (Keys.Escape, Keys.Control0), + "\x1b[1;7q": (Keys.Escape, Keys.Control1), + "\x1b[1;7r": (Keys.Escape, Keys.Control2), + "\x1b[1;7s": (Keys.Escape, Keys.Control3), + "\x1b[1;7t": (Keys.Escape, Keys.Control4), + "\x1b[1;7u": (Keys.Escape, Keys.Control5), + "\x1b[1;7v": (Keys.Escape, Keys.Control6), + "\x1b[1;7w": (Keys.Escape, Keys.Control7), + "\x1b[1;7x": (Keys.Escape, Keys.Control8), + "\x1b[1;7y": (Keys.Escape, Keys.Control9), + "\x1b[1;8p": (Keys.Escape, Keys.ControlShift0), + "\x1b[1;8q": (Keys.Escape, Keys.ControlShift1), + "\x1b[1;8r": (Keys.Escape, Keys.ControlShift2), + "\x1b[1;8s": (Keys.Escape, Keys.ControlShift3), + "\x1b[1;8t": (Keys.Escape, Keys.ControlShift4), + "\x1b[1;8u": (Keys.Escape, Keys.ControlShift5), + "\x1b[1;8v": (Keys.Escape, Keys.ControlShift6), + "\x1b[1;8w": (Keys.Escape, Keys.ControlShift7), + "\x1b[1;8x": (Keys.Escape, Keys.ControlShift8), + "\x1b[1;8y": (Keys.Escape, Keys.ControlShift9), +} + + +def _get_reverse_ansi_sequences() -> Dict[Keys, str]: + """ + Create a dictionary that maps prompt_toolkit keys back to the VT100 escape + sequences. + """ + result: Dict[Keys, str] = {} + + for sequence, key in ANSI_SEQUENCES.items(): + if not isinstance(key, tuple): + if key not in result: + result[key] = sequence + + return result + + +REVERSE_ANSI_SEQUENCES = _get_reverse_ansi_sequences() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/base.py new file mode 100644 index 0000000..9885a37 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/base.py @@ -0,0 +1,144 @@ +""" +Abstraction of CLI Input. +""" +from abc import ABCMeta, abstractmethod, abstractproperty +from contextlib import contextmanager +from typing import Callable, ContextManager, Generator, List + +from prompt_toolkit.key_binding import KeyPress + +__all__ = [ + "Input", + "DummyInput", +] + + +class Input(metaclass=ABCMeta): + """ + Abstraction for any input. + + An instance of this class can be given to the constructor of a + :class:`~prompt_toolkit.application.Application` and will also be + passed to the :class:`~prompt_toolkit.eventloop.base.EventLoop`. + """ + + @abstractmethod + def fileno(self) -> int: + """ + Fileno for putting this in an event loop. + """ + + @abstractmethod + def typeahead_hash(self) -> str: + """ + Identifier for storing type ahead key presses. + """ + + @abstractmethod + def read_keys(self) -> List[KeyPress]: + """ + Return a list of Key objects which are read/parsed from the input. + """ + + def flush_keys(self) -> List[KeyPress]: + """ + Flush the underlying parser. and return the pending keys. + (Used for vt100 input.) + """ + return [] + + def flush(self) -> None: + "The event loop can call this when the input has to be flushed." + pass + + @abstractproperty + def closed(self) -> bool: + "Should be true when the input stream is closed." + return False + + @abstractmethod + def raw_mode(self) -> ContextManager[None]: + """ + Context manager that turns the input into raw mode. + """ + + @abstractmethod + def cooked_mode(self) -> ContextManager[None]: + """ + Context manager that turns the input into cooked mode. + """ + + @abstractmethod + def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: + """ + Return a context manager that makes this input active in the current + event loop. + """ + + @abstractmethod + def detach(self) -> ContextManager[None]: + """ + Return a context manager that makes sure that this input is not active + in the current event loop. + """ + + def close(self) -> None: + "Close input." + pass + + +class PipeInput(Input): + """ + Abstraction for pipe input. + """ + + @abstractmethod + def send_bytes(self, data: bytes) -> None: + """Feed byte string into the pipe""" + + @abstractmethod + def send_text(self, data: str) -> None: + """Feed a text string into the pipe""" + + +class DummyInput(Input): + """ + Input for use in a `DummyApplication` + """ + + def fileno(self) -> int: + raise NotImplementedError + + def typeahead_hash(self) -> str: + return "dummy-%s" % id(self) + + def read_keys(self) -> List[KeyPress]: + return [] + + @property + def closed(self) -> bool: + return True + + def raw_mode(self) -> ContextManager[None]: + return _dummy_context_manager() + + def cooked_mode(self) -> ContextManager[None]: + return _dummy_context_manager() + + def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: + # Call the callback immediately once after attaching. + # This tells the callback to call `read_keys` and check the + # `input.closed` flag, after which it won't receive any keys, but knows + # that `EOFError` should be raised. This unblocks `read_from_input` in + # `application.py`. + input_ready_callback() + + return _dummy_context_manager() + + def detach(self) -> ContextManager[None]: + return _dummy_context_manager() + + +@contextmanager +def _dummy_context_manager() -> Generator[None, None, None]: + yield diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/defaults.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/defaults.py new file mode 100644 index 0000000..347f8c6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/defaults.py @@ -0,0 +1,63 @@ +import sys +from typing import Optional, TextIO + +from prompt_toolkit.utils import is_windows + +from .base import DummyInput, Input, PipeInput + +__all__ = [ + "create_input", + "create_pipe_input", +] + + +def create_input( + stdin: Optional[TextIO] = None, always_prefer_tty: bool = False +) -> Input: + """ + Create the appropriate `Input` object for the current os/environment. + + :param always_prefer_tty: When set, if `sys.stdin` is connected to a Unix + `pipe`, check whether `sys.stdout` or `sys.stderr` are connected to a + pseudo terminal. If so, open the tty for reading instead of reading for + `sys.stdin`. (We can open `stdout` or `stderr` for reading, this is how + a `$PAGER` works.) + """ + if is_windows(): + from .win32 import Win32Input + + # If `stdin` was assigned `None` (which happens with pythonw.exe), use + # a `DummyInput`. This triggers `EOFError` in the application code. + if stdin is None and sys.stdin is None: + return DummyInput() + + return Win32Input(stdin or sys.stdin) + else: + from .vt100 import Vt100Input + + # If no input TextIO is given, use stdin/stdout. + if stdin is None: + stdin = sys.stdin + + if always_prefer_tty: + for io in [sys.stdin, sys.stdout, sys.stderr]: + if io.isatty(): + stdin = io + break + + return Vt100Input(stdin) + + +def create_pipe_input() -> PipeInput: + """ + Create an input pipe. + This is mostly useful for unit testing. + """ + if is_windows(): + from .win32_pipe import Win32PipeInput + + return Win32PipeInput() + else: + from .posix_pipe import PosixPipeInput + + return PosixPipeInput() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/posix_pipe.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/posix_pipe.py new file mode 100644 index 0000000..22dd7be --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/posix_pipe.py @@ -0,0 +1,72 @@ +import os +from typing import ContextManager, TextIO, cast + +from ..utils import DummyContext +from .base import PipeInput +from .vt100 import Vt100Input + +__all__ = [ + "PosixPipeInput", +] + + +class PosixPipeInput(Vt100Input, PipeInput): + """ + Input that is send through a pipe. + This is useful if we want to send the input programmatically into the + application. Mostly useful for unit testing. + + Usage:: + + input = PosixPipeInput() + input.send_text('inputdata') + """ + + _id = 0 + + def __init__(self, text: str = "") -> None: + self._r, self._w = os.pipe() + + class Stdin: + encoding = "utf-8" + + def isatty(stdin) -> bool: + return True + + def fileno(stdin) -> int: + return self._r + + super().__init__(cast(TextIO, Stdin())) + self.send_text(text) + + # Identifier for every PipeInput for the hash. + self.__class__._id += 1 + self._id = self.__class__._id + + def send_bytes(self, data: bytes) -> None: + os.write(self._w, data) + + def send_text(self, data: str) -> None: + "Send text to the input." + os.write(self._w, data.encode("utf-8")) + + def raw_mode(self) -> ContextManager[None]: + return DummyContext() + + def cooked_mode(self) -> ContextManager[None]: + return DummyContext() + + def close(self) -> None: + "Close pipe fds." + os.close(self._r) + os.close(self._w) + + # We should assign `None` to 'self._r` and 'self._w', + # The event loop still needs to know the the fileno for this input in order + # to properly remove it from the selectors. + + def typeahead_hash(self) -> str: + """ + This needs to be unique for every `PipeInput`. + """ + return f"pipe-input-{self._id}" diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/posix_utils.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/posix_utils.py new file mode 100644 index 0000000..7cf31ee --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/posix_utils.py @@ -0,0 +1,95 @@ +import os +import select +from codecs import getincrementaldecoder + +__all__ = [ + "PosixStdinReader", +] + + +class PosixStdinReader: + """ + Wrapper around stdin which reads (nonblocking) the next available 1024 + bytes and decodes it. + + Note that you can't be sure that the input file is closed if the ``read`` + function returns an empty string. When ``errors=ignore`` is passed, + ``read`` can return an empty string if all malformed input was replaced by + an empty string. (We can't block here and wait for more input.) So, because + of that, check the ``closed`` attribute, to be sure that the file has been + closed. + + :param stdin_fd: File descriptor from which we read. + :param errors: Can be 'ignore', 'strict' or 'replace'. + On Python3, this can be 'surrogateescape', which is the default. + + 'surrogateescape' is preferred, because this allows us to transfer + unrecognised bytes to the key bindings. Some terminals, like lxterminal + and Guake, use the 'Mxx' notation to send mouse events, where each 'x' + can be any possible byte. + """ + + # By default, we want to 'ignore' errors here. The input stream can be full + # of junk. One occurrence of this that I had was when using iTerm2 on OS X, + # with "Option as Meta" checked (You should choose "Option as +Esc".) + + def __init__( + self, stdin_fd: int, errors: str = "surrogateescape", encoding: str = "utf-8" + ) -> None: + self.stdin_fd = stdin_fd + self.errors = errors + + # Create incremental decoder for decoding stdin. + # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because + # it could be that we are in the middle of a utf-8 byte sequence. + self._stdin_decoder_cls = getincrementaldecoder(encoding) + self._stdin_decoder = self._stdin_decoder_cls(errors=errors) + + #: True when there is nothing anymore to read. + self.closed = False + + def read(self, count: int = 1024) -> str: + # By default we choose a rather small chunk size, because reading + # big amounts of input at once, causes the event loop to process + # all these key bindings also at once without going back to the + # loop. This will make the application feel unresponsive. + """ + Read the input and return it as a string. + + Return the text. Note that this can return an empty string, even when + the input stream was not yet closed. This means that something went + wrong during the decoding. + """ + if self.closed: + return "" + + # Check whether there is some input to read. `os.read` would block + # otherwise. + # (Actually, the event loop is responsible to make sure that this + # function is only called when there is something to read, but for some + # reason this happens in certain situations.) + try: + if not select.select([self.stdin_fd], [], [], 0)[0]: + return "" + except OSError: + # Happens for instance when the file descriptor was closed. + # (We had this in ptterm, where the FD became ready, a callback was + # scheduled, but in the meantime another callback closed it already.) + self.closed = True + + # Note: the following works better than wrapping `self.stdin` like + # `codecs.getreader('utf-8')(stdin)` and doing `read(1)`. + # Somehow that causes some latency when the escape + # character is pressed. (Especially on combination with the `select`.) + try: + data = os.read(self.stdin_fd, count) + + # Nothing more to read, stream is closed. + if data == b"": + self.closed = True + return "" + except OSError: + # In case of SIGWINCH + data = b"" + + return self._stdin_decoder.decode(data) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/typeahead.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/typeahead.py new file mode 100644 index 0000000..a3d7866 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/typeahead.py @@ -0,0 +1,76 @@ +r""" +Store input key strokes if we did read more than was required. + +The input classes `Vt100Input` and `Win32Input` read the input text in chunks +of a few kilobytes. This means that if we read input from stdin, it could be +that we read a couple of lines (with newlines in between) at once. + +This creates a problem: potentially, we read too much from stdin. Sometimes +people paste several lines at once because they paste input in a REPL and +expect each input() call to process one line. Or they rely on type ahead +because the application can't keep up with the processing. + +However, we need to read input in bigger chunks. We need this mostly to support +pasting of larger chunks of text. We don't want everything to become +unresponsive because we: + - read one character; + - parse one character; + - call the key binding, which does a string operation with one character; + - and render the user interface. +Doing text operations on single characters is very inefficient in Python, so we +prefer to work on bigger chunks of text. This is why we have to read the input +in bigger chunks. + +Further, line buffering is also not an option, because it doesn't work well in +the architecture. We use lower level Posix APIs, that work better with the +event loop and so on. In fact, there is also nothing that defines that only \n +can accept the input, you could create a key binding for any key to accept the +input. + +To support type ahead, this module will store all the key strokes that were +read too early, so that they can be feed into to the next `prompt()` call or to +the next prompt_toolkit `Application`. +""" +from collections import defaultdict +from typing import Dict, List + +from ..key_binding import KeyPress +from .base import Input + +__all__ = [ + "store_typeahead", + "get_typeahead", + "clear_typeahead", +] + +_buffer: Dict[str, List[KeyPress]] = defaultdict(list) + + +def store_typeahead(input_obj: Input, key_presses: List[KeyPress]) -> None: + """ + Insert typeahead key presses for the given input. + """ + global _buffer + key = input_obj.typeahead_hash() + _buffer[key].extend(key_presses) + + +def get_typeahead(input_obj: Input) -> List[KeyPress]: + """ + Retrieve typeahead and reset the buffer for this input. + """ + global _buffer + + key = input_obj.typeahead_hash() + result = _buffer[key] + _buffer[key] = [] + return result + + +def clear_typeahead(input_obj: Input) -> None: + """ + Clear typeahead buffer. + """ + global _buffer + key = input_obj.typeahead_hash() + _buffer[key] = [] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/vt100.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/vt100.py new file mode 100644 index 0000000..639d372 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/vt100.py @@ -0,0 +1,317 @@ +import contextlib +import io +import sys +import termios +import tty +from asyncio import AbstractEventLoop +from typing import ( + Callable, + ContextManager, + Dict, + Generator, + List, + Optional, + Set, + TextIO, + Tuple, + Union, +) + +from prompt_toolkit.eventloop import get_event_loop + +from ..key_binding import KeyPress +from .base import Input +from .posix_utils import PosixStdinReader +from .vt100_parser import Vt100Parser + +__all__ = [ + "Vt100Input", + "raw_mode", + "cooked_mode", +] + + +class Vt100Input(Input): + """ + Vt100 input for Posix systems. + (This uses a posix file descriptor that can be registered in the event loop.) + """ + + # For the error messages. Only display "Input is not a terminal" once per + # file descriptor. + _fds_not_a_terminal: Set[int] = set() + + def __init__(self, stdin: TextIO) -> None: + # Test whether the given input object has a file descriptor. + # (Idle reports stdin to be a TTY, but fileno() is not implemented.) + try: + # This should not raise, but can return 0. + stdin.fileno() + except io.UnsupportedOperation as e: + if "idlelib.run" in sys.modules: + raise io.UnsupportedOperation( + "Stdin is not a terminal. Running from Idle is not supported." + ) from e + else: + raise io.UnsupportedOperation("Stdin is not a terminal.") from e + + # Even when we have a file descriptor, it doesn't mean it's a TTY. + # Normally, this requires a real TTY device, but people instantiate + # this class often during unit tests as well. They use for instance + # pexpect to pipe data into an application. For convenience, we print + # an error message and go on. + isatty = stdin.isatty() + fd = stdin.fileno() + + if not isatty and fd not in Vt100Input._fds_not_a_terminal: + msg = "Warning: Input is not a terminal (fd=%r).\n" + sys.stderr.write(msg % fd) + sys.stderr.flush() + Vt100Input._fds_not_a_terminal.add(fd) + + # + self.stdin = stdin + + # Create a backup of the fileno(). We want this to work even if the + # underlying file is closed, so that `typeahead_hash()` keeps working. + self._fileno = stdin.fileno() + + self._buffer: List[KeyPress] = [] # Buffer to collect the Key objects. + self.stdin_reader = PosixStdinReader(self._fileno, encoding=stdin.encoding) + self.vt100_parser = Vt100Parser( + lambda key_press: self._buffer.append(key_press) + ) + + def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: + """ + Return a context manager that makes this input active in the current + event loop. + """ + return _attached_input(self, input_ready_callback) + + def detach(self) -> ContextManager[None]: + """ + Return a context manager that makes sure that this input is not active + in the current event loop. + """ + return _detached_input(self) + + def read_keys(self) -> List[KeyPress]: + "Read list of KeyPress." + # Read text from stdin. + data = self.stdin_reader.read() + + # Pass it through our vt100 parser. + self.vt100_parser.feed(data) + + # Return result. + result = self._buffer + self._buffer = [] + return result + + def flush_keys(self) -> List[KeyPress]: + """ + Flush pending keys and return them. + (Used for flushing the 'escape' key.) + """ + # Flush all pending keys. (This is most important to flush the vt100 + # 'Escape' key early when nothing else follows.) + self.vt100_parser.flush() + + # Return result. + result = self._buffer + self._buffer = [] + return result + + @property + def closed(self) -> bool: + return self.stdin_reader.closed + + def raw_mode(self) -> ContextManager[None]: + return raw_mode(self.stdin.fileno()) + + def cooked_mode(self) -> ContextManager[None]: + return cooked_mode(self.stdin.fileno()) + + def fileno(self) -> int: + return self.stdin.fileno() + + def typeahead_hash(self) -> str: + return f"fd-{self._fileno}" + + +_current_callbacks: Dict[ + Tuple[AbstractEventLoop, int], Optional[Callable[[], None]] +] = {} # (loop, fd) -> current callback + + +@contextlib.contextmanager +def _attached_input( + input: Vt100Input, callback: Callable[[], None] +) -> Generator[None, None, None]: + """ + Context manager that makes this input active in the current event loop. + + :param input: :class:`~prompt_toolkit.input.Input` object. + :param callback: Called when the input is ready to read. + """ + loop = get_event_loop() + fd = input.fileno() + previous = _current_callbacks.get((loop, fd)) + + def callback_wrapper() -> None: + """Wrapper around the callback that already removes the reader when + the input is closed. Otherwise, we keep continuously calling this + callback, until we leave the context manager (which can happen a bit + later). This fixes issues when piping /dev/null into a prompt_toolkit + application.""" + if input.closed: + loop.remove_reader(fd) + callback() + + try: + loop.add_reader(fd, callback_wrapper) + except PermissionError: + # For `EPollSelector`, adding /dev/null to the event loop will raise + # `PermisisonError` (that doesn't happen for `SelectSelector` + # apparently). Whenever we get a `PermissionError`, we can raise + # `EOFError`, because there's not more to be read anyway. `EOFError` is + # an exception that people expect in + # `prompt_toolkit.application.Application.run()`. + # To reproduce, do: `ptpython 0< /dev/null 1< /dev/null` + raise EOFError + + _current_callbacks[loop, fd] = callback + + try: + yield + finally: + loop.remove_reader(fd) + + if previous: + loop.add_reader(fd, previous) + _current_callbacks[loop, fd] = previous + else: + del _current_callbacks[loop, fd] + + +@contextlib.contextmanager +def _detached_input(input: Vt100Input) -> Generator[None, None, None]: + loop = get_event_loop() + fd = input.fileno() + previous = _current_callbacks.get((loop, fd)) + + if previous: + loop.remove_reader(fd) + _current_callbacks[loop, fd] = None + + try: + yield + finally: + if previous: + loop.add_reader(fd, previous) + _current_callbacks[loop, fd] = previous + + +class raw_mode: + """ + :: + + with raw_mode(stdin): + ''' the pseudo-terminal stdin is now used in raw mode ''' + + We ignore errors when executing `tcgetattr` fails. + """ + + # There are several reasons for ignoring errors: + # 1. To avoid the "Inappropriate ioctl for device" crash if somebody would + # execute this code (In a Python REPL, for instance): + # + # import os; f = open(os.devnull); os.dup2(f.fileno(), 0) + # + # The result is that the eventloop will stop correctly, because it has + # to logic to quit when stdin is closed. However, we should not fail at + # this point. See: + # https://github.com/jonathanslenders/python-prompt-toolkit/pull/393 + # https://github.com/jonathanslenders/python-prompt-toolkit/issues/392 + + # 2. Related, when stdin is an SSH pipe, and no full terminal was allocated. + # See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/165 + def __init__(self, fileno: int) -> None: + self.fileno = fileno + self.attrs_before: Optional[List[Union[int, List[Union[bytes, int]]]]] + try: + self.attrs_before = termios.tcgetattr(fileno) + except termios.error: + # Ignore attribute errors. + self.attrs_before = None + + def __enter__(self) -> None: + # NOTE: On os X systems, using pty.setraw() fails. Therefor we are using this: + try: + newattr = termios.tcgetattr(self.fileno) + except termios.error: + pass + else: + newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG]) + newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG]) + + # VMIN defines the number of characters read at a time in + # non-canonical mode. It seems to default to 1 on Linux, but on + # Solaris and derived operating systems it defaults to 4. (This is + # because the VMIN slot is the same as the VEOF slot, which + # defaults to ASCII EOT = Ctrl-D = 4.) + newattr[tty.CC][termios.VMIN] = 1 + + termios.tcsetattr(self.fileno, termios.TCSANOW, newattr) + + @classmethod + def _patch_lflag(cls, attrs: int) -> int: + return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) + + @classmethod + def _patch_iflag(cls, attrs: int) -> int: + return attrs & ~( + # Disable XON/XOFF flow control on output and input. + # (Don't capture Ctrl-S and Ctrl-Q.) + # Like executing: "stty -ixon." + termios.IXON + | termios.IXOFF + | + # Don't translate carriage return into newline on input. + termios.ICRNL + | termios.INLCR + | termios.IGNCR + ) + + def __exit__(self, *a: object) -> None: + if self.attrs_before is not None: + try: + termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) + except termios.error: + pass + + # # Put the terminal in application mode. + # self._stdout.write('\x1b[?1h') + + +class cooked_mode(raw_mode): + """ + The opposite of ``raw_mode``, used when we need cooked mode inside a + `raw_mode` block. Used in `Application.run_in_terminal`.:: + + with cooked_mode(stdin): + ''' the pseudo-terminal stdin is now used in cooked mode. ''' + """ + + @classmethod + def _patch_lflag(cls, attrs: int) -> int: + return attrs | (termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) + + @classmethod + def _patch_iflag(cls, attrs: int) -> int: + # Turn the ICRNL flag back on. (Without this, calling `input()` in + # run_in_terminal doesn't work and displays ^M instead. Ptpython + # evaluates commands using `run_in_terminal`, so it's important that + # they translate ^M back into ^J.) + return attrs | termios.ICRNL diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/vt100_parser.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/vt100_parser.py new file mode 100644 index 0000000..3ee1e14 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/vt100_parser.py @@ -0,0 +1,247 @@ +""" +Parser for VT100 input stream. +""" +import re +from typing import Callable, Dict, Generator, Tuple, Union + +from ..key_binding.key_processor import KeyPress +from ..keys import Keys +from .ansi_escape_sequences import ANSI_SEQUENCES + +__all__ = [ + "Vt100Parser", +] + + +# Regex matching any CPR response +# (Note that we use '\Z' instead of '$', because '$' could include a trailing +# newline.) +_cpr_response_re = re.compile("^" + re.escape("\x1b[") + r"\d+;\d+R\Z") + +# Mouse events: +# Typical: "Esc[MaB*" Urxvt: "Esc[96;14;13M" and for Xterm SGR: "Esc[<64;85;12M" +_mouse_event_re = re.compile("^" + re.escape("\x1b[") + r"( bool: + # (hard coded) If this could be a prefix of a CPR response, return + # True. + if _cpr_response_prefix_re.match(prefix) or _mouse_event_prefix_re.match( + prefix + ): + result = True + else: + # If this could be a prefix of anything else, also return True. + result = any( + v + for k, v in ANSI_SEQUENCES.items() + if k.startswith(prefix) and k != prefix + ) + + self[prefix] = result + return result + + +_IS_PREFIX_OF_LONGER_MATCH_CACHE = _IsPrefixOfLongerMatchCache() + + +class Vt100Parser: + """ + Parser for VT100 input stream. + Data can be fed through the `feed` method and the given callback will be + called with KeyPress objects. + + :: + + def callback(key): + pass + i = Vt100Parser(callback) + i.feed('data\x01...') + + :attr feed_key_callback: Function that will be called when a key is parsed. + """ + + # Lookup table of ANSI escape sequences for a VT100 terminal + # Hint: in order to know what sequences your terminal writes to stdin, run + # "od -c" and start typing. + def __init__(self, feed_key_callback: Callable[[KeyPress], None]) -> None: + self.feed_key_callback = feed_key_callback + self.reset() + + def reset(self, request: bool = False) -> None: + self._in_bracketed_paste = False + self._start_parser() + + def _start_parser(self) -> None: + """ + Start the parser coroutine. + """ + self._input_parser = self._input_parser_generator() + self._input_parser.send(None) # type: ignore + + def _get_match(self, prefix: str) -> Union[None, Keys, Tuple[Keys, ...]]: + """ + Return the key (or keys) that maps to this prefix. + """ + # (hard coded) If we match a CPR response, return Keys.CPRResponse. + # (This one doesn't fit in the ANSI_SEQUENCES, because it contains + # integer variables.) + if _cpr_response_re.match(prefix): + return Keys.CPRResponse + + elif _mouse_event_re.match(prefix): + return Keys.Vt100MouseEvent + + # Otherwise, use the mappings. + try: + return ANSI_SEQUENCES[prefix] + except KeyError: + return None + + def _input_parser_generator(self) -> Generator[None, Union[str, _Flush], None]: + """ + Coroutine (state machine) for the input parser. + """ + prefix = "" + retry = False + flush = False + + while True: + flush = False + + if retry: + retry = False + else: + # Get next character. + c = yield + + if isinstance(c, _Flush): + flush = True + else: + prefix += c + + # If we have some data, check for matches. + if prefix: + is_prefix_of_longer_match = _IS_PREFIX_OF_LONGER_MATCH_CACHE[prefix] + match = self._get_match(prefix) + + # Exact matches found, call handlers.. + if (flush or not is_prefix_of_longer_match) and match: + self._call_handler(match, prefix) + prefix = "" + + # No exact match found. + elif (flush or not is_prefix_of_longer_match) and not match: + found = False + retry = True + + # Loop over the input, try the longest match first and + # shift. + for i in range(len(prefix), 0, -1): + match = self._get_match(prefix[:i]) + if match: + self._call_handler(match, prefix[:i]) + prefix = prefix[i:] + found = True + + if not found: + self._call_handler(prefix[0], prefix[0]) + prefix = prefix[1:] + + def _call_handler( + self, key: Union[str, Keys, Tuple[Keys, ...]], insert_text: str + ) -> None: + """ + Callback to handler. + """ + if isinstance(key, tuple): + # Received ANSI sequence that corresponds with multiple keys + # (probably alt+something). Handle keys individually, but only pass + # data payload to first KeyPress (so that we won't insert it + # multiple times). + for i, k in enumerate(key): + self._call_handler(k, insert_text if i == 0 else "") + else: + if key == Keys.BracketedPaste: + self._in_bracketed_paste = True + self._paste_buffer = "" + else: + self.feed_key_callback(KeyPress(key, insert_text)) + + def feed(self, data: str) -> None: + """ + Feed the input stream. + + :param data: Input string (unicode). + """ + # Handle bracketed paste. (We bypass the parser that matches all other + # key presses and keep reading input until we see the end mark.) + # This is much faster then parsing character by character. + if self._in_bracketed_paste: + self._paste_buffer += data + end_mark = "\x1b[201~" + + if end_mark in self._paste_buffer: + end_index = self._paste_buffer.index(end_mark) + + # Feed content to key bindings. + paste_content = self._paste_buffer[:end_index] + self.feed_key_callback(KeyPress(Keys.BracketedPaste, paste_content)) + + # Quit bracketed paste mode and handle remaining input. + self._in_bracketed_paste = False + remaining = self._paste_buffer[end_index + len(end_mark) :] + self._paste_buffer = "" + + self.feed(remaining) + + # Handle normal input character by character. + else: + for i, c in enumerate(data): + if self._in_bracketed_paste: + # Quit loop and process from this position when the parser + # entered bracketed paste. + self.feed(data[i:]) + break + else: + self._input_parser.send(c) + + def flush(self) -> None: + """ + Flush the buffer of the input stream. + + This will allow us to handle the escape key (or maybe meta) sooner. + The input received by the escape key is actually the same as the first + characters of e.g. Arrow-Up, so without knowing what follows the escape + sequence, we don't know whether escape has been pressed, or whether + it's something else. This flush function should be called after a + timeout, and processes everything that's still in the buffer as-is, so + without assuming any characters will follow. + """ + self._input_parser.send(_Flush()) + + def feed_and_flush(self, data: str) -> None: + """ + Wrapper around ``feed`` and ``flush``. + """ + self.feed(data) + self.flush() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/win32.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/win32.py new file mode 100644 index 0000000..c59375b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/win32.py @@ -0,0 +1,755 @@ +import os +import sys +from abc import abstractmethod +from contextlib import contextmanager + +from prompt_toolkit.eventloop import get_event_loop + +from ..utils import SPHINX_AUTODOC_RUNNING + +# Do not import win32-specific stuff when generating documentation. +# Otherwise RTD would be unable to generate docs for this module. +if not SPHINX_AUTODOC_RUNNING: + import msvcrt + from ctypes import windll + +from ctypes import Array, pointer +from ctypes.wintypes import DWORD, HANDLE +from typing import ( + Callable, + ContextManager, + Dict, + Iterable, + Iterator, + List, + Optional, + TextIO, +) + +from prompt_toolkit.eventloop import run_in_executor_with_context +from prompt_toolkit.eventloop.win32 import create_win32_event, wait_for_handles +from prompt_toolkit.key_binding.key_processor import KeyPress +from prompt_toolkit.keys import Keys +from prompt_toolkit.mouse_events import MouseButton, MouseEventType +from prompt_toolkit.win32_types import ( + INPUT_RECORD, + KEY_EVENT_RECORD, + MOUSE_EVENT_RECORD, + STD_INPUT_HANDLE, + EventTypes, +) + +from .ansi_escape_sequences import REVERSE_ANSI_SEQUENCES +from .base import Input + +__all__ = [ + "Win32Input", + "ConsoleInputReader", + "raw_mode", + "cooked_mode", + "attach_win32_input", + "detach_win32_input", +] + +# Win32 Constants for MOUSE_EVENT_RECORD. +# See: https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 +RIGHTMOST_BUTTON_PRESSED = 0x2 +MOUSE_MOVED = 0x0001 +MOUSE_WHEELED = 0x0004 + + +class _Win32InputBase(Input): + """ + Base class for `Win32Input` and `Win32PipeInput`. + """ + + def __init__(self) -> None: + self.win32_handles = _Win32Handles() + + @property + @abstractmethod + def handle(self) -> HANDLE: + pass + + +class Win32Input(_Win32InputBase): + """ + `Input` class that reads from the Windows console. + """ + + def __init__(self, stdin: Optional[TextIO] = None) -> None: + super().__init__() + self.console_input_reader = ConsoleInputReader() + + def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: + """ + Return a context manager that makes this input active in the current + event loop. + """ + return attach_win32_input(self, input_ready_callback) + + def detach(self) -> ContextManager[None]: + """ + Return a context manager that makes sure that this input is not active + in the current event loop. + """ + return detach_win32_input(self) + + def read_keys(self) -> List[KeyPress]: + return list(self.console_input_reader.read()) + + def flush(self) -> None: + pass + + @property + def closed(self) -> bool: + return False + + def raw_mode(self) -> ContextManager[None]: + return raw_mode() + + def cooked_mode(self) -> ContextManager[None]: + return cooked_mode() + + def fileno(self) -> int: + # The windows console doesn't depend on the file handle, so + # this is not used for the event loop (which uses the + # handle instead). But it's used in `Application.run_system_command` + # which opens a subprocess with a given stdin/stdout. + return sys.stdin.fileno() + + def typeahead_hash(self) -> str: + return "win32-input" + + def close(self) -> None: + self.console_input_reader.close() + + @property + def handle(self) -> HANDLE: + return self.console_input_reader.handle + + +class ConsoleInputReader: + """ + :param recognize_paste: When True, try to discover paste actions and turn + the event into a BracketedPaste. + """ + + # Keys with character data. + mappings = { + b"\x1b": Keys.Escape, + b"\x00": Keys.ControlSpace, # Control-Space (Also for Ctrl-@) + b"\x01": Keys.ControlA, # Control-A (home) + b"\x02": Keys.ControlB, # Control-B (emacs cursor left) + b"\x03": Keys.ControlC, # Control-C (interrupt) + b"\x04": Keys.ControlD, # Control-D (exit) + b"\x05": Keys.ControlE, # Control-E (end) + b"\x06": Keys.ControlF, # Control-F (cursor forward) + b"\x07": Keys.ControlG, # Control-G + b"\x08": Keys.ControlH, # Control-H (8) (Identical to '\b') + b"\x09": Keys.ControlI, # Control-I (9) (Identical to '\t') + b"\x0a": Keys.ControlJ, # Control-J (10) (Identical to '\n') + b"\x0b": Keys.ControlK, # Control-K (delete until end of line; vertical tab) + b"\x0c": Keys.ControlL, # Control-L (clear; form feed) + b"\x0d": Keys.ControlM, # Control-M (enter) + b"\x0e": Keys.ControlN, # Control-N (14) (history forward) + b"\x0f": Keys.ControlO, # Control-O (15) + b"\x10": Keys.ControlP, # Control-P (16) (history back) + b"\x11": Keys.ControlQ, # Control-Q + b"\x12": Keys.ControlR, # Control-R (18) (reverse search) + b"\x13": Keys.ControlS, # Control-S (19) (forward search) + b"\x14": Keys.ControlT, # Control-T + b"\x15": Keys.ControlU, # Control-U + b"\x16": Keys.ControlV, # Control-V + b"\x17": Keys.ControlW, # Control-W + b"\x18": Keys.ControlX, # Control-X + b"\x19": Keys.ControlY, # Control-Y (25) + b"\x1a": Keys.ControlZ, # Control-Z + b"\x1c": Keys.ControlBackslash, # Both Control-\ and Ctrl-| + b"\x1d": Keys.ControlSquareClose, # Control-] + b"\x1e": Keys.ControlCircumflex, # Control-^ + b"\x1f": Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hyphen.) + b"\x7f": Keys.Backspace, # (127) Backspace (ASCII Delete.) + } + + # Keys that don't carry character data. + keycodes = { + # Home/End + 33: Keys.PageUp, + 34: Keys.PageDown, + 35: Keys.End, + 36: Keys.Home, + # Arrows + 37: Keys.Left, + 38: Keys.Up, + 39: Keys.Right, + 40: Keys.Down, + 45: Keys.Insert, + 46: Keys.Delete, + # F-keys. + 112: Keys.F1, + 113: Keys.F2, + 114: Keys.F3, + 115: Keys.F4, + 116: Keys.F5, + 117: Keys.F6, + 118: Keys.F7, + 119: Keys.F8, + 120: Keys.F9, + 121: Keys.F10, + 122: Keys.F11, + 123: Keys.F12, + } + + LEFT_ALT_PRESSED = 0x0002 + RIGHT_ALT_PRESSED = 0x0001 + SHIFT_PRESSED = 0x0010 + LEFT_CTRL_PRESSED = 0x0008 + RIGHT_CTRL_PRESSED = 0x0004 + + def __init__(self, recognize_paste: bool = True) -> None: + self._fdcon = None + self.recognize_paste = recognize_paste + + # When stdin is a tty, use that handle, otherwise, create a handle from + # CONIN$. + self.handle: HANDLE + if sys.stdin.isatty(): + self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) + else: + self._fdcon = os.open("CONIN$", os.O_RDWR | os.O_BINARY) + self.handle = HANDLE(msvcrt.get_osfhandle(self._fdcon)) + + def close(self) -> None: + "Close fdcon." + if self._fdcon is not None: + os.close(self._fdcon) + + def read(self) -> Iterable[KeyPress]: + """ + Return a list of `KeyPress` instances. It won't return anything when + there was nothing to read. (This function doesn't block.) + + http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx + """ + max_count = 2048 # Max events to read at the same time. + + read = DWORD(0) + arrtype = INPUT_RECORD * max_count + input_records = arrtype() + + # Check whether there is some input to read. `ReadConsoleInputW` would + # block otherwise. + # (Actually, the event loop is responsible to make sure that this + # function is only called when there is something to read, but for some + # reason this happened in the asyncio_win32 loop, and it's better to be + # safe anyway.) + if not wait_for_handles([self.handle], timeout=0): + return + + # Get next batch of input event. + windll.kernel32.ReadConsoleInputW( + self.handle, pointer(input_records), max_count, pointer(read) + ) + + # First, get all the keys from the input buffer, in order to determine + # whether we should consider this a paste event or not. + all_keys = list(self._get_keys(read, input_records)) + + # Fill in 'data' for key presses. + all_keys = [self._insert_key_data(key) for key in all_keys] + + # Correct non-bmp characters that are passed as separate surrogate codes + all_keys = list(self._merge_paired_surrogates(all_keys)) + + if self.recognize_paste and self._is_paste(all_keys): + gen = iter(all_keys) + k: Optional[KeyPress] + + for k in gen: + # Pasting: if the current key consists of text or \n, turn it + # into a BracketedPaste. + data = [] + while k and ( + not isinstance(k.key, Keys) + or k.key in {Keys.ControlJ, Keys.ControlM} + ): + data.append(k.data) + try: + k = next(gen) + except StopIteration: + k = None + + if data: + yield KeyPress(Keys.BracketedPaste, "".join(data)) + if k is not None: + yield k + else: + yield from all_keys + + def _insert_key_data(self, key_press: KeyPress) -> KeyPress: + """ + Insert KeyPress data, for vt100 compatibility. + """ + if key_press.data: + return key_press + + if isinstance(key_press.key, Keys): + data = REVERSE_ANSI_SEQUENCES.get(key_press.key, "") + else: + data = "" + + return KeyPress(key_press.key, data) + + def _get_keys( + self, read: DWORD, input_records: "Array[INPUT_RECORD]" + ) -> Iterator[KeyPress]: + """ + Generator that yields `KeyPress` objects from the input records. + """ + for i in range(read.value): + ir = input_records[i] + + # Get the right EventType from the EVENT_RECORD. + # (For some reason the Windows console application 'cmder' + # [http://gooseberrycreative.com/cmder/] can return '0' for + # ir.EventType. -- Just ignore that.) + if ir.EventType in EventTypes: + ev = getattr(ir.Event, EventTypes[ir.EventType]) + + # Process if this is a key event. (We also have mouse, menu and + # focus events.) + if type(ev) == KEY_EVENT_RECORD and ev.KeyDown: + yield from self._event_to_key_presses(ev) + + elif type(ev) == MOUSE_EVENT_RECORD: + yield from self._handle_mouse(ev) + + @staticmethod + def _merge_paired_surrogates(key_presses: List[KeyPress]) -> Iterator[KeyPress]: + """ + Combines consecutive KeyPresses with high and low surrogates into + single characters + """ + buffered_high_surrogate = None + for key in key_presses: + is_text = not isinstance(key.key, Keys) + is_high_surrogate = is_text and "\uD800" <= key.key <= "\uDBFF" + is_low_surrogate = is_text and "\uDC00" <= key.key <= "\uDFFF" + + if buffered_high_surrogate: + if is_low_surrogate: + # convert high surrogate + low surrogate to single character + fullchar = ( + (buffered_high_surrogate.key + key.key) + .encode("utf-16-le", "surrogatepass") + .decode("utf-16-le") + ) + key = KeyPress(fullchar, fullchar) + else: + yield buffered_high_surrogate + buffered_high_surrogate = None + + if is_high_surrogate: + buffered_high_surrogate = key + else: + yield key + + if buffered_high_surrogate: + yield buffered_high_surrogate + + @staticmethod + def _is_paste(keys: List[KeyPress]) -> bool: + """ + Return `True` when we should consider this list of keys as a paste + event. Pasted text on windows will be turned into a + `Keys.BracketedPaste` event. (It's not 100% correct, but it is probably + the best possible way to detect pasting of text and handle that + correctly.) + """ + # Consider paste when it contains at least one newline and at least one + # other character. + text_count = 0 + newline_count = 0 + + for k in keys: + if not isinstance(k.key, Keys): + text_count += 1 + if k.key == Keys.ControlM: + newline_count += 1 + + return newline_count >= 1 and text_count >= 1 + + def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> List[KeyPress]: + """ + For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. + """ + assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown + + result: Optional[KeyPress] = None + + control_key_state = ev.ControlKeyState + u_char = ev.uChar.UnicodeChar + # Use surrogatepass because u_char may be an unmatched surrogate + ascii_char = u_char.encode("utf-8", "surrogatepass") + + # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be the + # unicode code point truncated to 1 byte. See also: + # https://github.com/ipython/ipython/issues/10004 + # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389 + + if u_char == "\x00": + if ev.VirtualKeyCode in self.keycodes: + result = KeyPress(self.keycodes[ev.VirtualKeyCode], "") + else: + if ascii_char in self.mappings: + if self.mappings[ascii_char] == Keys.ControlJ: + u_char = ( + "\n" # Windows sends \n, turn into \r for unix compatibility. + ) + result = KeyPress(self.mappings[ascii_char], u_char) + else: + result = KeyPress(u_char, u_char) + + # First we handle Shift-Control-Arrow/Home/End (need to do this first) + if ( + ( + control_key_state & self.LEFT_CTRL_PRESSED + or control_key_state & self.RIGHT_CTRL_PRESSED + ) + and control_key_state & self.SHIFT_PRESSED + and result + ): + mapping: Dict[str, str] = { + Keys.Left: Keys.ControlShiftLeft, + Keys.Right: Keys.ControlShiftRight, + Keys.Up: Keys.ControlShiftUp, + Keys.Down: Keys.ControlShiftDown, + Keys.Home: Keys.ControlShiftHome, + Keys.End: Keys.ControlShiftEnd, + Keys.Insert: Keys.ControlShiftInsert, + Keys.PageUp: Keys.ControlShiftPageUp, + Keys.PageDown: Keys.ControlShiftPageDown, + } + result.key = mapping.get(result.key, result.key) + + # Correctly handle Control-Arrow/Home/End and Control-Insert/Delete keys. + if ( + control_key_state & self.LEFT_CTRL_PRESSED + or control_key_state & self.RIGHT_CTRL_PRESSED + ) and result: + mapping = { + Keys.Left: Keys.ControlLeft, + Keys.Right: Keys.ControlRight, + Keys.Up: Keys.ControlUp, + Keys.Down: Keys.ControlDown, + Keys.Home: Keys.ControlHome, + Keys.End: Keys.ControlEnd, + Keys.Insert: Keys.ControlInsert, + Keys.Delete: Keys.ControlDelete, + Keys.PageUp: Keys.ControlPageUp, + Keys.PageDown: Keys.ControlPageDown, + } + result.key = mapping.get(result.key, result.key) + + # Turn 'Tab' into 'BackTab' when shift was pressed. + # Also handle other shift-key combination + if control_key_state & self.SHIFT_PRESSED and result: + mapping = { + Keys.Tab: Keys.BackTab, + Keys.Left: Keys.ShiftLeft, + Keys.Right: Keys.ShiftRight, + Keys.Up: Keys.ShiftUp, + Keys.Down: Keys.ShiftDown, + Keys.Home: Keys.ShiftHome, + Keys.End: Keys.ShiftEnd, + Keys.Insert: Keys.ShiftInsert, + Keys.Delete: Keys.ShiftDelete, + Keys.PageUp: Keys.ShiftPageUp, + Keys.PageDown: Keys.ShiftPageDown, + } + result.key = mapping.get(result.key, result.key) + + # Turn 'Space' into 'ControlSpace' when control was pressed. + if ( + ( + control_key_state & self.LEFT_CTRL_PRESSED + or control_key_state & self.RIGHT_CTRL_PRESSED + ) + and result + and result.data == " " + ): + result = KeyPress(Keys.ControlSpace, " ") + + # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot + # detect this combination. But it's really practical on Windows.) + if ( + ( + control_key_state & self.LEFT_CTRL_PRESSED + or control_key_state & self.RIGHT_CTRL_PRESSED + ) + and result + and result.key == Keys.ControlJ + ): + return [KeyPress(Keys.Escape, ""), result] + + # Return result. If alt was pressed, prefix the result with an + # 'Escape' key, just like unix VT100 terminals do. + + # NOTE: Only replace the left alt with escape. The right alt key often + # acts as altgr and is used in many non US keyboard layouts for + # typing some special characters, like a backslash. We don't want + # all backslashes to be prefixed with escape. (Esc-\ has a + # meaning in E-macs, for instance.) + if result: + meta_pressed = control_key_state & self.LEFT_ALT_PRESSED + + if meta_pressed: + return [KeyPress(Keys.Escape, ""), result] + else: + return [result] + + else: + return [] + + def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> List[KeyPress]: + """ + Handle mouse events. Return a list of KeyPress instances. + """ + event_flags = ev.EventFlags + button_state = ev.ButtonState + + event_type: Optional[MouseEventType] = None + button: MouseButton = MouseButton.NONE + + # Scroll events. + if event_flags & MOUSE_WHEELED: + if button_state > 0: + event_type = MouseEventType.SCROLL_UP + else: + event_type = MouseEventType.SCROLL_DOWN + else: + # Handle button state for non-scroll events. + if button_state == FROM_LEFT_1ST_BUTTON_PRESSED: + button = MouseButton.LEFT + + elif button_state == RIGHTMOST_BUTTON_PRESSED: + button = MouseButton.RIGHT + + # Move events. + if event_flags & MOUSE_MOVED: + event_type = MouseEventType.MOUSE_MOVE + + # No key pressed anymore: mouse up. + if event_type is None: + if button_state > 0: + # Some button pressed. + event_type = MouseEventType.MOUSE_DOWN + else: + # No button pressed. + event_type = MouseEventType.MOUSE_UP + + data = ";".join( + [ + button.value, + event_type.value, + str(ev.MousePosition.X), + str(ev.MousePosition.Y), + ] + ) + return [KeyPress(Keys.WindowsMouseEvent, data)] + + +class _Win32Handles: + """ + Utility to keep track of which handles are connectod to which callbacks. + + `add_win32_handle` starts a tiny event loop in another thread which waits + for the Win32 handle to become ready. When this happens, the callback will + be called in the current asyncio event loop using `call_soon_threadsafe`. + + `remove_win32_handle` will stop this tiny event loop. + + NOTE: We use this technique, so that we don't have to use the + `ProactorEventLoop` on Windows and we can wait for things like stdin + in a `SelectorEventLoop`. This is important, because our inputhook + mechanism (used by IPython), only works with the `SelectorEventLoop`. + """ + + def __init__(self) -> None: + self._handle_callbacks: Dict[int, Callable[[], None]] = {} + + # Windows Events that are triggered when we have to stop watching this + # handle. + self._remove_events: Dict[int, HANDLE] = {} + + def add_win32_handle(self, handle: HANDLE, callback: Callable[[], None]) -> None: + """ + Add a Win32 handle to the event loop. + """ + handle_value = handle.value + + if handle_value is None: + raise ValueError("Invalid handle.") + + # Make sure to remove a previous registered handler first. + self.remove_win32_handle(handle) + + loop = get_event_loop() + self._handle_callbacks[handle_value] = callback + + # Create remove event. + remove_event = create_win32_event() + self._remove_events[handle_value] = remove_event + + # Add reader. + def ready() -> None: + # Tell the callback that input's ready. + try: + callback() + finally: + run_in_executor_with_context(wait, loop=loop) + + # Wait for the input to become ready. + # (Use an executor for this, the Windows asyncio event loop doesn't + # allow us to wait for handles like stdin.) + def wait() -> None: + # Wait until either the handle becomes ready, or the remove event + # has been set. + result = wait_for_handles([remove_event, handle]) + + if result is remove_event: + windll.kernel32.CloseHandle(remove_event) + return + else: + loop.call_soon_threadsafe(ready) + + run_in_executor_with_context(wait, loop=loop) + + def remove_win32_handle(self, handle: HANDLE) -> Optional[Callable[[], None]]: + """ + Remove a Win32 handle from the event loop. + Return either the registered handler or `None`. + """ + if handle.value is None: + return None # Ignore. + + # Trigger remove events, so that the reader knows to stop. + try: + event = self._remove_events.pop(handle.value) + except KeyError: + pass + else: + windll.kernel32.SetEvent(event) + + try: + return self._handle_callbacks.pop(handle.value) + except KeyError: + return None + + +@contextmanager +def attach_win32_input( + input: _Win32InputBase, callback: Callable[[], None] +) -> Iterator[None]: + """ + Context manager that makes this input active in the current event loop. + + :param input: :class:`~prompt_toolkit.input.Input` object. + :param input_ready_callback: Called when the input is ready to read. + """ + win32_handles = input.win32_handles + handle = input.handle + + if handle.value is None: + raise ValueError("Invalid handle.") + + # Add reader. + previous_callback = win32_handles.remove_win32_handle(handle) + win32_handles.add_win32_handle(handle, callback) + + try: + yield + finally: + win32_handles.remove_win32_handle(handle) + + if previous_callback: + win32_handles.add_win32_handle(handle, previous_callback) + + +@contextmanager +def detach_win32_input(input: _Win32InputBase) -> Iterator[None]: + win32_handles = input.win32_handles + handle = input.handle + + if handle.value is None: + raise ValueError("Invalid handle.") + + previous_callback = win32_handles.remove_win32_handle(handle) + + try: + yield + finally: + if previous_callback: + win32_handles.add_win32_handle(handle, previous_callback) + + +class raw_mode: + """ + :: + + with raw_mode(stdin): + ''' the windows terminal is now in 'raw' mode. ''' + + The ``fileno`` attribute is ignored. This is to be compatible with the + `raw_input` method of `.vt100_input`. + """ + + def __init__(self, fileno: Optional[int] = None) -> None: + self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) + + def __enter__(self) -> None: + # Remember original mode. + original_mode = DWORD() + windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode)) + self.original_mode = original_mode + + self._patch() + + def _patch(self) -> None: + # Set raw + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_PROCESSED_INPUT = 0x0001 + + windll.kernel32.SetConsoleMode( + self.handle, + self.original_mode.value + & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT), + ) + + def __exit__(self, *a: object) -> None: + # Restore original mode + windll.kernel32.SetConsoleMode(self.handle, self.original_mode) + + +class cooked_mode(raw_mode): + """ + :: + + with cooked_mode(stdin): + ''' The pseudo-terminal stdin is now used in cooked mode. ''' + """ + + def _patch(self) -> None: + # Set cooked. + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_PROCESSED_INPUT = 0x0001 + + windll.kernel32.SetConsoleMode( + self.handle, + self.original_mode.value + | (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT), + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/input/win32_pipe.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/win32_pipe.py new file mode 100644 index 0000000..fdbcb8e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/input/win32_pipe.py @@ -0,0 +1,135 @@ +from ctypes import windll +from ctypes.wintypes import HANDLE +from typing import Callable, ContextManager, List + +from prompt_toolkit.eventloop.win32 import create_win32_event + +from ..key_binding import KeyPress +from ..utils import DummyContext +from .base import PipeInput +from .vt100_parser import Vt100Parser +from .win32 import _Win32InputBase, attach_win32_input, detach_win32_input + +__all__ = ["Win32PipeInput"] + + +class Win32PipeInput(_Win32InputBase, PipeInput): + """ + This is an input pipe that works on Windows. + Text or bytes can be feed into the pipe, and key strokes can be read from + the pipe. This is useful if we want to send the input programmatically into + the application. Mostly useful for unit testing. + + Notice that even though it's Windows, we use vt100 escape sequences over + the pipe. + + Usage:: + + input = Win32PipeInput() + input.send_text('inputdata') + """ + + _id = 0 + + def __init__(self) -> None: + super().__init__() + # Event (handle) for registering this input in the event loop. + # This event is set when there is data available to read from the pipe. + # Note: We use this approach instead of using a regular pipe, like + # returned from `os.pipe()`, because making such a regular pipe + # non-blocking is tricky and this works really well. + self._event = create_win32_event() + + self._closed = False + + # Parser for incoming keys. + self._buffer: List[KeyPress] = [] # Buffer to collect the Key objects. + self.vt100_parser = Vt100Parser(lambda key: self._buffer.append(key)) + + # Identifier for every PipeInput for the hash. + self.__class__._id += 1 + self._id = self.__class__._id + + @property + def closed(self) -> bool: + return self._closed + + def fileno(self) -> int: + """ + The windows pipe doesn't depend on the file handle. + """ + raise NotImplementedError + + @property + def handle(self) -> HANDLE: + "The handle used for registering this pipe in the event loop." + return self._event + + def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]: + """ + Return a context manager that makes this input active in the current + event loop. + """ + return attach_win32_input(self, input_ready_callback) + + def detach(self) -> ContextManager[None]: + """ + Return a context manager that makes sure that this input is not active + in the current event loop. + """ + return detach_win32_input(self) + + def read_keys(self) -> List[KeyPress]: + "Read list of KeyPress." + + # Return result. + result = self._buffer + self._buffer = [] + + # Reset event. + windll.kernel32.ResetEvent(self._event) + + return result + + def flush_keys(self) -> List[KeyPress]: + """ + Flush pending keys and return them. + (Used for flushing the 'escape' key.) + """ + # Flush all pending keys. (This is most important to flush the vt100 + # 'Escape' key early when nothing else follows.) + self.vt100_parser.flush() + + # Return result. + result = self._buffer + self._buffer = [] + return result + + def send_bytes(self, data: bytes) -> None: + "Send bytes to the input." + self.send_text(data.decode("utf-8", "ignore")) + + def send_text(self, text: str) -> None: + "Send text to the input." + # Pass it through our vt100 parser. + self.vt100_parser.feed(text) + + # Set event. + windll.kernel32.SetEvent(self._event) + + def raw_mode(self) -> ContextManager[None]: + return DummyContext() + + def cooked_mode(self) -> ContextManager[None]: + return DummyContext() + + def close(self) -> None: + "Close pipe handles." + windll.kernel32.CloseHandle(self._event) + self._closed = True + + def typeahead_hash(self) -> str: + """ + This needs to be unique for every `PipeInput`. + """ + return f"pipe-input-{self._id}" diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__init__.py new file mode 100644 index 0000000..be10536 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__init__.py @@ -0,0 +1,20 @@ +from .key_bindings import ( + ConditionalKeyBindings, + DynamicKeyBindings, + KeyBindings, + KeyBindingsBase, + merge_key_bindings, +) +from .key_processor import KeyPress, KeyPressEvent + +__all__ = [ + # key_bindings. + "ConditionalKeyBindings", + "DynamicKeyBindings", + "KeyBindings", + "KeyBindingsBase", + "merge_key_bindings", + # key_processor + "KeyPress", + "KeyPressEvent", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..8a4f7f6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/defaults.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/defaults.cpython-38.pyc new file mode 100644 index 0000000..3919456 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/defaults.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/digraphs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/digraphs.cpython-38.pyc new file mode 100644 index 0000000..e5db382 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/digraphs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/emacs_state.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/emacs_state.cpython-38.pyc new file mode 100644 index 0000000..0a51383 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/emacs_state.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/key_bindings.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/key_bindings.cpython-38.pyc new file mode 100644 index 0000000..c2f8e73 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/key_bindings.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/key_processor.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/key_processor.cpython-38.pyc new file mode 100644 index 0000000..aab762d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/key_processor.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/vi_state.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/vi_state.cpython-38.pyc new file mode 100644 index 0000000..80a6a04 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/__pycache__/vi_state.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..a69ca91 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/auto_suggest.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/auto_suggest.cpython-38.pyc new file mode 100644 index 0000000..8157bb0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/auto_suggest.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/basic.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/basic.cpython-38.pyc new file mode 100644 index 0000000..ee5c8fe Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/basic.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/completion.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/completion.cpython-38.pyc new file mode 100644 index 0000000..7a7cc34 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/completion.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/cpr.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/cpr.cpython-38.pyc new file mode 100644 index 0000000..6a688ee Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/cpr.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/emacs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/emacs.cpython-38.pyc new file mode 100644 index 0000000..216deec Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/emacs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/focus.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/focus.cpython-38.pyc new file mode 100644 index 0000000..5a8d580 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/focus.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/mouse.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/mouse.cpython-38.pyc new file mode 100644 index 0000000..cb5e91b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/mouse.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/named_commands.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/named_commands.cpython-38.pyc new file mode 100644 index 0000000..425d356 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/named_commands.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/open_in_editor.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/open_in_editor.cpython-38.pyc new file mode 100644 index 0000000..9b63976 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/open_in_editor.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/page_navigation.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/page_navigation.cpython-38.pyc new file mode 100644 index 0000000..c917817 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/page_navigation.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/scroll.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/scroll.cpython-38.pyc new file mode 100644 index 0000000..0cdd6a6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/scroll.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/search.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/search.cpython-38.pyc new file mode 100644 index 0000000..efb0222 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/search.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/vi.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/vi.cpython-38.pyc new file mode 100644 index 0000000..de6daf2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/__pycache__/vi.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/auto_suggest.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/auto_suggest.py new file mode 100644 index 0000000..c016c06 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/auto_suggest.py @@ -0,0 +1,63 @@ +""" +Key bindings for auto suggestion (for fish-style auto suggestion). +""" +import re + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.filters import Condition, emacs_mode +from prompt_toolkit.key_binding.key_bindings import KeyBindings +from prompt_toolkit.key_binding.key_processor import KeyPressEvent + +__all__ = [ + "load_auto_suggest_bindings", +] + +E = KeyPressEvent + + +def load_auto_suggest_bindings() -> KeyBindings: + """ + Key bindings for accepting auto suggestion text. + + (This has to come after the Vi bindings, because they also have an + implementation for the "right arrow", but we really want the suggestion + binding when a suggestion is available.) + """ + key_bindings = KeyBindings() + handle = key_bindings.add + + @Condition + def suggestion_available() -> bool: + app = get_app() + return ( + app.current_buffer.suggestion is not None + and len(app.current_buffer.suggestion.text) > 0 + and app.current_buffer.document.is_cursor_at_the_end + ) + + @handle("c-f", filter=suggestion_available) + @handle("c-e", filter=suggestion_available) + @handle("right", filter=suggestion_available) + def _accept(event: E) -> None: + """ + Accept suggestion. + """ + b = event.current_buffer + suggestion = b.suggestion + + if suggestion: + b.insert_text(suggestion.text) + + @handle("escape", "f", filter=suggestion_available & emacs_mode) + def _fill(event: E) -> None: + """ + Fill partial suggestion. + """ + b = event.current_buffer + suggestion = b.suggestion + + if suggestion: + t = re.split(r"(\S+\s+)", suggestion.text) + b.insert_text(next(x for x in t if x)) + + return key_bindings diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/basic.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/basic.py new file mode 100644 index 0000000..fc8f964 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/basic.py @@ -0,0 +1,253 @@ +# pylint: disable=function-redefined +from prompt_toolkit.application.current import get_app +from prompt_toolkit.filters import ( + Condition, + emacs_insert_mode, + has_selection, + in_paste_mode, + is_multiline, + vi_insert_mode, +) +from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent +from prompt_toolkit.keys import Keys + +from ..key_bindings import KeyBindings +from .named_commands import get_by_name + +__all__ = [ + "load_basic_bindings", +] + +E = KeyPressEvent + + +def if_no_repeat(event: E) -> bool: + """Callable that returns True when the previous event was delivered to + another handler.""" + return not event.is_repeat + + +def load_basic_bindings() -> KeyBindings: + key_bindings = KeyBindings() + insert_mode = vi_insert_mode | emacs_insert_mode + handle = key_bindings.add + + @handle("c-a") + @handle("c-b") + @handle("c-c") + @handle("c-d") + @handle("c-e") + @handle("c-f") + @handle("c-g") + @handle("c-h") + @handle("c-i") + @handle("c-j") + @handle("c-k") + @handle("c-l") + @handle("c-m") + @handle("c-n") + @handle("c-o") + @handle("c-p") + @handle("c-q") + @handle("c-r") + @handle("c-s") + @handle("c-t") + @handle("c-u") + @handle("c-v") + @handle("c-w") + @handle("c-x") + @handle("c-y") + @handle("c-z") + @handle("f1") + @handle("f2") + @handle("f3") + @handle("f4") + @handle("f5") + @handle("f6") + @handle("f7") + @handle("f8") + @handle("f9") + @handle("f10") + @handle("f11") + @handle("f12") + @handle("f13") + @handle("f14") + @handle("f15") + @handle("f16") + @handle("f17") + @handle("f18") + @handle("f19") + @handle("f20") + @handle("f21") + @handle("f22") + @handle("f23") + @handle("f24") + @handle("c-@") # Also c-space. + @handle("c-\\") + @handle("c-]") + @handle("c-^") + @handle("c-_") + @handle("backspace") + @handle("up") + @handle("down") + @handle("right") + @handle("left") + @handle("s-up") + @handle("s-down") + @handle("s-right") + @handle("s-left") + @handle("home") + @handle("end") + @handle("s-home") + @handle("s-end") + @handle("delete") + @handle("s-delete") + @handle("c-delete") + @handle("pageup") + @handle("pagedown") + @handle("s-tab") + @handle("tab") + @handle("c-s-left") + @handle("c-s-right") + @handle("c-s-home") + @handle("c-s-end") + @handle("c-left") + @handle("c-right") + @handle("c-up") + @handle("c-down") + @handle("c-home") + @handle("c-end") + @handle("insert") + @handle("s-insert") + @handle("c-insert") + @handle("") + @handle(Keys.Ignore) + def _ignore(event: E) -> None: + """ + First, for any of these keys, Don't do anything by default. Also don't + catch them in the 'Any' handler which will insert them as data. + + If people want to insert these characters as a literal, they can always + do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi + mode.) + """ + pass + + # Readline-style bindings. + handle("home")(get_by_name("beginning-of-line")) + handle("end")(get_by_name("end-of-line")) + handle("left")(get_by_name("backward-char")) + handle("right")(get_by_name("forward-char")) + handle("c-up")(get_by_name("previous-history")) + handle("c-down")(get_by_name("next-history")) + handle("c-l")(get_by_name("clear-screen")) + + handle("c-k", filter=insert_mode)(get_by_name("kill-line")) + handle("c-u", filter=insert_mode)(get_by_name("unix-line-discard")) + handle("backspace", filter=insert_mode, save_before=if_no_repeat)( + get_by_name("backward-delete-char") + ) + handle("delete", filter=insert_mode, save_before=if_no_repeat)( + get_by_name("delete-char") + ) + handle("c-delete", filter=insert_mode, save_before=if_no_repeat)( + get_by_name("delete-char") + ) + handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)( + get_by_name("self-insert") + ) + handle("c-t", filter=insert_mode)(get_by_name("transpose-chars")) + handle("c-i", filter=insert_mode)(get_by_name("menu-complete")) + handle("s-tab", filter=insert_mode)(get_by_name("menu-complete-backward")) + + # Control-W should delete, using whitespace as separator, while M-Del + # should delete using [^a-zA-Z0-9] as a boundary. + handle("c-w", filter=insert_mode)(get_by_name("unix-word-rubout")) + + handle("pageup", filter=~has_selection)(get_by_name("previous-history")) + handle("pagedown", filter=~has_selection)(get_by_name("next-history")) + + # CTRL keys. + + @Condition + def has_text_before_cursor() -> bool: + return bool(get_app().current_buffer.text) + + handle("c-d", filter=has_text_before_cursor & insert_mode)( + get_by_name("delete-char") + ) + + @handle("enter", filter=insert_mode & is_multiline) + def _newline(event: E) -> None: + """ + Newline (in case of multiline input. + """ + event.current_buffer.newline(copy_margin=not in_paste_mode()) + + @handle("c-j") + def _newline2(event: E) -> None: + r""" + By default, handle \n as if it were a \r (enter). + (It appears that some terminals send \n instead of \r when pressing + enter. - at least the Linux subsystem for Windows.) + """ + event.key_processor.feed(KeyPress(Keys.ControlM, "\r"), first=True) + + # Delete the word before the cursor. + + @handle("up") + def _go_up(event: E) -> None: + event.current_buffer.auto_up(count=event.arg) + + @handle("down") + def _go_down(event: E) -> None: + event.current_buffer.auto_down(count=event.arg) + + @handle("delete", filter=has_selection) + def _cut(event: E) -> None: + data = event.current_buffer.cut_selection() + event.app.clipboard.set_data(data) + + # Global bindings. + + @handle("c-z") + def _insert_ctrl_z(event: E) -> None: + """ + By default, control-Z should literally insert Ctrl-Z. + (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File. + In a Python REPL for instance, it's possible to type + Control-Z followed by enter to quit.) + + When the system bindings are loaded and suspend-to-background is + supported, that will override this binding. + """ + event.current_buffer.insert_text(event.data) + + @handle(Keys.BracketedPaste) + def _paste(event: E) -> None: + """ + Pasting from clipboard. + """ + data = event.data + + # Be sure to use \n as line ending. + # Some terminals (Like iTerm2) seem to paste \r\n line endings in a + # bracketed paste. See: https://github.com/ipython/ipython/issues/9737 + data = data.replace("\r\n", "\n") + data = data.replace("\r", "\n") + + event.current_buffer.insert_text(data) + + @Condition + def in_quoted_insert() -> bool: + return get_app().quoted_insert + + @handle(Keys.Any, filter=in_quoted_insert, eager=True) + def _insert_text(event: E) -> None: + """ + Handle quoted insert. + """ + event.current_buffer.insert_text(event.data, overwrite=False) + event.app.quoted_insert = False + + return key_bindings diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/completion.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/completion.py new file mode 100644 index 0000000..a30b54e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/completion.py @@ -0,0 +1,203 @@ +""" +Key binding handlers for displaying completions. +""" +import asyncio +import math +from typing import TYPE_CHECKING, List + +from prompt_toolkit.application.run_in_terminal import in_terminal +from prompt_toolkit.completion import ( + CompleteEvent, + Completion, + get_common_complete_suffix, +) +from prompt_toolkit.formatted_text import StyleAndTextTuples +from prompt_toolkit.key_binding.key_bindings import KeyBindings +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.utils import get_cwidth + +if TYPE_CHECKING: + from prompt_toolkit.application import Application + from prompt_toolkit.shortcuts import PromptSession + +__all__ = [ + "generate_completions", + "display_completions_like_readline", +] + +E = KeyPressEvent + + +def generate_completions(event: E) -> None: + r""" + Tab-completion: where the first tab completes the common suffix and the + second tab lists all the completions. + """ + b = event.current_buffer + + # When already navigating through completions, select the next one. + if b.complete_state: + b.complete_next() + else: + b.start_completion(insert_common_part=True) + + +def display_completions_like_readline(event: E) -> None: + """ + Key binding handler for readline-style tab completion. + This is meant to be as similar as possible to the way how readline displays + completions. + + Generate the completions immediately (blocking) and display them above the + prompt in columns. + + Usage:: + + # Call this handler when 'Tab' has been pressed. + key_bindings.add(Keys.ControlI)(display_completions_like_readline) + """ + # Request completions. + b = event.current_buffer + if b.completer is None: + return + complete_event = CompleteEvent(completion_requested=True) + completions = list(b.completer.get_completions(b.document, complete_event)) + + # Calculate the common suffix. + common_suffix = get_common_complete_suffix(b.document, completions) + + # One completion: insert it. + if len(completions) == 1: + b.delete_before_cursor(-completions[0].start_position) + b.insert_text(completions[0].text) + # Multiple completions with common part. + elif common_suffix: + b.insert_text(common_suffix) + # Otherwise: display all completions. + elif completions: + _display_completions_like_readline(event.app, completions) + + +def _display_completions_like_readline( + app: "Application[object]", completions: List[Completion] +) -> "asyncio.Task[None]": + """ + Display the list of completions in columns above the prompt. + This will ask for a confirmation if there are too many completions to fit + on a single page and provide a paginator to walk through them. + """ + from prompt_toolkit.formatted_text import to_formatted_text + from prompt_toolkit.shortcuts.prompt import create_confirm_session + + # Get terminal dimensions. + term_size = app.output.get_size() + term_width = term_size.columns + term_height = term_size.rows + + # Calculate amount of required columns/rows for displaying the + # completions. (Keep in mind that completions are displayed + # alphabetically column-wise.) + max_compl_width = min( + term_width, max(get_cwidth(c.display_text) for c in completions) + 1 + ) + column_count = max(1, term_width // max_compl_width) + completions_per_page = column_count * (term_height - 1) + page_count = int(math.ceil(len(completions) / float(completions_per_page))) + # Note: math.ceil can return float on Python2. + + def display(page: int) -> None: + # Display completions. + page_completions = completions[ + page * completions_per_page : (page + 1) * completions_per_page + ] + + page_row_count = int(math.ceil(len(page_completions) / float(column_count))) + page_columns = [ + page_completions[i * page_row_count : (i + 1) * page_row_count] + for i in range(column_count) + ] + + result: StyleAndTextTuples = [] + + for r in range(page_row_count): + for c in range(column_count): + try: + completion = page_columns[c][r] + style = "class:readline-like-completions.completion " + ( + completion.style or "" + ) + + result.extend(to_formatted_text(completion.display, style=style)) + + # Add padding. + padding = max_compl_width - get_cwidth(completion.display_text) + result.append((completion.style, " " * padding)) + except IndexError: + pass + result.append(("", "\n")) + + app.print_text(to_formatted_text(result, "class:readline-like-completions")) + + # User interaction through an application generator function. + async def run_compl() -> None: + "Coroutine." + async with in_terminal(render_cli_done=True): + if len(completions) > completions_per_page: + # Ask confirmation if it doesn't fit on the screen. + confirm = await create_confirm_session( + f"Display all {len(completions)} possibilities?", + ).prompt_async() + + if confirm: + # Display pages. + for page in range(page_count): + display(page) + + if page != page_count - 1: + # Display --MORE-- and go to the next page. + show_more = await _create_more_session( + "--MORE--" + ).prompt_async() + + if not show_more: + return + else: + app.output.flush() + else: + # Display all completions. + display(0) + + return app.create_background_task(run_compl()) + + +def _create_more_session(message: str = "--MORE--") -> "PromptSession[bool]": + """ + Create a `PromptSession` object for displaying the "--MORE--". + """ + from prompt_toolkit.shortcuts import PromptSession + + bindings = KeyBindings() + + @bindings.add(" ") + @bindings.add("y") + @bindings.add("Y") + @bindings.add(Keys.ControlJ) + @bindings.add(Keys.ControlM) + @bindings.add(Keys.ControlI) # Tab. + def _yes(event: E) -> None: + event.app.exit(result=True) + + @bindings.add("n") + @bindings.add("N") + @bindings.add("q") + @bindings.add("Q") + @bindings.add(Keys.ControlC) + def _no(event: E) -> None: + event.app.exit(result=False) + + @bindings.add(Keys.Any) + def _ignore(event: E) -> None: + "Disable inserting of text." + + return PromptSession(message, key_bindings=bindings, erase_when_done=True) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/cpr.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/cpr.py new file mode 100644 index 0000000..07b0fa7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/cpr.py @@ -0,0 +1,28 @@ +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.keys import Keys + +from ..key_bindings import KeyBindings + +__all__ = [ + "load_cpr_bindings", +] + +E = KeyPressEvent + + +def load_cpr_bindings() -> KeyBindings: + key_bindings = KeyBindings() + + @key_bindings.add(Keys.CPRResponse, save_before=lambda e: False) + def _(event: E) -> None: + """ + Handle incoming Cursor-Position-Request response. + """ + # The incoming data looks like u'\x1b[35;1R' + # Parse row/col information. + row, col = map(int, event.data[2:-1].split(";")) + + # Report absolute cursor position to the renderer. + event.app.renderer.report_absolute_cursor_row(row) + + return key_bindings diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/emacs.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/emacs.py new file mode 100644 index 0000000..a4a5e34 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/emacs.py @@ -0,0 +1,557 @@ +# pylint: disable=function-redefined +from typing import Dict, Union + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import Buffer, indent, unindent +from prompt_toolkit.completion import CompleteEvent +from prompt_toolkit.filters import ( + Condition, + emacs_insert_mode, + emacs_mode, + has_arg, + has_selection, + in_paste_mode, + is_multiline, + is_read_only, + shift_selection_mode, + vi_search_direction_reversed, +) +from prompt_toolkit.key_binding.key_bindings import Binding +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.selection import SelectionType + +from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase +from .named_commands import get_by_name + +__all__ = [ + "load_emacs_bindings", + "load_emacs_search_bindings", + "load_emacs_shift_selection_bindings", +] + +E = KeyPressEvent + + +def load_emacs_bindings() -> KeyBindingsBase: + """ + Some e-macs extensions. + """ + # Overview of Readline emacs commands: + # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf + key_bindings = KeyBindings() + handle = key_bindings.add + + insert_mode = emacs_insert_mode + + @handle("escape") + def _esc(event: E) -> None: + """ + By default, ignore escape key. + + (If we don't put this here, and Esc is followed by a key which sequence + is not handled, we'll insert an Escape character in the input stream. + Something we don't want and happens to easily in emacs mode. + Further, people can always use ControlQ to do a quoted insert.) + """ + pass + + handle("c-a")(get_by_name("beginning-of-line")) + handle("c-b")(get_by_name("backward-char")) + handle("c-delete", filter=insert_mode)(get_by_name("kill-word")) + handle("c-e")(get_by_name("end-of-line")) + handle("c-f")(get_by_name("forward-char")) + handle("c-left")(get_by_name("backward-word")) + handle("c-right")(get_by_name("forward-word")) + handle("c-x", "r", "y", filter=insert_mode)(get_by_name("yank")) + handle("c-y", filter=insert_mode)(get_by_name("yank")) + handle("escape", "b")(get_by_name("backward-word")) + handle("escape", "c", filter=insert_mode)(get_by_name("capitalize-word")) + handle("escape", "d", filter=insert_mode)(get_by_name("kill-word")) + handle("escape", "f")(get_by_name("forward-word")) + handle("escape", "l", filter=insert_mode)(get_by_name("downcase-word")) + handle("escape", "u", filter=insert_mode)(get_by_name("uppercase-word")) + handle("escape", "y", filter=insert_mode)(get_by_name("yank-pop")) + handle("escape", "backspace", filter=insert_mode)(get_by_name("backward-kill-word")) + handle("escape", "\\", filter=insert_mode)(get_by_name("delete-horizontal-space")) + + handle("c-home")(get_by_name("beginning-of-buffer")) + handle("c-end")(get_by_name("end-of-buffer")) + + handle("c-_", save_before=(lambda e: False), filter=insert_mode)( + get_by_name("undo") + ) + + handle("c-x", "c-u", save_before=(lambda e: False), filter=insert_mode)( + get_by_name("undo") + ) + + handle("escape", "<", filter=~has_selection)(get_by_name("beginning-of-history")) + handle("escape", ">", filter=~has_selection)(get_by_name("end-of-history")) + + handle("escape", ".", filter=insert_mode)(get_by_name("yank-last-arg")) + handle("escape", "_", filter=insert_mode)(get_by_name("yank-last-arg")) + handle("escape", "c-y", filter=insert_mode)(get_by_name("yank-nth-arg")) + handle("escape", "#", filter=insert_mode)(get_by_name("insert-comment")) + handle("c-o")(get_by_name("operate-and-get-next")) + + # ControlQ does a quoted insert. Not that for vt100 terminals, you have to + # disable flow control by running ``stty -ixon``, otherwise Ctrl-Q and + # Ctrl-S are captured by the terminal. + handle("c-q", filter=~has_selection)(get_by_name("quoted-insert")) + + handle("c-x", "(")(get_by_name("start-kbd-macro")) + handle("c-x", ")")(get_by_name("end-kbd-macro")) + handle("c-x", "e")(get_by_name("call-last-kbd-macro")) + + @handle("c-n") + def _next(event: E) -> None: + "Next line." + event.current_buffer.auto_down() + + @handle("c-p") + def _prev(event: E) -> None: + "Previous line." + event.current_buffer.auto_up(count=event.arg) + + def handle_digit(c: str) -> None: + """ + Handle input of arguments. + The first number needs to be preceded by escape. + """ + + @handle(c, filter=has_arg) + @handle("escape", c) + def _(event: E) -> None: + event.append_to_arg_count(c) + + for c in "0123456789": + handle_digit(c) + + @handle("escape", "-", filter=~has_arg) + def _meta_dash(event: E) -> None: + """""" + if event._arg is None: + event.append_to_arg_count("-") + + @handle("-", filter=Condition(lambda: get_app().key_processor.arg == "-")) + def _dash(event: E) -> None: + """ + When '-' is typed again, after exactly '-' has been given as an + argument, ignore this. + """ + event.app.key_processor.arg = "-" + + @Condition + def is_returnable() -> bool: + return get_app().current_buffer.is_returnable + + # Meta + Enter: always accept input. + handle("escape", "enter", filter=insert_mode & is_returnable)( + get_by_name("accept-line") + ) + + # Enter: accept input in single line mode. + handle("enter", filter=insert_mode & is_returnable & ~is_multiline)( + get_by_name("accept-line") + ) + + def character_search(buff: Buffer, char: str, count: int) -> None: + if count < 0: + match = buff.document.find_backwards( + char, in_current_line=True, count=-count + ) + else: + match = buff.document.find(char, in_current_line=True, count=count) + + if match is not None: + buff.cursor_position += match + + @handle("c-]", Keys.Any) + def _goto_char(event: E) -> None: + "When Ctl-] + a character is pressed. go to that character." + # Also named 'character-search' + character_search(event.current_buffer, event.data, event.arg) + + @handle("escape", "c-]", Keys.Any) + def _goto_char_backwards(event: E) -> None: + "Like Ctl-], but backwards." + # Also named 'character-search-backward' + character_search(event.current_buffer, event.data, -event.arg) + + @handle("escape", "a") + def _prev_sentence(event: E) -> None: + "Previous sentence." + # TODO: + + @handle("escape", "e") + def _end_of_sentence(event: E) -> None: + "Move to end of sentence." + # TODO: + + @handle("escape", "t", filter=insert_mode) + def _swap_characters(event: E) -> None: + """ + Swap the last two words before the cursor. + """ + # TODO + + @handle("escape", "*", filter=insert_mode) + def _insert_all_completions(event: E) -> None: + """ + `meta-*`: Insert all possible completions of the preceding text. + """ + buff = event.current_buffer + + # List all completions. + complete_event = CompleteEvent(text_inserted=False, completion_requested=True) + completions = list( + buff.completer.get_completions(buff.document, complete_event) + ) + + # Insert them. + text_to_insert = " ".join(c.text for c in completions) + buff.insert_text(text_to_insert) + + @handle("c-x", "c-x") + def _toggle_start_end(event: E) -> None: + """ + Move cursor back and forth between the start and end of the current + line. + """ + buffer = event.current_buffer + + if buffer.document.is_cursor_at_the_end_of_line: + buffer.cursor_position += buffer.document.get_start_of_line_position( + after_whitespace=False + ) + else: + buffer.cursor_position += buffer.document.get_end_of_line_position() + + @handle("c-@") # Control-space or Control-@ + def _start_selection(event: E) -> None: + """ + Start of the selection (if the current buffer is not empty). + """ + # Take the current cursor position as the start of this selection. + buff = event.current_buffer + if buff.text: + buff.start_selection(selection_type=SelectionType.CHARACTERS) + + @handle("c-g", filter=~has_selection) + def _cancel(event: E) -> None: + """ + Control + G: Cancel completion menu and validation state. + """ + event.current_buffer.complete_state = None + event.current_buffer.validation_error = None + + @handle("c-g", filter=has_selection) + def _cancel_selection(event: E) -> None: + """ + Cancel selection. + """ + event.current_buffer.exit_selection() + + @handle("c-w", filter=has_selection) + @handle("c-x", "r", "k", filter=has_selection) + def _cut(event: E) -> None: + """ + Cut selected text. + """ + data = event.current_buffer.cut_selection() + event.app.clipboard.set_data(data) + + @handle("escape", "w", filter=has_selection) + def _copy(event: E) -> None: + """ + Copy selected text. + """ + data = event.current_buffer.copy_selection() + event.app.clipboard.set_data(data) + + @handle("escape", "left") + def _start_of_word(event: E) -> None: + """ + Cursor to start of previous word. + """ + buffer = event.current_buffer + buffer.cursor_position += ( + buffer.document.find_previous_word_beginning(count=event.arg) or 0 + ) + + @handle("escape", "right") + def _start_next_word(event: E) -> None: + """ + Cursor to start of next word. + """ + buffer = event.current_buffer + buffer.cursor_position += ( + buffer.document.find_next_word_beginning(count=event.arg) + or buffer.document.get_end_of_document_position() + ) + + @handle("escape", "/", filter=insert_mode) + def _complete(event: E) -> None: + """ + M-/: Complete. + """ + b = event.current_buffer + if b.complete_state: + b.complete_next() + else: + b.start_completion(select_first=True) + + @handle("c-c", ">", filter=has_selection) + def _indent(event: E) -> None: + """ + Indent selected text. + """ + buffer = event.current_buffer + + buffer.cursor_position += buffer.document.get_start_of_line_position( + after_whitespace=True + ) + + from_, to = buffer.document.selection_range() + from_, _ = buffer.document.translate_index_to_position(from_) + to, _ = buffer.document.translate_index_to_position(to) + + indent(buffer, from_, to + 1, count=event.arg) + + @handle("c-c", "<", filter=has_selection) + def _unindent(event: E) -> None: + """ + Unindent selected text. + """ + buffer = event.current_buffer + + from_, to = buffer.document.selection_range() + from_, _ = buffer.document.translate_index_to_position(from_) + to, _ = buffer.document.translate_index_to_position(to) + + unindent(buffer, from_, to + 1, count=event.arg) + + return ConditionalKeyBindings(key_bindings, emacs_mode) + + +def load_emacs_search_bindings() -> KeyBindingsBase: + key_bindings = KeyBindings() + handle = key_bindings.add + from . import search + + # NOTE: We don't bind 'Escape' to 'abort_search'. The reason is that we + # want Alt+Enter to accept input directly in incremental search mode. + # Instead, we have double escape. + + handle("c-r")(search.start_reverse_incremental_search) + handle("c-s")(search.start_forward_incremental_search) + + handle("c-c")(search.abort_search) + handle("c-g")(search.abort_search) + handle("c-r")(search.reverse_incremental_search) + handle("c-s")(search.forward_incremental_search) + handle("up")(search.reverse_incremental_search) + handle("down")(search.forward_incremental_search) + handle("enter")(search.accept_search) + + # Handling of escape. + handle("escape", eager=True)(search.accept_search) + + # Like Readline, it's more natural to accept the search when escape has + # been pressed, however instead the following two bindings could be used + # instead. + # #handle('escape', 'escape', eager=True)(search.abort_search) + # #handle('escape', 'enter', eager=True)(search.accept_search_and_accept_input) + + # If Read-only: also include the following key bindings: + + # '/' and '?' key bindings for searching, just like Vi mode. + handle("?", filter=is_read_only & ~vi_search_direction_reversed)( + search.start_reverse_incremental_search + ) + handle("/", filter=is_read_only & ~vi_search_direction_reversed)( + search.start_forward_incremental_search + ) + handle("?", filter=is_read_only & vi_search_direction_reversed)( + search.start_forward_incremental_search + ) + handle("/", filter=is_read_only & vi_search_direction_reversed)( + search.start_reverse_incremental_search + ) + + @handle("n", filter=is_read_only) + def _jump_next(event: E) -> None: + "Jump to next match." + event.current_buffer.apply_search( + event.app.current_search_state, + include_current_position=False, + count=event.arg, + ) + + @handle("N", filter=is_read_only) + def _jump_prev(event: E) -> None: + "Jump to previous match." + event.current_buffer.apply_search( + ~event.app.current_search_state, + include_current_position=False, + count=event.arg, + ) + + return ConditionalKeyBindings(key_bindings, emacs_mode) + + +def load_emacs_shift_selection_bindings() -> KeyBindingsBase: + """ + Bindings to select text with shift + cursor movements + """ + + key_bindings = KeyBindings() + handle = key_bindings.add + + def unshift_move(event: E) -> None: + """ + Used for the shift selection mode. When called with + a shift + movement key press event, moves the cursor + as if shift is not pressed. + """ + key = event.key_sequence[0].key + + if key == Keys.ShiftUp: + event.current_buffer.auto_up(count=event.arg) + return + if key == Keys.ShiftDown: + event.current_buffer.auto_down(count=event.arg) + return + + # the other keys are handled through their readline command + key_to_command: Dict[Union[Keys, str], str] = { + Keys.ShiftLeft: "backward-char", + Keys.ShiftRight: "forward-char", + Keys.ShiftHome: "beginning-of-line", + Keys.ShiftEnd: "end-of-line", + Keys.ControlShiftLeft: "backward-word", + Keys.ControlShiftRight: "forward-word", + Keys.ControlShiftHome: "beginning-of-buffer", + Keys.ControlShiftEnd: "end-of-buffer", + } + + try: + # Both the dict lookup and `get_by_name` can raise KeyError. + binding = get_by_name(key_to_command[key]) + except KeyError: + pass + else: # (`else` is not really needed here.) + if isinstance(binding, Binding): + # (It should always be a binding here) + binding.call(event) + + @handle("s-left", filter=~has_selection) + @handle("s-right", filter=~has_selection) + @handle("s-up", filter=~has_selection) + @handle("s-down", filter=~has_selection) + @handle("s-home", filter=~has_selection) + @handle("s-end", filter=~has_selection) + @handle("c-s-left", filter=~has_selection) + @handle("c-s-right", filter=~has_selection) + @handle("c-s-home", filter=~has_selection) + @handle("c-s-end", filter=~has_selection) + def _start_selection(event: E) -> None: + """ + Start selection with shift + movement. + """ + # Take the current cursor position as the start of this selection. + buff = event.current_buffer + if buff.text: + buff.start_selection(selection_type=SelectionType.CHARACTERS) + + if buff.selection_state is not None: + # (`selection_state` should never be `None`, it is created by + # `start_selection`.) + buff.selection_state.enter_shift_mode() + + # Then move the cursor + original_position = buff.cursor_position + unshift_move(event) + if buff.cursor_position == original_position: + # Cursor didn't actually move - so cancel selection + # to avoid having an empty selection + buff.exit_selection() + + @handle("s-left", filter=shift_selection_mode) + @handle("s-right", filter=shift_selection_mode) + @handle("s-up", filter=shift_selection_mode) + @handle("s-down", filter=shift_selection_mode) + @handle("s-home", filter=shift_selection_mode) + @handle("s-end", filter=shift_selection_mode) + @handle("c-s-left", filter=shift_selection_mode) + @handle("c-s-right", filter=shift_selection_mode) + @handle("c-s-home", filter=shift_selection_mode) + @handle("c-s-end", filter=shift_selection_mode) + def _extend_selection(event: E) -> None: + """ + Extend the selection + """ + # Just move the cursor, like shift was not pressed + unshift_move(event) + buff = event.current_buffer + + if buff.selection_state is not None: + if buff.cursor_position == buff.selection_state.original_cursor_position: + # selection is now empty, so cancel selection + buff.exit_selection() + + @handle(Keys.Any, filter=shift_selection_mode) + def _replace_selection(event: E) -> None: + """ + Replace selection by what is typed + """ + event.current_buffer.cut_selection() + get_by_name("self-insert").call(event) + + @handle("enter", filter=shift_selection_mode & is_multiline) + def _newline(event: E) -> None: + """ + A newline replaces the selection + """ + event.current_buffer.cut_selection() + event.current_buffer.newline(copy_margin=not in_paste_mode()) + + @handle("backspace", filter=shift_selection_mode) + def _delete(event: E) -> None: + """ + Delete selection. + """ + event.current_buffer.cut_selection() + + @handle("c-y", filter=shift_selection_mode) + def _yank(event: E) -> None: + """ + In shift selection mode, yanking (pasting) replace the selection. + """ + buff = event.current_buffer + if buff.selection_state: + buff.cut_selection() + get_by_name("yank").call(event) + + # moving the cursor in shift selection mode cancels the selection + @handle("left", filter=shift_selection_mode) + @handle("right", filter=shift_selection_mode) + @handle("up", filter=shift_selection_mode) + @handle("down", filter=shift_selection_mode) + @handle("home", filter=shift_selection_mode) + @handle("end", filter=shift_selection_mode) + @handle("c-left", filter=shift_selection_mode) + @handle("c-right", filter=shift_selection_mode) + @handle("c-home", filter=shift_selection_mode) + @handle("c-end", filter=shift_selection_mode) + def _cancel(event: E) -> None: + """ + Cancel selection. + """ + event.current_buffer.exit_selection() + # we then process the cursor movement + key_press = event.key_sequence[0] + event.key_processor.feed(key_press, first=True) + + return ConditionalKeyBindings(key_bindings, emacs_mode) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/focus.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/focus.py new file mode 100644 index 0000000..40844db --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/focus.py @@ -0,0 +1,24 @@ +from prompt_toolkit.key_binding.key_processor import KeyPressEvent + +__all__ = [ + "focus_next", + "focus_previous", +] + +E = KeyPressEvent + + +def focus_next(event: E) -> None: + """ + Focus the next visible Window. + (Often bound to the `Tab` key.) + """ + event.app.layout.focus_next() + + +def focus_previous(event: E) -> None: + """ + Focus the previous visible Window. + (Often bound to the `BackTab` key.) + """ + event.app.layout.focus_previous() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/mouse.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/mouse.py new file mode 100644 index 0000000..949c33f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/mouse.py @@ -0,0 +1,346 @@ +from typing import TYPE_CHECKING, FrozenSet + +from prompt_toolkit.data_structures import Point +from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.mouse_events import ( + MouseButton, + MouseEvent, + MouseEventType, + MouseModifier, +) +from prompt_toolkit.utils import is_windows + +from ..key_bindings import KeyBindings + +if TYPE_CHECKING: + from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone + +__all__ = [ + "load_mouse_bindings", +] + +E = KeyPressEvent + +# fmt: off +# flake8: noqa E201 +SCROLL_UP = MouseEventType.SCROLL_UP +SCROLL_DOWN = MouseEventType.SCROLL_DOWN +MOUSE_DOWN = MouseEventType.MOUSE_DOWN +MOUSE_MOVE = MouseEventType.MOUSE_MOVE +MOUSE_UP = MouseEventType.MOUSE_UP + +NO_MODIFIER : FrozenSet[MouseModifier] = frozenset() +SHIFT : FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT}) +ALT : FrozenSet[MouseModifier] = frozenset({MouseModifier.ALT}) +SHIFT_ALT : FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT}) +CONTROL : FrozenSet[MouseModifier] = frozenset({MouseModifier.CONTROL}) +SHIFT_CONTROL : FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.CONTROL}) +ALT_CONTROL : FrozenSet[MouseModifier] = frozenset({MouseModifier.ALT, MouseModifier.CONTROL}) +SHIFT_ALT_CONTROL: FrozenSet[MouseModifier] = frozenset({MouseModifier.SHIFT, MouseModifier.ALT, MouseModifier.CONTROL}) +UNKNOWN_MODIFIER : FrozenSet[MouseModifier] = frozenset() + +LEFT = MouseButton.LEFT +MIDDLE = MouseButton.MIDDLE +RIGHT = MouseButton.RIGHT +NO_BUTTON = MouseButton.NONE +UNKNOWN_BUTTON = MouseButton.UNKNOWN + +xterm_sgr_mouse_events = { + ( 0, 'm') : (LEFT, MOUSE_UP, NO_MODIFIER), # left_up 0+ + + =0 + ( 4, 'm') : (LEFT, MOUSE_UP, SHIFT), # left_up Shift 0+4+ + =4 + ( 8, 'm') : (LEFT, MOUSE_UP, ALT), # left_up Alt 0+ +8+ =8 + (12, 'm') : (LEFT, MOUSE_UP, SHIFT_ALT), # left_up Shift Alt 0+4+8+ =12 + (16, 'm') : (LEFT, MOUSE_UP, CONTROL), # left_up Control 0+ + +16=16 + (20, 'm') : (LEFT, MOUSE_UP, SHIFT_CONTROL), # left_up Shift Control 0+4+ +16=20 + (24, 'm') : (LEFT, MOUSE_UP, ALT_CONTROL), # left_up Alt Control 0+ +8+16=24 + (28, 'm') : (LEFT, MOUSE_UP, SHIFT_ALT_CONTROL), # left_up Shift Alt Control 0+4+8+16=28 + + ( 1, 'm') : (MIDDLE, MOUSE_UP, NO_MODIFIER), # middle_up 1+ + + =1 + ( 5, 'm') : (MIDDLE, MOUSE_UP, SHIFT), # middle_up Shift 1+4+ + =5 + ( 9, 'm') : (MIDDLE, MOUSE_UP, ALT), # middle_up Alt 1+ +8+ =9 + (13, 'm') : (MIDDLE, MOUSE_UP, SHIFT_ALT), # middle_up Shift Alt 1+4+8+ =13 + (17, 'm') : (MIDDLE, MOUSE_UP, CONTROL), # middle_up Control 1+ + +16=17 + (21, 'm') : (MIDDLE, MOUSE_UP, SHIFT_CONTROL), # middle_up Shift Control 1+4+ +16=21 + (25, 'm') : (MIDDLE, MOUSE_UP, ALT_CONTROL), # middle_up Alt Control 1+ +8+16=25 + (29, 'm') : (MIDDLE, MOUSE_UP, SHIFT_ALT_CONTROL), # middle_up Shift Alt Control 1+4+8+16=29 + + ( 2, 'm') : (RIGHT, MOUSE_UP, NO_MODIFIER), # right_up 2+ + + =2 + ( 6, 'm') : (RIGHT, MOUSE_UP, SHIFT), # right_up Shift 2+4+ + =6 + (10, 'm') : (RIGHT, MOUSE_UP, ALT), # right_up Alt 2+ +8+ =10 + (14, 'm') : (RIGHT, MOUSE_UP, SHIFT_ALT), # right_up Shift Alt 2+4+8+ =14 + (18, 'm') : (RIGHT, MOUSE_UP, CONTROL), # right_up Control 2+ + +16=18 + (22, 'm') : (RIGHT, MOUSE_UP, SHIFT_CONTROL), # right_up Shift Control 2+4+ +16=22 + (26, 'm') : (RIGHT, MOUSE_UP, ALT_CONTROL), # right_up Alt Control 2+ +8+16=26 + (30, 'm') : (RIGHT, MOUSE_UP, SHIFT_ALT_CONTROL), # right_up Shift Alt Control 2+4+8+16=30 + + ( 0, 'M') : (LEFT, MOUSE_DOWN, NO_MODIFIER), # left_down 0+ + + =0 + ( 4, 'M') : (LEFT, MOUSE_DOWN, SHIFT), # left_down Shift 0+4+ + =4 + ( 8, 'M') : (LEFT, MOUSE_DOWN, ALT), # left_down Alt 0+ +8+ =8 + (12, 'M') : (LEFT, MOUSE_DOWN, SHIFT_ALT), # left_down Shift Alt 0+4+8+ =12 + (16, 'M') : (LEFT, MOUSE_DOWN, CONTROL), # left_down Control 0+ + +16=16 + (20, 'M') : (LEFT, MOUSE_DOWN, SHIFT_CONTROL), # left_down Shift Control 0+4+ +16=20 + (24, 'M') : (LEFT, MOUSE_DOWN, ALT_CONTROL), # left_down Alt Control 0+ +8+16=24 + (28, 'M') : (LEFT, MOUSE_DOWN, SHIFT_ALT_CONTROL), # left_down Shift Alt Control 0+4+8+16=28 + + ( 1, 'M') : (MIDDLE, MOUSE_DOWN, NO_MODIFIER), # middle_down 1+ + + =1 + ( 5, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT), # middle_down Shift 1+4+ + =5 + ( 9, 'M') : (MIDDLE, MOUSE_DOWN, ALT), # middle_down Alt 1+ +8+ =9 + (13, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT_ALT), # middle_down Shift Alt 1+4+8+ =13 + (17, 'M') : (MIDDLE, MOUSE_DOWN, CONTROL), # middle_down Control 1+ + +16=17 + (21, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT_CONTROL), # middle_down Shift Control 1+4+ +16=21 + (25, 'M') : (MIDDLE, MOUSE_DOWN, ALT_CONTROL), # middle_down Alt Control 1+ +8+16=25 + (29, 'M') : (MIDDLE, MOUSE_DOWN, SHIFT_ALT_CONTROL), # middle_down Shift Alt Control 1+4+8+16=29 + + ( 2, 'M') : (RIGHT, MOUSE_DOWN, NO_MODIFIER), # right_down 2+ + + =2 + ( 6, 'M') : (RIGHT, MOUSE_DOWN, SHIFT), # right_down Shift 2+4+ + =6 + (10, 'M') : (RIGHT, MOUSE_DOWN, ALT), # right_down Alt 2+ +8+ =10 + (14, 'M') : (RIGHT, MOUSE_DOWN, SHIFT_ALT), # right_down Shift Alt 2+4+8+ =14 + (18, 'M') : (RIGHT, MOUSE_DOWN, CONTROL), # right_down Control 2+ + +16=18 + (22, 'M') : (RIGHT, MOUSE_DOWN, SHIFT_CONTROL), # right_down Shift Control 2+4+ +16=22 + (26, 'M') : (RIGHT, MOUSE_DOWN, ALT_CONTROL), # right_down Alt Control 2+ +8+16=26 + (30, 'M') : (RIGHT, MOUSE_DOWN, SHIFT_ALT_CONTROL), # right_down Shift Alt Control 2+4+8+16=30 + + (32, 'M') : (LEFT, MOUSE_MOVE, NO_MODIFIER), # left_drag 32+ + + =32 + (36, 'M') : (LEFT, MOUSE_MOVE, SHIFT), # left_drag Shift 32+4+ + =36 + (40, 'M') : (LEFT, MOUSE_MOVE, ALT), # left_drag Alt 32+ +8+ =40 + (44, 'M') : (LEFT, MOUSE_MOVE, SHIFT_ALT), # left_drag Shift Alt 32+4+8+ =44 + (48, 'M') : (LEFT, MOUSE_MOVE, CONTROL), # left_drag Control 32+ + +16=48 + (52, 'M') : (LEFT, MOUSE_MOVE, SHIFT_CONTROL), # left_drag Shift Control 32+4+ +16=52 + (56, 'M') : (LEFT, MOUSE_MOVE, ALT_CONTROL), # left_drag Alt Control 32+ +8+16=56 + (60, 'M') : (LEFT, MOUSE_MOVE, SHIFT_ALT_CONTROL), # left_drag Shift Alt Control 32+4+8+16=60 + + (33, 'M') : (MIDDLE, MOUSE_MOVE, NO_MODIFIER), # middle_drag 33+ + + =33 + (37, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT), # middle_drag Shift 33+4+ + =37 + (41, 'M') : (MIDDLE, MOUSE_MOVE, ALT), # middle_drag Alt 33+ +8+ =41 + (45, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT_ALT), # middle_drag Shift Alt 33+4+8+ =45 + (49, 'M') : (MIDDLE, MOUSE_MOVE, CONTROL), # middle_drag Control 33+ + +16=49 + (53, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT_CONTROL), # middle_drag Shift Control 33+4+ +16=53 + (57, 'M') : (MIDDLE, MOUSE_MOVE, ALT_CONTROL), # middle_drag Alt Control 33+ +8+16=57 + (61, 'M') : (MIDDLE, MOUSE_MOVE, SHIFT_ALT_CONTROL), # middle_drag Shift Alt Control 33+4+8+16=61 + + (34, 'M') : (RIGHT, MOUSE_MOVE, NO_MODIFIER), # right_drag 34+ + + =34 + (38, 'M') : (RIGHT, MOUSE_MOVE, SHIFT), # right_drag Shift 34+4+ + =38 + (42, 'M') : (RIGHT, MOUSE_MOVE, ALT), # right_drag Alt 34+ +8+ =42 + (46, 'M') : (RIGHT, MOUSE_MOVE, SHIFT_ALT), # right_drag Shift Alt 34+4+8+ =46 + (50, 'M') : (RIGHT, MOUSE_MOVE, CONTROL), # right_drag Control 34+ + +16=50 + (54, 'M') : (RIGHT, MOUSE_MOVE, SHIFT_CONTROL), # right_drag Shift Control 34+4+ +16=54 + (58, 'M') : (RIGHT, MOUSE_MOVE, ALT_CONTROL), # right_drag Alt Control 34+ +8+16=58 + (62, 'M') : (RIGHT, MOUSE_MOVE, SHIFT_ALT_CONTROL), # right_drag Shift Alt Control 34+4+8+16=62 + + (35, 'M') : (NO_BUTTON, MOUSE_MOVE, NO_MODIFIER), # none_drag 35+ + + =35 + (39, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT), # none_drag Shift 35+4+ + =39 + (43, 'M') : (NO_BUTTON, MOUSE_MOVE, ALT), # none_drag Alt 35+ +8+ =43 + (47, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT_ALT), # none_drag Shift Alt 35+4+8+ =47 + (51, 'M') : (NO_BUTTON, MOUSE_MOVE, CONTROL), # none_drag Control 35+ + +16=51 + (55, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT_CONTROL), # none_drag Shift Control 35+4+ +16=55 + (59, 'M') : (NO_BUTTON, MOUSE_MOVE, ALT_CONTROL), # none_drag Alt Control 35+ +8+16=59 + (63, 'M') : (NO_BUTTON, MOUSE_MOVE, SHIFT_ALT_CONTROL), # none_drag Shift Alt Control 35+4+8+16=63 + + (64, 'M') : (NO_BUTTON, SCROLL_UP, NO_MODIFIER), # scroll_up 64+ + + =64 + (68, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT), # scroll_up Shift 64+4+ + =68 + (72, 'M') : (NO_BUTTON, SCROLL_UP, ALT), # scroll_up Alt 64+ +8+ =72 + (76, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT_ALT), # scroll_up Shift Alt 64+4+8+ =76 + (80, 'M') : (NO_BUTTON, SCROLL_UP, CONTROL), # scroll_up Control 64+ + +16=80 + (84, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT_CONTROL), # scroll_up Shift Control 64+4+ +16=84 + (88, 'M') : (NO_BUTTON, SCROLL_UP, ALT_CONTROL), # scroll_up Alt Control 64+ +8+16=88 + (92, 'M') : (NO_BUTTON, SCROLL_UP, SHIFT_ALT_CONTROL), # scroll_up Shift Alt Control 64+4+8+16=92 + + (65, 'M') : (NO_BUTTON, SCROLL_DOWN, NO_MODIFIER), # scroll_down 64+ + + =65 + (69, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT), # scroll_down Shift 64+4+ + =69 + (73, 'M') : (NO_BUTTON, SCROLL_DOWN, ALT), # scroll_down Alt 64+ +8+ =73 + (77, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT_ALT), # scroll_down Shift Alt 64+4+8+ =77 + (81, 'M') : (NO_BUTTON, SCROLL_DOWN, CONTROL), # scroll_down Control 64+ + +16=81 + (85, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT_CONTROL), # scroll_down Shift Control 64+4+ +16=85 + (89, 'M') : (NO_BUTTON, SCROLL_DOWN, ALT_CONTROL), # scroll_down Alt Control 64+ +8+16=89 + (93, 'M') : (NO_BUTTON, SCROLL_DOWN, SHIFT_ALT_CONTROL), # scroll_down Shift Alt Control 64+4+8+16=93 +} + +typical_mouse_events = { + 32: (LEFT , MOUSE_DOWN , UNKNOWN_MODIFIER), + 33: (MIDDLE , MOUSE_DOWN , UNKNOWN_MODIFIER), + 34: (RIGHT , MOUSE_DOWN , UNKNOWN_MODIFIER), + 35: (UNKNOWN_BUTTON , MOUSE_UP , UNKNOWN_MODIFIER), + + 64: (LEFT , MOUSE_MOVE , UNKNOWN_MODIFIER), + 65: (MIDDLE , MOUSE_MOVE , UNKNOWN_MODIFIER), + 66: (RIGHT , MOUSE_MOVE , UNKNOWN_MODIFIER), + 67: (NO_BUTTON , MOUSE_MOVE , UNKNOWN_MODIFIER), + + 96: (NO_BUTTON , SCROLL_UP , UNKNOWN_MODIFIER), + 97: (NO_BUTTON , SCROLL_DOWN, UNKNOWN_MODIFIER), +} + +urxvt_mouse_events={ + 32: (UNKNOWN_BUTTON, MOUSE_DOWN , UNKNOWN_MODIFIER), + 35: (UNKNOWN_BUTTON, MOUSE_UP , UNKNOWN_MODIFIER), + 96: (NO_BUTTON , SCROLL_UP , UNKNOWN_MODIFIER), + 97: (NO_BUTTON , SCROLL_DOWN, UNKNOWN_MODIFIER), +} +# fmt:on + + +def load_mouse_bindings() -> KeyBindings: + """ + Key bindings, required for mouse support. + (Mouse events enter through the key binding system.) + """ + key_bindings = KeyBindings() + + @key_bindings.add(Keys.Vt100MouseEvent) + def _(event: E) -> "NotImplementedOrNone": + """ + Handling of incoming mouse event. + """ + # TypicaL: "eSC[MaB*" + # Urxvt: "Esc[96;14;13M" + # Xterm SGR: "Esc[<64;85;12M" + + # Parse incoming packet. + if event.data[2] == "M": + # Typical. + mouse_event, x, y = map(ord, event.data[3:]) + + # TODO: Is it possible to add modifiers here? + mouse_button, mouse_event_type, mouse_modifier = typical_mouse_events[ + mouse_event + ] + + # Handle situations where `PosixStdinReader` used surrogateescapes. + if x >= 0xDC00: + x -= 0xDC00 + if y >= 0xDC00: + y -= 0xDC00 + + x -= 32 + y -= 32 + else: + # Urxvt and Xterm SGR. + # When the '<' is not present, we are not using the Xterm SGR mode, + # but Urxvt instead. + data = event.data[2:] + if data[:1] == "<": + sgr = True + data = data[1:] + else: + sgr = False + + # Extract coordinates. + mouse_event, x, y = map(int, data[:-1].split(";")) + m = data[-1] + + # Parse event type. + if sgr: + try: + ( + mouse_button, + mouse_event_type, + mouse_modifiers, + ) = xterm_sgr_mouse_events[mouse_event, m] + except KeyError: + return NotImplemented + + else: + # Some other terminals, like urxvt, Hyper terminal, ... + ( + mouse_button, + mouse_event_type, + mouse_modifiers, + ) = urxvt_mouse_events.get( + mouse_event, (UNKNOWN_BUTTON, MOUSE_MOVE, UNKNOWN_MODIFIER) + ) + + x -= 1 + y -= 1 + + # Only handle mouse events when we know the window height. + if event.app.renderer.height_is_known and mouse_event_type is not None: + # Take region above the layout into account. The reported + # coordinates are absolute to the visible part of the terminal. + from prompt_toolkit.renderer import HeightIsUnknownError + + try: + y -= event.app.renderer.rows_above_layout + except HeightIsUnknownError: + return NotImplemented + + # Call the mouse handler from the renderer. + + # Note: This can return `NotImplemented` if no mouse handler was + # found for this position, or if no repainting needs to + # happen. this way, we avoid excessive repaints during mouse + # movements. + handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x] + return handler( + MouseEvent( + position=Point(x=x, y=y), + event_type=mouse_event_type, + button=mouse_button, + modifiers=mouse_modifiers, + ) + ) + + return NotImplemented + + @key_bindings.add(Keys.ScrollUp) + def _scroll_up(event: E) -> None: + """ + Scroll up event without cursor position. + """ + # We don't receive a cursor position, so we don't know which window to + # scroll. Just send an 'up' key press instead. + event.key_processor.feed(KeyPress(Keys.Up), first=True) + + @key_bindings.add(Keys.ScrollDown) + def _scroll_down(event: E) -> None: + """ + Scroll down event without cursor position. + """ + event.key_processor.feed(KeyPress(Keys.Down), first=True) + + @key_bindings.add(Keys.WindowsMouseEvent) + def _mouse(event: E) -> "NotImplementedOrNone": + """ + Handling of mouse events for Windows. + """ + assert is_windows() # This key binding should only exist for Windows. + + # Parse data. + pieces = event.data.split(";") + + button = MouseButton(pieces[0]) + event_type = MouseEventType(pieces[1]) + x = int(pieces[2]) + y = int(pieces[3]) + + # Make coordinates absolute to the visible part of the terminal. + output = event.app.renderer.output + + from prompt_toolkit.output.win32 import Win32Output + from prompt_toolkit.output.windows10 import Windows10_Output + + if isinstance(output, (Win32Output, Windows10_Output)): + screen_buffer_info = output.get_win32_screen_buffer_info() + rows_above_cursor = ( + screen_buffer_info.dwCursorPosition.Y - event.app.renderer._cursor_pos.y + ) + y -= rows_above_cursor + + # Call the mouse event handler. + # (Can return `NotImplemented`.) + handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x] + + return handler( + MouseEvent( + position=Point(x=x, y=y), + event_type=event_type, + button=button, + modifiers=UNKNOWN_MODIFIER, + ) + ) + + # No mouse handler found. Return `NotImplemented` so that we don't + # invalidate the UI. + return NotImplemented + + return key_bindings diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/named_commands.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/named_commands.py new file mode 100644 index 0000000..e0796ef --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/named_commands.py @@ -0,0 +1,687 @@ +""" +Key bindings which are also known by GNU Readline by the given names. + +See: http://www.delorie.com/gnu/docs/readline/rlman_13.html +""" +from typing import Callable, Dict, TypeVar, Union, cast + +from prompt_toolkit.document import Document +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.key_binding.key_bindings import Binding, key_binding +from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout.controls import BufferControl +from prompt_toolkit.search import SearchDirection +from prompt_toolkit.selection import PasteMode + +from .completion import display_completions_like_readline, generate_completions + +__all__ = [ + "get_by_name", +] + + +# Typing. +_Handler = Callable[[KeyPressEvent], None] +_HandlerOrBinding = Union[_Handler, Binding] +_T = TypeVar("_T", bound=_HandlerOrBinding) +E = KeyPressEvent + + +# Registry that maps the Readline command names to their handlers. +_readline_commands: Dict[str, Binding] = {} + + +def register(name: str) -> Callable[[_T], _T]: + """ + Store handler in the `_readline_commands` dictionary. + """ + + def decorator(handler: _T) -> _T: + "`handler` is a callable or Binding." + if isinstance(handler, Binding): + _readline_commands[name] = handler + else: + _readline_commands[name] = key_binding()(cast(_Handler, handler)) + + return handler + + return decorator + + +def get_by_name(name: str) -> Binding: + """ + Return the handler for the (Readline) command with the given name. + """ + try: + return _readline_commands[name] + except KeyError as e: + raise KeyError("Unknown Readline command: %r" % name) from e + + +# +# Commands for moving +# See: http://www.delorie.com/gnu/docs/readline/rlman_14.html +# + + +@register("beginning-of-buffer") +def beginning_of_buffer(event: E) -> None: + """ + Move to the start of the buffer. + """ + buff = event.current_buffer + buff.cursor_position = 0 + + +@register("end-of-buffer") +def end_of_buffer(event: E) -> None: + """ + Move to the end of the buffer. + """ + buff = event.current_buffer + buff.cursor_position = len(buff.text) + + +@register("beginning-of-line") +def beginning_of_line(event: E) -> None: + """ + Move to the start of the current line. + """ + buff = event.current_buffer + buff.cursor_position += buff.document.get_start_of_line_position( + after_whitespace=False + ) + + +@register("end-of-line") +def end_of_line(event: E) -> None: + """ + Move to the end of the line. + """ + buff = event.current_buffer + buff.cursor_position += buff.document.get_end_of_line_position() + + +@register("forward-char") +def forward_char(event: E) -> None: + """ + Move forward a character. + """ + buff = event.current_buffer + buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg) + + +@register("backward-char") +def backward_char(event: E) -> None: + "Move back a character." + buff = event.current_buffer + buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg) + + +@register("forward-word") +def forward_word(event: E) -> None: + """ + Move forward to the end of the next word. Words are composed of letters and + digits. + """ + buff = event.current_buffer + pos = buff.document.find_next_word_ending(count=event.arg) + + if pos: + buff.cursor_position += pos + + +@register("backward-word") +def backward_word(event: E) -> None: + """ + Move back to the start of the current or previous word. Words are composed + of letters and digits. + """ + buff = event.current_buffer + pos = buff.document.find_previous_word_beginning(count=event.arg) + + if pos: + buff.cursor_position += pos + + +@register("clear-screen") +def clear_screen(event: E) -> None: + """ + Clear the screen and redraw everything at the top of the screen. + """ + event.app.renderer.clear() + + +@register("redraw-current-line") +def redraw_current_line(event: E) -> None: + """ + Refresh the current line. + (Readline defines this command, but prompt-toolkit doesn't have it.) + """ + pass + + +# +# Commands for manipulating the history. +# See: http://www.delorie.com/gnu/docs/readline/rlman_15.html +# + + +@register("accept-line") +def accept_line(event: E) -> None: + """ + Accept the line regardless of where the cursor is. + """ + event.current_buffer.validate_and_handle() + + +@register("previous-history") +def previous_history(event: E) -> None: + """ + Move `back` through the history list, fetching the previous command. + """ + event.current_buffer.history_backward(count=event.arg) + + +@register("next-history") +def next_history(event: E) -> None: + """ + Move `forward` through the history list, fetching the next command. + """ + event.current_buffer.history_forward(count=event.arg) + + +@register("beginning-of-history") +def beginning_of_history(event: E) -> None: + """ + Move to the first line in the history. + """ + event.current_buffer.go_to_history(0) + + +@register("end-of-history") +def end_of_history(event: E) -> None: + """ + Move to the end of the input history, i.e., the line currently being entered. + """ + event.current_buffer.history_forward(count=10**100) + buff = event.current_buffer + buff.go_to_history(len(buff._working_lines) - 1) + + +@register("reverse-search-history") +def reverse_search_history(event: E) -> None: + """ + Search backward starting at the current line and moving `up` through + the history as necessary. This is an incremental search. + """ + control = event.app.layout.current_control + + if isinstance(control, BufferControl) and control.search_buffer_control: + event.app.current_search_state.direction = SearchDirection.BACKWARD + event.app.layout.current_control = control.search_buffer_control + + +# +# Commands for changing text +# + + +@register("end-of-file") +def end_of_file(event: E) -> None: + """ + Exit. + """ + event.app.exit() + + +@register("delete-char") +def delete_char(event: E) -> None: + """ + Delete character before the cursor. + """ + deleted = event.current_buffer.delete(count=event.arg) + if not deleted: + event.app.output.bell() + + +@register("backward-delete-char") +def backward_delete_char(event: E) -> None: + """ + Delete the character behind the cursor. + """ + if event.arg < 0: + # When a negative argument has been given, this should delete in front + # of the cursor. + deleted = event.current_buffer.delete(count=-event.arg) + else: + deleted = event.current_buffer.delete_before_cursor(count=event.arg) + + if not deleted: + event.app.output.bell() + + +@register("self-insert") +def self_insert(event: E) -> None: + """ + Insert yourself. + """ + event.current_buffer.insert_text(event.data * event.arg) + + +@register("transpose-chars") +def transpose_chars(event: E) -> None: + """ + Emulate Emacs transpose-char behavior: at the beginning of the buffer, + do nothing. At the end of a line or buffer, swap the characters before + the cursor. Otherwise, move the cursor right, and then swap the + characters before the cursor. + """ + b = event.current_buffer + p = b.cursor_position + if p == 0: + return + elif p == len(b.text) or b.text[p] == "\n": + b.swap_characters_before_cursor() + else: + b.cursor_position += b.document.get_cursor_right_position() + b.swap_characters_before_cursor() + + +@register("uppercase-word") +def uppercase_word(event: E) -> None: + """ + Uppercase the current (or following) word. + """ + buff = event.current_buffer + + for i in range(event.arg): + pos = buff.document.find_next_word_ending() + words = buff.document.text_after_cursor[:pos] + buff.insert_text(words.upper(), overwrite=True) + + +@register("downcase-word") +def downcase_word(event: E) -> None: + """ + Lowercase the current (or following) word. + """ + buff = event.current_buffer + + for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!! + pos = buff.document.find_next_word_ending() + words = buff.document.text_after_cursor[:pos] + buff.insert_text(words.lower(), overwrite=True) + + +@register("capitalize-word") +def capitalize_word(event: E) -> None: + """ + Capitalize the current (or following) word. + """ + buff = event.current_buffer + + for i in range(event.arg): + pos = buff.document.find_next_word_ending() + words = buff.document.text_after_cursor[:pos] + buff.insert_text(words.title(), overwrite=True) + + +@register("quoted-insert") +def quoted_insert(event: E) -> None: + """ + Add the next character typed to the line verbatim. This is how to insert + key sequences like C-q, for example. + """ + event.app.quoted_insert = True + + +# +# Killing and yanking. +# + + +@register("kill-line") +def kill_line(event: E) -> None: + """ + Kill the text from the cursor to the end of the line. + + If we are at the end of the line, this should remove the newline. + (That way, it is possible to delete multiple lines by executing this + command multiple times.) + """ + buff = event.current_buffer + if event.arg < 0: + deleted = buff.delete_before_cursor( + count=-buff.document.get_start_of_line_position() + ) + else: + if buff.document.current_char == "\n": + deleted = buff.delete(1) + else: + deleted = buff.delete(count=buff.document.get_end_of_line_position()) + event.app.clipboard.set_text(deleted) + + +@register("kill-word") +def kill_word(event: E) -> None: + """ + Kill from point to the end of the current word, or if between words, to the + end of the next word. Word boundaries are the same as forward-word. + """ + buff = event.current_buffer + pos = buff.document.find_next_word_ending(count=event.arg) + + if pos: + deleted = buff.delete(count=pos) + + if event.is_repeat: + deleted = event.app.clipboard.get_data().text + deleted + + event.app.clipboard.set_text(deleted) + + +@register("unix-word-rubout") +def unix_word_rubout(event: E, WORD: bool = True) -> None: + """ + Kill the word behind point, using whitespace as a word boundary. + Usually bound to ControlW. + """ + buff = event.current_buffer + pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD) + + if pos is None: + # Nothing found? delete until the start of the document. (The + # input starts with whitespace and no words were found before the + # cursor.) + pos = -buff.cursor_position + + if pos: + deleted = buff.delete_before_cursor(count=-pos) + + # If the previous key press was also Control-W, concatenate deleted + # text. + if event.is_repeat: + deleted += event.app.clipboard.get_data().text + + event.app.clipboard.set_text(deleted) + else: + # Nothing to delete. Bell. + event.app.output.bell() + + +@register("backward-kill-word") +def backward_kill_word(event: E) -> None: + """ + Kills the word before point, using "not a letter nor a digit" as a word boundary. + Usually bound to M-Del or M-Backspace. + """ + unix_word_rubout(event, WORD=False) + + +@register("delete-horizontal-space") +def delete_horizontal_space(event: E) -> None: + """ + Delete all spaces and tabs around point. + """ + buff = event.current_buffer + text_before_cursor = buff.document.text_before_cursor + text_after_cursor = buff.document.text_after_cursor + + delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip("\t ")) + delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip("\t ")) + + buff.delete_before_cursor(count=delete_before) + buff.delete(count=delete_after) + + +@register("unix-line-discard") +def unix_line_discard(event: E) -> None: + """ + Kill backward from the cursor to the beginning of the current line. + """ + buff = event.current_buffer + + if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0: + buff.delete_before_cursor(count=1) + else: + deleted = buff.delete_before_cursor( + count=-buff.document.get_start_of_line_position() + ) + event.app.clipboard.set_text(deleted) + + +@register("yank") +def yank(event: E) -> None: + """ + Paste before cursor. + """ + event.current_buffer.paste_clipboard_data( + event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS + ) + + +@register("yank-nth-arg") +def yank_nth_arg(event: E) -> None: + """ + Insert the first argument of the previous command. With an argument, insert + the nth word from the previous command (start counting at 0). + """ + n = event.arg if event.arg_present else None + event.current_buffer.yank_nth_arg(n) + + +@register("yank-last-arg") +def yank_last_arg(event: E) -> None: + """ + Like `yank_nth_arg`, but if no argument has been given, yank the last word + of each line. + """ + n = event.arg if event.arg_present else None + event.current_buffer.yank_last_arg(n) + + +@register("yank-pop") +def yank_pop(event: E) -> None: + """ + Rotate the kill ring, and yank the new top. Only works following yank or + yank-pop. + """ + buff = event.current_buffer + doc_before_paste = buff.document_before_paste + clipboard = event.app.clipboard + + if doc_before_paste is not None: + buff.document = doc_before_paste + clipboard.rotate() + buff.paste_clipboard_data(clipboard.get_data(), paste_mode=PasteMode.EMACS) + + +# +# Completion. +# + + +@register("complete") +def complete(event: E) -> None: + """ + Attempt to perform completion. + """ + display_completions_like_readline(event) + + +@register("menu-complete") +def menu_complete(event: E) -> None: + """ + Generate completions, or go to the next completion. (This is the default + way of completing input in prompt_toolkit.) + """ + generate_completions(event) + + +@register("menu-complete-backward") +def menu_complete_backward(event: E) -> None: + """ + Move backward through the list of possible completions. + """ + event.current_buffer.complete_previous() + + +# +# Keyboard macros. +# + + +@register("start-kbd-macro") +def start_kbd_macro(event: E) -> None: + """ + Begin saving the characters typed into the current keyboard macro. + """ + event.app.emacs_state.start_macro() + + +@register("end-kbd-macro") +def end_kbd_macro(event: E) -> None: + """ + Stop saving the characters typed into the current keyboard macro and save + the definition. + """ + event.app.emacs_state.end_macro() + + +@register("call-last-kbd-macro") +@key_binding(record_in_macro=False) +def call_last_kbd_macro(event: E) -> None: + """ + Re-execute the last keyboard macro defined, by making the characters in the + macro appear as if typed at the keyboard. + + Notice that we pass `record_in_macro=False`. This ensures that the 'c-x e' + key sequence doesn't appear in the recording itself. This function inserts + the body of the called macro back into the KeyProcessor, so these keys will + be added later on to the macro of their handlers have `record_in_macro=True`. + """ + # Insert the macro. + macro = event.app.emacs_state.macro + + if macro: + event.app.key_processor.feed_multiple(macro, first=True) + + +@register("print-last-kbd-macro") +def print_last_kbd_macro(event: E) -> None: + """ + Print the last keyboard macro. + """ + # TODO: Make the format suitable for the inputrc file. + def print_macro() -> None: + macro = event.app.emacs_state.macro + if macro: + for k in macro: + print(k) + + from prompt_toolkit.application.run_in_terminal import run_in_terminal + + run_in_terminal(print_macro) + + +# +# Miscellaneous Commands. +# + + +@register("undo") +def undo(event: E) -> None: + """ + Incremental undo. + """ + event.current_buffer.undo() + + +@register("insert-comment") +def insert_comment(event: E) -> None: + """ + Without numeric argument, comment all lines. + With numeric argument, uncomment all lines. + In any case accept the input. + """ + buff = event.current_buffer + + # Transform all lines. + if event.arg != 1: + + def change(line: str) -> str: + return line[1:] if line.startswith("#") else line + + else: + + def change(line: str) -> str: + return "#" + line + + buff.document = Document( + text="\n".join(map(change, buff.text.splitlines())), cursor_position=0 + ) + + # Accept input. + buff.validate_and_handle() + + +@register("vi-editing-mode") +def vi_editing_mode(event: E) -> None: + """ + Switch to Vi editing mode. + """ + event.app.editing_mode = EditingMode.VI + + +@register("emacs-editing-mode") +def emacs_editing_mode(event: E) -> None: + """ + Switch to Emacs editing mode. + """ + event.app.editing_mode = EditingMode.EMACS + + +@register("prefix-meta") +def prefix_meta(event: E) -> None: + """ + Metafy the next character typed. This is for keyboards without a meta key. + + Sometimes people also want to bind other keys to Meta, e.g. 'jj':: + + key_bindings.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta) + """ + # ('first' should be true, because we want to insert it at the current + # position in the queue.) + event.app.key_processor.feed(KeyPress(Keys.Escape), first=True) + + +@register("operate-and-get-next") +def operate_and_get_next(event: E) -> None: + """ + Accept the current line for execution and fetch the next line relative to + the current line from the history for editing. + """ + buff = event.current_buffer + new_index = buff.working_index + 1 + + # Accept the current input. (This will also redraw the interface in the + # 'done' state.) + buff.validate_and_handle() + + # Set the new index at the start of the next run. + def set_working_index() -> None: + if new_index < len(buff._working_lines): + buff.working_index = new_index + + event.app.pre_run_callables.append(set_working_index) + + +@register("edit-and-execute-command") +def edit_and_execute(event: E) -> None: + """ + Invoke an editor on the current command line, and accept the result. + """ + buff = event.current_buffer + buff.open_in_editor(validate_and_handle=True) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/open_in_editor.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/open_in_editor.py new file mode 100644 index 0000000..f8699f4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/open_in_editor.py @@ -0,0 +1,49 @@ +""" +Open in editor key bindings. +""" +from prompt_toolkit.filters import emacs_mode, has_selection, vi_navigation_mode + +from ..key_bindings import KeyBindings, KeyBindingsBase, merge_key_bindings +from .named_commands import get_by_name + +__all__ = [ + "load_open_in_editor_bindings", + "load_emacs_open_in_editor_bindings", + "load_vi_open_in_editor_bindings", +] + + +def load_open_in_editor_bindings() -> KeyBindingsBase: + """ + Load both the Vi and emacs key bindings for handling edit-and-execute-command. + """ + return merge_key_bindings( + [ + load_emacs_open_in_editor_bindings(), + load_vi_open_in_editor_bindings(), + ] + ) + + +def load_emacs_open_in_editor_bindings() -> KeyBindings: + """ + Pressing C-X C-E will open the buffer in an external editor. + """ + key_bindings = KeyBindings() + + key_bindings.add("c-x", "c-e", filter=emacs_mode & ~has_selection)( + get_by_name("edit-and-execute-command") + ) + + return key_bindings + + +def load_vi_open_in_editor_bindings() -> KeyBindings: + """ + Pressing 'v' in navigation mode will open the buffer in an external editor. + """ + key_bindings = KeyBindings() + key_bindings.add("v", filter=vi_navigation_mode)( + get_by_name("edit-and-execute-command") + ) + return key_bindings diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/page_navigation.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/page_navigation.py new file mode 100644 index 0000000..4d531c0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/page_navigation.py @@ -0,0 +1,82 @@ +""" +Key bindings for extra page navigation: bindings for up/down scrolling through +long pages, like in Emacs or Vi. +""" +from prompt_toolkit.filters import buffer_has_focus, emacs_mode, vi_mode +from prompt_toolkit.key_binding.key_bindings import ( + ConditionalKeyBindings, + KeyBindings, + KeyBindingsBase, + merge_key_bindings, +) + +from .scroll import ( + scroll_backward, + scroll_forward, + scroll_half_page_down, + scroll_half_page_up, + scroll_one_line_down, + scroll_one_line_up, + scroll_page_down, + scroll_page_up, +) + +__all__ = [ + "load_page_navigation_bindings", + "load_emacs_page_navigation_bindings", + "load_vi_page_navigation_bindings", +] + + +def load_page_navigation_bindings() -> KeyBindingsBase: + """ + Load both the Vi and Emacs bindings for page navigation. + """ + # Only enable when a `Buffer` is focused, otherwise, we would catch keys + # when another widget is focused (like for instance `c-d` in a + # ptterm.Terminal). + return ConditionalKeyBindings( + merge_key_bindings( + [ + load_emacs_page_navigation_bindings(), + load_vi_page_navigation_bindings(), + ] + ), + buffer_has_focus, + ) + + +def load_emacs_page_navigation_bindings() -> KeyBindingsBase: + """ + Key bindings, for scrolling up and down through pages. + This are separate bindings, because GNU readline doesn't have them. + """ + key_bindings = KeyBindings() + handle = key_bindings.add + + handle("c-v")(scroll_page_down) + handle("pagedown")(scroll_page_down) + handle("escape", "v")(scroll_page_up) + handle("pageup")(scroll_page_up) + + return ConditionalKeyBindings(key_bindings, emacs_mode) + + +def load_vi_page_navigation_bindings() -> KeyBindingsBase: + """ + Key bindings, for scrolling up and down through pages. + This are separate bindings, because GNU readline doesn't have them. + """ + key_bindings = KeyBindings() + handle = key_bindings.add + + handle("c-f")(scroll_forward) + handle("c-b")(scroll_backward) + handle("c-d")(scroll_half_page_down) + handle("c-u")(scroll_half_page_up) + handle("c-e")(scroll_one_line_down) + handle("c-y")(scroll_one_line_up) + handle("pagedown")(scroll_page_down) + handle("pageup")(scroll_page_up) + + return ConditionalKeyBindings(key_bindings, vi_mode) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/scroll.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/scroll.py new file mode 100644 index 0000000..4a43ff5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/scroll.py @@ -0,0 +1,187 @@ +""" +Key bindings, for scrolling up and down through pages. + +This are separate bindings, because GNU readline doesn't have them, but +they are very useful for navigating through long multiline buffers, like in +Vi, Emacs, etc... +""" +from prompt_toolkit.key_binding.key_processor import KeyPressEvent + +__all__ = [ + "scroll_forward", + "scroll_backward", + "scroll_half_page_up", + "scroll_half_page_down", + "scroll_one_line_up", + "scroll_one_line_down", +] + +E = KeyPressEvent + + +def scroll_forward(event: E, half: bool = False) -> None: + """ + Scroll window down. + """ + w = event.app.layout.current_window + b = event.app.current_buffer + + if w and w.render_info: + info = w.render_info + ui_content = info.ui_content + + # Height to scroll. + scroll_height = info.window_height + if half: + scroll_height //= 2 + + # Calculate how many lines is equivalent to that vertical space. + y = b.document.cursor_position_row + 1 + height = 0 + while y < ui_content.line_count: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y += 1 + else: + break + + b.cursor_position = b.document.translate_row_col_to_index(y, 0) + + +def scroll_backward(event: E, half: bool = False) -> None: + """ + Scroll window up. + """ + w = event.app.layout.current_window + b = event.app.current_buffer + + if w and w.render_info: + info = w.render_info + + # Height to scroll. + scroll_height = info.window_height + if half: + scroll_height //= 2 + + # Calculate how many lines is equivalent to that vertical space. + y = max(0, b.document.cursor_position_row - 1) + height = 0 + while y > 0: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y -= 1 + else: + break + + b.cursor_position = b.document.translate_row_col_to_index(y, 0) + + +def scroll_half_page_down(event: E) -> None: + """ + Same as ControlF, but only scroll half a page. + """ + scroll_forward(event, half=True) + + +def scroll_half_page_up(event: E) -> None: + """ + Same as ControlB, but only scroll half a page. + """ + scroll_backward(event, half=True) + + +def scroll_one_line_down(event: E) -> None: + """ + scroll_offset += 1 + """ + w = event.app.layout.current_window + b = event.app.current_buffer + + if w: + # When the cursor is at the top, move to the next line. (Otherwise, only scroll.) + if w.render_info: + info = w.render_info + + if w.vertical_scroll < info.content_height - info.window_height: + if info.cursor_position.y <= info.configured_scroll_offsets.top: + b.cursor_position += b.document.get_cursor_down_position() + + w.vertical_scroll += 1 + + +def scroll_one_line_up(event: E) -> None: + """ + scroll_offset -= 1 + """ + w = event.app.layout.current_window + b = event.app.current_buffer + + if w: + # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.) + if w.render_info: + info = w.render_info + + if w.vertical_scroll > 0: + first_line_height = info.get_height_for_line(info.first_visible_line()) + + cursor_up = info.cursor_position.y - ( + info.window_height + - 1 + - first_line_height + - info.configured_scroll_offsets.bottom + ) + + # Move cursor up, as many steps as the height of the first line. + # TODO: not entirely correct yet, in case of line wrapping and many long lines. + for _ in range(max(0, cursor_up)): + b.cursor_position += b.document.get_cursor_up_position() + + # Scroll window + w.vertical_scroll -= 1 + + +def scroll_page_down(event: E) -> None: + """ + Scroll page down. (Prefer the cursor at the top of the page, after scrolling.) + """ + w = event.app.layout.current_window + b = event.app.current_buffer + + if w and w.render_info: + # Scroll down one page. + line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1) + w.vertical_scroll = line_index + + b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) + b.cursor_position += b.document.get_start_of_line_position( + after_whitespace=True + ) + + +def scroll_page_up(event: E) -> None: + """ + Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.) + """ + w = event.app.layout.current_window + b = event.app.current_buffer + + if w and w.render_info: + # Put cursor at the first visible line. (But make sure that the cursor + # moves at least one line up.) + line_index = max( + 0, + min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1), + ) + + b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) + b.cursor_position += b.document.get_start_of_line_position( + after_whitespace=True + ) + + # Set the scroll offset. We can safely set it to zero; the Window will + # make sure that it scrolls at least until the cursor becomes visible. + w.vertical_scroll = 0 diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/search.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/search.py new file mode 100644 index 0000000..06a047e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/search.py @@ -0,0 +1,93 @@ +""" +Search related key bindings. +""" +from prompt_toolkit import search +from prompt_toolkit.application.current import get_app +from prompt_toolkit.filters import Condition, control_is_searchable, is_searching +from prompt_toolkit.key_binding.key_processor import KeyPressEvent + +from ..key_bindings import key_binding + +__all__ = [ + "abort_search", + "accept_search", + "start_reverse_incremental_search", + "start_forward_incremental_search", + "reverse_incremental_search", + "forward_incremental_search", + "accept_search_and_accept_input", +] + +E = KeyPressEvent + + +@key_binding(filter=is_searching) +def abort_search(event: E) -> None: + """ + Abort an incremental search and restore the original + line. + (Usually bound to ControlG/ControlC.) + """ + search.stop_search() + + +@key_binding(filter=is_searching) +def accept_search(event: E) -> None: + """ + When enter pressed in isearch, quit isearch mode. (Multiline + isearch would be too complicated.) + (Usually bound to Enter.) + """ + search.accept_search() + + +@key_binding(filter=control_is_searchable) +def start_reverse_incremental_search(event: E) -> None: + """ + Enter reverse incremental search. + (Usually ControlR.) + """ + search.start_search(direction=search.SearchDirection.BACKWARD) + + +@key_binding(filter=control_is_searchable) +def start_forward_incremental_search(event: E) -> None: + """ + Enter forward incremental search. + (Usually ControlS.) + """ + search.start_search(direction=search.SearchDirection.FORWARD) + + +@key_binding(filter=is_searching) +def reverse_incremental_search(event: E) -> None: + """ + Apply reverse incremental search, but keep search buffer focused. + """ + search.do_incremental_search(search.SearchDirection.BACKWARD, count=event.arg) + + +@key_binding(filter=is_searching) +def forward_incremental_search(event: E) -> None: + """ + Apply forward incremental search, but keep search buffer focused. + """ + search.do_incremental_search(search.SearchDirection.FORWARD, count=event.arg) + + +@Condition +def _previous_buffer_is_returnable() -> bool: + """ + True if the previously focused buffer has a return handler. + """ + prev_control = get_app().layout.search_target_buffer_control + return bool(prev_control and prev_control.buffer.is_returnable) + + +@key_binding(filter=is_searching & _previous_buffer_is_returnable) +def accept_search_and_accept_input(event: E) -> None: + """ + Accept the search operation first, then accept the input. + """ + search.accept_search() + event.current_buffer.validate_and_handle() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/vi.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/vi.py new file mode 100644 index 0000000..efbb107 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/vi.py @@ -0,0 +1,2221 @@ +# pylint: disable=function-redefined +import codecs +import string +from enum import Enum +from itertools import accumulate +from typing import Callable, Iterable, List, Optional, Tuple, TypeVar, Union + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import Buffer, indent, reshape_text, unindent +from prompt_toolkit.clipboard import ClipboardData +from prompt_toolkit.document import Document +from prompt_toolkit.filters import ( + Always, + Condition, + Filter, + has_arg, + is_read_only, + is_searching, +) +from prompt_toolkit.filters.app import ( + in_paste_mode, + is_multiline, + vi_digraph_mode, + vi_insert_mode, + vi_insert_multiple_mode, + vi_mode, + vi_navigation_mode, + vi_recording_macro, + vi_replace_mode, + vi_replace_single_mode, + vi_search_direction_reversed, + vi_selection_mode, + vi_waiting_for_text_object_mode, +) +from prompt_toolkit.input.vt100_parser import Vt100Parser +from prompt_toolkit.key_binding.digraphs import DIGRAPHS +from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent +from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode +from prompt_toolkit.keys import Keys +from prompt_toolkit.search import SearchDirection +from prompt_toolkit.selection import PasteMode, SelectionState, SelectionType + +from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase +from .named_commands import get_by_name + +__all__ = [ + "load_vi_bindings", + "load_vi_search_bindings", +] + +E = KeyPressEvent + +ascii_lowercase = string.ascii_lowercase + +vi_register_names = ascii_lowercase + "0123456789" + + +class TextObjectType(Enum): + EXCLUSIVE = "EXCLUSIVE" + INCLUSIVE = "INCLUSIVE" + LINEWISE = "LINEWISE" + BLOCK = "BLOCK" + + +class TextObject: + """ + Return struct for functions wrapped in ``text_object``. + Both `start` and `end` are relative to the current cursor position. + """ + + def __init__( + self, start: int, end: int = 0, type: TextObjectType = TextObjectType.EXCLUSIVE + ): + + self.start = start + self.end = end + self.type = type + + @property + def selection_type(self) -> SelectionType: + if self.type == TextObjectType.LINEWISE: + return SelectionType.LINES + if self.type == TextObjectType.BLOCK: + return SelectionType.BLOCK + else: + return SelectionType.CHARACTERS + + def sorted(self) -> Tuple[int, int]: + """ + Return a (start, end) tuple where start <= end. + """ + if self.start < self.end: + return self.start, self.end + else: + return self.end, self.start + + def operator_range(self, document: Document) -> Tuple[int, int]: + """ + Return a (start, end) tuple with start <= end that indicates the range + operators should operate on. + `buffer` is used to get start and end of line positions. + + This should return something that can be used in a slice, so the `end` + position is *not* included. + """ + start, end = self.sorted() + doc = document + + if ( + self.type == TextObjectType.EXCLUSIVE + and doc.translate_index_to_position(end + doc.cursor_position)[1] == 0 + ): + # If the motion is exclusive and the end of motion is on the first + # column, the end position becomes end of previous line. + end -= 1 + if self.type == TextObjectType.INCLUSIVE: + end += 1 + if self.type == TextObjectType.LINEWISE: + # Select whole lines + row, col = doc.translate_index_to_position(start + doc.cursor_position) + start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position + row, col = doc.translate_index_to_position(end + doc.cursor_position) + end = ( + doc.translate_row_col_to_index(row, len(doc.lines[row])) + - doc.cursor_position + ) + return start, end + + def get_line_numbers(self, buffer: Buffer) -> Tuple[int, int]: + """ + Return a (start_line, end_line) pair. + """ + # Get absolute cursor positions from the text object. + from_, to = self.operator_range(buffer.document) + from_ += buffer.cursor_position + to += buffer.cursor_position + + # Take the start of the lines. + from_, _ = buffer.document.translate_index_to_position(from_) + to, _ = buffer.document.translate_index_to_position(to) + + return from_, to + + def cut(self, buffer: Buffer) -> Tuple[Document, ClipboardData]: + """ + Turn text object into `ClipboardData` instance. + """ + from_, to = self.operator_range(buffer.document) + + from_ += buffer.cursor_position + to += buffer.cursor_position + + # For Vi mode, the SelectionState does include the upper position, + # while `self.operator_range` does not. So, go one to the left, unless + # we're in the line mode, then we don't want to risk going to the + # previous line, and missing one line in the selection. + if self.type != TextObjectType.LINEWISE: + to -= 1 + + document = Document( + buffer.text, + to, + SelectionState(original_cursor_position=from_, type=self.selection_type), + ) + + new_document, clipboard_data = document.cut_selection() + return new_document, clipboard_data + + +# Typevar for any text object function: +TextObjectFunction = Callable[[E], TextObject] +_TOF = TypeVar("_TOF", bound=TextObjectFunction) + + +def create_text_object_decorator( + key_bindings: KeyBindings, +) -> Callable[..., Callable[[_TOF], _TOF]]: + """ + Create a decorator that can be used to register Vi text object implementations. + """ + + def text_object_decorator( + *keys: Union[Keys, str], + filter: Filter = Always(), + no_move_handler: bool = False, + no_selection_handler: bool = False, + eager: bool = False, + ) -> Callable[[_TOF], _TOF]: + """ + Register a text object function. + + Usage:: + + @text_object('w', filter=..., no_move_handler=False) + def handler(event): + # Return a text object for this key. + return TextObject(...) + + :param no_move_handler: Disable the move handler in navigation mode. + (It's still active in selection mode.) + """ + + def decorator(text_object_func: _TOF) -> _TOF: + @key_bindings.add( + *keys, filter=vi_waiting_for_text_object_mode & filter, eager=eager + ) + def _apply_operator_to_text_object(event: E) -> None: + # Arguments are multiplied. + vi_state = event.app.vi_state + event._arg = str((vi_state.operator_arg or 1) * (event.arg or 1)) + + # Call the text object handler. + text_obj = text_object_func(event) + + # Get the operator function. + # (Should never be None here, given the + # `vi_waiting_for_text_object_mode` filter state.) + operator_func = vi_state.operator_func + + if text_obj is not None and operator_func is not None: + # Call the operator function with the text object. + operator_func(event, text_obj) + + # Clear operator. + event.app.vi_state.operator_func = None + event.app.vi_state.operator_arg = None + + # Register a move operation. (Doesn't need an operator.) + if not no_move_handler: + + @key_bindings.add( + *keys, + filter=~vi_waiting_for_text_object_mode + & filter + & vi_navigation_mode, + eager=eager, + ) + def _move_in_navigation_mode(event: E) -> None: + """ + Move handler for navigation mode. + """ + text_object = text_object_func(event) + event.current_buffer.cursor_position += text_object.start + + # Register a move selection operation. + if not no_selection_handler: + + @key_bindings.add( + *keys, + filter=~vi_waiting_for_text_object_mode + & filter + & vi_selection_mode, + eager=eager, + ) + def _move_in_selection_mode(event: E) -> None: + """ + Move handler for selection mode. + """ + text_object = text_object_func(event) + buff = event.current_buffer + selection_state = buff.selection_state + + if selection_state is None: + return # Should not happen, because of the `vi_selection_mode` filter. + + # When the text object has both a start and end position, like 'i(' or 'iw', + # Turn this into a selection, otherwise the cursor. + if text_object.end: + # Take selection positions from text object. + start, end = text_object.operator_range(buff.document) + start += buff.cursor_position + end += buff.cursor_position + + selection_state.original_cursor_position = start + buff.cursor_position = end + + # Take selection type from text object. + if text_object.type == TextObjectType.LINEWISE: + selection_state.type = SelectionType.LINES + else: + selection_state.type = SelectionType.CHARACTERS + else: + event.current_buffer.cursor_position += text_object.start + + # Make it possible to chain @text_object decorators. + return text_object_func + + return decorator + + return text_object_decorator + + +# Typevar for any operator function: +OperatorFunction = Callable[[E, TextObject], None] +_OF = TypeVar("_OF", bound=OperatorFunction) + + +def create_operator_decorator( + key_bindings: KeyBindings, +) -> Callable[..., Callable[[_OF], _OF]]: + """ + Create a decorator that can be used for registering Vi operators. + """ + + def operator_decorator( + *keys: Union[Keys, str], filter: Filter = Always(), eager: bool = False + ) -> Callable[[_OF], _OF]: + """ + Register a Vi operator. + + Usage:: + + @operator('d', filter=...) + def handler(event, text_object): + # Do something with the text object here. + """ + + def decorator(operator_func: _OF) -> _OF: + @key_bindings.add( + *keys, + filter=~vi_waiting_for_text_object_mode & filter & vi_navigation_mode, + eager=eager, + ) + def _operator_in_navigation(event: E) -> None: + """ + Handle operator in navigation mode. + """ + # When this key binding is matched, only set the operator + # function in the ViState. We should execute it after a text + # object has been received. + event.app.vi_state.operator_func = operator_func + event.app.vi_state.operator_arg = event.arg + + @key_bindings.add( + *keys, + filter=~vi_waiting_for_text_object_mode & filter & vi_selection_mode, + eager=eager, + ) + def _operator_in_selection(event: E) -> None: + """ + Handle operator in selection mode. + """ + buff = event.current_buffer + selection_state = buff.selection_state + + if selection_state is not None: + # Create text object from selection. + if selection_state.type == SelectionType.LINES: + text_obj_type = TextObjectType.LINEWISE + elif selection_state.type == SelectionType.BLOCK: + text_obj_type = TextObjectType.BLOCK + else: + text_obj_type = TextObjectType.INCLUSIVE + + text_object = TextObject( + selection_state.original_cursor_position - buff.cursor_position, + type=text_obj_type, + ) + + # Execute operator. + operator_func(event, text_object) + + # Quit selection mode. + buff.selection_state = None + + return operator_func + + return decorator + + return operator_decorator + + +def load_vi_bindings() -> KeyBindingsBase: + """ + Vi extensions. + + # Overview of Readline Vi commands: + # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf + """ + # Note: Some key bindings have the "~IsReadOnly()" filter added. This + # prevents the handler to be executed when the focus is on a + # read-only buffer. + # This is however only required for those that change the ViState to + # INSERT mode. The `Buffer` class itself throws the + # `EditReadOnlyBuffer` exception for any text operations which is + # handled correctly. There is no need to add "~IsReadOnly" to all key + # bindings that do text manipulation. + + key_bindings = KeyBindings() + handle = key_bindings.add + + # (Note: Always take the navigation bindings in read-only mode, even when + # ViState says different.) + + TransformFunction = Tuple[Tuple[str, ...], Filter, Callable[[str], str]] + + vi_transform_functions: List[TransformFunction] = [ + # Rot 13 transformation + ( + ("g", "?"), + Always(), + lambda string: codecs.encode(string, "rot_13"), + ), + # To lowercase + (("g", "u"), Always(), lambda string: string.lower()), + # To uppercase. + (("g", "U"), Always(), lambda string: string.upper()), + # Swap case. + (("g", "~"), Always(), lambda string: string.swapcase()), + ( + ("~",), + Condition(lambda: get_app().vi_state.tilde_operator), + lambda string: string.swapcase(), + ), + ] + + # Insert a character literally (quoted insert). + handle("c-v", filter=vi_insert_mode)(get_by_name("quoted-insert")) + + @handle("escape") + def _back_to_navigation(event: E) -> None: + """ + Escape goes to vi navigation mode. + """ + buffer = event.current_buffer + vi_state = event.app.vi_state + + if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): + buffer.cursor_position += buffer.document.get_cursor_left_position() + + vi_state.input_mode = InputMode.NAVIGATION + + if bool(buffer.selection_state): + buffer.exit_selection() + + @handle("k", filter=vi_selection_mode) + def _up_in_selection(event: E) -> None: + """ + Arrow up in selection mode. + """ + event.current_buffer.cursor_up(count=event.arg) + + @handle("j", filter=vi_selection_mode) + def _down_in_selection(event: E) -> None: + """ + Arrow down in selection mode. + """ + event.current_buffer.cursor_down(count=event.arg) + + @handle("up", filter=vi_navigation_mode) + @handle("c-p", filter=vi_navigation_mode) + def _up_in_navigation(event: E) -> None: + """ + Arrow up and ControlP in navigation mode go up. + """ + event.current_buffer.auto_up(count=event.arg) + + @handle("k", filter=vi_navigation_mode) + def _go_up(event: E) -> None: + """ + Go up, but if we enter a new history entry, move to the start of the + line. + """ + event.current_buffer.auto_up( + count=event.arg, go_to_start_of_line_if_history_changes=True + ) + + @handle("down", filter=vi_navigation_mode) + @handle("c-n", filter=vi_navigation_mode) + def _go_down(event: E) -> None: + """ + Arrow down and Control-N in navigation mode. + """ + event.current_buffer.auto_down(count=event.arg) + + @handle("j", filter=vi_navigation_mode) + def _go_down2(event: E) -> None: + """ + Go down, but if we enter a new history entry, go to the start of the line. + """ + event.current_buffer.auto_down( + count=event.arg, go_to_start_of_line_if_history_changes=True + ) + + @handle("backspace", filter=vi_navigation_mode) + def _go_left(event: E) -> None: + """ + In navigation-mode, move cursor. + """ + event.current_buffer.cursor_position += ( + event.current_buffer.document.get_cursor_left_position(count=event.arg) + ) + + @handle("c-n", filter=vi_insert_mode) + def _complete_next(event: E) -> None: + b = event.current_buffer + + if b.complete_state: + b.complete_next() + else: + b.start_completion(select_first=True) + + @handle("c-p", filter=vi_insert_mode) + def _complete_prev(event: E) -> None: + """ + Control-P: To previous completion. + """ + b = event.current_buffer + + if b.complete_state: + b.complete_previous() + else: + b.start_completion(select_last=True) + + @handle("c-g", filter=vi_insert_mode) + @handle("c-y", filter=vi_insert_mode) + def _accept_completion(event: E) -> None: + """ + Accept current completion. + """ + event.current_buffer.complete_state = None + + @handle("c-e", filter=vi_insert_mode) + def _cancel_completion(event: E) -> None: + """ + Cancel completion. Go back to originally typed text. + """ + event.current_buffer.cancel_completion() + + @Condition + def is_returnable() -> bool: + return get_app().current_buffer.is_returnable + + # In navigation mode, pressing enter will always return the input. + handle("enter", filter=vi_navigation_mode & is_returnable)( + get_by_name("accept-line") + ) + + # In insert mode, also accept input when enter is pressed, and the buffer + # has been marked as single line. + handle("enter", filter=is_returnable & ~is_multiline)(get_by_name("accept-line")) + + @handle("enter", filter=~is_returnable & vi_navigation_mode) + def _start_of_next_line(event: E) -> None: + """ + Go to the beginning of next line. + """ + b = event.current_buffer + b.cursor_down(count=event.arg) + b.cursor_position += b.document.get_start_of_line_position( + after_whitespace=True + ) + + # ** In navigation mode ** + + # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html + + @handle("insert", filter=vi_navigation_mode) + def _insert_mode(event: E) -> None: + """ + Pressing the Insert key. + """ + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("insert", filter=vi_insert_mode) + def _navigation_mode(event: E) -> None: + """ + Pressing the Insert key. + """ + event.app.vi_state.input_mode = InputMode.NAVIGATION + + @handle("a", filter=vi_navigation_mode & ~is_read_only) + # ~IsReadOnly, because we want to stay in navigation mode for + # read-only buffers. + def _a(event: E) -> None: + event.current_buffer.cursor_position += ( + event.current_buffer.document.get_cursor_right_position() + ) + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("A", filter=vi_navigation_mode & ~is_read_only) + def _A(event: E) -> None: + event.current_buffer.cursor_position += ( + event.current_buffer.document.get_end_of_line_position() + ) + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("C", filter=vi_navigation_mode & ~is_read_only) + def _change_until_end_of_line(event: E) -> None: + """ + Change to end of line. + Same as 'c$' (which is implemented elsewhere.) + """ + buffer = event.current_buffer + + deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) + event.app.clipboard.set_text(deleted) + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("c", "c", filter=vi_navigation_mode & ~is_read_only) + @handle("S", filter=vi_navigation_mode & ~is_read_only) + def _change_current_line(event: E) -> None: # TODO: implement 'arg' + """ + Change current line + """ + buffer = event.current_buffer + + # We copy the whole line. + data = ClipboardData(buffer.document.current_line, SelectionType.LINES) + event.app.clipboard.set_data(data) + + # But we delete after the whitespace + buffer.cursor_position += buffer.document.get_start_of_line_position( + after_whitespace=True + ) + buffer.delete(count=buffer.document.get_end_of_line_position()) + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("D", filter=vi_navigation_mode) + def _delete_until_end_of_line(event: E) -> None: + """ + Delete from cursor position until the end of the line. + """ + buffer = event.current_buffer + deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) + event.app.clipboard.set_text(deleted) + + @handle("d", "d", filter=vi_navigation_mode) + def _delete_line(event: E) -> None: + """ + Delete line. (Or the following 'n' lines.) + """ + buffer = event.current_buffer + + # Split string in before/deleted/after text. + lines = buffer.document.lines + + before = "\n".join(lines[: buffer.document.cursor_position_row]) + deleted = "\n".join( + lines[ + buffer.document.cursor_position_row : buffer.document.cursor_position_row + + event.arg + ] + ) + after = "\n".join(lines[buffer.document.cursor_position_row + event.arg :]) + + # Set new text. + if before and after: + before = before + "\n" + + # Set text and cursor position. + buffer.document = Document( + text=before + after, + # Cursor At the start of the first 'after' line, after the leading whitespace. + cursor_position=len(before) + len(after) - len(after.lstrip(" ")), + ) + + # Set clipboard data + event.app.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES)) + + @handle("x", filter=vi_selection_mode) + def _cut(event: E) -> None: + """ + Cut selection. + ('x' is not an operator.) + """ + clipboard_data = event.current_buffer.cut_selection() + event.app.clipboard.set_data(clipboard_data) + + @handle("i", filter=vi_navigation_mode & ~is_read_only) + def _i(event: E) -> None: + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("I", filter=vi_navigation_mode & ~is_read_only) + def _I(event: E) -> None: + event.app.vi_state.input_mode = InputMode.INSERT + event.current_buffer.cursor_position += ( + event.current_buffer.document.get_start_of_line_position( + after_whitespace=True + ) + ) + + @Condition + def in_block_selection() -> bool: + buff = get_app().current_buffer + return bool( + buff.selection_state and buff.selection_state.type == SelectionType.BLOCK + ) + + @handle("I", filter=in_block_selection & ~is_read_only) + def insert_in_block_selection(event: E, after: bool = False) -> None: + """ + Insert in block selection mode. + """ + buff = event.current_buffer + + # Store all cursor positions. + positions = [] + + if after: + + def get_pos(from_to: Tuple[int, int]) -> int: + return from_to[1] + + else: + + def get_pos(from_to: Tuple[int, int]) -> int: + return from_to[0] + + for i, from_to in enumerate(buff.document.selection_ranges()): + positions.append(get_pos(from_to)) + if i == 0: + buff.cursor_position = get_pos(from_to) + + buff.multiple_cursor_positions = positions + + # Go to 'INSERT_MULTIPLE' mode. + event.app.vi_state.input_mode = InputMode.INSERT_MULTIPLE + buff.exit_selection() + + @handle("A", filter=in_block_selection & ~is_read_only) + def _append_after_block(event: E) -> None: + insert_in_block_selection(event, after=True) + + @handle("J", filter=vi_navigation_mode & ~is_read_only) + def _join(event: E) -> None: + """ + Join lines. + """ + for i in range(event.arg): + event.current_buffer.join_next_line() + + @handle("g", "J", filter=vi_navigation_mode & ~is_read_only) + def _join_nospace(event: E) -> None: + """ + Join lines without space. + """ + for i in range(event.arg): + event.current_buffer.join_next_line(separator="") + + @handle("J", filter=vi_selection_mode & ~is_read_only) + def _join_selection(event: E) -> None: + """ + Join selected lines. + """ + event.current_buffer.join_selected_lines() + + @handle("g", "J", filter=vi_selection_mode & ~is_read_only) + def _join_selection_nospace(event: E) -> None: + """ + Join selected lines without space. + """ + event.current_buffer.join_selected_lines(separator="") + + @handle("p", filter=vi_navigation_mode) + def _paste(event: E) -> None: + """ + Paste after + """ + event.current_buffer.paste_clipboard_data( + event.app.clipboard.get_data(), + count=event.arg, + paste_mode=PasteMode.VI_AFTER, + ) + + @handle("P", filter=vi_navigation_mode) + def _paste_before(event: E) -> None: + """ + Paste before + """ + event.current_buffer.paste_clipboard_data( + event.app.clipboard.get_data(), + count=event.arg, + paste_mode=PasteMode.VI_BEFORE, + ) + + @handle('"', Keys.Any, "p", filter=vi_navigation_mode) + def _paste_register(event: E) -> None: + """ + Paste from named register. + """ + c = event.key_sequence[1].data + if c in vi_register_names: + data = event.app.vi_state.named_registers.get(c) + if data: + event.current_buffer.paste_clipboard_data( + data, count=event.arg, paste_mode=PasteMode.VI_AFTER + ) + + @handle('"', Keys.Any, "P", filter=vi_navigation_mode) + def _paste_register_before(event: E) -> None: + """ + Paste (before) from named register. + """ + c = event.key_sequence[1].data + if c in vi_register_names: + data = event.app.vi_state.named_registers.get(c) + if data: + event.current_buffer.paste_clipboard_data( + data, count=event.arg, paste_mode=PasteMode.VI_BEFORE + ) + + @handle("r", filter=vi_navigation_mode) + def _replace(event: E) -> None: + """ + Go to 'replace-single'-mode. + """ + event.app.vi_state.input_mode = InputMode.REPLACE_SINGLE + + @handle("R", filter=vi_navigation_mode) + def _replace_mode(event: E) -> None: + """ + Go to 'replace'-mode. + """ + event.app.vi_state.input_mode = InputMode.REPLACE + + @handle("s", filter=vi_navigation_mode & ~is_read_only) + def _substitute(event: E) -> None: + """ + Substitute with new text + (Delete character(s) and go to insert mode.) + """ + text = event.current_buffer.delete(count=event.arg) + event.app.clipboard.set_text(text) + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("u", filter=vi_navigation_mode, save_before=(lambda e: False)) + def _undo(event: E) -> None: + for i in range(event.arg): + event.current_buffer.undo() + + @handle("V", filter=vi_navigation_mode) + def _visual_line(event: E) -> None: + """ + Start lines selection. + """ + event.current_buffer.start_selection(selection_type=SelectionType.LINES) + + @handle("c-v", filter=vi_navigation_mode) + def _visual_block(event: E) -> None: + """ + Enter block selection mode. + """ + event.current_buffer.start_selection(selection_type=SelectionType.BLOCK) + + @handle("V", filter=vi_selection_mode) + def _visual_line2(event: E) -> None: + """ + Exit line selection mode, or go from non line selection mode to line + selection mode. + """ + selection_state = event.current_buffer.selection_state + + if selection_state is not None: + if selection_state.type != SelectionType.LINES: + selection_state.type = SelectionType.LINES + else: + event.current_buffer.exit_selection() + + @handle("v", filter=vi_navigation_mode) + def _visual(event: E) -> None: + """ + Enter character selection mode. + """ + event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) + + @handle("v", filter=vi_selection_mode) + def _visual2(event: E) -> None: + """ + Exit character selection mode, or go from non-character-selection mode + to character selection mode. + """ + selection_state = event.current_buffer.selection_state + + if selection_state is not None: + if selection_state.type != SelectionType.CHARACTERS: + selection_state.type = SelectionType.CHARACTERS + else: + event.current_buffer.exit_selection() + + @handle("c-v", filter=vi_selection_mode) + def _visual_block2(event: E) -> None: + """ + Exit block selection mode, or go from non block selection mode to block + selection mode. + """ + selection_state = event.current_buffer.selection_state + + if selection_state is not None: + if selection_state.type != SelectionType.BLOCK: + selection_state.type = SelectionType.BLOCK + else: + event.current_buffer.exit_selection() + + @handle("a", "w", filter=vi_selection_mode) + @handle("a", "W", filter=vi_selection_mode) + def _visual_auto_word(event: E) -> None: + """ + Switch from visual linewise mode to visual characterwise mode. + """ + buffer = event.current_buffer + + if ( + buffer.selection_state + and buffer.selection_state.type == SelectionType.LINES + ): + buffer.selection_state.type = SelectionType.CHARACTERS + + @handle("x", filter=vi_navigation_mode) + def _delete(event: E) -> None: + """ + Delete character. + """ + buff = event.current_buffer + count = min(event.arg, len(buff.document.current_line_after_cursor)) + if count: + text = event.current_buffer.delete(count=count) + event.app.clipboard.set_text(text) + + @handle("X", filter=vi_navigation_mode) + def _delete_before_cursor(event: E) -> None: + buff = event.current_buffer + count = min(event.arg, len(buff.document.current_line_before_cursor)) + if count: + text = event.current_buffer.delete_before_cursor(count=count) + event.app.clipboard.set_text(text) + + @handle("y", "y", filter=vi_navigation_mode) + @handle("Y", filter=vi_navigation_mode) + def _yank_line(event: E) -> None: + """ + Yank the whole line. + """ + text = "\n".join(event.current_buffer.document.lines_from_current[: event.arg]) + event.app.clipboard.set_data(ClipboardData(text, SelectionType.LINES)) + + @handle("+", filter=vi_navigation_mode) + def _next_line(event: E) -> None: + """ + Move to first non whitespace of next line + """ + buffer = event.current_buffer + buffer.cursor_position += buffer.document.get_cursor_down_position( + count=event.arg + ) + buffer.cursor_position += buffer.document.get_start_of_line_position( + after_whitespace=True + ) + + @handle("-", filter=vi_navigation_mode) + def _prev_line(event: E) -> None: + """ + Move to first non whitespace of previous line + """ + buffer = event.current_buffer + buffer.cursor_position += buffer.document.get_cursor_up_position( + count=event.arg + ) + buffer.cursor_position += buffer.document.get_start_of_line_position( + after_whitespace=True + ) + + @handle(">", ">", filter=vi_navigation_mode) + def _indent(event: E) -> None: + """ + Indent lines. + """ + buffer = event.current_buffer + current_row = buffer.document.cursor_position_row + indent(buffer, current_row, current_row + event.arg) + + @handle("<", "<", filter=vi_navigation_mode) + def _unindent(event: E) -> None: + """ + Unindent lines. + """ + current_row = event.current_buffer.document.cursor_position_row + unindent(event.current_buffer, current_row, current_row + event.arg) + + @handle("O", filter=vi_navigation_mode & ~is_read_only) + def _open_above(event: E) -> None: + """ + Open line above and enter insertion mode + """ + event.current_buffer.insert_line_above(copy_margin=not in_paste_mode()) + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("o", filter=vi_navigation_mode & ~is_read_only) + def _open_below(event: E) -> None: + """ + Open line below and enter insertion mode + """ + event.current_buffer.insert_line_below(copy_margin=not in_paste_mode()) + event.app.vi_state.input_mode = InputMode.INSERT + + @handle("~", filter=vi_navigation_mode) + def _reverse_case(event: E) -> None: + """ + Reverse case of current character and move cursor forward. + """ + buffer = event.current_buffer + c = buffer.document.current_char + + if c is not None and c != "\n": + buffer.insert_text(c.swapcase(), overwrite=True) + + @handle("g", "u", "u", filter=vi_navigation_mode & ~is_read_only) + def _lowercase_line(event: E) -> None: + """ + Lowercase current line. + """ + buff = event.current_buffer + buff.transform_current_line(lambda s: s.lower()) + + @handle("g", "U", "U", filter=vi_navigation_mode & ~is_read_only) + def _uppercase_line(event: E) -> None: + """ + Uppercase current line. + """ + buff = event.current_buffer + buff.transform_current_line(lambda s: s.upper()) + + @handle("g", "~", "~", filter=vi_navigation_mode & ~is_read_only) + def _swapcase_line(event: E) -> None: + """ + Swap case of the current line. + """ + buff = event.current_buffer + buff.transform_current_line(lambda s: s.swapcase()) + + @handle("#", filter=vi_navigation_mode) + def _prev_occurence(event: E) -> None: + """ + Go to previous occurrence of this word. + """ + b = event.current_buffer + search_state = event.app.current_search_state + + search_state.text = b.document.get_word_under_cursor() + search_state.direction = SearchDirection.BACKWARD + + b.apply_search(search_state, count=event.arg, include_current_position=False) + + @handle("*", filter=vi_navigation_mode) + def _next_occurance(event: E) -> None: + """ + Go to next occurrence of this word. + """ + b = event.current_buffer + search_state = event.app.current_search_state + + search_state.text = b.document.get_word_under_cursor() + search_state.direction = SearchDirection.FORWARD + + b.apply_search(search_state, count=event.arg, include_current_position=False) + + @handle("(", filter=vi_navigation_mode) + def _begin_of_sentence(event: E) -> None: + # TODO: go to begin of sentence. + # XXX: should become text_object. + pass + + @handle(")", filter=vi_navigation_mode) + def _end_of_sentence(event: E) -> None: + # TODO: go to end of sentence. + # XXX: should become text_object. + pass + + operator = create_operator_decorator(key_bindings) + text_object = create_text_object_decorator(key_bindings) + + @handle(Keys.Any, filter=vi_waiting_for_text_object_mode) + def _unknown_text_object(event: E) -> None: + """ + Unknown key binding while waiting for a text object. + """ + event.app.output.bell() + + # + # *** Operators *** + # + + def create_delete_and_change_operators( + delete_only: bool, with_register: bool = False + ) -> None: + """ + Delete and change operators. + + :param delete_only: Create an operator that deletes, but doesn't go to insert mode. + :param with_register: Copy the deleted text to this named register instead of the clipboard. + """ + handler_keys: Iterable[str] + if with_register: + handler_keys = ('"', Keys.Any, "cd"[delete_only]) + else: + handler_keys = "cd"[delete_only] + + @operator(*handler_keys, filter=~is_read_only) + def delete_or_change_operator(event: E, text_object: TextObject) -> None: + clipboard_data = None + buff = event.current_buffer + + if text_object: + new_document, clipboard_data = text_object.cut(buff) + buff.document = new_document + + # Set deleted/changed text to clipboard or named register. + if clipboard_data and clipboard_data.text: + if with_register: + reg_name = event.key_sequence[1].data + if reg_name in vi_register_names: + event.app.vi_state.named_registers[reg_name] = clipboard_data + else: + event.app.clipboard.set_data(clipboard_data) + + # Only go back to insert mode in case of 'change'. + if not delete_only: + event.app.vi_state.input_mode = InputMode.INSERT + + create_delete_and_change_operators(False, False) + create_delete_and_change_operators(False, True) + create_delete_and_change_operators(True, False) + create_delete_and_change_operators(True, True) + + def create_transform_handler( + filter: Filter, transform_func: Callable[[str], str], *a: str + ) -> None: + @operator(*a, filter=filter & ~is_read_only) + def _(event: E, text_object: TextObject) -> None: + """ + Apply transformation (uppercase, lowercase, rot13, swap case). + """ + buff = event.current_buffer + start, end = text_object.operator_range(buff.document) + + if start < end: + # Transform. + buff.transform_region( + buff.cursor_position + start, + buff.cursor_position + end, + transform_func, + ) + + # Move cursor + buff.cursor_position += text_object.end or text_object.start + + for k, f, func in vi_transform_functions: + create_transform_handler(f, func, *k) + + @operator("y") + def _yank(event: E, text_object: TextObject) -> None: + """ + Yank operator. (Copy text.) + """ + _, clipboard_data = text_object.cut(event.current_buffer) + if clipboard_data.text: + event.app.clipboard.set_data(clipboard_data) + + @operator('"', Keys.Any, "y") + def _yank_to_register(event: E, text_object: TextObject) -> None: + """ + Yank selection to named register. + """ + c = event.key_sequence[1].data + if c in vi_register_names: + _, clipboard_data = text_object.cut(event.current_buffer) + event.app.vi_state.named_registers[c] = clipboard_data + + @operator(">") + def _indent_text_object(event: E, text_object: TextObject) -> None: + """ + Indent. + """ + buff = event.current_buffer + from_, to = text_object.get_line_numbers(buff) + indent(buff, from_, to + 1, count=event.arg) + + @operator("<") + def _unindent_text_object(event: E, text_object: TextObject) -> None: + """ + Unindent. + """ + buff = event.current_buffer + from_, to = text_object.get_line_numbers(buff) + unindent(buff, from_, to + 1, count=event.arg) + + @operator("g", "q") + def _reshape(event: E, text_object: TextObject) -> None: + """ + Reshape text. + """ + buff = event.current_buffer + from_, to = text_object.get_line_numbers(buff) + reshape_text(buff, from_, to) + + # + # *** Text objects *** + # + + @text_object("b") + def _b(event: E) -> TextObject: + """ + Move one word or token left. + """ + return TextObject( + event.current_buffer.document.find_start_of_previous_word(count=event.arg) + or 0 + ) + + @text_object("B") + def _B(event: E) -> TextObject: + """ + Move one non-blank word left + """ + return TextObject( + event.current_buffer.document.find_start_of_previous_word( + count=event.arg, WORD=True + ) + or 0 + ) + + @text_object("$") + def _dollar(event: E) -> TextObject: + """ + 'c$', 'd$' and '$': Delete/change/move until end of line. + """ + return TextObject(event.current_buffer.document.get_end_of_line_position()) + + @text_object("w") + def _word_forward(event: E) -> TextObject: + """ + 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. + """ + return TextObject( + event.current_buffer.document.find_next_word_beginning(count=event.arg) + or event.current_buffer.document.get_end_of_document_position() + ) + + @text_object("W") + def _WORD_forward(event: E) -> TextObject: + """ + 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. + """ + return TextObject( + event.current_buffer.document.find_next_word_beginning( + count=event.arg, WORD=True + ) + or event.current_buffer.document.get_end_of_document_position() + ) + + @text_object("e") + def _end_of_word(event: E) -> TextObject: + """ + End of 'word': 'ce', 'de', 'e' + """ + end = event.current_buffer.document.find_next_word_ending(count=event.arg) + return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) + + @text_object("E") + def _end_of_WORD(event: E) -> TextObject: + """ + End of 'WORD': 'cE', 'dE', 'E' + """ + end = event.current_buffer.document.find_next_word_ending( + count=event.arg, WORD=True + ) + return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) + + @text_object("i", "w", no_move_handler=True) + def _inner_word(event: E) -> TextObject: + """ + Inner 'word': ciw and diw + """ + start, end = event.current_buffer.document.find_boundaries_of_current_word() + return TextObject(start, end) + + @text_object("a", "w", no_move_handler=True) + def _a_word(event: E) -> TextObject: + """ + A 'word': caw and daw + """ + start, end = event.current_buffer.document.find_boundaries_of_current_word( + include_trailing_whitespace=True + ) + return TextObject(start, end) + + @text_object("i", "W", no_move_handler=True) + def _inner_WORD(event: E) -> TextObject: + """ + Inner 'WORD': ciW and diW + """ + start, end = event.current_buffer.document.find_boundaries_of_current_word( + WORD=True + ) + return TextObject(start, end) + + @text_object("a", "W", no_move_handler=True) + def _a_WORD(event: E) -> TextObject: + """ + A 'WORD': caw and daw + """ + start, end = event.current_buffer.document.find_boundaries_of_current_word( + WORD=True, include_trailing_whitespace=True + ) + return TextObject(start, end) + + @text_object("a", "p", no_move_handler=True) + def _paragraph(event: E) -> TextObject: + """ + Auto paragraph. + """ + start = event.current_buffer.document.start_of_paragraph() + end = event.current_buffer.document.end_of_paragraph(count=event.arg) + return TextObject(start, end) + + @text_object("^") + def _start_of_line(event: E) -> TextObject: + """'c^', 'd^' and '^': Soft start of line, after whitespace.""" + return TextObject( + event.current_buffer.document.get_start_of_line_position( + after_whitespace=True + ) + ) + + @text_object("0") + def _hard_start_of_line(event: E) -> TextObject: + """ + 'c0', 'd0': Hard start of line, before whitespace. + (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) + """ + return TextObject( + event.current_buffer.document.get_start_of_line_position( + after_whitespace=False + ) + ) + + def create_ci_ca_handles( + ci_start: str, ci_end: str, inner: bool, key: Optional[str] = None + ) -> None: + # TODO: 'dat', 'dit', (tags (like xml) + """ + Delete/Change string between this start and stop character. But keep these characters. + This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. + """ + + def handler(event: E) -> TextObject: + if ci_start == ci_end: + # Quotes + start = event.current_buffer.document.find_backwards( + ci_start, in_current_line=False + ) + end = event.current_buffer.document.find(ci_end, in_current_line=False) + else: + # Brackets + start = event.current_buffer.document.find_enclosing_bracket_left( + ci_start, ci_end + ) + end = event.current_buffer.document.find_enclosing_bracket_right( + ci_start, ci_end + ) + + if start is not None and end is not None: + offset = 0 if inner else 1 + return TextObject(start + 1 - offset, end + offset) + else: + # Nothing found. + return TextObject(0) + + if key is None: + text_object("ai"[inner], ci_start, no_move_handler=True)(handler) + text_object("ai"[inner], ci_end, no_move_handler=True)(handler) + else: + text_object("ai"[inner], key, no_move_handler=True)(handler) + + for inner in (False, True): + for ci_start, ci_end in [ + ('"', '"'), + ("'", "'"), + ("`", "`"), + ("[", "]"), + ("<", ">"), + ("{", "}"), + ("(", ")"), + ]: + create_ci_ca_handles(ci_start, ci_end, inner) + + create_ci_ca_handles("(", ")", inner, "b") # 'dab', 'dib' + create_ci_ca_handles("{", "}", inner, "B") # 'daB', 'diB' + + @text_object("{") + def _previous_section(event: E) -> TextObject: + """ + Move to previous blank-line separated section. + Implements '{', 'c{', 'd{', 'y{' + """ + index = event.current_buffer.document.start_of_paragraph( + count=event.arg, before=True + ) + return TextObject(index) + + @text_object("}") + def _next_section(event: E) -> TextObject: + """ + Move to next blank-line separated section. + Implements '}', 'c}', 'd}', 'y}' + """ + index = event.current_buffer.document.end_of_paragraph( + count=event.arg, after=True + ) + return TextObject(index) + + @text_object("f", Keys.Any) + def _next_occurence(event: E) -> TextObject: + """ + Go to next occurrence of character. Typing 'fx' will move the + cursor to the next occurrence of character. 'x'. + """ + event.app.vi_state.last_character_find = CharacterFind(event.data, False) + match = event.current_buffer.document.find( + event.data, in_current_line=True, count=event.arg + ) + if match: + return TextObject(match, type=TextObjectType.INCLUSIVE) + else: + return TextObject(0) + + @text_object("F", Keys.Any) + def _previous_occurance(event: E) -> TextObject: + """ + Go to previous occurrence of character. Typing 'Fx' will move the + cursor to the previous occurrence of character. 'x'. + """ + event.app.vi_state.last_character_find = CharacterFind(event.data, True) + return TextObject( + event.current_buffer.document.find_backwards( + event.data, in_current_line=True, count=event.arg + ) + or 0 + ) + + @text_object("t", Keys.Any) + def _t(event: E) -> TextObject: + """ + Move right to the next occurrence of c, then one char backward. + """ + event.app.vi_state.last_character_find = CharacterFind(event.data, False) + match = event.current_buffer.document.find( + event.data, in_current_line=True, count=event.arg + ) + if match: + return TextObject(match - 1, type=TextObjectType.INCLUSIVE) + else: + return TextObject(0) + + @text_object("T", Keys.Any) + def _T(event: E) -> TextObject: + """ + Move left to the previous occurrence of c, then one char forward. + """ + event.app.vi_state.last_character_find = CharacterFind(event.data, True) + match = event.current_buffer.document.find_backwards( + event.data, in_current_line=True, count=event.arg + ) + return TextObject(match + 1 if match else 0) + + def repeat(reverse: bool) -> None: + """ + Create ',' and ';' commands. + """ + + @text_object("," if reverse else ";") + def _(event: E) -> TextObject: + """ + Repeat the last 'f'/'F'/'t'/'T' command. + """ + pos: Optional[int] = 0 + vi_state = event.app.vi_state + + type = TextObjectType.EXCLUSIVE + + if vi_state.last_character_find: + char = vi_state.last_character_find.character + backwards = vi_state.last_character_find.backwards + + if reverse: + backwards = not backwards + + if backwards: + pos = event.current_buffer.document.find_backwards( + char, in_current_line=True, count=event.arg + ) + else: + pos = event.current_buffer.document.find( + char, in_current_line=True, count=event.arg + ) + type = TextObjectType.INCLUSIVE + if pos: + return TextObject(pos, type=type) + else: + return TextObject(0) + + repeat(True) + repeat(False) + + @text_object("h") + @text_object("left") + def _left(event: E) -> TextObject: + """ + Implements 'ch', 'dh', 'h': Cursor left. + """ + return TextObject( + event.current_buffer.document.get_cursor_left_position(count=event.arg) + ) + + @text_object("j", no_move_handler=True, no_selection_handler=True) + # Note: We also need `no_selection_handler`, because we in + # selection mode, we prefer the other 'j' binding that keeps + # `buffer.preferred_column`. + def _down(event: E) -> TextObject: + """ + Implements 'cj', 'dj', 'j', ... Cursor up. + """ + return TextObject( + event.current_buffer.document.get_cursor_down_position(count=event.arg), + type=TextObjectType.LINEWISE, + ) + + @text_object("k", no_move_handler=True, no_selection_handler=True) + def _up(event: E) -> TextObject: + """ + Implements 'ck', 'dk', 'k', ... Cursor up. + """ + return TextObject( + event.current_buffer.document.get_cursor_up_position(count=event.arg), + type=TextObjectType.LINEWISE, + ) + + @text_object("l") + @text_object(" ") + @text_object("right") + def _right(event: E) -> TextObject: + """ + Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. + """ + return TextObject( + event.current_buffer.document.get_cursor_right_position(count=event.arg) + ) + + @text_object("H") + def _top_of_screen(event: E) -> TextObject: + """ + Moves to the start of the visible region. (Below the scroll offset.) + Implements 'cH', 'dH', 'H'. + """ + w = event.app.layout.current_window + b = event.current_buffer + + if w and w.render_info: + # When we find a Window that has BufferControl showing this window, + # move to the start of the visible area. + pos = ( + b.document.translate_row_col_to_index( + w.render_info.first_visible_line(after_scroll_offset=True), 0 + ) + - b.cursor_position + ) + + else: + # Otherwise, move to the start of the input. + pos = -len(b.document.text_before_cursor) + return TextObject(pos, type=TextObjectType.LINEWISE) + + @text_object("M") + def _middle_of_screen(event: E) -> TextObject: + """ + Moves cursor to the vertical center of the visible region. + Implements 'cM', 'dM', 'M'. + """ + w = event.app.layout.current_window + b = event.current_buffer + + if w and w.render_info: + # When we find a Window that has BufferControl showing this window, + # move to the center of the visible area. + pos = ( + b.document.translate_row_col_to_index( + w.render_info.center_visible_line(), 0 + ) + - b.cursor_position + ) + + else: + # Otherwise, move to the start of the input. + pos = -len(b.document.text_before_cursor) + return TextObject(pos, type=TextObjectType.LINEWISE) + + @text_object("L") + def _end_of_screen(event: E) -> TextObject: + """ + Moves to the end of the visible region. (Above the scroll offset.) + """ + w = event.app.layout.current_window + b = event.current_buffer + + if w and w.render_info: + # When we find a Window that has BufferControl showing this window, + # move to the end of the visible area. + pos = ( + b.document.translate_row_col_to_index( + w.render_info.last_visible_line(before_scroll_offset=True), 0 + ) + - b.cursor_position + ) + + else: + # Otherwise, move to the end of the input. + pos = len(b.document.text_after_cursor) + return TextObject(pos, type=TextObjectType.LINEWISE) + + @text_object("n", no_move_handler=True) + def _search_next(event: E) -> TextObject: + """ + Search next. + """ + buff = event.current_buffer + search_state = event.app.current_search_state + + cursor_position = buff.get_search_position( + search_state, include_current_position=False, count=event.arg + ) + return TextObject(cursor_position - buff.cursor_position) + + @handle("n", filter=vi_navigation_mode) + def _search_next2(event: E) -> None: + """ + Search next in navigation mode. (This goes through the history.) + """ + search_state = event.app.current_search_state + + event.current_buffer.apply_search( + search_state, include_current_position=False, count=event.arg + ) + + @text_object("N", no_move_handler=True) + def _search_previous(event: E) -> TextObject: + """ + Search previous. + """ + buff = event.current_buffer + search_state = event.app.current_search_state + + cursor_position = buff.get_search_position( + ~search_state, include_current_position=False, count=event.arg + ) + return TextObject(cursor_position - buff.cursor_position) + + @handle("N", filter=vi_navigation_mode) + def _search_previous2(event: E) -> None: + """ + Search previous in navigation mode. (This goes through the history.) + """ + search_state = event.app.current_search_state + + event.current_buffer.apply_search( + ~search_state, include_current_position=False, count=event.arg + ) + + @handle("z", "+", filter=vi_navigation_mode | vi_selection_mode) + @handle("z", "t", filter=vi_navigation_mode | vi_selection_mode) + @handle("z", "enter", filter=vi_navigation_mode | vi_selection_mode) + def _scroll_top(event: E) -> None: + """ + Scrolls the window to makes the current line the first line in the visible region. + """ + b = event.current_buffer + event.app.layout.current_window.vertical_scroll = b.document.cursor_position_row + + @handle("z", "-", filter=vi_navigation_mode | vi_selection_mode) + @handle("z", "b", filter=vi_navigation_mode | vi_selection_mode) + def _scroll_bottom(event: E) -> None: + """ + Scrolls the window to makes the current line the last line in the visible region. + """ + # We can safely set the scroll offset to zero; the Window will make + # sure that it scrolls at least enough to make the cursor visible + # again. + event.app.layout.current_window.vertical_scroll = 0 + + @handle("z", "z", filter=vi_navigation_mode | vi_selection_mode) + def _scroll_center(event: E) -> None: + """ + Center Window vertically around cursor. + """ + w = event.app.layout.current_window + b = event.current_buffer + + if w and w.render_info: + info = w.render_info + + # Calculate the offset that we need in order to position the row + # containing the cursor in the center. + scroll_height = info.window_height // 2 + + y = max(0, b.document.cursor_position_row - 1) + height = 0 + while y > 0: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y -= 1 + else: + break + + w.vertical_scroll = y + + @text_object("%") + def _goto_corresponding_bracket(event: E) -> TextObject: + """ + Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) + If an 'arg' has been given, go this this % position in the file. + """ + buffer = event.current_buffer + + if event._arg: + # If 'arg' has been given, the meaning of % is to go to the 'x%' + # row in the file. + if 0 < event.arg <= 100: + absolute_index = buffer.document.translate_row_col_to_index( + int((event.arg * buffer.document.line_count - 1) / 100), 0 + ) + return TextObject( + absolute_index - buffer.document.cursor_position, + type=TextObjectType.LINEWISE, + ) + else: + return TextObject(0) # Do nothing. + + else: + # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). + match = buffer.document.find_matching_bracket_position() + if match: + return TextObject(match, type=TextObjectType.INCLUSIVE) + else: + return TextObject(0) + + @text_object("|") + def _to_column(event: E) -> TextObject: + """ + Move to the n-th column (you may specify the argument n by typing it on + number keys, for example, 20|). + """ + return TextObject( + event.current_buffer.document.get_column_cursor_position(event.arg - 1) + ) + + @text_object("g", "g") + def _goto_first_line(event: E) -> TextObject: + """ + Go to the start of the very first line. + Implements 'gg', 'cgg', 'ygg' + """ + d = event.current_buffer.document + + if event._arg: + # Move to the given line. + return TextObject( + d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, + type=TextObjectType.LINEWISE, + ) + else: + # Move to the top of the input. + return TextObject( + d.get_start_of_document_position(), type=TextObjectType.LINEWISE + ) + + @text_object("g", "_") + def _goto_last_line(event: E) -> TextObject: + """ + Go to last non-blank of line. + 'g_', 'cg_', 'yg_', etc.. + """ + return TextObject( + event.current_buffer.document.last_non_blank_of_current_line_position(), + type=TextObjectType.INCLUSIVE, + ) + + @text_object("g", "e") + def _ge(event: E) -> TextObject: + """ + Go to last character of previous word. + 'ge', 'cge', 'yge', etc.. + """ + prev_end = event.current_buffer.document.find_previous_word_ending( + count=event.arg + ) + return TextObject( + prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE + ) + + @text_object("g", "E") + def _gE(event: E) -> TextObject: + """ + Go to last character of previous WORD. + 'gE', 'cgE', 'ygE', etc.. + """ + prev_end = event.current_buffer.document.find_previous_word_ending( + count=event.arg, WORD=True + ) + return TextObject( + prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE + ) + + @text_object("g", "m") + def _gm(event: E) -> TextObject: + """ + Like g0, but half a screenwidth to the right. (Or as much as possible.) + """ + w = event.app.layout.current_window + buff = event.current_buffer + + if w and w.render_info: + width = w.render_info.window_width + start = buff.document.get_start_of_line_position(after_whitespace=False) + start += int(min(width / 2, len(buff.document.current_line))) + + return TextObject(start, type=TextObjectType.INCLUSIVE) + return TextObject(0) + + @text_object("G") + def _last_line(event: E) -> TextObject: + """ + Go to the end of the document. (If no arg has been given.) + """ + buf = event.current_buffer + return TextObject( + buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) + - buf.cursor_position, + type=TextObjectType.LINEWISE, + ) + + # + # *** Other *** + # + + @handle("G", filter=has_arg) + def _to_nth_history_line(event: E) -> None: + """ + If an argument is given, move to this line in the history. (for + example, 15G) + """ + event.current_buffer.go_to_history(event.arg - 1) + + for n in "123456789": + + @handle( + n, + filter=vi_navigation_mode + | vi_selection_mode + | vi_waiting_for_text_object_mode, + ) + def _arg(event: E) -> None: + """ + Always handle numberics in navigation mode as arg. + """ + event.append_to_arg_count(event.data) + + @handle( + "0", + filter=( + vi_navigation_mode | vi_selection_mode | vi_waiting_for_text_object_mode + ) + & has_arg, + ) + def _0_arg(event: E) -> None: + """ + Zero when an argument was already give. + """ + event.append_to_arg_count(event.data) + + @handle(Keys.Any, filter=vi_replace_mode) + def _insert_text(event: E) -> None: + """ + Insert data at cursor position. + """ + event.current_buffer.insert_text(event.data, overwrite=True) + + @handle(Keys.Any, filter=vi_replace_single_mode) + def _replace_single(event: E) -> None: + """ + Replace single character at cursor position. + """ + event.current_buffer.insert_text(event.data, overwrite=True) + event.current_buffer.cursor_position -= 1 + event.app.vi_state.input_mode = InputMode.NAVIGATION + + @handle( + Keys.Any, + filter=vi_insert_multiple_mode, + save_before=(lambda e: not e.is_repeat), + ) + def _insert_text_multiple_cursors(event: E) -> None: + """ + Insert data at multiple cursor positions at once. + (Usually a result of pressing 'I' or 'A' in block-selection mode.) + """ + buff = event.current_buffer + original_text = buff.text + + # Construct new text. + text = [] + p = 0 + + for p2 in buff.multiple_cursor_positions: + text.append(original_text[p:p2]) + text.append(event.data) + p = p2 + + text.append(original_text[p:]) + + # Shift all cursor positions. + new_cursor_positions = [ + pos + i + 1 for i, pos in enumerate(buff.multiple_cursor_positions) + ] + + # Set result. + buff.text = "".join(text) + buff.multiple_cursor_positions = new_cursor_positions + buff.cursor_position += 1 + + @handle("backspace", filter=vi_insert_multiple_mode) + def _delete_before_multiple_cursors(event: E) -> None: + """ + Backspace, using multiple cursors. + """ + buff = event.current_buffer + original_text = buff.text + + # Construct new text. + deleted_something = False + text = [] + p = 0 + + for p2 in buff.multiple_cursor_positions: + if p2 > 0 and original_text[p2 - 1] != "\n": # Don't delete across lines. + text.append(original_text[p : p2 - 1]) + deleted_something = True + else: + text.append(original_text[p:p2]) + p = p2 + + text.append(original_text[p:]) + + if deleted_something: + # Shift all cursor positions. + lengths = [len(part) for part in text[:-1]] + new_cursor_positions = list(accumulate(lengths)) + + # Set result. + buff.text = "".join(text) + buff.multiple_cursor_positions = new_cursor_positions + buff.cursor_position -= 1 + else: + event.app.output.bell() + + @handle("delete", filter=vi_insert_multiple_mode) + def _delete_after_multiple_cursors(event: E) -> None: + """ + Delete, using multiple cursors. + """ + buff = event.current_buffer + original_text = buff.text + + # Construct new text. + deleted_something = False + text = [] + new_cursor_positions = [] + p = 0 + + for p2 in buff.multiple_cursor_positions: + text.append(original_text[p:p2]) + if p2 >= len(original_text) or original_text[p2] == "\n": + # Don't delete across lines. + p = p2 + else: + p = p2 + 1 + deleted_something = True + + text.append(original_text[p:]) + + if deleted_something: + # Shift all cursor positions. + lengths = [len(part) for part in text[:-1]] + new_cursor_positions = list(accumulate(lengths)) + + # Set result. + buff.text = "".join(text) + buff.multiple_cursor_positions = new_cursor_positions + else: + event.app.output.bell() + + @handle("left", filter=vi_insert_multiple_mode) + def _left_multiple(event: E) -> None: + """ + Move all cursors to the left. + (But keep all cursors on the same line.) + """ + buff = event.current_buffer + new_positions = [] + + for p in buff.multiple_cursor_positions: + if buff.document.translate_index_to_position(p)[1] > 0: + p -= 1 + new_positions.append(p) + + buff.multiple_cursor_positions = new_positions + + if buff.document.cursor_position_col > 0: + buff.cursor_position -= 1 + + @handle("right", filter=vi_insert_multiple_mode) + def _right_multiple(event: E) -> None: + """ + Move all cursors to the right. + (But keep all cursors on the same line.) + """ + buff = event.current_buffer + new_positions = [] + + for p in buff.multiple_cursor_positions: + row, column = buff.document.translate_index_to_position(p) + if column < len(buff.document.lines[row]): + p += 1 + new_positions.append(p) + + buff.multiple_cursor_positions = new_positions + + if not buff.document.is_cursor_at_the_end_of_line: + buff.cursor_position += 1 + + @handle("up", filter=vi_insert_multiple_mode) + @handle("down", filter=vi_insert_multiple_mode) + def _updown_multiple(event: E) -> None: + """ + Ignore all up/down key presses when in multiple cursor mode. + """ + + @handle("c-x", "c-l", filter=vi_insert_mode) + def _complete_line(event: E) -> None: + """ + Pressing the ControlX - ControlL sequence in Vi mode does line + completion based on the other lines in the document and the history. + """ + event.current_buffer.start_history_lines_completion() + + @handle("c-x", "c-f", filter=vi_insert_mode) + def _complete_filename(event: E) -> None: + """ + Complete file names. + """ + # TODO + pass + + @handle("c-k", filter=vi_insert_mode | vi_replace_mode) + def _digraph(event: E) -> None: + """ + Go into digraph mode. + """ + event.app.vi_state.waiting_for_digraph = True + + @Condition + def digraph_symbol_1_given() -> bool: + return get_app().vi_state.digraph_symbol1 is not None + + @handle(Keys.Any, filter=vi_digraph_mode & ~digraph_symbol_1_given) + def _digraph1(event: E) -> None: + """ + First digraph symbol. + """ + event.app.vi_state.digraph_symbol1 = event.data + + @handle(Keys.Any, filter=vi_digraph_mode & digraph_symbol_1_given) + def _create_digraph(event: E) -> None: + """ + Insert digraph. + """ + try: + # Lookup. + code: Tuple[str, str] = ( + event.app.vi_state.digraph_symbol1 or "", + event.data, + ) + if code not in DIGRAPHS: + code = code[::-1] # Try reversing. + symbol = DIGRAPHS[code] + except KeyError: + # Unknown digraph. + event.app.output.bell() + else: + # Insert digraph. + overwrite = event.app.vi_state.input_mode == InputMode.REPLACE + event.current_buffer.insert_text(chr(symbol), overwrite=overwrite) + event.app.vi_state.waiting_for_digraph = False + finally: + event.app.vi_state.waiting_for_digraph = False + event.app.vi_state.digraph_symbol1 = None + + @handle("c-o", filter=vi_insert_mode | vi_replace_mode) + def _quick_normal_mode(event: E) -> None: + """ + Go into normal mode for one single action. + """ + event.app.vi_state.temporary_navigation_mode = True + + @handle("q", Keys.Any, filter=vi_navigation_mode & ~vi_recording_macro) + def _start_macro(event: E) -> None: + """ + Start recording macro. + """ + c = event.key_sequence[1].data + if c in vi_register_names: + vi_state = event.app.vi_state + + vi_state.recording_register = c + vi_state.current_recording = "" + + @handle("q", filter=vi_navigation_mode & vi_recording_macro) + def _stop_macro(event: E) -> None: + """ + Stop recording macro. + """ + vi_state = event.app.vi_state + + # Store and stop recording. + if vi_state.recording_register: + vi_state.named_registers[vi_state.recording_register] = ClipboardData( + vi_state.current_recording + ) + vi_state.recording_register = None + vi_state.current_recording = "" + + @handle("@", Keys.Any, filter=vi_navigation_mode, record_in_macro=False) + def _execute_macro(event: E) -> None: + """ + Execute macro. + + Notice that we pass `record_in_macro=False`. This ensures that the `@x` + keys don't appear in the recording itself. This function inserts the + body of the called macro back into the KeyProcessor, so these keys will + be added later on to the macro of their handlers have + `record_in_macro=True`. + """ + # Retrieve macro. + c = event.key_sequence[1].data + try: + macro = event.app.vi_state.named_registers[c] + except KeyError: + return + + # Expand macro (which is a string in the register), in individual keys. + # Use vt100 parser for this. + keys: List[KeyPress] = [] + + parser = Vt100Parser(keys.append) + parser.feed(macro.text) + parser.flush() + + # Now feed keys back to the input processor. + for _ in range(event.arg): + event.app.key_processor.feed_multiple(keys, first=True) + + return ConditionalKeyBindings(key_bindings, vi_mode) + + +def load_vi_search_bindings() -> KeyBindingsBase: + key_bindings = KeyBindings() + handle = key_bindings.add + from . import search + + @Condition + def search_buffer_is_empty() -> bool: + "Returns True when the search buffer is empty." + return get_app().current_buffer.text == "" + + # Vi-style forward search. + handle( + "/", + filter=(vi_navigation_mode | vi_selection_mode) & ~vi_search_direction_reversed, + )(search.start_forward_incremental_search) + handle( + "?", + filter=(vi_navigation_mode | vi_selection_mode) & vi_search_direction_reversed, + )(search.start_forward_incremental_search) + handle("c-s")(search.start_forward_incremental_search) + + # Vi-style backward search. + handle( + "?", + filter=(vi_navigation_mode | vi_selection_mode) & ~vi_search_direction_reversed, + )(search.start_reverse_incremental_search) + handle( + "/", + filter=(vi_navigation_mode | vi_selection_mode) & vi_search_direction_reversed, + )(search.start_reverse_incremental_search) + handle("c-r")(search.start_reverse_incremental_search) + + # Apply the search. (At the / or ? prompt.) + handle("enter", filter=is_searching)(search.accept_search) + + handle("c-r", filter=is_searching)(search.reverse_incremental_search) + handle("c-s", filter=is_searching)(search.forward_incremental_search) + + handle("c-c")(search.abort_search) + handle("c-g")(search.abort_search) + handle("backspace", filter=search_buffer_is_empty)(search.abort_search) + + # Handle escape. This should accept the search, just like readline. + # `abort_search` would be a meaningful alternative. + handle("escape")(search.accept_search) + + return ConditionalKeyBindings(key_bindings, vi_mode) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/defaults.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/defaults.py new file mode 100644 index 0000000..baa5974 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/defaults.py @@ -0,0 +1,60 @@ +""" +Default key bindings.:: + + key_bindings = load_key_bindings() + app = Application(key_bindings=key_bindings) +""" +from prompt_toolkit.filters import buffer_has_focus +from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings +from prompt_toolkit.key_binding.bindings.cpr import load_cpr_bindings +from prompt_toolkit.key_binding.bindings.emacs import ( + load_emacs_bindings, + load_emacs_search_bindings, + load_emacs_shift_selection_bindings, +) +from prompt_toolkit.key_binding.bindings.mouse import load_mouse_bindings +from prompt_toolkit.key_binding.bindings.vi import ( + load_vi_bindings, + load_vi_search_bindings, +) +from prompt_toolkit.key_binding.key_bindings import ( + ConditionalKeyBindings, + KeyBindingsBase, + merge_key_bindings, +) + +__all__ = [ + "load_key_bindings", +] + + +def load_key_bindings() -> KeyBindingsBase: + """ + Create a KeyBindings object that contains the default key bindings. + """ + all_bindings = merge_key_bindings( + [ + # Load basic bindings. + load_basic_bindings(), + # Load emacs bindings. + load_emacs_bindings(), + load_emacs_search_bindings(), + load_emacs_shift_selection_bindings(), + # Load Vi bindings. + load_vi_bindings(), + load_vi_search_bindings(), + ] + ) + + return merge_key_bindings( + [ + # Make sure that the above key bindings are only active if the + # currently focused control is a `BufferControl`. For other controls, we + # don't want these key bindings to intervene. (This would break "ptterm" + # for instance, which handles 'Keys.Any' in the user control itself.) + ConditionalKeyBindings(all_bindings, buffer_has_focus), + # Active, even when no buffer has been focused. + load_mouse_bindings(), + load_cpr_bindings(), + ] + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/digraphs.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/digraphs.py new file mode 100644 index 0000000..ad3d288 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/digraphs.py @@ -0,0 +1,1377 @@ +""" +Vi Digraphs. +This is a list of special characters that can be inserted in Vi insert mode by +pressing Control-K followed by to normal characters. + +Taken from Neovim and translated to Python: +https://raw.githubusercontent.com/neovim/neovim/master/src/nvim/digraph.c +""" +from typing import Dict, Tuple + +__all__ = [ + "DIGRAPHS", +] + +# digraphs for Unicode from RFC1345 +# (also work for ISO-8859-1 aka latin1) +DIGRAPHS: Dict[Tuple[str, str], int] = { + ("N", "U"): 0x00, + ("S", "H"): 0x01, + ("S", "X"): 0x02, + ("E", "X"): 0x03, + ("E", "T"): 0x04, + ("E", "Q"): 0x05, + ("A", "K"): 0x06, + ("B", "L"): 0x07, + ("B", "S"): 0x08, + ("H", "T"): 0x09, + ("L", "F"): 0x0A, + ("V", "T"): 0x0B, + ("F", "F"): 0x0C, + ("C", "R"): 0x0D, + ("S", "O"): 0x0E, + ("S", "I"): 0x0F, + ("D", "L"): 0x10, + ("D", "1"): 0x11, + ("D", "2"): 0x12, + ("D", "3"): 0x13, + ("D", "4"): 0x14, + ("N", "K"): 0x15, + ("S", "Y"): 0x16, + ("E", "B"): 0x17, + ("C", "N"): 0x18, + ("E", "M"): 0x19, + ("S", "B"): 0x1A, + ("E", "C"): 0x1B, + ("F", "S"): 0x1C, + ("G", "S"): 0x1D, + ("R", "S"): 0x1E, + ("U", "S"): 0x1F, + ("S", "P"): 0x20, + ("N", "b"): 0x23, + ("D", "O"): 0x24, + ("A", "t"): 0x40, + ("<", "("): 0x5B, + ("/", "/"): 0x5C, + (")", ">"): 0x5D, + ("'", ">"): 0x5E, + ("'", "!"): 0x60, + ("(", "!"): 0x7B, + ("!", "!"): 0x7C, + ("!", ")"): 0x7D, + ("'", "?"): 0x7E, + ("D", "T"): 0x7F, + ("P", "A"): 0x80, + ("H", "O"): 0x81, + ("B", "H"): 0x82, + ("N", "H"): 0x83, + ("I", "N"): 0x84, + ("N", "L"): 0x85, + ("S", "A"): 0x86, + ("E", "S"): 0x87, + ("H", "S"): 0x88, + ("H", "J"): 0x89, + ("V", "S"): 0x8A, + ("P", "D"): 0x8B, + ("P", "U"): 0x8C, + ("R", "I"): 0x8D, + ("S", "2"): 0x8E, + ("S", "3"): 0x8F, + ("D", "C"): 0x90, + ("P", "1"): 0x91, + ("P", "2"): 0x92, + ("T", "S"): 0x93, + ("C", "C"): 0x94, + ("M", "W"): 0x95, + ("S", "G"): 0x96, + ("E", "G"): 0x97, + ("S", "S"): 0x98, + ("G", "C"): 0x99, + ("S", "C"): 0x9A, + ("C", "I"): 0x9B, + ("S", "T"): 0x9C, + ("O", "C"): 0x9D, + ("P", "M"): 0x9E, + ("A", "C"): 0x9F, + ("N", "S"): 0xA0, + ("!", "I"): 0xA1, + ("C", "t"): 0xA2, + ("P", "d"): 0xA3, + ("C", "u"): 0xA4, + ("Y", "e"): 0xA5, + ("B", "B"): 0xA6, + ("S", "E"): 0xA7, + ("'", ":"): 0xA8, + ("C", "o"): 0xA9, + ("-", "a"): 0xAA, + ("<", "<"): 0xAB, + ("N", "O"): 0xAC, + ("-", "-"): 0xAD, + ("R", "g"): 0xAE, + ("'", "m"): 0xAF, + ("D", "G"): 0xB0, + ("+", "-"): 0xB1, + ("2", "S"): 0xB2, + ("3", "S"): 0xB3, + ("'", "'"): 0xB4, + ("M", "y"): 0xB5, + ("P", "I"): 0xB6, + (".", "M"): 0xB7, + ("'", ","): 0xB8, + ("1", "S"): 0xB9, + ("-", "o"): 0xBA, + (">", ">"): 0xBB, + ("1", "4"): 0xBC, + ("1", "2"): 0xBD, + ("3", "4"): 0xBE, + ("?", "I"): 0xBF, + ("A", "!"): 0xC0, + ("A", "'"): 0xC1, + ("A", ">"): 0xC2, + ("A", "?"): 0xC3, + ("A", ":"): 0xC4, + ("A", "A"): 0xC5, + ("A", "E"): 0xC6, + ("C", ","): 0xC7, + ("E", "!"): 0xC8, + ("E", "'"): 0xC9, + ("E", ">"): 0xCA, + ("E", ":"): 0xCB, + ("I", "!"): 0xCC, + ("I", "'"): 0xCD, + ("I", ">"): 0xCE, + ("I", ":"): 0xCF, + ("D", "-"): 0xD0, + ("N", "?"): 0xD1, + ("O", "!"): 0xD2, + ("O", "'"): 0xD3, + ("O", ">"): 0xD4, + ("O", "?"): 0xD5, + ("O", ":"): 0xD6, + ("*", "X"): 0xD7, + ("O", "/"): 0xD8, + ("U", "!"): 0xD9, + ("U", "'"): 0xDA, + ("U", ">"): 0xDB, + ("U", ":"): 0xDC, + ("Y", "'"): 0xDD, + ("T", "H"): 0xDE, + ("s", "s"): 0xDF, + ("a", "!"): 0xE0, + ("a", "'"): 0xE1, + ("a", ">"): 0xE2, + ("a", "?"): 0xE3, + ("a", ":"): 0xE4, + ("a", "a"): 0xE5, + ("a", "e"): 0xE6, + ("c", ","): 0xE7, + ("e", "!"): 0xE8, + ("e", "'"): 0xE9, + ("e", ">"): 0xEA, + ("e", ":"): 0xEB, + ("i", "!"): 0xEC, + ("i", "'"): 0xED, + ("i", ">"): 0xEE, + ("i", ":"): 0xEF, + ("d", "-"): 0xF0, + ("n", "?"): 0xF1, + ("o", "!"): 0xF2, + ("o", "'"): 0xF3, + ("o", ">"): 0xF4, + ("o", "?"): 0xF5, + ("o", ":"): 0xF6, + ("-", ":"): 0xF7, + ("o", "/"): 0xF8, + ("u", "!"): 0xF9, + ("u", "'"): 0xFA, + ("u", ">"): 0xFB, + ("u", ":"): 0xFC, + ("y", "'"): 0xFD, + ("t", "h"): 0xFE, + ("y", ":"): 0xFF, + ("A", "-"): 0x0100, + ("a", "-"): 0x0101, + ("A", "("): 0x0102, + ("a", "("): 0x0103, + ("A", ";"): 0x0104, + ("a", ";"): 0x0105, + ("C", "'"): 0x0106, + ("c", "'"): 0x0107, + ("C", ">"): 0x0108, + ("c", ">"): 0x0109, + ("C", "."): 0x010A, + ("c", "."): 0x010B, + ("C", "<"): 0x010C, + ("c", "<"): 0x010D, + ("D", "<"): 0x010E, + ("d", "<"): 0x010F, + ("D", "/"): 0x0110, + ("d", "/"): 0x0111, + ("E", "-"): 0x0112, + ("e", "-"): 0x0113, + ("E", "("): 0x0114, + ("e", "("): 0x0115, + ("E", "."): 0x0116, + ("e", "."): 0x0117, + ("E", ";"): 0x0118, + ("e", ";"): 0x0119, + ("E", "<"): 0x011A, + ("e", "<"): 0x011B, + ("G", ">"): 0x011C, + ("g", ">"): 0x011D, + ("G", "("): 0x011E, + ("g", "("): 0x011F, + ("G", "."): 0x0120, + ("g", "."): 0x0121, + ("G", ","): 0x0122, + ("g", ","): 0x0123, + ("H", ">"): 0x0124, + ("h", ">"): 0x0125, + ("H", "/"): 0x0126, + ("h", "/"): 0x0127, + ("I", "?"): 0x0128, + ("i", "?"): 0x0129, + ("I", "-"): 0x012A, + ("i", "-"): 0x012B, + ("I", "("): 0x012C, + ("i", "("): 0x012D, + ("I", ";"): 0x012E, + ("i", ";"): 0x012F, + ("I", "."): 0x0130, + ("i", "."): 0x0131, + ("I", "J"): 0x0132, + ("i", "j"): 0x0133, + ("J", ">"): 0x0134, + ("j", ">"): 0x0135, + ("K", ","): 0x0136, + ("k", ","): 0x0137, + ("k", "k"): 0x0138, + ("L", "'"): 0x0139, + ("l", "'"): 0x013A, + ("L", ","): 0x013B, + ("l", ","): 0x013C, + ("L", "<"): 0x013D, + ("l", "<"): 0x013E, + ("L", "."): 0x013F, + ("l", "."): 0x0140, + ("L", "/"): 0x0141, + ("l", "/"): 0x0142, + ("N", "'"): 0x0143, + ("n", "'"): 0x0144, + ("N", ","): 0x0145, + ("n", ","): 0x0146, + ("N", "<"): 0x0147, + ("n", "<"): 0x0148, + ("'", "n"): 0x0149, + ("N", "G"): 0x014A, + ("n", "g"): 0x014B, + ("O", "-"): 0x014C, + ("o", "-"): 0x014D, + ("O", "("): 0x014E, + ("o", "("): 0x014F, + ("O", '"'): 0x0150, + ("o", '"'): 0x0151, + ("O", "E"): 0x0152, + ("o", "e"): 0x0153, + ("R", "'"): 0x0154, + ("r", "'"): 0x0155, + ("R", ","): 0x0156, + ("r", ","): 0x0157, + ("R", "<"): 0x0158, + ("r", "<"): 0x0159, + ("S", "'"): 0x015A, + ("s", "'"): 0x015B, + ("S", ">"): 0x015C, + ("s", ">"): 0x015D, + ("S", ","): 0x015E, + ("s", ","): 0x015F, + ("S", "<"): 0x0160, + ("s", "<"): 0x0161, + ("T", ","): 0x0162, + ("t", ","): 0x0163, + ("T", "<"): 0x0164, + ("t", "<"): 0x0165, + ("T", "/"): 0x0166, + ("t", "/"): 0x0167, + ("U", "?"): 0x0168, + ("u", "?"): 0x0169, + ("U", "-"): 0x016A, + ("u", "-"): 0x016B, + ("U", "("): 0x016C, + ("u", "("): 0x016D, + ("U", "0"): 0x016E, + ("u", "0"): 0x016F, + ("U", '"'): 0x0170, + ("u", '"'): 0x0171, + ("U", ";"): 0x0172, + ("u", ";"): 0x0173, + ("W", ">"): 0x0174, + ("w", ">"): 0x0175, + ("Y", ">"): 0x0176, + ("y", ">"): 0x0177, + ("Y", ":"): 0x0178, + ("Z", "'"): 0x0179, + ("z", "'"): 0x017A, + ("Z", "."): 0x017B, + ("z", "."): 0x017C, + ("Z", "<"): 0x017D, + ("z", "<"): 0x017E, + ("O", "9"): 0x01A0, + ("o", "9"): 0x01A1, + ("O", "I"): 0x01A2, + ("o", "i"): 0x01A3, + ("y", "r"): 0x01A6, + ("U", "9"): 0x01AF, + ("u", "9"): 0x01B0, + ("Z", "/"): 0x01B5, + ("z", "/"): 0x01B6, + ("E", "D"): 0x01B7, + ("A", "<"): 0x01CD, + ("a", "<"): 0x01CE, + ("I", "<"): 0x01CF, + ("i", "<"): 0x01D0, + ("O", "<"): 0x01D1, + ("o", "<"): 0x01D2, + ("U", "<"): 0x01D3, + ("u", "<"): 0x01D4, + ("A", "1"): 0x01DE, + ("a", "1"): 0x01DF, + ("A", "7"): 0x01E0, + ("a", "7"): 0x01E1, + ("A", "3"): 0x01E2, + ("a", "3"): 0x01E3, + ("G", "/"): 0x01E4, + ("g", "/"): 0x01E5, + ("G", "<"): 0x01E6, + ("g", "<"): 0x01E7, + ("K", "<"): 0x01E8, + ("k", "<"): 0x01E9, + ("O", ";"): 0x01EA, + ("o", ";"): 0x01EB, + ("O", "1"): 0x01EC, + ("o", "1"): 0x01ED, + ("E", "Z"): 0x01EE, + ("e", "z"): 0x01EF, + ("j", "<"): 0x01F0, + ("G", "'"): 0x01F4, + ("g", "'"): 0x01F5, + (";", "S"): 0x02BF, + ("'", "<"): 0x02C7, + ("'", "("): 0x02D8, + ("'", "."): 0x02D9, + ("'", "0"): 0x02DA, + ("'", ";"): 0x02DB, + ("'", '"'): 0x02DD, + ("A", "%"): 0x0386, + ("E", "%"): 0x0388, + ("Y", "%"): 0x0389, + ("I", "%"): 0x038A, + ("O", "%"): 0x038C, + ("U", "%"): 0x038E, + ("W", "%"): 0x038F, + ("i", "3"): 0x0390, + ("A", "*"): 0x0391, + ("B", "*"): 0x0392, + ("G", "*"): 0x0393, + ("D", "*"): 0x0394, + ("E", "*"): 0x0395, + ("Z", "*"): 0x0396, + ("Y", "*"): 0x0397, + ("H", "*"): 0x0398, + ("I", "*"): 0x0399, + ("K", "*"): 0x039A, + ("L", "*"): 0x039B, + ("M", "*"): 0x039C, + ("N", "*"): 0x039D, + ("C", "*"): 0x039E, + ("O", "*"): 0x039F, + ("P", "*"): 0x03A0, + ("R", "*"): 0x03A1, + ("S", "*"): 0x03A3, + ("T", "*"): 0x03A4, + ("U", "*"): 0x03A5, + ("F", "*"): 0x03A6, + ("X", "*"): 0x03A7, + ("Q", "*"): 0x03A8, + ("W", "*"): 0x03A9, + ("J", "*"): 0x03AA, + ("V", "*"): 0x03AB, + ("a", "%"): 0x03AC, + ("e", "%"): 0x03AD, + ("y", "%"): 0x03AE, + ("i", "%"): 0x03AF, + ("u", "3"): 0x03B0, + ("a", "*"): 0x03B1, + ("b", "*"): 0x03B2, + ("g", "*"): 0x03B3, + ("d", "*"): 0x03B4, + ("e", "*"): 0x03B5, + ("z", "*"): 0x03B6, + ("y", "*"): 0x03B7, + ("h", "*"): 0x03B8, + ("i", "*"): 0x03B9, + ("k", "*"): 0x03BA, + ("l", "*"): 0x03BB, + ("m", "*"): 0x03BC, + ("n", "*"): 0x03BD, + ("c", "*"): 0x03BE, + ("o", "*"): 0x03BF, + ("p", "*"): 0x03C0, + ("r", "*"): 0x03C1, + ("*", "s"): 0x03C2, + ("s", "*"): 0x03C3, + ("t", "*"): 0x03C4, + ("u", "*"): 0x03C5, + ("f", "*"): 0x03C6, + ("x", "*"): 0x03C7, + ("q", "*"): 0x03C8, + ("w", "*"): 0x03C9, + ("j", "*"): 0x03CA, + ("v", "*"): 0x03CB, + ("o", "%"): 0x03CC, + ("u", "%"): 0x03CD, + ("w", "%"): 0x03CE, + ("'", "G"): 0x03D8, + (",", "G"): 0x03D9, + ("T", "3"): 0x03DA, + ("t", "3"): 0x03DB, + ("M", "3"): 0x03DC, + ("m", "3"): 0x03DD, + ("K", "3"): 0x03DE, + ("k", "3"): 0x03DF, + ("P", "3"): 0x03E0, + ("p", "3"): 0x03E1, + ("'", "%"): 0x03F4, + ("j", "3"): 0x03F5, + ("I", "O"): 0x0401, + ("D", "%"): 0x0402, + ("G", "%"): 0x0403, + ("I", "E"): 0x0404, + ("D", "S"): 0x0405, + ("I", "I"): 0x0406, + ("Y", "I"): 0x0407, + ("J", "%"): 0x0408, + ("L", "J"): 0x0409, + ("N", "J"): 0x040A, + ("T", "s"): 0x040B, + ("K", "J"): 0x040C, + ("V", "%"): 0x040E, + ("D", "Z"): 0x040F, + ("A", "="): 0x0410, + ("B", "="): 0x0411, + ("V", "="): 0x0412, + ("G", "="): 0x0413, + ("D", "="): 0x0414, + ("E", "="): 0x0415, + ("Z", "%"): 0x0416, + ("Z", "="): 0x0417, + ("I", "="): 0x0418, + ("J", "="): 0x0419, + ("K", "="): 0x041A, + ("L", "="): 0x041B, + ("M", "="): 0x041C, + ("N", "="): 0x041D, + ("O", "="): 0x041E, + ("P", "="): 0x041F, + ("R", "="): 0x0420, + ("S", "="): 0x0421, + ("T", "="): 0x0422, + ("U", "="): 0x0423, + ("F", "="): 0x0424, + ("H", "="): 0x0425, + ("C", "="): 0x0426, + ("C", "%"): 0x0427, + ("S", "%"): 0x0428, + ("S", "c"): 0x0429, + ("=", '"'): 0x042A, + ("Y", "="): 0x042B, + ("%", '"'): 0x042C, + ("J", "E"): 0x042D, + ("J", "U"): 0x042E, + ("J", "A"): 0x042F, + ("a", "="): 0x0430, + ("b", "="): 0x0431, + ("v", "="): 0x0432, + ("g", "="): 0x0433, + ("d", "="): 0x0434, + ("e", "="): 0x0435, + ("z", "%"): 0x0436, + ("z", "="): 0x0437, + ("i", "="): 0x0438, + ("j", "="): 0x0439, + ("k", "="): 0x043A, + ("l", "="): 0x043B, + ("m", "="): 0x043C, + ("n", "="): 0x043D, + ("o", "="): 0x043E, + ("p", "="): 0x043F, + ("r", "="): 0x0440, + ("s", "="): 0x0441, + ("t", "="): 0x0442, + ("u", "="): 0x0443, + ("f", "="): 0x0444, + ("h", "="): 0x0445, + ("c", "="): 0x0446, + ("c", "%"): 0x0447, + ("s", "%"): 0x0448, + ("s", "c"): 0x0449, + ("=", "'"): 0x044A, + ("y", "="): 0x044B, + ("%", "'"): 0x044C, + ("j", "e"): 0x044D, + ("j", "u"): 0x044E, + ("j", "a"): 0x044F, + ("i", "o"): 0x0451, + ("d", "%"): 0x0452, + ("g", "%"): 0x0453, + ("i", "e"): 0x0454, + ("d", "s"): 0x0455, + ("i", "i"): 0x0456, + ("y", "i"): 0x0457, + ("j", "%"): 0x0458, + ("l", "j"): 0x0459, + ("n", "j"): 0x045A, + ("t", "s"): 0x045B, + ("k", "j"): 0x045C, + ("v", "%"): 0x045E, + ("d", "z"): 0x045F, + ("Y", "3"): 0x0462, + ("y", "3"): 0x0463, + ("O", "3"): 0x046A, + ("o", "3"): 0x046B, + ("F", "3"): 0x0472, + ("f", "3"): 0x0473, + ("V", "3"): 0x0474, + ("v", "3"): 0x0475, + ("C", "3"): 0x0480, + ("c", "3"): 0x0481, + ("G", "3"): 0x0490, + ("g", "3"): 0x0491, + ("A", "+"): 0x05D0, + ("B", "+"): 0x05D1, + ("G", "+"): 0x05D2, + ("D", "+"): 0x05D3, + ("H", "+"): 0x05D4, + ("W", "+"): 0x05D5, + ("Z", "+"): 0x05D6, + ("X", "+"): 0x05D7, + ("T", "j"): 0x05D8, + ("J", "+"): 0x05D9, + ("K", "%"): 0x05DA, + ("K", "+"): 0x05DB, + ("L", "+"): 0x05DC, + ("M", "%"): 0x05DD, + ("M", "+"): 0x05DE, + ("N", "%"): 0x05DF, + ("N", "+"): 0x05E0, + ("S", "+"): 0x05E1, + ("E", "+"): 0x05E2, + ("P", "%"): 0x05E3, + ("P", "+"): 0x05E4, + ("Z", "j"): 0x05E5, + ("Z", "J"): 0x05E6, + ("Q", "+"): 0x05E7, + ("R", "+"): 0x05E8, + ("S", "h"): 0x05E9, + ("T", "+"): 0x05EA, + (",", "+"): 0x060C, + (";", "+"): 0x061B, + ("?", "+"): 0x061F, + ("H", "'"): 0x0621, + ("a", "M"): 0x0622, + ("a", "H"): 0x0623, + ("w", "H"): 0x0624, + ("a", "h"): 0x0625, + ("y", "H"): 0x0626, + ("a", "+"): 0x0627, + ("b", "+"): 0x0628, + ("t", "m"): 0x0629, + ("t", "+"): 0x062A, + ("t", "k"): 0x062B, + ("g", "+"): 0x062C, + ("h", "k"): 0x062D, + ("x", "+"): 0x062E, + ("d", "+"): 0x062F, + ("d", "k"): 0x0630, + ("r", "+"): 0x0631, + ("z", "+"): 0x0632, + ("s", "+"): 0x0633, + ("s", "n"): 0x0634, + ("c", "+"): 0x0635, + ("d", "d"): 0x0636, + ("t", "j"): 0x0637, + ("z", "H"): 0x0638, + ("e", "+"): 0x0639, + ("i", "+"): 0x063A, + ("+", "+"): 0x0640, + ("f", "+"): 0x0641, + ("q", "+"): 0x0642, + ("k", "+"): 0x0643, + ("l", "+"): 0x0644, + ("m", "+"): 0x0645, + ("n", "+"): 0x0646, + ("h", "+"): 0x0647, + ("w", "+"): 0x0648, + ("j", "+"): 0x0649, + ("y", "+"): 0x064A, + (":", "+"): 0x064B, + ('"', "+"): 0x064C, + ("=", "+"): 0x064D, + ("/", "+"): 0x064E, + ("'", "+"): 0x064F, + ("1", "+"): 0x0650, + ("3", "+"): 0x0651, + ("0", "+"): 0x0652, + ("a", "S"): 0x0670, + ("p", "+"): 0x067E, + ("v", "+"): 0x06A4, + ("g", "f"): 0x06AF, + ("0", "a"): 0x06F0, + ("1", "a"): 0x06F1, + ("2", "a"): 0x06F2, + ("3", "a"): 0x06F3, + ("4", "a"): 0x06F4, + ("5", "a"): 0x06F5, + ("6", "a"): 0x06F6, + ("7", "a"): 0x06F7, + ("8", "a"): 0x06F8, + ("9", "a"): 0x06F9, + ("B", "."): 0x1E02, + ("b", "."): 0x1E03, + ("B", "_"): 0x1E06, + ("b", "_"): 0x1E07, + ("D", "."): 0x1E0A, + ("d", "."): 0x1E0B, + ("D", "_"): 0x1E0E, + ("d", "_"): 0x1E0F, + ("D", ","): 0x1E10, + ("d", ","): 0x1E11, + ("F", "."): 0x1E1E, + ("f", "."): 0x1E1F, + ("G", "-"): 0x1E20, + ("g", "-"): 0x1E21, + ("H", "."): 0x1E22, + ("h", "."): 0x1E23, + ("H", ":"): 0x1E26, + ("h", ":"): 0x1E27, + ("H", ","): 0x1E28, + ("h", ","): 0x1E29, + ("K", "'"): 0x1E30, + ("k", "'"): 0x1E31, + ("K", "_"): 0x1E34, + ("k", "_"): 0x1E35, + ("L", "_"): 0x1E3A, + ("l", "_"): 0x1E3B, + ("M", "'"): 0x1E3E, + ("m", "'"): 0x1E3F, + ("M", "."): 0x1E40, + ("m", "."): 0x1E41, + ("N", "."): 0x1E44, + ("n", "."): 0x1E45, + ("N", "_"): 0x1E48, + ("n", "_"): 0x1E49, + ("P", "'"): 0x1E54, + ("p", "'"): 0x1E55, + ("P", "."): 0x1E56, + ("p", "."): 0x1E57, + ("R", "."): 0x1E58, + ("r", "."): 0x1E59, + ("R", "_"): 0x1E5E, + ("r", "_"): 0x1E5F, + ("S", "."): 0x1E60, + ("s", "."): 0x1E61, + ("T", "."): 0x1E6A, + ("t", "."): 0x1E6B, + ("T", "_"): 0x1E6E, + ("t", "_"): 0x1E6F, + ("V", "?"): 0x1E7C, + ("v", "?"): 0x1E7D, + ("W", "!"): 0x1E80, + ("w", "!"): 0x1E81, + ("W", "'"): 0x1E82, + ("w", "'"): 0x1E83, + ("W", ":"): 0x1E84, + ("w", ":"): 0x1E85, + ("W", "."): 0x1E86, + ("w", "."): 0x1E87, + ("X", "."): 0x1E8A, + ("x", "."): 0x1E8B, + ("X", ":"): 0x1E8C, + ("x", ":"): 0x1E8D, + ("Y", "."): 0x1E8E, + ("y", "."): 0x1E8F, + ("Z", ">"): 0x1E90, + ("z", ">"): 0x1E91, + ("Z", "_"): 0x1E94, + ("z", "_"): 0x1E95, + ("h", "_"): 0x1E96, + ("t", ":"): 0x1E97, + ("w", "0"): 0x1E98, + ("y", "0"): 0x1E99, + ("A", "2"): 0x1EA2, + ("a", "2"): 0x1EA3, + ("E", "2"): 0x1EBA, + ("e", "2"): 0x1EBB, + ("E", "?"): 0x1EBC, + ("e", "?"): 0x1EBD, + ("I", "2"): 0x1EC8, + ("i", "2"): 0x1EC9, + ("O", "2"): 0x1ECE, + ("o", "2"): 0x1ECF, + ("U", "2"): 0x1EE6, + ("u", "2"): 0x1EE7, + ("Y", "!"): 0x1EF2, + ("y", "!"): 0x1EF3, + ("Y", "2"): 0x1EF6, + ("y", "2"): 0x1EF7, + ("Y", "?"): 0x1EF8, + ("y", "?"): 0x1EF9, + (";", "'"): 0x1F00, + (",", "'"): 0x1F01, + (";", "!"): 0x1F02, + (",", "!"): 0x1F03, + ("?", ";"): 0x1F04, + ("?", ","): 0x1F05, + ("!", ":"): 0x1F06, + ("?", ":"): 0x1F07, + ("1", "N"): 0x2002, + ("1", "M"): 0x2003, + ("3", "M"): 0x2004, + ("4", "M"): 0x2005, + ("6", "M"): 0x2006, + ("1", "T"): 0x2009, + ("1", "H"): 0x200A, + ("-", "1"): 0x2010, + ("-", "N"): 0x2013, + ("-", "M"): 0x2014, + ("-", "3"): 0x2015, + ("!", "2"): 0x2016, + ("=", "2"): 0x2017, + ("'", "6"): 0x2018, + ("'", "9"): 0x2019, + (".", "9"): 0x201A, + ("9", "'"): 0x201B, + ('"', "6"): 0x201C, + ('"', "9"): 0x201D, + (":", "9"): 0x201E, + ("9", '"'): 0x201F, + ("/", "-"): 0x2020, + ("/", "="): 0x2021, + (".", "."): 0x2025, + ("%", "0"): 0x2030, + ("1", "'"): 0x2032, + ("2", "'"): 0x2033, + ("3", "'"): 0x2034, + ("1", '"'): 0x2035, + ("2", '"'): 0x2036, + ("3", '"'): 0x2037, + ("C", "a"): 0x2038, + ("<", "1"): 0x2039, + (">", "1"): 0x203A, + (":", "X"): 0x203B, + ("'", "-"): 0x203E, + ("/", "f"): 0x2044, + ("0", "S"): 0x2070, + ("4", "S"): 0x2074, + ("5", "S"): 0x2075, + ("6", "S"): 0x2076, + ("7", "S"): 0x2077, + ("8", "S"): 0x2078, + ("9", "S"): 0x2079, + ("+", "S"): 0x207A, + ("-", "S"): 0x207B, + ("=", "S"): 0x207C, + ("(", "S"): 0x207D, + (")", "S"): 0x207E, + ("n", "S"): 0x207F, + ("0", "s"): 0x2080, + ("1", "s"): 0x2081, + ("2", "s"): 0x2082, + ("3", "s"): 0x2083, + ("4", "s"): 0x2084, + ("5", "s"): 0x2085, + ("6", "s"): 0x2086, + ("7", "s"): 0x2087, + ("8", "s"): 0x2088, + ("9", "s"): 0x2089, + ("+", "s"): 0x208A, + ("-", "s"): 0x208B, + ("=", "s"): 0x208C, + ("(", "s"): 0x208D, + (")", "s"): 0x208E, + ("L", "i"): 0x20A4, + ("P", "t"): 0x20A7, + ("W", "="): 0x20A9, + ("=", "e"): 0x20AC, # euro + ("E", "u"): 0x20AC, # euro + ("=", "R"): 0x20BD, # rouble + ("=", "P"): 0x20BD, # rouble + ("o", "C"): 0x2103, + ("c", "o"): 0x2105, + ("o", "F"): 0x2109, + ("N", "0"): 0x2116, + ("P", "O"): 0x2117, + ("R", "x"): 0x211E, + ("S", "M"): 0x2120, + ("T", "M"): 0x2122, + ("O", "m"): 0x2126, + ("A", "O"): 0x212B, + ("1", "3"): 0x2153, + ("2", "3"): 0x2154, + ("1", "5"): 0x2155, + ("2", "5"): 0x2156, + ("3", "5"): 0x2157, + ("4", "5"): 0x2158, + ("1", "6"): 0x2159, + ("5", "6"): 0x215A, + ("1", "8"): 0x215B, + ("3", "8"): 0x215C, + ("5", "8"): 0x215D, + ("7", "8"): 0x215E, + ("1", "R"): 0x2160, + ("2", "R"): 0x2161, + ("3", "R"): 0x2162, + ("4", "R"): 0x2163, + ("5", "R"): 0x2164, + ("6", "R"): 0x2165, + ("7", "R"): 0x2166, + ("8", "R"): 0x2167, + ("9", "R"): 0x2168, + ("a", "R"): 0x2169, + ("b", "R"): 0x216A, + ("c", "R"): 0x216B, + ("1", "r"): 0x2170, + ("2", "r"): 0x2171, + ("3", "r"): 0x2172, + ("4", "r"): 0x2173, + ("5", "r"): 0x2174, + ("6", "r"): 0x2175, + ("7", "r"): 0x2176, + ("8", "r"): 0x2177, + ("9", "r"): 0x2178, + ("a", "r"): 0x2179, + ("b", "r"): 0x217A, + ("c", "r"): 0x217B, + ("<", "-"): 0x2190, + ("-", "!"): 0x2191, + ("-", ">"): 0x2192, + ("-", "v"): 0x2193, + ("<", ">"): 0x2194, + ("U", "D"): 0x2195, + ("<", "="): 0x21D0, + ("=", ">"): 0x21D2, + ("=", "="): 0x21D4, + ("F", "A"): 0x2200, + ("d", "P"): 0x2202, + ("T", "E"): 0x2203, + ("/", "0"): 0x2205, + ("D", "E"): 0x2206, + ("N", "B"): 0x2207, + ("(", "-"): 0x2208, + ("-", ")"): 0x220B, + ("*", "P"): 0x220F, + ("+", "Z"): 0x2211, + ("-", "2"): 0x2212, + ("-", "+"): 0x2213, + ("*", "-"): 0x2217, + ("O", "b"): 0x2218, + ("S", "b"): 0x2219, + ("R", "T"): 0x221A, + ("0", "("): 0x221D, + ("0", "0"): 0x221E, + ("-", "L"): 0x221F, + ("-", "V"): 0x2220, + ("P", "P"): 0x2225, + ("A", "N"): 0x2227, + ("O", "R"): 0x2228, + ("(", "U"): 0x2229, + (")", "U"): 0x222A, + ("I", "n"): 0x222B, + ("D", "I"): 0x222C, + ("I", "o"): 0x222E, + (".", ":"): 0x2234, + (":", "."): 0x2235, + (":", "R"): 0x2236, + (":", ":"): 0x2237, + ("?", "1"): 0x223C, + ("C", "G"): 0x223E, + ("?", "-"): 0x2243, + ("?", "="): 0x2245, + ("?", "2"): 0x2248, + ("=", "?"): 0x224C, + ("H", "I"): 0x2253, + ("!", "="): 0x2260, + ("=", "3"): 0x2261, + ("=", "<"): 0x2264, + (">", "="): 0x2265, + ("<", "*"): 0x226A, + ("*", ">"): 0x226B, + ("!", "<"): 0x226E, + ("!", ">"): 0x226F, + ("(", "C"): 0x2282, + (")", "C"): 0x2283, + ("(", "_"): 0x2286, + (")", "_"): 0x2287, + ("0", "."): 0x2299, + ("0", "2"): 0x229A, + ("-", "T"): 0x22A5, + (".", "P"): 0x22C5, + (":", "3"): 0x22EE, + (".", "3"): 0x22EF, + ("E", "h"): 0x2302, + ("<", "7"): 0x2308, + (">", "7"): 0x2309, + ("7", "<"): 0x230A, + ("7", ">"): 0x230B, + ("N", "I"): 0x2310, + ("(", "A"): 0x2312, + ("T", "R"): 0x2315, + ("I", "u"): 0x2320, + ("I", "l"): 0x2321, + ("<", "/"): 0x2329, + ("/", ">"): 0x232A, + ("V", "s"): 0x2423, + ("1", "h"): 0x2440, + ("3", "h"): 0x2441, + ("2", "h"): 0x2442, + ("4", "h"): 0x2443, + ("1", "j"): 0x2446, + ("2", "j"): 0x2447, + ("3", "j"): 0x2448, + ("4", "j"): 0x2449, + ("1", "."): 0x2488, + ("2", "."): 0x2489, + ("3", "."): 0x248A, + ("4", "."): 0x248B, + ("5", "."): 0x248C, + ("6", "."): 0x248D, + ("7", "."): 0x248E, + ("8", "."): 0x248F, + ("9", "."): 0x2490, + ("h", "h"): 0x2500, + ("H", "H"): 0x2501, + ("v", "v"): 0x2502, + ("V", "V"): 0x2503, + ("3", "-"): 0x2504, + ("3", "_"): 0x2505, + ("3", "!"): 0x2506, + ("3", "/"): 0x2507, + ("4", "-"): 0x2508, + ("4", "_"): 0x2509, + ("4", "!"): 0x250A, + ("4", "/"): 0x250B, + ("d", "r"): 0x250C, + ("d", "R"): 0x250D, + ("D", "r"): 0x250E, + ("D", "R"): 0x250F, + ("d", "l"): 0x2510, + ("d", "L"): 0x2511, + ("D", "l"): 0x2512, + ("L", "D"): 0x2513, + ("u", "r"): 0x2514, + ("u", "R"): 0x2515, + ("U", "r"): 0x2516, + ("U", "R"): 0x2517, + ("u", "l"): 0x2518, + ("u", "L"): 0x2519, + ("U", "l"): 0x251A, + ("U", "L"): 0x251B, + ("v", "r"): 0x251C, + ("v", "R"): 0x251D, + ("V", "r"): 0x2520, + ("V", "R"): 0x2523, + ("v", "l"): 0x2524, + ("v", "L"): 0x2525, + ("V", "l"): 0x2528, + ("V", "L"): 0x252B, + ("d", "h"): 0x252C, + ("d", "H"): 0x252F, + ("D", "h"): 0x2530, + ("D", "H"): 0x2533, + ("u", "h"): 0x2534, + ("u", "H"): 0x2537, + ("U", "h"): 0x2538, + ("U", "H"): 0x253B, + ("v", "h"): 0x253C, + ("v", "H"): 0x253F, + ("V", "h"): 0x2542, + ("V", "H"): 0x254B, + ("F", "D"): 0x2571, + ("B", "D"): 0x2572, + ("T", "B"): 0x2580, + ("L", "B"): 0x2584, + ("F", "B"): 0x2588, + ("l", "B"): 0x258C, + ("R", "B"): 0x2590, + (".", "S"): 0x2591, + (":", "S"): 0x2592, + ("?", "S"): 0x2593, + ("f", "S"): 0x25A0, + ("O", "S"): 0x25A1, + ("R", "O"): 0x25A2, + ("R", "r"): 0x25A3, + ("R", "F"): 0x25A4, + ("R", "Y"): 0x25A5, + ("R", "H"): 0x25A6, + ("R", "Z"): 0x25A7, + ("R", "K"): 0x25A8, + ("R", "X"): 0x25A9, + ("s", "B"): 0x25AA, + ("S", "R"): 0x25AC, + ("O", "r"): 0x25AD, + ("U", "T"): 0x25B2, + ("u", "T"): 0x25B3, + ("P", "R"): 0x25B6, + ("T", "r"): 0x25B7, + ("D", "t"): 0x25BC, + ("d", "T"): 0x25BD, + ("P", "L"): 0x25C0, + ("T", "l"): 0x25C1, + ("D", "b"): 0x25C6, + ("D", "w"): 0x25C7, + ("L", "Z"): 0x25CA, + ("0", "m"): 0x25CB, + ("0", "o"): 0x25CE, + ("0", "M"): 0x25CF, + ("0", "L"): 0x25D0, + ("0", "R"): 0x25D1, + ("S", "n"): 0x25D8, + ("I", "c"): 0x25D9, + ("F", "d"): 0x25E2, + ("B", "d"): 0x25E3, + ("*", "2"): 0x2605, + ("*", "1"): 0x2606, + ("<", "H"): 0x261C, + (">", "H"): 0x261E, + ("0", "u"): 0x263A, + ("0", "U"): 0x263B, + ("S", "U"): 0x263C, + ("F", "m"): 0x2640, + ("M", "l"): 0x2642, + ("c", "S"): 0x2660, + ("c", "H"): 0x2661, + ("c", "D"): 0x2662, + ("c", "C"): 0x2663, + ("M", "d"): 0x2669, + ("M", "8"): 0x266A, + ("M", "2"): 0x266B, + ("M", "b"): 0x266D, + ("M", "x"): 0x266E, + ("M", "X"): 0x266F, + ("O", "K"): 0x2713, + ("X", "X"): 0x2717, + ("-", "X"): 0x2720, + ("I", "S"): 0x3000, + (",", "_"): 0x3001, + (".", "_"): 0x3002, + ("+", '"'): 0x3003, + ("+", "_"): 0x3004, + ("*", "_"): 0x3005, + (";", "_"): 0x3006, + ("0", "_"): 0x3007, + ("<", "+"): 0x300A, + (">", "+"): 0x300B, + ("<", "'"): 0x300C, + (">", "'"): 0x300D, + ("<", '"'): 0x300E, + (">", '"'): 0x300F, + ("(", '"'): 0x3010, + (")", '"'): 0x3011, + ("=", "T"): 0x3012, + ("=", "_"): 0x3013, + ("(", "'"): 0x3014, + (")", "'"): 0x3015, + ("(", "I"): 0x3016, + (")", "I"): 0x3017, + ("-", "?"): 0x301C, + ("A", "5"): 0x3041, + ("a", "5"): 0x3042, + ("I", "5"): 0x3043, + ("i", "5"): 0x3044, + ("U", "5"): 0x3045, + ("u", "5"): 0x3046, + ("E", "5"): 0x3047, + ("e", "5"): 0x3048, + ("O", "5"): 0x3049, + ("o", "5"): 0x304A, + ("k", "a"): 0x304B, + ("g", "a"): 0x304C, + ("k", "i"): 0x304D, + ("g", "i"): 0x304E, + ("k", "u"): 0x304F, + ("g", "u"): 0x3050, + ("k", "e"): 0x3051, + ("g", "e"): 0x3052, + ("k", "o"): 0x3053, + ("g", "o"): 0x3054, + ("s", "a"): 0x3055, + ("z", "a"): 0x3056, + ("s", "i"): 0x3057, + ("z", "i"): 0x3058, + ("s", "u"): 0x3059, + ("z", "u"): 0x305A, + ("s", "e"): 0x305B, + ("z", "e"): 0x305C, + ("s", "o"): 0x305D, + ("z", "o"): 0x305E, + ("t", "a"): 0x305F, + ("d", "a"): 0x3060, + ("t", "i"): 0x3061, + ("d", "i"): 0x3062, + ("t", "U"): 0x3063, + ("t", "u"): 0x3064, + ("d", "u"): 0x3065, + ("t", "e"): 0x3066, + ("d", "e"): 0x3067, + ("t", "o"): 0x3068, + ("d", "o"): 0x3069, + ("n", "a"): 0x306A, + ("n", "i"): 0x306B, + ("n", "u"): 0x306C, + ("n", "e"): 0x306D, + ("n", "o"): 0x306E, + ("h", "a"): 0x306F, + ("b", "a"): 0x3070, + ("p", "a"): 0x3071, + ("h", "i"): 0x3072, + ("b", "i"): 0x3073, + ("p", "i"): 0x3074, + ("h", "u"): 0x3075, + ("b", "u"): 0x3076, + ("p", "u"): 0x3077, + ("h", "e"): 0x3078, + ("b", "e"): 0x3079, + ("p", "e"): 0x307A, + ("h", "o"): 0x307B, + ("b", "o"): 0x307C, + ("p", "o"): 0x307D, + ("m", "a"): 0x307E, + ("m", "i"): 0x307F, + ("m", "u"): 0x3080, + ("m", "e"): 0x3081, + ("m", "o"): 0x3082, + ("y", "A"): 0x3083, + ("y", "a"): 0x3084, + ("y", "U"): 0x3085, + ("y", "u"): 0x3086, + ("y", "O"): 0x3087, + ("y", "o"): 0x3088, + ("r", "a"): 0x3089, + ("r", "i"): 0x308A, + ("r", "u"): 0x308B, + ("r", "e"): 0x308C, + ("r", "o"): 0x308D, + ("w", "A"): 0x308E, + ("w", "a"): 0x308F, + ("w", "i"): 0x3090, + ("w", "e"): 0x3091, + ("w", "o"): 0x3092, + ("n", "5"): 0x3093, + ("v", "u"): 0x3094, + ('"', "5"): 0x309B, + ("0", "5"): 0x309C, + ("*", "5"): 0x309D, + ("+", "5"): 0x309E, + ("a", "6"): 0x30A1, + ("A", "6"): 0x30A2, + ("i", "6"): 0x30A3, + ("I", "6"): 0x30A4, + ("u", "6"): 0x30A5, + ("U", "6"): 0x30A6, + ("e", "6"): 0x30A7, + ("E", "6"): 0x30A8, + ("o", "6"): 0x30A9, + ("O", "6"): 0x30AA, + ("K", "a"): 0x30AB, + ("G", "a"): 0x30AC, + ("K", "i"): 0x30AD, + ("G", "i"): 0x30AE, + ("K", "u"): 0x30AF, + ("G", "u"): 0x30B0, + ("K", "e"): 0x30B1, + ("G", "e"): 0x30B2, + ("K", "o"): 0x30B3, + ("G", "o"): 0x30B4, + ("S", "a"): 0x30B5, + ("Z", "a"): 0x30B6, + ("S", "i"): 0x30B7, + ("Z", "i"): 0x30B8, + ("S", "u"): 0x30B9, + ("Z", "u"): 0x30BA, + ("S", "e"): 0x30BB, + ("Z", "e"): 0x30BC, + ("S", "o"): 0x30BD, + ("Z", "o"): 0x30BE, + ("T", "a"): 0x30BF, + ("D", "a"): 0x30C0, + ("T", "i"): 0x30C1, + ("D", "i"): 0x30C2, + ("T", "U"): 0x30C3, + ("T", "u"): 0x30C4, + ("D", "u"): 0x30C5, + ("T", "e"): 0x30C6, + ("D", "e"): 0x30C7, + ("T", "o"): 0x30C8, + ("D", "o"): 0x30C9, + ("N", "a"): 0x30CA, + ("N", "i"): 0x30CB, + ("N", "u"): 0x30CC, + ("N", "e"): 0x30CD, + ("N", "o"): 0x30CE, + ("H", "a"): 0x30CF, + ("B", "a"): 0x30D0, + ("P", "a"): 0x30D1, + ("H", "i"): 0x30D2, + ("B", "i"): 0x30D3, + ("P", "i"): 0x30D4, + ("H", "u"): 0x30D5, + ("B", "u"): 0x30D6, + ("P", "u"): 0x30D7, + ("H", "e"): 0x30D8, + ("B", "e"): 0x30D9, + ("P", "e"): 0x30DA, + ("H", "o"): 0x30DB, + ("B", "o"): 0x30DC, + ("P", "o"): 0x30DD, + ("M", "a"): 0x30DE, + ("M", "i"): 0x30DF, + ("M", "u"): 0x30E0, + ("M", "e"): 0x30E1, + ("M", "o"): 0x30E2, + ("Y", "A"): 0x30E3, + ("Y", "a"): 0x30E4, + ("Y", "U"): 0x30E5, + ("Y", "u"): 0x30E6, + ("Y", "O"): 0x30E7, + ("Y", "o"): 0x30E8, + ("R", "a"): 0x30E9, + ("R", "i"): 0x30EA, + ("R", "u"): 0x30EB, + ("R", "e"): 0x30EC, + ("R", "o"): 0x30ED, + ("W", "A"): 0x30EE, + ("W", "a"): 0x30EF, + ("W", "i"): 0x30F0, + ("W", "e"): 0x30F1, + ("W", "o"): 0x30F2, + ("N", "6"): 0x30F3, + ("V", "u"): 0x30F4, + ("K", "A"): 0x30F5, + ("K", "E"): 0x30F6, + ("V", "a"): 0x30F7, + ("V", "i"): 0x30F8, + ("V", "e"): 0x30F9, + ("V", "o"): 0x30FA, + (".", "6"): 0x30FB, + ("-", "6"): 0x30FC, + ("*", "6"): 0x30FD, + ("+", "6"): 0x30FE, + ("b", "4"): 0x3105, + ("p", "4"): 0x3106, + ("m", "4"): 0x3107, + ("f", "4"): 0x3108, + ("d", "4"): 0x3109, + ("t", "4"): 0x310A, + ("n", "4"): 0x310B, + ("l", "4"): 0x310C, + ("g", "4"): 0x310D, + ("k", "4"): 0x310E, + ("h", "4"): 0x310F, + ("j", "4"): 0x3110, + ("q", "4"): 0x3111, + ("x", "4"): 0x3112, + ("z", "h"): 0x3113, + ("c", "h"): 0x3114, + ("s", "h"): 0x3115, + ("r", "4"): 0x3116, + ("z", "4"): 0x3117, + ("c", "4"): 0x3118, + ("s", "4"): 0x3119, + ("a", "4"): 0x311A, + ("o", "4"): 0x311B, + ("e", "4"): 0x311C, + ("a", "i"): 0x311E, + ("e", "i"): 0x311F, + ("a", "u"): 0x3120, + ("o", "u"): 0x3121, + ("a", "n"): 0x3122, + ("e", "n"): 0x3123, + ("a", "N"): 0x3124, + ("e", "N"): 0x3125, + ("e", "r"): 0x3126, + ("i", "4"): 0x3127, + ("u", "4"): 0x3128, + ("i", "u"): 0x3129, + ("v", "4"): 0x312A, + ("n", "G"): 0x312B, + ("g", "n"): 0x312C, + ("1", "c"): 0x3220, + ("2", "c"): 0x3221, + ("3", "c"): 0x3222, + ("4", "c"): 0x3223, + ("5", "c"): 0x3224, + ("6", "c"): 0x3225, + ("7", "c"): 0x3226, + ("8", "c"): 0x3227, + ("9", "c"): 0x3228, + # code points 0xe000 - 0xefff excluded, they have no assigned + # characters, only used in proposals. + ("f", "f"): 0xFB00, + ("f", "i"): 0xFB01, + ("f", "l"): 0xFB02, + ("f", "t"): 0xFB05, + ("s", "t"): 0xFB06, + # Vim 5.x compatible digraphs that don't conflict with the above + ("~", "!"): 161, + ("c", "|"): 162, + ("$", "$"): 163, + ("o", "x"): 164, # currency symbol in ISO 8859-1 + ("Y", "-"): 165, + ("|", "|"): 166, + ("c", "O"): 169, + ("-", ","): 172, + ("-", "="): 175, + ("~", "o"): 176, + ("2", "2"): 178, + ("3", "3"): 179, + ("p", "p"): 182, + ("~", "."): 183, + ("1", "1"): 185, + ("~", "?"): 191, + ("A", "`"): 192, + ("A", "^"): 194, + ("A", "~"): 195, + ("A", '"'): 196, + ("A", "@"): 197, + ("E", "`"): 200, + ("E", "^"): 202, + ("E", '"'): 203, + ("I", "`"): 204, + ("I", "^"): 206, + ("I", '"'): 207, + ("N", "~"): 209, + ("O", "`"): 210, + ("O", "^"): 212, + ("O", "~"): 213, + ("/", "\\"): 215, # multiplication symbol in ISO 8859-1 + ("U", "`"): 217, + ("U", "^"): 219, + ("I", "p"): 222, + ("a", "`"): 224, + ("a", "^"): 226, + ("a", "~"): 227, + ("a", '"'): 228, + ("a", "@"): 229, + ("e", "`"): 232, + ("e", "^"): 234, + ("e", '"'): 235, + ("i", "`"): 236, + ("i", "^"): 238, + ("n", "~"): 241, + ("o", "`"): 242, + ("o", "^"): 244, + ("o", "~"): 245, + ("u", "`"): 249, + ("u", "^"): 251, + ("y", '"'): 255, +} diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/emacs_state.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/emacs_state.py new file mode 100644 index 0000000..4c99622 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/emacs_state.py @@ -0,0 +1,36 @@ +from typing import List, Optional + +from .key_processor import KeyPress + +__all__ = [ + "EmacsState", +] + + +class EmacsState: + """ + Mutable class to hold Emacs specific state. + """ + + def __init__(self) -> None: + # Simple macro recording. (Like Readline does.) + # (For Emacs mode.) + self.macro: Optional[List[KeyPress]] = [] + self.current_recording: Optional[List[KeyPress]] = None + + def reset(self) -> None: + self.current_recording = None + + @property + def is_recording(self) -> bool: + "Tell whether we are recording a macro." + return self.current_recording is not None + + def start_macro(self) -> None: + "Start recording macro." + self.current_recording = [] + + def end_macro(self) -> None: + "End recording macro." + self.macro = self.current_recording + self.current_recording = None diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/key_bindings.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/key_bindings.py new file mode 100644 index 0000000..03bc79e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/key_bindings.py @@ -0,0 +1,672 @@ +""" +Key bindings registry. + +A `KeyBindings` object is a container that holds a list of key bindings. It has a +very efficient internal data structure for checking which key bindings apply +for a pressed key. + +Typical usage:: + + kb = KeyBindings() + + @kb.add(Keys.ControlX, Keys.ControlC, filter=INSERT) + def handler(event): + # Handle ControlX-ControlC key sequence. + pass + +It is also possible to combine multiple KeyBindings objects. We do this in the +default key bindings. There are some KeyBindings objects that contain the Emacs +bindings, while others contain the Vi bindings. They are merged together using +`merge_key_bindings`. + +We also have a `ConditionalKeyBindings` object that can enable/disable a group of +key bindings at once. + + +It is also possible to add a filter to a function, before a key binding has +been assigned, through the `key_binding` decorator.:: + + # First define a key handler with the `filter`. + @key_binding(filter=condition) + def my_key_binding(event): + ... + + # Later, add it to the key bindings. + kb.add(Keys.A, my_key_binding) +""" +from abc import ABCMeta, abstractmethod, abstractproperty +from inspect import isawaitable +from typing import ( + TYPE_CHECKING, + Awaitable, + Callable, + Hashable, + List, + Optional, + Sequence, + Tuple, + TypeVar, + Union, + cast, +) + +from prompt_toolkit.cache import SimpleCache +from prompt_toolkit.filters import FilterOrBool, Never, to_filter +from prompt_toolkit.keys import KEY_ALIASES, Keys + +if TYPE_CHECKING: + # Avoid circular imports. + from .key_processor import KeyPressEvent + + # The only two return values for a mouse hander (and key bindings) are + # `None` and `NotImplemented`. For the type checker it's best to annotate + # this as `object`. (The consumer never expects a more specific instance: + # checking for NotImplemented can be done using `is NotImplemented`.) + NotImplementedOrNone = object + # Other non-working options are: + # * Optional[Literal[NotImplemented]] + # --> Doesn't work, Literal can't take an Any. + # * None + # --> Doesn't work. We can't assign the result of a function that + # returns `None` to a variable. + # * Any + # --> Works, but too broad. + + +__all__ = [ + "NotImplementedOrNone", + "Binding", + "KeyBindingsBase", + "KeyBindings", + "ConditionalKeyBindings", + "merge_key_bindings", + "DynamicKeyBindings", + "GlobalOnlyKeyBindings", +] + +# Key bindings can be regular functions or coroutines. +# In both cases, if they return `NotImplemented`, the UI won't be invalidated. +# This is mainly used in case of mouse move events, to prevent excessive +# repainting during mouse move events. +KeyHandlerCallable = Callable[ + ["KeyPressEvent"], Union["NotImplementedOrNone", Awaitable["NotImplementedOrNone"]] +] + + +class Binding: + """ + Key binding: (key sequence + handler + filter). + (Immutable binding class.) + + :param record_in_macro: When True, don't record this key binding when a + macro is recorded. + """ + + def __init__( + self, + keys: Tuple[Union[Keys, str], ...], + handler: KeyHandlerCallable, + filter: FilterOrBool = True, + eager: FilterOrBool = False, + is_global: FilterOrBool = False, + save_before: Callable[["KeyPressEvent"], bool] = (lambda e: True), + record_in_macro: FilterOrBool = True, + ) -> None: + self.keys = keys + self.handler = handler + self.filter = to_filter(filter) + self.eager = to_filter(eager) + self.is_global = to_filter(is_global) + self.save_before = save_before + self.record_in_macro = to_filter(record_in_macro) + + def call(self, event: "KeyPressEvent") -> None: + result = self.handler(event) + + # If the handler is a coroutine, create an asyncio task. + if isawaitable(result): + awaitable = cast(Awaitable["NotImplementedOrNone"], result) + + async def bg_task() -> None: + result = await awaitable + if result != NotImplemented: + event.app.invalidate() + + event.app.create_background_task(bg_task()) + + elif result != NotImplemented: + event.app.invalidate() + + def __repr__(self) -> str: + return "{}(keys={!r}, handler={!r})".format( + self.__class__.__name__, + self.keys, + self.handler, + ) + + +# Sequence of keys presses. +KeysTuple = Tuple[Union[Keys, str], ...] + + +class KeyBindingsBase(metaclass=ABCMeta): + """ + Interface for a KeyBindings. + """ + + @abstractproperty + def _version(self) -> Hashable: + """ + For cache invalidation. - This should increase every time that + something changes. + """ + return 0 + + @abstractmethod + def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + """ + Return a list of key bindings that can handle these keys. + (This return also inactive bindings, so the `filter` still has to be + called, for checking it.) + + :param keys: tuple of keys. + """ + return [] + + @abstractmethod + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + """ + Return a list of key bindings that handle a key sequence starting with + `keys`. (It does only return bindings for which the sequences are + longer than `keys`. And like `get_bindings_for_keys`, it also includes + inactive bindings.) + + :param keys: tuple of keys. + """ + return [] + + @abstractproperty + def bindings(self) -> List[Binding]: + """ + List of `Binding` objects. + (These need to be exposed, so that `KeyBindings` objects can be merged + together.) + """ + return [] + + # `add` and `remove` don't have to be part of this interface. + + +T = TypeVar("T", bound=Union[KeyHandlerCallable, Binding]) + + +class KeyBindings(KeyBindingsBase): + """ + A container for a set of key bindings. + + Example usage:: + + kb = KeyBindings() + + @kb.add('c-t') + def _(event): + print('Control-T pressed') + + @kb.add('c-a', 'c-b') + def _(event): + print('Control-A pressed, followed by Control-B') + + @kb.add('c-x', filter=is_searching) + def _(event): + print('Control-X pressed') # Works only if we are searching. + + """ + + def __init__(self) -> None: + self._bindings: List[Binding] = [] + self._get_bindings_for_keys_cache: SimpleCache[ + KeysTuple, List[Binding] + ] = SimpleCache(maxsize=10000) + self._get_bindings_starting_with_keys_cache: SimpleCache[ + KeysTuple, List[Binding] + ] = SimpleCache(maxsize=1000) + self.__version = 0 # For cache invalidation. + + def _clear_cache(self) -> None: + self.__version += 1 + self._get_bindings_for_keys_cache.clear() + self._get_bindings_starting_with_keys_cache.clear() + + @property + def bindings(self) -> List[Binding]: + return self._bindings + + @property + def _version(self) -> Hashable: + return self.__version + + def add( + self, + *keys: Union[Keys, str], + filter: FilterOrBool = True, + eager: FilterOrBool = False, + is_global: FilterOrBool = False, + save_before: Callable[["KeyPressEvent"], bool] = (lambda e: True), + record_in_macro: FilterOrBool = True, + ) -> Callable[[T], T]: + """ + Decorator for adding a key bindings. + + :param filter: :class:`~prompt_toolkit.filters.Filter` to determine + when this key binding is active. + :param eager: :class:`~prompt_toolkit.filters.Filter` or `bool`. + When True, ignore potential longer matches when this key binding is + hit. E.g. when there is an active eager key binding for Ctrl-X, + execute the handler immediately and ignore the key binding for + Ctrl-X Ctrl-E of which it is a prefix. + :param is_global: When this key bindings is added to a `Container` or + `Control`, make it a global (always active) binding. + :param save_before: Callable that takes an `Event` and returns True if + we should save the current buffer, before handling the event. + (That's the default.) + :param record_in_macro: Record these key bindings when a macro is + being recorded. (True by default.) + """ + assert keys + + keys = tuple(_parse_key(k) for k in keys) + + if isinstance(filter, Never): + # When a filter is Never, it will always stay disabled, so in that + # case don't bother putting it in the key bindings. It will slow + # down every key press otherwise. + def decorator(func: T) -> T: + return func + + else: + + def decorator(func: T) -> T: + if isinstance(func, Binding): + # We're adding an existing Binding object. + self.bindings.append( + Binding( + keys, + func.handler, + filter=func.filter & to_filter(filter), + eager=to_filter(eager) | func.eager, + is_global=to_filter(is_global) | func.is_global, + save_before=func.save_before, + record_in_macro=func.record_in_macro, + ) + ) + else: + self.bindings.append( + Binding( + keys, + cast(KeyHandlerCallable, func), + filter=filter, + eager=eager, + is_global=is_global, + save_before=save_before, + record_in_macro=record_in_macro, + ) + ) + self._clear_cache() + + return func + + return decorator + + def remove(self, *args: Union[Keys, str, KeyHandlerCallable]) -> None: + """ + Remove a key binding. + + This expects either a function that was given to `add` method as + parameter or a sequence of key bindings. + + Raises `ValueError` when no bindings was found. + + Usage:: + + remove(handler) # Pass handler. + remove('c-x', 'c-a') # Or pass the key bindings. + """ + found = False + + if callable(args[0]): + assert len(args) == 1 + function = args[0] + + # Remove the given function. + for b in self.bindings: + if b.handler == function: + self.bindings.remove(b) + found = True + + else: + assert len(args) > 0 + args = cast(Tuple[Union[Keys, str]], args) + + # Remove this sequence of key bindings. + keys = tuple(_parse_key(k) for k in args) + + for b in self.bindings: + if b.keys == keys: + self.bindings.remove(b) + found = True + + if found: + self._clear_cache() + else: + # No key binding found for this function. Raise ValueError. + raise ValueError(f"Binding not found: {function!r}") + + # For backwards-compatibility. + add_binding = add + remove_binding = remove + + def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + """ + Return a list of key bindings that can handle this key. + (This return also inactive bindings, so the `filter` still has to be + called, for checking it.) + + :param keys: tuple of keys. + """ + + def get() -> List[Binding]: + result: List[Tuple[int, Binding]] = [] + + for b in self.bindings: + if len(keys) == len(b.keys): + match = True + any_count = 0 + + for i, j in zip(b.keys, keys): + if i != j and i != Keys.Any: + match = False + break + + if i == Keys.Any: + any_count += 1 + + if match: + result.append((any_count, b)) + + # Place bindings that have more 'Any' occurrences in them at the end. + result = sorted(result, key=lambda item: -item[0]) + + return [item[1] for item in result] + + return self._get_bindings_for_keys_cache.get(keys, get) + + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + """ + Return a list of key bindings that handle a key sequence starting with + `keys`. (It does only return bindings for which the sequences are + longer than `keys`. And like `get_bindings_for_keys`, it also includes + inactive bindings.) + + :param keys: tuple of keys. + """ + + def get() -> List[Binding]: + result = [] + for b in self.bindings: + if len(keys) < len(b.keys): + match = True + for i, j in zip(b.keys, keys): + if i != j and i != Keys.Any: + match = False + break + if match: + result.append(b) + return result + + return self._get_bindings_starting_with_keys_cache.get(keys, get) + + +def _parse_key(key: Union[Keys, str]) -> Union[str, Keys]: + """ + Replace key by alias and verify whether it's a valid one. + """ + # Already a parse key? -> Return it. + if isinstance(key, Keys): + return key + + # Lookup aliases. + key = KEY_ALIASES.get(key, key) + + # Replace 'space' by ' ' + if key == "space": + key = " " + + # Return as `Key` object when it's a special key. + try: + return Keys(key) + except ValueError: + pass + + # Final validation. + if len(key) != 1: + raise ValueError(f"Invalid key: {key}") + + return key + + +def key_binding( + filter: FilterOrBool = True, + eager: FilterOrBool = False, + is_global: FilterOrBool = False, + save_before: Callable[["KeyPressEvent"], bool] = (lambda event: True), + record_in_macro: FilterOrBool = True, +) -> Callable[[KeyHandlerCallable], Binding]: + """ + Decorator that turn a function into a `Binding` object. This can be added + to a `KeyBindings` object when a key binding is assigned. + """ + assert save_before is None or callable(save_before) + + filter = to_filter(filter) + eager = to_filter(eager) + is_global = to_filter(is_global) + save_before = save_before + record_in_macro = to_filter(record_in_macro) + keys = () + + def decorator(function: KeyHandlerCallable) -> Binding: + return Binding( + keys, + function, + filter=filter, + eager=eager, + is_global=is_global, + save_before=save_before, + record_in_macro=record_in_macro, + ) + + return decorator + + +class _Proxy(KeyBindingsBase): + """ + Common part for ConditionalKeyBindings and _MergedKeyBindings. + """ + + def __init__(self) -> None: + # `KeyBindings` to be synchronized with all the others. + self._bindings2: KeyBindingsBase = KeyBindings() + self._last_version: Hashable = () + + def _update_cache(self) -> None: + """ + If `self._last_version` is outdated, then this should update + the version and `self._bindings2`. + """ + raise NotImplementedError + + # Proxy methods to self._bindings2. + + @property + def bindings(self) -> List[Binding]: + self._update_cache() + return self._bindings2.bindings + + @property + def _version(self) -> Hashable: + self._update_cache() + return self._last_version + + def get_bindings_for_keys(self, keys: KeysTuple) -> List[Binding]: + self._update_cache() + return self._bindings2.get_bindings_for_keys(keys) + + def get_bindings_starting_with_keys(self, keys: KeysTuple) -> List[Binding]: + self._update_cache() + return self._bindings2.get_bindings_starting_with_keys(keys) + + +class ConditionalKeyBindings(_Proxy): + """ + Wraps around a `KeyBindings`. Disable/enable all the key bindings according to + the given (additional) filter.:: + + @Condition + def setting_is_true(): + return True # or False + + registry = ConditionalKeyBindings(key_bindings, setting_is_true) + + When new key bindings are added to this object. They are also + enable/disabled according to the given `filter`. + + :param registries: List of :class:`.KeyBindings` objects. + :param filter: :class:`~prompt_toolkit.filters.Filter` object. + """ + + def __init__( + self, key_bindings: KeyBindingsBase, filter: FilterOrBool = True + ) -> None: + + _Proxy.__init__(self) + + self.key_bindings = key_bindings + self.filter = to_filter(filter) + + def _update_cache(self) -> None: + "If the original key bindings was changed. Update our copy version." + expected_version = self.key_bindings._version + + if self._last_version != expected_version: + bindings2 = KeyBindings() + + # Copy all bindings from `self.key_bindings`, adding our condition. + for b in self.key_bindings.bindings: + bindings2.bindings.append( + Binding( + keys=b.keys, + handler=b.handler, + filter=self.filter & b.filter, + eager=b.eager, + is_global=b.is_global, + save_before=b.save_before, + record_in_macro=b.record_in_macro, + ) + ) + + self._bindings2 = bindings2 + self._last_version = expected_version + + +class _MergedKeyBindings(_Proxy): + """ + Merge multiple registries of key bindings into one. + + This class acts as a proxy to multiple :class:`.KeyBindings` objects, but + behaves as if this is just one bigger :class:`.KeyBindings`. + + :param registries: List of :class:`.KeyBindings` objects. + """ + + def __init__(self, registries: Sequence[KeyBindingsBase]) -> None: + _Proxy.__init__(self) + self.registries = registries + + def _update_cache(self) -> None: + """ + If one of the original registries was changed. Update our merged + version. + """ + expected_version = tuple(r._version for r in self.registries) + + if self._last_version != expected_version: + bindings2 = KeyBindings() + + for reg in self.registries: + bindings2.bindings.extend(reg.bindings) + + self._bindings2 = bindings2 + self._last_version = expected_version + + +def merge_key_bindings(bindings: Sequence[KeyBindingsBase]) -> _MergedKeyBindings: + """ + Merge multiple :class:`.Keybinding` objects together. + + Usage:: + + bindings = merge_key_bindings([bindings1, bindings2, ...]) + """ + return _MergedKeyBindings(bindings) + + +class DynamicKeyBindings(_Proxy): + """ + KeyBindings class that can dynamically returns any KeyBindings. + + :param get_key_bindings: Callable that returns a :class:`.KeyBindings` instance. + """ + + def __init__( + self, get_key_bindings: Callable[[], Optional[KeyBindingsBase]] + ) -> None: + self.get_key_bindings = get_key_bindings + self.__version = 0 + self._last_child_version = None + self._dummy = KeyBindings() # Empty key bindings. + + def _update_cache(self) -> None: + key_bindings = self.get_key_bindings() or self._dummy + assert isinstance(key_bindings, KeyBindingsBase) + version = id(key_bindings), key_bindings._version + + self._bindings2 = key_bindings + self._last_version = version + + +class GlobalOnlyKeyBindings(_Proxy): + """ + Wrapper around a :class:`.KeyBindings` object that only exposes the global + key bindings. + """ + + def __init__(self, key_bindings: KeyBindingsBase) -> None: + _Proxy.__init__(self) + self.key_bindings = key_bindings + + def _update_cache(self) -> None: + """ + If one of the original registries was changed. Update our merged + version. + """ + expected_version = self.key_bindings._version + + if self._last_version != expected_version: + bindings2 = KeyBindings() + + for b in self.key_bindings.bindings: + if b.is_global(): + bindings2.bindings.append(b) + + self._bindings2 = bindings2 + self._last_version = expected_version diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/key_processor.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/key_processor.py new file mode 100644 index 0000000..6fdd519 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/key_processor.py @@ -0,0 +1,528 @@ +""" +An :class:`~.KeyProcessor` receives callbacks for the keystrokes parsed from +the input in the :class:`~prompt_toolkit.inputstream.InputStream` instance. + +The `KeyProcessor` will according to the implemented keybindings call the +correct callbacks when new key presses are feed through `feed`. +""" +import weakref +from asyncio import Task, sleep +from collections import deque +from typing import TYPE_CHECKING, Any, Deque, Generator, List, Optional, Union + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.filters.app import vi_navigation_mode +from prompt_toolkit.keys import Keys +from prompt_toolkit.utils import Event + +from .key_bindings import Binding, KeyBindingsBase + +if TYPE_CHECKING: + from prompt_toolkit.application import Application + from prompt_toolkit.buffer import Buffer + + +__all__ = [ + "KeyProcessor", + "KeyPress", + "KeyPressEvent", +] + + +class KeyPress: + """ + :param key: A `Keys` instance or text (one character). + :param data: The received string on stdin. (Often vt100 escape codes.) + """ + + def __init__(self, key: Union[Keys, str], data: Optional[str] = None) -> None: + assert isinstance(key, Keys) or len(key) == 1 + + if data is None: + if isinstance(key, Keys): + data = key.value + else: + data = key # 'key' is a one character string. + + self.key = key + self.data = data + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(key={self.key!r}, data={self.data!r})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, KeyPress): + return False + return self.key == other.key and self.data == other.data + + +""" +Helper object to indicate flush operation in the KeyProcessor. +NOTE: the implementation is very similar to the VT100 parser. +""" +_Flush = KeyPress("?", data="_Flush") + + +class KeyProcessor: + """ + Statemachine that receives :class:`KeyPress` instances and according to the + key bindings in the given :class:`KeyBindings`, calls the matching handlers. + + :: + + p = KeyProcessor(key_bindings) + + # Send keys into the processor. + p.feed(KeyPress(Keys.ControlX, '\x18')) + p.feed(KeyPress(Keys.ControlC, '\x03') + + # Process all the keys in the queue. + p.process_keys() + + # Now the ControlX-ControlC callback will be called if this sequence is + # registered in the key bindings. + + :param key_bindings: `KeyBindingsBase` instance. + """ + + def __init__(self, key_bindings: KeyBindingsBase) -> None: + self._bindings = key_bindings + + self.before_key_press = Event(self) + self.after_key_press = Event(self) + + self._flush_wait_task: Optional[Task[None]] = None + + self.reset() + + def reset(self) -> None: + self._previous_key_sequence: List[KeyPress] = [] + self._previous_handler: Optional[Binding] = None + + # The queue of keys not yet send to our _process generator/state machine. + self.input_queue: Deque[KeyPress] = deque() + + # The key buffer that is matched in the generator state machine. + # (This is at at most the amount of keys that make up for one key binding.) + self.key_buffer: List[KeyPress] = [] + + #: Readline argument (for repetition of commands.) + #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html + self.arg: Optional[str] = None + + # Start the processor coroutine. + self._process_coroutine = self._process() + self._process_coroutine.send(None) # type: ignore + + def _get_matches(self, key_presses: List[KeyPress]) -> List[Binding]: + """ + For a list of :class:`KeyPress` instances. Give the matching handlers + that would handle this. + """ + keys = tuple(k.key for k in key_presses) + + # Try match, with mode flag + return [b for b in self._bindings.get_bindings_for_keys(keys) if b.filter()] + + def _is_prefix_of_longer_match(self, key_presses: List[KeyPress]) -> bool: + """ + For a list of :class:`KeyPress` instances. Return True if there is any + handler that is bound to a suffix of this keys. + """ + keys = tuple(k.key for k in key_presses) + + # Get the filters for all the key bindings that have a longer match. + # Note that we transform it into a `set`, because we don't care about + # the actual bindings and executing it more than once doesn't make + # sense. (Many key bindings share the same filter.) + filters = { + b.filter for b in self._bindings.get_bindings_starting_with_keys(keys) + } + + # When any key binding is active, return True. + return any(f() for f in filters) + + def _process(self) -> Generator[None, KeyPress, None]: + """ + Coroutine implementing the key match algorithm. Key strokes are sent + into this generator, and it calls the appropriate handlers. + """ + buffer = self.key_buffer + retry = False + + while True: + flush = False + + if retry: + retry = False + else: + key = yield + if key is _Flush: + flush = True + else: + buffer.append(key) + + # If we have some key presses, check for matches. + if buffer: + matches = self._get_matches(buffer) + + if flush: + is_prefix_of_longer_match = False + else: + is_prefix_of_longer_match = self._is_prefix_of_longer_match(buffer) + + # When eager matches were found, give priority to them and also + # ignore all the longer matches. + eager_matches = [m for m in matches if m.eager()] + + if eager_matches: + matches = eager_matches + is_prefix_of_longer_match = False + + # Exact matches found, call handler. + if not is_prefix_of_longer_match and matches: + self._call_handler(matches[-1], key_sequence=buffer[:]) + del buffer[:] # Keep reference. + + # No match found. + elif not is_prefix_of_longer_match and not matches: + retry = True + found = False + + # Loop over the input, try longest match first and shift. + for i in range(len(buffer), 0, -1): + matches = self._get_matches(buffer[:i]) + if matches: + self._call_handler(matches[-1], key_sequence=buffer[:i]) + del buffer[:i] + found = True + break + + if not found: + del buffer[:1] + + def feed(self, key_press: KeyPress, first: bool = False) -> None: + """ + Add a new :class:`KeyPress` to the input queue. + (Don't forget to call `process_keys` in order to process the queue.) + + :param first: If true, insert before everything else. + """ + if first: + self.input_queue.appendleft(key_press) + else: + self.input_queue.append(key_press) + + def feed_multiple(self, key_presses: List[KeyPress], first: bool = False) -> None: + """ + :param first: If true, insert before everything else. + """ + if first: + self.input_queue.extendleft(reversed(key_presses)) + else: + self.input_queue.extend(key_presses) + + def process_keys(self) -> None: + """ + Process all the keys in the `input_queue`. + (To be called after `feed`.) + + Note: because of the `feed`/`process_keys` separation, it is + possible to call `feed` from inside a key binding. + This function keeps looping until the queue is empty. + """ + app = get_app() + + def not_empty() -> bool: + # When the application result is set, stop processing keys. (E.g. + # if ENTER was received, followed by a few additional key strokes, + # leave the other keys in the queue.) + if app.is_done: + # But if there are still CPRResponse keys in the queue, these + # need to be processed. + return any(k for k in self.input_queue if k.key == Keys.CPRResponse) + else: + return bool(self.input_queue) + + def get_next() -> KeyPress: + if app.is_done: + # Only process CPR responses. Everything else is typeahead. + cpr = [k for k in self.input_queue if k.key == Keys.CPRResponse][0] + self.input_queue.remove(cpr) + return cpr + else: + return self.input_queue.popleft() + + is_flush = False + + while not_empty(): + # Process next key. + key_press = get_next() + + is_flush = key_press is _Flush + is_cpr = key_press.key == Keys.CPRResponse + + if not is_flush and not is_cpr: + self.before_key_press.fire() + + try: + self._process_coroutine.send(key_press) + except Exception: + # If for some reason something goes wrong in the parser, (maybe + # an exception was raised) restart the processor for next time. + self.reset() + self.empty_queue() + raise + + if not is_flush and not is_cpr: + self.after_key_press.fire() + + # Skip timeout if the last key was flush. + if not is_flush: + self._start_timeout() + + def empty_queue(self) -> List[KeyPress]: + """ + Empty the input queue. Return the unprocessed input. + """ + key_presses = list(self.input_queue) + self.input_queue.clear() + + # Filter out CPRs. We don't want to return these. + key_presses = [k for k in key_presses if k.key != Keys.CPRResponse] + return key_presses + + def _call_handler(self, handler: Binding, key_sequence: List[KeyPress]) -> None: + app = get_app() + was_recording_emacs = app.emacs_state.is_recording + was_recording_vi = bool(app.vi_state.recording_register) + was_temporary_navigation_mode = app.vi_state.temporary_navigation_mode + arg = self.arg + self.arg = None + + event = KeyPressEvent( + weakref.ref(self), + arg=arg, + key_sequence=key_sequence, + previous_key_sequence=self._previous_key_sequence, + is_repeat=(handler == self._previous_handler), + ) + + # Save the state of the current buffer. + if handler.save_before(event): + event.app.current_buffer.save_to_undo_stack() + + # Call handler. + from prompt_toolkit.buffer import EditReadOnlyBuffer + + try: + handler.call(event) + self._fix_vi_cursor_position(event) + + except EditReadOnlyBuffer: + # When a key binding does an attempt to change a buffer which is + # read-only, we can ignore that. We sound a bell and go on. + app.output.bell() + + if was_temporary_navigation_mode: + self._leave_vi_temp_navigation_mode(event) + + self._previous_key_sequence = key_sequence + self._previous_handler = handler + + # Record the key sequence in our macro. (Only if we're in macro mode + # before and after executing the key.) + if handler.record_in_macro(): + if app.emacs_state.is_recording and was_recording_emacs: + recording = app.emacs_state.current_recording + if recording is not None: # Should always be true, given that + # `was_recording_emacs` is set. + recording.extend(key_sequence) + + if app.vi_state.recording_register and was_recording_vi: + for k in key_sequence: + app.vi_state.current_recording += k.data + + def _fix_vi_cursor_position(self, event: "KeyPressEvent") -> None: + """ + After every command, make sure that if we are in Vi navigation mode, we + never put the cursor after the last character of a line. (Unless it's + an empty line.) + """ + app = event.app + buff = app.current_buffer + preferred_column = buff.preferred_column + + if ( + vi_navigation_mode() + and buff.document.is_cursor_at_the_end_of_line + and len(buff.document.current_line) > 0 + ): + buff.cursor_position -= 1 + + # Set the preferred_column for arrow up/down again. + # (This was cleared after changing the cursor position.) + buff.preferred_column = preferred_column + + def _leave_vi_temp_navigation_mode(self, event: "KeyPressEvent") -> None: + """ + If we're in Vi temporary navigation (normal) mode, return to + insert/replace mode after executing one action. + """ + app = event.app + + if app.editing_mode == EditingMode.VI: + # Not waiting for a text object and no argument has been given. + if app.vi_state.operator_func is None and self.arg is None: + app.vi_state.temporary_navigation_mode = False + + def _start_timeout(self) -> None: + """ + Start auto flush timeout. Similar to Vim's `timeoutlen` option. + + Start a background coroutine with a timer. When this timeout expires + and no key was pressed in the meantime, we flush all data in the queue + and call the appropriate key binding handlers. + """ + app = get_app() + timeout = app.timeoutlen + + if timeout is None: + return + + async def wait() -> None: + "Wait for timeout." + # This sleep can be cancelled. In that case we don't flush. + await sleep(timeout) + + if len(self.key_buffer) > 0: + # (No keys pressed in the meantime.) + flush_keys() + + def flush_keys() -> None: + "Flush keys." + self.feed(_Flush) + self.process_keys() + + # Automatically flush keys. + if self._flush_wait_task: + self._flush_wait_task.cancel() + self._flush_wait_task = app.create_background_task(wait()) + + def send_sigint(self) -> None: + """ + Send SIGINT. Immediately call the SIGINT key handler. + """ + self.feed(KeyPress(key=Keys.SIGINT), first=True) + self.process_keys() + + +class KeyPressEvent: + """ + Key press event, delivered to key bindings. + + :param key_processor_ref: Weak reference to the `KeyProcessor`. + :param arg: Repetition argument. + :param key_sequence: List of `KeyPress` instances. + :param previouskey_sequence: Previous list of `KeyPress` instances. + :param is_repeat: True when the previous event was delivered to the same handler. + """ + + def __init__( + self, + key_processor_ref: "weakref.ReferenceType[KeyProcessor]", + arg: Optional[str], + key_sequence: List[KeyPress], + previous_key_sequence: List[KeyPress], + is_repeat: bool, + ) -> None: + + self._key_processor_ref = key_processor_ref + self.key_sequence = key_sequence + self.previous_key_sequence = previous_key_sequence + + #: True when the previous key sequence was handled by the same handler. + self.is_repeat = is_repeat + + self._arg = arg + self._app = get_app() + + def __repr__(self) -> str: + return "KeyPressEvent(arg={!r}, key_sequence={!r}, is_repeat={!r})".format( + self.arg, + self.key_sequence, + self.is_repeat, + ) + + @property + def data(self) -> str: + return self.key_sequence[-1].data + + @property + def key_processor(self) -> KeyProcessor: + processor = self._key_processor_ref() + if processor is None: + raise Exception("KeyProcessor was lost. This should not happen.") + return processor + + @property + def app(self) -> "Application[Any]": + """ + The current `Application` object. + """ + return self._app + + @property + def current_buffer(self) -> "Buffer": + """ + The current buffer. + """ + return self.app.current_buffer + + @property + def arg(self) -> int: + """ + Repetition argument. + """ + if self._arg == "-": + return -1 + + result = int(self._arg or 1) + + # Don't exceed a million. + if int(result) >= 1000000: + result = 1 + + return result + + @property + def arg_present(self) -> bool: + """ + True if repetition argument was explicitly provided. + """ + return self._arg is not None + + def append_to_arg_count(self, data: str) -> None: + """ + Add digit to the input argument. + + :param data: the typed digit as string + """ + assert data in "-0123456789" + current = self._arg + + if data == "-": + assert current is None or current == "-" + result = data + elif current is None: + result = data + else: + result = f"{current}{data}" + + self.key_processor.arg = result + + @property + def cli(self) -> "Application[Any]": + "For backward-compatibility." + return self.app diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/vi_state.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/vi_state.py new file mode 100644 index 0000000..10593a8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/key_binding/vi_state.py @@ -0,0 +1,107 @@ +from enum import Enum +from typing import TYPE_CHECKING, Callable, Dict, Optional + +from prompt_toolkit.clipboard import ClipboardData + +if TYPE_CHECKING: + from .key_bindings.vi import TextObject + from .key_processor import KeyPressEvent + +__all__ = [ + "InputMode", + "CharacterFind", + "ViState", +] + + +class InputMode(str, Enum): + value: str + + INSERT = "vi-insert" + INSERT_MULTIPLE = "vi-insert-multiple" + NAVIGATION = "vi-navigation" # Normal mode. + REPLACE = "vi-replace" + REPLACE_SINGLE = "vi-replace-single" + + +class CharacterFind: + def __init__(self, character: str, backwards: bool = False) -> None: + self.character = character + self.backwards = backwards + + +class ViState: + """ + Mutable class to hold the state of the Vi navigation. + """ + + def __init__(self) -> None: + #: None or CharacterFind instance. (This is used to repeat the last + #: search in Vi mode, by pressing the 'n' or 'N' in navigation mode.) + self.last_character_find: Optional[CharacterFind] = None + + # When an operator is given and we are waiting for text object, + # -- e.g. in the case of 'dw', after the 'd' --, an operator callback + # is set here. + self.operator_func: Optional[ + Callable[["KeyPressEvent", "TextObject"], None] + ] = None + self.operator_arg: Optional[int] = None + + #: Named registers. Maps register name (e.g. 'a') to + #: :class:`ClipboardData` instances. + self.named_registers: Dict[str, ClipboardData] = {} + + #: The Vi mode we're currently in to. + self.__input_mode = InputMode.INSERT + + #: Waiting for digraph. + self.waiting_for_digraph = False + self.digraph_symbol1: Optional[str] = None # (None or a symbol.) + + #: When true, make ~ act as an operator. + self.tilde_operator = False + + #: Register in which we are recording a macro. + #: `None` when not recording anything. + # Note that the recording is only stored in the register after the + # recording is stopped. So we record in a separate `current_recording` + # variable. + self.recording_register: Optional[str] = None + self.current_recording: str = "" + + # Temporary navigation (normal) mode. + # This happens when control-o has been pressed in insert or replace + # mode. The user can now do one navigation action and we'll return back + # to insert/replace. + self.temporary_navigation_mode = False + + @property + def input_mode(self) -> InputMode: + "Get `InputMode`." + return self.__input_mode + + @input_mode.setter + def input_mode(self, value: InputMode) -> None: + "Set `InputMode`." + if value == InputMode.NAVIGATION: + self.waiting_for_digraph = False + self.operator_func = None + self.operator_arg = None + + self.__input_mode = value + + def reset(self) -> None: + """ + Reset state, go back to the given mode. INSERT by default. + """ + # Go back to insert mode. + self.input_mode = InputMode.INSERT + + self.waiting_for_digraph = False + self.operator_func = None + self.operator_arg = None + + # Reset recording state. + self.recording_register = None + self.current_recording = "" diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/keys.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/keys.py new file mode 100644 index 0000000..e10ba9d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/keys.py @@ -0,0 +1,221 @@ +from enum import Enum +from typing import Dict, List + +__all__ = [ + "Keys", + "ALL_KEYS", +] + + +class Keys(str, Enum): + """ + List of keys for use in key bindings. + + Note that this is an "StrEnum", all values can be compared against + strings. + """ + + value: str + + Escape = "escape" # Also Control-[ + ShiftEscape = "s-escape" + + ControlAt = "c-@" # Also Control-Space. + + ControlA = "c-a" + ControlB = "c-b" + ControlC = "c-c" + ControlD = "c-d" + ControlE = "c-e" + ControlF = "c-f" + ControlG = "c-g" + ControlH = "c-h" + ControlI = "c-i" # Tab + ControlJ = "c-j" # Newline + ControlK = "c-k" + ControlL = "c-l" + ControlM = "c-m" # Carriage return + ControlN = "c-n" + ControlO = "c-o" + ControlP = "c-p" + ControlQ = "c-q" + ControlR = "c-r" + ControlS = "c-s" + ControlT = "c-t" + ControlU = "c-u" + ControlV = "c-v" + ControlW = "c-w" + ControlX = "c-x" + ControlY = "c-y" + ControlZ = "c-z" + + Control1 = "c-1" + Control2 = "c-2" + Control3 = "c-3" + Control4 = "c-4" + Control5 = "c-5" + Control6 = "c-6" + Control7 = "c-7" + Control8 = "c-8" + Control9 = "c-9" + Control0 = "c-0" + + ControlShift1 = "c-s-1" + ControlShift2 = "c-s-2" + ControlShift3 = "c-s-3" + ControlShift4 = "c-s-4" + ControlShift5 = "c-s-5" + ControlShift6 = "c-s-6" + ControlShift7 = "c-s-7" + ControlShift8 = "c-s-8" + ControlShift9 = "c-s-9" + ControlShift0 = "c-s-0" + + ControlBackslash = "c-\\" + ControlSquareClose = "c-]" + ControlCircumflex = "c-^" + ControlUnderscore = "c-_" + + Left = "left" + Right = "right" + Up = "up" + Down = "down" + Home = "home" + End = "end" + Insert = "insert" + Delete = "delete" + PageUp = "pageup" + PageDown = "pagedown" + + ControlLeft = "c-left" + ControlRight = "c-right" + ControlUp = "c-up" + ControlDown = "c-down" + ControlHome = "c-home" + ControlEnd = "c-end" + ControlInsert = "c-insert" + ControlDelete = "c-delete" + ControlPageUp = "c-pageup" + ControlPageDown = "c-pagedown" + + ShiftLeft = "s-left" + ShiftRight = "s-right" + ShiftUp = "s-up" + ShiftDown = "s-down" + ShiftHome = "s-home" + ShiftEnd = "s-end" + ShiftInsert = "s-insert" + ShiftDelete = "s-delete" + ShiftPageUp = "s-pageup" + ShiftPageDown = "s-pagedown" + + ControlShiftLeft = "c-s-left" + ControlShiftRight = "c-s-right" + ControlShiftUp = "c-s-up" + ControlShiftDown = "c-s-down" + ControlShiftHome = "c-s-home" + ControlShiftEnd = "c-s-end" + ControlShiftInsert = "c-s-insert" + ControlShiftDelete = "c-s-delete" + ControlShiftPageUp = "c-s-pageup" + ControlShiftPageDown = "c-s-pagedown" + + BackTab = "s-tab" # shift + tab + + F1 = "f1" + F2 = "f2" + F3 = "f3" + F4 = "f4" + F5 = "f5" + F6 = "f6" + F7 = "f7" + F8 = "f8" + F9 = "f9" + F10 = "f10" + F11 = "f11" + F12 = "f12" + F13 = "f13" + F14 = "f14" + F15 = "f15" + F16 = "f16" + F17 = "f17" + F18 = "f18" + F19 = "f19" + F20 = "f20" + F21 = "f21" + F22 = "f22" + F23 = "f23" + F24 = "f24" + + ControlF1 = "c-f1" + ControlF2 = "c-f2" + ControlF3 = "c-f3" + ControlF4 = "c-f4" + ControlF5 = "c-f5" + ControlF6 = "c-f6" + ControlF7 = "c-f7" + ControlF8 = "c-f8" + ControlF9 = "c-f9" + ControlF10 = "c-f10" + ControlF11 = "c-f11" + ControlF12 = "c-f12" + ControlF13 = "c-f13" + ControlF14 = "c-f14" + ControlF15 = "c-f15" + ControlF16 = "c-f16" + ControlF17 = "c-f17" + ControlF18 = "c-f18" + ControlF19 = "c-f19" + ControlF20 = "c-f20" + ControlF21 = "c-f21" + ControlF22 = "c-f22" + ControlF23 = "c-f23" + ControlF24 = "c-f24" + + # Matches any key. + Any = "" + + # Special. + ScrollUp = "" + ScrollDown = "" + + CPRResponse = "" + Vt100MouseEvent = "" + WindowsMouseEvent = "" + BracketedPaste = "" + + SIGINT = "" + + # For internal use: key which is ignored. + # (The key binding for this key should not do anything.) + Ignore = "" + + # Some 'Key' aliases (for backwards-compatibility). + ControlSpace = ControlAt + Tab = ControlI + Enter = ControlM + Backspace = ControlH + + # ShiftControl was renamed to ControlShift in + # 888fcb6fa4efea0de8333177e1bbc792f3ff3c24 (20 Feb 2020). + ShiftControlLeft = ControlShiftLeft + ShiftControlRight = ControlShiftRight + ShiftControlHome = ControlShiftHome + ShiftControlEnd = ControlShiftEnd + + +ALL_KEYS: List[str] = [k.value for k in Keys] + + +# Aliases. +KEY_ALIASES: Dict[str, str] = { + "backspace": "c-h", + "c-space": "c-@", + "enter": "c-m", + "tab": "c-i", + # ShiftControl was renamed to ControlShift. + "s-c-left": "c-s-left", + "s-c-right": "c-s-right", + "s-c-home": "c-s-home", + "s-c-end": "c-s-end", +} diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__init__.py new file mode 100644 index 0000000..6669da5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__init__.py @@ -0,0 +1,144 @@ +""" +Command line layout definitions +------------------------------- + +The layout of a command line interface is defined by a Container instance. +There are two main groups of classes here. Containers and controls: + +- A container can contain other containers or controls, it can have multiple + children and it decides about the dimensions. +- A control is responsible for rendering the actual content to a screen. + A control can propose some dimensions, but it's the container who decides + about the dimensions -- or when the control consumes more space -- which part + of the control will be visible. + + +Container classes:: + + - Container (Abstract base class) + |- HSplit (Horizontal split) + |- VSplit (Vertical split) + |- FloatContainer (Container which can also contain menus and other floats) + `- Window (Container which contains one actual control + +Control classes:: + + - UIControl (Abstract base class) + |- FormattedTextControl (Renders formatted text, or a simple list of text fragments) + `- BufferControl (Renders an input buffer.) + + +Usually, you end up wrapping every control inside a `Window` object, because +that's the only way to render it in a layout. + +There are some prepared toolbars which are ready to use:: + +- SystemToolbar (Shows the 'system' input buffer, for entering system commands.) +- ArgToolbar (Shows the input 'arg', for repetition of input commands.) +- SearchToolbar (Shows the 'search' input buffer, for incremental search.) +- CompletionsToolbar (Shows the completions of the current buffer.) +- ValidationToolbar (Shows validation errors of the current buffer.) + +And one prepared menu: + +- CompletionsMenu + +""" +from .containers import ( + AnyContainer, + ColorColumn, + ConditionalContainer, + Container, + DynamicContainer, + Float, + FloatContainer, + HorizontalAlign, + HSplit, + ScrollOffsets, + VerticalAlign, + VSplit, + Window, + WindowAlign, + WindowRenderInfo, + is_container, + to_container, + to_window, +) +from .controls import ( + BufferControl, + DummyControl, + FormattedTextControl, + SearchBufferControl, + UIContent, + UIControl, +) +from .dimension import ( + AnyDimension, + D, + Dimension, + is_dimension, + max_layout_dimensions, + sum_layout_dimensions, + to_dimension, +) +from .layout import InvalidLayoutError, Layout, walk +from .margins import ( + ConditionalMargin, + Margin, + NumberedMargin, + PromptMargin, + ScrollbarMargin, +) +from .menus import CompletionsMenu, MultiColumnCompletionsMenu +from .scrollable_pane import ScrollablePane + +__all__ = [ + # Layout. + "Layout", + "InvalidLayoutError", + "walk", + # Dimensions. + "AnyDimension", + "Dimension", + "D", + "sum_layout_dimensions", + "max_layout_dimensions", + "to_dimension", + "is_dimension", + # Containers. + "AnyContainer", + "Container", + "HorizontalAlign", + "VerticalAlign", + "HSplit", + "VSplit", + "FloatContainer", + "Float", + "WindowAlign", + "Window", + "WindowRenderInfo", + "ConditionalContainer", + "ScrollOffsets", + "ColorColumn", + "to_container", + "to_window", + "is_container", + "DynamicContainer", + "ScrollablePane", + # Controls. + "BufferControl", + "SearchBufferControl", + "DummyControl", + "FormattedTextControl", + "UIControl", + "UIContent", + # Margins. + "Margin", + "NumberedMargin", + "ScrollbarMargin", + "ConditionalMargin", + "PromptMargin", + # Menus. + "CompletionsMenu", + "MultiColumnCompletionsMenu", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..73ee8d4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/containers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/containers.cpython-38.pyc new file mode 100644 index 0000000..634dcf9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/containers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/controls.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/controls.cpython-38.pyc new file mode 100644 index 0000000..ae38068 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/controls.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/dimension.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/dimension.cpython-38.pyc new file mode 100644 index 0000000..4712b23 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/dimension.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/dummy.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/dummy.cpython-38.pyc new file mode 100644 index 0000000..a5e00e9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/dummy.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/layout.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/layout.cpython-38.pyc new file mode 100644 index 0000000..2e278b5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/layout.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/margins.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/margins.cpython-38.pyc new file mode 100644 index 0000000..64c1260 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/margins.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/menus.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/menus.cpython-38.pyc new file mode 100644 index 0000000..e66ea3e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/menus.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/mouse_handlers.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/mouse_handlers.cpython-38.pyc new file mode 100644 index 0000000..8c2c885 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/mouse_handlers.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/processors.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/processors.cpython-38.pyc new file mode 100644 index 0000000..62c31dc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/processors.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/screen.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/screen.cpython-38.pyc new file mode 100644 index 0000000..a786a56 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/screen.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/scrollable_pane.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/scrollable_pane.cpython-38.pyc new file mode 100644 index 0000000..64785a0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/scrollable_pane.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..7ba905e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/containers.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/containers.py new file mode 100644 index 0000000..03f9e7d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/containers.py @@ -0,0 +1,2757 @@ +""" +Container for the layout. +(Containers can contain other containers or user interface controls.) +""" +from abc import ABCMeta, abstractmethod +from enum import Enum +from functools import partial +from typing import ( + TYPE_CHECKING, + Callable, + Dict, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.cache import SimpleCache +from prompt_toolkit.data_structures import Point +from prompt_toolkit.filters import ( + FilterOrBool, + emacs_insert_mode, + to_filter, + vi_insert_mode, +) +from prompt_toolkit.formatted_text import ( + AnyFormattedText, + StyleAndTextTuples, + to_formatted_text, +) +from prompt_toolkit.formatted_text.utils import ( + fragment_list_to_text, + fragment_list_width, +) +from prompt_toolkit.key_binding import KeyBindingsBase +from prompt_toolkit.mouse_events import MouseEvent, MouseEventType +from prompt_toolkit.utils import get_cwidth, take_using_weights, to_int, to_str + +from .controls import ( + DummyControl, + FormattedTextControl, + GetLinePrefixCallable, + UIContent, + UIControl, +) +from .dimension import ( + AnyDimension, + Dimension, + max_layout_dimensions, + sum_layout_dimensions, + to_dimension, +) +from .margins import Margin +from .mouse_handlers import MouseHandlers +from .screen import _CHAR_CACHE, Screen, WritePosition +from .utils import explode_text_fragments + +if TYPE_CHECKING: + from typing_extensions import Protocol, TypeGuard + + from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone + + +__all__ = [ + "AnyContainer", + "Container", + "HorizontalAlign", + "VerticalAlign", + "HSplit", + "VSplit", + "FloatContainer", + "Float", + "WindowAlign", + "Window", + "WindowRenderInfo", + "ConditionalContainer", + "ScrollOffsets", + "ColorColumn", + "to_container", + "to_window", + "is_container", + "DynamicContainer", +] + + +class Container(metaclass=ABCMeta): + """ + Base class for user interface layout. + """ + + @abstractmethod + def reset(self) -> None: + """ + Reset the state of this container and all the children. + (E.g. reset scroll offsets, etc...) + """ + + @abstractmethod + def preferred_width(self, max_available_width: int) -> Dimension: + """ + Return a :class:`~prompt_toolkit.layout.Dimension` that represents the + desired width for this container. + """ + + @abstractmethod + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + """ + Return a :class:`~prompt_toolkit.layout.Dimension` that represents the + desired height for this container. + """ + + @abstractmethod + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + """ + Write the actual content to the screen. + + :param screen: :class:`~prompt_toolkit.layout.screen.Screen` + :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`. + :param parent_style: Style string to pass to the :class:`.Window` + object. This will be applied to all content of the windows. + :class:`.VSplit` and :class:`.HSplit` can use it to pass their + style down to the windows that they contain. + :param z_index: Used for propagating z_index from parent to child. + """ + + def is_modal(self) -> bool: + """ + When this container is modal, key bindings from parent containers are + not taken into account if a user control in this container is focused. + """ + return False + + def get_key_bindings(self) -> Optional[KeyBindingsBase]: + """ + Returns a :class:`.KeyBindings` object. These bindings become active when any + user control in this container has the focus, except if any containers + between this container and the focused user control is modal. + """ + return None + + @abstractmethod + def get_children(self) -> List["Container"]: + """ + Return the list of child :class:`.Container` objects. + """ + return [] + + +if TYPE_CHECKING: + + class MagicContainer(Protocol): + """ + Any object that implements ``__pt_container__`` represents a container. + """ + + def __pt_container__(self) -> "AnyContainer": + ... + + +AnyContainer = Union[Container, "MagicContainer"] + + +def _window_too_small() -> "Window": + "Create a `Window` that displays the 'Window too small' text." + return Window( + FormattedTextControl(text=[("class:window-too-small", " Window too small... ")]) + ) + + +class VerticalAlign(Enum): + "Alignment for `HSplit`." + TOP = "TOP" + CENTER = "CENTER" + BOTTOM = "BOTTOM" + JUSTIFY = "JUSTIFY" + + +class HorizontalAlign(Enum): + "Alignment for `VSplit`." + LEFT = "LEFT" + CENTER = "CENTER" + RIGHT = "RIGHT" + JUSTIFY = "JUSTIFY" + + +class _Split(Container): + """ + The common parts of `VSplit` and `HSplit`. + """ + + def __init__( + self, + children: Sequence[AnyContainer], + window_too_small: Optional[Container] = None, + padding: AnyDimension = Dimension.exact(0), + padding_char: Optional[str] = None, + padding_style: str = "", + width: AnyDimension = None, + height: AnyDimension = None, + z_index: Optional[int] = None, + modal: bool = False, + key_bindings: Optional[KeyBindingsBase] = None, + style: Union[str, Callable[[], str]] = "", + ) -> None: + + self.children = [to_container(c) for c in children] + self.window_too_small = window_too_small or _window_too_small() + self.padding = padding + self.padding_char = padding_char + self.padding_style = padding_style + + self.width = width + self.height = height + self.z_index = z_index + + self.modal = modal + self.key_bindings = key_bindings + self.style = style + + def is_modal(self) -> bool: + return self.modal + + def get_key_bindings(self) -> Optional[KeyBindingsBase]: + return self.key_bindings + + def get_children(self) -> List[Container]: + return self.children + + +class HSplit(_Split): + """ + Several layouts, one stacked above/under the other. :: + + +--------------------+ + | | + +--------------------+ + | | + +--------------------+ + + By default, this doesn't display a horizontal line between the children, + but if this is something you need, then create a HSplit as follows:: + + HSplit(children=[ ... ], padding_char='-', + padding=1, padding_style='#ffff00') + + :param children: List of child :class:`.Container` objects. + :param window_too_small: A :class:`.Container` object that is displayed if + there is not enough space for all the children. By default, this is a + "Window too small" message. + :param align: `VerticalAlign` value. + :param width: When given, use this width instead of looking at the children. + :param height: When given, use this height instead of looking at the children. + :param z_index: (int or None) When specified, this can be used to bring + element in front of floating elements. `None` means: inherit from parent. + :param style: A style string. + :param modal: ``True`` or ``False``. + :param key_bindings: ``None`` or a :class:`.KeyBindings` object. + + :param padding: (`Dimension` or int), size to be used for the padding. + :param padding_char: Character to be used for filling in the padding. + :param padding_style: Style to applied to the padding. + """ + + def __init__( + self, + children: Sequence[AnyContainer], + window_too_small: Optional[Container] = None, + align: VerticalAlign = VerticalAlign.JUSTIFY, + padding: AnyDimension = 0, + padding_char: Optional[str] = None, + padding_style: str = "", + width: AnyDimension = None, + height: AnyDimension = None, + z_index: Optional[int] = None, + modal: bool = False, + key_bindings: Optional[KeyBindingsBase] = None, + style: Union[str, Callable[[], str]] = "", + ) -> None: + + super().__init__( + children=children, + window_too_small=window_too_small, + padding=padding, + padding_char=padding_char, + padding_style=padding_style, + width=width, + height=height, + z_index=z_index, + modal=modal, + key_bindings=key_bindings, + style=style, + ) + + self.align = align + + self._children_cache: SimpleCache[ + Tuple[Container, ...], List[Container] + ] = SimpleCache(maxsize=1) + self._remaining_space_window = Window() # Dummy window. + + def preferred_width(self, max_available_width: int) -> Dimension: + if self.width is not None: + return to_dimension(self.width) + + if self.children: + dimensions = [c.preferred_width(max_available_width) for c in self.children] + return max_layout_dimensions(dimensions) + else: + return Dimension() + + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + if self.height is not None: + return to_dimension(self.height) + + dimensions = [ + c.preferred_height(width, max_available_height) for c in self._all_children + ] + return sum_layout_dimensions(dimensions) + + def reset(self) -> None: + for c in self.children: + c.reset() + + @property + def _all_children(self) -> List[Container]: + """ + List of child objects, including padding. + """ + + def get() -> List[Container]: + result: List[Container] = [] + + # Padding Top. + if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM): + result.append(Window(width=Dimension(preferred=0))) + + # The children with padding. + for child in self.children: + result.append(child) + result.append( + Window( + height=self.padding, + char=self.padding_char, + style=self.padding_style, + ) + ) + if result: + result.pop() + + # Padding right. + if self.align in (VerticalAlign.CENTER, VerticalAlign.TOP): + result.append(Window(width=Dimension(preferred=0))) + + return result + + return self._children_cache.get(tuple(self.children), get) + + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + """ + Render the prompt to a `Screen` instance. + + :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class + to which the output has to be written. + """ + sizes = self._divide_heights(write_position) + style = parent_style + " " + to_str(self.style) + z_index = z_index if self.z_index is None else self.z_index + + if sizes is None: + self.window_too_small.write_to_screen( + screen, mouse_handlers, write_position, style, erase_bg, z_index + ) + else: + # + ypos = write_position.ypos + xpos = write_position.xpos + width = write_position.width + + # Draw child panes. + for s, c in zip(sizes, self._all_children): + c.write_to_screen( + screen, + mouse_handlers, + WritePosition(xpos, ypos, width, s), + style, + erase_bg, + z_index, + ) + ypos += s + + # Fill in the remaining space. This happens when a child control + # refuses to take more space and we don't have any padding. Adding a + # dummy child control for this (in `self._all_children`) is not + # desired, because in some situations, it would take more space, even + # when it's not required. This is required to apply the styling. + remaining_height = write_position.ypos + write_position.height - ypos + if remaining_height > 0: + self._remaining_space_window.write_to_screen( + screen, + mouse_handlers, + WritePosition(xpos, ypos, width, remaining_height), + style, + erase_bg, + z_index, + ) + + def _divide_heights(self, write_position: WritePosition) -> Optional[List[int]]: + """ + Return the heights for all rows. + Or None when there is not enough space. + """ + if not self.children: + return [] + + width = write_position.width + height = write_position.height + + # Calculate heights. + dimensions = [c.preferred_height(width, height) for c in self._all_children] + + # Sum dimensions + sum_dimensions = sum_layout_dimensions(dimensions) + + # If there is not enough space for both. + # Don't do anything. + if sum_dimensions.min > height: + return None + + # Find optimal sizes. (Start with minimal size, increase until we cover + # the whole height.) + sizes = [d.min for d in dimensions] + + child_generator = take_using_weights( + items=list(range(len(dimensions))), weights=[d.weight for d in dimensions] + ) + + i = next(child_generator) + + # Increase until we meet at least the 'preferred' size. + preferred_stop = min(height, sum_dimensions.preferred) + preferred_dimensions = [d.preferred for d in dimensions] + + while sum(sizes) < preferred_stop: + if sizes[i] < preferred_dimensions[i]: + sizes[i] += 1 + i = next(child_generator) + + # Increase until we use all the available space. (or until "max") + if not get_app().is_done: + max_stop = min(height, sum_dimensions.max) + max_dimensions = [d.max for d in dimensions] + + while sum(sizes) < max_stop: + if sizes[i] < max_dimensions[i]: + sizes[i] += 1 + i = next(child_generator) + + return sizes + + +class VSplit(_Split): + """ + Several layouts, one stacked left/right of the other. :: + + +---------+----------+ + | | | + | | | + +---------+----------+ + + By default, this doesn't display a vertical line between the children, but + if this is something you need, then create a HSplit as follows:: + + VSplit(children=[ ... ], padding_char='|', + padding=1, padding_style='#ffff00') + + :param children: List of child :class:`.Container` objects. + :param window_too_small: A :class:`.Container` object that is displayed if + there is not enough space for all the children. By default, this is a + "Window too small" message. + :param align: `HorizontalAlign` value. + :param width: When given, use this width instead of looking at the children. + :param height: When given, use this height instead of looking at the children. + :param z_index: (int or None) When specified, this can be used to bring + element in front of floating elements. `None` means: inherit from parent. + :param style: A style string. + :param modal: ``True`` or ``False``. + :param key_bindings: ``None`` or a :class:`.KeyBindings` object. + + :param padding: (`Dimension` or int), size to be used for the padding. + :param padding_char: Character to be used for filling in the padding. + :param padding_style: Style to applied to the padding. + """ + + def __init__( + self, + children: Sequence[AnyContainer], + window_too_small: Optional[Container] = None, + align: HorizontalAlign = HorizontalAlign.JUSTIFY, + padding: AnyDimension = 0, + padding_char: Optional[str] = None, + padding_style: str = "", + width: AnyDimension = None, + height: AnyDimension = None, + z_index: Optional[int] = None, + modal: bool = False, + key_bindings: Optional[KeyBindingsBase] = None, + style: Union[str, Callable[[], str]] = "", + ) -> None: + + super().__init__( + children=children, + window_too_small=window_too_small, + padding=padding, + padding_char=padding_char, + padding_style=padding_style, + width=width, + height=height, + z_index=z_index, + modal=modal, + key_bindings=key_bindings, + style=style, + ) + + self.align = align + + self._children_cache: SimpleCache[ + Tuple[Container, ...], List[Container] + ] = SimpleCache(maxsize=1) + self._remaining_space_window = Window() # Dummy window. + + def preferred_width(self, max_available_width: int) -> Dimension: + if self.width is not None: + return to_dimension(self.width) + + dimensions = [ + c.preferred_width(max_available_width) for c in self._all_children + ] + + return sum_layout_dimensions(dimensions) + + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + if self.height is not None: + return to_dimension(self.height) + + # At the point where we want to calculate the heights, the widths have + # already been decided. So we can trust `width` to be the actual + # `width` that's going to be used for the rendering. So, + # `divide_widths` is supposed to use all of the available width. + # Using only the `preferred` width caused a bug where the reported + # height was more than required. (we had a `BufferControl` which did + # wrap lines because of the smaller width returned by `_divide_widths`. + + sizes = self._divide_widths(width) + children = self._all_children + + if sizes is None: + return Dimension() + else: + dimensions = [ + c.preferred_height(s, max_available_height) + for s, c in zip(sizes, children) + ] + return max_layout_dimensions(dimensions) + + def reset(self) -> None: + for c in self.children: + c.reset() + + @property + def _all_children(self) -> List[Container]: + """ + List of child objects, including padding. + """ + + def get() -> List[Container]: + result: List[Container] = [] + + # Padding left. + if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT): + result.append(Window(width=Dimension(preferred=0))) + + # The children with padding. + for child in self.children: + result.append(child) + result.append( + Window( + width=self.padding, + char=self.padding_char, + style=self.padding_style, + ) + ) + if result: + result.pop() + + # Padding right. + if self.align in (HorizontalAlign.CENTER, HorizontalAlign.LEFT): + result.append(Window(width=Dimension(preferred=0))) + + return result + + return self._children_cache.get(tuple(self.children), get) + + def _divide_widths(self, width: int) -> Optional[List[int]]: + """ + Return the widths for all columns. + Or None when there is not enough space. + """ + children = self._all_children + + if not children: + return [] + + # Calculate widths. + dimensions = [c.preferred_width(width) for c in children] + preferred_dimensions = [d.preferred for d in dimensions] + + # Sum dimensions + sum_dimensions = sum_layout_dimensions(dimensions) + + # If there is not enough space for both. + # Don't do anything. + if sum_dimensions.min > width: + return None + + # Find optimal sizes. (Start with minimal size, increase until we cover + # the whole width.) + sizes = [d.min for d in dimensions] + + child_generator = take_using_weights( + items=list(range(len(dimensions))), weights=[d.weight for d in dimensions] + ) + + i = next(child_generator) + + # Increase until we meet at least the 'preferred' size. + preferred_stop = min(width, sum_dimensions.preferred) + + while sum(sizes) < preferred_stop: + if sizes[i] < preferred_dimensions[i]: + sizes[i] += 1 + i = next(child_generator) + + # Increase until we use all the available space. + max_dimensions = [d.max for d in dimensions] + max_stop = min(width, sum_dimensions.max) + + while sum(sizes) < max_stop: + if sizes[i] < max_dimensions[i]: + sizes[i] += 1 + i = next(child_generator) + + return sizes + + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + """ + Render the prompt to a `Screen` instance. + + :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class + to which the output has to be written. + """ + if not self.children: + return + + children = self._all_children + sizes = self._divide_widths(write_position.width) + style = parent_style + " " + to_str(self.style) + z_index = z_index if self.z_index is None else self.z_index + + # If there is not enough space. + if sizes is None: + self.window_too_small.write_to_screen( + screen, mouse_handlers, write_position, style, erase_bg, z_index + ) + return + + # Calculate heights, take the largest possible, but not larger than + # write_position.height. + heights = [ + child.preferred_height(width, write_position.height).preferred + for width, child in zip(sizes, children) + ] + height = max(write_position.height, min(write_position.height, max(heights))) + + # + ypos = write_position.ypos + xpos = write_position.xpos + + # Draw all child panes. + for s, c in zip(sizes, children): + c.write_to_screen( + screen, + mouse_handlers, + WritePosition(xpos, ypos, s, height), + style, + erase_bg, + z_index, + ) + xpos += s + + # Fill in the remaining space. This happens when a child control + # refuses to take more space and we don't have any padding. Adding a + # dummy child control for this (in `self._all_children`) is not + # desired, because in some situations, it would take more space, even + # when it's not required. This is required to apply the styling. + remaining_width = write_position.xpos + write_position.width - xpos + if remaining_width > 0: + self._remaining_space_window.write_to_screen( + screen, + mouse_handlers, + WritePosition(xpos, ypos, remaining_width, height), + style, + erase_bg, + z_index, + ) + + +class FloatContainer(Container): + """ + Container which can contain another container for the background, as well + as a list of floating containers on top of it. + + Example Usage:: + + FloatContainer(content=Window(...), + floats=[ + Float(xcursor=True, + ycursor=True, + content=CompletionsMenu(...)) + ]) + + :param z_index: (int or None) When specified, this can be used to bring + element in front of floating elements. `None` means: inherit from parent. + This is the z_index for the whole `Float` container as a whole. + """ + + def __init__( + self, + content: AnyContainer, + floats: List["Float"], + modal: bool = False, + key_bindings: Optional[KeyBindingsBase] = None, + style: Union[str, Callable[[], str]] = "", + z_index: Optional[int] = None, + ) -> None: + + self.content = to_container(content) + self.floats = floats + + self.modal = modal + self.key_bindings = key_bindings + self.style = style + self.z_index = z_index + + def reset(self) -> None: + self.content.reset() + + for f in self.floats: + f.content.reset() + + def preferred_width(self, max_available_width: int) -> Dimension: + return self.content.preferred_width(max_available_width) + + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + """ + Return the preferred height of the float container. + (We don't care about the height of the floats, they should always fit + into the dimensions provided by the container.) + """ + return self.content.preferred_height(width, max_available_height) + + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + style = parent_style + " " + to_str(self.style) + z_index = z_index if self.z_index is None else self.z_index + + self.content.write_to_screen( + screen, mouse_handlers, write_position, style, erase_bg, z_index + ) + + for number, fl in enumerate(self.floats): + # z_index of a Float is computed by summing the z_index of the + # container and the `Float`. + new_z_index = (z_index or 0) + fl.z_index + style = parent_style + " " + to_str(self.style) + + # If the float that we have here, is positioned relative to the + # cursor position, but the Window that specifies the cursor + # position is not drawn yet, because it's a Float itself, we have + # to postpone this calculation. (This is a work-around, but good + # enough for now.) + postpone = fl.xcursor is not None or fl.ycursor is not None + + if postpone: + new_z_index = ( + number + 10**8 + ) # Draw as late as possible, but keep the order. + screen.draw_with_z_index( + z_index=new_z_index, + draw_func=partial( + self._draw_float, + fl, + screen, + mouse_handlers, + write_position, + style, + erase_bg, + new_z_index, + ), + ) + else: + self._draw_float( + fl, + screen, + mouse_handlers, + write_position, + style, + erase_bg, + new_z_index, + ) + + def _draw_float( + self, + fl: "Float", + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + "Draw a single Float." + # When a menu_position was given, use this instead of the cursor + # position. (These cursor positions are absolute, translate again + # relative to the write_position.) + # Note: This should be inside the for-loop, because one float could + # set the cursor position to be used for the next one. + cpos = screen.get_menu_position( + fl.attach_to_window or get_app().layout.current_window + ) + cursor_position = Point( + x=cpos.x - write_position.xpos, y=cpos.y - write_position.ypos + ) + + fl_width = fl.get_width() + fl_height = fl.get_height() + width: int + height: int + xpos: int + ypos: int + + # Left & width given. + if fl.left is not None and fl_width is not None: + xpos = fl.left + width = fl_width + # Left & right given -> calculate width. + elif fl.left is not None and fl.right is not None: + xpos = fl.left + width = write_position.width - fl.left - fl.right + # Width & right given -> calculate left. + elif fl_width is not None and fl.right is not None: + xpos = write_position.width - fl.right - fl_width + width = fl_width + # Near x position of cursor. + elif fl.xcursor: + if fl_width is None: + width = fl.content.preferred_width(write_position.width).preferred + width = min(write_position.width, width) + else: + width = fl_width + + xpos = cursor_position.x + if xpos + width > write_position.width: + xpos = max(0, write_position.width - width) + # Only width given -> center horizontally. + elif fl_width: + xpos = int((write_position.width - fl_width) / 2) + width = fl_width + # Otherwise, take preferred width from float content. + else: + width = fl.content.preferred_width(write_position.width).preferred + + if fl.left is not None: + xpos = fl.left + elif fl.right is not None: + xpos = max(0, write_position.width - width - fl.right) + else: # Center horizontally. + xpos = max(0, int((write_position.width - width) / 2)) + + # Trim. + width = min(width, write_position.width - xpos) + + # Top & height given. + if fl.top is not None and fl_height is not None: + ypos = fl.top + height = fl_height + # Top & bottom given -> calculate height. + elif fl.top is not None and fl.bottom is not None: + ypos = fl.top + height = write_position.height - fl.top - fl.bottom + # Height & bottom given -> calculate top. + elif fl_height is not None and fl.bottom is not None: + ypos = write_position.height - fl_height - fl.bottom + height = fl_height + # Near cursor. + elif fl.ycursor: + ypos = cursor_position.y + (0 if fl.allow_cover_cursor else 1) + + if fl_height is None: + height = fl.content.preferred_height( + width, write_position.height + ).preferred + else: + height = fl_height + + # Reduce height if not enough space. (We can use the height + # when the content requires it.) + if height > write_position.height - ypos: + if write_position.height - ypos + 1 >= ypos: + # When the space below the cursor is more than + # the space above, just reduce the height. + height = write_position.height - ypos + else: + # Otherwise, fit the float above the cursor. + height = min(height, cursor_position.y) + ypos = cursor_position.y - height + + # Only height given -> center vertically. + elif fl_height: + ypos = int((write_position.height - fl_height) / 2) + height = fl_height + # Otherwise, take preferred height from content. + else: + height = fl.content.preferred_height(width, write_position.height).preferred + + if fl.top is not None: + ypos = fl.top + elif fl.bottom is not None: + ypos = max(0, write_position.height - height - fl.bottom) + else: # Center vertically. + ypos = max(0, int((write_position.height - height) / 2)) + + # Trim. + height = min(height, write_position.height - ypos) + + # Write float. + # (xpos and ypos can be negative: a float can be partially visible.) + if height > 0 and width > 0: + wp = WritePosition( + xpos=xpos + write_position.xpos, + ypos=ypos + write_position.ypos, + width=width, + height=height, + ) + + if not fl.hide_when_covering_content or self._area_is_empty(screen, wp): + fl.content.write_to_screen( + screen, + mouse_handlers, + wp, + style, + erase_bg=not fl.transparent(), + z_index=z_index, + ) + + def _area_is_empty(self, screen: Screen, write_position: WritePosition) -> bool: + """ + Return True when the area below the write position is still empty. + (For floats that should not hide content underneath.) + """ + wp = write_position + + for y in range(wp.ypos, wp.ypos + wp.height): + if y in screen.data_buffer: + row = screen.data_buffer[y] + + for x in range(wp.xpos, wp.xpos + wp.width): + c = row[x] + if c.char != " ": + return False + + return True + + def is_modal(self) -> bool: + return self.modal + + def get_key_bindings(self) -> Optional[KeyBindingsBase]: + return self.key_bindings + + def get_children(self) -> List[Container]: + children = [self.content] + children.extend(f.content for f in self.floats) + return children + + +class Float: + """ + Float for use in a :class:`.FloatContainer`. + Except for the `content` parameter, all other options are optional. + + :param content: :class:`.Container` instance. + + :param width: :class:`.Dimension` or callable which returns a :class:`.Dimension`. + :param height: :class:`.Dimension` or callable which returns a :class:`.Dimension`. + + :param left: Distance to the left edge of the :class:`.FloatContainer`. + :param right: Distance to the right edge of the :class:`.FloatContainer`. + :param top: Distance to the top of the :class:`.FloatContainer`. + :param bottom: Distance to the bottom of the :class:`.FloatContainer`. + + :param attach_to_window: Attach to the cursor from this window, instead of + the current window. + :param hide_when_covering_content: Hide the float when it covers content underneath. + :param allow_cover_cursor: When `False`, make sure to display the float + below the cursor. Not on top of the indicated position. + :param z_index: Z-index position. For a Float, this needs to be at least + one. It is relative to the z_index of the parent container. + :param transparent: :class:`.Filter` indicating whether this float needs to be + drawn transparently. + """ + + def __init__( + self, + content: AnyContainer, + top: Optional[int] = None, + right: Optional[int] = None, + bottom: Optional[int] = None, + left: Optional[int] = None, + width: Optional[Union[int, Callable[[], int]]] = None, + height: Optional[Union[int, Callable[[], int]]] = None, + xcursor: bool = False, + ycursor: bool = False, + attach_to_window: Optional[AnyContainer] = None, + hide_when_covering_content: bool = False, + allow_cover_cursor: bool = False, + z_index: int = 1, + transparent: bool = False, + ) -> None: + + assert z_index >= 1 + + self.left = left + self.right = right + self.top = top + self.bottom = bottom + + self.width = width + self.height = height + + self.xcursor = xcursor + self.ycursor = ycursor + + self.attach_to_window = ( + to_window(attach_to_window) if attach_to_window else None + ) + + self.content = to_container(content) + self.hide_when_covering_content = hide_when_covering_content + self.allow_cover_cursor = allow_cover_cursor + self.z_index = z_index + self.transparent = to_filter(transparent) + + def get_width(self) -> Optional[int]: + if callable(self.width): + return self.width() + return self.width + + def get_height(self) -> Optional[int]: + if callable(self.height): + return self.height() + return self.height + + def __repr__(self) -> str: + return "Float(content=%r)" % self.content + + +class WindowRenderInfo: + """ + Render information for the last render time of this control. + It stores mapping information between the input buffers (in case of a + :class:`~prompt_toolkit.layout.controls.BufferControl`) and the actual + render position on the output screen. + + (Could be used for implementation of the Vi 'H' and 'L' key bindings as + well as implementing mouse support.) + + :param ui_content: The original :class:`.UIContent` instance that contains + the whole input, without clipping. (ui_content) + :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. + :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. + :param window_width: The width of the window that displays the content, + without the margins. + :param window_height: The height of the window that displays the content. + :param configured_scroll_offsets: The scroll offsets as configured for the + :class:`Window` instance. + :param visible_line_to_row_col: Mapping that maps the row numbers on the + displayed screen (starting from zero for the first visible line) to + (row, col) tuples pointing to the row and column of the :class:`.UIContent`. + :param rowcol_to_yx: Mapping that maps (row, column) tuples representing + coordinates of the :class:`UIContent` to (y, x) absolute coordinates at + the rendered screen. + """ + + def __init__( + self, + window: "Window", + ui_content: UIContent, + horizontal_scroll: int, + vertical_scroll: int, + window_width: int, + window_height: int, + configured_scroll_offsets: "ScrollOffsets", + visible_line_to_row_col: Dict[int, Tuple[int, int]], + rowcol_to_yx: Dict[Tuple[int, int], Tuple[int, int]], + x_offset: int, + y_offset: int, + wrap_lines: bool, + ) -> None: + + self.window = window + self.ui_content = ui_content + self.vertical_scroll = vertical_scroll + self.window_width = window_width # Width without margins. + self.window_height = window_height + + self.configured_scroll_offsets = configured_scroll_offsets + self.visible_line_to_row_col = visible_line_to_row_col + self.wrap_lines = wrap_lines + + self._rowcol_to_yx = rowcol_to_yx # row/col from input to absolute y/x + # screen coordinates. + self._x_offset = x_offset + self._y_offset = y_offset + + @property + def visible_line_to_input_line(self) -> Dict[int, int]: + return { + visible_line: rowcol[0] + for visible_line, rowcol in self.visible_line_to_row_col.items() + } + + @property + def cursor_position(self) -> Point: + """ + Return the cursor position coordinates, relative to the left/top corner + of the rendered screen. + """ + cpos = self.ui_content.cursor_position + try: + y, x = self._rowcol_to_yx[cpos.y, cpos.x] + except KeyError: + # For `DummyControl` for instance, the content can be empty, and so + # will `_rowcol_to_yx` be. Return 0/0 by default. + return Point(x=0, y=0) + else: + return Point(x=x - self._x_offset, y=y - self._y_offset) + + @property + def applied_scroll_offsets(self) -> "ScrollOffsets": + """ + Return a :class:`.ScrollOffsets` instance that indicates the actual + offset. This can be less than or equal to what's configured. E.g, when + the cursor is completely at the top, the top offset will be zero rather + than what's configured. + """ + if self.displayed_lines[0] == 0: + top = 0 + else: + # Get row where the cursor is displayed. + y = self.input_line_to_visible_line[self.ui_content.cursor_position.y] + top = min(y, self.configured_scroll_offsets.top) + + return ScrollOffsets( + top=top, + bottom=min( + self.ui_content.line_count - self.displayed_lines[-1] - 1, + self.configured_scroll_offsets.bottom, + ), + # For left/right, it probably doesn't make sense to return something. + # (We would have to calculate the widths of all the lines and keep + # double width characters in mind.) + left=0, + right=0, + ) + + @property + def displayed_lines(self) -> List[int]: + """ + List of all the visible rows. (Line numbers of the input buffer.) + The last line may not be entirely visible. + """ + return sorted(row for row, col in self.visible_line_to_row_col.values()) + + @property + def input_line_to_visible_line(self) -> Dict[int, int]: + """ + Return the dictionary mapping the line numbers of the input buffer to + the lines of the screen. When a line spans several rows at the screen, + the first row appears in the dictionary. + """ + result: Dict[int, int] = {} + for k, v in self.visible_line_to_input_line.items(): + if v in result: + result[v] = min(result[v], k) + else: + result[v] = k + return result + + def first_visible_line(self, after_scroll_offset: bool = False) -> int: + """ + Return the line number (0 based) of the input document that corresponds + with the first visible line. + """ + if after_scroll_offset: + return self.displayed_lines[self.applied_scroll_offsets.top] + else: + return self.displayed_lines[0] + + def last_visible_line(self, before_scroll_offset: bool = False) -> int: + """ + Like `first_visible_line`, but for the last visible line. + """ + if before_scroll_offset: + return self.displayed_lines[-1 - self.applied_scroll_offsets.bottom] + else: + return self.displayed_lines[-1] + + def center_visible_line( + self, before_scroll_offset: bool = False, after_scroll_offset: bool = False + ) -> int: + """ + Like `first_visible_line`, but for the center visible line. + """ + return ( + self.first_visible_line(after_scroll_offset) + + ( + self.last_visible_line(before_scroll_offset) + - self.first_visible_line(after_scroll_offset) + ) + // 2 + ) + + @property + def content_height(self) -> int: + """ + The full height of the user control. + """ + return self.ui_content.line_count + + @property + def full_height_visible(self) -> bool: + """ + True when the full height is visible (There is no vertical scroll.) + """ + return ( + self.vertical_scroll == 0 + and self.last_visible_line() == self.content_height + ) + + @property + def top_visible(self) -> bool: + """ + True when the top of the buffer is visible. + """ + return self.vertical_scroll == 0 + + @property + def bottom_visible(self) -> bool: + """ + True when the bottom of the buffer is visible. + """ + return self.last_visible_line() == self.content_height - 1 + + @property + def vertical_scroll_percentage(self) -> int: + """ + Vertical scroll as a percentage. (0 means: the top is visible, + 100 means: the bottom is visible.) + """ + if self.bottom_visible: + return 100 + else: + return 100 * self.vertical_scroll // self.content_height + + def get_height_for_line(self, lineno: int) -> int: + """ + Return the height of the given line. + (The height that it would take, if this line became visible.) + """ + if self.wrap_lines: + return self.ui_content.get_height_for_line( + lineno, self.window_width, self.window.get_line_prefix + ) + else: + return 1 + + +class ScrollOffsets: + """ + Scroll offsets for the :class:`.Window` class. + + Note that left/right offsets only make sense if line wrapping is disabled. + """ + + def __init__( + self, + top: Union[int, Callable[[], int]] = 0, + bottom: Union[int, Callable[[], int]] = 0, + left: Union[int, Callable[[], int]] = 0, + right: Union[int, Callable[[], int]] = 0, + ) -> None: + + self._top = top + self._bottom = bottom + self._left = left + self._right = right + + @property + def top(self) -> int: + return to_int(self._top) + + @property + def bottom(self) -> int: + return to_int(self._bottom) + + @property + def left(self) -> int: + return to_int(self._left) + + @property + def right(self) -> int: + return to_int(self._right) + + def __repr__(self) -> str: + return "ScrollOffsets(top={!r}, bottom={!r}, left={!r}, right={!r})".format( + self._top, + self._bottom, + self._left, + self._right, + ) + + +class ColorColumn: + """ + Column for a :class:`.Window` to be colored. + """ + + def __init__(self, position: int, style: str = "class:color-column") -> None: + self.position = position + self.style = style + + +_in_insert_mode = vi_insert_mode | emacs_insert_mode + + +class WindowAlign(Enum): + """ + Alignment of the Window content. + + Note that this is different from `HorizontalAlign` and `VerticalAlign`, + which are used for the alignment of the child containers in respectively + `VSplit` and `HSplit`. + """ + + LEFT = "LEFT" + RIGHT = "RIGHT" + CENTER = "CENTER" + + +class Window(Container): + """ + Container that holds a control. + + :param content: :class:`.UIControl` instance. + :param width: :class:`.Dimension` instance or callable. + :param height: :class:`.Dimension` instance or callable. + :param z_index: When specified, this can be used to bring element in front + of floating elements. + :param dont_extend_width: When `True`, don't take up more width then the + preferred width reported by the control. + :param dont_extend_height: When `True`, don't take up more width then the + preferred height reported by the control. + :param ignore_content_width: A `bool` or :class:`.Filter` instance. Ignore + the :class:`.UIContent` width when calculating the dimensions. + :param ignore_content_height: A `bool` or :class:`.Filter` instance. Ignore + the :class:`.UIContent` height when calculating the dimensions. + :param left_margins: A list of :class:`.Margin` instance to be displayed on + the left. For instance: :class:`~prompt_toolkit.layout.NumberedMargin` + can be one of them in order to show line numbers. + :param right_margins: Like `left_margins`, but on the other side. + :param scroll_offsets: :class:`.ScrollOffsets` instance, representing the + preferred amount of lines/columns to be always visible before/after the + cursor. When both top and bottom are a very high number, the cursor + will be centered vertically most of the time. + :param allow_scroll_beyond_bottom: A `bool` or + :class:`.Filter` instance. When True, allow scrolling so far, that the + top part of the content is not visible anymore, while there is still + empty space available at the bottom of the window. In the Vi editor for + instance, this is possible. You will see tildes while the top part of + the body is hidden. + :param wrap_lines: A `bool` or :class:`.Filter` instance. When True, don't + scroll horizontally, but wrap lines instead. + :param get_vertical_scroll: Callable that takes this window + instance as input and returns a preferred vertical scroll. + (When this is `None`, the scroll is only determined by the last and + current cursor position.) + :param get_horizontal_scroll: Callable that takes this window + instance as input and returns a preferred vertical scroll. + :param always_hide_cursor: A `bool` or + :class:`.Filter` instance. When True, never display the cursor, even + when the user control specifies a cursor position. + :param cursorline: A `bool` or :class:`.Filter` instance. When True, + display a cursorline. + :param cursorcolumn: A `bool` or :class:`.Filter` instance. When True, + display a cursorcolumn. + :param colorcolumns: A list of :class:`.ColorColumn` instances that + describe the columns to be highlighted, or a callable that returns such + a list. + :param align: :class:`.WindowAlign` value or callable that returns an + :class:`.WindowAlign` value. alignment of content. + :param style: A style string. Style to be applied to all the cells in this + window. (This can be a callable that returns a string.) + :param char: (string) Character to be used for filling the background. This + can also be a callable that returns a character. + :param get_line_prefix: None or a callable that returns formatted text to + be inserted before a line. It takes a line number (int) and a + wrap_count and returns formatted text. This can be used for + implementation of line continuations, things like Vim "breakindent" and + so on. + """ + + def __init__( + self, + content: Optional[UIControl] = None, + width: AnyDimension = None, + height: AnyDimension = None, + z_index: Optional[int] = None, + dont_extend_width: FilterOrBool = False, + dont_extend_height: FilterOrBool = False, + ignore_content_width: FilterOrBool = False, + ignore_content_height: FilterOrBool = False, + left_margins: Optional[Sequence[Margin]] = None, + right_margins: Optional[Sequence[Margin]] = None, + scroll_offsets: Optional[ScrollOffsets] = None, + allow_scroll_beyond_bottom: FilterOrBool = False, + wrap_lines: FilterOrBool = False, + get_vertical_scroll: Optional[Callable[["Window"], int]] = None, + get_horizontal_scroll: Optional[Callable[["Window"], int]] = None, + always_hide_cursor: FilterOrBool = False, + cursorline: FilterOrBool = False, + cursorcolumn: FilterOrBool = False, + colorcolumns: Union[ + None, List[ColorColumn], Callable[[], List[ColorColumn]] + ] = None, + align: Union[WindowAlign, Callable[[], WindowAlign]] = WindowAlign.LEFT, + style: Union[str, Callable[[], str]] = "", + char: Union[None, str, Callable[[], str]] = None, + get_line_prefix: Optional[GetLinePrefixCallable] = None, + ) -> None: + + self.allow_scroll_beyond_bottom = to_filter(allow_scroll_beyond_bottom) + self.always_hide_cursor = to_filter(always_hide_cursor) + self.wrap_lines = to_filter(wrap_lines) + self.cursorline = to_filter(cursorline) + self.cursorcolumn = to_filter(cursorcolumn) + + self.content = content or DummyControl() + self.dont_extend_width = to_filter(dont_extend_width) + self.dont_extend_height = to_filter(dont_extend_height) + self.ignore_content_width = to_filter(ignore_content_width) + self.ignore_content_height = to_filter(ignore_content_height) + self.left_margins = left_margins or [] + self.right_margins = right_margins or [] + self.scroll_offsets = scroll_offsets or ScrollOffsets() + self.get_vertical_scroll = get_vertical_scroll + self.get_horizontal_scroll = get_horizontal_scroll + self.colorcolumns = colorcolumns or [] + self.align = align + self.style = style + self.char = char + self.get_line_prefix = get_line_prefix + + self.width = width + self.height = height + self.z_index = z_index + + # Cache for the screens generated by the margin. + self._ui_content_cache: SimpleCache[ + Tuple[int, int, int], UIContent + ] = SimpleCache(maxsize=8) + self._margin_width_cache: SimpleCache[Tuple[Margin, int], int] = SimpleCache( + maxsize=1 + ) + + self.reset() + + def __repr__(self) -> str: + return "Window(content=%r)" % self.content + + def reset(self) -> None: + self.content.reset() + + #: Scrolling position of the main content. + self.vertical_scroll = 0 + self.horizontal_scroll = 0 + + # Vertical scroll 2: this is the vertical offset that a line is + # scrolled if a single line (the one that contains the cursor) consumes + # all of the vertical space. + self.vertical_scroll_2 = 0 + + #: Keep render information (mappings between buffer input and render + #: output.) + self.render_info: Optional[WindowRenderInfo] = None + + def _get_margin_width(self, margin: Margin) -> int: + """ + Return the width for this margin. + (Calculate only once per render time.) + """ + # Margin.get_width, needs to have a UIContent instance. + def get_ui_content() -> UIContent: + return self._get_ui_content(width=0, height=0) + + def get_width() -> int: + return margin.get_width(get_ui_content) + + key = (margin, get_app().render_counter) + return self._margin_width_cache.get(key, get_width) + + def _get_total_margin_width(self) -> int: + """ + Calculate and return the width of the margin (left + right). + """ + return sum(self._get_margin_width(m) for m in self.left_margins) + sum( + self._get_margin_width(m) for m in self.right_margins + ) + + def preferred_width(self, max_available_width: int) -> Dimension: + """ + Calculate the preferred width for this window. + """ + + def preferred_content_width() -> Optional[int]: + """Content width: is only calculated if no exact width for the + window was given.""" + if self.ignore_content_width(): + return None + + # Calculate the width of the margin. + total_margin_width = self._get_total_margin_width() + + # Window of the content. (Can be `None`.) + preferred_width = self.content.preferred_width( + max_available_width - total_margin_width + ) + + if preferred_width is not None: + # Include width of the margins. + preferred_width += total_margin_width + return preferred_width + + # Merge. + return self._merge_dimensions( + dimension=to_dimension(self.width), + get_preferred=preferred_content_width, + dont_extend=self.dont_extend_width(), + ) + + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + """ + Calculate the preferred height for this window. + """ + + def preferred_content_height() -> Optional[int]: + """Content height: is only calculated if no exact height for the + window was given.""" + if self.ignore_content_height(): + return None + + total_margin_width = self._get_total_margin_width() + wrap_lines = self.wrap_lines() + + return self.content.preferred_height( + width - total_margin_width, + max_available_height, + wrap_lines, + self.get_line_prefix, + ) + + return self._merge_dimensions( + dimension=to_dimension(self.height), + get_preferred=preferred_content_height, + dont_extend=self.dont_extend_height(), + ) + + @staticmethod + def _merge_dimensions( + dimension: Optional[Dimension], + get_preferred: Callable[[], Optional[int]], + dont_extend: bool = False, + ) -> Dimension: + """ + Take the Dimension from this `Window` class and the received preferred + size from the `UIControl` and return a `Dimension` to report to the + parent container. + """ + dimension = dimension or Dimension() + + # When a preferred dimension was explicitly given to the Window, + # ignore the UIControl. + preferred: Optional[int] + + if dimension.preferred_specified: + preferred = dimension.preferred + else: + # Otherwise, calculate the preferred dimension from the UI control + # content. + preferred = get_preferred() + + # When a 'preferred' dimension is given by the UIControl, make sure + # that it stays within the bounds of the Window. + if preferred is not None: + if dimension.max_specified: + preferred = min(preferred, dimension.max) + + if dimension.min_specified: + preferred = max(preferred, dimension.min) + + # When a `dont_extend` flag has been given, use the preferred dimension + # also as the max dimension. + max_: Optional[int] + min_: Optional[int] + + if dont_extend and preferred is not None: + max_ = min(dimension.max, preferred) + else: + max_ = dimension.max if dimension.max_specified else None + + min_ = dimension.min if dimension.min_specified else None + + return Dimension( + min=min_, max=max_, preferred=preferred, weight=dimension.weight + ) + + def _get_ui_content(self, width: int, height: int) -> UIContent: + """ + Create a `UIContent` instance. + """ + + def get_content() -> UIContent: + return self.content.create_content(width=width, height=height) + + key = (get_app().render_counter, width, height) + return self._ui_content_cache.get(key, get_content) + + def _get_digraph_char(self) -> Optional[str]: + "Return `False`, or the Digraph symbol to be used." + app = get_app() + if app.quoted_insert: + return "^" + if app.vi_state.waiting_for_digraph: + if app.vi_state.digraph_symbol1: + return app.vi_state.digraph_symbol1 + return "?" + return None + + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + """ + Write window to screen. This renders the user control, the margins and + copies everything over to the absolute position at the given screen. + """ + # If dont_extend_width/height was given. Then reduce width/height in + # WritePosition if the parent wanted us to paint in a bigger area. + # (This happens if this window is bundled with another window in a + # HSplit/VSplit, but with different size requirements.) + write_position = WritePosition( + xpos=write_position.xpos, + ypos=write_position.ypos, + width=write_position.width, + height=write_position.height, + ) + + if self.dont_extend_width(): + write_position.width = min( + write_position.width, + self.preferred_width(write_position.width).preferred, + ) + + if self.dont_extend_height(): + write_position.height = min( + write_position.height, + self.preferred_height( + write_position.width, write_position.height + ).preferred, + ) + + # Draw + z_index = z_index if self.z_index is None else self.z_index + + draw_func = partial( + self._write_to_screen_at_index, + screen, + mouse_handlers, + write_position, + parent_style, + erase_bg, + ) + + if z_index is None or z_index <= 0: + # When no z_index is given, draw right away. + draw_func() + else: + # Otherwise, postpone. + screen.draw_with_z_index(z_index=z_index, draw_func=draw_func) + + def _write_to_screen_at_index( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + ) -> None: + # Don't bother writing invisible windows. + # (We save some time, but also avoid applying last-line styling.) + if write_position.height <= 0 or write_position.width <= 0: + return + + # Calculate margin sizes. + left_margin_widths = [self._get_margin_width(m) for m in self.left_margins] + right_margin_widths = [self._get_margin_width(m) for m in self.right_margins] + total_margin_width = sum(left_margin_widths + right_margin_widths) + + # Render UserControl. + ui_content = self.content.create_content( + write_position.width - total_margin_width, write_position.height + ) + assert isinstance(ui_content, UIContent) + + # Scroll content. + wrap_lines = self.wrap_lines() + self._scroll( + ui_content, write_position.width - total_margin_width, write_position.height + ) + + # Erase background and fill with `char`. + self._fill_bg(screen, write_position, erase_bg) + + # Resolve `align` attribute. + align = self.align() if callable(self.align) else self.align + + # Write body + visible_line_to_row_col, rowcol_to_yx = self._copy_body( + ui_content, + screen, + write_position, + sum(left_margin_widths), + write_position.width - total_margin_width, + self.vertical_scroll, + self.horizontal_scroll, + wrap_lines=wrap_lines, + highlight_lines=True, + vertical_scroll_2=self.vertical_scroll_2, + always_hide_cursor=self.always_hide_cursor(), + has_focus=get_app().layout.current_control == self.content, + align=align, + get_line_prefix=self.get_line_prefix, + ) + + # Remember render info. (Set before generating the margins. They need this.) + x_offset = write_position.xpos + sum(left_margin_widths) + y_offset = write_position.ypos + + render_info = WindowRenderInfo( + window=self, + ui_content=ui_content, + horizontal_scroll=self.horizontal_scroll, + vertical_scroll=self.vertical_scroll, + window_width=write_position.width - total_margin_width, + window_height=write_position.height, + configured_scroll_offsets=self.scroll_offsets, + visible_line_to_row_col=visible_line_to_row_col, + rowcol_to_yx=rowcol_to_yx, + x_offset=x_offset, + y_offset=y_offset, + wrap_lines=wrap_lines, + ) + self.render_info = render_info + + # Set mouse handlers. + def mouse_handler(mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + Wrapper around the mouse_handler of the `UIControl` that turns + screen coordinates into line coordinates. + Returns `NotImplemented` if no UI invalidation should be done. + """ + # Don't handle mouse events outside of the current modal part of + # the UI. + if self not in get_app().layout.walk_through_modal_area(): + return NotImplemented + + # Find row/col position first. + yx_to_rowcol = {v: k for k, v in rowcol_to_yx.items()} + y = mouse_event.position.y + x = mouse_event.position.x + + # If clicked below the content area, look for a position in the + # last line instead. + max_y = write_position.ypos + len(visible_line_to_row_col) - 1 + y = min(max_y, y) + result: NotImplementedOrNone + + while x >= 0: + try: + row, col = yx_to_rowcol[y, x] + except KeyError: + # Try again. (When clicking on the right side of double + # width characters, or on the right side of the input.) + x -= 1 + else: + # Found position, call handler of UIControl. + result = self.content.mouse_handler( + MouseEvent( + position=Point(x=col, y=row), + event_type=mouse_event.event_type, + button=mouse_event.button, + modifiers=mouse_event.modifiers, + ) + ) + break + else: + # nobreak. + # (No x/y coordinate found for the content. This happens in + # case of a DummyControl, that does not have any content. + # Report (0,0) instead.) + result = self.content.mouse_handler( + MouseEvent( + position=Point(x=0, y=0), + event_type=mouse_event.event_type, + button=mouse_event.button, + modifiers=mouse_event.modifiers, + ) + ) + + # If it returns NotImplemented, handle it here. + if result == NotImplemented: + result = self._mouse_handler(mouse_event) + + return result + + mouse_handlers.set_mouse_handler_for_range( + x_min=write_position.xpos + sum(left_margin_widths), + x_max=write_position.xpos + write_position.width - total_margin_width, + y_min=write_position.ypos, + y_max=write_position.ypos + write_position.height, + handler=mouse_handler, + ) + + # Render and copy margins. + move_x = 0 + + def render_margin(m: Margin, width: int) -> UIContent: + "Render margin. Return `Screen`." + # Retrieve margin fragments. + fragments = m.create_margin(render_info, width, write_position.height) + + # Turn it into a UIContent object. + # already rendered those fragments using this size.) + return FormattedTextControl(fragments).create_content( + width + 1, write_position.height + ) + + for m, width in zip(self.left_margins, left_margin_widths): + if width > 0: # (ConditionalMargin returns a zero width. -- Don't render.) + # Create screen for margin. + margin_content = render_margin(m, width) + + # Copy and shift X. + self._copy_margin(margin_content, screen, write_position, move_x, width) + move_x += width + + move_x = write_position.width - sum(right_margin_widths) + + for m, width in zip(self.right_margins, right_margin_widths): + # Create screen for margin. + margin_content = render_margin(m, width) + + # Copy and shift X. + self._copy_margin(margin_content, screen, write_position, move_x, width) + move_x += width + + # Apply 'self.style' + self._apply_style(screen, write_position, parent_style) + + # Tell the screen that this user control has been painted at this + # position. + screen.visible_windows_to_write_positions[self] = write_position + + def _copy_body( + self, + ui_content: UIContent, + new_screen: Screen, + write_position: WritePosition, + move_x: int, + width: int, + vertical_scroll: int = 0, + horizontal_scroll: int = 0, + wrap_lines: bool = False, + highlight_lines: bool = False, + vertical_scroll_2: int = 0, + always_hide_cursor: bool = False, + has_focus: bool = False, + align: WindowAlign = WindowAlign.LEFT, + get_line_prefix: Optional[Callable[[int, int], AnyFormattedText]] = None, + ) -> Tuple[Dict[int, Tuple[int, int]], Dict[Tuple[int, int], Tuple[int, int]]]: + """ + Copy the UIContent into the output screen. + Return (visible_line_to_row_col, rowcol_to_yx) tuple. + + :param get_line_prefix: None or a callable that takes a line number + (int) and a wrap_count (int) and returns formatted text. + """ + xpos = write_position.xpos + move_x + ypos = write_position.ypos + line_count = ui_content.line_count + new_buffer = new_screen.data_buffer + empty_char = _CHAR_CACHE["", ""] + + # Map visible line number to (row, col) of input. + # 'col' will always be zero if line wrapping is off. + visible_line_to_row_col: Dict[int, Tuple[int, int]] = {} + + # Maps (row, col) from the input to (y, x) screen coordinates. + rowcol_to_yx: Dict[Tuple[int, int], Tuple[int, int]] = {} + + def copy_line( + line: StyleAndTextTuples, + lineno: int, + x: int, + y: int, + is_input: bool = False, + ) -> Tuple[int, int]: + """ + Copy over a single line to the output screen. This can wrap over + multiple lines in the output. It will call the prefix (prompt) + function before every line. + """ + if is_input: + current_rowcol_to_yx = rowcol_to_yx + else: + current_rowcol_to_yx = {} # Throwaway dictionary. + + # Draw line prefix. + if is_input and get_line_prefix: + prompt = to_formatted_text(get_line_prefix(lineno, 0)) + x, y = copy_line(prompt, lineno, x, y, is_input=False) + + # Scroll horizontally. + skipped = 0 # Characters skipped because of horizontal scrolling. + if horizontal_scroll and is_input: + h_scroll = horizontal_scroll + line = explode_text_fragments(line) + while h_scroll > 0 and line: + h_scroll -= get_cwidth(line[0][1]) + skipped += 1 + del line[:1] # Remove first character. + + x -= h_scroll # When scrolling over double width character, + # this can end up being negative. + + # Align this line. (Note that this doesn't work well when we use + # get_line_prefix and that function returns variable width prefixes.) + if align == WindowAlign.CENTER: + line_width = fragment_list_width(line) + if line_width < width: + x += (width - line_width) // 2 + elif align == WindowAlign.RIGHT: + line_width = fragment_list_width(line) + if line_width < width: + x += width - line_width + + col = 0 + wrap_count = 0 + for style, text, *_ in line: + new_buffer_row = new_buffer[y + ypos] + + # Remember raw VT escape sequences. (E.g. FinalTerm's + # escape sequences.) + if "[ZeroWidthEscape]" in style: + new_screen.zero_width_escapes[y + ypos][x + xpos] += text + continue + + for c in text: + char = _CHAR_CACHE[c, style] + char_width = char.width + + # Wrap when the line width is exceeded. + if wrap_lines and x + char_width > width: + visible_line_to_row_col[y + 1] = ( + lineno, + visible_line_to_row_col[y][1] + x, + ) + y += 1 + wrap_count += 1 + x = 0 + + # Insert line prefix (continuation prompt). + if is_input and get_line_prefix: + prompt = to_formatted_text( + get_line_prefix(lineno, wrap_count) + ) + x, y = copy_line(prompt, lineno, x, y, is_input=False) + + new_buffer_row = new_buffer[y + ypos] + + if y >= write_position.height: + return x, y # Break out of all for loops. + + # Set character in screen and shift 'x'. + if x >= 0 and y >= 0 and x < width: + new_buffer_row[x + xpos] = char + + # When we print a multi width character, make sure + # to erase the neighbours positions in the screen. + # (The empty string if different from everything, + # so next redraw this cell will repaint anyway.) + if char_width > 1: + for i in range(1, char_width): + new_buffer_row[x + xpos + i] = empty_char + + # If this is a zero width characters, then it's + # probably part of a decomposed unicode character. + # See: https://en.wikipedia.org/wiki/Unicode_equivalence + # Merge it in the previous cell. + elif char_width == 0: + # Handle all character widths. If the previous + # character is a multiwidth character, then + # merge it two positions back. + for pw in [2, 1]: # Previous character width. + if ( + x - pw >= 0 + and new_buffer_row[x + xpos - pw].width == pw + ): + prev_char = new_buffer_row[x + xpos - pw] + char2 = _CHAR_CACHE[ + prev_char.char + c, prev_char.style + ] + new_buffer_row[x + xpos - pw] = char2 + + # Keep track of write position for each character. + current_rowcol_to_yx[lineno, col + skipped] = ( + y + ypos, + x + xpos, + ) + + col += 1 + x += char_width + return x, y + + # Copy content. + def copy() -> int: + y = -vertical_scroll_2 + lineno = vertical_scroll + + while y < write_position.height and lineno < line_count: + # Take the next line and copy it in the real screen. + line = ui_content.get_line(lineno) + + visible_line_to_row_col[y] = (lineno, horizontal_scroll) + + # Copy margin and actual line. + x = 0 + x, y = copy_line(line, lineno, x, y, is_input=True) + + lineno += 1 + y += 1 + return y + + copy() + + def cursor_pos_to_screen_pos(row: int, col: int) -> Point: + "Translate row/col from UIContent to real Screen coordinates." + try: + y, x = rowcol_to_yx[row, col] + except KeyError: + # Normally this should never happen. (It is a bug, if it happens.) + # But to be sure, return (0, 0) + return Point(x=0, y=0) + + # raise ValueError( + # 'Invalid position. row=%r col=%r, vertical_scroll=%r, ' + # 'horizontal_scroll=%r, height=%r' % + # (row, col, vertical_scroll, horizontal_scroll, write_position.height)) + else: + return Point(x=x, y=y) + + # Set cursor and menu positions. + if ui_content.cursor_position: + screen_cursor_position = cursor_pos_to_screen_pos( + ui_content.cursor_position.y, ui_content.cursor_position.x + ) + + if has_focus: + new_screen.set_cursor_position(self, screen_cursor_position) + + if always_hide_cursor: + new_screen.show_cursor = False + else: + new_screen.show_cursor = ui_content.show_cursor + + self._highlight_digraph(new_screen) + + if highlight_lines: + self._highlight_cursorlines( + new_screen, + screen_cursor_position, + xpos, + ypos, + width, + write_position.height, + ) + + # Draw input characters from the input processor queue. + if has_focus and ui_content.cursor_position: + self._show_key_processor_key_buffer(new_screen) + + # Set menu position. + if ui_content.menu_position: + new_screen.set_menu_position( + self, + cursor_pos_to_screen_pos( + ui_content.menu_position.y, ui_content.menu_position.x + ), + ) + + # Update output screen height. + new_screen.height = max(new_screen.height, ypos + write_position.height) + + return visible_line_to_row_col, rowcol_to_yx + + def _fill_bg( + self, screen: Screen, write_position: WritePosition, erase_bg: bool + ) -> None: + """ + Erase/fill the background. + (Useful for floats and when a `char` has been given.) + """ + char: Optional[str] + if callable(self.char): + char = self.char() + else: + char = self.char + + if erase_bg or char: + wp = write_position + char_obj = _CHAR_CACHE[char or " ", ""] + + for y in range(wp.ypos, wp.ypos + wp.height): + row = screen.data_buffer[y] + for x in range(wp.xpos, wp.xpos + wp.width): + row[x] = char_obj + + def _apply_style( + self, new_screen: Screen, write_position: WritePosition, parent_style: str + ) -> None: + + # Apply `self.style`. + style = parent_style + " " + to_str(self.style) + + new_screen.fill_area(write_position, style=style, after=False) + + # Apply the 'last-line' class to the last line of each Window. This can + # be used to apply an 'underline' to the user control. + wp = WritePosition( + write_position.xpos, + write_position.ypos + write_position.height - 1, + write_position.width, + 1, + ) + new_screen.fill_area(wp, "class:last-line", after=True) + + def _highlight_digraph(self, new_screen: Screen) -> None: + """ + When we are in Vi digraph mode, put a question mark underneath the + cursor. + """ + digraph_char = self._get_digraph_char() + if digraph_char: + cpos = new_screen.get_cursor_position(self) + new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[ + digraph_char, "class:digraph" + ] + + def _show_key_processor_key_buffer(self, new_screen: Screen) -> None: + """ + When the user is typing a key binding that consists of several keys, + display the last pressed key if the user is in insert mode and the key + is meaningful to be displayed. + E.g. Some people want to bind 'jj' to escape in Vi insert mode. But the + first 'j' needs to be displayed in order to get some feedback. + """ + app = get_app() + key_buffer = app.key_processor.key_buffer + + if key_buffer and _in_insert_mode() and not app.is_done: + # The textual data for the given key. (Can be a VT100 escape + # sequence.) + data = key_buffer[-1].data + + # Display only if this is a 1 cell width character. + if get_cwidth(data) == 1: + cpos = new_screen.get_cursor_position(self) + new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[ + data, "class:partial-key-binding" + ] + + def _highlight_cursorlines( + self, new_screen: Screen, cpos: Point, x: int, y: int, width: int, height: int + ) -> None: + """ + Highlight cursor row/column. + """ + cursor_line_style = " class:cursor-line " + cursor_column_style = " class:cursor-column " + + data_buffer = new_screen.data_buffer + + # Highlight cursor line. + if self.cursorline(): + row = data_buffer[cpos.y] + for x in range(x, x + width): + original_char = row[x] + row[x] = _CHAR_CACHE[ + original_char.char, original_char.style + cursor_line_style + ] + + # Highlight cursor column. + if self.cursorcolumn(): + for y2 in range(y, y + height): + row = data_buffer[y2] + original_char = row[cpos.x] + row[cpos.x] = _CHAR_CACHE[ + original_char.char, original_char.style + cursor_column_style + ] + + # Highlight color columns + colorcolumns = self.colorcolumns + if callable(colorcolumns): + colorcolumns = colorcolumns() + + for cc in colorcolumns: + assert isinstance(cc, ColorColumn) + column = cc.position + + if column < x + width: # Only draw when visible. + color_column_style = " " + cc.style + + for y2 in range(y, y + height): + row = data_buffer[y2] + original_char = row[column + x] + row[column + x] = _CHAR_CACHE[ + original_char.char, original_char.style + color_column_style + ] + + def _copy_margin( + self, + margin_content: UIContent, + new_screen: Screen, + write_position: WritePosition, + move_x: int, + width: int, + ) -> None: + """ + Copy characters from the margin screen to the real screen. + """ + xpos = write_position.xpos + move_x + ypos = write_position.ypos + + margin_write_position = WritePosition(xpos, ypos, width, write_position.height) + self._copy_body(margin_content, new_screen, margin_write_position, 0, width) + + def _scroll(self, ui_content: UIContent, width: int, height: int) -> None: + """ + Scroll body. Ensure that the cursor is visible. + """ + if self.wrap_lines(): + func = self._scroll_when_linewrapping + else: + func = self._scroll_without_linewrapping + + func(ui_content, width, height) + + def _scroll_when_linewrapping( + self, ui_content: UIContent, width: int, height: int + ) -> None: + """ + Scroll to make sure the cursor position is visible and that we maintain + the requested scroll offset. + + Set `self.horizontal_scroll/vertical_scroll`. + """ + scroll_offsets_bottom = self.scroll_offsets.bottom + scroll_offsets_top = self.scroll_offsets.top + + # We don't have horizontal scrolling. + self.horizontal_scroll = 0 + + def get_line_height(lineno: int) -> int: + return ui_content.get_height_for_line(lineno, width, self.get_line_prefix) + + # When there is no space, reset `vertical_scroll_2` to zero and abort. + # This can happen if the margin is bigger than the window width. + # Otherwise the text height will become "infinite" (a big number) and + # the copy_line will spend a huge amount of iterations trying to render + # nothing. + if width <= 0: + self.vertical_scroll = ui_content.cursor_position.y + self.vertical_scroll_2 = 0 + return + + # If the current line consumes more than the whole window height, + # then we have to scroll vertically inside this line. (We don't take + # the scroll offsets into account for this.) + # Also, ignore the scroll offsets in this case. Just set the vertical + # scroll to this line. + line_height = get_line_height(ui_content.cursor_position.y) + if line_height > height - scroll_offsets_top: + # Calculate the height of the text before the cursor (including + # line prefixes). + text_before_height = ui_content.get_height_for_line( + ui_content.cursor_position.y, + width, + self.get_line_prefix, + slice_stop=ui_content.cursor_position.x, + ) + + # Adjust scroll offset. + self.vertical_scroll = ui_content.cursor_position.y + self.vertical_scroll_2 = min( + text_before_height - 1, # Keep the cursor visible. + line_height + - height, # Avoid blank lines at the bottom when scolling up again. + self.vertical_scroll_2, + ) + self.vertical_scroll_2 = max( + 0, text_before_height - height, self.vertical_scroll_2 + ) + return + else: + self.vertical_scroll_2 = 0 + + # Current line doesn't consume the whole height. Take scroll offsets into account. + def get_min_vertical_scroll() -> int: + # Make sure that the cursor line is not below the bottom. + # (Calculate how many lines can be shown between the cursor and the .) + used_height = 0 + prev_lineno = ui_content.cursor_position.y + + for lineno in range(ui_content.cursor_position.y, -1, -1): + used_height += get_line_height(lineno) + + if used_height > height - scroll_offsets_bottom: + return prev_lineno + else: + prev_lineno = lineno + return 0 + + def get_max_vertical_scroll() -> int: + # Make sure that the cursor line is not above the top. + prev_lineno = ui_content.cursor_position.y + used_height = 0 + + for lineno in range(ui_content.cursor_position.y - 1, -1, -1): + used_height += get_line_height(lineno) + + if used_height > scroll_offsets_top: + return prev_lineno + else: + prev_lineno = lineno + return prev_lineno + + def get_topmost_visible() -> int: + """ + Calculate the upper most line that can be visible, while the bottom + is still visible. We should not allow scroll more than this if + `allow_scroll_beyond_bottom` is false. + """ + prev_lineno = ui_content.line_count - 1 + used_height = 0 + for lineno in range(ui_content.line_count - 1, -1, -1): + used_height += get_line_height(lineno) + if used_height > height: + return prev_lineno + else: + prev_lineno = lineno + return prev_lineno + + # Scroll vertically. (Make sure that the whole line which contains the + # cursor is visible. + topmost_visible = get_topmost_visible() + + # Note: the `min(topmost_visible, ...)` is to make sure that we + # don't require scrolling up because of the bottom scroll offset, + # when we are at the end of the document. + self.vertical_scroll = max( + self.vertical_scroll, min(topmost_visible, get_min_vertical_scroll()) + ) + self.vertical_scroll = min(self.vertical_scroll, get_max_vertical_scroll()) + + # Disallow scrolling beyond bottom? + if not self.allow_scroll_beyond_bottom(): + self.vertical_scroll = min(self.vertical_scroll, topmost_visible) + + def _scroll_without_linewrapping( + self, ui_content: UIContent, width: int, height: int + ) -> None: + """ + Scroll to make sure the cursor position is visible and that we maintain + the requested scroll offset. + + Set `self.horizontal_scroll/vertical_scroll`. + """ + cursor_position = ui_content.cursor_position or Point(x=0, y=0) + + # Without line wrapping, we will never have to scroll vertically inside + # a single line. + self.vertical_scroll_2 = 0 + + if ui_content.line_count == 0: + self.vertical_scroll = 0 + self.horizontal_scroll = 0 + return + else: + current_line_text = fragment_list_to_text( + ui_content.get_line(cursor_position.y) + ) + + def do_scroll( + current_scroll: int, + scroll_offset_start: int, + scroll_offset_end: int, + cursor_pos: int, + window_size: int, + content_size: int, + ) -> int: + "Scrolling algorithm. Used for both horizontal and vertical scrolling." + # Calculate the scroll offset to apply. + # This can obviously never be more than have the screen size. Also, when the + # cursor appears at the top or bottom, we don't apply the offset. + scroll_offset_start = int( + min(scroll_offset_start, window_size / 2, cursor_pos) + ) + scroll_offset_end = int( + min(scroll_offset_end, window_size / 2, content_size - 1 - cursor_pos) + ) + + # Prevent negative scroll offsets. + if current_scroll < 0: + current_scroll = 0 + + # Scroll back if we scrolled to much and there's still space to show more of the document. + if ( + not self.allow_scroll_beyond_bottom() + and current_scroll > content_size - window_size + ): + current_scroll = max(0, content_size - window_size) + + # Scroll up if cursor is before visible part. + if current_scroll > cursor_pos - scroll_offset_start: + current_scroll = max(0, cursor_pos - scroll_offset_start) + + # Scroll down if cursor is after visible part. + if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end: + current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end + + return current_scroll + + # When a preferred scroll is given, take that first into account. + if self.get_vertical_scroll: + self.vertical_scroll = self.get_vertical_scroll(self) + assert isinstance(self.vertical_scroll, int) + if self.get_horizontal_scroll: + self.horizontal_scroll = self.get_horizontal_scroll(self) + assert isinstance(self.horizontal_scroll, int) + + # Update horizontal/vertical scroll to make sure that the cursor + # remains visible. + offsets = self.scroll_offsets + + self.vertical_scroll = do_scroll( + current_scroll=self.vertical_scroll, + scroll_offset_start=offsets.top, + scroll_offset_end=offsets.bottom, + cursor_pos=ui_content.cursor_position.y, + window_size=height, + content_size=ui_content.line_count, + ) + + if self.get_line_prefix: + current_line_prefix_width = fragment_list_width( + to_formatted_text(self.get_line_prefix(ui_content.cursor_position.y, 0)) + ) + else: + current_line_prefix_width = 0 + + self.horizontal_scroll = do_scroll( + current_scroll=self.horizontal_scroll, + scroll_offset_start=offsets.left, + scroll_offset_end=offsets.right, + cursor_pos=get_cwidth(current_line_text[: ui_content.cursor_position.x]), + window_size=width - current_line_prefix_width, + # We can only analyse the current line. Calculating the width off + # all the lines is too expensive. + content_size=max( + get_cwidth(current_line_text), self.horizontal_scroll + width + ), + ) + + def _mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + Mouse handler. Called when the UI control doesn't handle this + particular event. + + Return `NotImplemented` if nothing was done as a consequence of this + key binding (no UI invalidate required in that case). + """ + if mouse_event.event_type == MouseEventType.SCROLL_DOWN: + self._scroll_down() + return None + elif mouse_event.event_type == MouseEventType.SCROLL_UP: + self._scroll_up() + return None + + return NotImplemented + + def _scroll_down(self) -> None: + "Scroll window down." + info = self.render_info + + if info is None: + return + + if self.vertical_scroll < info.content_height - info.window_height: + if info.cursor_position.y <= info.configured_scroll_offsets.top: + self.content.move_cursor_down() + + self.vertical_scroll += 1 + + def _scroll_up(self) -> None: + "Scroll window up." + info = self.render_info + + if info is None: + return + + if info.vertical_scroll > 0: + # TODO: not entirely correct yet in case of line wrapping and long lines. + if ( + info.cursor_position.y + >= info.window_height - 1 - info.configured_scroll_offsets.bottom + ): + self.content.move_cursor_up() + + self.vertical_scroll -= 1 + + def get_key_bindings(self) -> Optional[KeyBindingsBase]: + return self.content.get_key_bindings() + + def get_children(self) -> List[Container]: + return [] + + +class ConditionalContainer(Container): + """ + Wrapper around any other container that can change the visibility. The + received `filter` determines whether the given container should be + displayed or not. + + :param content: :class:`.Container` instance. + :param filter: :class:`.Filter` instance. + """ + + def __init__(self, content: AnyContainer, filter: FilterOrBool) -> None: + self.content = to_container(content) + self.filter = to_filter(filter) + + def __repr__(self) -> str: + return f"ConditionalContainer({self.content!r}, filter={self.filter!r})" + + def reset(self) -> None: + self.content.reset() + + def preferred_width(self, max_available_width: int) -> Dimension: + if self.filter(): + return self.content.preferred_width(max_available_width) + else: + return Dimension.zero() + + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + if self.filter(): + return self.content.preferred_height(width, max_available_height) + else: + return Dimension.zero() + + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + if self.filter(): + return self.content.write_to_screen( + screen, mouse_handlers, write_position, parent_style, erase_bg, z_index + ) + + def get_children(self) -> List[Container]: + return [self.content] + + +class DynamicContainer(Container): + """ + Container class that dynamically returns any Container. + + :param get_container: Callable that returns a :class:`.Container` instance + or any widget with a ``__pt_container__`` method. + """ + + def __init__(self, get_container: Callable[[], AnyContainer]) -> None: + self.get_container = get_container + + def _get_container(self) -> Container: + """ + Return the current container object. + + We call `to_container`, because `get_container` can also return a + widget with a ``__pt_container__`` method. + """ + obj = self.get_container() + return to_container(obj) + + def reset(self) -> None: + self._get_container().reset() + + def preferred_width(self, max_available_width: int) -> Dimension: + return self._get_container().preferred_width(max_available_width) + + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + return self._get_container().preferred_height(width, max_available_height) + + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + self._get_container().write_to_screen( + screen, mouse_handlers, write_position, parent_style, erase_bg, z_index + ) + + def is_modal(self) -> bool: + return False + + def get_key_bindings(self) -> Optional[KeyBindingsBase]: + # Key bindings will be collected when `layout.walk()` finds the child + # container. + return None + + def get_children(self) -> List[Container]: + # Here we have to return the current active container itself, not its + # children. Otherwise, we run into issues where `layout.walk()` will + # never see an object of type `Window` if this contains a window. We + # can't/shouldn't proxy the "isinstance" check. + return [self._get_container()] + + +def to_container(container: AnyContainer) -> Container: + """ + Make sure that the given object is a :class:`.Container`. + """ + if isinstance(container, Container): + return container + elif hasattr(container, "__pt_container__"): + return to_container(container.__pt_container__()) + else: + raise ValueError(f"Not a container object: {container!r}") + + +def to_window(container: AnyContainer) -> Window: + """ + Make sure that the given argument is a :class:`.Window`. + """ + if isinstance(container, Window): + return container + elif hasattr(container, "__pt_container__"): + return to_window(cast("MagicContainer", container).__pt_container__()) + else: + raise ValueError(f"Not a Window object: {container!r}.") + + +def is_container(value: object) -> "TypeGuard[AnyContainer]": + """ + Checks whether the given value is a container object + (for use in assert statements). + """ + if isinstance(value, Container): + return True + if hasattr(value, "__pt_container__"): + return is_container(cast("MagicContainer", value).__pt_container__()) + return False diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/controls.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/controls.py new file mode 100644 index 0000000..016d289 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/controls.py @@ -0,0 +1,957 @@ +""" +User interface Controls for the layout. +""" +import time +from abc import ABCMeta, abstractmethod +from typing import ( + TYPE_CHECKING, + Callable, + Dict, + Hashable, + Iterable, + List, + NamedTuple, + Optional, + Union, +) + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.cache import SimpleCache +from prompt_toolkit.data_structures import Point +from prompt_toolkit.document import Document +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.formatted_text import ( + AnyFormattedText, + StyleAndTextTuples, + to_formatted_text, +) +from prompt_toolkit.formatted_text.utils import ( + fragment_list_to_text, + fragment_list_width, + split_lines, +) +from prompt_toolkit.lexers import Lexer, SimpleLexer +from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType +from prompt_toolkit.search import SearchState +from prompt_toolkit.selection import SelectionType +from prompt_toolkit.utils import get_cwidth + +from .processors import ( + DisplayMultipleCursors, + HighlightIncrementalSearchProcessor, + HighlightSearchProcessor, + HighlightSelectionProcessor, + Processor, + TransformationInput, + merge_processors, +) + +if TYPE_CHECKING: + from prompt_toolkit.key_binding.key_bindings import ( + KeyBindingsBase, + NotImplementedOrNone, + ) + from prompt_toolkit.utils import Event + + +__all__ = [ + "BufferControl", + "SearchBufferControl", + "DummyControl", + "FormattedTextControl", + "UIControl", + "UIContent", +] + +GetLinePrefixCallable = Callable[[int, int], AnyFormattedText] + + +class UIControl(metaclass=ABCMeta): + """ + Base class for all user interface controls. + """ + + def reset(self) -> None: + # Default reset. (Doesn't have to be implemented.) + pass + + def preferred_width(self, max_available_width: int) -> Optional[int]: + return None + + def preferred_height( + self, + width: int, + max_available_height: int, + wrap_lines: bool, + get_line_prefix: Optional[GetLinePrefixCallable], + ) -> Optional[int]: + return None + + def is_focusable(self) -> bool: + """ + Tell whether this user control is focusable. + """ + return False + + @abstractmethod + def create_content(self, width: int, height: int) -> "UIContent": + """ + Generate the content for this user control. + + Returns a :class:`.UIContent` instance. + """ + + def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + Handle mouse events. + + When `NotImplemented` is returned, it means that the given event is not + handled by the `UIControl` itself. The `Window` or key bindings can + decide to handle this event as scrolling or changing focus. + + :param mouse_event: `MouseEvent` instance. + """ + return NotImplemented + + def move_cursor_down(self) -> None: + """ + Request to move the cursor down. + This happens when scrolling down and the cursor is completely at the + top. + """ + + def move_cursor_up(self) -> None: + """ + Request to move the cursor up. + """ + + def get_key_bindings(self) -> Optional["KeyBindingsBase"]: + """ + The key bindings that are specific for this user control. + + Return a :class:`.KeyBindings` object if some key bindings are + specified, or `None` otherwise. + """ + + def get_invalidate_events(self) -> Iterable["Event[object]"]: + """ + Return a list of `Event` objects. This can be a generator. + (The application collects all these events, in order to bind redraw + handlers to these events.) + """ + return [] + + +class UIContent: + """ + Content generated by a user control. This content consists of a list of + lines. + + :param get_line: Callable that takes a line number and returns the current + line. This is a list of (style_str, text) tuples. + :param line_count: The number of lines. + :param cursor_position: a :class:`.Point` for the cursor position. + :param menu_position: a :class:`.Point` for the menu position. + :param show_cursor: Make the cursor visible. + """ + + def __init__( + self, + get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []), + line_count: int = 0, + cursor_position: Optional[Point] = None, + menu_position: Optional[Point] = None, + show_cursor: bool = True, + ): + + self.get_line = get_line + self.line_count = line_count + self.cursor_position = cursor_position or Point(x=0, y=0) + self.menu_position = menu_position + self.show_cursor = show_cursor + + # Cache for line heights. Maps cache key -> height + self._line_heights_cache: Dict[Hashable, int] = {} + + def __getitem__(self, lineno: int) -> StyleAndTextTuples: + "Make it iterable (iterate line by line)." + if lineno < self.line_count: + return self.get_line(lineno) + else: + raise IndexError + + def get_height_for_line( + self, + lineno: int, + width: int, + get_line_prefix: Optional[GetLinePrefixCallable], + slice_stop: Optional[int] = None, + ) -> int: + """ + Return the height that a given line would need if it is rendered in a + space with the given width (using line wrapping). + + :param get_line_prefix: None or a `Window.get_line_prefix` callable + that returns the prefix to be inserted before this line. + :param slice_stop: Wrap only "line[:slice_stop]" and return that + partial result. This is needed for scrolling the window correctly + when line wrapping. + :returns: The computed height. + """ + # Instead of using `get_line_prefix` as key, we use render_counter + # instead. This is more reliable, because this function could still be + # the same, while the content would change over time. + key = get_app().render_counter, lineno, width, slice_stop + + try: + return self._line_heights_cache[key] + except KeyError: + if width == 0: + height = 10**8 + else: + # Calculate line width first. + line = fragment_list_to_text(self.get_line(lineno))[:slice_stop] + text_width = get_cwidth(line) + + if get_line_prefix: + # Add prefix width. + text_width += fragment_list_width( + to_formatted_text(get_line_prefix(lineno, 0)) + ) + + # Slower path: compute path when there's a line prefix. + height = 1 + + # Keep wrapping as long as the line doesn't fit. + # Keep adding new prefixes for every wrapped line. + while text_width > width: + height += 1 + text_width -= width + + fragments2 = to_formatted_text( + get_line_prefix(lineno, height - 1) + ) + prefix_width = get_cwidth(fragment_list_to_text(fragments2)) + + if prefix_width >= width: # Prefix doesn't fit. + height = 10**8 + break + + text_width += prefix_width + else: + # Fast path: compute height when there's no line prefix. + try: + quotient, remainder = divmod(text_width, width) + except ZeroDivisionError: + height = 10**8 + else: + if remainder: + quotient += 1 # Like math.ceil. + height = max(1, quotient) + + # Cache and return + self._line_heights_cache[key] = height + return height + + +class FormattedTextControl(UIControl): + """ + Control that displays formatted text. This can be either plain text, an + :class:`~prompt_toolkit.formatted_text.HTML` object an + :class:`~prompt_toolkit.formatted_text.ANSI` object, a list of ``(style_str, + text)`` tuples or a callable that takes no argument and returns one of + those, depending on how you prefer to do the formatting. See + ``prompt_toolkit.layout.formatted_text`` for more information. + + (It's mostly optimized for rather small widgets, like toolbars, menus, etc...) + + When this UI control has the focus, the cursor will be shown in the upper + left corner of this control by default. There are two ways for specifying + the cursor position: + + - Pass a `get_cursor_position` function which returns a `Point` instance + with the current cursor position. + + - If the (formatted) text is passed as a list of ``(style, text)`` tuples + and there is one that looks like ``('[SetCursorPosition]', '')``, then + this will specify the cursor position. + + Mouse support: + + The list of fragments can also contain tuples of three items, looking like: + (style_str, text, handler). When mouse support is enabled and the user + clicks on this fragment, then the given handler is called. That handler + should accept two inputs: (Application, MouseEvent) and it should + either handle the event or return `NotImplemented` in case we want the + containing Window to handle this event. + + :param focusable: `bool` or :class:`.Filter`: Tell whether this control is + focusable. + + :param text: Text or formatted text to be displayed. + :param style: Style string applied to the content. (If you want to style + the whole :class:`~prompt_toolkit.layout.Window`, pass the style to the + :class:`~prompt_toolkit.layout.Window` instead.) + :param key_bindings: a :class:`.KeyBindings` object. + :param get_cursor_position: A callable that returns the cursor position as + a `Point` instance. + """ + + def __init__( + self, + text: AnyFormattedText = "", + style: str = "", + focusable: FilterOrBool = False, + key_bindings: Optional["KeyBindingsBase"] = None, + show_cursor: bool = True, + modal: bool = False, + get_cursor_position: Optional[Callable[[], Optional[Point]]] = None, + ) -> None: + + self.text = text # No type check on 'text'. This is done dynamically. + self.style = style + self.focusable = to_filter(focusable) + + # Key bindings. + self.key_bindings = key_bindings + self.show_cursor = show_cursor + self.modal = modal + self.get_cursor_position = get_cursor_position + + #: Cache for the content. + self._content_cache: SimpleCache[Hashable, UIContent] = SimpleCache(maxsize=18) + self._fragment_cache: SimpleCache[int, StyleAndTextTuples] = SimpleCache( + maxsize=1 + ) + # Only cache one fragment list. We don't need the previous item. + + # Render info for the mouse support. + self._fragments: Optional[StyleAndTextTuples] = None + + def reset(self) -> None: + self._fragments = None + + def is_focusable(self) -> bool: + return self.focusable() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.text!r})" + + def _get_formatted_text_cached(self) -> StyleAndTextTuples: + """ + Get fragments, but only retrieve fragments once during one render run. + (This function is called several times during one rendering, because + we also need those for calculating the dimensions.) + """ + return self._fragment_cache.get( + get_app().render_counter, lambda: to_formatted_text(self.text, self.style) + ) + + def preferred_width(self, max_available_width: int) -> int: + """ + Return the preferred width for this control. + That is the width of the longest line. + """ + text = fragment_list_to_text(self._get_formatted_text_cached()) + line_lengths = [get_cwidth(l) for l in text.split("\n")] + return max(line_lengths) + + def preferred_height( + self, + width: int, + max_available_height: int, + wrap_lines: bool, + get_line_prefix: Optional[GetLinePrefixCallable], + ) -> Optional[int]: + """ + Return the preferred height for this control. + """ + content = self.create_content(width, None) + if wrap_lines: + height = 0 + for i in range(content.line_count): + height += content.get_height_for_line(i, width, get_line_prefix) + if height >= max_available_height: + return max_available_height + return height + else: + return content.line_count + + def create_content(self, width: int, height: Optional[int]) -> UIContent: + # Get fragments + fragments_with_mouse_handlers = self._get_formatted_text_cached() + fragment_lines_with_mouse_handlers = list( + split_lines(fragments_with_mouse_handlers) + ) + + # Strip mouse handlers from fragments. + fragment_lines: List[StyleAndTextTuples] = [ + [(item[0], item[1]) for item in line] + for line in fragment_lines_with_mouse_handlers + ] + + # Keep track of the fragments with mouse handler, for later use in + # `mouse_handler`. + self._fragments = fragments_with_mouse_handlers + + # If there is a `[SetCursorPosition]` in the fragment list, set the + # cursor position here. + def get_cursor_position( + fragment: str = "[SetCursorPosition]", + ) -> Optional[Point]: + for y, line in enumerate(fragment_lines): + x = 0 + for style_str, text, *_ in line: + if fragment in style_str: + return Point(x=x, y=y) + x += len(text) + return None + + # If there is a `[SetMenuPosition]`, set the menu over here. + def get_menu_position() -> Optional[Point]: + return get_cursor_position("[SetMenuPosition]") + + cursor_position = (self.get_cursor_position or get_cursor_position)() + + # Create content, or take it from the cache. + key = (tuple(fragments_with_mouse_handlers), width, cursor_position) + + def get_content() -> UIContent: + return UIContent( + get_line=lambda i: fragment_lines[i], + line_count=len(fragment_lines), + show_cursor=self.show_cursor, + cursor_position=cursor_position, + menu_position=get_menu_position(), + ) + + return self._content_cache.get(key, get_content) + + def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + Handle mouse events. + + (When the fragment list contained mouse handlers and the user clicked on + on any of these, the matching handler is called. This handler can still + return `NotImplemented` in case we want the + :class:`~prompt_toolkit.layout.Window` to handle this particular + event.) + """ + if self._fragments: + # Read the generator. + fragments_for_line = list(split_lines(self._fragments)) + + try: + fragments = fragments_for_line[mouse_event.position.y] + except IndexError: + return NotImplemented + else: + # Find position in the fragment list. + xpos = mouse_event.position.x + + # Find mouse handler for this character. + count = 0 + for item in fragments: + count += len(item[1]) + if count > xpos: + if len(item) >= 3: + # Handler found. Call it. + # (Handler can return NotImplemented, so return + # that result.) + handler = item[2] # type: ignore + return handler(mouse_event) + else: + break + + # Otherwise, don't handle here. + return NotImplemented + + def is_modal(self) -> bool: + return self.modal + + def get_key_bindings(self) -> Optional["KeyBindingsBase"]: + return self.key_bindings + + +class DummyControl(UIControl): + """ + A dummy control object that doesn't paint any content. + + Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The + `fragment` and `char` attributes of the `Window` class can be used to + define the filling.) + """ + + def create_content(self, width: int, height: int) -> UIContent: + def get_line(i: int) -> StyleAndTextTuples: + return [] + + return UIContent( + get_line=get_line, line_count=100**100 + ) # Something very big. + + def is_focusable(self) -> bool: + return False + + +class _ProcessedLine(NamedTuple): + fragments: StyleAndTextTuples + source_to_display: Callable[[int], int] + display_to_source: Callable[[int], int] + + +class BufferControl(UIControl): + """ + Control for visualising the content of a :class:`.Buffer`. + + :param buffer: The :class:`.Buffer` object to be displayed. + :param input_processors: A list of + :class:`~prompt_toolkit.layout.processors.Processor` objects. + :param include_default_input_processors: When True, include the default + processors for highlighting of selection, search and displaying of + multiple cursors. + :param lexer: :class:`.Lexer` instance for syntax highlighting. + :param preview_search: `bool` or :class:`.Filter`: Show search while + typing. When this is `True`, probably you want to add a + ``HighlightIncrementalSearchProcessor`` as well. Otherwise only the + cursor position will move, but the text won't be highlighted. + :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable. + :param focus_on_click: Focus this buffer when it's click, but not yet focused. + :param key_bindings: a :class:`.KeyBindings` object. + """ + + def __init__( + self, + buffer: Optional[Buffer] = None, + input_processors: Optional[List[Processor]] = None, + include_default_input_processors: bool = True, + lexer: Optional[Lexer] = None, + preview_search: FilterOrBool = False, + focusable: FilterOrBool = True, + search_buffer_control: Union[ + None, "SearchBufferControl", Callable[[], "SearchBufferControl"] + ] = None, + menu_position: Optional[Callable[[], Optional[int]]] = None, + focus_on_click: FilterOrBool = False, + key_bindings: Optional["KeyBindingsBase"] = None, + ): + + self.input_processors = input_processors + self.include_default_input_processors = include_default_input_processors + + self.default_input_processors = [ + HighlightSearchProcessor(), + HighlightIncrementalSearchProcessor(), + HighlightSelectionProcessor(), + DisplayMultipleCursors(), + ] + + self.preview_search = to_filter(preview_search) + self.focusable = to_filter(focusable) + self.focus_on_click = to_filter(focus_on_click) + + self.buffer = buffer or Buffer() + self.menu_position = menu_position + self.lexer = lexer or SimpleLexer() + self.key_bindings = key_bindings + self._search_buffer_control = search_buffer_control + + #: Cache for the lexer. + #: Often, due to cursor movement, undo/redo and window resizing + #: operations, it happens that a short time, the same document has to be + #: lexed. This is a fairly easy way to cache such an expensive operation. + self._fragment_cache: SimpleCache[ + Hashable, Callable[[int], StyleAndTextTuples] + ] = SimpleCache(maxsize=8) + + self._last_click_timestamp: Optional[float] = None + self._last_get_processed_line: Optional[Callable[[int], _ProcessedLine]] = None + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} buffer={self.buffer!r} at {id(self)!r}>" + + @property + def search_buffer_control(self) -> Optional["SearchBufferControl"]: + result: Optional[SearchBufferControl] + + if callable(self._search_buffer_control): + result = self._search_buffer_control() + else: + result = self._search_buffer_control + + assert result is None or isinstance(result, SearchBufferControl) + return result + + @property + def search_buffer(self) -> Optional[Buffer]: + control = self.search_buffer_control + if control is not None: + return control.buffer + return None + + @property + def search_state(self) -> SearchState: + """ + Return the `SearchState` for searching this `BufferControl`. This is + always associated with the search control. If one search bar is used + for searching multiple `BufferControls`, then they share the same + `SearchState`. + """ + search_buffer_control = self.search_buffer_control + if search_buffer_control: + return search_buffer_control.searcher_search_state + else: + return SearchState() + + def is_focusable(self) -> bool: + return self.focusable() + + def preferred_width(self, max_available_width: int) -> Optional[int]: + """ + This should return the preferred width. + + Note: We don't specify a preferred width according to the content, + because it would be too expensive. Calculating the preferred + width can be done by calculating the longest line, but this would + require applying all the processors to each line. This is + unfeasible for a larger document, and doing it for small + documents only would result in inconsistent behaviour. + """ + return None + + def preferred_height( + self, + width: int, + max_available_height: int, + wrap_lines: bool, + get_line_prefix: Optional[GetLinePrefixCallable], + ) -> Optional[int]: + + # Calculate the content height, if it was drawn on a screen with the + # given width. + height = 0 + content = self.create_content(width, height=1) # Pass a dummy '1' as height. + + # When line wrapping is off, the height should be equal to the amount + # of lines. + if not wrap_lines: + return content.line_count + + # When the number of lines exceeds the max_available_height, just + # return max_available_height. No need to calculate anything. + if content.line_count >= max_available_height: + return max_available_height + + for i in range(content.line_count): + height += content.get_height_for_line(i, width, get_line_prefix) + + if height >= max_available_height: + return max_available_height + + return height + + def _get_formatted_text_for_line_func( + self, document: Document + ) -> Callable[[int], StyleAndTextTuples]: + """ + Create a function that returns the fragments for a given line. + """ + # Cache using `document.text`. + def get_formatted_text_for_line() -> Callable[[int], StyleAndTextTuples]: + return self.lexer.lex_document(document) + + key = (document.text, self.lexer.invalidation_hash()) + return self._fragment_cache.get(key, get_formatted_text_for_line) + + def _create_get_processed_line_func( + self, document: Document, width: int, height: int + ) -> Callable[[int], _ProcessedLine]: + """ + Create a function that takes a line number of the current document and + returns a _ProcessedLine(processed_fragments, source_to_display, display_to_source) + tuple. + """ + # Merge all input processors together. + input_processors = self.input_processors or [] + if self.include_default_input_processors: + input_processors = self.default_input_processors + input_processors + + merged_processor = merge_processors(input_processors) + + def transform(lineno: int, fragments: StyleAndTextTuples) -> _ProcessedLine: + "Transform the fragments for a given line number." + # Get cursor position at this line. + def source_to_display(i: int) -> int: + """X position from the buffer to the x position in the + processed fragment list. By default, we start from the 'identity' + operation.""" + return i + + transformation = merged_processor.apply_transformation( + TransformationInput( + self, document, lineno, source_to_display, fragments, width, height + ) + ) + + return _ProcessedLine( + transformation.fragments, + transformation.source_to_display, + transformation.display_to_source, + ) + + def create_func() -> Callable[[int], _ProcessedLine]: + get_line = self._get_formatted_text_for_line_func(document) + cache: Dict[int, _ProcessedLine] = {} + + def get_processed_line(i: int) -> _ProcessedLine: + try: + return cache[i] + except KeyError: + processed_line = transform(i, get_line(i)) + cache[i] = processed_line + return processed_line + + return get_processed_line + + return create_func() + + def create_content( + self, width: int, height: int, preview_search: bool = False + ) -> UIContent: + """ + Create a UIContent. + """ + buffer = self.buffer + + # Trigger history loading of the buffer. We do this during the + # rendering of the UI here, because it needs to happen when an + # `Application` with its event loop is running. During the rendering of + # the buffer control is the earliest place we can achieve this, where + # we're sure the right event loop is active, and don't require user + # interaction (like in a key binding). + buffer.load_history_if_not_yet_loaded() + + # Get the document to be shown. If we are currently searching (the + # search buffer has focus, and the preview_search filter is enabled), + # then use the search document, which has possibly a different + # text/cursor position.) + search_control = self.search_buffer_control + preview_now = preview_search or bool( + # Only if this feature is enabled. + self.preview_search() + and + # And something was typed in the associated search field. + search_control + and search_control.buffer.text + and + # And we are searching in this control. (Many controls can point to + # the same search field, like in Pyvim.) + get_app().layout.search_target_buffer_control == self + ) + + if preview_now and search_control is not None: + ss = self.search_state + + document = buffer.document_for_search( + SearchState( + text=search_control.buffer.text, + direction=ss.direction, + ignore_case=ss.ignore_case, + ) + ) + else: + document = buffer.document + + get_processed_line = self._create_get_processed_line_func( + document, width, height + ) + self._last_get_processed_line = get_processed_line + + def translate_rowcol(row: int, col: int) -> Point: + "Return the content column for this coordinate." + return Point(x=get_processed_line(row).source_to_display(col), y=row) + + def get_line(i: int) -> StyleAndTextTuples: + "Return the fragments for a given line number." + fragments = get_processed_line(i).fragments + + # Add a space at the end, because that is a possible cursor + # position. (When inserting after the input.) We should do this on + # all the lines, not just the line containing the cursor. (Because + # otherwise, line wrapping/scrolling could change when moving the + # cursor around.) + fragments = fragments + [("", " ")] + return fragments + + content = UIContent( + get_line=get_line, + line_count=document.line_count, + cursor_position=translate_rowcol( + document.cursor_position_row, document.cursor_position_col + ), + ) + + # If there is an auto completion going on, use that start point for a + # pop-up menu position. (But only when this buffer has the focus -- + # there is only one place for a menu, determined by the focused buffer.) + if get_app().layout.current_control == self: + menu_position = self.menu_position() if self.menu_position else None + if menu_position is not None: + assert isinstance(menu_position, int) + menu_row, menu_col = buffer.document.translate_index_to_position( + menu_position + ) + content.menu_position = translate_rowcol(menu_row, menu_col) + elif buffer.complete_state: + # Position for completion menu. + # Note: We use 'min', because the original cursor position could be + # behind the input string when the actual completion is for + # some reason shorter than the text we had before. (A completion + # can change and shorten the input.) + menu_row, menu_col = buffer.document.translate_index_to_position( + min( + buffer.cursor_position, + buffer.complete_state.original_document.cursor_position, + ) + ) + content.menu_position = translate_rowcol(menu_row, menu_col) + else: + content.menu_position = None + + return content + + def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + Mouse handler for this control. + """ + buffer = self.buffer + position = mouse_event.position + + # Focus buffer when clicked. + if get_app().layout.current_control == self: + if self._last_get_processed_line: + processed_line = self._last_get_processed_line(position.y) + + # Translate coordinates back to the cursor position of the + # original input. + xpos = processed_line.display_to_source(position.x) + index = buffer.document.translate_row_col_to_index(position.y, xpos) + + # Set the cursor position. + if mouse_event.event_type == MouseEventType.MOUSE_DOWN: + buffer.exit_selection() + buffer.cursor_position = index + + elif ( + mouse_event.event_type == MouseEventType.MOUSE_MOVE + and mouse_event.button != MouseButton.NONE + ): + # Click and drag to highlight a selection + if ( + buffer.selection_state is None + and abs(buffer.cursor_position - index) > 0 + ): + buffer.start_selection(selection_type=SelectionType.CHARACTERS) + buffer.cursor_position = index + + elif mouse_event.event_type == MouseEventType.MOUSE_UP: + # When the cursor was moved to another place, select the text. + # (The >1 is actually a small but acceptable workaround for + # selecting text in Vi navigation mode. In navigation mode, + # the cursor can never be after the text, so the cursor + # will be repositioned automatically.) + if abs(buffer.cursor_position - index) > 1: + if buffer.selection_state is None: + buffer.start_selection( + selection_type=SelectionType.CHARACTERS + ) + buffer.cursor_position = index + + # Select word around cursor on double click. + # Two MOUSE_UP events in a short timespan are considered a double click. + double_click = ( + self._last_click_timestamp + and time.time() - self._last_click_timestamp < 0.3 + ) + self._last_click_timestamp = time.time() + + if double_click: + start, end = buffer.document.find_boundaries_of_current_word() + buffer.cursor_position += start + buffer.start_selection(selection_type=SelectionType.CHARACTERS) + buffer.cursor_position += end - start + else: + # Don't handle scroll events here. + return NotImplemented + + # Not focused, but focusing on click events. + else: + if ( + self.focus_on_click() + and mouse_event.event_type == MouseEventType.MOUSE_UP + ): + # Focus happens on mouseup. (If we did this on mousedown, the + # up event will be received at the point where this widget is + # focused and be handled anyway.) + get_app().layout.current_control = self + else: + return NotImplemented + + return None + + def move_cursor_down(self) -> None: + b = self.buffer + b.cursor_position += b.document.get_cursor_down_position() + + def move_cursor_up(self) -> None: + b = self.buffer + b.cursor_position += b.document.get_cursor_up_position() + + def get_key_bindings(self) -> Optional["KeyBindingsBase"]: + """ + When additional key bindings are given. Return these. + """ + return self.key_bindings + + def get_invalidate_events(self) -> Iterable["Event[object]"]: + """ + Return the Window invalidate events. + """ + # Whenever the buffer changes, the UI has to be updated. + yield self.buffer.on_text_changed + yield self.buffer.on_cursor_position_changed + + yield self.buffer.on_completions_changed + yield self.buffer.on_suggestion_set + + +class SearchBufferControl(BufferControl): + """ + :class:`.BufferControl` which is used for searching another + :class:`.BufferControl`. + + :param ignore_case: Search case insensitive. + """ + + def __init__( + self, + buffer: Optional[Buffer] = None, + input_processors: Optional[List[Processor]] = None, + lexer: Optional[Lexer] = None, + focus_on_click: FilterOrBool = False, + key_bindings: Optional["KeyBindingsBase"] = None, + ignore_case: FilterOrBool = False, + ): + + super().__init__( + buffer=buffer, + input_processors=input_processors, + lexer=lexer, + focus_on_click=focus_on_click, + key_bindings=key_bindings, + ) + + # If this BufferControl is used as a search field for one or more other + # BufferControls, then represents the search state. + self.searcher_search_state = SearchState(ignore_case=ignore_case) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/dimension.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/dimension.py new file mode 100644 index 0000000..04c2163 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/dimension.py @@ -0,0 +1,217 @@ +""" +Layout dimensions are used to give the minimum, maximum and preferred +dimensions for containers and controls. +""" +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union + +__all__ = [ + "Dimension", + "D", + "sum_layout_dimensions", + "max_layout_dimensions", + "AnyDimension", + "to_dimension", + "is_dimension", +] + +if TYPE_CHECKING: + from typing_extensions import TypeGuard + + +class Dimension: + """ + Specified dimension (width/height) of a user control or window. + + The layout engine tries to honor the preferred size. If that is not + possible, because the terminal is larger or smaller, it tries to keep in + between min and max. + + :param min: Minimum size. + :param max: Maximum size. + :param weight: For a VSplit/HSplit, the actual size will be determined + by taking the proportion of weights from all the children. + E.g. When there are two children, one with a weight of 1, + and the other with a weight of 2, the second will always be + twice as big as the first, if the min/max values allow it. + :param preferred: Preferred size. + """ + + def __init__( + self, + min: Optional[int] = None, + max: Optional[int] = None, + weight: Optional[int] = None, + preferred: Optional[int] = None, + ) -> None: + if weight is not None: + assert weight >= 0 # Also cannot be a float. + + assert min is None or min >= 0 + assert max is None or max >= 0 + assert preferred is None or preferred >= 0 + + self.min_specified = min is not None + self.max_specified = max is not None + self.preferred_specified = preferred is not None + self.weight_specified = weight is not None + + if min is None: + min = 0 # Smallest possible value. + if max is None: # 0-values are allowed, so use "is None" + max = 1000**10 # Something huge. + if preferred is None: + preferred = min + if weight is None: + weight = 1 + + self.min = min + self.max = max + self.preferred = preferred + self.weight = weight + + # Don't allow situations where max < min. (This would be a bug.) + if max < min: + raise ValueError("Invalid Dimension: max < min.") + + # Make sure that the 'preferred' size is always in the min..max range. + if self.preferred < self.min: + self.preferred = self.min + + if self.preferred > self.max: + self.preferred = self.max + + @classmethod + def exact(cls, amount: int) -> "Dimension": + """ + Return a :class:`.Dimension` with an exact size. (min, max and + preferred set to ``amount``). + """ + return cls(min=amount, max=amount, preferred=amount) + + @classmethod + def zero(cls) -> "Dimension": + """ + Create a dimension that represents a zero size. (Used for 'invisible' + controls.) + """ + return cls.exact(amount=0) + + def is_zero(self) -> bool: + "True if this `Dimension` represents a zero size." + return self.preferred == 0 or self.max == 0 + + def __repr__(self) -> str: + fields = [] + if self.min_specified: + fields.append("min=%r" % self.min) + if self.max_specified: + fields.append("max=%r" % self.max) + if self.preferred_specified: + fields.append("preferred=%r" % self.preferred) + if self.weight_specified: + fields.append("weight=%r" % self.weight) + + return "Dimension(%s)" % ", ".join(fields) + + +def sum_layout_dimensions(dimensions: List[Dimension]) -> Dimension: + """ + Sum a list of :class:`.Dimension` instances. + """ + min = sum(d.min for d in dimensions) + max = sum(d.max for d in dimensions) + preferred = sum(d.preferred for d in dimensions) + + return Dimension(min=min, max=max, preferred=preferred) + + +def max_layout_dimensions(dimensions: List[Dimension]) -> Dimension: + """ + Take the maximum of a list of :class:`.Dimension` instances. + Used when we have a HSplit/VSplit, and we want to get the best width/height.) + """ + if not len(dimensions): + return Dimension.zero() + + # If all dimensions are size zero. Return zero. + # (This is important for HSplit/VSplit, to report the right values to their + # parent when all children are invisible.) + if all(d.is_zero() for d in dimensions): + return dimensions[0] + + # Ignore empty dimensions. (They should not reduce the size of others.) + dimensions = [d for d in dimensions if not d.is_zero()] + + if dimensions: + # Take the highest minimum dimension. + min_ = max(d.min for d in dimensions) + + # For the maximum, we would prefer not to go larger than then smallest + # 'max' value, unless other dimensions have a bigger preferred value. + # This seems to work best: + # - We don't want that a widget with a small height in a VSplit would + # shrink other widgets in the split. + # If it doesn't work well enough, then it's up to the UI designer to + # explicitly pass dimensions. + max_ = min(d.max for d in dimensions) + max_ = max(max_, max(d.preferred for d in dimensions)) + + # Make sure that min>=max. In some scenarios, when certain min..max + # ranges don't have any overlap, we can end up in such an impossible + # situation. In that case, give priority to the max value. + # E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8). + if min_ > max_: + max_ = min_ + + preferred = max(d.preferred for d in dimensions) + + return Dimension(min=min_, max=max_, preferred=preferred) + else: + return Dimension() + + +# Anything that can be converted to a dimension. +AnyDimension = Union[ + None, # None is a valid dimension that will fit anything. + int, + Dimension, + # Callable[[], 'AnyDimension'] # Recursive definition not supported by mypy. + Callable[[], Any], +] + + +def to_dimension(value: AnyDimension) -> Dimension: + """ + Turn the given object into a `Dimension` object. + """ + if value is None: + return Dimension() + if isinstance(value, int): + return Dimension.exact(value) + if isinstance(value, Dimension): + return value + if callable(value): + return to_dimension(value()) + + raise ValueError("Not an integer or Dimension object.") + + +def is_dimension(value: object) -> "TypeGuard[AnyDimension]": + """ + Test whether the given value could be a valid dimension. + (For usage in an assertion. It's not guaranteed in case of a callable.) + """ + if value is None: + return True + if callable(value): + return True # Assume it's a callable that doesn't take arguments. + if isinstance(value, (int, Dimension)): + return True + return False + + +# Common alias. +D = Dimension + +# For backward-compatibility. +LayoutDimension = Dimension diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/dummy.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/dummy.py new file mode 100644 index 0000000..dcd54e9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/dummy.py @@ -0,0 +1,37 @@ +""" +Dummy layout. Used when somebody creates an `Application` without specifying a +`Layout`. +""" +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.key_processor import KeyPressEvent + +from .containers import Window +from .controls import FormattedTextControl +from .dimension import D +from .layout import Layout + +__all__ = [ + "create_dummy_layout", +] + +E = KeyPressEvent + + +def create_dummy_layout() -> Layout: + """ + Create a dummy layout for use in an 'Application' that doesn't have a + layout specified. When ENTER is pressed, the application quits. + """ + kb = KeyBindings() + + @kb.add("enter") + def enter(event: E) -> None: + event.app.exit() + + control = FormattedTextControl( + HTML("No layout specified. Press ENTER to quit."), + key_bindings=kb, + ) + window = Window(content=control, height=D(min=1)) + return Layout(container=window, focused_element=window) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/layout.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/layout.py new file mode 100644 index 0000000..62a3184 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/layout.py @@ -0,0 +1,411 @@ +""" +Wrapper for the layout. +""" +from typing import Dict, Generator, Iterable, List, Optional, Union + +from prompt_toolkit.buffer import Buffer + +from .containers import ( + AnyContainer, + ConditionalContainer, + Container, + Window, + to_container, +) +from .controls import BufferControl, SearchBufferControl, UIControl + +__all__ = [ + "Layout", + "InvalidLayoutError", + "walk", +] + +FocusableElement = Union[str, Buffer, UIControl, AnyContainer] + + +class Layout: + """ + The layout for a prompt_toolkit + :class:`~prompt_toolkit.application.Application`. + This also keeps track of which user control is focused. + + :param container: The "root" container for the layout. + :param focused_element: element to be focused initially. (Can be anything + the `focus` function accepts.) + """ + + def __init__( + self, + container: AnyContainer, + focused_element: Optional[FocusableElement] = None, + ) -> None: + + self.container = to_container(container) + self._stack: List[Window] = [] + + # Map search BufferControl back to the original BufferControl. + # This is used to keep track of when exactly we are searching, and for + # applying the search. + # When a link exists in this dictionary, that means the search is + # currently active. + # Map: search_buffer_control -> original buffer control. + self.search_links: Dict[SearchBufferControl, BufferControl] = {} + + # Mapping that maps the children in the layout to their parent. + # This relationship is calculated dynamically, each time when the UI + # is rendered. (UI elements have only references to their children.) + self._child_to_parent: Dict[Container, Container] = {} + + if focused_element is None: + try: + self._stack.append(next(self.find_all_windows())) + except StopIteration as e: + raise InvalidLayoutError( + "Invalid layout. The layout does not contain any Window object." + ) from e + else: + self.focus(focused_element) + + # List of visible windows. + self.visible_windows: List[Window] = [] # List of `Window` objects. + + def __repr__(self) -> str: + return f"Layout({self.container!r}, current_window={self.current_window!r})" + + def find_all_windows(self) -> Generator[Window, None, None]: + """ + Find all the :class:`.UIControl` objects in this layout. + """ + for item in self.walk(): + if isinstance(item, Window): + yield item + + def find_all_controls(self) -> Iterable[UIControl]: + for container in self.find_all_windows(): + yield container.content + + def focus(self, value: FocusableElement) -> None: + """ + Focus the given UI element. + + `value` can be either: + + - a :class:`.UIControl` + - a :class:`.Buffer` instance or the name of a :class:`.Buffer` + - a :class:`.Window` + - Any container object. In this case we will focus the :class:`.Window` + from this container that was focused most recent, or the very first + focusable :class:`.Window` of the container. + """ + # BufferControl by buffer name. + if isinstance(value, str): + for control in self.find_all_controls(): + if isinstance(control, BufferControl) and control.buffer.name == value: + self.focus(control) + return + raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.") + + # BufferControl by buffer object. + elif isinstance(value, Buffer): + for control in self.find_all_controls(): + if isinstance(control, BufferControl) and control.buffer == value: + self.focus(control) + return + raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.") + + # Focus UIControl. + elif isinstance(value, UIControl): + if value not in self.find_all_controls(): + raise ValueError( + "Invalid value. Container does not appear in the layout." + ) + if not value.is_focusable(): + raise ValueError("Invalid value. UIControl is not focusable.") + + self.current_control = value + + # Otherwise, expecting any Container object. + else: + value = to_container(value) + + if isinstance(value, Window): + # This is a `Window`: focus that. + if value not in self.find_all_windows(): + raise ValueError( + "Invalid value. Window does not appear in the layout: %r" + % (value,) + ) + + self.current_window = value + else: + # Focus a window in this container. + # If we have many windows as part of this container, and some + # of them have been focused before, take the last focused + # item. (This is very useful when the UI is composed of more + # complex sub components.) + windows = [] + for c in walk(value, skip_hidden=True): + if isinstance(c, Window) and c.content.is_focusable(): + windows.append(c) + + # Take the first one that was focused before. + for w in reversed(self._stack): + if w in windows: + self.current_window = w + return + + # None was focused before: take the very first focusable window. + if windows: + self.current_window = windows[0] + return + + raise ValueError( + f"Invalid value. Container cannot be focused: {value!r}" + ) + + def has_focus(self, value: FocusableElement) -> bool: + """ + Check whether the given control has the focus. + :param value: :class:`.UIControl` or :class:`.Window` instance. + """ + if isinstance(value, str): + if self.current_buffer is None: + return False + return self.current_buffer.name == value + if isinstance(value, Buffer): + return self.current_buffer == value + if isinstance(value, UIControl): + return self.current_control == value + else: + value = to_container(value) + if isinstance(value, Window): + return self.current_window == value + else: + # Check whether this "container" is focused. This is true if + # one of the elements inside is focused. + for element in walk(value): + if element == self.current_window: + return True + return False + + @property + def current_control(self) -> UIControl: + """ + Get the :class:`.UIControl` to currently has the focus. + """ + return self._stack[-1].content + + @current_control.setter + def current_control(self, control: UIControl) -> None: + """ + Set the :class:`.UIControl` to receive the focus. + """ + for window in self.find_all_windows(): + if window.content == control: + self.current_window = window + return + + raise ValueError("Control not found in the user interface.") + + @property + def current_window(self) -> Window: + "Return the :class:`.Window` object that is currently focused." + return self._stack[-1] + + @current_window.setter + def current_window(self, value: Window) -> None: + "Set the :class:`.Window` object to be currently focused." + self._stack.append(value) + + @property + def is_searching(self) -> bool: + "True if we are searching right now." + return self.current_control in self.search_links + + @property + def search_target_buffer_control(self) -> Optional[BufferControl]: + """ + Return the :class:`.BufferControl` in which we are searching or `None`. + """ + # Not every `UIControl` is a `BufferControl`. This only applies to + # `BufferControl`. + control = self.current_control + + if isinstance(control, SearchBufferControl): + return self.search_links.get(control) + else: + return None + + def get_focusable_windows(self) -> Iterable[Window]: + """ + Return all the :class:`.Window` objects which are focusable (in the + 'modal' area). + """ + for w in self.walk_through_modal_area(): + if isinstance(w, Window) and w.content.is_focusable(): + yield w + + def get_visible_focusable_windows(self) -> List[Window]: + """ + Return a list of :class:`.Window` objects that are focusable. + """ + # focusable windows are windows that are visible, but also part of the + # modal container. Make sure to keep the ordering. + visible_windows = self.visible_windows + return [w for w in self.get_focusable_windows() if w in visible_windows] + + @property + def current_buffer(self) -> Optional[Buffer]: + """ + The currently focused :class:`~.Buffer` or `None`. + """ + ui_control = self.current_control + if isinstance(ui_control, BufferControl): + return ui_control.buffer + return None + + def get_buffer_by_name(self, buffer_name: str) -> Optional[Buffer]: + """ + Look in the layout for a buffer with the given name. + Return `None` when nothing was found. + """ + for w in self.walk(): + if isinstance(w, Window) and isinstance(w.content, BufferControl): + if w.content.buffer.name == buffer_name: + return w.content.buffer + return None + + @property + def buffer_has_focus(self) -> bool: + """ + Return `True` if the currently focused control is a + :class:`.BufferControl`. (For instance, used to determine whether the + default key bindings should be active or not.) + """ + ui_control = self.current_control + return isinstance(ui_control, BufferControl) + + @property + def previous_control(self) -> UIControl: + """ + Get the :class:`.UIControl` to previously had the focus. + """ + try: + return self._stack[-2].content + except IndexError: + return self._stack[-1].content + + def focus_last(self) -> None: + """ + Give the focus to the last focused control. + """ + if len(self._stack) > 1: + self._stack = self._stack[:-1] + + def focus_next(self) -> None: + """ + Focus the next visible/focusable Window. + """ + windows = self.get_visible_focusable_windows() + + if len(windows) > 0: + try: + index = windows.index(self.current_window) + except ValueError: + index = 0 + else: + index = (index + 1) % len(windows) + + self.focus(windows[index]) + + def focus_previous(self) -> None: + """ + Focus the previous visible/focusable Window. + """ + windows = self.get_visible_focusable_windows() + + if len(windows) > 0: + try: + index = windows.index(self.current_window) + except ValueError: + index = 0 + else: + index = (index - 1) % len(windows) + + self.focus(windows[index]) + + def walk(self) -> Iterable[Container]: + """ + Walk through all the layout nodes (and their children) and yield them. + """ + yield from walk(self.container) + + def walk_through_modal_area(self) -> Iterable[Container]: + """ + Walk through all the containers which are in the current 'modal' part + of the layout. + """ + # Go up in the tree, and find the root. (it will be a part of the + # layout, if the focus is in a modal part.) + root: Container = self.current_window + while not root.is_modal() and root in self._child_to_parent: + root = self._child_to_parent[root] + + yield from walk(root) + + def update_parents_relations(self) -> None: + """ + Update child->parent relationships mapping. + """ + parents = {} + + def walk(e: Container) -> None: + for c in e.get_children(): + parents[c] = e + walk(c) + + walk(self.container) + + self._child_to_parent = parents + + def reset(self) -> None: + # Remove all search links when the UI starts. + # (Important, for instance when control-c is been pressed while + # searching. The prompt cancels, but next `run()` call the search + # links are still there.) + self.search_links.clear() + + self.container.reset() + + def get_parent(self, container: Container) -> Optional[Container]: + """ + Return the parent container for the given container, or ``None``, if it + wasn't found. + """ + try: + return self._child_to_parent[container] + except KeyError: + return None + + +class InvalidLayoutError(Exception): + pass + + +def walk(container: Container, skip_hidden: bool = False) -> Iterable[Container]: + """ + Walk through layout, starting at this container. + """ + # When `skip_hidden` is set, don't go into disabled ConditionalContainer containers. + if ( + skip_hidden + and isinstance(container, ConditionalContainer) + and not container.filter() + ): + return + + yield container + + for c in container.get_children(): + # yield from walk(c) + yield from walk(c, skip_hidden=skip_hidden) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/margins.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/margins.py new file mode 100644 index 0000000..7c46819 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/margins.py @@ -0,0 +1,305 @@ +""" +Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. +""" +from abc import ABCMeta, abstractmethod +from typing import TYPE_CHECKING, Callable, Optional + +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.formatted_text import ( + StyleAndTextTuples, + fragment_list_to_text, + to_formatted_text, +) +from prompt_toolkit.utils import get_cwidth + +from .controls import UIContent + +if TYPE_CHECKING: + from .containers import WindowRenderInfo + +__all__ = [ + "Margin", + "NumberedMargin", + "ScrollbarMargin", + "ConditionalMargin", + "PromptMargin", +] + + +class Margin(metaclass=ABCMeta): + """ + Base interface for a margin. + """ + + @abstractmethod + def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: + """ + Return the width that this margin is going to consume. + + :param get_ui_content: Callable that asks the user control to create + a :class:`.UIContent` instance. This can be used for instance to + obtain the number of lines. + """ + return 0 + + @abstractmethod + def create_margin( + self, window_render_info: "WindowRenderInfo", width: int, height: int + ) -> StyleAndTextTuples: + """ + Creates a margin. + This should return a list of (style_str, text) tuples. + + :param window_render_info: + :class:`~prompt_toolkit.layout.containers.WindowRenderInfo` + instance, generated after rendering and copying the visible part of + the :class:`~prompt_toolkit.layout.controls.UIControl` into the + :class:`~prompt_toolkit.layout.containers.Window`. + :param width: The width that's available for this margin. (As reported + by :meth:`.get_width`.) + :param height: The height that's available for this margin. (The height + of the :class:`~prompt_toolkit.layout.containers.Window`.) + """ + return [] + + +class NumberedMargin(Margin): + """ + Margin that displays the line numbers. + + :param relative: Number relative to the cursor position. Similar to the Vi + 'relativenumber' option. + :param display_tildes: Display tildes after the end of the document, just + like Vi does. + """ + + def __init__( + self, relative: FilterOrBool = False, display_tildes: FilterOrBool = False + ) -> None: + + self.relative = to_filter(relative) + self.display_tildes = to_filter(display_tildes) + + def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: + line_count = get_ui_content().line_count + return max(3, len("%s" % line_count) + 1) + + def create_margin( + self, window_render_info: "WindowRenderInfo", width: int, height: int + ) -> StyleAndTextTuples: + relative = self.relative() + + style = "class:line-number" + style_current = "class:line-number.current" + + # Get current line number. + current_lineno = window_render_info.ui_content.cursor_position.y + + # Construct margin. + result: StyleAndTextTuples = [] + last_lineno = None + + for y, lineno in enumerate(window_render_info.displayed_lines): + # Only display line number if this line is not a continuation of the previous line. + if lineno != last_lineno: + if lineno is None: + pass + elif lineno == current_lineno: + # Current line. + if relative: + # Left align current number in relative mode. + result.append((style_current, "%i" % (lineno + 1))) + else: + result.append( + (style_current, ("%i " % (lineno + 1)).rjust(width)) + ) + else: + # Other lines. + if relative: + lineno = abs(lineno - current_lineno) - 1 + + result.append((style, ("%i " % (lineno + 1)).rjust(width))) + + last_lineno = lineno + result.append(("", "\n")) + + # Fill with tildes. + if self.display_tildes(): + while y < window_render_info.window_height: + result.append(("class:tilde", "~\n")) + y += 1 + + return result + + +class ConditionalMargin(Margin): + """ + Wrapper around other :class:`.Margin` classes to show/hide them. + """ + + def __init__(self, margin: Margin, filter: FilterOrBool) -> None: + self.margin = margin + self.filter = to_filter(filter) + + def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: + if self.filter(): + return self.margin.get_width(get_ui_content) + else: + return 0 + + def create_margin( + self, window_render_info: "WindowRenderInfo", width: int, height: int + ) -> StyleAndTextTuples: + if width and self.filter(): + return self.margin.create_margin(window_render_info, width, height) + else: + return [] + + +class ScrollbarMargin(Margin): + """ + Margin displaying a scrollbar. + + :param display_arrows: Display scroll up/down arrows. + """ + + def __init__( + self, + display_arrows: FilterOrBool = False, + up_arrow_symbol: str = "^", + down_arrow_symbol: str = "v", + ) -> None: + + self.display_arrows = to_filter(display_arrows) + self.up_arrow_symbol = up_arrow_symbol + self.down_arrow_symbol = down_arrow_symbol + + def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: + return 1 + + def create_margin( + self, window_render_info: "WindowRenderInfo", width: int, height: int + ) -> StyleAndTextTuples: + content_height = window_render_info.content_height + window_height = window_render_info.window_height + display_arrows = self.display_arrows() + + if display_arrows: + window_height -= 2 + + try: + fraction_visible = len(window_render_info.displayed_lines) / float( + content_height + ) + fraction_above = window_render_info.vertical_scroll / float(content_height) + + scrollbar_height = int( + min(window_height, max(1, window_height * fraction_visible)) + ) + scrollbar_top = int(window_height * fraction_above) + except ZeroDivisionError: + return [] + else: + + def is_scroll_button(row: int) -> bool: + "True if we should display a button on this row." + return scrollbar_top <= row <= scrollbar_top + scrollbar_height + + # Up arrow. + result: StyleAndTextTuples = [] + if display_arrows: + result.extend( + [ + ("class:scrollbar.arrow", self.up_arrow_symbol), + ("class:scrollbar", "\n"), + ] + ) + + # Scrollbar body. + scrollbar_background = "class:scrollbar.background" + scrollbar_background_start = "class:scrollbar.background,scrollbar.start" + scrollbar_button = "class:scrollbar.button" + scrollbar_button_end = "class:scrollbar.button,scrollbar.end" + + for i in range(window_height): + if is_scroll_button(i): + if not is_scroll_button(i + 1): + # Give the last cell a different style, because we + # want to underline this. + result.append((scrollbar_button_end, " ")) + else: + result.append((scrollbar_button, " ")) + else: + if is_scroll_button(i + 1): + result.append((scrollbar_background_start, " ")) + else: + result.append((scrollbar_background, " ")) + result.append(("", "\n")) + + # Down arrow + if display_arrows: + result.append(("class:scrollbar.arrow", self.down_arrow_symbol)) + + return result + + +class PromptMargin(Margin): + """ + [Deprecated] + + Create margin that displays a prompt. + This can display one prompt at the first line, and a continuation prompt + (e.g, just dots) on all the following lines. + + This `PromptMargin` implementation has been largely superseded in favor of + the `get_line_prefix` attribute of `Window`. The reason is that a margin is + always a fixed width, while `get_line_prefix` can return a variable width + prefix in front of every line, making it more powerful, especially for line + continuations. + + :param get_prompt: Callable returns formatted text or a list of + `(style_str, type)` tuples to be shown as the prompt at the first line. + :param get_continuation: Callable that takes three inputs. The width (int), + line_number (int), and is_soft_wrap (bool). It should return formatted + text or a list of `(style_str, type)` tuples for the next lines of the + input. + """ + + def __init__( + self, + get_prompt: Callable[[], StyleAndTextTuples], + get_continuation: Optional[ + Callable[[int, int, bool], StyleAndTextTuples] + ] = None, + ) -> None: + + self.get_prompt = get_prompt + self.get_continuation = get_continuation + + def get_width(self, get_ui_content: Callable[[], UIContent]) -> int: + "Width to report to the `Window`." + # Take the width from the first line. + text = fragment_list_to_text(self.get_prompt()) + return get_cwidth(text) + + def create_margin( + self, window_render_info: "WindowRenderInfo", width: int, height: int + ) -> StyleAndTextTuples: + get_continuation = self.get_continuation + result: StyleAndTextTuples = [] + + # First line. + result.extend(to_formatted_text(self.get_prompt())) + + # Next lines. + if get_continuation: + last_y = None + + for y in window_render_info.displayed_lines[1:]: + result.append(("", "\n")) + result.extend( + to_formatted_text(get_continuation(width, y, y == last_y)) + ) + last_y = y + + return result diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/menus.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/menus.py new file mode 100644 index 0000000..8218f74 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/menus.py @@ -0,0 +1,722 @@ +import math +from itertools import zip_longest +from typing import ( + TYPE_CHECKING, + Callable, + Dict, + Iterable, + List, + Optional, + Tuple, + TypeVar, + Union, + cast, +) + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import CompletionState +from prompt_toolkit.completion import Completion +from prompt_toolkit.data_structures import Point +from prompt_toolkit.filters import ( + Condition, + FilterOrBool, + has_completions, + is_done, + to_filter, +) +from prompt_toolkit.formatted_text import ( + StyleAndTextTuples, + fragment_list_width, + to_formatted_text, +) +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.layout.utils import explode_text_fragments +from prompt_toolkit.mouse_events import MouseEvent, MouseEventType +from prompt_toolkit.utils import get_cwidth + +from .containers import ConditionalContainer, HSplit, ScrollOffsets, Window +from .controls import GetLinePrefixCallable, UIContent, UIControl +from .dimension import Dimension +from .margins import ScrollbarMargin + +if TYPE_CHECKING: + from prompt_toolkit.key_binding.key_bindings import ( + KeyBindings, + NotImplementedOrNone, + ) + + +__all__ = [ + "CompletionsMenu", + "MultiColumnCompletionsMenu", +] + +E = KeyPressEvent + + +class CompletionsMenuControl(UIControl): + """ + Helper for drawing the complete menu to the screen. + + :param scroll_offset: Number (integer) representing the preferred amount of + completions to be displayed before and after the current one. When this + is a very high number, the current completion will be shown in the + middle most of the time. + """ + + # Preferred minimum size of the menu control. + # The CompletionsMenu class defines a width of 8, and there is a scrollbar + # of 1.) + MIN_WIDTH = 7 + + def has_focus(self) -> bool: + return False + + def preferred_width(self, max_available_width: int) -> Optional[int]: + complete_state = get_app().current_buffer.complete_state + if complete_state: + menu_width = self._get_menu_width(500, complete_state) + menu_meta_width = self._get_menu_meta_width(500, complete_state) + + return menu_width + menu_meta_width + else: + return 0 + + def preferred_height( + self, + width: int, + max_available_height: int, + wrap_lines: bool, + get_line_prefix: Optional[GetLinePrefixCallable], + ) -> Optional[int]: + + complete_state = get_app().current_buffer.complete_state + if complete_state: + return len(complete_state.completions) + else: + return 0 + + def create_content(self, width: int, height: int) -> UIContent: + """ + Create a UIContent object for this control. + """ + complete_state = get_app().current_buffer.complete_state + if complete_state: + completions = complete_state.completions + index = complete_state.complete_index # Can be None! + + # Calculate width of completions menu. + menu_width = self._get_menu_width(width, complete_state) + menu_meta_width = self._get_menu_meta_width( + width - menu_width, complete_state + ) + show_meta = self._show_meta(complete_state) + + def get_line(i: int) -> StyleAndTextTuples: + c = completions[i] + is_current_completion = i == index + result = _get_menu_item_fragments( + c, is_current_completion, menu_width, space_after=True + ) + + if show_meta: + result += self._get_menu_item_meta_fragments( + c, is_current_completion, menu_meta_width + ) + return result + + return UIContent( + get_line=get_line, + cursor_position=Point(x=0, y=index or 0), + line_count=len(completions), + ) + + return UIContent() + + def _show_meta(self, complete_state: CompletionState) -> bool: + """ + Return ``True`` if we need to show a column with meta information. + """ + return any(c.display_meta_text for c in complete_state.completions) + + def _get_menu_width(self, max_width: int, complete_state: CompletionState) -> int: + """ + Return the width of the main column. + """ + return min( + max_width, + max( + self.MIN_WIDTH, + max(get_cwidth(c.display_text) for c in complete_state.completions) + 2, + ), + ) + + def _get_menu_meta_width( + self, max_width: int, complete_state: CompletionState + ) -> int: + """ + Return the width of the meta column. + """ + + def meta_width(completion: Completion) -> int: + return get_cwidth(completion.display_meta_text) + + if self._show_meta(complete_state): + return min( + max_width, max(meta_width(c) for c in complete_state.completions) + 2 + ) + else: + return 0 + + def _get_menu_item_meta_fragments( + self, completion: Completion, is_current_completion: bool, width: int + ) -> StyleAndTextTuples: + + if is_current_completion: + style_str = "class:completion-menu.meta.completion.current" + else: + style_str = "class:completion-menu.meta.completion" + + text, tw = _trim_formatted_text(completion.display_meta, width - 2) + padding = " " * (width - 1 - tw) + + return to_formatted_text( + cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], + style=style_str, + ) + + def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + Handle mouse events: clicking and scrolling. + """ + b = get_app().current_buffer + + if mouse_event.event_type == MouseEventType.MOUSE_UP: + # Select completion. + b.go_to_completion(mouse_event.position.y) + b.complete_state = None + + elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: + # Scroll up. + b.complete_next(count=3, disable_wrap_around=True) + + elif mouse_event.event_type == MouseEventType.SCROLL_UP: + # Scroll down. + b.complete_previous(count=3, disable_wrap_around=True) + + return None + + +def _get_menu_item_fragments( + completion: Completion, + is_current_completion: bool, + width: int, + space_after: bool = False, +) -> StyleAndTextTuples: + """ + Get the style/text tuples for a menu item, styled and trimmed to the given + width. + """ + if is_current_completion: + style_str = "class:completion-menu.completion.current {} {}".format( + completion.style, + completion.selected_style, + ) + else: + style_str = "class:completion-menu.completion " + completion.style + + text, tw = _trim_formatted_text( + completion.display, (width - 2 if space_after else width - 1) + ) + + padding = " " * (width - 1 - tw) + + return to_formatted_text( + cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], + style=style_str, + ) + + +def _trim_formatted_text( + formatted_text: StyleAndTextTuples, max_width: int +) -> Tuple[StyleAndTextTuples, int]: + """ + Trim the text to `max_width`, append dots when the text is too long. + Returns (text, width) tuple. + """ + width = fragment_list_width(formatted_text) + + # When the text is too wide, trim it. + if width > max_width: + result = [] # Text fragments. + remaining_width = max_width - 3 + + for style_and_ch in explode_text_fragments(formatted_text): + ch_width = get_cwidth(style_and_ch[1]) + + if ch_width <= remaining_width: + result.append(style_and_ch) + remaining_width -= ch_width + else: + break + + result.append(("", "...")) + + return result, max_width - remaining_width + else: + return formatted_text, width + + +class CompletionsMenu(ConditionalContainer): + # NOTE: We use a pretty big z_index by default. Menus are supposed to be + # above anything else. We also want to make sure that the content is + # visible at the point where we draw this menu. + def __init__( + self, + max_height: Optional[int] = None, + scroll_offset: Union[int, Callable[[], int]] = 0, + extra_filter: FilterOrBool = True, + display_arrows: FilterOrBool = False, + z_index: int = 10**8, + ) -> None: + + extra_filter = to_filter(extra_filter) + display_arrows = to_filter(display_arrows) + + super().__init__( + content=Window( + content=CompletionsMenuControl(), + width=Dimension(min=8), + height=Dimension(min=1, max=max_height), + scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset), + right_margins=[ScrollbarMargin(display_arrows=display_arrows)], + dont_extend_width=True, + style="class:completion-menu", + z_index=z_index, + ), + # Show when there are completions but not at the point we are + # returning the input. + filter=has_completions & ~is_done & extra_filter, + ) + + +class MultiColumnCompletionMenuControl(UIControl): + """ + Completion menu that displays all the completions in several columns. + When there are more completions than space for them to be displayed, an + arrow is shown on the left or right side. + + `min_rows` indicates how many rows will be available in any possible case. + When this is larger than one, it will try to use less columns and more + rows until this value is reached. + Be careful passing in a too big value, if less than the given amount of + rows are available, more columns would have been required, but + `preferred_width` doesn't know about that and reports a too small value. + This results in less completions displayed and additional scrolling. + (It's a limitation of how the layout engine currently works: first the + widths are calculated, then the heights.) + + :param suggested_max_column_width: The suggested max width of a column. + The column can still be bigger than this, but if there is place for two + columns of this width, we will display two columns. This to avoid that + if there is one very wide completion, that it doesn't significantly + reduce the amount of columns. + """ + + _required_margin = 3 # One extra padding on the right + space for arrows. + + def __init__(self, min_rows: int = 3, suggested_max_column_width: int = 30) -> None: + assert min_rows >= 1 + + self.min_rows = min_rows + self.suggested_max_column_width = suggested_max_column_width + self.scroll = 0 + + # Info of last rendering. + self._rendered_rows = 0 + self._rendered_columns = 0 + self._total_columns = 0 + self._render_pos_to_completion: Dict[Tuple[int, int], Completion] = {} + self._render_left_arrow = False + self._render_right_arrow = False + self._render_width = 0 + + def reset(self) -> None: + self.scroll = 0 + + def has_focus(self) -> bool: + return False + + def preferred_width(self, max_available_width: int) -> Optional[int]: + """ + Preferred width: prefer to use at least min_rows, but otherwise as much + as possible horizontally. + """ + complete_state = get_app().current_buffer.complete_state + if complete_state is None: + return 0 + + column_width = self._get_column_width(complete_state) + result = int( + column_width + * math.ceil(len(complete_state.completions) / float(self.min_rows)) + ) + + # When the desired width is still more than the maximum available, + # reduce by removing columns until we are less than the available + # width. + while ( + result > column_width + and result > max_available_width - self._required_margin + ): + result -= column_width + return result + self._required_margin + + def preferred_height( + self, + width: int, + max_available_height: int, + wrap_lines: bool, + get_line_prefix: Optional[GetLinePrefixCallable], + ) -> Optional[int]: + """ + Preferred height: as much as needed in order to display all the completions. + """ + complete_state = get_app().current_buffer.complete_state + if complete_state is None: + return 0 + + column_width = self._get_column_width(complete_state) + column_count = max(1, (width - self._required_margin) // column_width) + + return int(math.ceil(len(complete_state.completions) / float(column_count))) + + def create_content(self, width: int, height: int) -> UIContent: + """ + Create a UIContent object for this menu. + """ + complete_state = get_app().current_buffer.complete_state + if complete_state is None: + return UIContent() + + column_width = self._get_column_width(complete_state) + self._render_pos_to_completion = {} + + _T = TypeVar("_T") + + def grouper( + n: int, iterable: Iterable[_T], fillvalue: Optional[_T] = None + ) -> Iterable[List[_T]]: + "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" + args = [iter(iterable)] * n + return zip_longest(fillvalue=fillvalue, *args) + + def is_current_completion(completion: Completion) -> bool: + "Returns True when this completion is the currently selected one." + return ( + complete_state is not None + and complete_state.complete_index is not None + and c == complete_state.current_completion + ) + + # Space required outside of the regular columns, for displaying the + # left and right arrow. + HORIZONTAL_MARGIN_REQUIRED = 3 + + # There should be at least one column, but it cannot be wider than + # the available width. + column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) + + # However, when the columns tend to be very wide, because there are + # some very wide entries, shrink it anyway. + if column_width > self.suggested_max_column_width: + # `column_width` can still be bigger that `suggested_max_column_width`, + # but if there is place for two columns, we divide by two. + column_width //= column_width // self.suggested_max_column_width + + visible_columns = max(1, (width - self._required_margin) // column_width) + + columns_ = list(grouper(height, complete_state.completions)) + rows_ = list(zip(*columns_)) + + # Make sure the current completion is always visible: update scroll offset. + selected_column = (complete_state.complete_index or 0) // height + self.scroll = min( + selected_column, max(self.scroll, selected_column - visible_columns + 1) + ) + + render_left_arrow = self.scroll > 0 + render_right_arrow = self.scroll < len(rows_[0]) - visible_columns + + # Write completions to screen. + fragments_for_line = [] + + for row_index, row in enumerate(rows_): + fragments: StyleAndTextTuples = [] + middle_row = row_index == len(rows_) // 2 + + # Draw left arrow if we have hidden completions on the left. + if render_left_arrow: + fragments.append(("class:scrollbar", "<" if middle_row else " ")) + elif render_right_arrow: + # Reserve one column empty space. (If there is a right + # arrow right now, there can be a left arrow as well.) + fragments.append(("", " ")) + + # Draw row content. + for column_index, c in enumerate(row[self.scroll :][:visible_columns]): + if c is not None: + fragments += _get_menu_item_fragments( + c, is_current_completion(c), column_width, space_after=False + ) + + # Remember render position for mouse click handler. + for x in range(column_width): + self._render_pos_to_completion[ + (column_index * column_width + x, row_index) + ] = c + else: + fragments.append(("class:completion", " " * column_width)) + + # Draw trailing padding for this row. + # (_get_menu_item_fragments only returns padding on the left.) + if render_left_arrow or render_right_arrow: + fragments.append(("class:completion", " ")) + + # Draw right arrow if we have hidden completions on the right. + if render_right_arrow: + fragments.append(("class:scrollbar", ">" if middle_row else " ")) + elif render_left_arrow: + fragments.append(("class:completion", " ")) + + # Add line. + fragments_for_line.append( + to_formatted_text(fragments, style="class:completion-menu") + ) + + self._rendered_rows = height + self._rendered_columns = visible_columns + self._total_columns = len(columns_) + self._render_left_arrow = render_left_arrow + self._render_right_arrow = render_right_arrow + self._render_width = ( + column_width * visible_columns + render_left_arrow + render_right_arrow + 1 + ) + + def get_line(i: int) -> StyleAndTextTuples: + return fragments_for_line[i] + + return UIContent(get_line=get_line, line_count=len(rows_)) + + def _get_column_width(self, complete_state: CompletionState) -> int: + """ + Return the width of each column. + """ + return max(get_cwidth(c.display_text) for c in complete_state.completions) + 1 + + def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + Handle scroll and click events. + """ + b = get_app().current_buffer + + def scroll_left() -> None: + b.complete_previous(count=self._rendered_rows, disable_wrap_around=True) + self.scroll = max(0, self.scroll - 1) + + def scroll_right() -> None: + b.complete_next(count=self._rendered_rows, disable_wrap_around=True) + self.scroll = min( + self._total_columns - self._rendered_columns, self.scroll + 1 + ) + + if mouse_event.event_type == MouseEventType.SCROLL_DOWN: + scroll_right() + + elif mouse_event.event_type == MouseEventType.SCROLL_UP: + scroll_left() + + elif mouse_event.event_type == MouseEventType.MOUSE_UP: + x = mouse_event.position.x + y = mouse_event.position.y + + # Mouse click on left arrow. + if x == 0: + if self._render_left_arrow: + scroll_left() + + # Mouse click on right arrow. + elif x == self._render_width - 1: + if self._render_right_arrow: + scroll_right() + + # Mouse click on completion. + else: + completion = self._render_pos_to_completion.get((x, y)) + if completion: + b.apply_completion(completion) + + return None + + def get_key_bindings(self) -> "KeyBindings": + """ + Expose key bindings that handle the left/right arrow keys when the menu + is displayed. + """ + from prompt_toolkit.key_binding.key_bindings import KeyBindings + + kb = KeyBindings() + + @Condition + def filter() -> bool: + "Only handle key bindings if this menu is visible." + app = get_app() + complete_state = app.current_buffer.complete_state + + # There need to be completions, and one needs to be selected. + if complete_state is None or complete_state.complete_index is None: + return False + + # This menu needs to be visible. + return any(window.content == self for window in app.layout.visible_windows) + + def move(right: bool = False) -> None: + buff = get_app().current_buffer + complete_state = buff.complete_state + + if complete_state is not None and complete_state.complete_index is not None: + # Calculate new complete index. + new_index = complete_state.complete_index + if right: + new_index += self._rendered_rows + else: + new_index -= self._rendered_rows + + if 0 <= new_index < len(complete_state.completions): + buff.go_to_completion(new_index) + + # NOTE: the is_global is required because the completion menu will + # never be focussed. + + @kb.add("left", is_global=True, filter=filter) + def _left(event: E) -> None: + move() + + @kb.add("right", is_global=True, filter=filter) + def _right(event: E) -> None: + move(True) + + return kb + + +class MultiColumnCompletionsMenu(HSplit): + """ + Container that displays the completions in several columns. + When `show_meta` (a :class:`~prompt_toolkit.filters.Filter`) evaluates + to True, it shows the meta information at the bottom. + """ + + def __init__( + self, + min_rows: int = 3, + suggested_max_column_width: int = 30, + show_meta: FilterOrBool = True, + extra_filter: FilterOrBool = True, + z_index: int = 10**8, + ) -> None: + + show_meta = to_filter(show_meta) + extra_filter = to_filter(extra_filter) + + # Display filter: show when there are completions but not at the point + # we are returning the input. + full_filter = has_completions & ~is_done & extra_filter + + @Condition + def any_completion_has_meta() -> bool: + complete_state = get_app().current_buffer.complete_state + return complete_state is not None and any( + c.display_meta for c in complete_state.completions + ) + + # Create child windows. + # NOTE: We don't set style='class:completion-menu' to the + # `MultiColumnCompletionMenuControl`, because this is used in a + # Float that is made transparent, and the size of the control + # doesn't always correspond exactly with the size of the + # generated content. + completions_window = ConditionalContainer( + content=Window( + content=MultiColumnCompletionMenuControl( + min_rows=min_rows, + suggested_max_column_width=suggested_max_column_width, + ), + width=Dimension(min=8), + height=Dimension(min=1), + ), + filter=full_filter, + ) + + meta_window = ConditionalContainer( + content=Window(content=_SelectedCompletionMetaControl()), + filter=show_meta & full_filter & any_completion_has_meta, + ) + + # Initialise split. + super().__init__([completions_window, meta_window], z_index=z_index) + + +class _SelectedCompletionMetaControl(UIControl): + """ + Control that shows the meta information of the selected completion. + """ + + def preferred_width(self, max_available_width: int) -> Optional[int]: + """ + Report the width of the longest meta text as the preferred width of this control. + + It could be that we use less width, but this way, we're sure that the + layout doesn't change when we select another completion (E.g. that + completions are suddenly shown in more or fewer columns.) + """ + app = get_app() + if app.current_buffer.complete_state: + state = app.current_buffer.complete_state + return 2 + max(get_cwidth(c.display_meta_text) for c in state.completions) + else: + return 0 + + def preferred_height( + self, + width: int, + max_available_height: int, + wrap_lines: bool, + get_line_prefix: Optional[GetLinePrefixCallable], + ) -> Optional[int]: + return 1 + + def create_content(self, width: int, height: int) -> UIContent: + fragments = self._get_text_fragments() + + def get_line(i: int) -> StyleAndTextTuples: + return fragments + + return UIContent(get_line=get_line, line_count=1 if fragments else 0) + + def _get_text_fragments(self) -> StyleAndTextTuples: + style = "class:completion-menu.multi-column-meta" + state = get_app().current_buffer.complete_state + + if ( + state + and state.current_completion + and state.current_completion.display_meta_text + ): + return to_formatted_text( + cast(StyleAndTextTuples, [("", " ")]) + + state.current_completion.display_meta + + [("", " ")], + style=style, + ) + + return [] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/mouse_handlers.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/mouse_handlers.py new file mode 100644 index 0000000..2562317 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/mouse_handlers.py @@ -0,0 +1,54 @@ +from collections import defaultdict +from typing import TYPE_CHECKING, Callable, DefaultDict + +from prompt_toolkit.mouse_events import MouseEvent + +if TYPE_CHECKING: + from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone + +__all__ = [ + "MouseHandler", + "MouseHandlers", +] + + +MouseHandler = Callable[[MouseEvent], "NotImplementedOrNone"] + + +class MouseHandlers: + """ + Two dimensional raster of callbacks for mouse events. + """ + + def __init__(self) -> None: + def dummy_callback(mouse_event: MouseEvent) -> "NotImplementedOrNone": + """ + :param mouse_event: `MouseEvent` instance. + """ + return NotImplemented + + # NOTE: Previously, the data structure was a dictionary mapping (x,y) + # to the handlers. This however would be more inefficient when copying + # over the mouse handlers of the visible region in the scrollable pane. + + # Map y (row) to x (column) to handlers. + self.mouse_handlers: DefaultDict[ + int, DefaultDict[int, MouseHandler] + ] = defaultdict(lambda: defaultdict(lambda: dummy_callback)) + + def set_mouse_handler_for_range( + self, + x_min: int, + x_max: int, + y_min: int, + y_max: int, + handler: Callable[[MouseEvent], "NotImplementedOrNone"], + ) -> None: + """ + Set mouse handler for a region. + """ + for y in range(y_min, y_max): + row = self.mouse_handlers[y] + + for x in range(x_min, x_max): + row[x] = handler diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/processors.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/processors.py new file mode 100644 index 0000000..722658a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/processors.py @@ -0,0 +1,1029 @@ +""" +Processors are little transformation blocks that transform the fragments list +from a buffer before the BufferControl will render it to the screen. + +They can insert fragments before or after, or highlight fragments by replacing the +fragment types. +""" +import re +from abc import ABCMeta, abstractmethod +from typing import ( + TYPE_CHECKING, + Callable, + Hashable, + List, + Optional, + Tuple, + Type, + Union, + cast, +) + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.cache import SimpleCache +from prompt_toolkit.document import Document +from prompt_toolkit.filters import FilterOrBool, to_filter, vi_insert_multiple_mode +from prompt_toolkit.formatted_text import ( + AnyFormattedText, + StyleAndTextTuples, + to_formatted_text, +) +from prompt_toolkit.formatted_text.utils import fragment_list_len, fragment_list_to_text +from prompt_toolkit.search import SearchDirection +from prompt_toolkit.utils import to_int, to_str + +from .utils import explode_text_fragments + +if TYPE_CHECKING: + from .controls import BufferControl, UIContent + +__all__ = [ + "Processor", + "TransformationInput", + "Transformation", + "DummyProcessor", + "HighlightSearchProcessor", + "HighlightIncrementalSearchProcessor", + "HighlightSelectionProcessor", + "PasswordProcessor", + "HighlightMatchingBracketProcessor", + "DisplayMultipleCursors", + "BeforeInput", + "ShowArg", + "AfterInput", + "AppendAutoSuggestion", + "ConditionalProcessor", + "ShowLeadingWhiteSpaceProcessor", + "ShowTrailingWhiteSpaceProcessor", + "TabsProcessor", + "ReverseSearchProcessor", + "DynamicProcessor", + "merge_processors", +] + + +class Processor(metaclass=ABCMeta): + """ + Manipulate the fragments for a given line in a + :class:`~prompt_toolkit.layout.controls.BufferControl`. + """ + + @abstractmethod + def apply_transformation( + self, transformation_input: "TransformationInput" + ) -> "Transformation": + """ + Apply transformation. Returns a :class:`.Transformation` instance. + + :param transformation_input: :class:`.TransformationInput` object. + """ + return Transformation(transformation_input.fragments) + + +SourceToDisplay = Callable[[int], int] +DisplayToSource = Callable[[int], int] + + +class TransformationInput: + """ + :param buffer_control: :class:`.BufferControl` instance. + :param lineno: The number of the line to which we apply the processor. + :param source_to_display: A function that returns the position in the + `fragments` for any position in the source string. (This takes + previous processors into account.) + :param fragments: List of fragments that we can transform. (Received from the + previous processor.) + """ + + def __init__( + self, + buffer_control: "BufferControl", + document: Document, + lineno: int, + source_to_display: SourceToDisplay, + fragments: StyleAndTextTuples, + width: int, + height: int, + ) -> None: + + self.buffer_control = buffer_control + self.document = document + self.lineno = lineno + self.source_to_display = source_to_display + self.fragments = fragments + self.width = width + self.height = height + + def unpack( + self, + ) -> Tuple[ + "BufferControl", Document, int, SourceToDisplay, StyleAndTextTuples, int, int + ]: + return ( + self.buffer_control, + self.document, + self.lineno, + self.source_to_display, + self.fragments, + self.width, + self.height, + ) + + +class Transformation: + """ + Transformation result, as returned by :meth:`.Processor.apply_transformation`. + + Important: Always make sure that the length of `document.text` is equal to + the length of all the text in `fragments`! + + :param fragments: The transformed fragments. To be displayed, or to pass to + the next processor. + :param source_to_display: Cursor position transformation from original + string to transformed string. + :param display_to_source: Cursor position transformed from source string to + original string. + """ + + def __init__( + self, + fragments: StyleAndTextTuples, + source_to_display: Optional[SourceToDisplay] = None, + display_to_source: Optional[DisplayToSource] = None, + ) -> None: + + self.fragments = fragments + self.source_to_display = source_to_display or (lambda i: i) + self.display_to_source = display_to_source or (lambda i: i) + + +class DummyProcessor(Processor): + """ + A `Processor` that doesn't do anything. + """ + + def apply_transformation( + self, transformation_input: TransformationInput + ) -> Transformation: + return Transformation(transformation_input.fragments) + + +class HighlightSearchProcessor(Processor): + """ + Processor that highlights search matches in the document. + Note that this doesn't support multiline search matches yet. + + The style classes 'search' and 'search.current' will be applied to the + content. + """ + + _classname = "search" + _classname_current = "search.current" + + def _get_search_text(self, buffer_control: "BufferControl") -> str: + """ + The text we are searching for. + """ + return buffer_control.search_state.text + + def apply_transformation( + self, transformation_input: TransformationInput + ) -> Transformation: + + ( + buffer_control, + document, + lineno, + source_to_display, + fragments, + _, + _, + ) = transformation_input.unpack() + + search_text = self._get_search_text(buffer_control) + searchmatch_fragment = f" class:{self._classname} " + searchmatch_current_fragment = f" class:{self._classname_current} " + + if search_text and not get_app().is_done: + # For each search match, replace the style string. + line_text = fragment_list_to_text(fragments) + fragments = explode_text_fragments(fragments) + + if buffer_control.search_state.ignore_case(): + flags = re.IGNORECASE + else: + flags = re.RegexFlag(0) + + # Get cursor column. + cursor_column: Optional[int] + if document.cursor_position_row == lineno: + cursor_column = source_to_display(document.cursor_position_col) + else: + cursor_column = None + + for match in re.finditer(re.escape(search_text), line_text, flags=flags): + if cursor_column is not None: + on_cursor = match.start() <= cursor_column < match.end() + else: + on_cursor = False + + for i in range(match.start(), match.end()): + old_fragment, text, *_ = fragments[i] + if on_cursor: + fragments[i] = ( + old_fragment + searchmatch_current_fragment, + fragments[i][1], + ) + else: + fragments[i] = ( + old_fragment + searchmatch_fragment, + fragments[i][1], + ) + + return Transformation(fragments) + + +class HighlightIncrementalSearchProcessor(HighlightSearchProcessor): + """ + Highlight the search terms that are used for highlighting the incremental + search. The style class 'incsearch' will be applied to the content. + + Important: this requires the `preview_search=True` flag to be set for the + `BufferControl`. Otherwise, the cursor position won't be set to the search + match while searching, and nothing happens. + """ + + _classname = "incsearch" + _classname_current = "incsearch.current" + + def _get_search_text(self, buffer_control: "BufferControl") -> str: + """ + The text we are searching for. + """ + # When the search buffer has focus, take that text. + search_buffer = buffer_control.search_buffer + if search_buffer is not None and search_buffer.text: + return search_buffer.text + return "" + + +class HighlightSelectionProcessor(Processor): + """ + Processor that highlights the selection in the document. + """ + + def apply_transformation( + self, transformation_input: TransformationInput + ) -> Transformation: + ( + buffer_control, + document, + lineno, + source_to_display, + fragments, + _, + _, + ) = transformation_input.unpack() + + selected_fragment = " class:selected " + + # In case of selection, highlight all matches. + selection_at_line = document.selection_range_at_line(lineno) + + if selection_at_line: + from_, to = selection_at_line + from_ = source_to_display(from_) + to = source_to_display(to) + + fragments = explode_text_fragments(fragments) + + if from_ == 0 and to == 0 and len(fragments) == 0: + # When this is an empty line, insert a space in order to + # visualise the selection. + return Transformation([(selected_fragment, " ")]) + else: + for i in range(from_, to): + if i < len(fragments): + old_fragment, old_text, *_ = fragments[i] + fragments[i] = (old_fragment + selected_fragment, old_text) + elif i == len(fragments): + fragments.append((selected_fragment, " ")) + + return Transformation(fragments) + + +class PasswordProcessor(Processor): + """ + Processor that masks the input. (For passwords.) + + :param char: (string) Character to be used. "*" by default. + """ + + def __init__(self, char: str = "*") -> None: + self.char = char + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + fragments: StyleAndTextTuples = cast( + StyleAndTextTuples, + [ + (style, self.char * len(text), *handler) + for style, text, *handler in ti.fragments + ], + ) + + return Transformation(fragments) + + +class HighlightMatchingBracketProcessor(Processor): + """ + When the cursor is on or right after a bracket, it highlights the matching + bracket. + + :param max_cursor_distance: Only highlight matching brackets when the + cursor is within this distance. (From inside a `Processor`, we can't + know which lines will be visible on the screen. But we also don't want + to scan the whole document for matching brackets on each key press, so + we limit to this value.) + """ + + _closing_braces = "])}>" + + def __init__( + self, chars: str = "[](){}<>", max_cursor_distance: int = 1000 + ) -> None: + self.chars = chars + self.max_cursor_distance = max_cursor_distance + + self._positions_cache: SimpleCache[ + Hashable, List[Tuple[int, int]] + ] = SimpleCache(maxsize=8) + + def _get_positions_to_highlight(self, document: Document) -> List[Tuple[int, int]]: + """ + Return a list of (row, col) tuples that need to be highlighted. + """ + pos: Optional[int] + + # Try for the character under the cursor. + if document.current_char and document.current_char in self.chars: + pos = document.find_matching_bracket_position( + start_pos=document.cursor_position - self.max_cursor_distance, + end_pos=document.cursor_position + self.max_cursor_distance, + ) + + # Try for the character before the cursor. + elif ( + document.char_before_cursor + and document.char_before_cursor in self._closing_braces + and document.char_before_cursor in self.chars + ): + document = Document(document.text, document.cursor_position - 1) + + pos = document.find_matching_bracket_position( + start_pos=document.cursor_position - self.max_cursor_distance, + end_pos=document.cursor_position + self.max_cursor_distance, + ) + else: + pos = None + + # Return a list of (row, col) tuples that need to be highlighted. + if pos: + pos += document.cursor_position # pos is relative. + row, col = document.translate_index_to_position(pos) + return [ + (row, col), + (document.cursor_position_row, document.cursor_position_col), + ] + else: + return [] + + def apply_transformation( + self, transformation_input: TransformationInput + ) -> Transformation: + + ( + buffer_control, + document, + lineno, + source_to_display, + fragments, + _, + _, + ) = transformation_input.unpack() + + # When the application is in the 'done' state, don't highlight. + if get_app().is_done: + return Transformation(fragments) + + # Get the highlight positions. + key = (get_app().render_counter, document.text, document.cursor_position) + positions = self._positions_cache.get( + key, lambda: self._get_positions_to_highlight(document) + ) + + # Apply if positions were found at this line. + if positions: + for row, col in positions: + if row == lineno: + col = source_to_display(col) + fragments = explode_text_fragments(fragments) + style, text, *_ = fragments[col] + + if col == document.cursor_position_col: + style += " class:matching-bracket.cursor " + else: + style += " class:matching-bracket.other " + + fragments[col] = (style, text) + + return Transformation(fragments) + + +class DisplayMultipleCursors(Processor): + """ + When we're in Vi block insert mode, display all the cursors. + """ + + def apply_transformation( + self, transformation_input: TransformationInput + ) -> Transformation: + + ( + buffer_control, + document, + lineno, + source_to_display, + fragments, + _, + _, + ) = transformation_input.unpack() + + buff = buffer_control.buffer + + if vi_insert_multiple_mode(): + cursor_positions = buff.multiple_cursor_positions + fragments = explode_text_fragments(fragments) + + # If any cursor appears on the current line, highlight that. + start_pos = document.translate_row_col_to_index(lineno, 0) + end_pos = start_pos + len(document.lines[lineno]) + + fragment_suffix = " class:multiple-cursors" + + for p in cursor_positions: + if start_pos <= p <= end_pos: + column = source_to_display(p - start_pos) + + # Replace fragment. + try: + style, text, *_ = fragments[column] + except IndexError: + # Cursor needs to be displayed after the current text. + fragments.append((fragment_suffix, " ")) + else: + style += fragment_suffix + fragments[column] = (style, text) + + return Transformation(fragments) + else: + return Transformation(fragments) + + +class BeforeInput(Processor): + """ + Insert text before the input. + + :param text: This can be either plain text or formatted text + (or a callable that returns any of those). + :param style: style to be applied to this prompt/prefix. + """ + + def __init__(self, text: AnyFormattedText, style: str = "") -> None: + self.text = text + self.style = style + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + source_to_display: Optional[SourceToDisplay] + display_to_source: Optional[DisplayToSource] + + if ti.lineno == 0: + # Get fragments. + fragments_before = to_formatted_text(self.text, self.style) + fragments = fragments_before + ti.fragments + + shift_position = fragment_list_len(fragments_before) + source_to_display = lambda i: i + shift_position + display_to_source = lambda i: i - shift_position + else: + fragments = ti.fragments + source_to_display = None + display_to_source = None + + return Transformation( + fragments, + source_to_display=source_to_display, + display_to_source=display_to_source, + ) + + def __repr__(self) -> str: + return f"BeforeInput({self.text!r}, {self.style!r})" + + +class ShowArg(BeforeInput): + """ + Display the 'arg' in front of the input. + + This was used by the `PromptSession`, but now it uses the + `Window.get_line_prefix` function instead. + """ + + def __init__(self) -> None: + super().__init__(self._get_text_fragments) + + def _get_text_fragments(self) -> StyleAndTextTuples: + app = get_app() + if app.key_processor.arg is None: + return [] + else: + arg = app.key_processor.arg + + return [ + ("class:prompt.arg", "(arg: "), + ("class:prompt.arg.text", str(arg)), + ("class:prompt.arg", ") "), + ] + + def __repr__(self) -> str: + return "ShowArg()" + + +class AfterInput(Processor): + """ + Insert text after the input. + + :param text: This can be either plain text or formatted text + (or a callable that returns any of those). + :param style: style to be applied to this prompt/prefix. + """ + + def __init__(self, text: AnyFormattedText, style: str = "") -> None: + self.text = text + self.style = style + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + # Insert fragments after the last line. + if ti.lineno == ti.document.line_count - 1: + # Get fragments. + fragments_after = to_formatted_text(self.text, self.style) + return Transformation(fragments=ti.fragments + fragments_after) + else: + return Transformation(fragments=ti.fragments) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.text!r}, style={self.style!r})" + + +class AppendAutoSuggestion(Processor): + """ + Append the auto suggestion to the input. + (The user can then press the right arrow the insert the suggestion.) + """ + + def __init__(self, style: str = "class:auto-suggestion") -> None: + self.style = style + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + # Insert fragments after the last line. + if ti.lineno == ti.document.line_count - 1: + buffer = ti.buffer_control.buffer + + if buffer.suggestion and ti.document.is_cursor_at_the_end: + suggestion = buffer.suggestion.text + else: + suggestion = "" + + return Transformation(fragments=ti.fragments + [(self.style, suggestion)]) + else: + return Transformation(fragments=ti.fragments) + + +class ShowLeadingWhiteSpaceProcessor(Processor): + """ + Make leading whitespace visible. + + :param get_char: Callable that returns one character. + """ + + def __init__( + self, + get_char: Optional[Callable[[], str]] = None, + style: str = "class:leading-whitespace", + ) -> None: + def default_get_char() -> str: + if "\xb7".encode(get_app().output.encoding(), "replace") == b"?": + return "." + else: + return "\xb7" + + self.style = style + self.get_char = get_char or default_get_char + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + fragments = ti.fragments + + # Walk through all te fragments. + if fragments and fragment_list_to_text(fragments).startswith(" "): + t = (self.style, self.get_char()) + fragments = explode_text_fragments(fragments) + + for i in range(len(fragments)): + if fragments[i][1] == " ": + fragments[i] = t + else: + break + + return Transformation(fragments) + + +class ShowTrailingWhiteSpaceProcessor(Processor): + """ + Make trailing whitespace visible. + + :param get_char: Callable that returns one character. + """ + + def __init__( + self, + get_char: Optional[Callable[[], str]] = None, + style: str = "class:training-whitespace", + ) -> None: + def default_get_char() -> str: + if "\xb7".encode(get_app().output.encoding(), "replace") == b"?": + return "." + else: + return "\xb7" + + self.style = style + self.get_char = get_char or default_get_char + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + fragments = ti.fragments + + if fragments and fragments[-1][1].endswith(" "): + t = (self.style, self.get_char()) + fragments = explode_text_fragments(fragments) + + # Walk backwards through all te fragments and replace whitespace. + for i in range(len(fragments) - 1, -1, -1): + char = fragments[i][1] + if char == " ": + fragments[i] = t + else: + break + + return Transformation(fragments) + + +class TabsProcessor(Processor): + """ + Render tabs as spaces (instead of ^I) or make them visible (for instance, + by replacing them with dots.) + + :param tabstop: Horizontal space taken by a tab. (`int` or callable that + returns an `int`). + :param char1: Character or callable that returns a character (text of + length one). This one is used for the first space taken by the tab. + :param char2: Like `char1`, but for the rest of the space. + """ + + def __init__( + self, + tabstop: Union[int, Callable[[], int]] = 4, + char1: Union[str, Callable[[], str]] = "|", + char2: Union[str, Callable[[], str]] = "\u2508", + style: str = "class:tab", + ) -> None: + + self.char1 = char1 + self.char2 = char2 + self.tabstop = tabstop + self.style = style + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + tabstop = to_int(self.tabstop) + style = self.style + + # Create separator for tabs. + separator1 = to_str(self.char1) + separator2 = to_str(self.char2) + + # Transform fragments. + fragments = explode_text_fragments(ti.fragments) + + position_mappings = {} + result_fragments: StyleAndTextTuples = [] + pos = 0 + + for i, fragment_and_text in enumerate(fragments): + position_mappings[i] = pos + + if fragment_and_text[1] == "\t": + # Calculate how many characters we have to insert. + count = tabstop - (pos % tabstop) + if count == 0: + count = tabstop + + # Insert tab. + result_fragments.append((style, separator1)) + result_fragments.append((style, separator2 * (count - 1))) + pos += count + else: + result_fragments.append(fragment_and_text) + pos += 1 + + position_mappings[len(fragments)] = pos + # Add `pos+1` to mapping, because the cursor can be right after the + # line as well. + position_mappings[len(fragments) + 1] = pos + 1 + + def source_to_display(from_position: int) -> int: + "Maps original cursor position to the new one." + return position_mappings[from_position] + + def display_to_source(display_pos: int) -> int: + "Maps display cursor position to the original one." + position_mappings_reversed = {v: k for k, v in position_mappings.items()} + + while display_pos >= 0: + try: + return position_mappings_reversed[display_pos] + except KeyError: + display_pos -= 1 + return 0 + + return Transformation( + result_fragments, + source_to_display=source_to_display, + display_to_source=display_to_source, + ) + + +class ReverseSearchProcessor(Processor): + """ + Process to display the "(reverse-i-search)`...`:..." stuff around + the search buffer. + + Note: This processor is meant to be applied to the BufferControl that + contains the search buffer, it's not meant for the original input. + """ + + _excluded_input_processors: List[Type[Processor]] = [ + HighlightSearchProcessor, + HighlightSelectionProcessor, + BeforeInput, + AfterInput, + ] + + def _get_main_buffer( + self, buffer_control: "BufferControl" + ) -> Optional["BufferControl"]: + from prompt_toolkit.layout.controls import BufferControl + + prev_control = get_app().layout.search_target_buffer_control + if ( + isinstance(prev_control, BufferControl) + and prev_control.search_buffer_control == buffer_control + ): + return prev_control + return None + + def _content( + self, main_control: "BufferControl", ti: TransformationInput + ) -> "UIContent": + from prompt_toolkit.layout.controls import BufferControl + + # Emulate the BufferControl through which we are searching. + # For this we filter out some of the input processors. + excluded_processors = tuple(self._excluded_input_processors) + + def filter_processor(item: Processor) -> Optional[Processor]: + """Filter processors from the main control that we want to disable + here. This returns either an accepted processor or None.""" + # For a `_MergedProcessor`, check each individual processor, recursively. + if isinstance(item, _MergedProcessor): + accepted_processors = [filter_processor(p) for p in item.processors] + return merge_processors( + [p for p in accepted_processors if p is not None] + ) + + # For a `ConditionalProcessor`, check the body. + elif isinstance(item, ConditionalProcessor): + p = filter_processor(item.processor) + if p: + return ConditionalProcessor(p, item.filter) + + # Otherwise, check the processor itself. + else: + if not isinstance(item, excluded_processors): + return item + + return None + + filtered_processor = filter_processor( + merge_processors(main_control.input_processors or []) + ) + highlight_processor = HighlightIncrementalSearchProcessor() + + if filtered_processor: + new_processors = [filtered_processor, highlight_processor] + else: + new_processors = [highlight_processor] + + from .controls import SearchBufferControl + + assert isinstance(ti.buffer_control, SearchBufferControl) + + buffer_control = BufferControl( + buffer=main_control.buffer, + input_processors=new_processors, + include_default_input_processors=False, + lexer=main_control.lexer, + preview_search=True, + search_buffer_control=ti.buffer_control, + ) + + return buffer_control.create_content(ti.width, ti.height, preview_search=True) + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + from .controls import SearchBufferControl + + assert isinstance( + ti.buffer_control, SearchBufferControl + ), "`ReverseSearchProcessor` should be applied to a `SearchBufferControl` only." + + source_to_display: Optional[SourceToDisplay] + display_to_source: Optional[DisplayToSource] + + main_control = self._get_main_buffer(ti.buffer_control) + + if ti.lineno == 0 and main_control: + content = self._content(main_control, ti) + + # Get the line from the original document for this search. + line_fragments = content.get_line(content.cursor_position.y) + + if main_control.search_state.direction == SearchDirection.FORWARD: + direction_text = "i-search" + else: + direction_text = "reverse-i-search" + + fragments_before: StyleAndTextTuples = [ + ("class:prompt.search", "("), + ("class:prompt.search", direction_text), + ("class:prompt.search", ")`"), + ] + + fragments = ( + fragments_before + + [ + ("class:prompt.search.text", fragment_list_to_text(ti.fragments)), + ("", "': "), + ] + + line_fragments + ) + + shift_position = fragment_list_len(fragments_before) + source_to_display = lambda i: i + shift_position + display_to_source = lambda i: i - shift_position + else: + source_to_display = None + display_to_source = None + fragments = ti.fragments + + return Transformation( + fragments, + source_to_display=source_to_display, + display_to_source=display_to_source, + ) + + +class ConditionalProcessor(Processor): + """ + Processor that applies another processor, according to a certain condition. + Example:: + + # Create a function that returns whether or not the processor should + # currently be applied. + def highlight_enabled(): + return true_or_false + + # Wrapped it in a `ConditionalProcessor` for usage in a `BufferControl`. + BufferControl(input_processors=[ + ConditionalProcessor(HighlightSearchProcessor(), + Condition(highlight_enabled))]) + + :param processor: :class:`.Processor` instance. + :param filter: :class:`~prompt_toolkit.filters.Filter` instance. + """ + + def __init__(self, processor: Processor, filter: FilterOrBool) -> None: + self.processor = processor + self.filter = to_filter(filter) + + def apply_transformation( + self, transformation_input: TransformationInput + ) -> Transformation: + # Run processor when enabled. + if self.filter(): + return self.processor.apply_transformation(transformation_input) + else: + return Transformation(transformation_input.fragments) + + def __repr__(self) -> str: + return "{}(processor={!r}, filter={!r})".format( + self.__class__.__name__, + self.processor, + self.filter, + ) + + +class DynamicProcessor(Processor): + """ + Processor class that dynamically returns any Processor. + + :param get_processor: Callable that returns a :class:`.Processor` instance. + """ + + def __init__(self, get_processor: Callable[[], Optional[Processor]]) -> None: + self.get_processor = get_processor + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + processor = self.get_processor() or DummyProcessor() + return processor.apply_transformation(ti) + + +def merge_processors(processors: List[Processor]) -> Processor: + """ + Merge multiple `Processor` objects into one. + """ + if len(processors) == 0: + return DummyProcessor() + + if len(processors) == 1: + return processors[0] # Nothing to merge. + + return _MergedProcessor(processors) + + +class _MergedProcessor(Processor): + """ + Processor that groups multiple other `Processor` objects, but exposes an + API as if it is one `Processor`. + """ + + def __init__(self, processors: List[Processor]): + self.processors = processors + + def apply_transformation(self, ti: TransformationInput) -> Transformation: + source_to_display_functions = [ti.source_to_display] + display_to_source_functions = [] + fragments = ti.fragments + + def source_to_display(i: int) -> int: + """Translate x position from the buffer to the x position in the + processor fragments list.""" + for f in source_to_display_functions: + i = f(i) + return i + + for p in self.processors: + transformation = p.apply_transformation( + TransformationInput( + ti.buffer_control, + ti.document, + ti.lineno, + source_to_display, + fragments, + ti.width, + ti.height, + ) + ) + fragments = transformation.fragments + display_to_source_functions.append(transformation.display_to_source) + source_to_display_functions.append(transformation.source_to_display) + + def display_to_source(i: int) -> int: + for f in reversed(display_to_source_functions): + i = f(i) + return i + + # In the case of a nested _MergedProcessor, each processor wants to + # receive a 'source_to_display' function (as part of the + # TransformationInput) that has everything in the chain before + # included, because it can be called as part of the + # `apply_transformation` function. However, this first + # `source_to_display` should not be part of the output that we are + # returning. (This is the most consistent with `display_to_source`.) + del source_to_display_functions[:1] + + return Transformation(fragments, source_to_display, display_to_source) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/screen.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/screen.py new file mode 100644 index 0000000..5d27ab2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/screen.py @@ -0,0 +1,327 @@ +from collections import defaultdict +from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Tuple + +from prompt_toolkit.cache import FastDictCache +from prompt_toolkit.data_structures import Point +from prompt_toolkit.utils import get_cwidth + +if TYPE_CHECKING: + from .containers import Window + + +__all__ = [ + "Screen", + "Char", +] + + +class Char: + """ + Represent a single character in a :class:`.Screen`. + + This should be considered immutable. + + :param char: A single character (can be a double-width character). + :param style: A style string. (Can contain classnames.) + """ + + __slots__ = ("char", "style", "width") + + # If we end up having one of these special control sequences in the input string, + # we should display them as follows: + # Usually this happens after a "quoted insert". + display_mappings: Dict[str, str] = { + "\x00": "^@", # Control space + "\x01": "^A", + "\x02": "^B", + "\x03": "^C", + "\x04": "^D", + "\x05": "^E", + "\x06": "^F", + "\x07": "^G", + "\x08": "^H", + "\x09": "^I", + "\x0a": "^J", + "\x0b": "^K", + "\x0c": "^L", + "\x0d": "^M", + "\x0e": "^N", + "\x0f": "^O", + "\x10": "^P", + "\x11": "^Q", + "\x12": "^R", + "\x13": "^S", + "\x14": "^T", + "\x15": "^U", + "\x16": "^V", + "\x17": "^W", + "\x18": "^X", + "\x19": "^Y", + "\x1a": "^Z", + "\x1b": "^[", # Escape + "\x1c": "^\\", + "\x1d": "^]", + "\x1f": "^_", + "\x7f": "^?", # ASCII Delete (backspace). + # Special characters. All visualized like Vim does. + "\x80": "<80>", + "\x81": "<81>", + "\x82": "<82>", + "\x83": "<83>", + "\x84": "<84>", + "\x85": "<85>", + "\x86": "<86>", + "\x87": "<87>", + "\x88": "<88>", + "\x89": "<89>", + "\x8a": "<8a>", + "\x8b": "<8b>", + "\x8c": "<8c>", + "\x8d": "<8d>", + "\x8e": "<8e>", + "\x8f": "<8f>", + "\x90": "<90>", + "\x91": "<91>", + "\x92": "<92>", + "\x93": "<93>", + "\x94": "<94>", + "\x95": "<95>", + "\x96": "<96>", + "\x97": "<97>", + "\x98": "<98>", + "\x99": "<99>", + "\x9a": "<9a>", + "\x9b": "<9b>", + "\x9c": "<9c>", + "\x9d": "<9d>", + "\x9e": "<9e>", + "\x9f": "<9f>", + # For the non-breaking space: visualize like Emacs does by default. + # (Print a space, but attach the 'nbsp' class that applies the + # underline style.) + "\xa0": " ", + } + + def __init__(self, char: str = " ", style: str = "") -> None: + # If this character has to be displayed otherwise, take that one. + if char in self.display_mappings: + if char == "\xa0": + style += " class:nbsp " # Will be underlined. + else: + style += " class:control-character " + + char = self.display_mappings[char] + + self.char = char + self.style = style + + # Calculate width. (We always need this, so better to store it directly + # as a member for performance.) + self.width = get_cwidth(char) + + # In theory, `other` can be any type of object, but because of performance + # we don't want to do an `isinstance` check every time. We assume "other" + # is always a "Char". + def _equal(self, other: "Char") -> bool: + return self.char == other.char and self.style == other.style + + def _not_equal(self, other: "Char") -> bool: + # Not equal: We don't do `not char.__eq__` here, because of the + # performance of calling yet another function. + return self.char != other.char or self.style != other.style + + if not TYPE_CHECKING: + __eq__ = _equal + __ne__ = _not_equal + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.char!r}, {self.style!r})" + + +_CHAR_CACHE: FastDictCache[Tuple[str, str], Char] = FastDictCache( + Char, size=1000 * 1000 +) +Transparent = "[transparent]" + + +class Screen: + """ + Two dimensional buffer of :class:`.Char` instances. + """ + + def __init__( + self, + default_char: Optional[Char] = None, + initial_width: int = 0, + initial_height: int = 0, + ) -> None: + + if default_char is None: + default_char2 = _CHAR_CACHE[" ", Transparent] + else: + default_char2 = default_char + + self.data_buffer: DefaultDict[int, DefaultDict[int, Char]] = defaultdict( + lambda: defaultdict(lambda: default_char2) + ) + + #: Escape sequences to be injected. + self.zero_width_escapes: DefaultDict[int, DefaultDict[int, str]] = defaultdict( + lambda: defaultdict(lambda: "") + ) + + #: Position of the cursor. + self.cursor_positions: Dict[ + "Window", Point + ] = {} # Map `Window` objects to `Point` objects. + + #: Visibility of the cursor. + self.show_cursor = True + + #: (Optional) Where to position the menu. E.g. at the start of a completion. + #: (We can't use the cursor position, because we don't want the + #: completion menu to change its position when we browse through all the + #: completions.) + self.menu_positions: Dict[ + "Window", Point + ] = {} # Map `Window` objects to `Point` objects. + + #: Currently used width/height of the screen. This will increase when + #: data is written to the screen. + self.width = initial_width or 0 + self.height = initial_height or 0 + + # Windows that have been drawn. (Each `Window` class will add itself to + # this list.) + self.visible_windows_to_write_positions: Dict["Window", "WritePosition"] = {} + + # List of (z_index, draw_func) + self._draw_float_functions: List[Tuple[int, Callable[[], None]]] = [] + + @property + def visible_windows(self) -> List["Window"]: + return list(self.visible_windows_to_write_positions.keys()) + + def set_cursor_position(self, window: "Window", position: Point) -> None: + """ + Set the cursor position for a given window. + """ + self.cursor_positions[window] = position + + def set_menu_position(self, window: "Window", position: Point) -> None: + """ + Set the cursor position for a given window. + """ + self.menu_positions[window] = position + + def get_cursor_position(self, window: "Window") -> Point: + """ + Get the cursor position for a given window. + Returns a `Point`. + """ + try: + return self.cursor_positions[window] + except KeyError: + return Point(x=0, y=0) + + def get_menu_position(self, window: "Window") -> Point: + """ + Get the menu position for a given window. + (This falls back to the cursor position if no menu position was set.) + """ + try: + return self.menu_positions[window] + except KeyError: + try: + return self.cursor_positions[window] + except KeyError: + return Point(x=0, y=0) + + def draw_with_z_index(self, z_index: int, draw_func: Callable[[], None]) -> None: + """ + Add a draw-function for a `Window` which has a >= 0 z_index. + This will be postponed until `draw_all_floats` is called. + """ + self._draw_float_functions.append((z_index, draw_func)) + + def draw_all_floats(self) -> None: + """ + Draw all float functions in order of z-index. + """ + # We keep looping because some draw functions could add new functions + # to this list. See `FloatContainer`. + while self._draw_float_functions: + # Sort the floats that we have so far by z_index. + functions = sorted(self._draw_float_functions, key=lambda item: item[0]) + + # Draw only one at a time, then sort everything again. Now floats + # might have been added. + self._draw_float_functions = functions[1:] + functions[0][1]() + + def append_style_to_content(self, style_str: str) -> None: + """ + For all the characters in the screen. + Set the style string to the given `style_str`. + """ + b = self.data_buffer + char_cache = _CHAR_CACHE + + append_style = " " + style_str + + for y, row in b.items(): + for x, char in row.items(): + b[y][x] = char_cache[char.char, char.style + append_style] + + def fill_area( + self, write_position: "WritePosition", style: str = "", after: bool = False + ) -> None: + """ + Fill the content of this area, using the given `style`. + The style is prepended before whatever was here before. + """ + if not style.strip(): + return + + xmin = write_position.xpos + xmax = write_position.xpos + write_position.width + char_cache = _CHAR_CACHE + data_buffer = self.data_buffer + + if after: + append_style = " " + style + prepend_style = "" + else: + append_style = "" + prepend_style = style + " " + + for y in range( + write_position.ypos, write_position.ypos + write_position.height + ): + row = data_buffer[y] + for x in range(xmin, xmax): + cell = row[x] + row[x] = char_cache[ + cell.char, prepend_style + cell.style + append_style + ] + + +class WritePosition: + def __init__(self, xpos: int, ypos: int, width: int, height: int) -> None: + assert height >= 0 + assert width >= 0 + # xpos and ypos can be negative. (A float can be partially visible.) + + self.xpos = xpos + self.ypos = ypos + self.width = width + self.height = height + + def __repr__(self) -> str: + return "{}(x={!r}, y={!r}, width={!r}, height={!r})".format( + self.__class__.__name__, + self.xpos, + self.ypos, + self.width, + self.height, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/scrollable_pane.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/scrollable_pane.py new file mode 100644 index 0000000..a5500d7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/scrollable_pane.py @@ -0,0 +1,493 @@ +from typing import Dict, List, Optional + +from prompt_toolkit.data_structures import Point +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.key_binding import KeyBindingsBase +from prompt_toolkit.mouse_events import MouseEvent + +from .containers import Container, ScrollOffsets +from .dimension import AnyDimension, Dimension, sum_layout_dimensions, to_dimension +from .mouse_handlers import MouseHandler, MouseHandlers +from .screen import Char, Screen, WritePosition + +__all__ = ["ScrollablePane"] + +# Never go beyond this height, because performance will degrade. +MAX_AVAILABLE_HEIGHT = 10_000 + + +class ScrollablePane(Container): + """ + Container widget that exposes a larger virtual screen to its content and + displays it in a vertical scrollbale region. + + Typically this is wrapped in a large `HSplit` container. Make sure in that + case to not specify a `height` dimension of the `HSplit`, so that it will + scale according to the content. + + .. note:: + + If you want to display a completion menu for widgets in this + `ScrollablePane`, then it's still a good practice to use a + `FloatContainer` with a `CompletionsMenu` in a `Float` at the top-level + of the layout hierarchy, rather then nesting a `FloatContainer` in this + `ScrollablePane`. (Otherwise, it's possible that the completion menu + is clipped.) + + :param content: The content container. + :param scrolloffset: Try to keep the cursor within this distance from the + top/bottom (left/right offset is not used). + :param keep_cursor_visible: When `True`, automatically scroll the pane so + that the cursor (of the focused window) is always visible. + :param keep_focused_window_visible: When `True`, automatically scroll th e + pane so that the focused window is visible, or as much visible as + possible if it doen't completely fit the screen. + :param max_available_height: Always constraint the height to this amount + for performance reasons. + :param width: When given, use this width instead of looking at the children. + :param height: When given, use this height instead of looking at the children. + :param show_scrollbar: When `True` display a scrollbar on the right. + """ + + def __init__( + self, + content: Container, + scroll_offsets: Optional[ScrollOffsets] = None, + keep_cursor_visible: FilterOrBool = True, + keep_focused_window_visible: FilterOrBool = True, + max_available_height: int = MAX_AVAILABLE_HEIGHT, + width: AnyDimension = None, + height: AnyDimension = None, + show_scrollbar: FilterOrBool = True, + display_arrows: FilterOrBool = True, + up_arrow_symbol: str = "^", + down_arrow_symbol: str = "v", + ) -> None: + self.content = content + self.scroll_offsets = scroll_offsets or ScrollOffsets(top=1, bottom=1) + self.keep_cursor_visible = to_filter(keep_cursor_visible) + self.keep_focused_window_visible = to_filter(keep_focused_window_visible) + self.max_available_height = max_available_height + self.width = width + self.height = height + self.show_scrollbar = to_filter(show_scrollbar) + self.display_arrows = to_filter(display_arrows) + self.up_arrow_symbol = up_arrow_symbol + self.down_arrow_symbol = down_arrow_symbol + + self.vertical_scroll = 0 + + def __repr__(self) -> str: + return f"ScrollablePane({self.content!r})" + + def reset(self) -> None: + self.content.reset() + + def preferred_width(self, max_available_width: int) -> Dimension: + if self.width is not None: + return to_dimension(self.width) + + # We're only scrolling vertical. So the preferred width is equal to + # that of the content. + content_width = self.content.preferred_width(max_available_width) + + # If a scrollbar needs to be displayed, add +1 to the content width. + if self.show_scrollbar(): + return sum_layout_dimensions([Dimension.exact(1), content_width]) + + return content_width + + def preferred_height(self, width: int, max_available_height: int) -> Dimension: + if self.height is not None: + return to_dimension(self.height) + + # Prefer a height large enough so that it fits all the content. If not, + # we'll make the pane scrollable. + if self.show_scrollbar(): + # If `show_scrollbar` is set. Always reserve space for the scrollbar. + width -= 1 + + dimension = self.content.preferred_height(width, self.max_available_height) + + # Only take 'preferred' into account. Min/max can be anything. + return Dimension(min=0, preferred=dimension.preferred) + + def write_to_screen( + self, + screen: Screen, + mouse_handlers: MouseHandlers, + write_position: WritePosition, + parent_style: str, + erase_bg: bool, + z_index: Optional[int], + ) -> None: + """ + Render scrollable pane content. + + This works by rendering on an off-screen canvas, and copying over the + visible region. + """ + show_scrollbar = self.show_scrollbar() + + if show_scrollbar: + virtual_width = write_position.width - 1 + else: + virtual_width = write_position.width + + # Compute preferred height again. + virtual_height = self.content.preferred_height( + virtual_width, self.max_available_height + ).preferred + + # Ensure virtual height is at least the available height. + virtual_height = max(virtual_height, write_position.height) + virtual_height = min(virtual_height, self.max_available_height) + + # First, write the content to a virtual screen, then copy over the + # visible part to the real screen. + temp_screen = Screen(default_char=Char(char=" ", style=parent_style)) + temp_write_position = WritePosition( + xpos=0, ypos=0, width=virtual_width, height=virtual_height + ) + + temp_mouse_handlers = MouseHandlers() + + self.content.write_to_screen( + temp_screen, + temp_mouse_handlers, + temp_write_position, + parent_style, + erase_bg, + z_index, + ) + temp_screen.draw_all_floats() + + # If anything in the virtual screen is focused, move vertical scroll to + from prompt_toolkit.application import get_app + + focused_window = get_app().layout.current_window + + try: + visible_win_write_pos = temp_screen.visible_windows_to_write_positions[ + focused_window + ] + except KeyError: + pass # No window focused here. Don't scroll. + else: + # Make sure this window is visible. + self._make_window_visible( + write_position.height, + virtual_height, + visible_win_write_pos, + temp_screen.cursor_positions.get(focused_window), + ) + + # Copy over virtual screen and zero width escapes to real screen. + self._copy_over_screen(screen, temp_screen, write_position, virtual_width) + + # Copy over mouse handlers. + self._copy_over_mouse_handlers( + mouse_handlers, temp_mouse_handlers, write_position, virtual_width + ) + + # Set screen.width/height. + ypos = write_position.ypos + xpos = write_position.xpos + + screen.width = max(screen.width, xpos + virtual_width) + screen.height = max(screen.height, ypos + write_position.height) + + # Copy over window write positions. + self._copy_over_write_positions(screen, temp_screen, write_position) + + if temp_screen.show_cursor: + screen.show_cursor = True + + # Copy over cursor positions, if they are visible. + for window, point in temp_screen.cursor_positions.items(): + if ( + 0 <= point.x < write_position.width + and self.vertical_scroll + <= point.y + < write_position.height + self.vertical_scroll + ): + screen.cursor_positions[window] = Point( + x=point.x + xpos, y=point.y + ypos - self.vertical_scroll + ) + + # Copy over menu positions, but clip them to the visible area. + for window, point in temp_screen.menu_positions.items(): + screen.menu_positions[window] = self._clip_point_to_visible_area( + Point(x=point.x + xpos, y=point.y + ypos - self.vertical_scroll), + write_position, + ) + + # Draw scrollbar. + if show_scrollbar: + self._draw_scrollbar( + write_position, + virtual_height, + screen, + ) + + def _clip_point_to_visible_area( + self, point: Point, write_position: WritePosition + ) -> Point: + """ + Ensure that the cursor and menu positions always are always reported + """ + if point.x < write_position.xpos: + point = point._replace(x=write_position.xpos) + if point.y < write_position.ypos: + point = point._replace(y=write_position.ypos) + if point.x >= write_position.xpos + write_position.width: + point = point._replace(x=write_position.xpos + write_position.width - 1) + if point.y >= write_position.ypos + write_position.height: + point = point._replace(y=write_position.ypos + write_position.height - 1) + + return point + + def _copy_over_screen( + self, + screen: Screen, + temp_screen: Screen, + write_position: WritePosition, + virtual_width: int, + ) -> None: + """ + Copy over visible screen content and "zero width escape sequences". + """ + ypos = write_position.ypos + xpos = write_position.xpos + + for y in range(write_position.height): + temp_row = temp_screen.data_buffer[y + self.vertical_scroll] + row = screen.data_buffer[y + ypos] + temp_zero_width_escapes = temp_screen.zero_width_escapes[ + y + self.vertical_scroll + ] + zero_width_escapes = screen.zero_width_escapes[y + ypos] + + for x in range(virtual_width): + row[x + xpos] = temp_row[x] + + if x in temp_zero_width_escapes: + zero_width_escapes[x + xpos] = temp_zero_width_escapes[x] + + def _copy_over_mouse_handlers( + self, + mouse_handlers: MouseHandlers, + temp_mouse_handlers: MouseHandlers, + write_position: WritePosition, + virtual_width: int, + ) -> None: + """ + Copy over mouse handlers from virtual screen to real screen. + + Note: we take `virtual_width` because we don't want to copy over mouse + handlers that we possibly have behind the scrollbar. + """ + ypos = write_position.ypos + xpos = write_position.xpos + + # Cache mouse handlers when wrapping them. Very often the same mouse + # handler is registered for many positions. + mouse_handler_wrappers: Dict[MouseHandler, MouseHandler] = {} + + def wrap_mouse_handler(handler: MouseHandler) -> MouseHandler: + "Wrap mouse handler. Translate coordinates in `MouseEvent`." + if handler not in mouse_handler_wrappers: + + def new_handler(event: MouseEvent) -> None: + new_event = MouseEvent( + position=Point( + x=event.position.x - xpos, + y=event.position.y + self.vertical_scroll - ypos, + ), + event_type=event.event_type, + button=event.button, + modifiers=event.modifiers, + ) + handler(new_event) + + mouse_handler_wrappers[handler] = new_handler + return mouse_handler_wrappers[handler] + + # Copy handlers. + mouse_handlers_dict = mouse_handlers.mouse_handlers + temp_mouse_handlers_dict = temp_mouse_handlers.mouse_handlers + + for y in range(write_position.height): + if y in temp_mouse_handlers_dict: + temp_mouse_row = temp_mouse_handlers_dict[y + self.vertical_scroll] + mouse_row = mouse_handlers_dict[y + ypos] + for x in range(virtual_width): + if x in temp_mouse_row: + mouse_row[x + xpos] = wrap_mouse_handler(temp_mouse_row[x]) + + def _copy_over_write_positions( + self, screen: Screen, temp_screen: Screen, write_position: WritePosition + ) -> None: + """ + Copy over window write positions. + """ + ypos = write_position.ypos + xpos = write_position.xpos + + for win, write_pos in temp_screen.visible_windows_to_write_positions.items(): + screen.visible_windows_to_write_positions[win] = WritePosition( + xpos=write_pos.xpos + xpos, + ypos=write_pos.ypos + ypos - self.vertical_scroll, + # TODO: if the window is only partly visible, then truncate width/height. + # This could be important if we have nested ScrollablePanes. + height=write_pos.height, + width=write_pos.width, + ) + + def is_modal(self) -> bool: + return self.content.is_modal() + + def get_key_bindings(self) -> Optional[KeyBindingsBase]: + return self.content.get_key_bindings() + + def get_children(self) -> List["Container"]: + return [self.content] + + def _make_window_visible( + self, + visible_height: int, + virtual_height: int, + visible_win_write_pos: WritePosition, + cursor_position: Optional[Point], + ) -> None: + """ + Scroll the scrollable pane, so that this window becomes visible. + + :param visible_height: Height of this `ScrollablePane` that is rendered. + :param virtual_height: Height of the virtual, temp screen. + :param visible_win_write_pos: `WritePosition` of the nested window on the + temp screen. + :param cursor_position: The location of the cursor position of this + window on the temp screen. + """ + # Start with maximum allowed scroll range, and then reduce according to + # the focused window and cursor position. + min_scroll = 0 + max_scroll = virtual_height - visible_height + + if self.keep_cursor_visible(): + # Reduce min/max scroll according to the cursor in the focused window. + if cursor_position is not None: + offsets = self.scroll_offsets + cpos_min_scroll = ( + cursor_position.y - visible_height + 1 + offsets.bottom + ) + cpos_max_scroll = cursor_position.y - offsets.top + min_scroll = max(min_scroll, cpos_min_scroll) + max_scroll = max(0, min(max_scroll, cpos_max_scroll)) + + if self.keep_focused_window_visible(): + # Reduce min/max scroll according to focused window position. + # If the window is small enough, bot the top and bottom of the window + # should be visible. + if visible_win_write_pos.height <= visible_height: + window_min_scroll = ( + visible_win_write_pos.ypos + + visible_win_write_pos.height + - visible_height + ) + window_max_scroll = visible_win_write_pos.ypos + else: + # Window does not fit on the screen. Make sure at least the whole + # screen is occupied with this window, and nothing else is shown. + window_min_scroll = visible_win_write_pos.ypos + window_max_scroll = ( + visible_win_write_pos.ypos + + visible_win_write_pos.height + - visible_height + ) + + min_scroll = max(min_scroll, window_min_scroll) + max_scroll = min(max_scroll, window_max_scroll) + + if min_scroll > max_scroll: + min_scroll = max_scroll # Should not happen. + + # Finally, properly clip the vertical scroll. + if self.vertical_scroll > max_scroll: + self.vertical_scroll = max_scroll + if self.vertical_scroll < min_scroll: + self.vertical_scroll = min_scroll + + def _draw_scrollbar( + self, write_position: WritePosition, content_height: int, screen: Screen + ) -> None: + """ + Draw the scrollbar on the screen. + + Note: There is some code duplication with the `ScrollbarMargin` + implementation. + """ + + window_height = write_position.height + display_arrows = self.display_arrows() + + if display_arrows: + window_height -= 2 + + try: + fraction_visible = write_position.height / float(content_height) + fraction_above = self.vertical_scroll / float(content_height) + + scrollbar_height = int( + min(window_height, max(1, window_height * fraction_visible)) + ) + scrollbar_top = int(window_height * fraction_above) + except ZeroDivisionError: + return + else: + + def is_scroll_button(row: int) -> bool: + "True if we should display a button on this row." + return scrollbar_top <= row <= scrollbar_top + scrollbar_height + + xpos = write_position.xpos + write_position.width - 1 + ypos = write_position.ypos + data_buffer = screen.data_buffer + + # Up arrow. + if display_arrows: + data_buffer[ypos][xpos] = Char( + self.up_arrow_symbol, "class:scrollbar.arrow" + ) + ypos += 1 + + # Scrollbar body. + scrollbar_background = "class:scrollbar.background" + scrollbar_background_start = "class:scrollbar.background,scrollbar.start" + scrollbar_button = "class:scrollbar.button" + scrollbar_button_end = "class:scrollbar.button,scrollbar.end" + + for i in range(window_height): + style = "" + if is_scroll_button(i): + if not is_scroll_button(i + 1): + # Give the last cell a different style, because we want + # to underline this. + style = scrollbar_button_end + else: + style = scrollbar_button + else: + if is_scroll_button(i + 1): + style = scrollbar_background_start + else: + style = scrollbar_background + + data_buffer[ypos][xpos] = Char(" ", style) + ypos += 1 + + # Down arrow + if display_arrows: + data_buffer[ypos][xpos] = Char( + self.down_arrow_symbol, "class:scrollbar.arrow" + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/utils.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/utils.py new file mode 100644 index 0000000..2e0f343 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/layout/utils.py @@ -0,0 +1,80 @@ +from typing import TYPE_CHECKING, Iterable, List, TypeVar, Union, cast, overload + +from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple + +if TYPE_CHECKING: + from typing_extensions import SupportsIndex + +__all__ = [ + "explode_text_fragments", +] + +_T = TypeVar("_T", bound=OneStyleAndTextTuple) + + +class _ExplodedList(List[_T]): + """ + Wrapper around a list, that marks it as 'exploded'. + + As soon as items are added or the list is extended, the new items are + automatically exploded as well. + """ + + exploded = True + + def append(self, item: _T) -> None: + self.extend([item]) + + def extend(self, lst: Iterable[_T]) -> None: + super().extend(explode_text_fragments(lst)) + + def insert(self, index: "SupportsIndex", item: _T) -> None: + raise NotImplementedError # TODO + + # TODO: When creating a copy() or [:], return also an _ExplodedList. + + @overload + def __setitem__(self, index: "SupportsIndex", value: _T) -> None: + ... + + @overload + def __setitem__(self, index: slice, value: Iterable[_T]) -> None: + ... + + def __setitem__( + self, index: Union["SupportsIndex", slice], value: Union[_T, Iterable[_T]] + ) -> None: + """ + Ensure that when `(style_str, 'long string')` is set, the string will be + exploded. + """ + if not isinstance(index, slice): + int_index = index.__index__() + index = slice(int_index, int_index + 1) + if isinstance(value, tuple): # In case of `OneStyleAndTextTuple`. + value = cast("List[_T]", [value]) + + super().__setitem__(index, explode_text_fragments(cast("Iterable[_T]", value))) + + +def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]: + """ + Turn a list of (style_str, text) tuples into another list where each string is + exactly one character. + + It should be fine to call this function several times. Calling this on a + list that is already exploded, is a null operation. + + :param fragments: List of (style, text) tuples. + """ + # When the fragments is already exploded, don't explode again. + if isinstance(fragments, _ExplodedList): + return fragments + + result: List[_T] = [] + + for style, string, *rest in fragments: # type: ignore + for c in string: # type: ignore + result.append((style, c, *rest)) # type: ignore + + return _ExplodedList(result) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__init__.py new file mode 100644 index 0000000..3e875e6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__init__.py @@ -0,0 +1,18 @@ +""" +Lexer interface and implementations. +Used for syntax highlighting. +""" +from .base import DynamicLexer, Lexer, SimpleLexer +from .pygments import PygmentsLexer, RegexSync, SyncFromStart, SyntaxSync + +__all__ = [ + # Base. + "Lexer", + "SimpleLexer", + "DynamicLexer", + # Pygments. + "PygmentsLexer", + "RegexSync", + "SyncFromStart", + "SyntaxSync", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..7ac7924 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..1996961 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/pygments.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/pygments.cpython-38.pyc new file mode 100644 index 0000000..a737e3f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/__pycache__/pygments.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/base.py new file mode 100644 index 0000000..dd9f245 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/base.py @@ -0,0 +1,82 @@ +""" +Base classes for prompt_toolkit lexers. +""" +from abc import ABCMeta, abstractmethod +from typing import Callable, Hashable, Optional + +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text.base import StyleAndTextTuples + +__all__ = [ + "Lexer", + "SimpleLexer", + "DynamicLexer", +] + + +class Lexer(metaclass=ABCMeta): + """ + Base class for all lexers. + """ + + @abstractmethod + def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: + """ + Takes a :class:`~prompt_toolkit.document.Document` and returns a + callable that takes a line number and returns a list of + ``(style_str, text)`` tuples for that line. + + XXX: Note that in the past, this was supposed to return a list + of ``(Token, text)`` tuples, just like a Pygments lexer. + """ + + def invalidation_hash(self) -> Hashable: + """ + When this changes, `lex_document` could give a different output. + (Only used for `DynamicLexer`.) + """ + return id(self) + + +class SimpleLexer(Lexer): + """ + Lexer that doesn't do any tokenizing and returns the whole input as one + token. + + :param style: The style string for this lexer. + """ + + def __init__(self, style: str = "") -> None: + self.style = style + + def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: + lines = document.lines + + def get_line(lineno: int) -> StyleAndTextTuples: + "Return the tokens for the given line." + try: + return [(self.style, lines[lineno])] + except IndexError: + return [] + + return get_line + + +class DynamicLexer(Lexer): + """ + Lexer class that can dynamically returns any Lexer. + + :param get_lexer: Callable that returns a :class:`.Lexer` instance. + """ + + def __init__(self, get_lexer: Callable[[], Optional[Lexer]]) -> None: + self.get_lexer = get_lexer + self._dummy = SimpleLexer() + + def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: + lexer = self.get_lexer() or self._dummy + return lexer.lex_document(document) + + def invalidation_hash(self) -> Hashable: + lexer = self.get_lexer() or self._dummy + return id(lexer) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/pygments.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/pygments.py new file mode 100644 index 0000000..d50f8af --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/lexers/pygments.py @@ -0,0 +1,335 @@ +""" +Adaptor classes for using Pygments lexers within prompt_toolkit. + +This includes syntax synchronization code, so that we don't have to start +lexing at the beginning of a document, when displaying a very large text. +""" +import re +from abc import ABCMeta, abstractmethod +from typing import ( + TYPE_CHECKING, + Callable, + Dict, + Generator, + Iterable, + Optional, + Tuple, + Type, +) + +from prompt_toolkit.document import Document +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.formatted_text.base import StyleAndTextTuples +from prompt_toolkit.formatted_text.utils import split_lines +from prompt_toolkit.styles.pygments import pygments_token_to_classname + +from .base import Lexer, SimpleLexer + +if TYPE_CHECKING: + from pygments.lexer import Lexer as PygmentsLexerCls + +__all__ = [ + "PygmentsLexer", + "SyntaxSync", + "SyncFromStart", + "RegexSync", +] + + +class SyntaxSync(metaclass=ABCMeta): + """ + Syntax synchroniser. This is a tool that finds a start position for the + lexer. This is especially important when editing big documents; we don't + want to start the highlighting by running the lexer from the beginning of + the file. That is very slow when editing. + """ + + @abstractmethod + def get_sync_start_position( + self, document: Document, lineno: int + ) -> Tuple[int, int]: + """ + Return the position from where we can start lexing as a (row, column) + tuple. + + :param document: `Document` instance that contains all the lines. + :param lineno: The line that we want to highlight. (We need to return + this line, or an earlier position.) + """ + + +class SyncFromStart(SyntaxSync): + """ + Always start the syntax highlighting from the beginning. + """ + + def get_sync_start_position( + self, document: Document, lineno: int + ) -> Tuple[int, int]: + return 0, 0 + + +class RegexSync(SyntaxSync): + """ + Synchronize by starting at a line that matches the given regex pattern. + """ + + # Never go more than this amount of lines backwards for synchronisation. + # That would be too CPU intensive. + MAX_BACKWARDS = 500 + + # Start lexing at the start, if we are in the first 'n' lines and no + # synchronisation position was found. + FROM_START_IF_NO_SYNC_POS_FOUND = 100 + + def __init__(self, pattern: str) -> None: + self._compiled_pattern = re.compile(pattern) + + def get_sync_start_position( + self, document: Document, lineno: int + ) -> Tuple[int, int]: + """ + Scan backwards, and find a possible position to start. + """ + pattern = self._compiled_pattern + lines = document.lines + + # Scan upwards, until we find a point where we can start the syntax + # synchronisation. + for i in range(lineno, max(-1, lineno - self.MAX_BACKWARDS), -1): + match = pattern.match(lines[i]) + if match: + return i, match.start() + + # No synchronisation point found. If we aren't that far from the + # beginning, start at the very beginning, otherwise, just try to start + # at the current line. + if lineno < self.FROM_START_IF_NO_SYNC_POS_FOUND: + return 0, 0 + else: + return lineno, 0 + + @classmethod + def from_pygments_lexer_cls(cls, lexer_cls: "PygmentsLexerCls") -> "RegexSync": + """ + Create a :class:`.RegexSync` instance for this Pygments lexer class. + """ + patterns = { + # For Python, start highlighting at any class/def block. + "Python": r"^\s*(class|def)\s+", + "Python 3": r"^\s*(class|def)\s+", + # For HTML, start at any open/close tag definition. + "HTML": r"<[/a-zA-Z]", + # For javascript, start at a function. + "JavaScript": r"\bfunction\b" + # TODO: Add definitions for other languages. + # By default, we start at every possible line. + } + p = patterns.get(lexer_cls.name, "^") + return cls(p) + + +class _TokenCache(Dict[Tuple[str, ...], str]): + """ + Cache that converts Pygments tokens into `prompt_toolkit` style objects. + + ``Token.A.B.C`` will be converted into: + ``class:pygments,pygments.A,pygments.A.B,pygments.A.B.C`` + """ + + def __missing__(self, key: Tuple[str, ...]) -> str: + result = "class:" + pygments_token_to_classname(key) + self[key] = result + return result + + +_token_cache = _TokenCache() + + +class PygmentsLexer(Lexer): + """ + Lexer that calls a pygments lexer. + + Example:: + + from pygments.lexers.html import HtmlLexer + lexer = PygmentsLexer(HtmlLexer) + + Note: Don't forget to also load a Pygments compatible style. E.g.:: + + from prompt_toolkit.styles.from_pygments import style_from_pygments_cls + from pygments.styles import get_style_by_name + style = style_from_pygments_cls(get_style_by_name('monokai')) + + :param pygments_lexer_cls: A `Lexer` from Pygments. + :param sync_from_start: Start lexing at the start of the document. This + will always give the best results, but it will be slow for bigger + documents. (When the last part of the document is display, then the + whole document will be lexed by Pygments on every key stroke.) It is + recommended to disable this for inputs that are expected to be more + than 1,000 lines. + :param syntax_sync: `SyntaxSync` object. + """ + + # Minimum amount of lines to go backwards when starting the parser. + # This is important when the lines are retrieved in reverse order, or when + # scrolling upwards. (Due to the complexity of calculating the vertical + # scroll offset in the `Window` class, lines are not always retrieved in + # order.) + MIN_LINES_BACKWARDS = 50 + + # When a parser was started this amount of lines back, read the parser + # until we get the current line. Otherwise, start a new parser. + # (This should probably be bigger than MIN_LINES_BACKWARDS.) + REUSE_GENERATOR_MAX_DISTANCE = 100 + + def __init__( + self, + pygments_lexer_cls: Type["PygmentsLexerCls"], + sync_from_start: FilterOrBool = True, + syntax_sync: Optional[SyntaxSync] = None, + ) -> None: + + self.pygments_lexer_cls = pygments_lexer_cls + self.sync_from_start = to_filter(sync_from_start) + + # Instantiate the Pygments lexer. + self.pygments_lexer = pygments_lexer_cls( + stripnl=False, stripall=False, ensurenl=False + ) + + # Create syntax sync instance. + self.syntax_sync = syntax_sync or RegexSync.from_pygments_lexer_cls( + pygments_lexer_cls + ) + + @classmethod + def from_filename( + cls, filename: str, sync_from_start: FilterOrBool = True + ) -> "Lexer": + """ + Create a `Lexer` from a filename. + """ + # Inline imports: the Pygments dependency is optional! + from pygments.lexers import get_lexer_for_filename + from pygments.util import ClassNotFound + + try: + pygments_lexer = get_lexer_for_filename(filename) + except ClassNotFound: + return SimpleLexer() + else: + return cls(pygments_lexer.__class__, sync_from_start=sync_from_start) + + def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: + """ + Create a lexer function that takes a line number and returns the list + of (style_str, text) tuples as the Pygments lexer returns for that line. + """ + LineGenerator = Generator[Tuple[int, StyleAndTextTuples], None, None] + + # Cache of already lexed lines. + cache: Dict[int, StyleAndTextTuples] = {} + + # Pygments generators that are currently lexing. + # Map lexer generator to the line number. + line_generators: Dict[LineGenerator, int] = {} + + def get_syntax_sync() -> SyntaxSync: + "The Syntax synchronisation object that we currently use." + if self.sync_from_start(): + return SyncFromStart() + else: + return self.syntax_sync + + def find_closest_generator(i: int) -> Optional[LineGenerator]: + "Return a generator close to line 'i', or None if none was found." + for generator, lineno in line_generators.items(): + if lineno < i and i - lineno < self.REUSE_GENERATOR_MAX_DISTANCE: + return generator + return None + + def create_line_generator(start_lineno: int, column: int = 0) -> LineGenerator: + """ + Create a generator that yields the lexed lines. + Each iteration it yields a (line_number, [(style_str, text), ...]) tuple. + """ + + def get_text_fragments() -> Iterable[Tuple[str, str]]: + text = "\n".join(document.lines[start_lineno:])[column:] + + # We call `get_text_fragments_unprocessed`, because `get_tokens` will + # still replace \r\n and \r by \n. (We don't want that, + # Pygments should return exactly the same amount of text, as we + # have given as input.) + for _, t, v in self.pygments_lexer.get_tokens_unprocessed(text): + # Turn Pygments `Token` object into prompt_toolkit style + # strings. + yield _token_cache[t], v + + yield from enumerate(split_lines(list(get_text_fragments())), start_lineno) + + def get_generator(i: int) -> LineGenerator: + """ + Find an already started generator that is close, or create a new one. + """ + # Find closest line generator. + generator = find_closest_generator(i) + if generator: + return generator + + # No generator found. Determine starting point for the syntax + # synchronisation first. + + # Go at least x lines back. (Make scrolling upwards more + # efficient.) + i = max(0, i - self.MIN_LINES_BACKWARDS) + + if i == 0: + row = 0 + column = 0 + else: + row, column = get_syntax_sync().get_sync_start_position(document, i) + + # Find generator close to this point, or otherwise create a new one. + generator = find_closest_generator(i) + if generator: + return generator + else: + generator = create_line_generator(row, column) + + # If the column is not 0, ignore the first line. (Which is + # incomplete. This happens when the synchronisation algorithm tells + # us to start parsing in the middle of a line.) + if column: + next(generator) + row += 1 + + line_generators[generator] = row + return generator + + def get_line(i: int) -> StyleAndTextTuples: + "Return the tokens for a given line number." + try: + return cache[i] + except KeyError: + generator = get_generator(i) + + # Exhaust the generator, until we find the requested line. + for num, line in generator: + cache[num] = line + if num == i: + line_generators[generator] = i + + # Remove the next item from the cache. + # (It could happen that it's already there, because of + # another generator that started filling these lines, + # but we want to synchronise these lines with the + # current lexer's state.) + if num + 1 in cache: + del cache[num + 1] + + return cache[num] + return [] + + return get_line diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/log.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/log.py new file mode 100644 index 0000000..36ceced --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/log.py @@ -0,0 +1,10 @@ +""" +Logging configuration. +""" +import logging + +__all__ = [ + "logger", +] + +logger = logging.getLogger(__package__) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/mouse_events.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/mouse_events.py new file mode 100644 index 0000000..e10ff03 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/mouse_events.py @@ -0,0 +1,88 @@ +""" +Mouse events. + + +How it works +------------ + +The renderer has a 2 dimensional grid of mouse event handlers. +(`prompt_toolkit.layout.MouseHandlers`.) When the layout is rendered, the +`Window` class will make sure that this grid will also be filled with +callbacks. For vt100 terminals, mouse events are received through stdin, just +like any other key press. There is a handler among the key bindings that +catches these events and forwards them to such a mouse event handler. It passes +through the `Window` class where the coordinates are translated from absolute +coordinates to coordinates relative to the user control, and there +`UIControl.mouse_handler` is called. +""" +from enum import Enum +from typing import FrozenSet + +from .data_structures import Point + +__all__ = ["MouseEventType", "MouseButton", "MouseModifier", "MouseEvent"] + + +class MouseEventType(Enum): + # Mouse up: This same event type is fired for all three events: left mouse + # up, right mouse up, or middle mouse up + MOUSE_UP = "MOUSE_UP" + + # Mouse down: This implicitly refers to the left mouse down (this event is + # not fired upon pressing the middle or right mouse buttons). + MOUSE_DOWN = "MOUSE_DOWN" + + SCROLL_UP = "SCROLL_UP" + SCROLL_DOWN = "SCROLL_DOWN" + + # Triggered when the left mouse button is held down, and the mouse moves + MOUSE_MOVE = "MOUSE_MOVE" + + +class MouseButton(Enum): + LEFT = "LEFT" + MIDDLE = "MIDDLE" + RIGHT = "RIGHT" + + # When we're scrolling, or just moving the mouse and not pressing a button. + NONE = "NONE" + + # This is for when we don't know which mouse button was pressed, but we do + # know that one has been pressed during this mouse event (as opposed to + # scrolling, for example) + UNKNOWN = "UNKNOWN" + + +class MouseModifier(Enum): + SHIFT = "SHIFT" + ALT = "ALT" + CONTROL = "CONTROL" + + +class MouseEvent: + """ + Mouse event, sent to `UIControl.mouse_handler`. + + :param position: `Point` instance. + :param event_type: `MouseEventType`. + """ + + def __init__( + self, + position: Point, + event_type: MouseEventType, + button: MouseButton, + modifiers: FrozenSet[MouseModifier], + ) -> None: + self.position = position + self.event_type = event_type + self.button = button + self.modifiers = modifiers + + def __repr__(self) -> str: + return "MouseEvent({!r},{!r},{!r},{!r})".format( + self.position, + self.event_type, + self.button, + self.modifiers, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__init__.py new file mode 100644 index 0000000..7b90b47 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__init__.py @@ -0,0 +1,13 @@ +from .base import DummyOutput, Output +from .color_depth import ColorDepth +from .defaults import create_output + +__all__ = [ + # Base. + "Output", + "DummyOutput", + # Color depth. + "ColorDepth", + # Defaults. + "create_output", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3b0454f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..4557c74 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/color_depth.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/color_depth.cpython-38.pyc new file mode 100644 index 0000000..ca8d659 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/color_depth.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/conemu.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/conemu.cpython-38.pyc new file mode 100644 index 0000000..6fd549b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/conemu.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/defaults.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/defaults.cpython-38.pyc new file mode 100644 index 0000000..430a66d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/defaults.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/flush_stdout.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/flush_stdout.cpython-38.pyc new file mode 100644 index 0000000..51b6a23 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/flush_stdout.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/plain_text.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/plain_text.cpython-38.pyc new file mode 100644 index 0000000..daebb80 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/plain_text.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/vt100.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/vt100.cpython-38.pyc new file mode 100644 index 0000000..4c7dd93 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/vt100.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/win32.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/win32.cpython-38.pyc new file mode 100644 index 0000000..02a9c2c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/win32.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/windows10.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/windows10.cpython-38.pyc new file mode 100644 index 0000000..c032a65 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/__pycache__/windows10.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/base.py new file mode 100644 index 0000000..c78677b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/base.py @@ -0,0 +1,329 @@ +""" +Interface for an output. +""" +from abc import ABCMeta, abstractmethod +from typing import Optional, TextIO + +from prompt_toolkit.cursor_shapes import CursorShape +from prompt_toolkit.data_structures import Size +from prompt_toolkit.styles import Attrs + +from .color_depth import ColorDepth + +__all__ = [ + "Output", + "DummyOutput", +] + + +class Output(metaclass=ABCMeta): + """ + Base class defining the output interface for a + :class:`~prompt_toolkit.renderer.Renderer`. + + Actual implementations are + :class:`~prompt_toolkit.output.vt100.Vt100_Output` and + :class:`~prompt_toolkit.output.win32.Win32Output`. + """ + + stdout: Optional[TextIO] = None + + @abstractmethod + def fileno(self) -> int: + "Return the file descriptor to which we can write for the output." + + @abstractmethod + def encoding(self) -> str: + """ + Return the encoding for this output, e.g. 'utf-8'. + (This is used mainly to know which characters are supported by the + output the data, so that the UI can provide alternatives, when + required.) + """ + + @abstractmethod + def write(self, data: str) -> None: + "Write text (Terminal escape sequences will be removed/escaped.)" + + @abstractmethod + def write_raw(self, data: str) -> None: + "Write text." + + @abstractmethod + def set_title(self, title: str) -> None: + "Set terminal title." + + @abstractmethod + def clear_title(self) -> None: + "Clear title again. (or restore previous title.)" + + @abstractmethod + def flush(self) -> None: + "Write to output stream and flush." + + @abstractmethod + def erase_screen(self) -> None: + """ + Erases the screen with the background colour and moves the cursor to + home. + """ + + @abstractmethod + def enter_alternate_screen(self) -> None: + "Go to the alternate screen buffer. (For full screen applications)." + + @abstractmethod + def quit_alternate_screen(self) -> None: + "Leave the alternate screen buffer." + + @abstractmethod + def enable_mouse_support(self) -> None: + "Enable mouse." + + @abstractmethod + def disable_mouse_support(self) -> None: + "Disable mouse." + + @abstractmethod + def erase_end_of_line(self) -> None: + """ + Erases from the current cursor position to the end of the current line. + """ + + @abstractmethod + def erase_down(self) -> None: + """ + Erases the screen from the current line down to the bottom of the + screen. + """ + + @abstractmethod + def reset_attributes(self) -> None: + "Reset color and styling attributes." + + @abstractmethod + def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: + "Set new color and styling attributes." + + @abstractmethod + def disable_autowrap(self) -> None: + "Disable auto line wrapping." + + @abstractmethod + def enable_autowrap(self) -> None: + "Enable auto line wrapping." + + @abstractmethod + def cursor_goto(self, row: int = 0, column: int = 0) -> None: + "Move cursor position." + + @abstractmethod + def cursor_up(self, amount: int) -> None: + "Move cursor `amount` place up." + + @abstractmethod + def cursor_down(self, amount: int) -> None: + "Move cursor `amount` place down." + + @abstractmethod + def cursor_forward(self, amount: int) -> None: + "Move cursor `amount` place forward." + + @abstractmethod + def cursor_backward(self, amount: int) -> None: + "Move cursor `amount` place backward." + + @abstractmethod + def hide_cursor(self) -> None: + "Hide cursor." + + @abstractmethod + def show_cursor(self) -> None: + "Show cursor." + + @abstractmethod + def set_cursor_shape(self, cursor_shape: CursorShape) -> None: + "Set cursor shape to block, beam or underline." + + @abstractmethod + def reset_cursor_shape(self) -> None: + "Reset cursor shape." + + def ask_for_cpr(self) -> None: + """ + Asks for a cursor position report (CPR). + (VT100 only.) + """ + + @property + def responds_to_cpr(self) -> bool: + """ + `True` if the `Application` can expect to receive a CPR response after + calling `ask_for_cpr` (this will come back through the corresponding + `Input`). + + This is used to determine the amount of available rows we have below + the cursor position. In the first place, we have this so that the drop + down autocompletion menus are sized according to the available space. + + On Windows, we don't need this, there we have + `get_rows_below_cursor_position`. + """ + return False + + @abstractmethod + def get_size(self) -> Size: + "Return the size of the output window." + + def bell(self) -> None: + "Sound bell." + + def enable_bracketed_paste(self) -> None: + "For vt100 only." + + def disable_bracketed_paste(self) -> None: + "For vt100 only." + + def reset_cursor_key_mode(self) -> None: + """ + For vt100 only. + Put the terminal in normal cursor mode (instead of application mode). + + See: https://vt100.net/docs/vt100-ug/chapter3.html + """ + + def scroll_buffer_to_prompt(self) -> None: + "For Win32 only." + + def get_rows_below_cursor_position(self) -> int: + "For Windows only." + raise NotImplementedError + + @abstractmethod + def get_default_color_depth(self) -> ColorDepth: + """ + Get default color depth for this output. + + This value will be used if no color depth was explicitely passed to the + `Application`. + + .. note:: + + If the `$PROMPT_TOOLKIT_COLOR_DEPTH` environment variable has been + set, then `outputs.defaults.create_output` will pass this value to + the implementation as the default_color_depth, which is returned + here. (This is not used when the output corresponds to a + prompt_toolkit SSH/Telnet session.) + """ + + +class DummyOutput(Output): + """ + For testing. An output class that doesn't render anything. + """ + + def fileno(self) -> int: + "There is no sensible default for fileno()." + raise NotImplementedError + + def encoding(self) -> str: + return "utf-8" + + def write(self, data: str) -> None: + pass + + def write_raw(self, data: str) -> None: + pass + + def set_title(self, title: str) -> None: + pass + + def clear_title(self) -> None: + pass + + def flush(self) -> None: + pass + + def erase_screen(self) -> None: + pass + + def enter_alternate_screen(self) -> None: + pass + + def quit_alternate_screen(self) -> None: + pass + + def enable_mouse_support(self) -> None: + pass + + def disable_mouse_support(self) -> None: + pass + + def erase_end_of_line(self) -> None: + pass + + def erase_down(self) -> None: + pass + + def reset_attributes(self) -> None: + pass + + def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: + pass + + def disable_autowrap(self) -> None: + pass + + def enable_autowrap(self) -> None: + pass + + def cursor_goto(self, row: int = 0, column: int = 0) -> None: + pass + + def cursor_up(self, amount: int) -> None: + pass + + def cursor_down(self, amount: int) -> None: + pass + + def cursor_forward(self, amount: int) -> None: + pass + + def cursor_backward(self, amount: int) -> None: + pass + + def hide_cursor(self) -> None: + pass + + def show_cursor(self) -> None: + pass + + def set_cursor_shape(self, cursor_shape: CursorShape) -> None: + pass + + def reset_cursor_shape(self) -> None: + pass + + def ask_for_cpr(self) -> None: + pass + + def bell(self) -> None: + pass + + def enable_bracketed_paste(self) -> None: + pass + + def disable_bracketed_paste(self) -> None: + pass + + def scroll_buffer_to_prompt(self) -> None: + pass + + def get_size(self) -> Size: + return Size(rows=40, columns=80) + + def get_rows_below_cursor_position(self) -> int: + return 40 + + def get_default_color_depth(self) -> ColorDepth: + return ColorDepth.DEPTH_1_BIT diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/color_depth.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/color_depth.py new file mode 100644 index 0000000..a6166ba --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/color_depth.py @@ -0,0 +1,58 @@ +import os +from enum import Enum +from typing import Optional + +__all__ = [ + "ColorDepth", +] + + +class ColorDepth(str, Enum): + """ + Possible color depth values for the output. + """ + + value: str + + #: One color only. + DEPTH_1_BIT = "DEPTH_1_BIT" + + #: ANSI Colors. + DEPTH_4_BIT = "DEPTH_4_BIT" + + #: The default. + DEPTH_8_BIT = "DEPTH_8_BIT" + + #: 24 bit True color. + DEPTH_24_BIT = "DEPTH_24_BIT" + + # Aliases. + MONOCHROME = DEPTH_1_BIT + ANSI_COLORS_ONLY = DEPTH_4_BIT + DEFAULT = DEPTH_8_BIT + TRUE_COLOR = DEPTH_24_BIT + + @classmethod + def from_env(cls) -> Optional["ColorDepth"]: + """ + Return the color depth if the $PROMPT_TOOLKIT_COLOR_DEPTH environment + variable has been set. + + This is a way to enforce a certain color depth in all prompt_toolkit + applications. + """ + # Check the `PROMPT_TOOLKIT_COLOR_DEPTH` environment variable. + all_values = [i.value for i in ColorDepth] + if os.environ.get("PROMPT_TOOLKIT_COLOR_DEPTH") in all_values: + return cls(os.environ["PROMPT_TOOLKIT_COLOR_DEPTH"]) + + return None + + @classmethod + def default(cls) -> "ColorDepth": + """ + Return the default color depth for the default output. + """ + from .defaults import create_output + + return create_output().get_default_color_depth() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/conemu.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/conemu.py new file mode 100644 index 0000000..ee1ac41 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/conemu.py @@ -0,0 +1,59 @@ +from typing import Any, Optional, TextIO + +from prompt_toolkit.data_structures import Size + +from .base import Output +from .color_depth import ColorDepth +from .vt100 import Vt100_Output +from .win32 import Win32Output + +__all__ = [ + "ConEmuOutput", +] + + +class ConEmuOutput: + """ + ConEmu (Windows) output abstraction. + + ConEmu is a Windows console application, but it also supports ANSI escape + sequences. This output class is actually a proxy to both `Win32Output` and + `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but + all cursor movements and scrolling happens through the `Vt100_Output`. + + This way, we can have 256 colors in ConEmu and Cmder. Rendering will be + even a little faster as well. + + http://conemu.github.io/ + http://gooseberrycreative.com/cmder/ + """ + + def __init__( + self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None + ) -> None: + self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth) + self.vt100_output = Vt100_Output( + stdout, lambda: Size(0, 0), default_color_depth=default_color_depth + ) + + @property + def responds_to_cpr(self) -> bool: + return False # We don't need this on Windows. + + def __getattr__(self, name: str) -> Any: + if name in ( + "get_size", + "get_rows_below_cursor_position", + "enable_mouse_support", + "disable_mouse_support", + "scroll_buffer_to_prompt", + "get_win32_screen_buffer_info", + "enable_bracketed_paste", + "disable_bracketed_paste", + ): + return getattr(self.win32_output, name) + else: + return getattr(self.vt100_output, name) + + +Output.register(ConEmuOutput) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/defaults.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/defaults.py new file mode 100644 index 0000000..bd4bf95 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/defaults.py @@ -0,0 +1,101 @@ +import sys +from typing import Optional, TextIO, cast + +from prompt_toolkit.utils import ( + get_bell_environment_variable, + get_term_environment_variable, + is_conemu_ansi, + is_windows, +) + +from .base import DummyOutput, Output +from .color_depth import ColorDepth +from .plain_text import PlainTextOutput + +__all__ = [ + "create_output", +] + + +def create_output( + stdout: Optional[TextIO] = None, always_prefer_tty: bool = False +) -> Output: + """ + Return an :class:`~prompt_toolkit.output.Output` instance for the command + line. + + :param stdout: The stdout object + :param always_prefer_tty: When set, look for `sys.stderr` if `sys.stdout` + is not a TTY. Useful if `sys.stdout` is redirected to a file, but we + still want user input and output on the terminal. + + By default, this is `False`. If `sys.stdout` is not a terminal (maybe + it's redirected to a file), then a `PlainTextOutput` will be returned. + That way, tools like `print_formatted_text` will write plain text into + that file. + """ + # Consider TERM, PROMPT_TOOLKIT_BELL, and PROMPT_TOOLKIT_COLOR_DEPTH + # environment variables. Notice that PROMPT_TOOLKIT_COLOR_DEPTH value is + # the default that's used if the Application doesn't override it. + term_from_env = get_term_environment_variable() + bell_from_env = get_bell_environment_variable() + color_depth_from_env = ColorDepth.from_env() + + if stdout is None: + # By default, render to stdout. If the output is piped somewhere else, + # render to stderr. + stdout = sys.stdout + + if always_prefer_tty: + for io in [sys.stdout, sys.stderr]: + if io is not None and io.isatty(): + # (This is `None` when using `pythonw.exe` on Windows.) + stdout = io + break + + # If the output is still `None`, use a DummyOutput. + # This happens for instance on Windows, when running the application under + # `pythonw.exe`. In that case, there won't be a terminal Window, and + # stdin/stdout/stderr are `None`. + if stdout is None: + return DummyOutput() + + # If the patch_stdout context manager has been used, then sys.stdout is + # replaced by this proxy. For prompt_toolkit applications, we want to use + # the real stdout. + from prompt_toolkit.patch_stdout import StdoutProxy + + while isinstance(stdout, StdoutProxy): + stdout = stdout.original_stdout + + if is_windows(): + from .conemu import ConEmuOutput + from .win32 import Win32Output + from .windows10 import Windows10_Output, is_win_vt100_enabled + + if is_win_vt100_enabled(): + return cast( + Output, + Windows10_Output(stdout, default_color_depth=color_depth_from_env), + ) + if is_conemu_ansi(): + return cast( + Output, ConEmuOutput(stdout, default_color_depth=color_depth_from_env) + ) + else: + return Win32Output(stdout, default_color_depth=color_depth_from_env) + else: + from .vt100 import Vt100_Output + + # Stdout is not a TTY? Render as plain text. + # This is mostly useful if stdout is redirected to a file, and + # `print_formatted_text` is used. + if not stdout.isatty(): + return PlainTextOutput(stdout) + + return Vt100_Output.from_pty( + stdout, + term=term_from_env, + default_color_depth=color_depth_from_env, + enable_bell=bell_from_env, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/flush_stdout.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/flush_stdout.py new file mode 100644 index 0000000..05a3423 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/flush_stdout.py @@ -0,0 +1,84 @@ +import errno +import os +import sys +from contextlib import contextmanager +from typing import IO, Iterator, TextIO, cast + +__all__ = ["flush_stdout"] + + +def flush_stdout(stdout: TextIO, data: str, write_binary: bool) -> None: + try: + # Ensure that `stdout` is made blocking when writing into it. + # Otherwise, when uvloop is activated (which makes stdout + # non-blocking), and we write big amounts of text, then we get a + # `BlockingIOError` here. + with _blocking_io(stdout): + # (We try to encode ourself, because that way we can replace + # characters that don't exist in the character set, avoiding + # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.) + # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968' + # for sys.stdout.encoding in xterm. + out: IO[bytes] + if write_binary: + if hasattr(stdout, "buffer"): + out = stdout.buffer + else: + # IO[bytes] was given to begin with. + # (Used in the unit tests, for instance.) + out = cast(IO[bytes], stdout) + out.write(data.encode(stdout.encoding or "utf-8", "replace")) + else: + stdout.write(data) + + stdout.flush() + except OSError as e: + if e.args and e.args[0] == errno.EINTR: + # Interrupted system call. Can happen in case of a window + # resize signal. (Just ignore. The resize handler will render + # again anyway.) + pass + elif e.args and e.args[0] == 0: + # This can happen when there is a lot of output and the user + # sends a KeyboardInterrupt by pressing Control-C. E.g. in + # a Python REPL when we execute "while True: print('test')". + # (The `ptpython` REPL uses this `Output` class instead of + # `stdout` directly -- in order to be network transparent.) + # So, just ignore. + pass + else: + raise + + +@contextmanager +def _blocking_io(io: IO[str]) -> Iterator[None]: + """ + Ensure that the FD for `io` is set to blocking in here. + """ + if sys.platform == "win32": + # On Windows, the `os` module doesn't have a `get/set_blocking` + # function. + yield + return + + try: + fd = io.fileno() + blocking = os.get_blocking(fd) + except: # noqa + # Failed somewhere. + # `get_blocking` can raise `OSError`. + # The io object can raise `AttributeError` when no `fileno()` method is + # present if we're not a real file object. + blocking = True # Assume we're good, and don't do anything. + + try: + # Make blocking if we weren't blocking yet. + if not blocking: + os.set_blocking(fd, True) + + yield + + finally: + # Restore original blocking mode. + if not blocking: + os.set_blocking(fd, blocking) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/plain_text.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/plain_text.py new file mode 100644 index 0000000..23c1e94 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/plain_text.py @@ -0,0 +1,145 @@ +from typing import List, TextIO + +from prompt_toolkit.cursor_shapes import CursorShape +from prompt_toolkit.data_structures import Size +from prompt_toolkit.styles import Attrs + +from .base import Output +from .color_depth import ColorDepth +from .flush_stdout import flush_stdout + +__all__ = ["PlainTextOutput"] + + +class PlainTextOutput(Output): + """ + Output that won't include any ANSI escape sequences. + + Useful when stdout is not a terminal. Maybe stdout is redirected to a file. + In this case, if `print_formatted_text` is used, for instance, we don't + want to include formatting. + + (The code is mostly identical to `Vt100_Output`, but without the + formatting.) + """ + + def __init__(self, stdout: TextIO, write_binary: bool = True) -> None: + assert all(hasattr(stdout, a) for a in ("write", "flush")) + + if write_binary: + assert hasattr(stdout, "encoding") + + self.stdout: TextIO = stdout + self.write_binary = write_binary + self._buffer: List[str] = [] + + def fileno(self) -> int: + "There is no sensible default for fileno()." + return self.stdout.fileno() + + def encoding(self) -> str: + return "utf-8" + + def write(self, data: str) -> None: + self._buffer.append(data) + + def write_raw(self, data: str) -> None: + self._buffer.append(data) + + def set_title(self, title: str) -> None: + pass + + def clear_title(self) -> None: + pass + + def flush(self) -> None: + if not self._buffer: + return + + data = "".join(self._buffer) + self._buffer = [] + flush_stdout(self.stdout, data, write_binary=self.write_binary) + + def erase_screen(self) -> None: + pass + + def enter_alternate_screen(self) -> None: + pass + + def quit_alternate_screen(self) -> None: + pass + + def enable_mouse_support(self) -> None: + pass + + def disable_mouse_support(self) -> None: + pass + + def erase_end_of_line(self) -> None: + pass + + def erase_down(self) -> None: + pass + + def reset_attributes(self) -> None: + pass + + def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: + pass + + def disable_autowrap(self) -> None: + pass + + def enable_autowrap(self) -> None: + pass + + def cursor_goto(self, row: int = 0, column: int = 0) -> None: + pass + + def cursor_up(self, amount: int) -> None: + pass + + def cursor_down(self, amount: int) -> None: + self._buffer.append("\n") + + def cursor_forward(self, amount: int) -> None: + self._buffer.append(" " * amount) + + def cursor_backward(self, amount: int) -> None: + pass + + def hide_cursor(self) -> None: + pass + + def show_cursor(self) -> None: + pass + + def set_cursor_shape(self, cursor_shape: CursorShape) -> None: + pass + + def reset_cursor_shape(self) -> None: + pass + + def ask_for_cpr(self) -> None: + pass + + def bell(self) -> None: + pass + + def enable_bracketed_paste(self) -> None: + pass + + def disable_bracketed_paste(self) -> None: + pass + + def scroll_buffer_to_prompt(self) -> None: + pass + + def get_size(self) -> Size: + return Size(rows=40, columns=80) + + def get_rows_below_cursor_position(self) -> int: + return 8 + + def get_default_color_depth(self) -> ColorDepth: + return ColorDepth.DEPTH_1_BIT diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/vt100.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/vt100.py new file mode 100644 index 0000000..0586267 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/vt100.py @@ -0,0 +1,753 @@ +""" +Output for vt100 terminals. + +A lot of thanks, regarding outputting of colors, goes to the Pygments project: +(We don't rely on Pygments anymore, because many things are very custom, and +everything has been highly optimized.) +http://pygments.org/ +""" +import io +import os +import sys +from typing import ( + Callable, + Dict, + Hashable, + Iterable, + List, + Optional, + Sequence, + Set, + TextIO, + Tuple, +) + +from prompt_toolkit.cursor_shapes import CursorShape +from prompt_toolkit.data_structures import Size +from prompt_toolkit.output import Output +from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs +from prompt_toolkit.utils import is_dumb_terminal + +from .color_depth import ColorDepth +from .flush_stdout import flush_stdout + +__all__ = [ + "Vt100_Output", +] + + +FG_ANSI_COLORS = { + "ansidefault": 39, + # Low intensity. + "ansiblack": 30, + "ansired": 31, + "ansigreen": 32, + "ansiyellow": 33, + "ansiblue": 34, + "ansimagenta": 35, + "ansicyan": 36, + "ansigray": 37, + # High intensity. + "ansibrightblack": 90, + "ansibrightred": 91, + "ansibrightgreen": 92, + "ansibrightyellow": 93, + "ansibrightblue": 94, + "ansibrightmagenta": 95, + "ansibrightcyan": 96, + "ansiwhite": 97, +} + +BG_ANSI_COLORS = { + "ansidefault": 49, + # Low intensity. + "ansiblack": 40, + "ansired": 41, + "ansigreen": 42, + "ansiyellow": 43, + "ansiblue": 44, + "ansimagenta": 45, + "ansicyan": 46, + "ansigray": 47, + # High intensity. + "ansibrightblack": 100, + "ansibrightred": 101, + "ansibrightgreen": 102, + "ansibrightyellow": 103, + "ansibrightblue": 104, + "ansibrightmagenta": 105, + "ansibrightcyan": 106, + "ansiwhite": 107, +} + + +ANSI_COLORS_TO_RGB = { + "ansidefault": ( + 0x00, + 0x00, + 0x00, + ), # Don't use, 'default' doesn't really have a value. + "ansiblack": (0x00, 0x00, 0x00), + "ansigray": (0xE5, 0xE5, 0xE5), + "ansibrightblack": (0x7F, 0x7F, 0x7F), + "ansiwhite": (0xFF, 0xFF, 0xFF), + # Low intensity. + "ansired": (0xCD, 0x00, 0x00), + "ansigreen": (0x00, 0xCD, 0x00), + "ansiyellow": (0xCD, 0xCD, 0x00), + "ansiblue": (0x00, 0x00, 0xCD), + "ansimagenta": (0xCD, 0x00, 0xCD), + "ansicyan": (0x00, 0xCD, 0xCD), + # High intensity. + "ansibrightred": (0xFF, 0x00, 0x00), + "ansibrightgreen": (0x00, 0xFF, 0x00), + "ansibrightyellow": (0xFF, 0xFF, 0x00), + "ansibrightblue": (0x00, 0x00, 0xFF), + "ansibrightmagenta": (0xFF, 0x00, 0xFF), + "ansibrightcyan": (0x00, 0xFF, 0xFF), +} + + +assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) +assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) +assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) + + +def _get_closest_ansi_color(r: int, g: int, b: int, exclude: Sequence[str] = ()) -> str: + """ + Find closest ANSI color. Return it by name. + + :param r: Red (Between 0 and 255.) + :param g: Green (Between 0 and 255.) + :param b: Blue (Between 0 and 255.) + :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.) + """ + exclude = list(exclude) + + # When we have a bit of saturation, avoid the gray-like colors, otherwise, + # too often the distance to the gray color is less. + saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510 + + if saturation > 30: + exclude.extend(["ansilightgray", "ansidarkgray", "ansiwhite", "ansiblack"]) + + # Take the closest color. + # (Thanks to Pygments for this part.) + distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) + match = "ansidefault" + + for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items(): + if name != "ansidefault" and name not in exclude: + d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 + + if d < distance: + match = name + distance = d + + return match + + +_ColorCodeAndName = Tuple[int, str] + + +class _16ColorCache: + """ + Cache which maps (r, g, b) tuples to 16 ansi colors. + + :param bg: Cache for background colors, instead of foreground. + """ + + def __init__(self, bg: bool = False) -> None: + self.bg = bg + self._cache: Dict[Hashable, _ColorCodeAndName] = {} + + def get_code( + self, value: Tuple[int, int, int], exclude: Sequence[str] = () + ) -> _ColorCodeAndName: + """ + Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for + a given (r,g,b) value. + """ + key: Hashable = (value, tuple(exclude)) + cache = self._cache + + if key not in cache: + cache[key] = self._get(value, exclude) + + return cache[key] + + def _get( + self, value: Tuple[int, int, int], exclude: Sequence[str] = () + ) -> _ColorCodeAndName: + + r, g, b = value + match = _get_closest_ansi_color(r, g, b, exclude=exclude) + + # Turn color name into code. + if self.bg: + code = BG_ANSI_COLORS[match] + else: + code = FG_ANSI_COLORS[match] + + return code, match + + +class _256ColorCache(Dict[Tuple[int, int, int], int]): + """ + Cache which maps (r, g, b) tuples to 256 colors. + """ + + def __init__(self) -> None: + # Build color table. + colors: List[Tuple[int, int, int]] = [] + + # colors 0..15: 16 basic colors + colors.append((0x00, 0x00, 0x00)) # 0 + colors.append((0xCD, 0x00, 0x00)) # 1 + colors.append((0x00, 0xCD, 0x00)) # 2 + colors.append((0xCD, 0xCD, 0x00)) # 3 + colors.append((0x00, 0x00, 0xEE)) # 4 + colors.append((0xCD, 0x00, 0xCD)) # 5 + colors.append((0x00, 0xCD, 0xCD)) # 6 + colors.append((0xE5, 0xE5, 0xE5)) # 7 + colors.append((0x7F, 0x7F, 0x7F)) # 8 + colors.append((0xFF, 0x00, 0x00)) # 9 + colors.append((0x00, 0xFF, 0x00)) # 10 + colors.append((0xFF, 0xFF, 0x00)) # 11 + colors.append((0x5C, 0x5C, 0xFF)) # 12 + colors.append((0xFF, 0x00, 0xFF)) # 13 + colors.append((0x00, 0xFF, 0xFF)) # 14 + colors.append((0xFF, 0xFF, 0xFF)) # 15 + + # colors 16..232: the 6x6x6 color cube + valuerange = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF) + + for i in range(217): + r = valuerange[(i // 36) % 6] + g = valuerange[(i // 6) % 6] + b = valuerange[i % 6] + colors.append((r, g, b)) + + # colors 233..253: grayscale + for i in range(1, 22): + v = 8 + i * 10 + colors.append((v, v, v)) + + self.colors = colors + + def __missing__(self, value: Tuple[int, int, int]) -> int: + r, g, b = value + + # Find closest color. + # (Thanks to Pygments for this!) + distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) + match = 0 + + for i, (r2, g2, b2) in enumerate(self.colors): + if i >= 16: # XXX: We ignore the 16 ANSI colors when mapping RGB + # to the 256 colors, because these highly depend on + # the color scheme of the terminal. + d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 + + if d < distance: + match = i + distance = d + + # Turn color name into code. + self[value] = match + return match + + +_16_fg_colors = _16ColorCache(bg=False) +_16_bg_colors = _16ColorCache(bg=True) +_256_colors = _256ColorCache() + + +class _EscapeCodeCache(Dict[Attrs, str]): + """ + Cache for VT100 escape codes. It maps + (fgcolor, bgcolor, bold, underline, strike, reverse) tuples to VT100 + escape sequences. + + :param true_color: When True, use 24bit colors instead of 256 colors. + """ + + def __init__(self, color_depth: ColorDepth) -> None: + self.color_depth = color_depth + + def __missing__(self, attrs: Attrs) -> str: + ( + fgcolor, + bgcolor, + bold, + underline, + strike, + italic, + blink, + reverse, + hidden, + ) = attrs + parts: List[str] = [] + + parts.extend(self._colors_to_code(fgcolor or "", bgcolor or "")) + + if bold: + parts.append("1") + if italic: + parts.append("3") + if blink: + parts.append("5") + if underline: + parts.append("4") + if reverse: + parts.append("7") + if hidden: + parts.append("8") + if strike: + parts.append("9") + + if parts: + result = "\x1b[0;" + ";".join(parts) + "m" + else: + result = "\x1b[0m" + + self[attrs] = result + return result + + def _color_name_to_rgb(self, color: str) -> Tuple[int, int, int]: + "Turn 'ffffff', into (0xff, 0xff, 0xff)." + try: + rgb = int(color, 16) + except ValueError: + raise + else: + r = (rgb >> 16) & 0xFF + g = (rgb >> 8) & 0xFF + b = rgb & 0xFF + return r, g, b + + def _colors_to_code(self, fg_color: str, bg_color: str) -> Iterable[str]: + """ + Return a tuple with the vt100 values that represent this color. + """ + # When requesting ANSI colors only, and both fg/bg color were converted + # to ANSI, ensure that the foreground and background color are not the + # same. (Unless they were explicitly defined to be the same color.) + fg_ansi = "" + + def get(color: str, bg: bool) -> List[int]: + nonlocal fg_ansi + + table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS + + if not color or self.color_depth == ColorDepth.DEPTH_1_BIT: + return [] + + # 16 ANSI colors. (Given by name.) + elif color in table: + return [table[color]] + + # RGB colors. (Defined as 'ffffff'.) + else: + try: + rgb = self._color_name_to_rgb(color) + except ValueError: + return [] + + # When only 16 colors are supported, use that. + if self.color_depth == ColorDepth.DEPTH_4_BIT: + if bg: # Background. + if fg_color != bg_color: + exclude = [fg_ansi] + else: + exclude = [] + code, name = _16_bg_colors.get_code(rgb, exclude=exclude) + return [code] + else: # Foreground. + code, name = _16_fg_colors.get_code(rgb) + fg_ansi = name + return [code] + + # True colors. (Only when this feature is enabled.) + elif self.color_depth == ColorDepth.DEPTH_24_BIT: + r, g, b = rgb + return [(48 if bg else 38), 2, r, g, b] + + # 256 RGB colors. + else: + return [(48 if bg else 38), 5, _256_colors[rgb]] + + result: List[int] = [] + result.extend(get(fg_color, False)) + result.extend(get(bg_color, True)) + + return map(str, result) + + +def _get_size(fileno: int) -> Tuple[int, int]: + """ + Get the size of this pseudo terminal. + + :param fileno: stdout.fileno() + :returns: A (rows, cols) tuple. + """ + size = os.get_terminal_size(fileno) + return size.lines, size.columns + + +class Vt100_Output(Output): + """ + :param get_size: A callable which returns the `Size` of the output terminal. + :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property. + :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...) + :param write_binary: Encode the output before writing it. If `True` (the + default), the `stdout` object is supposed to expose an `encoding` attribute. + """ + + # For the error messages. Only display "Output is not a terminal" once per + # file descriptor. + _fds_not_a_terminal: Set[int] = set() + + def __init__( + self, + stdout: TextIO, + get_size: Callable[[], Size], + term: Optional[str] = None, + write_binary: bool = True, + default_color_depth: Optional[ColorDepth] = None, + enable_bell: bool = True, + ) -> None: + + assert all(hasattr(stdout, a) for a in ("write", "flush")) + + if write_binary: + assert hasattr(stdout, "encoding") + + self._buffer: List[str] = [] + self.stdout: TextIO = stdout + self.write_binary = write_binary + self.default_color_depth = default_color_depth + self._get_size = get_size + self.term = term + self.enable_bell = enable_bell + + # Cache for escape codes. + self._escape_code_caches: Dict[ColorDepth, _EscapeCodeCache] = { + ColorDepth.DEPTH_1_BIT: _EscapeCodeCache(ColorDepth.DEPTH_1_BIT), + ColorDepth.DEPTH_4_BIT: _EscapeCodeCache(ColorDepth.DEPTH_4_BIT), + ColorDepth.DEPTH_8_BIT: _EscapeCodeCache(ColorDepth.DEPTH_8_BIT), + ColorDepth.DEPTH_24_BIT: _EscapeCodeCache(ColorDepth.DEPTH_24_BIT), + } + + # Keep track of whether the cursor shape was ever changed. + # (We don't restore the cursor shape if it was never changed - by + # default, we don't change them.) + self._cursor_shape_changed = False + + @classmethod + def from_pty( + cls, + stdout: TextIO, + term: Optional[str] = None, + default_color_depth: Optional[ColorDepth] = None, + enable_bell: bool = True, + ) -> "Vt100_Output": + """ + Create an Output class from a pseudo terminal. + (This will take the dimensions by reading the pseudo + terminal attributes.) + """ + fd: Optional[int] + # Normally, this requires a real TTY device, but people instantiate + # this class often during unit tests as well. For convenience, we print + # an error message, use standard dimensions, and go on. + try: + fd = stdout.fileno() + except io.UnsupportedOperation: + fd = None + + if not stdout.isatty() and (fd is None or fd not in cls._fds_not_a_terminal): + msg = "Warning: Output is not a terminal (fd=%r).\n" + sys.stderr.write(msg % fd) + sys.stderr.flush() + if fd is not None: + cls._fds_not_a_terminal.add(fd) + + def get_size() -> Size: + # If terminal (incorrectly) reports its size as 0, pick a + # reasonable default. See + # https://github.com/ipython/ipython/issues/10071 + rows, columns = (None, None) + + # It is possible that `stdout` is no longer a TTY device at this + # point. In that case we get an `OSError` in the ioctl call in + # `get_size`. See: + # https://github.com/prompt-toolkit/python-prompt-toolkit/pull/1021 + try: + rows, columns = _get_size(stdout.fileno()) + except OSError: + pass + return Size(rows=rows or 24, columns=columns or 80) + + return cls( + stdout, + get_size, + term=term, + default_color_depth=default_color_depth, + enable_bell=enable_bell, + ) + + def get_size(self) -> Size: + return self._get_size() + + def fileno(self) -> int: + "Return file descriptor." + return self.stdout.fileno() + + def encoding(self) -> str: + "Return encoding used for stdout." + return self.stdout.encoding + + def write_raw(self, data: str) -> None: + """ + Write raw data to output. + """ + self._buffer.append(data) + + def write(self, data: str) -> None: + """ + Write text to output. + (Removes vt100 escape codes. -- used for safely writing text.) + """ + self._buffer.append(data.replace("\x1b", "?")) + + def set_title(self, title: str) -> None: + """ + Set terminal title. + """ + if self.term not in ( + "linux", + "eterm-color", + ): # Not supported by the Linux console. + self.write_raw( + "\x1b]2;%s\x07" % title.replace("\x1b", "").replace("\x07", "") + ) + + def clear_title(self) -> None: + self.set_title("") + + def erase_screen(self) -> None: + """ + Erases the screen with the background colour and moves the cursor to + home. + """ + self.write_raw("\x1b[2J") + + def enter_alternate_screen(self) -> None: + self.write_raw("\x1b[?1049h\x1b[H") + + def quit_alternate_screen(self) -> None: + self.write_raw("\x1b[?1049l") + + def enable_mouse_support(self) -> None: + self.write_raw("\x1b[?1000h") + + # Enable mouse-drag support. + self.write_raw("\x1b[?1003h") + + # Enable urxvt Mouse mode. (For terminals that understand this.) + self.write_raw("\x1b[?1015h") + + # Also enable Xterm SGR mouse mode. (For terminals that understand this.) + self.write_raw("\x1b[?1006h") + + # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr + # extensions. + + def disable_mouse_support(self) -> None: + self.write_raw("\x1b[?1000l") + self.write_raw("\x1b[?1015l") + self.write_raw("\x1b[?1006l") + self.write_raw("\x1b[?1003l") + + def erase_end_of_line(self) -> None: + """ + Erases from the current cursor position to the end of the current line. + """ + self.write_raw("\x1b[K") + + def erase_down(self) -> None: + """ + Erases the screen from the current line down to the bottom of the + screen. + """ + self.write_raw("\x1b[J") + + def reset_attributes(self) -> None: + self.write_raw("\x1b[0m") + + def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: + """ + Create new style and output. + + :param attrs: `Attrs` instance. + """ + # Get current depth. + escape_code_cache = self._escape_code_caches[color_depth] + + # Write escape character. + self.write_raw(escape_code_cache[attrs]) + + def disable_autowrap(self) -> None: + self.write_raw("\x1b[?7l") + + def enable_autowrap(self) -> None: + self.write_raw("\x1b[?7h") + + def enable_bracketed_paste(self) -> None: + self.write_raw("\x1b[?2004h") + + def disable_bracketed_paste(self) -> None: + self.write_raw("\x1b[?2004l") + + def reset_cursor_key_mode(self) -> None: + """ + For vt100 only. + Put the terminal in cursor mode (instead of application mode). + """ + # Put the terminal in cursor mode. (Instead of application mode.) + self.write_raw("\x1b[?1l") + + def cursor_goto(self, row: int = 0, column: int = 0) -> None: + """ + Move cursor position. + """ + self.write_raw("\x1b[%i;%iH" % (row, column)) + + def cursor_up(self, amount: int) -> None: + if amount == 0: + pass + elif amount == 1: + self.write_raw("\x1b[A") + else: + self.write_raw("\x1b[%iA" % amount) + + def cursor_down(self, amount: int) -> None: + if amount == 0: + pass + elif amount == 1: + # Note: Not the same as '\n', '\n' can cause the window content to + # scroll. + self.write_raw("\x1b[B") + else: + self.write_raw("\x1b[%iB" % amount) + + def cursor_forward(self, amount: int) -> None: + if amount == 0: + pass + elif amount == 1: + self.write_raw("\x1b[C") + else: + self.write_raw("\x1b[%iC" % amount) + + def cursor_backward(self, amount: int) -> None: + if amount == 0: + pass + elif amount == 1: + self.write_raw("\b") # '\x1b[D' + else: + self.write_raw("\x1b[%iD" % amount) + + def hide_cursor(self) -> None: + self.write_raw("\x1b[?25l") + + def show_cursor(self) -> None: + self.write_raw("\x1b[?12l\x1b[?25h") # Stop blinking cursor and show. + + def set_cursor_shape(self, cursor_shape: CursorShape) -> None: + if cursor_shape == CursorShape._NEVER_CHANGE: + return + + self._cursor_shape_changed = True + self.write_raw( + { + CursorShape.BLOCK: "\x1b[2 q", + CursorShape.BEAM: "\x1b[6 q", + CursorShape.UNDERLINE: "\x1b[4 q", + CursorShape.BLINKING_BLOCK: "\x1b[1 q", + CursorShape.BLINKING_BEAM: "\x1b[5 q", + CursorShape.BLINKING_UNDERLINE: "\x1b[3 q", + }.get(cursor_shape, "") + ) + + def reset_cursor_shape(self) -> None: + "Reset cursor shape." + # (Only reset cursor shape, if we ever changed it.) + if self._cursor_shape_changed: + self._cursor_shape_changed = False + + # Reset cursor shape. + self.write_raw("\x1b[0 q") + + def flush(self) -> None: + """ + Write to output stream and flush. + """ + if not self._buffer: + return + + data = "".join(self._buffer) + self._buffer = [] + + flush_stdout(self.stdout, data, write_binary=self.write_binary) + + def ask_for_cpr(self) -> None: + """ + Asks for a cursor position report (CPR). + """ + self.write_raw("\x1b[6n") + self.flush() + + @property + def responds_to_cpr(self) -> bool: + # When the input is a tty, we assume that CPR is supported. + # It's not when the input is piped from Pexpect. + if os.environ.get("PROMPT_TOOLKIT_NO_CPR", "") == "1": + return False + + if is_dumb_terminal(self.term): + return False + try: + return self.stdout.isatty() + except ValueError: + return False # ValueError: I/O operation on closed file + + def bell(self) -> None: + "Sound bell." + if self.enable_bell: + self.write_raw("\a") + self.flush() + + def get_default_color_depth(self) -> ColorDepth: + """ + Return the default color depth for a vt100 terminal, according to the + our term value. + + We prefer 256 colors almost always, because this is what most terminals + support these days, and is a good default. + """ + if self.default_color_depth is not None: + return self.default_color_depth + + term = self.term + + if term is None: + return ColorDepth.DEFAULT + + if is_dumb_terminal(term): + return ColorDepth.DEPTH_1_BIT + + if term in ("linux", "eterm-color"): + return ColorDepth.DEPTH_4_BIT + + return ColorDepth.DEFAULT diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/win32.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/win32.py new file mode 100644 index 0000000..a73978c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/win32.py @@ -0,0 +1,683 @@ +import os +from ctypes import ArgumentError, byref, c_char, c_long, c_uint, c_ulong, pointer + +from ..utils import SPHINX_AUTODOC_RUNNING + +# Do not import win32-specific stuff when generating documentation. +# Otherwise RTD would be unable to generate docs for this module. +if not SPHINX_AUTODOC_RUNNING: + from ctypes import windll + +from ctypes.wintypes import DWORD, HANDLE +from typing import Callable, Dict, List, Optional, TextIO, Tuple, Type, TypeVar, Union + +from prompt_toolkit.cursor_shapes import CursorShape +from prompt_toolkit.data_structures import Size +from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs +from prompt_toolkit.utils import get_cwidth +from prompt_toolkit.win32_types import ( + CONSOLE_SCREEN_BUFFER_INFO, + COORD, + SMALL_RECT, + STD_INPUT_HANDLE, + STD_OUTPUT_HANDLE, +) + +from .base import Output +from .color_depth import ColorDepth + +__all__ = [ + "Win32Output", +] + + +def _coord_byval(coord: COORD) -> c_long: + """ + Turns a COORD object into a c_long. + This will cause it to be passed by value instead of by reference. (That is what I think at least.) + + When running ``ptipython`` is run (only with IPython), we often got the following error:: + + Error in 'SetConsoleCursorPosition'. + ArgumentError("argument 2: : wrong type",) + argument 2: : wrong type + + It was solved by turning ``COORD`` parameters into a ``c_long`` like this. + + More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx + """ + return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF) + + +#: If True: write the output of the renderer also to the following file. This +#: is very useful for debugging. (e.g.: to see that we don't write more bytes +#: than required.) +_DEBUG_RENDER_OUTPUT = False +_DEBUG_RENDER_OUTPUT_FILENAME = r"prompt-toolkit-windows-output.log" + + +class NoConsoleScreenBufferError(Exception): + """ + Raised when the application is not running inside a Windows Console, but + the user tries to instantiate Win32Output. + """ + + def __init__(self) -> None: + # Are we running in 'xterm' on Windows, like git-bash for instance? + xterm = "xterm" in os.environ.get("TERM", "") + + if xterm: + message = ( + "Found %s, while expecting a Windows console. " + 'Maybe try to run this program using "winpty" ' + "or run it in cmd.exe instead. Or otherwise, " + "in case of Cygwin, use the Python executable " + "that is compiled for Cygwin." % os.environ["TERM"] + ) + else: + message = "No Windows console found. Are you running cmd.exe?" + super().__init__(message) + + +_T = TypeVar("_T") + + +class Win32Output(Output): + """ + I/O abstraction for rendering to Windows consoles. + (cmd.exe and similar.) + """ + + def __init__( + self, + stdout: TextIO, + use_complete_width: bool = False, + default_color_depth: Optional[ColorDepth] = None, + ) -> None: + self.use_complete_width = use_complete_width + self.default_color_depth = default_color_depth + + self._buffer: List[str] = [] + self.stdout: TextIO = stdout + self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) + + self._in_alternate_screen = False + self._hidden = False + + self.color_lookup_table = ColorLookupTable() + + # Remember the default console colors. + info = self.get_win32_screen_buffer_info() + self.default_attrs = info.wAttributes if info else 15 + + if _DEBUG_RENDER_OUTPUT: + self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, "ab") + + def fileno(self) -> int: + "Return file descriptor." + return self.stdout.fileno() + + def encoding(self) -> str: + "Return encoding used for stdout." + return self.stdout.encoding + + def write(self, data: str) -> None: + if self._hidden: + data = " " * get_cwidth(data) + + self._buffer.append(data) + + def write_raw(self, data: str) -> None: + "For win32, there is no difference between write and write_raw." + self.write(data) + + def get_size(self) -> Size: + info = self.get_win32_screen_buffer_info() + + # We take the width of the *visible* region as the size. Not the width + # of the complete screen buffer. (Unless use_complete_width has been + # set.) + if self.use_complete_width: + width = info.dwSize.X + else: + width = info.srWindow.Right - info.srWindow.Left + + height = info.srWindow.Bottom - info.srWindow.Top + 1 + + # We avoid the right margin, windows will wrap otherwise. + maxwidth = info.dwSize.X - 1 + width = min(maxwidth, width) + + # Create `Size` object. + return Size(rows=height, columns=width) + + def _winapi(self, func: Callable[..., _T], *a: object, **kw: object) -> _T: + """ + Flush and call win API function. + """ + self.flush() + + if _DEBUG_RENDER_OUTPUT: + self.LOG.write(("%r" % func.__name__).encode("utf-8") + b"\n") + self.LOG.write( + b" " + ", ".join(["%r" % i for i in a]).encode("utf-8") + b"\n" + ) + self.LOG.write( + b" " + + ", ".join(["%r" % type(i) for i in a]).encode("utf-8") + + b"\n" + ) + self.LOG.flush() + + try: + return func(*a, **kw) + except ArgumentError as e: + if _DEBUG_RENDER_OUTPUT: + self.LOG.write((f" Error in {func.__name__!r} {e!r} {e}\n").encode()) + + raise + + def get_win32_screen_buffer_info(self) -> CONSOLE_SCREEN_BUFFER_INFO: + """ + Return Screen buffer info. + """ + # NOTE: We don't call the `GetConsoleScreenBufferInfo` API through + # `self._winapi`. Doing so causes Python to crash on certain 64bit + # Python versions. (Reproduced with 64bit Python 2.7.6, on Windows + # 10). It is not clear why. Possibly, it has to do with passing + # these objects as an argument, or through *args. + + # The Python documentation contains the following - possibly related - warning: + # ctypes does not support passing unions or structures with + # bit-fields to functions by value. While this may work on 32-bit + # x86, it's not guaranteed by the library to work in the general + # case. Unions and structures with bit-fields should always be + # passed to functions by pointer. + + # Also see: + # - https://github.com/ipython/ipython/issues/10070 + # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/406 + # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86 + + self.flush() + sbinfo = CONSOLE_SCREEN_BUFFER_INFO() + success = windll.kernel32.GetConsoleScreenBufferInfo( + self.hconsole, byref(sbinfo) + ) + + # success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo, + # self.hconsole, byref(sbinfo)) + + if success: + return sbinfo + else: + raise NoConsoleScreenBufferError + + def set_title(self, title: str) -> None: + """ + Set terminal title. + """ + self._winapi(windll.kernel32.SetConsoleTitleW, title) + + def clear_title(self) -> None: + self._winapi(windll.kernel32.SetConsoleTitleW, "") + + def erase_screen(self) -> None: + start = COORD(0, 0) + sbinfo = self.get_win32_screen_buffer_info() + length = sbinfo.dwSize.X * sbinfo.dwSize.Y + + self.cursor_goto(row=0, column=0) + self._erase(start, length) + + def erase_down(self) -> None: + sbinfo = self.get_win32_screen_buffer_info() + size = sbinfo.dwSize + + start = sbinfo.dwCursorPosition + length = (size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y) + + self._erase(start, length) + + def erase_end_of_line(self) -> None: + """""" + sbinfo = self.get_win32_screen_buffer_info() + start = sbinfo.dwCursorPosition + length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X + + self._erase(start, length) + + def _erase(self, start: COORD, length: int) -> None: + chars_written = c_ulong() + + self._winapi( + windll.kernel32.FillConsoleOutputCharacterA, + self.hconsole, + c_char(b" "), + DWORD(length), + _coord_byval(start), + byref(chars_written), + ) + + # Reset attributes. + sbinfo = self.get_win32_screen_buffer_info() + self._winapi( + windll.kernel32.FillConsoleOutputAttribute, + self.hconsole, + sbinfo.wAttributes, + length, + _coord_byval(start), + byref(chars_written), + ) + + def reset_attributes(self) -> None: + "Reset the console foreground/background color." + self._winapi( + windll.kernel32.SetConsoleTextAttribute, self.hconsole, self.default_attrs + ) + self._hidden = False + + def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: + ( + fgcolor, + bgcolor, + bold, + underline, + strike, + italic, + blink, + reverse, + hidden, + ) = attrs + self._hidden = bool(hidden) + + # Start from the default attributes. + win_attrs: int = self.default_attrs + + if color_depth != ColorDepth.DEPTH_1_BIT: + # Override the last four bits: foreground color. + if fgcolor: + win_attrs = win_attrs & ~0xF + win_attrs |= self.color_lookup_table.lookup_fg_color(fgcolor) + + # Override the next four bits: background color. + if bgcolor: + win_attrs = win_attrs & ~0xF0 + win_attrs |= self.color_lookup_table.lookup_bg_color(bgcolor) + + # Reverse: swap these four bits groups. + if reverse: + win_attrs = ( + (win_attrs & ~0xFF) + | ((win_attrs & 0xF) << 4) + | ((win_attrs & 0xF0) >> 4) + ) + + self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, win_attrs) + + def disable_autowrap(self) -> None: + # Not supported by Windows. + pass + + def enable_autowrap(self) -> None: + # Not supported by Windows. + pass + + def cursor_goto(self, row: int = 0, column: int = 0) -> None: + pos = COORD(X=column, Y=row) + self._winapi( + windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos) + ) + + def cursor_up(self, amount: int) -> None: + sr = self.get_win32_screen_buffer_info().dwCursorPosition + pos = COORD(X=sr.X, Y=sr.Y - amount) + self._winapi( + windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos) + ) + + def cursor_down(self, amount: int) -> None: + self.cursor_up(-amount) + + def cursor_forward(self, amount: int) -> None: + sr = self.get_win32_screen_buffer_info().dwCursorPosition + # assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount) + + pos = COORD(X=max(0, sr.X + amount), Y=sr.Y) + self._winapi( + windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos) + ) + + def cursor_backward(self, amount: int) -> None: + self.cursor_forward(-amount) + + def flush(self) -> None: + """ + Write to output stream and flush. + """ + if not self._buffer: + # Only flush stdout buffer. (It could be that Python still has + # something in its buffer. -- We want to be sure to print that in + # the correct color.) + self.stdout.flush() + return + + data = "".join(self._buffer) + + if _DEBUG_RENDER_OUTPUT: + self.LOG.write(("%r" % data).encode("utf-8") + b"\n") + self.LOG.flush() + + # Print characters one by one. This appears to be the best solution + # in oder to avoid traces of vertical lines when the completion + # menu disappears. + for b in data: + written = DWORD() + + retval = windll.kernel32.WriteConsoleW( + self.hconsole, b, 1, byref(written), None + ) + assert retval != 0 + + self._buffer = [] + + def get_rows_below_cursor_position(self) -> int: + info = self.get_win32_screen_buffer_info() + return info.srWindow.Bottom - info.dwCursorPosition.Y + 1 + + def scroll_buffer_to_prompt(self) -> None: + """ + To be called before drawing the prompt. This should scroll the console + to left, with the cursor at the bottom (if possible). + """ + # Get current window size + info = self.get_win32_screen_buffer_info() + sr = info.srWindow + cursor_pos = info.dwCursorPosition + + result = SMALL_RECT() + + # Scroll to the left. + result.Left = 0 + result.Right = sr.Right - sr.Left + + # Scroll vertical + win_height = sr.Bottom - sr.Top + if 0 < sr.Bottom - cursor_pos.Y < win_height - 1: + # no vertical scroll if cursor already on the screen + result.Bottom = sr.Bottom + else: + result.Bottom = max(win_height, cursor_pos.Y) + result.Top = result.Bottom - win_height + + # Scroll API + self._winapi( + windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result) + ) + + def enter_alternate_screen(self) -> None: + """ + Go to alternate screen buffer. + """ + if not self._in_alternate_screen: + GENERIC_READ = 0x80000000 + GENERIC_WRITE = 0x40000000 + + # Create a new console buffer and activate that one. + handle = HANDLE( + self._winapi( + windll.kernel32.CreateConsoleScreenBuffer, + GENERIC_READ | GENERIC_WRITE, + DWORD(0), + None, + DWORD(1), + None, + ) + ) + + self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle) + self.hconsole = handle + self._in_alternate_screen = True + + def quit_alternate_screen(self) -> None: + """ + Make stdout again the active buffer. + """ + if self._in_alternate_screen: + stdout = HANDLE( + self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE) + ) + self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout) + self._winapi(windll.kernel32.CloseHandle, self.hconsole) + self.hconsole = stdout + self._in_alternate_screen = False + + def enable_mouse_support(self) -> None: + ENABLE_MOUSE_INPUT = 0x10 + + # This `ENABLE_QUICK_EDIT_MODE` flag needs to be cleared for mouse + # support to work, but it's possible that it was already cleared + # before. + ENABLE_QUICK_EDIT_MODE = 0x0040 + + handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) + + original_mode = DWORD() + self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) + self._winapi( + windll.kernel32.SetConsoleMode, + handle, + (original_mode.value | ENABLE_MOUSE_INPUT) & ~ENABLE_QUICK_EDIT_MODE, + ) + + def disable_mouse_support(self) -> None: + ENABLE_MOUSE_INPUT = 0x10 + handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) + + original_mode = DWORD() + self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) + self._winapi( + windll.kernel32.SetConsoleMode, + handle, + original_mode.value & ~ENABLE_MOUSE_INPUT, + ) + + def hide_cursor(self) -> None: + pass + + def show_cursor(self) -> None: + pass + + def set_cursor_shape(self, cursor_shape: CursorShape) -> None: + pass + + def reset_cursor_shape(self) -> None: + pass + + @classmethod + def win32_refresh_window(cls) -> None: + """ + Call win32 API to refresh the whole Window. + + This is sometimes necessary when the application paints background + for completion menus. When the menu disappears, it leaves traces due + to a bug in the Windows Console. Sending a repaint request solves it. + """ + # Get console handle + handle = HANDLE(windll.kernel32.GetConsoleWindow()) + + RDW_INVALIDATE = 0x0001 + windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE)) + + def get_default_color_depth(self) -> ColorDepth: + """ + Return the default color depth for a windows terminal. + + Contrary to the Vt100 implementation, this doesn't depend on a $TERM + variable. + """ + if self.default_color_depth is not None: + return self.default_color_depth + + # For now, by default, always use 4 bit color on Windows 10 by default, + # even when vt100 escape sequences with + # ENABLE_VIRTUAL_TERMINAL_PROCESSING are supported. We don't have a + # reliable way yet to know whether our console supports true color or + # only 4-bit. + return ColorDepth.DEPTH_4_BIT + + +class FOREGROUND_COLOR: + BLACK = 0x0000 + BLUE = 0x0001 + GREEN = 0x0002 + CYAN = 0x0003 + RED = 0x0004 + MAGENTA = 0x0005 + YELLOW = 0x0006 + GRAY = 0x0007 + INTENSITY = 0x0008 # Foreground color is intensified. + + +class BACKGROUND_COLOR: + BLACK = 0x0000 + BLUE = 0x0010 + GREEN = 0x0020 + CYAN = 0x0030 + RED = 0x0040 + MAGENTA = 0x0050 + YELLOW = 0x0060 + GRAY = 0x0070 + INTENSITY = 0x0080 # Background color is intensified. + + +def _create_ansi_color_dict( + color_cls: Union[Type[FOREGROUND_COLOR], Type[BACKGROUND_COLOR]] +) -> Dict[str, int]: + "Create a table that maps the 16 named ansi colors to their Windows code." + return { + "ansidefault": color_cls.BLACK, + "ansiblack": color_cls.BLACK, + "ansigray": color_cls.GRAY, + "ansibrightblack": color_cls.BLACK | color_cls.INTENSITY, + "ansiwhite": color_cls.GRAY | color_cls.INTENSITY, + # Low intensity. + "ansired": color_cls.RED, + "ansigreen": color_cls.GREEN, + "ansiyellow": color_cls.YELLOW, + "ansiblue": color_cls.BLUE, + "ansimagenta": color_cls.MAGENTA, + "ansicyan": color_cls.CYAN, + # High intensity. + "ansibrightred": color_cls.RED | color_cls.INTENSITY, + "ansibrightgreen": color_cls.GREEN | color_cls.INTENSITY, + "ansibrightyellow": color_cls.YELLOW | color_cls.INTENSITY, + "ansibrightblue": color_cls.BLUE | color_cls.INTENSITY, + "ansibrightmagenta": color_cls.MAGENTA | color_cls.INTENSITY, + "ansibrightcyan": color_cls.CYAN | color_cls.INTENSITY, + } + + +FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR) +BG_ANSI_COLORS = _create_ansi_color_dict(BACKGROUND_COLOR) + +assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) +assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) + + +class ColorLookupTable: + """ + Inspired by pygments/formatters/terminal256.py + """ + + def __init__(self) -> None: + self._win32_colors = self._build_color_table() + + # Cache (map color string to foreground and background code). + self.best_match: Dict[str, Tuple[int, int]] = {} + + @staticmethod + def _build_color_table() -> List[Tuple[int, int, int, int, int]]: + """ + Build an RGB-to-256 color conversion table + """ + FG = FOREGROUND_COLOR + BG = BACKGROUND_COLOR + + return [ + (0x00, 0x00, 0x00, FG.BLACK, BG.BLACK), + (0x00, 0x00, 0xAA, FG.BLUE, BG.BLUE), + (0x00, 0xAA, 0x00, FG.GREEN, BG.GREEN), + (0x00, 0xAA, 0xAA, FG.CYAN, BG.CYAN), + (0xAA, 0x00, 0x00, FG.RED, BG.RED), + (0xAA, 0x00, 0xAA, FG.MAGENTA, BG.MAGENTA), + (0xAA, 0xAA, 0x00, FG.YELLOW, BG.YELLOW), + (0x88, 0x88, 0x88, FG.GRAY, BG.GRAY), + (0x44, 0x44, 0xFF, FG.BLUE | FG.INTENSITY, BG.BLUE | BG.INTENSITY), + (0x44, 0xFF, 0x44, FG.GREEN | FG.INTENSITY, BG.GREEN | BG.INTENSITY), + (0x44, 0xFF, 0xFF, FG.CYAN | FG.INTENSITY, BG.CYAN | BG.INTENSITY), + (0xFF, 0x44, 0x44, FG.RED | FG.INTENSITY, BG.RED | BG.INTENSITY), + (0xFF, 0x44, 0xFF, FG.MAGENTA | FG.INTENSITY, BG.MAGENTA | BG.INTENSITY), + (0xFF, 0xFF, 0x44, FG.YELLOW | FG.INTENSITY, BG.YELLOW | BG.INTENSITY), + (0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY), + (0xFF, 0xFF, 0xFF, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY), + ] + + def _closest_color(self, r: int, g: int, b: int) -> Tuple[int, int]: + distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) + fg_match = 0 + bg_match = 0 + + for r_, g_, b_, fg_, bg_ in self._win32_colors: + rd = r - r_ + gd = g - g_ + bd = b - b_ + + d = rd * rd + gd * gd + bd * bd + + if d < distance: + fg_match = fg_ + bg_match = bg_ + distance = d + return fg_match, bg_match + + def _color_indexes(self, color: str) -> Tuple[int, int]: + indexes = self.best_match.get(color, None) + if indexes is None: + try: + rgb = int(str(color), 16) + except ValueError: + rgb = 0 + + r = (rgb >> 16) & 0xFF + g = (rgb >> 8) & 0xFF + b = rgb & 0xFF + indexes = self._closest_color(r, g, b) + self.best_match[color] = indexes + return indexes + + def lookup_fg_color(self, fg_color: str) -> int: + """ + Return the color for use in the + `windll.kernel32.SetConsoleTextAttribute` API call. + + :param fg_color: Foreground as text. E.g. 'ffffff' or 'red' + """ + # Foreground. + if fg_color in FG_ANSI_COLORS: + return FG_ANSI_COLORS[fg_color] + else: + return self._color_indexes(fg_color)[0] + + def lookup_bg_color(self, bg_color: str) -> int: + """ + Return the color for use in the + `windll.kernel32.SetConsoleTextAttribute` API call. + + :param bg_color: Background as text. E.g. 'ffffff' or 'red' + """ + # Background. + if bg_color in BG_ANSI_COLORS: + return BG_ANSI_COLORS[bg_color] + else: + return self._color_indexes(bg_color)[1] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/output/windows10.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/windows10.py new file mode 100644 index 0000000..933f54a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/output/windows10.py @@ -0,0 +1,104 @@ +from ctypes import byref, windll +from ctypes.wintypes import DWORD, HANDLE +from typing import Any, Optional, TextIO + +from prompt_toolkit.data_structures import Size +from prompt_toolkit.utils import is_windows +from prompt_toolkit.win32_types import STD_OUTPUT_HANDLE + +from .base import Output +from .color_depth import ColorDepth +from .vt100 import Vt100_Output +from .win32 import Win32Output + +__all__ = [ + "Windows10_Output", +] + +# See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx +ENABLE_PROCESSED_INPUT = 0x0001 +ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + + +class Windows10_Output: + """ + Windows 10 output abstraction. This enables and uses vt100 escape sequences. + """ + + def __init__( + self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None + ) -> None: + self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth) + self.vt100_output = Vt100_Output( + stdout, lambda: Size(0, 0), default_color_depth=default_color_depth + ) + self._hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) + + def flush(self) -> None: + """ + Write to output stream and flush. + """ + original_mode = DWORD(0) + + # Remember the previous console mode. + windll.kernel32.GetConsoleMode(self._hconsole, byref(original_mode)) + + # Enable processing of vt100 sequences. + windll.kernel32.SetConsoleMode( + self._hconsole, + DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING), + ) + + try: + self.vt100_output.flush() + finally: + # Restore console mode. + windll.kernel32.SetConsoleMode(self._hconsole, original_mode) + + @property + def responds_to_cpr(self) -> bool: + return False # We don't need this on Windows. + + def __getattr__(self, name: str) -> Any: + if name in ( + "get_size", + "get_rows_below_cursor_position", + "enable_mouse_support", + "disable_mouse_support", + "scroll_buffer_to_prompt", + "get_win32_screen_buffer_info", + "enable_bracketed_paste", + "disable_bracketed_paste", + "get_default_color_depth", + ): + return getattr(self.win32_output, name) + else: + return getattr(self.vt100_output, name) + + +Output.register(Windows10_Output) + + +def is_win_vt100_enabled() -> bool: + """ + Returns True when we're running Windows and VT100 escape sequences are + supported. + """ + if not is_windows(): + return False + + hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) + + # Get original console mode. + original_mode = DWORD(0) + windll.kernel32.GetConsoleMode(hconsole, byref(original_mode)) + + try: + # Try to enable VT100 sequences. + result: int = windll.kernel32.SetConsoleMode( + hconsole, DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + ) + + return result == 1 + finally: + windll.kernel32.SetConsoleMode(hconsole, original_mode) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/patch_stdout.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/patch_stdout.py new file mode 100644 index 0000000..0abbcdb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/patch_stdout.py @@ -0,0 +1,288 @@ +""" +patch_stdout +============ + +This implements a context manager that ensures that print statements within +it won't destroy the user interface. The context manager will replace +`sys.stdout` by something that draws the output above the current prompt, +rather than overwriting the UI. + +Usage:: + + with patch_stdout(application): + ... + application.run() + ... + +Multiple applications can run in the body of the context manager, one after the +other. +""" +import asyncio +import queue +import sys +import threading +import time +from contextlib import contextmanager +from typing import Generator, List, Optional, TextIO, Union, cast + +from .application import get_app_session, run_in_terminal +from .output import Output + +__all__ = [ + "patch_stdout", + "StdoutProxy", +] + + +@contextmanager +def patch_stdout(raw: bool = False) -> Generator[None, None, None]: + """ + Replace `sys.stdout` by an :class:`_StdoutProxy` instance. + + Writing to this proxy will make sure that the text appears above the + prompt, and that it doesn't destroy the output from the renderer. If no + application is curring, the behaviour should be identical to writing to + `sys.stdout` directly. + + Warning: If a new event loop is installed using `asyncio.set_event_loop()`, + then make sure that the context manager is applied after the event loop + is changed. Printing to stdout will be scheduled in the event loop + that's active when the context manager is created. + + :param raw: (`bool`) When True, vt100 terminal escape sequences are not + removed/escaped. + """ + with StdoutProxy(raw=raw) as proxy: + original_stdout = sys.stdout + original_stderr = sys.stderr + + # Enter. + sys.stdout = cast(TextIO, proxy) + sys.stderr = cast(TextIO, proxy) + + try: + yield + finally: + sys.stdout = original_stdout + sys.stderr = original_stderr + + +class _Done: + "Sentinel value for stopping the stdout proxy." + + +class StdoutProxy: + """ + File-like object, which prints everything written to it, output above the + current application/prompt. This class is compatible with other file + objects and can be used as a drop-in replacement for `sys.stdout` or can + for instance be passed to `logging.StreamHandler`. + + The current application, above which we print, is determined by looking + what application currently runs in the `AppSession` that is active during + the creation of this instance. + + This class can be used as a context manager. + + In order to avoid having to repaint the prompt continuously for every + little write, a short delay of `sleep_between_writes` seconds will be added + between writes in order to bundle many smaller writes in a short timespan. + """ + + def __init__( + self, + sleep_between_writes: float = 0.2, + raw: bool = False, + ) -> None: + + self.sleep_between_writes = sleep_between_writes + self.raw = raw + + self._lock = threading.RLock() + self._buffer: List[str] = [] + + # Keep track of the curret app session. + self.app_session = get_app_session() + + # See what output is active *right now*. We should do it at this point, + # before this `StdoutProxy` instance is possibly assigned to `sys.stdout`. + # Otherwise, if `patch_stdout` is used, and no `Output` instance has + # been created, then the default output creation code will see this + # proxy object as `sys.stdout`, and get in a recursive loop trying to + # access `StdoutProxy.isatty()` which will again retrieve the output. + self._output: Output = self.app_session.output + + # Flush thread + self._flush_queue: queue.Queue[Union[str, _Done]] = queue.Queue() + self._flush_thread = self._start_write_thread() + self.closed = False + + def __enter__(self) -> "StdoutProxy": + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def close(self) -> None: + """ + Stop `StdoutProxy` proxy. + + This will terminate the write thread, make sure everything is flushed + and wait for the write thread to finish. + """ + if not self.closed: + self._flush_queue.put(_Done()) + self._flush_thread.join() + self.closed = True + + def _start_write_thread(self) -> threading.Thread: + thread = threading.Thread( + target=self._write_thread, + name="patch-stdout-flush-thread", + daemon=True, + ) + thread.start() + return thread + + def _write_thread(self) -> None: + done = False + + while not done: + item = self._flush_queue.get() + + if isinstance(item, _Done): + break + + # Don't bother calling when we got an empty string. + if not item: + continue + + text = [] + text.append(item) + + # Read the rest of the queue if more data was queued up. + while True: + try: + item = self._flush_queue.get_nowait() + except queue.Empty: + break + else: + if isinstance(item, _Done): + done = True + else: + text.append(item) + + app_loop = self._get_app_loop() + self._write_and_flush(app_loop, "".join(text)) + + # If an application was running that requires repainting, then wait + # for a very short time, in order to bundle actual writes and avoid + # having to repaint to often. + if app_loop is not None: + time.sleep(self.sleep_between_writes) + + def _get_app_loop(self) -> Optional[asyncio.AbstractEventLoop]: + """ + Return the event loop for the application currently running in our + `AppSession`. + """ + app = self.app_session.app + + if app is None: + return None + + return app.loop + + def _write_and_flush( + self, loop: Optional[asyncio.AbstractEventLoop], text: str + ) -> None: + """ + Write the given text to stdout and flush. + If an application is running, use `run_in_terminal`. + """ + + def write_and_flush() -> None: + if self.raw: + self._output.write_raw(text) + else: + self._output.write(text) + + self._output.flush() + + def write_and_flush_in_loop() -> None: + # If an application is running, use `run_in_terminal`, otherwise + # call it directly. + run_in_terminal(write_and_flush, in_executor=False) + + if loop is None: + # No loop, write immediately. + write_and_flush() + else: + # Make sure `write_and_flush` is executed *in* the event loop, not + # in another thread. + loop.call_soon_threadsafe(write_and_flush_in_loop) + + def _write(self, data: str) -> None: + """ + Note: print()-statements cause to multiple write calls. + (write('line') and write('\n')). Of course we don't want to call + `run_in_terminal` for every individual call, because that's too + expensive, and as long as the newline hasn't been written, the + text itself is again overwritten by the rendering of the input + command line. Therefor, we have a little buffer which holds the + text until a newline is written to stdout. + """ + if "\n" in data: + # When there is a newline in the data, write everything before the + # newline, including the newline itself. + before, after = data.rsplit("\n", 1) + to_write = self._buffer + [before, "\n"] + self._buffer = [after] + + text = "".join(to_write) + self._flush_queue.put(text) + else: + # Otherwise, cache in buffer. + self._buffer.append(data) + + def _flush(self) -> None: + text = "".join(self._buffer) + self._buffer = [] + self._flush_queue.put(text) + + def write(self, data: str) -> int: + with self._lock: + self._write(data) + + return len(data) # Pretend everything was written. + + def flush(self) -> None: + """ + Flush buffered output. + """ + with self._lock: + self._flush() + + @property + def original_stdout(self) -> TextIO: + return self._output.stdout or sys.__stdout__ + + # Attributes for compatibility with sys.__stdout__: + + def fileno(self) -> int: + return self._output.fileno() + + def isatty(self) -> bool: + stdout = self._output.stdout + if stdout is None: + return False + + return stdout.isatty() + + @property + def encoding(self) -> str: + return self._output.encoding() + + @property + def errors(self) -> str: + return "strict" diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/py.typed b/.venv/lib/python3.8/site-packages/prompt_toolkit/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/renderer.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/renderer.py new file mode 100644 index 0000000..e40fc9e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/renderer.py @@ -0,0 +1,811 @@ +""" +Renders the command line on the console. +(Redraws parts of the input line that were changed.) +""" +from asyncio import FIRST_COMPLETED, Future, ensure_future, sleep, wait +from collections import deque +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, Hashable, Optional, Tuple + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.cursor_shapes import CursorShape +from prompt_toolkit.data_structures import Point, Size +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text +from prompt_toolkit.layout.mouse_handlers import MouseHandlers +from prompt_toolkit.layout.screen import Char, Screen, WritePosition +from prompt_toolkit.output import ColorDepth, Output +from prompt_toolkit.styles import ( + Attrs, + BaseStyle, + DummyStyleTransformation, + StyleTransformation, +) + +if TYPE_CHECKING: + from prompt_toolkit.application import Application + from prompt_toolkit.layout.layout import Layout + + +__all__ = [ + "Renderer", + "print_formatted_text", +] + + +def _output_screen_diff( + app: "Application[Any]", + output: Output, + screen: Screen, + current_pos: Point, + color_depth: ColorDepth, + previous_screen: Optional[Screen], + last_style: Optional[str], + is_done: bool, # XXX: drop is_done + full_screen: bool, + attrs_for_style_string: "_StyleStringToAttrsCache", + style_string_has_style: "_StyleStringHasStyleCache", + size: Size, + previous_width: int, +) -> Tuple[Point, Optional[str]]: + """ + Render the diff between this screen and the previous screen. + + This takes two `Screen` instances. The one that represents the output like + it was during the last rendering and one that represents the current + output raster. Looking at these two `Screen` instances, this function will + render the difference by calling the appropriate methods of the `Output` + object that only paint the changes to the terminal. + + This is some performance-critical code which is heavily optimized. + Don't change things without profiling first. + + :param current_pos: Current cursor position. + :param last_style: The style string, used for drawing the last drawn + character. (Color/attributes.) + :param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance. + :param width: The width of the terminal. + :param previous_width: The width of the terminal during the last rendering. + """ + width, height = size.columns, size.rows + + #: Variable for capturing the output. + write = output.write + write_raw = output.write_raw + + # Create locals for the most used output methods. + # (Save expensive attribute lookups.) + _output_set_attributes = output.set_attributes + _output_reset_attributes = output.reset_attributes + _output_cursor_forward = output.cursor_forward + _output_cursor_up = output.cursor_up + _output_cursor_backward = output.cursor_backward + + # Hide cursor before rendering. (Avoid flickering.) + output.hide_cursor() + + def reset_attributes() -> None: + "Wrapper around Output.reset_attributes." + nonlocal last_style + _output_reset_attributes() + last_style = None # Forget last char after resetting attributes. + + def move_cursor(new: Point) -> Point: + "Move cursor to this `new` point. Returns the given Point." + current_x, current_y = current_pos.x, current_pos.y + + if new.y > current_y: + # Use newlines instead of CURSOR_DOWN, because this might add new lines. + # CURSOR_DOWN will never create new lines at the bottom. + # Also reset attributes, otherwise the newline could draw a + # background color. + reset_attributes() + write("\r\n" * (new.y - current_y)) + current_x = 0 + _output_cursor_forward(new.x) + return new + elif new.y < current_y: + _output_cursor_up(current_y - new.y) + + if current_x >= width - 1: + write("\r") + _output_cursor_forward(new.x) + elif new.x < current_x or current_x >= width - 1: + _output_cursor_backward(current_x - new.x) + elif new.x > current_x: + _output_cursor_forward(new.x - current_x) + + return new + + def output_char(char: Char) -> None: + """ + Write the output of this character. + """ + nonlocal last_style + + # If the last printed character has the same style, don't output the + # style again. + if last_style == char.style: + write(char.char) + else: + # Look up `Attr` for this style string. Only set attributes if different. + # (Two style strings can still have the same formatting.) + # Note that an empty style string can have formatting that needs to + # be applied, because of style transformations. + new_attrs = attrs_for_style_string[char.style] + if not last_style or new_attrs != attrs_for_style_string[last_style]: + _output_set_attributes(new_attrs, color_depth) + + write(char.char) + last_style = char.style + + def get_max_column_index(row: Dict[int, Char]) -> int: + """ + Return max used column index, ignoring whitespace (without style) at + the end of the line. This is important for people that copy/paste + terminal output. + + There are two reasons we are sometimes seeing whitespace at the end: + - `BufferControl` adds a trailing space to each line, because it's a + possible cursor position, so that the line wrapping won't change if + the cursor position moves around. + - The `Window` adds a style class to the current line for highlighting + (cursor-line). + """ + numbers = [ + index + for index, cell in row.items() + if cell.char != " " or style_string_has_style[cell.style] + ] + numbers.append(0) + return max(numbers) + + # Render for the first time: reset styling. + if not previous_screen: + reset_attributes() + + # Disable autowrap. (When entering a the alternate screen, or anytime when + # we have a prompt. - In the case of a REPL, like IPython, people can have + # background threads, and it's hard for debugging if their output is not + # wrapped.) + if not previous_screen or not full_screen: + output.disable_autowrap() + + # When the previous screen has a different size, redraw everything anyway. + # Also when we are done. (We might take up less rows, so clearing is important.) + if ( + is_done or not previous_screen or previous_width != width + ): # XXX: also consider height?? + current_pos = move_cursor(Point(x=0, y=0)) + reset_attributes() + output.erase_down() + + previous_screen = Screen() + + # Get height of the screen. + # (height changes as we loop over data_buffer, so remember the current value.) + # (Also make sure to clip the height to the size of the output.) + current_height = min(screen.height, height) + + # Loop over the rows. + row_count = min(max(screen.height, previous_screen.height), height) + c = 0 # Column counter. + + for y in range(row_count): + new_row = screen.data_buffer[y] + previous_row = previous_screen.data_buffer[y] + zero_width_escapes_row = screen.zero_width_escapes[y] + + new_max_line_len = min(width - 1, get_max_column_index(new_row)) + previous_max_line_len = min(width - 1, get_max_column_index(previous_row)) + + # Loop over the columns. + c = 0 + while c <= new_max_line_len: + new_char = new_row[c] + old_char = previous_row[c] + char_width = new_char.width or 1 + + # When the old and new character at this position are different, + # draw the output. (Because of the performance, we don't call + # `Char.__ne__`, but inline the same expression.) + if new_char.char != old_char.char or new_char.style != old_char.style: + current_pos = move_cursor(Point(x=c, y=y)) + + # Send injected escape sequences to output. + if c in zero_width_escapes_row: + write_raw(zero_width_escapes_row[c]) + + output_char(new_char) + current_pos = Point(x=current_pos.x + char_width, y=current_pos.y) + + c += char_width + + # If the new line is shorter, trim it. + if previous_screen and new_max_line_len < previous_max_line_len: + current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y)) + reset_attributes() + output.erase_end_of_line() + + # Correctly reserve vertical space as required by the layout. + # When this is a new screen (drawn for the first time), or for some reason + # higher than the previous one. Move the cursor once to the bottom of the + # output. That way, we're sure that the terminal scrolls up, even when the + # lower lines of the canvas just contain whitespace. + + # The most obvious reason that we actually want this behaviour is the avoid + # the artifact of the input scrolling when the completion menu is shown. + # (If the scrolling is actually wanted, the layout can still be build in a + # way to behave that way by setting a dynamic height.) + if current_height > previous_screen.height: + current_pos = move_cursor(Point(x=0, y=current_height - 1)) + + # Move cursor: + if is_done: + current_pos = move_cursor(Point(x=0, y=current_height)) + output.erase_down() + else: + current_pos = move_cursor(screen.get_cursor_position(app.layout.current_window)) + + if is_done or not full_screen: + output.enable_autowrap() + + # Always reset the color attributes. This is important because a background + # thread could print data to stdout and we want that to be displayed in the + # default colors. (Also, if a background color has been set, many terminals + # give weird artifacts on resize events.) + reset_attributes() + + if screen.show_cursor or is_done: + output.show_cursor() + + return current_pos, last_style + + +class HeightIsUnknownError(Exception): + "Information unavailable. Did not yet receive the CPR response." + + +class _StyleStringToAttrsCache(Dict[str, Attrs]): + """ + A cache structure that maps style strings to :class:`.Attr`. + (This is an important speed up.) + """ + + def __init__( + self, + get_attrs_for_style_str: Callable[["str"], Attrs], + style_transformation: StyleTransformation, + ) -> None: + + self.get_attrs_for_style_str = get_attrs_for_style_str + self.style_transformation = style_transformation + + def __missing__(self, style_str: str) -> Attrs: + attrs = self.get_attrs_for_style_str(style_str) + attrs = self.style_transformation.transform_attrs(attrs) + + self[style_str] = attrs + return attrs + + +class _StyleStringHasStyleCache(Dict[str, bool]): + """ + Cache for remember which style strings don't render the default output + style (default fg/bg, no underline and no reverse and no blink). That way + we know that we should render these cells, even when they're empty (when + they contain a space). + + Note: we don't consider bold/italic/hidden because they don't change the + output if there's no text in the cell. + """ + + def __init__(self, style_string_to_attrs: Dict[str, Attrs]) -> None: + self.style_string_to_attrs = style_string_to_attrs + + def __missing__(self, style_str: str) -> bool: + attrs = self.style_string_to_attrs[style_str] + is_default = bool( + attrs.color + or attrs.bgcolor + or attrs.underline + or attrs.strike + or attrs.blink + or attrs.reverse + ) + + self[style_str] = is_default + return is_default + + +class CPR_Support(Enum): + "Enum: whether or not CPR is supported." + SUPPORTED = "SUPPORTED" + NOT_SUPPORTED = "NOT_SUPPORTED" + UNKNOWN = "UNKNOWN" + + +class Renderer: + """ + Typical usage: + + :: + + output = Vt100_Output.from_pty(sys.stdout) + r = Renderer(style, output) + r.render(app, layout=...) + """ + + CPR_TIMEOUT = 2 # Time to wait until we consider CPR to be not supported. + + def __init__( + self, + style: BaseStyle, + output: Output, + full_screen: bool = False, + mouse_support: FilterOrBool = False, + cpr_not_supported_callback: Optional[Callable[[], None]] = None, + ) -> None: + + self.style = style + self.output = output + self.full_screen = full_screen + self.mouse_support = to_filter(mouse_support) + self.cpr_not_supported_callback = cpr_not_supported_callback + + self._in_alternate_screen = False + self._mouse_support_enabled = False + self._bracketed_paste_enabled = False + self._cursor_key_mode_reset = False + + # Future set when we are waiting for a CPR flag. + self._waiting_for_cpr_futures: Deque[Future[None]] = deque() + self.cpr_support = CPR_Support.UNKNOWN + + if not output.responds_to_cpr: + self.cpr_support = CPR_Support.NOT_SUPPORTED + + # Cache for the style. + self._attrs_for_style: Optional[_StyleStringToAttrsCache] = None + self._style_string_has_style: Optional[_StyleStringHasStyleCache] = None + self._last_style_hash: Optional[Hashable] = None + self._last_transformation_hash: Optional[Hashable] = None + self._last_color_depth: Optional[ColorDepth] = None + + self.reset(_scroll=True) + + def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None: + + # Reset position + self._cursor_pos = Point(x=0, y=0) + + # Remember the last screen instance between renderers. This way, + # we can create a `diff` between two screens and only output the + # difference. It's also to remember the last height. (To show for + # instance a toolbar at the bottom position.) + self._last_screen: Optional[Screen] = None + self._last_size: Optional[Size] = None + self._last_style: Optional[str] = None + self._last_cursor_shape: Optional[CursorShape] = None + + # Default MouseHandlers. (Just empty.) + self.mouse_handlers = MouseHandlers() + + #: Space from the top of the layout, until the bottom of the terminal. + #: We don't know this until a `report_absolute_cursor_row` call. + self._min_available_height = 0 + + # In case of Windows, also make sure to scroll to the current cursor + # position. (Only when rendering the first time.) + # It does nothing for vt100 terminals. + if _scroll: + self.output.scroll_buffer_to_prompt() + + # Quit alternate screen. + if self._in_alternate_screen and leave_alternate_screen: + self.output.quit_alternate_screen() + self._in_alternate_screen = False + + # Disable mouse support. + if self._mouse_support_enabled: + self.output.disable_mouse_support() + self._mouse_support_enabled = False + + # Disable bracketed paste. + if self._bracketed_paste_enabled: + self.output.disable_bracketed_paste() + self._bracketed_paste_enabled = False + + self.output.reset_cursor_shape() + + # NOTE: No need to set/reset cursor key mode here. + + # Flush output. `disable_mouse_support` needs to write to stdout. + self.output.flush() + + @property + def last_rendered_screen(self) -> Optional[Screen]: + """ + The `Screen` class that was generated during the last rendering. + This can be `None`. + """ + return self._last_screen + + @property + def height_is_known(self) -> bool: + """ + True when the height from the cursor until the bottom of the terminal + is known. (It's often nicer to draw bottom toolbars only if the height + is known, in order to avoid flickering when the CPR response arrives.) + """ + if self.full_screen or self._min_available_height > 0: + return True + try: + self._min_available_height = self.output.get_rows_below_cursor_position() + return True + except NotImplementedError: + return False + + @property + def rows_above_layout(self) -> int: + """ + Return the number of rows visible in the terminal above the layout. + """ + if self._in_alternate_screen: + return 0 + elif self._min_available_height > 0: + total_rows = self.output.get_size().rows + last_screen_height = self._last_screen.height if self._last_screen else 0 + return total_rows - max(self._min_available_height, last_screen_height) + else: + raise HeightIsUnknownError("Rows above layout is unknown.") + + def request_absolute_cursor_position(self) -> None: + """ + Get current cursor position. + + We do this to calculate the minimum available height that we can + consume for rendering the prompt. This is the available space below te + cursor. + + For vt100: Do CPR request. (answer will arrive later.) + For win32: Do API call. (Answer comes immediately.) + """ + # Only do this request when the cursor is at the top row. (after a + # clear or reset). We will rely on that in `report_absolute_cursor_row`. + assert self._cursor_pos.y == 0 + + # In full-screen mode, always use the total height as min-available-height. + if self.full_screen: + self._min_available_height = self.output.get_size().rows + return + + # For Win32, we have an API call to get the number of rows below the + # cursor. + try: + self._min_available_height = self.output.get_rows_below_cursor_position() + return + except NotImplementedError: + pass + + # Use CPR. + if self.cpr_support == CPR_Support.NOT_SUPPORTED: + return + + def do_cpr() -> None: + # Asks for a cursor position report (CPR). + self._waiting_for_cpr_futures.append(Future()) + self.output.ask_for_cpr() + + if self.cpr_support == CPR_Support.SUPPORTED: + do_cpr() + return + + # If we don't know whether CPR is supported, only do a request if + # none is pending, and test it, using a timer. + if self.waiting_for_cpr: + return + + do_cpr() + + async def timer() -> None: + await sleep(self.CPR_TIMEOUT) + + # Not set in the meantime -> not supported. + if self.cpr_support == CPR_Support.UNKNOWN: + self.cpr_support = CPR_Support.NOT_SUPPORTED + + if self.cpr_not_supported_callback: + # Make sure to call this callback in the main thread. + self.cpr_not_supported_callback() + + get_app().create_background_task(timer()) + + def report_absolute_cursor_row(self, row: int) -> None: + """ + To be called when we know the absolute cursor position. + (As an answer of a "Cursor Position Request" response.) + """ + self.cpr_support = CPR_Support.SUPPORTED + + # Calculate the amount of rows from the cursor position until the + # bottom of the terminal. + total_rows = self.output.get_size().rows + rows_below_cursor = total_rows - row + 1 + + # Set the minimum available height. + self._min_available_height = rows_below_cursor + + # Pop and set waiting for CPR future. + try: + f = self._waiting_for_cpr_futures.popleft() + except IndexError: + pass # Received CPR response without having a CPR. + else: + f.set_result(None) + + @property + def waiting_for_cpr(self) -> bool: + """ + Waiting for CPR flag. True when we send the request, but didn't got a + response. + """ + return bool(self._waiting_for_cpr_futures) + + async def wait_for_cpr_responses(self, timeout: int = 1) -> None: + """ + Wait for a CPR response. + """ + cpr_futures = list(self._waiting_for_cpr_futures) # Make copy. + + # When there are no CPRs in the queue. Don't do anything. + if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED: + return None + + async def wait_for_responses() -> None: + for response_f in cpr_futures: + await response_f + + async def wait_for_timeout() -> None: + await sleep(timeout) + + # Got timeout, erase queue. + for response_f in cpr_futures: + response_f.cancel() + self._waiting_for_cpr_futures = deque() + + tasks = { + ensure_future(wait_for_responses()), + ensure_future(wait_for_timeout()), + } + _, pending = await wait(tasks, return_when=FIRST_COMPLETED) + for task in pending: + task.cancel() + + def render( + self, app: "Application[Any]", layout: "Layout", is_done: bool = False + ) -> None: + """ + Render the current interface to the output. + + :param is_done: When True, put the cursor at the end of the interface. We + won't print any changes to this part. + """ + output = self.output + + # Enter alternate screen. + if self.full_screen and not self._in_alternate_screen: + self._in_alternate_screen = True + output.enter_alternate_screen() + + # Enable bracketed paste. + if not self._bracketed_paste_enabled: + self.output.enable_bracketed_paste() + self._bracketed_paste_enabled = True + + # Reset cursor key mode. + if not self._cursor_key_mode_reset: + self.output.reset_cursor_key_mode() + self._cursor_key_mode_reset = True + + # Enable/disable mouse support. + needs_mouse_support = self.mouse_support() + + if needs_mouse_support and not self._mouse_support_enabled: + output.enable_mouse_support() + self._mouse_support_enabled = True + + elif not needs_mouse_support and self._mouse_support_enabled: + output.disable_mouse_support() + self._mouse_support_enabled = False + + # Create screen and write layout to it. + size = output.get_size() + screen = Screen() + screen.show_cursor = False # Hide cursor by default, unless one of the + # containers decides to display it. + mouse_handlers = MouseHandlers() + + # Calculate height. + if self.full_screen: + height = size.rows + elif is_done: + # When we are done, we don't necessary want to fill up until the bottom. + height = layout.container.preferred_height( + size.columns, size.rows + ).preferred + else: + last_height = self._last_screen.height if self._last_screen else 0 + height = max( + self._min_available_height, + last_height, + layout.container.preferred_height(size.columns, size.rows).preferred, + ) + + height = min(height, size.rows) + + # When the size changes, don't consider the previous screen. + if self._last_size != size: + self._last_screen = None + + # When we render using another style or another color depth, do a full + # repaint. (Forget about the previous rendered screen.) + # (But note that we still use _last_screen to calculate the height.) + if ( + self.style.invalidation_hash() != self._last_style_hash + or app.style_transformation.invalidation_hash() + != self._last_transformation_hash + or app.color_depth != self._last_color_depth + ): + self._last_screen = None + self._attrs_for_style = None + self._style_string_has_style = None + + if self._attrs_for_style is None: + self._attrs_for_style = _StyleStringToAttrsCache( + self.style.get_attrs_for_style_str, app.style_transformation + ) + if self._style_string_has_style is None: + self._style_string_has_style = _StyleStringHasStyleCache( + self._attrs_for_style + ) + + self._last_style_hash = self.style.invalidation_hash() + self._last_transformation_hash = app.style_transformation.invalidation_hash() + self._last_color_depth = app.color_depth + + layout.container.write_to_screen( + screen, + mouse_handlers, + WritePosition(xpos=0, ypos=0, width=size.columns, height=height), + parent_style="", + erase_bg=False, + z_index=None, + ) + screen.draw_all_floats() + + # When grayed. Replace all styles in the new screen. + if app.exit_style: + screen.append_style_to_content(app.exit_style) + + # Process diff and write to output. + self._cursor_pos, self._last_style = _output_screen_diff( + app, + output, + screen, + self._cursor_pos, + app.color_depth, + self._last_screen, + self._last_style, + is_done, + full_screen=self.full_screen, + attrs_for_style_string=self._attrs_for_style, + style_string_has_style=self._style_string_has_style, + size=size, + previous_width=(self._last_size.columns if self._last_size else 0), + ) + self._last_screen = screen + self._last_size = size + self.mouse_handlers = mouse_handlers + + # Handle cursor shapes. + new_cursor_shape = app.cursor.get_cursor_shape(app) + if ( + self._last_cursor_shape is None + or self._last_cursor_shape != new_cursor_shape + ): + output.set_cursor_shape(new_cursor_shape) + self._last_cursor_shape = new_cursor_shape + + # Flush buffered output. + output.flush() + + # Set visible windows in layout. + app.layout.visible_windows = screen.visible_windows + + if is_done: + self.reset() + + def erase(self, leave_alternate_screen: bool = True) -> None: + """ + Hide all output and put the cursor back at the first line. This is for + instance used for running a system command (while hiding the CLI) and + later resuming the same CLI.) + + :param leave_alternate_screen: When True, and when inside an alternate + screen buffer, quit the alternate screen. + """ + output = self.output + + output.cursor_backward(self._cursor_pos.x) + output.cursor_up(self._cursor_pos.y) + output.erase_down() + output.reset_attributes() + output.enable_autowrap() + + output.flush() + + self.reset(leave_alternate_screen=leave_alternate_screen) + + def clear(self) -> None: + """ + Clear screen and go to 0,0 + """ + # Erase current output first. + self.erase() + + # Send "Erase Screen" command and go to (0, 0). + output = self.output + + output.erase_screen() + output.cursor_goto(0, 0) + output.flush() + + self.request_absolute_cursor_position() + + +def print_formatted_text( + output: Output, + formatted_text: AnyFormattedText, + style: BaseStyle, + style_transformation: Optional[StyleTransformation] = None, + color_depth: Optional[ColorDepth] = None, +) -> None: + """ + Print a list of (style_str, text) tuples in the given style to the output. + """ + fragments = to_formatted_text(formatted_text) + style_transformation = style_transformation or DummyStyleTransformation() + color_depth = color_depth or output.get_default_color_depth() + + # Reset first. + output.reset_attributes() + output.enable_autowrap() + last_attrs: Optional[Attrs] = None + + # Print all (style_str, text) tuples. + attrs_for_style_string = _StyleStringToAttrsCache( + style.get_attrs_for_style_str, style_transformation + ) + + for style_str, text, *_ in fragments: + attrs = attrs_for_style_string[style_str] + + # Set style attributes if something changed. + if attrs != last_attrs: + if attrs: + output.set_attributes(attrs, color_depth) + else: + output.reset_attributes() + last_attrs = attrs + + # Eliminate carriage returns + text = text.replace("\r", "") + + # Assume that the output is raw, and insert a carriage return before + # every newline. (Also important when the front-end is a telnet client.) + output.write(text.replace("\n", "\r\n")) + + # Reset again. + output.reset_attributes() + output.flush() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/search.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/search.py new file mode 100644 index 0000000..413cc6a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/search.py @@ -0,0 +1,229 @@ +""" +Search operations. + +For the key bindings implementation with attached filters, check +`prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings +instead of calling these function directly.) +""" +from enum import Enum +from typing import TYPE_CHECKING, Dict, Optional + +from .application.current import get_app +from .filters import FilterOrBool, is_searching, to_filter +from .key_binding.vi_state import InputMode + +if TYPE_CHECKING: + from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl + from prompt_toolkit.layout.layout import Layout + +__all__ = [ + "SearchDirection", + "start_search", + "stop_search", +] + + +class SearchDirection(Enum): + FORWARD = "FORWARD" + BACKWARD = "BACKWARD" + + +class SearchState: + """ + A search 'query', associated with a search field (like a SearchToolbar). + + Every searchable `BufferControl` points to a `search_buffer_control` + (another `BufferControls`) which represents the search field. The + `SearchState` attached to that search field is used for storing the current + search query. + + It is possible to have one searchfield for multiple `BufferControls`. In + that case, they'll share the same `SearchState`. + If there are multiple `BufferControls` that display the same `Buffer`, then + they can have a different `SearchState` each (if they have a different + search control). + """ + + __slots__ = ("text", "direction", "ignore_case") + + def __init__( + self, + text: str = "", + direction: SearchDirection = SearchDirection.FORWARD, + ignore_case: FilterOrBool = False, + ) -> None: + + self.text = text + self.direction = direction + self.ignore_case = to_filter(ignore_case) + + def __repr__(self) -> str: + return "{}({!r}, direction={!r}, ignore_case={!r})".format( + self.__class__.__name__, + self.text, + self.direction, + self.ignore_case, + ) + + def __invert__(self) -> "SearchState": + """ + Create a new SearchState where backwards becomes forwards and the other + way around. + """ + if self.direction == SearchDirection.BACKWARD: + direction = SearchDirection.FORWARD + else: + direction = SearchDirection.BACKWARD + + return SearchState( + text=self.text, direction=direction, ignore_case=self.ignore_case + ) + + +def start_search( + buffer_control: Optional["BufferControl"] = None, + direction: SearchDirection = SearchDirection.FORWARD, +) -> None: + """ + Start search through the given `buffer_control` using the + `search_buffer_control`. + + :param buffer_control: Start search for this `BufferControl`. If not given, + search through the current control. + """ + from prompt_toolkit.layout.controls import BufferControl + + assert buffer_control is None or isinstance(buffer_control, BufferControl) + + layout = get_app().layout + + # When no control is given, use the current control if that's a BufferControl. + if buffer_control is None: + if not isinstance(layout.current_control, BufferControl): + return + buffer_control = layout.current_control + + # Only if this control is searchable. + search_buffer_control = buffer_control.search_buffer_control + + if search_buffer_control: + buffer_control.search_state.direction = direction + + # Make sure to focus the search BufferControl + layout.focus(search_buffer_control) + + # Remember search link. + layout.search_links[search_buffer_control] = buffer_control + + # If we're in Vi mode, make sure to go into insert mode. + get_app().vi_state.input_mode = InputMode.INSERT + + +def stop_search(buffer_control: Optional["BufferControl"] = None) -> None: + """ + Stop search through the given `buffer_control`. + """ + layout = get_app().layout + + if buffer_control is None: + buffer_control = layout.search_target_buffer_control + if buffer_control is None: + # (Should not happen, but possible when `stop_search` is called + # when we're not searching.) + return + search_buffer_control = buffer_control.search_buffer_control + else: + assert buffer_control in layout.search_links.values() + search_buffer_control = _get_reverse_search_links(layout)[buffer_control] + + # Focus the original buffer again. + layout.focus(buffer_control) + + if search_buffer_control is not None: + # Remove the search link. + del layout.search_links[search_buffer_control] + + # Reset content of search control. + search_buffer_control.buffer.reset() + + # If we're in Vi mode, go back to navigation mode. + get_app().vi_state.input_mode = InputMode.NAVIGATION + + +def do_incremental_search(direction: SearchDirection, count: int = 1) -> None: + """ + Apply search, but keep search buffer focused. + """ + assert is_searching() + + layout = get_app().layout + + # Only search if the current control is a `BufferControl`. + from prompt_toolkit.layout.controls import BufferControl + + search_control = layout.current_control + if not isinstance(search_control, BufferControl): + return + + prev_control = layout.search_target_buffer_control + if prev_control is None: + return + search_state = prev_control.search_state + + # Update search_state. + direction_changed = search_state.direction != direction + + search_state.text = search_control.buffer.text + search_state.direction = direction + + # Apply search to current buffer. + if not direction_changed: + prev_control.buffer.apply_search( + search_state, include_current_position=False, count=count + ) + + +def accept_search() -> None: + """ + Accept current search query. Focus original `BufferControl` again. + """ + layout = get_app().layout + + search_control = layout.current_control + target_buffer_control = layout.search_target_buffer_control + + from prompt_toolkit.layout.controls import BufferControl + + if not isinstance(search_control, BufferControl): + return + if target_buffer_control is None: + return + + search_state = target_buffer_control.search_state + + # Update search state. + if search_control.buffer.text: + search_state.text = search_control.buffer.text + + # Apply search. + target_buffer_control.buffer.apply_search( + search_state, include_current_position=True + ) + + # Add query to history of search line. + search_control.buffer.append_to_history() + + # Stop search and focus previous control again. + stop_search(target_buffer_control) + + +def _get_reverse_search_links( + layout: "Layout", +) -> Dict["BufferControl", "SearchBufferControl"]: + """ + Return mapping from BufferControl to SearchBufferControl. + """ + return { + buffer_control: search_buffer_control + for search_buffer_control, buffer_control in layout.search_links.items() + } diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/selection.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/selection.py new file mode 100644 index 0000000..a02aa87 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/selection.py @@ -0,0 +1,60 @@ +""" +Data structures for the selection. +""" +from enum import Enum + +__all__ = [ + "SelectionType", + "PasteMode", + "SelectionState", +] + + +class SelectionType(Enum): + """ + Type of selection. + """ + + #: Characters. (Visual in Vi.) + CHARACTERS = "CHARACTERS" + + #: Whole lines. (Visual-Line in Vi.) + LINES = "LINES" + + #: A block selection. (Visual-Block in Vi.) + BLOCK = "BLOCK" + + +class PasteMode(Enum): + EMACS = "EMACS" # Yank like emacs. + VI_AFTER = "VI_AFTER" # When pressing 'p' in Vi. + VI_BEFORE = "VI_BEFORE" # When pressing 'P' in Vi. + + +class SelectionState: + """ + State of the current selection. + + :param original_cursor_position: int + :param type: :class:`~.SelectionType` + """ + + def __init__( + self, + original_cursor_position: int = 0, + type: SelectionType = SelectionType.CHARACTERS, + ) -> None: + + self.original_cursor_position = original_cursor_position + self.type = type + self.shift_mode = False + + def enter_shift_mode(self) -> None: + self.shift_mode = True + + def __repr__(self) -> str: + return "{}(original_cursor_position={!r}, type={!r})".format( + self.__class__.__name__, + self.original_cursor_position, + self.type, + ) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__init__.py new file mode 100644 index 0000000..10ad314 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__init__.py @@ -0,0 +1,44 @@ +from .dialogs import ( + button_dialog, + checkboxlist_dialog, + input_dialog, + message_dialog, + progress_dialog, + radiolist_dialog, + yes_no_dialog, +) +from .progress_bar import ProgressBar, ProgressBarCounter +from .prompt import ( + CompleteStyle, + PromptSession, + confirm, + create_confirm_session, + prompt, +) +from .utils import clear, clear_title, print_container, print_formatted_text, set_title + +__all__ = [ + # Dialogs. + "input_dialog", + "message_dialog", + "progress_dialog", + "checkboxlist_dialog", + "radiolist_dialog", + "yes_no_dialog", + "button_dialog", + # Prompts. + "PromptSession", + "prompt", + "confirm", + "create_confirm_session", + "CompleteStyle", + # Progress bars. + "ProgressBar", + "ProgressBarCounter", + # Utils. + "clear", + "clear_title", + "print_container", + "print_formatted_text", + "set_title", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3281b7f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/dialogs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/dialogs.cpython-38.pyc new file mode 100644 index 0000000..83fe43a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/dialogs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/prompt.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/prompt.cpython-38.pyc new file mode 100644 index 0000000..ede8a69 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/prompt.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..abe5a46 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/dialogs.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/dialogs.py new file mode 100644 index 0000000..9140f86 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/dialogs.py @@ -0,0 +1,325 @@ +import functools +from typing import Any, Callable, List, Optional, Sequence, Tuple, TypeVar + +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.completion import Completer +from prompt_toolkit.eventloop import get_event_loop, run_in_executor_with_context +from prompt_toolkit.filters import FilterOrBool +from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.key_binding.defaults import load_key_bindings +from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings +from prompt_toolkit.layout import Layout +from prompt_toolkit.layout.containers import AnyContainer, HSplit +from prompt_toolkit.layout.dimension import Dimension as D +from prompt_toolkit.styles import BaseStyle +from prompt_toolkit.validation import Validator +from prompt_toolkit.widgets import ( + Box, + Button, + CheckboxList, + Dialog, + Label, + ProgressBar, + RadioList, + TextArea, + ValidationToolbar, +) + +__all__ = [ + "yes_no_dialog", + "button_dialog", + "input_dialog", + "message_dialog", + "radiolist_dialog", + "checkboxlist_dialog", + "progress_dialog", +] + + +def yes_no_dialog( + title: AnyFormattedText = "", + text: AnyFormattedText = "", + yes_text: str = "Yes", + no_text: str = "No", + style: Optional[BaseStyle] = None, +) -> Application[bool]: + """ + Display a Yes/No dialog. + Return a boolean. + """ + + def yes_handler() -> None: + get_app().exit(result=True) + + def no_handler() -> None: + get_app().exit(result=False) + + dialog = Dialog( + title=title, + body=Label(text=text, dont_extend_height=True), + buttons=[ + Button(text=yes_text, handler=yes_handler), + Button(text=no_text, handler=no_handler), + ], + with_background=True, + ) + + return _create_app(dialog, style) + + +_T = TypeVar("_T") + + +def button_dialog( + title: AnyFormattedText = "", + text: AnyFormattedText = "", + buttons: List[Tuple[str, _T]] = [], + style: Optional[BaseStyle] = None, +) -> Application[_T]: + """ + Display a dialog with button choices (given as a list of tuples). + Return the value associated with button. + """ + + def button_handler(v: _T) -> None: + get_app().exit(result=v) + + dialog = Dialog( + title=title, + body=Label(text=text, dont_extend_height=True), + buttons=[ + Button(text=t, handler=functools.partial(button_handler, v)) + for t, v in buttons + ], + with_background=True, + ) + + return _create_app(dialog, style) + + +def input_dialog( + title: AnyFormattedText = "", + text: AnyFormattedText = "", + ok_text: str = "OK", + cancel_text: str = "Cancel", + completer: Optional[Completer] = None, + validator: Optional[Validator] = None, + password: FilterOrBool = False, + style: Optional[BaseStyle] = None, +) -> Application[str]: + """ + Display a text input box. + Return the given text, or None when cancelled. + """ + + def accept(buf: Buffer) -> bool: + get_app().layout.focus(ok_button) + return True # Keep text. + + def ok_handler() -> None: + get_app().exit(result=textfield.text) + + ok_button = Button(text=ok_text, handler=ok_handler) + cancel_button = Button(text=cancel_text, handler=_return_none) + + textfield = TextArea( + multiline=False, + password=password, + completer=completer, + validator=validator, + accept_handler=accept, + ) + + dialog = Dialog( + title=title, + body=HSplit( + [ + Label(text=text, dont_extend_height=True), + textfield, + ValidationToolbar(), + ], + padding=D(preferred=1, max=1), + ), + buttons=[ok_button, cancel_button], + with_background=True, + ) + + return _create_app(dialog, style) + + +def message_dialog( + title: AnyFormattedText = "", + text: AnyFormattedText = "", + ok_text: str = "Ok", + style: Optional[BaseStyle] = None, +) -> Application[None]: + """ + Display a simple message box and wait until the user presses enter. + """ + dialog = Dialog( + title=title, + body=Label(text=text, dont_extend_height=True), + buttons=[Button(text=ok_text, handler=_return_none)], + with_background=True, + ) + + return _create_app(dialog, style) + + +def radiolist_dialog( + title: AnyFormattedText = "", + text: AnyFormattedText = "", + ok_text: str = "Ok", + cancel_text: str = "Cancel", + values: Optional[Sequence[Tuple[_T, AnyFormattedText]]] = None, + default: Optional[_T] = None, + style: Optional[BaseStyle] = None, +) -> Application[_T]: + """ + Display a simple list of element the user can choose amongst. + + Only one element can be selected at a time using Arrow keys and Enter. + The focus can be moved between the list and the Ok/Cancel button with tab. + """ + if values is None: + values = [] + + def ok_handler() -> None: + get_app().exit(result=radio_list.current_value) + + radio_list = RadioList(values=values, default=default) + + dialog = Dialog( + title=title, + body=HSplit( + [Label(text=text, dont_extend_height=True), radio_list], + padding=1, + ), + buttons=[ + Button(text=ok_text, handler=ok_handler), + Button(text=cancel_text, handler=_return_none), + ], + with_background=True, + ) + + return _create_app(dialog, style) + + +def checkboxlist_dialog( + title: AnyFormattedText = "", + text: AnyFormattedText = "", + ok_text: str = "Ok", + cancel_text: str = "Cancel", + values: Optional[Sequence[Tuple[_T, AnyFormattedText]]] = None, + default_values: Optional[Sequence[_T]] = None, + style: Optional[BaseStyle] = None, +) -> Application[List[_T]]: + """ + Display a simple list of element the user can choose multiple values amongst. + + Several elements can be selected at a time using Arrow keys and Enter. + The focus can be moved between the list and the Ok/Cancel button with tab. + """ + if values is None: + values = [] + + def ok_handler() -> None: + get_app().exit(result=cb_list.current_values) + + cb_list = CheckboxList(values=values, default_values=default_values) + + dialog = Dialog( + title=title, + body=HSplit( + [Label(text=text, dont_extend_height=True), cb_list], + padding=1, + ), + buttons=[ + Button(text=ok_text, handler=ok_handler), + Button(text=cancel_text, handler=_return_none), + ], + with_background=True, + ) + + return _create_app(dialog, style) + + +def progress_dialog( + title: AnyFormattedText = "", + text: AnyFormattedText = "", + run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = ( + lambda *a: None + ), + style: Optional[BaseStyle] = None, +) -> Application[None]: + """ + :param run_callback: A function that receives as input a `set_percentage` + function and it does the work. + """ + loop = get_event_loop() + progressbar = ProgressBar() + text_area = TextArea( + focusable=False, + # Prefer this text area as big as possible, to avoid having a window + # that keeps resizing when we add text to it. + height=D(preferred=10**10), + ) + + dialog = Dialog( + body=HSplit( + [ + Box(Label(text=text)), + Box(text_area, padding=D.exact(1)), + progressbar, + ] + ), + title=title, + with_background=True, + ) + app = _create_app(dialog, style) + + def set_percentage(value: int) -> None: + progressbar.percentage = int(value) + app.invalidate() + + def log_text(text: str) -> None: + loop.call_soon_threadsafe(text_area.buffer.insert_text, text) + app.invalidate() + + # Run the callback in the executor. When done, set a return value for the + # UI, so that it quits. + def start() -> None: + try: + run_callback(set_percentage, log_text) + finally: + app.exit() + + def pre_run() -> None: + run_in_executor_with_context(start) + + app.pre_run_callables.append(pre_run) + + return app + + +def _create_app(dialog: AnyContainer, style: Optional[BaseStyle]) -> Application[Any]: + # Key bindings. + bindings = KeyBindings() + bindings.add("tab")(focus_next) + bindings.add("s-tab")(focus_previous) + + return Application( + layout=Layout(dialog), + key_bindings=merge_key_bindings([load_key_bindings(), bindings]), + mouse_support=True, + style=style, + full_screen=True, + ) + + +def _return_none() -> None: + "Button handler that returns None." + get_app().exit() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__init__.py new file mode 100644 index 0000000..7d0fbb5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__init__.py @@ -0,0 +1,31 @@ +from .base import ProgressBar, ProgressBarCounter +from .formatters import ( + Bar, + Formatter, + IterationsPerSecond, + Label, + Percentage, + Progress, + Rainbow, + SpinningWheel, + Text, + TimeElapsed, + TimeLeft, +) + +__all__ = [ + "ProgressBar", + "ProgressBarCounter", + # Formatters. + "Formatter", + "Text", + "Label", + "Percentage", + "Bar", + "Progress", + "TimeElapsed", + "TimeLeft", + "IterationsPerSecond", + "SpinningWheel", + "Rainbow", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..fe14df3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..11936d8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/formatters.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/formatters.cpython-38.pyc new file mode 100644 index 0000000..6316932 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/__pycache__/formatters.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/base.py new file mode 100644 index 0000000..c22507e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/base.py @@ -0,0 +1,438 @@ +""" +Progress bar implementation on top of prompt_toolkit. + +:: + + with ProgressBar(...) as pb: + for item in pb(data): + ... +""" +import datetime +import functools +import os +import signal +import threading +import traceback +from asyncio import new_event_loop, set_event_loop +from typing import ( + Generic, + Iterable, + Iterator, + List, + Optional, + Sequence, + Sized, + TextIO, + TypeVar, + cast, +) + +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app_session +from prompt_toolkit.eventloop import get_event_loop +from prompt_toolkit.filters import Condition, is_done, renderer_height_is_known +from prompt_toolkit.formatted_text import ( + AnyFormattedText, + StyleAndTextTuples, + to_formatted_text, +) +from prompt_toolkit.input import Input +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.layout import ( + ConditionalContainer, + FormattedTextControl, + HSplit, + Layout, + VSplit, + Window, +) +from prompt_toolkit.layout.controls import UIContent, UIControl +from prompt_toolkit.layout.dimension import AnyDimension, D +from prompt_toolkit.output import ColorDepth, Output +from prompt_toolkit.styles import BaseStyle + +from .formatters import Formatter, create_default_formatters + +try: + import contextvars +except ImportError: + from prompt_toolkit.eventloop import dummy_contextvars + + contextvars = dummy_contextvars # type: ignore + + +__all__ = ["ProgressBar"] + +E = KeyPressEvent + +_SIGWINCH = getattr(signal, "SIGWINCH", None) + + +def create_key_bindings() -> KeyBindings: + """ + Key bindings handled by the progress bar. + (The main thread is not supposed to handle any key bindings.) + """ + kb = KeyBindings() + + @kb.add("c-l") + def _clear(event: E) -> None: + event.app.renderer.clear() + + @kb.add("c-c") + def _interrupt(event: E) -> None: + # Send KeyboardInterrupt to the main thread. + os.kill(os.getpid(), signal.SIGINT) + + return kb + + +_T = TypeVar("_T") + + +class ProgressBar: + """ + Progress bar context manager. + + Usage :: + + with ProgressBar(...) as pb: + for item in pb(data): + ... + + :param title: Text to be displayed above the progress bars. This can be a + callable or formatted text as well. + :param formatters: List of :class:`.Formatter` instances. + :param bottom_toolbar: Text to be displayed in the bottom toolbar. This + can be a callable or formatted text. + :param style: :class:`prompt_toolkit.styles.BaseStyle` instance. + :param key_bindings: :class:`.KeyBindings` instance. + :param file: The file object used for rendering, by default `sys.stderr` is used. + + :param color_depth: `prompt_toolkit` `ColorDepth` instance. + :param output: :class:`~prompt_toolkit.output.Output` instance. + :param input: :class:`~prompt_toolkit.input.Input` instance. + """ + + def __init__( + self, + title: AnyFormattedText = None, + formatters: Optional[Sequence[Formatter]] = None, + bottom_toolbar: AnyFormattedText = None, + style: Optional[BaseStyle] = None, + key_bindings: Optional[KeyBindings] = None, + file: Optional[TextIO] = None, + color_depth: Optional[ColorDepth] = None, + output: Optional[Output] = None, + input: Optional[Input] = None, + ) -> None: + + self.title = title + self.formatters = formatters or create_default_formatters() + self.bottom_toolbar = bottom_toolbar + self.counters: List[ProgressBarCounter[object]] = [] + self.style = style + self.key_bindings = key_bindings + + # Note that we use __stderr__ as default error output, because that + # works best with `patch_stdout`. + self.color_depth = color_depth + self.output = output or get_app_session().output + self.input = input or get_app_session().input + + self._thread: Optional[threading.Thread] = None + + self._loop = get_event_loop() + self._app_loop = new_event_loop() + self._has_sigwinch = False + self._app_started = threading.Event() + + def __enter__(self) -> "ProgressBar": + # Create UI Application. + title_toolbar = ConditionalContainer( + Window( + FormattedTextControl(lambda: self.title), + height=1, + style="class:progressbar,title", + ), + filter=Condition(lambda: self.title is not None), + ) + + bottom_toolbar = ConditionalContainer( + Window( + FormattedTextControl( + lambda: self.bottom_toolbar, style="class:bottom-toolbar.text" + ), + style="class:bottom-toolbar", + height=1, + ), + filter=~is_done + & renderer_height_is_known + & Condition(lambda: self.bottom_toolbar is not None), + ) + + def width_for_formatter(formatter: Formatter) -> AnyDimension: + # Needs to be passed as callable (partial) to the 'width' + # parameter, because we want to call it on every resize. + return formatter.get_width(progress_bar=self) + + progress_controls = [ + Window( + content=_ProgressControl(self, f), + width=functools.partial(width_for_formatter, f), + ) + for f in self.formatters + ] + + self.app: Application[None] = Application( + min_redraw_interval=0.05, + layout=Layout( + HSplit( + [ + title_toolbar, + VSplit( + progress_controls, + height=lambda: D( + preferred=len(self.counters), max=len(self.counters) + ), + ), + Window(), + bottom_toolbar, + ] + ) + ), + style=self.style, + key_bindings=self.key_bindings, + refresh_interval=0.3, + color_depth=self.color_depth, + output=self.output, + input=self.input, + ) + + # Run application in different thread. + def run() -> None: + set_event_loop(self._app_loop) + try: + self.app.run(pre_run=self._app_started.set) + except BaseException as e: + traceback.print_exc() + print(e) + + ctx: contextvars.Context = contextvars.copy_context() + + self._thread = threading.Thread(target=ctx.run, args=(run,)) + self._thread.start() + + return self + + def __exit__(self, *a: object) -> None: + # Wait for the app to be started. Make sure we don't quit earlier, + # otherwise `self.app.exit` won't terminate the app because + # `self.app.future` has not yet been set. + self._app_started.wait() + + # Quit UI application. + if self.app.is_running: + self._app_loop.call_soon_threadsafe(self.app.exit) + + if self._thread is not None: + self._thread.join() + self._app_loop.close() + + def __call__( + self, + data: Optional[Iterable[_T]] = None, + label: AnyFormattedText = "", + remove_when_done: bool = False, + total: Optional[int] = None, + ) -> "ProgressBarCounter[_T]": + """ + Start a new counter. + + :param label: Title text or description for this progress. (This can be + formatted text as well). + :param remove_when_done: When `True`, hide this progress bar. + :param total: Specify the maximum value if it can't be calculated by + calling ``len``. + """ + counter = ProgressBarCounter( + self, data, label=label, remove_when_done=remove_when_done, total=total + ) + self.counters.append(counter) + return counter + + def invalidate(self) -> None: + self.app.invalidate() + + +class _ProgressControl(UIControl): + """ + User control for the progress bar. + """ + + def __init__(self, progress_bar: ProgressBar, formatter: Formatter) -> None: + self.progress_bar = progress_bar + self.formatter = formatter + self._key_bindings = create_key_bindings() + + def create_content(self, width: int, height: int) -> UIContent: + items: List[StyleAndTextTuples] = [] + + for pr in self.progress_bar.counters: + try: + text = self.formatter.format(self.progress_bar, pr, width) + except BaseException: + traceback.print_exc() + text = "ERROR" + + items.append(to_formatted_text(text)) + + def get_line(i: int) -> StyleAndTextTuples: + return items[i] + + return UIContent(get_line=get_line, line_count=len(items), show_cursor=False) + + def is_focusable(self) -> bool: + return True # Make sure that the key bindings work. + + def get_key_bindings(self) -> KeyBindings: + return self._key_bindings + + +_CounterItem = TypeVar("_CounterItem", covariant=True) + + +class ProgressBarCounter(Generic[_CounterItem]): + """ + An individual counter (A progress bar can have multiple counters). + """ + + def __init__( + self, + progress_bar: ProgressBar, + data: Optional[Iterable[_CounterItem]] = None, + label: AnyFormattedText = "", + remove_when_done: bool = False, + total: Optional[int] = None, + ) -> None: + + self.start_time = datetime.datetime.now() + self.stop_time: Optional[datetime.datetime] = None + self.progress_bar = progress_bar + self.data = data + self.items_completed = 0 + self.label = label + self.remove_when_done = remove_when_done + self._done = False + self.total: Optional[int] + + if total is None: + try: + self.total = len(cast(Sized, data)) + except TypeError: + self.total = None # We don't know the total length. + else: + self.total = total + + def __iter__(self) -> Iterator[_CounterItem]: + if self.data is not None: + try: + for item in self.data: + yield item + self.item_completed() + + # Only done if we iterate to the very end. + self.done = True + finally: + # Ensure counter has stopped even if we did not iterate to the + # end (e.g. break or exceptions). + self.stopped = True + else: + raise NotImplementedError("No data defined to iterate over.") + + def item_completed(self) -> None: + """ + Start handling the next item. + + (Can be called manually in case we don't have a collection to loop through.) + """ + self.items_completed += 1 + self.progress_bar.invalidate() + + @property + def done(self) -> bool: + """Whether a counter has been completed. + + Done counter have been stopped (see stopped) and removed depending on + remove_when_done value. + + Contrast this with stopped. A stopped counter may be terminated before + 100% completion. A done counter has reached its 100% completion. + """ + return self._done + + @done.setter + def done(self, value: bool) -> None: + self._done = value + self.stopped = value + + if value and self.remove_when_done: + self.progress_bar.counters.remove(self) + + @property + def stopped(self) -> bool: + """Whether a counter has been stopped. + + Stopped counters no longer have increasing time_elapsed. This distinction is + also used to prevent the Bar formatter with unknown totals from continuing to run. + + A stopped counter (but not done) can be used to signal that a given counter has + encountered an error but allows other counters to continue + (e.g. download X of Y failed). Given how only done counters are removed + (see remove_when_done) this can help aggregate failures from a large number of + successes. + + Contrast this with done. A done counter has reached its 100% completion. + A stopped counter may be terminated before 100% completion. + """ + return self.stop_time is not None + + @stopped.setter + def stopped(self, value: bool) -> None: + if value: + # This counter has not already been stopped. + if not self.stop_time: + self.stop_time = datetime.datetime.now() + else: + # Clearing any previously set stop_time. + self.stop_time = None + + @property + def percentage(self) -> float: + if self.total is None: + return 0 + else: + return self.items_completed * 100 / max(self.total, 1) + + @property + def time_elapsed(self) -> datetime.timedelta: + """ + Return how much time has been elapsed since the start. + """ + if self.stop_time is None: + return datetime.datetime.now() - self.start_time + else: + return self.stop_time - self.start_time + + @property + def time_left(self) -> Optional[datetime.timedelta]: + """ + Timedelta representing the time left. + """ + if self.total is None or not self.percentage: + return None + elif self.done or self.stopped: + return datetime.timedelta(0) + else: + return self.time_elapsed * (100 - self.percentage) / self.percentage diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/formatters.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/formatters.py new file mode 100644 index 0000000..1383d7a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/progress_bar/formatters.py @@ -0,0 +1,436 @@ +""" +Formatter classes for the progress bar. +Each progress bar consists of a list of these formatters. +""" +import datetime +import time +from abc import ABCMeta, abstractmethod +from typing import TYPE_CHECKING, List, Tuple + +from prompt_toolkit.formatted_text import ( + HTML, + AnyFormattedText, + StyleAndTextTuples, + to_formatted_text, +) +from prompt_toolkit.formatted_text.utils import fragment_list_width +from prompt_toolkit.layout.dimension import AnyDimension, D +from prompt_toolkit.layout.utils import explode_text_fragments +from prompt_toolkit.utils import get_cwidth + +if TYPE_CHECKING: + from .base import ProgressBar, ProgressBarCounter + +__all__ = [ + "Formatter", + "Text", + "Label", + "Percentage", + "Bar", + "Progress", + "TimeElapsed", + "TimeLeft", + "IterationsPerSecond", + "SpinningWheel", + "Rainbow", + "create_default_formatters", +] + + +class Formatter(metaclass=ABCMeta): + """ + Base class for any formatter. + """ + + @abstractmethod + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + pass + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + return D() + + +class Text(Formatter): + """ + Display plain text. + """ + + def __init__(self, text: AnyFormattedText, style: str = "") -> None: + self.text = to_formatted_text(text, style=style) + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + return self.text + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + return fragment_list_width(self.text) + + +class Label(Formatter): + """ + Display the name of the current task. + + :param width: If a `width` is given, use this width. Scroll the text if it + doesn't fit in this width. + :param suffix: String suffix to be added after the task name, e.g. ': '. + If no task name was given, no suffix will be added. + """ + + def __init__(self, width: AnyDimension = None, suffix: str = "") -> None: + self.width = width + self.suffix = suffix + + def _add_suffix(self, label: AnyFormattedText) -> StyleAndTextTuples: + label = to_formatted_text(label, style="class:label") + return label + [("", self.suffix)] + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + label = self._add_suffix(progress.label) + cwidth = fragment_list_width(label) + + if cwidth > width: + # It doesn't fit -> scroll task name. + label = explode_text_fragments(label) + max_scroll = cwidth - width + current_scroll = int(time.time() * 3 % max_scroll) + label = label[current_scroll:] + + return label + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + if self.width: + return self.width + + all_labels = [self._add_suffix(c.label) for c in progress_bar.counters] + if all_labels: + max_widths = max(fragment_list_width(l) for l in all_labels) + return D(preferred=max_widths, max=max_widths) + else: + return D() + + +class Percentage(Formatter): + """ + Display the progress as a percentage. + """ + + template = "{percentage:>5}%" + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + return HTML(self.template).format(percentage=round(progress.percentage, 1)) + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + return D.exact(6) + + +class Bar(Formatter): + """ + Display the progress bar itself. + """ + + template = "{start}{bar_a}{bar_b}{bar_c}{end}" + + def __init__( + self, + start: str = "[", + end: str = "]", + sym_a: str = "=", + sym_b: str = ">", + sym_c: str = " ", + unknown: str = "#", + ) -> None: + + assert len(sym_a) == 1 and get_cwidth(sym_a) == 1 + assert len(sym_c) == 1 and get_cwidth(sym_c) == 1 + + self.start = start + self.end = end + self.sym_a = sym_a + self.sym_b = sym_b + self.sym_c = sym_c + self.unknown = unknown + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + if progress.done or progress.total or progress.stopped: + sym_a, sym_b, sym_c = self.sym_a, self.sym_b, self.sym_c + + # Compute pb_a based on done, total, or stopped states. + if progress.done: + # 100% completed irrelevant of how much was actually marked as completed. + percent = 1.0 + else: + # Show percentage completed. + percent = progress.percentage / 100 + else: + # Total is unknown and bar is still running. + sym_a, sym_b, sym_c = self.sym_c, self.unknown, self.sym_c + + # Compute percent based on the time. + percent = time.time() * 20 % 100 / 100 + + # Subtract left, sym_b, and right. + width -= get_cwidth(self.start + sym_b + self.end) + + # Scale percent by width + pb_a = int(percent * width) + bar_a = sym_a * pb_a + bar_b = sym_b + bar_c = sym_c * (width - pb_a) + + return HTML(self.template).format( + start=self.start, end=self.end, bar_a=bar_a, bar_b=bar_b, bar_c=bar_c + ) + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + return D(min=9) + + +class Progress(Formatter): + """ + Display the progress as text. E.g. "8/20" + """ + + template = "{current:>3}/{total:>3}" + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + return HTML(self.template).format( + current=progress.items_completed, total=progress.total or "?" + ) + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + all_lengths = [ + len("{:>3}".format(c.total or "?")) for c in progress_bar.counters + ] + all_lengths.append(1) + return D.exact(max(all_lengths) * 2 + 1) + + +def _format_timedelta(timedelta: datetime.timedelta) -> str: + """ + Return hh:mm:ss, or mm:ss if the amount of hours is zero. + """ + result = f"{timedelta}".split(".")[0] + if result.startswith("0:"): + result = result[2:] + return result + + +class TimeElapsed(Formatter): + """ + Display the elapsed time. + """ + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + text = _format_timedelta(progress.time_elapsed).rjust(width) + return HTML("{time_elapsed}").format( + time_elapsed=text + ) + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + all_values = [ + len(_format_timedelta(c.time_elapsed)) for c in progress_bar.counters + ] + if all_values: + return max(all_values) + return 0 + + +class TimeLeft(Formatter): + """ + Display the time left. + """ + + template = "{time_left}" + unknown = "?:??:??" + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + time_left = progress.time_left + if time_left is not None: + formatted_time_left = _format_timedelta(time_left) + else: + formatted_time_left = self.unknown + + return HTML(self.template).format(time_left=formatted_time_left.rjust(width)) + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + all_values = [ + len(_format_timedelta(c.time_left)) if c.time_left is not None else 7 + for c in progress_bar.counters + ] + if all_values: + return max(all_values) + return 0 + + +class IterationsPerSecond(Formatter): + """ + Display the iterations per second. + """ + + template = ( + "{iterations_per_second:.2f}" + ) + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + value = progress.items_completed / progress.time_elapsed.total_seconds() + return HTML(self.template.format(iterations_per_second=value)) + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + all_values = [ + len(f"{c.items_completed / c.time_elapsed.total_seconds():.2f}") + for c in progress_bar.counters + ] + if all_values: + return max(all_values) + return 0 + + +class SpinningWheel(Formatter): + """ + Display a spinning wheel. + """ + + characters = r"/-\|" + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + index = int(time.time() * 3) % len(self.characters) + return HTML("{0}").format( + self.characters[index] + ) + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + return D.exact(1) + + +def _hue_to_rgb(hue: float) -> Tuple[int, int, int]: + """ + Take hue between 0 and 1, return (r, g, b). + """ + i = int(hue * 6.0) + f = (hue * 6.0) - i + + q = int(255 * (1.0 - f)) + t = int(255 * (1.0 - (1.0 - f))) + + i %= 6 + + return [ + (255, t, 0), + (q, 255, 0), + (0, 255, t), + (0, q, 255), + (t, 0, 255), + (255, 0, q), + ][i] + + +class Rainbow(Formatter): + """ + For the fun. Add rainbow colors to any of the other formatters. + """ + + colors = ["#%.2x%.2x%.2x" % _hue_to_rgb(h / 100.0) for h in range(0, 100)] + + def __init__(self, formatter: Formatter) -> None: + self.formatter = formatter + + def format( + self, + progress_bar: "ProgressBar", + progress: "ProgressBarCounter[object]", + width: int, + ) -> AnyFormattedText: + + # Get formatted text from nested formatter, and explode it in + # text/style tuples. + result = self.formatter.format(progress_bar, progress, width) + result = explode_text_fragments(to_formatted_text(result)) + + # Insert colors. + result2: StyleAndTextTuples = [] + shift = int(time.time() * 3) % len(self.colors) + + for i, (style, text, *_) in enumerate(result): + result2.append( + (style + " " + self.colors[(i + shift) % len(self.colors)], text) + ) + return result2 + + def get_width(self, progress_bar: "ProgressBar") -> AnyDimension: + return self.formatter.get_width(progress_bar) + + +def create_default_formatters() -> List[Formatter]: + """ + Return the list of default formatters. + """ + return [ + Label(), + Text(" "), + Percentage(), + Text(" "), + Bar(), + Text(" "), + Progress(), + Text(" "), + Text("eta [", style="class:time-left"), + TimeLeft(), + Text("]", style="class:time-left"), + Text(" "), + ] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/prompt.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/prompt.py new file mode 100644 index 0000000..a8d8a58 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/prompt.py @@ -0,0 +1,1498 @@ +""" +Line editing functionality. +--------------------------- + +This provides a UI for a line input, similar to GNU Readline, libedit and +linenoise. + +Either call the `prompt` function for every line input. Or create an instance +of the :class:`.PromptSession` class and call the `prompt` method from that +class. In the second case, we'll have a 'session' that keeps all the state like +the history in between several calls. + +There is a lot of overlap between the arguments taken by the `prompt` function +and the `PromptSession` (like `completer`, `style`, etcetera). There we have +the freedom to decide which settings we want for the whole 'session', and which +we want for an individual `prompt`. + +Example:: + + # Simple `prompt` call. + result = prompt('Say something: ') + + # Using a 'session'. + s = PromptSession() + result = s.prompt('Say something: ') +""" +from contextlib import contextmanager +from enum import Enum +from functools import partial +from typing import ( + TYPE_CHECKING, + Callable, + Generic, + Iterator, + List, + Optional, + Tuple, + TypeVar, + Union, + cast, +) + +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.clipboard import Clipboard, DynamicClipboard, InMemoryClipboard +from prompt_toolkit.completion import Completer, DynamicCompleter, ThreadedCompleter +from prompt_toolkit.cursor_shapes import ( + AnyCursorShapeConfig, + CursorShapeConfig, + DynamicCursorShapeConfig, +) +from prompt_toolkit.document import Document +from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode +from prompt_toolkit.eventloop import get_event_loop +from prompt_toolkit.filters import ( + Condition, + FilterOrBool, + has_arg, + has_focus, + is_done, + is_true, + renderer_height_is_known, + to_filter, +) +from prompt_toolkit.formatted_text import ( + AnyFormattedText, + StyleAndTextTuples, + fragment_list_to_text, + merge_formatted_text, + to_formatted_text, +) +from prompt_toolkit.history import History, InMemoryHistory +from prompt_toolkit.input.base import Input +from prompt_toolkit.key_binding.bindings.auto_suggest import load_auto_suggest_bindings +from prompt_toolkit.key_binding.bindings.completion import ( + display_completions_like_readline, +) +from prompt_toolkit.key_binding.bindings.open_in_editor import ( + load_open_in_editor_bindings, +) +from prompt_toolkit.key_binding.key_bindings import ( + ConditionalKeyBindings, + DynamicKeyBindings, + KeyBindings, + KeyBindingsBase, + merge_key_bindings, +) +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout import Float, FloatContainer, HSplit, Window +from prompt_toolkit.layout.containers import ConditionalContainer, WindowAlign +from prompt_toolkit.layout.controls import ( + BufferControl, + FormattedTextControl, + SearchBufferControl, +) +from prompt_toolkit.layout.dimension import Dimension +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu +from prompt_toolkit.layout.processors import ( + AfterInput, + AppendAutoSuggestion, + ConditionalProcessor, + DisplayMultipleCursors, + DynamicProcessor, + HighlightIncrementalSearchProcessor, + HighlightSelectionProcessor, + PasswordProcessor, + Processor, + ReverseSearchProcessor, + merge_processors, +) +from prompt_toolkit.layout.utils import explode_text_fragments +from prompt_toolkit.lexers import DynamicLexer, Lexer +from prompt_toolkit.output import ColorDepth, DummyOutput, Output +from prompt_toolkit.styles import ( + BaseStyle, + ConditionalStyleTransformation, + DynamicStyle, + DynamicStyleTransformation, + StyleTransformation, + SwapLightAndDarkStyleTransformation, + merge_style_transformations, +) +from prompt_toolkit.utils import ( + get_cwidth, + is_dumb_terminal, + suspend_to_background_supported, + to_str, +) +from prompt_toolkit.validation import DynamicValidator, Validator +from prompt_toolkit.widgets.toolbars import ( + SearchToolbar, + SystemToolbar, + ValidationToolbar, +) + +if TYPE_CHECKING: + from prompt_toolkit.formatted_text.base import MagicFormattedText + +__all__ = [ + "PromptSession", + "prompt", + "confirm", + "create_confirm_session", # Used by '_display_completions_like_readline'. + "CompleteStyle", +] + +_StyleAndTextTuplesCallable = Callable[[], StyleAndTextTuples] +E = KeyPressEvent + + +def _split_multiline_prompt( + get_prompt_text: _StyleAndTextTuplesCallable, +) -> Tuple[ + Callable[[], bool], _StyleAndTextTuplesCallable, _StyleAndTextTuplesCallable +]: + """ + Take a `get_prompt_text` function and return three new functions instead. + One that tells whether this prompt consists of multiple lines; one that + returns the fragments to be shown on the lines above the input; and another + one with the fragments to be shown at the first line of the input. + """ + + def has_before_fragments() -> bool: + for fragment, char, *_ in get_prompt_text(): + if "\n" in char: + return True + return False + + def before() -> StyleAndTextTuples: + result: StyleAndTextTuples = [] + found_nl = False + for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())): + if found_nl: + result.insert(0, (fragment, char)) + elif char == "\n": + found_nl = True + return result + + def first_input_line() -> StyleAndTextTuples: + result: StyleAndTextTuples = [] + for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())): + if char == "\n": + break + else: + result.insert(0, (fragment, char)) + return result + + return has_before_fragments, before, first_input_line + + +class _RPrompt(Window): + """ + The prompt that is displayed on the right side of the Window. + """ + + def __init__(self, text: AnyFormattedText) -> None: + super().__init__( + FormattedTextControl(text=text), + align=WindowAlign.RIGHT, + style="class:rprompt", + ) + + +class CompleteStyle(str, Enum): + """ + How to display autocompletions for the prompt. + """ + + value: str + + COLUMN = "COLUMN" + MULTI_COLUMN = "MULTI_COLUMN" + READLINE_LIKE = "READLINE_LIKE" + + +# Formatted text for the continuation prompt. It's the same like other +# formatted text, except that if it's a callable, it takes three arguments. +PromptContinuationText = Union[ + str, + "MagicFormattedText", + StyleAndTextTuples, + # (prompt_width, line_number, wrap_count) -> AnyFormattedText. + Callable[[int, int, int], AnyFormattedText], +] + +_T = TypeVar("_T") + + +class PromptSession(Generic[_T]): + """ + PromptSession for a prompt application, which can be used as a GNU Readline + replacement. + + This is a wrapper around a lot of ``prompt_toolkit`` functionality and can + be a replacement for `raw_input`. + + All parameters that expect "formatted text" can take either just plain text + (a unicode object), a list of ``(style_str, text)`` tuples or an HTML object. + + Example usage:: + + s = PromptSession(message='>') + text = s.prompt() + + :param message: Plain text or formatted text to be shown before the prompt. + This can also be a callable that returns formatted text. + :param multiline: `bool` or :class:`~prompt_toolkit.filters.Filter`. + When True, prefer a layout that is more adapted for multiline input. + Text after newlines is automatically indented, and search/arg input is + shown below the input, instead of replacing the prompt. + :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.Filter`. + When True (the default), automatically wrap long lines instead of + scrolling horizontally. + :param is_password: Show asterisks instead of the actual typed characters. + :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``. + :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``. + :param complete_while_typing: `bool` or + :class:`~prompt_toolkit.filters.Filter`. Enable autocompletion while + typing. + :param validate_while_typing: `bool` or + :class:`~prompt_toolkit.filters.Filter`. Enable input validation while + typing. + :param enable_history_search: `bool` or + :class:`~prompt_toolkit.filters.Filter`. Enable up-arrow parting + string matching. + :param search_ignore_case: + :class:`~prompt_toolkit.filters.Filter`. Search case insensitive. + :param lexer: :class:`~prompt_toolkit.lexers.Lexer` to be used for the + syntax highlighting. + :param validator: :class:`~prompt_toolkit.validation.Validator` instance + for input validation. + :param completer: :class:`~prompt_toolkit.completion.Completer` instance + for input completion. + :param complete_in_thread: `bool` or + :class:`~prompt_toolkit.filters.Filter`. Run the completer code in a + background thread in order to avoid blocking the user interface. + For ``CompleteStyle.READLINE_LIKE``, this setting has no effect. There + we always run the completions in the main thread. + :param reserve_space_for_menu: Space to be reserved for displaying the menu. + (0 means that no space needs to be reserved.) + :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` + instance for input suggestions. + :param style: :class:`.Style` instance for the color scheme. + :param include_default_pygments_style: `bool` or + :class:`~prompt_toolkit.filters.Filter`. Tell whether the default + styling for Pygments lexers has to be included. By default, this is + true, but it is recommended to be disabled if another Pygments style is + passed as the `style` argument, otherwise, two Pygments styles will be + merged. + :param style_transformation: + :class:`~prompt_toolkit.style.StyleTransformation` instance. + :param swap_light_and_dark_colors: `bool` or + :class:`~prompt_toolkit.filters.Filter`. When enabled, apply + :class:`~prompt_toolkit.style.SwapLightAndDarkStyleTransformation`. + This is useful for switching between dark and light terminal + backgrounds. + :param enable_system_prompt: `bool` or + :class:`~prompt_toolkit.filters.Filter`. Pressing Meta+'!' will show + a system prompt. + :param enable_suspend: `bool` or :class:`~prompt_toolkit.filters.Filter`. + Enable Control-Z style suspension. + :param enable_open_in_editor: `bool` or + :class:`~prompt_toolkit.filters.Filter`. Pressing 'v' in Vi mode or + C-X C-E in emacs mode will open an external editor. + :param history: :class:`~prompt_toolkit.history.History` instance. + :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` instance. + (e.g. :class:`~prompt_toolkit.clipboard.InMemoryClipboard`) + :param rprompt: Text or formatted text to be displayed on the right side. + This can also be a callable that returns (formatted) text. + :param bottom_toolbar: Formatted text or callable which is supposed to + return formatted text. + :param prompt_continuation: Text that needs to be displayed for a multiline + prompt continuation. This can either be formatted text or a callable + that takes a `prompt_width`, `line_number` and `wrap_count` as input + and returns formatted text. When this is `None` (the default), then + `prompt_width` spaces will be used. + :param complete_style: ``CompleteStyle.COLUMN``, + ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``. + :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter` + to enable mouse support. + :param placeholder: Text to be displayed when no input has been given + yet. Unlike the `default` parameter, this won't be returned as part of + the output ever. This can be formatted text or a callable that returns + formatted text. + :param refresh_interval: (number; in seconds) When given, refresh the UI + every so many seconds. + :param input: `Input` object. (Note that the preferred way to change the + input/output is by creating an `AppSession`.) + :param output: `Output` object. + """ + + _fields = ( + "message", + "lexer", + "completer", + "complete_in_thread", + "is_password", + "editing_mode", + "key_bindings", + "is_password", + "bottom_toolbar", + "style", + "style_transformation", + "swap_light_and_dark_colors", + "color_depth", + "cursor", + "include_default_pygments_style", + "rprompt", + "multiline", + "prompt_continuation", + "wrap_lines", + "enable_history_search", + "search_ignore_case", + "complete_while_typing", + "validate_while_typing", + "complete_style", + "mouse_support", + "auto_suggest", + "clipboard", + "validator", + "refresh_interval", + "input_processors", + "placeholder", + "enable_system_prompt", + "enable_suspend", + "enable_open_in_editor", + "reserve_space_for_menu", + "tempfile_suffix", + "tempfile", + ) + + def __init__( + self, + message: AnyFormattedText = "", + *, + multiline: FilterOrBool = False, + wrap_lines: FilterOrBool = True, + is_password: FilterOrBool = False, + vi_mode: bool = False, + editing_mode: EditingMode = EditingMode.EMACS, + complete_while_typing: FilterOrBool = True, + validate_while_typing: FilterOrBool = True, + enable_history_search: FilterOrBool = False, + search_ignore_case: FilterOrBool = False, + lexer: Optional[Lexer] = None, + enable_system_prompt: FilterOrBool = False, + enable_suspend: FilterOrBool = False, + enable_open_in_editor: FilterOrBool = False, + validator: Optional[Validator] = None, + completer: Optional[Completer] = None, + complete_in_thread: bool = False, + reserve_space_for_menu: int = 8, + complete_style: CompleteStyle = CompleteStyle.COLUMN, + auto_suggest: Optional[AutoSuggest] = None, + style: Optional[BaseStyle] = None, + style_transformation: Optional[StyleTransformation] = None, + swap_light_and_dark_colors: FilterOrBool = False, + color_depth: Optional[ColorDepth] = None, + cursor: AnyCursorShapeConfig = None, + include_default_pygments_style: FilterOrBool = True, + history: Optional[History] = None, + clipboard: Optional[Clipboard] = None, + prompt_continuation: Optional[PromptContinuationText] = None, + rprompt: AnyFormattedText = None, + bottom_toolbar: AnyFormattedText = None, + mouse_support: FilterOrBool = False, + input_processors: Optional[List[Processor]] = None, + placeholder: Optional[AnyFormattedText] = None, + key_bindings: Optional[KeyBindingsBase] = None, + erase_when_done: bool = False, + tempfile_suffix: Optional[Union[str, Callable[[], str]]] = ".txt", + tempfile: Optional[Union[str, Callable[[], str]]] = None, + refresh_interval: float = 0, + input: Optional[Input] = None, + output: Optional[Output] = None, + ) -> None: + + history = history or InMemoryHistory() + clipboard = clipboard or InMemoryClipboard() + + # Ensure backwards-compatibility, when `vi_mode` is passed. + if vi_mode: + editing_mode = EditingMode.VI + + # Store all settings in this class. + self._input = input + self._output = output + + # Store attributes. + # (All except 'editing_mode'.) + self.message = message + self.lexer = lexer + self.completer = completer + self.complete_in_thread = complete_in_thread + self.is_password = is_password + self.key_bindings = key_bindings + self.bottom_toolbar = bottom_toolbar + self.style = style + self.style_transformation = style_transformation + self.swap_light_and_dark_colors = swap_light_and_dark_colors + self.color_depth = color_depth + self.cursor = cursor + self.include_default_pygments_style = include_default_pygments_style + self.rprompt = rprompt + self.multiline = multiline + self.prompt_continuation = prompt_continuation + self.wrap_lines = wrap_lines + self.enable_history_search = enable_history_search + self.search_ignore_case = search_ignore_case + self.complete_while_typing = complete_while_typing + self.validate_while_typing = validate_while_typing + self.complete_style = complete_style + self.mouse_support = mouse_support + self.auto_suggest = auto_suggest + self.clipboard = clipboard + self.validator = validator + self.refresh_interval = refresh_interval + self.input_processors = input_processors + self.placeholder = placeholder + self.enable_system_prompt = enable_system_prompt + self.enable_suspend = enable_suspend + self.enable_open_in_editor = enable_open_in_editor + self.reserve_space_for_menu = reserve_space_for_menu + self.tempfile_suffix = tempfile_suffix + self.tempfile = tempfile + + # Create buffers, layout and Application. + self.history = history + self.default_buffer = self._create_default_buffer() + self.search_buffer = self._create_search_buffer() + self.layout = self._create_layout() + self.app = self._create_application(editing_mode, erase_when_done) + + def _dyncond(self, attr_name: str) -> Condition: + """ + Dynamically take this setting from this 'PromptSession' class. + `attr_name` represents an attribute name of this class. Its value + can either be a boolean or a `Filter`. + + This returns something that can be used as either a `Filter` + or `Filter`. + """ + + @Condition + def dynamic() -> bool: + value = cast(FilterOrBool, getattr(self, attr_name)) + return to_filter(value)() + + return dynamic + + def _create_default_buffer(self) -> Buffer: + """ + Create and return the default input buffer. + """ + dyncond = self._dyncond + + # Create buffers list. + def accept(buff: Buffer) -> bool: + """Accept the content of the default buffer. This is called when + the validation succeeds.""" + cast(Application[str], get_app()).exit(result=buff.document.text) + return True # Keep text, we call 'reset' later on. + + return Buffer( + name=DEFAULT_BUFFER, + # Make sure that complete_while_typing is disabled when + # enable_history_search is enabled. (First convert to Filter, + # to avoid doing bitwise operations on bool objects.) + complete_while_typing=Condition( + lambda: is_true(self.complete_while_typing) + and not is_true(self.enable_history_search) + and not self.complete_style == CompleteStyle.READLINE_LIKE + ), + validate_while_typing=dyncond("validate_while_typing"), + enable_history_search=dyncond("enable_history_search"), + validator=DynamicValidator(lambda: self.validator), + completer=DynamicCompleter( + lambda: ThreadedCompleter(self.completer) + if self.complete_in_thread and self.completer + else self.completer + ), + history=self.history, + auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), + accept_handler=accept, + tempfile_suffix=lambda: to_str(self.tempfile_suffix or ""), + tempfile=lambda: to_str(self.tempfile or ""), + ) + + def _create_search_buffer(self) -> Buffer: + return Buffer(name=SEARCH_BUFFER) + + def _create_layout(self) -> Layout: + """ + Create `Layout` for this prompt. + """ + dyncond = self._dyncond + + # Create functions that will dynamically split the prompt. (If we have + # a multiline prompt.) + ( + has_before_fragments, + get_prompt_text_1, + get_prompt_text_2, + ) = _split_multiline_prompt(self._get_prompt) + + default_buffer = self.default_buffer + search_buffer = self.search_buffer + + # Create processors list. + @Condition + def display_placeholder() -> bool: + return self.placeholder is not None and self.default_buffer.text == "" + + all_input_processors = [ + HighlightIncrementalSearchProcessor(), + HighlightSelectionProcessor(), + ConditionalProcessor( + AppendAutoSuggestion(), has_focus(default_buffer) & ~is_done + ), + ConditionalProcessor(PasswordProcessor(), dyncond("is_password")), + DisplayMultipleCursors(), + # Users can insert processors here. + DynamicProcessor(lambda: merge_processors(self.input_processors or [])), + ConditionalProcessor( + AfterInput(lambda: self.placeholder), + filter=display_placeholder, + ), + ] + + # Create bottom toolbars. + bottom_toolbar = ConditionalContainer( + Window( + FormattedTextControl( + lambda: self.bottom_toolbar, style="class:bottom-toolbar.text" + ), + style="class:bottom-toolbar", + dont_extend_height=True, + height=Dimension(min=1), + ), + filter=~is_done + & renderer_height_is_known + & Condition(lambda: self.bottom_toolbar is not None), + ) + + search_toolbar = SearchToolbar( + search_buffer, ignore_case=dyncond("search_ignore_case") + ) + + search_buffer_control = SearchBufferControl( + buffer=search_buffer, + input_processors=[ReverseSearchProcessor()], + ignore_case=dyncond("search_ignore_case"), + ) + + system_toolbar = SystemToolbar( + enable_global_bindings=dyncond("enable_system_prompt") + ) + + def get_search_buffer_control() -> SearchBufferControl: + "Return the UIControl to be focused when searching start." + if is_true(self.multiline): + return search_toolbar.control + else: + return search_buffer_control + + default_buffer_control = BufferControl( + buffer=default_buffer, + search_buffer_control=get_search_buffer_control, + input_processors=all_input_processors, + include_default_input_processors=False, + lexer=DynamicLexer(lambda: self.lexer), + preview_search=True, + ) + + default_buffer_window = Window( + default_buffer_control, + height=self._get_default_buffer_control_height, + get_line_prefix=partial( + self._get_line_prefix, get_prompt_text_2=get_prompt_text_2 + ), + wrap_lines=dyncond("wrap_lines"), + ) + + @Condition + def multi_column_complete_style() -> bool: + return self.complete_style == CompleteStyle.MULTI_COLUMN + + # Build the layout. + layout = HSplit( + [ + # The main input, with completion menus floating on top of it. + FloatContainer( + HSplit( + [ + ConditionalContainer( + Window( + FormattedTextControl(get_prompt_text_1), + dont_extend_height=True, + ), + Condition(has_before_fragments), + ), + ConditionalContainer( + default_buffer_window, + Condition( + lambda: get_app().layout.current_control + != search_buffer_control + ), + ), + ConditionalContainer( + Window(search_buffer_control), + Condition( + lambda: get_app().layout.current_control + == search_buffer_control + ), + ), + ] + ), + [ + # Completion menus. + # NOTE: Especially the multi-column menu needs to be + # transparent, because the shape is not always + # rectangular due to the meta-text below the menu. + Float( + xcursor=True, + ycursor=True, + transparent=True, + content=CompletionsMenu( + max_height=16, + scroll_offset=1, + extra_filter=has_focus(default_buffer) + & ~multi_column_complete_style, + ), + ), + Float( + xcursor=True, + ycursor=True, + transparent=True, + content=MultiColumnCompletionsMenu( + show_meta=True, + extra_filter=has_focus(default_buffer) + & multi_column_complete_style, + ), + ), + # The right prompt. + Float( + right=0, + bottom=0, + hide_when_covering_content=True, + content=_RPrompt(lambda: self.rprompt), + ), + ], + ), + ConditionalContainer(ValidationToolbar(), filter=~is_done), + ConditionalContainer( + system_toolbar, dyncond("enable_system_prompt") & ~is_done + ), + # In multiline mode, we use two toolbars for 'arg' and 'search'. + ConditionalContainer( + Window(FormattedTextControl(self._get_arg_text), height=1), + dyncond("multiline") & has_arg, + ), + ConditionalContainer(search_toolbar, dyncond("multiline") & ~is_done), + bottom_toolbar, + ] + ) + + return Layout(layout, default_buffer_window) + + def _create_application( + self, editing_mode: EditingMode, erase_when_done: bool + ) -> Application[_T]: + """ + Create the `Application` object. + """ + dyncond = self._dyncond + + # Default key bindings. + auto_suggest_bindings = load_auto_suggest_bindings() + open_in_editor_bindings = load_open_in_editor_bindings() + prompt_bindings = self._create_prompt_bindings() + + # Create application + application: Application[_T] = Application( + layout=self.layout, + style=DynamicStyle(lambda: self.style), + style_transformation=merge_style_transformations( + [ + DynamicStyleTransformation(lambda: self.style_transformation), + ConditionalStyleTransformation( + SwapLightAndDarkStyleTransformation(), + dyncond("swap_light_and_dark_colors"), + ), + ] + ), + include_default_pygments_style=dyncond("include_default_pygments_style"), + clipboard=DynamicClipboard(lambda: self.clipboard), + key_bindings=merge_key_bindings( + [ + merge_key_bindings( + [ + auto_suggest_bindings, + ConditionalKeyBindings( + open_in_editor_bindings, + dyncond("enable_open_in_editor") + & has_focus(DEFAULT_BUFFER), + ), + prompt_bindings, + ] + ), + DynamicKeyBindings(lambda: self.key_bindings), + ] + ), + mouse_support=dyncond("mouse_support"), + editing_mode=editing_mode, + erase_when_done=erase_when_done, + reverse_vi_search_direction=True, + color_depth=lambda: self.color_depth, + cursor=DynamicCursorShapeConfig(lambda: self.cursor), + refresh_interval=self.refresh_interval, + input=self._input, + output=self._output, + ) + + # During render time, make sure that we focus the right search control + # (if we are searching). - This could be useful if people make the + # 'multiline' property dynamic. + """ + def on_render(app): + multiline = is_true(self.multiline) + current_control = app.layout.current_control + + if multiline: + if current_control == search_buffer_control: + app.layout.current_control = search_toolbar.control + app.invalidate() + else: + if current_control == search_toolbar.control: + app.layout.current_control = search_buffer_control + app.invalidate() + + app.on_render += on_render + """ + + return application + + def _create_prompt_bindings(self) -> KeyBindings: + """ + Create the KeyBindings for a prompt application. + """ + kb = KeyBindings() + handle = kb.add + default_focused = has_focus(DEFAULT_BUFFER) + + @Condition + def do_accept() -> bool: + return not is_true(self.multiline) and self.app.layout.has_focus( + DEFAULT_BUFFER + ) + + @handle("enter", filter=do_accept & default_focused) + def _accept_input(event: E) -> None: + "Accept input when enter has been pressed." + self.default_buffer.validate_and_handle() + + @Condition + def readline_complete_style() -> bool: + return self.complete_style == CompleteStyle.READLINE_LIKE + + @handle("tab", filter=readline_complete_style & default_focused) + def _complete_like_readline(event: E) -> None: + "Display completions (like Readline)." + display_completions_like_readline(event) + + @handle("c-c", filter=default_focused) + @handle("") + def _keyboard_interrupt(event: E) -> None: + "Abort when Control-C has been pressed." + event.app.exit(exception=KeyboardInterrupt, style="class:aborting") + + @Condition + def ctrl_d_condition() -> bool: + """Ctrl-D binding is only active when the default buffer is selected + and empty.""" + app = get_app() + return ( + app.current_buffer.name == DEFAULT_BUFFER + and not app.current_buffer.text + ) + + @handle("c-d", filter=ctrl_d_condition & default_focused) + def _eof(event: E) -> None: + "Exit when Control-D has been pressed." + event.app.exit(exception=EOFError, style="class:exiting") + + suspend_supported = Condition(suspend_to_background_supported) + + @Condition + def enable_suspend() -> bool: + return to_filter(self.enable_suspend)() + + @handle("c-z", filter=suspend_supported & enable_suspend) + def _suspend(event: E) -> None: + """ + Suspend process to background. + """ + event.app.suspend_to_background() + + return kb + + def prompt( + self, + # When any of these arguments are passed, this value is overwritten + # in this PromptSession. + message: Optional[AnyFormattedText] = None, + # `message` should go first, because people call it as + # positional argument. + *, + editing_mode: Optional[EditingMode] = None, + refresh_interval: Optional[float] = None, + vi_mode: Optional[bool] = None, + lexer: Optional[Lexer] = None, + completer: Optional[Completer] = None, + complete_in_thread: Optional[bool] = None, + is_password: Optional[bool] = None, + key_bindings: Optional[KeyBindingsBase] = None, + bottom_toolbar: Optional[AnyFormattedText] = None, + style: Optional[BaseStyle] = None, + color_depth: Optional[ColorDepth] = None, + cursor: Optional[AnyCursorShapeConfig] = None, + include_default_pygments_style: Optional[FilterOrBool] = None, + style_transformation: Optional[StyleTransformation] = None, + swap_light_and_dark_colors: Optional[FilterOrBool] = None, + rprompt: Optional[AnyFormattedText] = None, + multiline: Optional[FilterOrBool] = None, + prompt_continuation: Optional[PromptContinuationText] = None, + wrap_lines: Optional[FilterOrBool] = None, + enable_history_search: Optional[FilterOrBool] = None, + search_ignore_case: Optional[FilterOrBool] = None, + complete_while_typing: Optional[FilterOrBool] = None, + validate_while_typing: Optional[FilterOrBool] = None, + complete_style: Optional[CompleteStyle] = None, + auto_suggest: Optional[AutoSuggest] = None, + validator: Optional[Validator] = None, + clipboard: Optional[Clipboard] = None, + mouse_support: Optional[FilterOrBool] = None, + input_processors: Optional[List[Processor]] = None, + placeholder: Optional[AnyFormattedText] = None, + reserve_space_for_menu: Optional[int] = None, + enable_system_prompt: Optional[FilterOrBool] = None, + enable_suspend: Optional[FilterOrBool] = None, + enable_open_in_editor: Optional[FilterOrBool] = None, + tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None, + tempfile: Optional[Union[str, Callable[[], str]]] = None, + # Following arguments are specific to the current `prompt()` call. + default: Union[str, Document] = "", + accept_default: bool = False, + pre_run: Optional[Callable[[], None]] = None, + set_exception_handler: bool = True, + in_thread: bool = False, + ) -> _T: + """ + Display the prompt. + + The first set of arguments is a subset of the :class:`~.PromptSession` + class itself. For these, passing in ``None`` will keep the current + values that are active in the session. Passing in a value will set the + attribute for the session, which means that it applies to the current, + but also to the next prompts. + + Note that in order to erase a ``Completer``, ``Validator`` or + ``AutoSuggest``, you can't use ``None``. Instead pass in a + ``DummyCompleter``, ``DummyValidator`` or ``DummyAutoSuggest`` instance + respectively. For a ``Lexer`` you can pass in an empty ``SimpleLexer``. + + Additional arguments, specific for this prompt: + + :param default: The default input text to be shown. (This can be edited + by the user). + :param accept_default: When `True`, automatically accept the default + value without allowing the user to edit the input. + :param pre_run: Callable, called at the start of `Application.run`. + :param in_thread: Run the prompt in a background thread; block the + current thread. This avoids interference with an event loop in the + current thread. Like `Application.run(in_thread=True)`. + + This method will raise ``KeyboardInterrupt`` when control-c has been + pressed (for abort) and ``EOFError`` when control-d has been pressed + (for exit). + """ + # NOTE: We used to create a backup of the PromptSession attributes and + # restore them after exiting the prompt. This code has been + # removed, because it was confusing and didn't really serve a use + # case. (People were changing `Application.editing_mode` + # dynamically and surprised that it was reset after every call.) + + # NOTE 2: YES, this is a lot of repeation below... + # However, it is a very convenient for a user to accept all + # these parameters in this `prompt` method as well. We could + # use `locals()` and `setattr` to avoid the repetition, but + # then we loose the advantage of mypy and pyflakes to be able + # to verify the code. + if message is not None: + self.message = message + if editing_mode is not None: + self.editing_mode = editing_mode + if refresh_interval is not None: + self.refresh_interval = refresh_interval + if vi_mode: + self.editing_mode = EditingMode.VI + if lexer is not None: + self.lexer = lexer + if completer is not None: + self.completer = completer + if complete_in_thread is not None: + self.complete_in_thread = complete_in_thread + if is_password is not None: + self.is_password = is_password + if key_bindings is not None: + self.key_bindings = key_bindings + if bottom_toolbar is not None: + self.bottom_toolbar = bottom_toolbar + if style is not None: + self.style = style + if color_depth is not None: + self.color_depth = color_depth + if cursor is not None: + self.cursor = cursor + if include_default_pygments_style is not None: + self.include_default_pygments_style = include_default_pygments_style + if style_transformation is not None: + self.style_transformation = style_transformation + if swap_light_and_dark_colors is not None: + self.swap_light_and_dark_colors = swap_light_and_dark_colors + if rprompt is not None: + self.rprompt = rprompt + if multiline is not None: + self.multiline = multiline + if prompt_continuation is not None: + self.prompt_continuation = prompt_continuation + if wrap_lines is not None: + self.wrap_lines = wrap_lines + if enable_history_search is not None: + self.enable_history_search = enable_history_search + if search_ignore_case is not None: + self.search_ignore_case = search_ignore_case + if complete_while_typing is not None: + self.complete_while_typing = complete_while_typing + if validate_while_typing is not None: + self.validate_while_typing = validate_while_typing + if complete_style is not None: + self.complete_style = complete_style + if auto_suggest is not None: + self.auto_suggest = auto_suggest + if validator is not None: + self.validator = validator + if clipboard is not None: + self.clipboard = clipboard + if mouse_support is not None: + self.mouse_support = mouse_support + if input_processors is not None: + self.input_processors = input_processors + if placeholder is not None: + self.placeholder = placeholder + if reserve_space_for_menu is not None: + self.reserve_space_for_menu = reserve_space_for_menu + if enable_system_prompt is not None: + self.enable_system_prompt = enable_system_prompt + if enable_suspend is not None: + self.enable_suspend = enable_suspend + if enable_open_in_editor is not None: + self.enable_open_in_editor = enable_open_in_editor + if tempfile_suffix is not None: + self.tempfile_suffix = tempfile_suffix + if tempfile is not None: + self.tempfile = tempfile + + self._add_pre_run_callables(pre_run, accept_default) + self.default_buffer.reset( + default if isinstance(default, Document) else Document(default) + ) + self.app.refresh_interval = self.refresh_interval # This is not reactive. + + # If we are using the default output, and have a dumb terminal. Use the + # dumb prompt. + if self._output is None and is_dumb_terminal(): + with self._dumb_prompt(self.message) as dump_app: + return dump_app.run(in_thread=in_thread) + + return self.app.run( + set_exception_handler=set_exception_handler, in_thread=in_thread + ) + + @contextmanager + def _dumb_prompt(self, message: AnyFormattedText = "") -> Iterator[Application[_T]]: + """ + Create prompt `Application` for prompt function for dumb terminals. + + Dumb terminals have minimum rendering capabilities. We can only print + text to the screen. We can't use colors, and we can't do cursor + movements. The Emacs inferior shell is an example of a dumb terminal. + + We will show the prompt, and wait for the input. We still handle arrow + keys, and all custom key bindings, but we don't really render the + cursor movements. Instead we only print the typed character that's + right before the cursor. + """ + # Send prompt to output. + self.output.write(fragment_list_to_text(to_formatted_text(self.message))) + self.output.flush() + + # Key bindings for the dumb prompt: mostly the same as the full prompt. + key_bindings: KeyBindingsBase = self._create_prompt_bindings() + if self.key_bindings: + key_bindings = merge_key_bindings([self.key_bindings, key_bindings]) + + # Create and run application. + application = cast( + Application[_T], + Application( + input=self.input, + output=DummyOutput(), + layout=self.layout, + key_bindings=key_bindings, + ), + ) + + def on_text_changed(_: object) -> None: + self.output.write(self.default_buffer.document.text_before_cursor[-1:]) + self.output.flush() + + self.default_buffer.on_text_changed += on_text_changed + + try: + yield application + finally: + # Render line ending. + self.output.write("\r\n") + self.output.flush() + + self.default_buffer.on_text_changed -= on_text_changed + + async def prompt_async( + self, + # When any of these arguments are passed, this value is overwritten + # in this PromptSession. + message: Optional[AnyFormattedText] = None, + # `message` should go first, because people call it as + # positional argument. + *, + editing_mode: Optional[EditingMode] = None, + refresh_interval: Optional[float] = None, + vi_mode: Optional[bool] = None, + lexer: Optional[Lexer] = None, + completer: Optional[Completer] = None, + complete_in_thread: Optional[bool] = None, + is_password: Optional[bool] = None, + key_bindings: Optional[KeyBindingsBase] = None, + bottom_toolbar: Optional[AnyFormattedText] = None, + style: Optional[BaseStyle] = None, + color_depth: Optional[ColorDepth] = None, + cursor: Optional[CursorShapeConfig] = None, + include_default_pygments_style: Optional[FilterOrBool] = None, + style_transformation: Optional[StyleTransformation] = None, + swap_light_and_dark_colors: Optional[FilterOrBool] = None, + rprompt: Optional[AnyFormattedText] = None, + multiline: Optional[FilterOrBool] = None, + prompt_continuation: Optional[PromptContinuationText] = None, + wrap_lines: Optional[FilterOrBool] = None, + enable_history_search: Optional[FilterOrBool] = None, + search_ignore_case: Optional[FilterOrBool] = None, + complete_while_typing: Optional[FilterOrBool] = None, + validate_while_typing: Optional[FilterOrBool] = None, + complete_style: Optional[CompleteStyle] = None, + auto_suggest: Optional[AutoSuggest] = None, + validator: Optional[Validator] = None, + clipboard: Optional[Clipboard] = None, + mouse_support: Optional[FilterOrBool] = None, + input_processors: Optional[List[Processor]] = None, + placeholder: Optional[AnyFormattedText] = None, + reserve_space_for_menu: Optional[int] = None, + enable_system_prompt: Optional[FilterOrBool] = None, + enable_suspend: Optional[FilterOrBool] = None, + enable_open_in_editor: Optional[FilterOrBool] = None, + tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None, + tempfile: Optional[Union[str, Callable[[], str]]] = None, + # Following arguments are specific to the current `prompt()` call. + default: Union[str, Document] = "", + accept_default: bool = False, + pre_run: Optional[Callable[[], None]] = None, + set_exception_handler: bool = True, + ) -> _T: + + if message is not None: + self.message = message + if editing_mode is not None: + self.editing_mode = editing_mode + if refresh_interval is not None: + self.refresh_interval = refresh_interval + if vi_mode: + self.editing_mode = EditingMode.VI + if lexer is not None: + self.lexer = lexer + if completer is not None: + self.completer = completer + if complete_in_thread is not None: + self.complete_in_thread = complete_in_thread + if is_password is not None: + self.is_password = is_password + if key_bindings is not None: + self.key_bindings = key_bindings + if bottom_toolbar is not None: + self.bottom_toolbar = bottom_toolbar + if style is not None: + self.style = style + if color_depth is not None: + self.color_depth = color_depth + if cursor is not None: + self.cursor = cursor + if include_default_pygments_style is not None: + self.include_default_pygments_style = include_default_pygments_style + if style_transformation is not None: + self.style_transformation = style_transformation + if swap_light_and_dark_colors is not None: + self.swap_light_and_dark_colors = swap_light_and_dark_colors + if rprompt is not None: + self.rprompt = rprompt + if multiline is not None: + self.multiline = multiline + if prompt_continuation is not None: + self.prompt_continuation = prompt_continuation + if wrap_lines is not None: + self.wrap_lines = wrap_lines + if enable_history_search is not None: + self.enable_history_search = enable_history_search + if search_ignore_case is not None: + self.search_ignore_case = search_ignore_case + if complete_while_typing is not None: + self.complete_while_typing = complete_while_typing + if validate_while_typing is not None: + self.validate_while_typing = validate_while_typing + if complete_style is not None: + self.complete_style = complete_style + if auto_suggest is not None: + self.auto_suggest = auto_suggest + if validator is not None: + self.validator = validator + if clipboard is not None: + self.clipboard = clipboard + if mouse_support is not None: + self.mouse_support = mouse_support + if input_processors is not None: + self.input_processors = input_processors + if placeholder is not None: + self.placeholder = placeholder + if reserve_space_for_menu is not None: + self.reserve_space_for_menu = reserve_space_for_menu + if enable_system_prompt is not None: + self.enable_system_prompt = enable_system_prompt + if enable_suspend is not None: + self.enable_suspend = enable_suspend + if enable_open_in_editor is not None: + self.enable_open_in_editor = enable_open_in_editor + if tempfile_suffix is not None: + self.tempfile_suffix = tempfile_suffix + if tempfile is not None: + self.tempfile = tempfile + + self._add_pre_run_callables(pre_run, accept_default) + self.default_buffer.reset( + default if isinstance(default, Document) else Document(default) + ) + self.app.refresh_interval = self.refresh_interval # This is not reactive. + + # If we are using the default output, and have a dumb terminal. Use the + # dumb prompt. + if self._output is None and is_dumb_terminal(): + with self._dumb_prompt(self.message) as dump_app: + return await dump_app.run_async() + + return await self.app.run_async(set_exception_handler=set_exception_handler) + + def _add_pre_run_callables( + self, pre_run: Optional[Callable[[], None]], accept_default: bool + ) -> None: + def pre_run2() -> None: + if pre_run: + pre_run() + + if accept_default: + # Validate and handle input. We use `call_from_executor` in + # order to run it "soon" (during the next iteration of the + # event loop), instead of right now. Otherwise, it won't + # display the default value. + get_event_loop().call_soon(self.default_buffer.validate_and_handle) + + self.app.pre_run_callables.append(pre_run2) + + @property + def editing_mode(self) -> EditingMode: + return self.app.editing_mode + + @editing_mode.setter + def editing_mode(self, value: EditingMode) -> None: + self.app.editing_mode = value + + def _get_default_buffer_control_height(self) -> Dimension: + # If there is an autocompletion menu to be shown, make sure that our + # layout has at least a minimal height in order to display it. + if ( + self.completer is not None + and self.complete_style != CompleteStyle.READLINE_LIKE + ): + space = self.reserve_space_for_menu + else: + space = 0 + + if space and not get_app().is_done: + buff = self.default_buffer + + # Reserve the space, either when there are completions, or when + # `complete_while_typing` is true and we expect completions very + # soon. + if buff.complete_while_typing() or buff.complete_state is not None: + return Dimension(min=space) + + return Dimension() + + def _get_prompt(self) -> StyleAndTextTuples: + return to_formatted_text(self.message, style="class:prompt") + + def _get_continuation( + self, width: int, line_number: int, wrap_count: int + ) -> StyleAndTextTuples: + """ + Insert the prompt continuation. + + :param width: The width that was used for the prompt. (more or less can + be used.) + :param line_number: + :param wrap_count: Amount of times that the line has been wrapped. + """ + prompt_continuation = self.prompt_continuation + + if callable(prompt_continuation): + continuation: AnyFormattedText = prompt_continuation( + width, line_number, wrap_count + ) + else: + continuation = prompt_continuation + + # When the continuation prompt is not given, choose the same width as + # the actual prompt. + if continuation is None and is_true(self.multiline): + continuation = " " * width + + return to_formatted_text(continuation, style="class:prompt-continuation") + + def _get_line_prefix( + self, + line_number: int, + wrap_count: int, + get_prompt_text_2: _StyleAndTextTuplesCallable, + ) -> StyleAndTextTuples: + """ + Return whatever needs to be inserted before every line. + (the prompt, or a line continuation.) + """ + # First line: display the "arg" or the prompt. + if line_number == 0 and wrap_count == 0: + if not is_true(self.multiline) and get_app().key_processor.arg is not None: + return self._inline_arg() + else: + return get_prompt_text_2() + + # For the next lines, display the appropriate continuation. + prompt_width = get_cwidth(fragment_list_to_text(get_prompt_text_2())) + return self._get_continuation(prompt_width, line_number, wrap_count) + + def _get_arg_text(self) -> StyleAndTextTuples: + "'arg' toolbar, for in multiline mode." + arg = self.app.key_processor.arg + if arg is None: + # Should not happen because of the `has_arg` filter in the layout. + return [] + + if arg == "-": + arg = "-1" + + return [("class:arg-toolbar", "Repeat: "), ("class:arg-toolbar.text", arg)] + + def _inline_arg(self) -> StyleAndTextTuples: + "'arg' prefix, for in single line mode." + app = get_app() + if app.key_processor.arg is None: + return [] + else: + arg = app.key_processor.arg + + return [ + ("class:prompt.arg", "(arg: "), + ("class:prompt.arg.text", str(arg)), + ("class:prompt.arg", ") "), + ] + + # Expose the Input and Output objects as attributes, mainly for + # backward-compatibility. + + @property + def input(self) -> Input: + return self.app.input + + @property + def output(self) -> Output: + return self.app.output + + +def prompt( + message: Optional[AnyFormattedText] = None, + *, + history: Optional[History] = None, + editing_mode: Optional[EditingMode] = None, + refresh_interval: Optional[float] = None, + vi_mode: Optional[bool] = None, + lexer: Optional[Lexer] = None, + completer: Optional[Completer] = None, + complete_in_thread: Optional[bool] = None, + is_password: Optional[bool] = None, + key_bindings: Optional[KeyBindingsBase] = None, + bottom_toolbar: Optional[AnyFormattedText] = None, + style: Optional[BaseStyle] = None, + color_depth: Optional[ColorDepth] = None, + cursor: AnyCursorShapeConfig = None, + include_default_pygments_style: Optional[FilterOrBool] = None, + style_transformation: Optional[StyleTransformation] = None, + swap_light_and_dark_colors: Optional[FilterOrBool] = None, + rprompt: Optional[AnyFormattedText] = None, + multiline: Optional[FilterOrBool] = None, + prompt_continuation: Optional[PromptContinuationText] = None, + wrap_lines: Optional[FilterOrBool] = None, + enable_history_search: Optional[FilterOrBool] = None, + search_ignore_case: Optional[FilterOrBool] = None, + complete_while_typing: Optional[FilterOrBool] = None, + validate_while_typing: Optional[FilterOrBool] = None, + complete_style: Optional[CompleteStyle] = None, + auto_suggest: Optional[AutoSuggest] = None, + validator: Optional[Validator] = None, + clipboard: Optional[Clipboard] = None, + mouse_support: Optional[FilterOrBool] = None, + input_processors: Optional[List[Processor]] = None, + placeholder: Optional[AnyFormattedText] = None, + reserve_space_for_menu: Optional[int] = None, + enable_system_prompt: Optional[FilterOrBool] = None, + enable_suspend: Optional[FilterOrBool] = None, + enable_open_in_editor: Optional[FilterOrBool] = None, + tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None, + tempfile: Optional[Union[str, Callable[[], str]]] = None, + # Following arguments are specific to the current `prompt()` call. + default: str = "", + accept_default: bool = False, + pre_run: Optional[Callable[[], None]] = None, +) -> str: + """ + The global `prompt` function. This will create a new `PromptSession` + instance for every call. + """ + # The history is the only attribute that has to be passed to the + # `PromptSession`, it can't be passed into the `prompt()` method. + session: PromptSession[str] = PromptSession(history=history) + + return session.prompt( + message, + editing_mode=editing_mode, + refresh_interval=refresh_interval, + vi_mode=vi_mode, + lexer=lexer, + completer=completer, + complete_in_thread=complete_in_thread, + is_password=is_password, + key_bindings=key_bindings, + bottom_toolbar=bottom_toolbar, + style=style, + color_depth=color_depth, + cursor=cursor, + include_default_pygments_style=include_default_pygments_style, + style_transformation=style_transformation, + swap_light_and_dark_colors=swap_light_and_dark_colors, + rprompt=rprompt, + multiline=multiline, + prompt_continuation=prompt_continuation, + wrap_lines=wrap_lines, + enable_history_search=enable_history_search, + search_ignore_case=search_ignore_case, + complete_while_typing=complete_while_typing, + validate_while_typing=validate_while_typing, + complete_style=complete_style, + auto_suggest=auto_suggest, + validator=validator, + clipboard=clipboard, + mouse_support=mouse_support, + input_processors=input_processors, + placeholder=placeholder, + reserve_space_for_menu=reserve_space_for_menu, + enable_system_prompt=enable_system_prompt, + enable_suspend=enable_suspend, + enable_open_in_editor=enable_open_in_editor, + tempfile_suffix=tempfile_suffix, + tempfile=tempfile, + default=default, + accept_default=accept_default, + pre_run=pre_run, + ) + + +prompt.__doc__ = PromptSession.prompt.__doc__ + + +def create_confirm_session( + message: str, suffix: str = " (y/n) " +) -> PromptSession[bool]: + """ + Create a `PromptSession` object for the 'confirm' function. + """ + bindings = KeyBindings() + + @bindings.add("y") + @bindings.add("Y") + def yes(event: E) -> None: + session.default_buffer.text = "y" + event.app.exit(result=True) + + @bindings.add("n") + @bindings.add("N") + def no(event: E) -> None: + session.default_buffer.text = "n" + event.app.exit(result=False) + + @bindings.add(Keys.Any) + def _(event: E) -> None: + "Disallow inserting other text." + pass + + complete_message = merge_formatted_text([message, suffix]) + session: PromptSession[bool] = PromptSession( + complete_message, key_bindings=bindings + ) + return session + + +def confirm(message: str = "Confirm?", suffix: str = " (y/n) ") -> bool: + """ + Display a confirmation prompt that returns True/False. + """ + session = create_confirm_session(message, suffix) + return session.prompt() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/utils.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/utils.py new file mode 100644 index 0000000..c7ce74e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/shortcuts/utils.py @@ -0,0 +1,239 @@ +from asyncio.events import AbstractEventLoop +from typing import TYPE_CHECKING, Any, Optional, TextIO + +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app_or_none, get_app_session +from prompt_toolkit.application.run_in_terminal import run_in_terminal +from prompt_toolkit.eventloop import get_event_loop +from prompt_toolkit.formatted_text import ( + FormattedText, + StyleAndTextTuples, + to_formatted_text, +) +from prompt_toolkit.input import DummyInput +from prompt_toolkit.layout import Layout +from prompt_toolkit.output import ColorDepth, Output +from prompt_toolkit.output.defaults import create_output +from prompt_toolkit.renderer import ( + print_formatted_text as renderer_print_formatted_text, +) +from prompt_toolkit.styles import ( + BaseStyle, + StyleTransformation, + default_pygments_style, + default_ui_style, + merge_styles, +) + +if TYPE_CHECKING: + from prompt_toolkit.layout.containers import AnyContainer + +__all__ = [ + "print_formatted_text", + "print_container", + "clear", + "set_title", + "clear_title", +] + + +def print_formatted_text( + *values: Any, + sep: str = " ", + end: str = "\n", + file: Optional[TextIO] = None, + flush: bool = False, + style: Optional[BaseStyle] = None, + output: Optional[Output] = None, + color_depth: Optional[ColorDepth] = None, + style_transformation: Optional[StyleTransformation] = None, + include_default_pygments_style: bool = True, +) -> None: + """ + :: + + print_formatted_text(*values, sep=' ', end='\\n', file=None, flush=False, style=None, output=None) + + Print text to stdout. This is supposed to be compatible with Python's print + function, but supports printing of formatted text. You can pass a + :class:`~prompt_toolkit.formatted_text.FormattedText`, + :class:`~prompt_toolkit.formatted_text.HTML` or + :class:`~prompt_toolkit.formatted_text.ANSI` object to print formatted + text. + + * Print HTML as follows:: + + print_formatted_text(HTML('Some italic text This is red!')) + + style = Style.from_dict({ + 'hello': '#ff0066', + 'world': '#884444 italic', + }) + print_formatted_text(HTML('Hello world!'), style=style) + + * Print a list of (style_str, text) tuples in the given style to the + output. E.g.:: + + style = Style.from_dict({ + 'hello': '#ff0066', + 'world': '#884444 italic', + }) + fragments = FormattedText([ + ('class:hello', 'Hello'), + ('class:world', 'World'), + ]) + print_formatted_text(fragments, style=style) + + If you want to print a list of Pygments tokens, wrap it in + :class:`~prompt_toolkit.formatted_text.PygmentsTokens` to do the + conversion. + + If a prompt_toolkit `Application` is currently running, this will always + print above the application or prompt (similar to `patch_stdout`). So, + `print_formatted_text` will erase the current application, print the text, + and render the application again. + + :param values: Any kind of printable object, or formatted string. + :param sep: String inserted between values, default a space. + :param end: String appended after the last value, default a newline. + :param style: :class:`.Style` instance for the color scheme. + :param include_default_pygments_style: `bool`. Include the default Pygments + style when set to `True` (the default). + """ + assert not (output and file) + + # Create Output object. + if output is None: + if file: + output = create_output(stdout=file) + else: + output = get_app_session().output + + assert isinstance(output, Output) + + # Get color depth. + color_depth = color_depth or output.get_default_color_depth() + + # Merges values. + def to_text(val: Any) -> StyleAndTextTuples: + # Normal lists which are not instances of `FormattedText` are + # considered plain text. + if isinstance(val, list) and not isinstance(val, FormattedText): + return to_formatted_text(f"{val}") + return to_formatted_text(val, auto_convert=True) + + fragments = [] + for i, value in enumerate(values): + fragments.extend(to_text(value)) + + if sep and i != len(values) - 1: + fragments.extend(to_text(sep)) + + fragments.extend(to_text(end)) + + # Print output. + def render() -> None: + assert isinstance(output, Output) + + renderer_print_formatted_text( + output, + fragments, + _create_merged_style( + style, include_default_pygments_style=include_default_pygments_style + ), + color_depth=color_depth, + style_transformation=style_transformation, + ) + + # Flush the output stream. + if flush: + output.flush() + + # If an application is running, print above the app. This does not require + # `patch_stdout`. + loop: Optional[AbstractEventLoop] = None + + app = get_app_or_none() + if app is not None: + loop = app.loop + + if loop is not None: + loop.call_soon_threadsafe(lambda: run_in_terminal(render)) + else: + render() + + +def print_container( + container: "AnyContainer", + file: Optional[TextIO] = None, + style: Optional[BaseStyle] = None, + include_default_pygments_style: bool = True, +) -> None: + """ + Print any layout to the output in a non-interactive way. + + Example usage:: + + from prompt_toolkit.widgets import Frame, TextArea + print_container( + Frame(TextArea(text='Hello world!'))) + """ + if file: + output = create_output(stdout=file) + else: + output = get_app_session().output + + def exit_immediately() -> None: + # Use `call_from_executor` to exit "soon", so that we still render one + # initial time, before exiting the application. + get_event_loop().call_soon(lambda: app.exit()) + + app: Application[None] = Application( + layout=Layout(container=container), + output=output, + input=DummyInput(), + style=_create_merged_style( + style, include_default_pygments_style=include_default_pygments_style + ), + ) + app.run(pre_run=exit_immediately, in_thread=True) + + +def _create_merged_style( + style: Optional[BaseStyle], include_default_pygments_style: bool +) -> BaseStyle: + """ + Merge user defined style with built-in style. + """ + styles = [default_ui_style()] + if include_default_pygments_style: + styles.append(default_pygments_style()) + if style: + styles.append(style) + + return merge_styles(styles) + + +def clear() -> None: + """ + Clear the screen. + """ + output = get_app_session().output + output.erase_screen() + output.cursor_goto(0, 0) + output.flush() + + +def set_title(text: str) -> None: + """ + Set the terminal title. + """ + output = get_app_session().output + output.set_title(text) + + +def clear_title() -> None: + """ + Erase the current title. + """ + set_title("") diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__init__.py new file mode 100644 index 0000000..c270ae0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__init__.py @@ -0,0 +1,64 @@ +""" +Styling for prompt_toolkit applications. +""" +from .base import ( + ANSI_COLOR_NAMES, + DEFAULT_ATTRS, + Attrs, + BaseStyle, + DummyStyle, + DynamicStyle, +) +from .defaults import default_pygments_style, default_ui_style +from .named_colors import NAMED_COLORS +from .pygments import ( + pygments_token_to_classname, + style_from_pygments_cls, + style_from_pygments_dict, +) +from .style import Priority, Style, merge_styles, parse_color +from .style_transformation import ( + AdjustBrightnessStyleTransformation, + ConditionalStyleTransformation, + DummyStyleTransformation, + DynamicStyleTransformation, + ReverseStyleTransformation, + SetDefaultColorStyleTransformation, + StyleTransformation, + SwapLightAndDarkStyleTransformation, + merge_style_transformations, +) + +__all__ = [ + # Base. + "Attrs", + "DEFAULT_ATTRS", + "ANSI_COLOR_NAMES", + "BaseStyle", + "DummyStyle", + "DynamicStyle", + # Defaults. + "default_ui_style", + "default_pygments_style", + # Style. + "Style", + "Priority", + "merge_styles", + "parse_color", + # Style transformation. + "StyleTransformation", + "SwapLightAndDarkStyleTransformation", + "ReverseStyleTransformation", + "SetDefaultColorStyleTransformation", + "AdjustBrightnessStyleTransformation", + "DummyStyleTransformation", + "ConditionalStyleTransformation", + "DynamicStyleTransformation", + "merge_style_transformations", + # Pygments. + "style_from_pygments_cls", + "style_from_pygments_dict", + "pygments_token_to_classname", + # Named colors. + "NAMED_COLORS", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..53afc4d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..8dbe883 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/defaults.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/defaults.cpython-38.pyc new file mode 100644 index 0000000..fc0c5f0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/defaults.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/named_colors.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/named_colors.cpython-38.pyc new file mode 100644 index 0000000..de81aa3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/named_colors.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/pygments.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/pygments.cpython-38.pyc new file mode 100644 index 0000000..f5969a1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/pygments.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/style.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/style.cpython-38.pyc new file mode 100644 index 0000000..47ab225 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/style.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/style_transformation.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/style_transformation.cpython-38.pyc new file mode 100644 index 0000000..1ecebd9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/__pycache__/style_transformation.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/base.py new file mode 100644 index 0000000..aa77b9a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/base.py @@ -0,0 +1,181 @@ +""" +The base classes for the styling. +""" +from abc import ABCMeta, abstractmethod, abstractproperty +from typing import Callable, Dict, Hashable, List, NamedTuple, Optional, Tuple + +__all__ = [ + "Attrs", + "DEFAULT_ATTRS", + "ANSI_COLOR_NAMES", + "ANSI_COLOR_NAMES_ALIASES", + "BaseStyle", + "DummyStyle", + "DynamicStyle", +] + + +#: Style attributes. +class Attrs(NamedTuple): + color: Optional[str] + bgcolor: Optional[str] + bold: Optional[bool] + underline: Optional[bool] + strike: Optional[bool] + italic: Optional[bool] + blink: Optional[bool] + reverse: Optional[bool] + hidden: Optional[bool] + + +""" +:param color: Hexadecimal string. E.g. '000000' or Ansi color name: e.g. 'ansiblue' +:param bgcolor: Hexadecimal string. E.g. 'ffffff' or Ansi color name: e.g. 'ansired' +:param bold: Boolean +:param underline: Boolean +:param strike: Boolean +:param italic: Boolean +:param blink: Boolean +:param reverse: Boolean +:param hidden: Boolean +""" + +#: The default `Attrs`. +DEFAULT_ATTRS = Attrs( + color="", + bgcolor="", + bold=False, + underline=False, + strike=False, + italic=False, + blink=False, + reverse=False, + hidden=False, +) + + +#: ``Attrs.bgcolor/fgcolor`` can be in either 'ffffff' format, or can be any of +#: the following in case we want to take colors from the 8/16 color palette. +#: Usually, in that case, the terminal application allows to configure the RGB +#: values for these names. +#: ISO 6429 colors +ANSI_COLOR_NAMES = [ + "ansidefault", + # Low intensity, dark. (One or two components 0x80, the other 0x00.) + "ansiblack", + "ansired", + "ansigreen", + "ansiyellow", + "ansiblue", + "ansimagenta", + "ansicyan", + "ansigray", + # High intensity, bright. (One or two components 0xff, the other 0x00. Not supported everywhere.) + "ansibrightblack", + "ansibrightred", + "ansibrightgreen", + "ansibrightyellow", + "ansibrightblue", + "ansibrightmagenta", + "ansibrightcyan", + "ansiwhite", +] + + +# People don't use the same ANSI color names everywhere. In prompt_toolkit 1.0 +# we used some unconventional names (which were contributed like that to +# Pygments). This is fixed now, but we still support the old names. + +# The table below maps the old aliases to the current names. +ANSI_COLOR_NAMES_ALIASES: Dict[str, str] = { + "ansidarkgray": "ansibrightblack", + "ansiteal": "ansicyan", + "ansiturquoise": "ansibrightcyan", + "ansibrown": "ansiyellow", + "ansipurple": "ansimagenta", + "ansifuchsia": "ansibrightmagenta", + "ansilightgray": "ansigray", + "ansidarkred": "ansired", + "ansidarkgreen": "ansigreen", + "ansidarkblue": "ansiblue", +} +assert set(ANSI_COLOR_NAMES_ALIASES.values()).issubset(set(ANSI_COLOR_NAMES)) +assert not (set(ANSI_COLOR_NAMES_ALIASES.keys()) & set(ANSI_COLOR_NAMES)) + + +class BaseStyle(metaclass=ABCMeta): + """ + Abstract base class for prompt_toolkit styles. + """ + + @abstractmethod + def get_attrs_for_style_str( + self, style_str: str, default: Attrs = DEFAULT_ATTRS + ) -> Attrs: + """ + Return :class:`.Attrs` for the given style string. + + :param style_str: The style string. This can contain inline styling as + well as classnames (e.g. "class:title"). + :param default: `Attrs` to be used if no styling was defined. + """ + + @abstractproperty + def style_rules(self) -> List[Tuple[str, str]]: + """ + The list of style rules, used to create this style. + (Required for `DynamicStyle` and `_MergedStyle` to work.) + """ + return [] + + @abstractmethod + def invalidation_hash(self) -> Hashable: + """ + Invalidation hash for the style. When this changes over time, the + renderer knows that something in the style changed, and that everything + has to be redrawn. + """ + + +class DummyStyle(BaseStyle): + """ + A style that doesn't style anything. + """ + + def get_attrs_for_style_str( + self, style_str: str, default: Attrs = DEFAULT_ATTRS + ) -> Attrs: + return default + + def invalidation_hash(self) -> Hashable: + return 1 # Always the same value. + + @property + def style_rules(self) -> List[Tuple[str, str]]: + return [] + + +class DynamicStyle(BaseStyle): + """ + Style class that can dynamically returns an other Style. + + :param get_style: Callable that returns a :class:`.Style` instance. + """ + + def __init__(self, get_style: Callable[[], Optional[BaseStyle]]): + self.get_style = get_style + self._dummy = DummyStyle() + + def get_attrs_for_style_str( + self, style_str: str, default: Attrs = DEFAULT_ATTRS + ) -> Attrs: + style = self.get_style() or self._dummy + + return style.get_attrs_for_style_str(style_str, default) + + def invalidation_hash(self) -> Hashable: + return (self.get_style() or self._dummy).invalidation_hash() + + @property + def style_rules(self) -> List[Tuple[str, str]]: + return (self.get_style() or self._dummy).style_rules diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/defaults.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/defaults.py new file mode 100644 index 0000000..4ac5545 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/defaults.py @@ -0,0 +1,231 @@ +""" +The default styling. +""" +from prompt_toolkit.cache import memoized + +from .base import ANSI_COLOR_NAMES, BaseStyle +from .named_colors import NAMED_COLORS +from .style import Style, merge_styles + +__all__ = [ + "default_ui_style", + "default_pygments_style", +] + +#: Default styling. Mapping from classnames to their style definition. +PROMPT_TOOLKIT_STYLE = [ + # Highlighting of search matches in document. + ("search", "bg:ansibrightyellow ansiblack"), + ("search.current", ""), + # Incremental search. + ("incsearch", ""), + ("incsearch.current", "reverse"), + # Highlighting of select text in document. + ("selected", "reverse"), + ("cursor-column", "bg:#dddddd"), + ("cursor-line", "underline"), + ("color-column", "bg:#ccaacc"), + # Highlighting of matching brackets. + ("matching-bracket", ""), + ("matching-bracket.other", "#000000 bg:#aacccc"), + ("matching-bracket.cursor", "#ff8888 bg:#880000"), + # Styling of other cursors, in case of block editing. + ("multiple-cursors", "#000000 bg:#ccccaa"), + # Line numbers. + ("line-number", "#888888"), + ("line-number.current", "bold"), + ("tilde", "#8888ff"), + # Default prompt. + ("prompt", ""), + ("prompt.arg", "noinherit"), + ("prompt.arg.text", ""), + ("prompt.search", "noinherit"), + ("prompt.search.text", ""), + # Search toolbar. + ("search-toolbar", "bold"), + ("search-toolbar.text", "nobold"), + # System toolbar + ("system-toolbar", "bold"), + ("system-toolbar.text", "nobold"), + # "arg" toolbar. + ("arg-toolbar", "bold"), + ("arg-toolbar.text", "nobold"), + # Validation toolbar. + ("validation-toolbar", "bg:#550000 #ffffff"), + ("window-too-small", "bg:#550000 #ffffff"), + # Completions toolbar. + ("completion-toolbar", "bg:#bbbbbb #000000"), + ("completion-toolbar.arrow", "bg:#bbbbbb #000000 bold"), + ("completion-toolbar.completion", "bg:#bbbbbb #000000"), + ("completion-toolbar.completion.current", "bg:#444444 #ffffff"), + # Completions menu. + ("completion-menu", "bg:#bbbbbb #000000"), + ("completion-menu.completion", ""), + ("completion-menu.completion.current", "bg:#888888 #ffffff"), + ("completion-menu.meta.completion", "bg:#999999 #000000"), + ("completion-menu.meta.completion.current", "bg:#aaaaaa #000000"), + ("completion-menu.multi-column-meta", "bg:#aaaaaa #000000"), + # Fuzzy matches in completion menu (for FuzzyCompleter). + ("completion-menu.completion fuzzymatch.outside", "fg:#444444"), + ("completion-menu.completion fuzzymatch.inside", "bold"), + ("completion-menu.completion fuzzymatch.inside.character", "underline"), + ("completion-menu.completion.current fuzzymatch.outside", "fg:default"), + ("completion-menu.completion.current fuzzymatch.inside", "nobold"), + # Styling of readline-like completions. + ("readline-like-completions", ""), + ("readline-like-completions.completion", ""), + ("readline-like-completions.completion fuzzymatch.outside", "#888888"), + ("readline-like-completions.completion fuzzymatch.inside", ""), + ("readline-like-completions.completion fuzzymatch.inside.character", "underline"), + # Scrollbars. + ("scrollbar.background", "bg:#aaaaaa"), + ("scrollbar.button", "bg:#444444"), + ("scrollbar.arrow", "noinherit bold"), + # Start/end of scrollbars. Adding 'underline' here provides a nice little + # detail to the progress bar, but it doesn't look good on all terminals. + # ('scrollbar.start', 'underline #ffffff'), + # ('scrollbar.end', 'underline #000000'), + # Auto suggestion text. + ("auto-suggestion", "#666666"), + # Trailing whitespace and tabs. + ("trailing-whitespace", "#999999"), + ("tab", "#999999"), + # When Control-C/D has been pressed. Grayed. + ("aborting", "#888888 bg:default noreverse noitalic nounderline noblink"), + ("exiting", "#888888 bg:default noreverse noitalic nounderline noblink"), + # Entering a Vi digraph. + ("digraph", "#4444ff"), + # Control characters, like ^C, ^X. + ("control-character", "ansiblue"), + # Non-breaking space. + ("nbsp", "underline ansiyellow"), + # Default styling of HTML elements. + ("i", "italic"), + ("u", "underline"), + ("s", "strike"), + ("b", "bold"), + ("em", "italic"), + ("strong", "bold"), + ("del", "strike"), + ("hidden", "hidden"), + # It should be possible to use the style names in HTML. + # ... or .... + ("italic", "italic"), + ("underline", "underline"), + ("strike", "strike"), + ("bold", "bold"), + ("reverse", "reverse"), + ("noitalic", "noitalic"), + ("nounderline", "nounderline"), + ("nostrike", "nostrike"), + ("nobold", "nobold"), + ("noreverse", "noreverse"), + # Prompt bottom toolbar + ("bottom-toolbar", "reverse"), +] + + +# Style that will turn for instance the class 'red' into 'red'. +COLORS_STYLE = [(name, "fg:" + name) for name in ANSI_COLOR_NAMES] + [ + (name.lower(), "fg:" + name) for name in NAMED_COLORS +] + + +WIDGETS_STYLE = [ + # Dialog windows. + ("dialog", "bg:#4444ff"), + ("dialog.body", "bg:#ffffff #000000"), + ("dialog.body text-area", "bg:#cccccc"), + ("dialog.body text-area last-line", "underline"), + ("dialog frame.label", "#ff0000 bold"), + # Scrollbars in dialogs. + ("dialog.body scrollbar.background", ""), + ("dialog.body scrollbar.button", "bg:#000000"), + ("dialog.body scrollbar.arrow", ""), + ("dialog.body scrollbar.start", "nounderline"), + ("dialog.body scrollbar.end", "nounderline"), + # Buttons. + ("button", ""), + ("button.arrow", "bold"), + ("button.focused", "bg:#aa0000 #ffffff"), + # Menu bars. + ("menu-bar", "bg:#aaaaaa #000000"), + ("menu-bar.selected-item", "bg:#ffffff #000000"), + ("menu", "bg:#888888 #ffffff"), + ("menu.border", "#aaaaaa"), + ("menu.border shadow", "#444444"), + # Shadows. + ("dialog shadow", "bg:#000088"), + ("dialog.body shadow", "bg:#aaaaaa"), + ("progress-bar", "bg:#000088"), + ("progress-bar.used", "bg:#ff0000"), +] + + +# The default Pygments style, include this by default in case a Pygments lexer +# is used. +PYGMENTS_DEFAULT_STYLE = { + "pygments.whitespace": "#bbbbbb", + "pygments.comment": "italic #408080", + "pygments.comment.preproc": "noitalic #bc7a00", + "pygments.keyword": "bold #008000", + "pygments.keyword.pseudo": "nobold", + "pygments.keyword.type": "nobold #b00040", + "pygments.operator": "#666666", + "pygments.operator.word": "bold #aa22ff", + "pygments.name.builtin": "#008000", + "pygments.name.function": "#0000ff", + "pygments.name.class": "bold #0000ff", + "pygments.name.namespace": "bold #0000ff", + "pygments.name.exception": "bold #d2413a", + "pygments.name.variable": "#19177c", + "pygments.name.constant": "#880000", + "pygments.name.label": "#a0a000", + "pygments.name.entity": "bold #999999", + "pygments.name.attribute": "#7d9029", + "pygments.name.tag": "bold #008000", + "pygments.name.decorator": "#aa22ff", + # Note: In Pygments, Token.String is an alias for Token.Literal.String, + # and Token.Number as an alias for Token.Literal.Number. + "pygments.literal.string": "#ba2121", + "pygments.literal.string.doc": "italic", + "pygments.literal.string.interpol": "bold #bb6688", + "pygments.literal.string.escape": "bold #bb6622", + "pygments.literal.string.regex": "#bb6688", + "pygments.literal.string.symbol": "#19177c", + "pygments.literal.string.other": "#008000", + "pygments.literal.number": "#666666", + "pygments.generic.heading": "bold #000080", + "pygments.generic.subheading": "bold #800080", + "pygments.generic.deleted": "#a00000", + "pygments.generic.inserted": "#00a000", + "pygments.generic.error": "#ff0000", + "pygments.generic.emph": "italic", + "pygments.generic.strong": "bold", + "pygments.generic.prompt": "bold #000080", + "pygments.generic.output": "#888", + "pygments.generic.traceback": "#04d", + "pygments.error": "border:#ff0000", +} + + +@memoized() +def default_ui_style() -> BaseStyle: + """ + Create a default `Style` object. + """ + return merge_styles( + [ + Style(PROMPT_TOOLKIT_STYLE), + Style(COLORS_STYLE), + Style(WIDGETS_STYLE), + ] + ) + + +@memoized() +def default_pygments_style() -> Style: + """ + Create a `Style` object that contains the default Pygments style. + """ + return Style.from_dict(PYGMENTS_DEFAULT_STYLE) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/named_colors.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/named_colors.py new file mode 100644 index 0000000..37bd6de --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/named_colors.py @@ -0,0 +1,161 @@ +""" +All modern web browsers support these 140 color names. +Taken from: https://www.w3schools.com/colors/colors_names.asp +""" +from typing import Dict + +__all__ = [ + "NAMED_COLORS", +] + + +NAMED_COLORS: Dict[str, str] = { + "AliceBlue": "#f0f8ff", + "AntiqueWhite": "#faebd7", + "Aqua": "#00ffff", + "Aquamarine": "#7fffd4", + "Azure": "#f0ffff", + "Beige": "#f5f5dc", + "Bisque": "#ffe4c4", + "Black": "#000000", + "BlanchedAlmond": "#ffebcd", + "Blue": "#0000ff", + "BlueViolet": "#8a2be2", + "Brown": "#a52a2a", + "BurlyWood": "#deb887", + "CadetBlue": "#5f9ea0", + "Chartreuse": "#7fff00", + "Chocolate": "#d2691e", + "Coral": "#ff7f50", + "CornflowerBlue": "#6495ed", + "Cornsilk": "#fff8dc", + "Crimson": "#dc143c", + "Cyan": "#00ffff", + "DarkBlue": "#00008b", + "DarkCyan": "#008b8b", + "DarkGoldenRod": "#b8860b", + "DarkGray": "#a9a9a9", + "DarkGreen": "#006400", + "DarkGrey": "#a9a9a9", + "DarkKhaki": "#bdb76b", + "DarkMagenta": "#8b008b", + "DarkOliveGreen": "#556b2f", + "DarkOrange": "#ff8c00", + "DarkOrchid": "#9932cc", + "DarkRed": "#8b0000", + "DarkSalmon": "#e9967a", + "DarkSeaGreen": "#8fbc8f", + "DarkSlateBlue": "#483d8b", + "DarkSlateGray": "#2f4f4f", + "DarkSlateGrey": "#2f4f4f", + "DarkTurquoise": "#00ced1", + "DarkViolet": "#9400d3", + "DeepPink": "#ff1493", + "DeepSkyBlue": "#00bfff", + "DimGray": "#696969", + "DimGrey": "#696969", + "DodgerBlue": "#1e90ff", + "FireBrick": "#b22222", + "FloralWhite": "#fffaf0", + "ForestGreen": "#228b22", + "Fuchsia": "#ff00ff", + "Gainsboro": "#dcdcdc", + "GhostWhite": "#f8f8ff", + "Gold": "#ffd700", + "GoldenRod": "#daa520", + "Gray": "#808080", + "Green": "#008000", + "GreenYellow": "#adff2f", + "Grey": "#808080", + "HoneyDew": "#f0fff0", + "HotPink": "#ff69b4", + "IndianRed": "#cd5c5c", + "Indigo": "#4b0082", + "Ivory": "#fffff0", + "Khaki": "#f0e68c", + "Lavender": "#e6e6fa", + "LavenderBlush": "#fff0f5", + "LawnGreen": "#7cfc00", + "LemonChiffon": "#fffacd", + "LightBlue": "#add8e6", + "LightCoral": "#f08080", + "LightCyan": "#e0ffff", + "LightGoldenRodYellow": "#fafad2", + "LightGray": "#d3d3d3", + "LightGreen": "#90ee90", + "LightGrey": "#d3d3d3", + "LightPink": "#ffb6c1", + "LightSalmon": "#ffa07a", + "LightSeaGreen": "#20b2aa", + "LightSkyBlue": "#87cefa", + "LightSlateGray": "#778899", + "LightSlateGrey": "#778899", + "LightSteelBlue": "#b0c4de", + "LightYellow": "#ffffe0", + "Lime": "#00ff00", + "LimeGreen": "#32cd32", + "Linen": "#faf0e6", + "Magenta": "#ff00ff", + "Maroon": "#800000", + "MediumAquaMarine": "#66cdaa", + "MediumBlue": "#0000cd", + "MediumOrchid": "#ba55d3", + "MediumPurple": "#9370db", + "MediumSeaGreen": "#3cb371", + "MediumSlateBlue": "#7b68ee", + "MediumSpringGreen": "#00fa9a", + "MediumTurquoise": "#48d1cc", + "MediumVioletRed": "#c71585", + "MidnightBlue": "#191970", + "MintCream": "#f5fffa", + "MistyRose": "#ffe4e1", + "Moccasin": "#ffe4b5", + "NavajoWhite": "#ffdead", + "Navy": "#000080", + "OldLace": "#fdf5e6", + "Olive": "#808000", + "OliveDrab": "#6b8e23", + "Orange": "#ffa500", + "OrangeRed": "#ff4500", + "Orchid": "#da70d6", + "PaleGoldenRod": "#eee8aa", + "PaleGreen": "#98fb98", + "PaleTurquoise": "#afeeee", + "PaleVioletRed": "#db7093", + "PapayaWhip": "#ffefd5", + "PeachPuff": "#ffdab9", + "Peru": "#cd853f", + "Pink": "#ffc0cb", + "Plum": "#dda0dd", + "PowderBlue": "#b0e0e6", + "Purple": "#800080", + "RebeccaPurple": "#663399", + "Red": "#ff0000", + "RosyBrown": "#bc8f8f", + "RoyalBlue": "#4169e1", + "SaddleBrown": "#8b4513", + "Salmon": "#fa8072", + "SandyBrown": "#f4a460", + "SeaGreen": "#2e8b57", + "SeaShell": "#fff5ee", + "Sienna": "#a0522d", + "Silver": "#c0c0c0", + "SkyBlue": "#87ceeb", + "SlateBlue": "#6a5acd", + "SlateGray": "#708090", + "SlateGrey": "#708090", + "Snow": "#fffafa", + "SpringGreen": "#00ff7f", + "SteelBlue": "#4682b4", + "Tan": "#d2b48c", + "Teal": "#008080", + "Thistle": "#d8bfd8", + "Tomato": "#ff6347", + "Turquoise": "#40e0d0", + "Violet": "#ee82ee", + "Wheat": "#f5deb3", + "White": "#ffffff", + "WhiteSmoke": "#f5f5f5", + "Yellow": "#ffff00", + "YellowGreen": "#9acd32", +} diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/pygments.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/pygments.py new file mode 100644 index 0000000..382e5e3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/pygments.py @@ -0,0 +1,67 @@ +""" +Adaptor for building prompt_toolkit styles, starting from a Pygments style. + +Usage:: + + from pygments.styles.tango import TangoStyle + style = style_from_pygments_cls(pygments_style_cls=TangoStyle) +""" +from typing import TYPE_CHECKING, Dict, Type + +from .style import Style + +if TYPE_CHECKING: + from pygments.style import Style as PygmentsStyle + from pygments.token import Token + + +__all__ = [ + "style_from_pygments_cls", + "style_from_pygments_dict", + "pygments_token_to_classname", +] + + +def style_from_pygments_cls(pygments_style_cls: Type["PygmentsStyle"]) -> Style: + """ + Shortcut to create a :class:`.Style` instance from a Pygments style class + and a style dictionary. + + Example:: + + from prompt_toolkit.styles.from_pygments import style_from_pygments_cls + from pygments.styles import get_style_by_name + style = style_from_pygments_cls(get_style_by_name('monokai')) + + :param pygments_style_cls: Pygments style class to start from. + """ + # Import inline. + from pygments.style import Style as PygmentsStyle + + assert issubclass(pygments_style_cls, PygmentsStyle) + + return style_from_pygments_dict(pygments_style_cls.styles) + + +def style_from_pygments_dict(pygments_dict: Dict["Token", str]) -> Style: + """ + Create a :class:`.Style` instance from a Pygments style dictionary. + (One that maps Token objects to style strings.) + """ + pygments_style = [] + + for token, style in pygments_dict.items(): + pygments_style.append((pygments_token_to_classname(token), style)) + + return Style(pygments_style) + + +def pygments_token_to_classname(token: "Token") -> str: + """ + Turn e.g. `Token.Name.Exception` into `'pygments.name.exception'`. + + (Our Pygments lexer will also turn the tokens that pygments produces in a + prompt_toolkit list of fragments that match these styling rules.) + """ + parts = ("pygments",) + token + return ".".join(parts).lower() diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/style.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/style.py new file mode 100644 index 0000000..6e4bd1f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/style.py @@ -0,0 +1,399 @@ +""" +Tool for creating styles from a dictionary. +""" +import itertools +import re +import sys +from enum import Enum +from typing import Dict, Hashable, List, Set, Tuple, TypeVar + +from prompt_toolkit.cache import SimpleCache + +from .base import ( + ANSI_COLOR_NAMES, + ANSI_COLOR_NAMES_ALIASES, + DEFAULT_ATTRS, + Attrs, + BaseStyle, +) +from .named_colors import NAMED_COLORS + +__all__ = [ + "Style", + "parse_color", + "Priority", + "merge_styles", +] + +_named_colors_lowercase = {k.lower(): v.lstrip("#") for k, v in NAMED_COLORS.items()} + + +def parse_color(text: str) -> str: + """ + Parse/validate color format. + + Like in Pygments, but also support the ANSI color names. + (These will map to the colors of the 16 color palette.) + """ + # ANSI color names. + if text in ANSI_COLOR_NAMES: + return text + if text in ANSI_COLOR_NAMES_ALIASES: + return ANSI_COLOR_NAMES_ALIASES[text] + + # 140 named colors. + try: + # Replace by 'hex' value. + return _named_colors_lowercase[text.lower()] + except KeyError: + pass + + # Hex codes. + if text[0:1] == "#": + col = text[1:] + + # Keep this for backwards-compatibility (Pygments does it). + # I don't like the '#' prefix for named colors. + if col in ANSI_COLOR_NAMES: + return col + elif col in ANSI_COLOR_NAMES_ALIASES: + return ANSI_COLOR_NAMES_ALIASES[col] + + # 6 digit hex color. + elif len(col) == 6: + return col + + # 3 digit hex color. + elif len(col) == 3: + return col[0] * 2 + col[1] * 2 + col[2] * 2 + + # Default. + elif text in ("", "default"): + return text + + raise ValueError("Wrong color format %r" % text) + + +# Attributes, when they are not filled in by a style. None means that we take +# the value from the parent. +_EMPTY_ATTRS = Attrs( + color=None, + bgcolor=None, + bold=None, + underline=None, + strike=None, + italic=None, + blink=None, + reverse=None, + hidden=None, +) + + +def _expand_classname(classname: str) -> List[str]: + """ + Split a single class name at the `.` operator, and build a list of classes. + + E.g. 'a.b.c' becomes ['a', 'a.b', 'a.b.c'] + """ + result = [] + parts = classname.split(".") + + for i in range(1, len(parts) + 1): + result.append(".".join(parts[:i]).lower()) + + return result + + +def _parse_style_str(style_str: str) -> Attrs: + """ + Take a style string, e.g. 'bg:red #88ff00 class:title' + and return a `Attrs` instance. + """ + # Start from default Attrs. + if "noinherit" in style_str: + attrs = DEFAULT_ATTRS + else: + attrs = _EMPTY_ATTRS + + # Now update with the given attributes. + for part in style_str.split(): + if part == "noinherit": + pass + elif part == "bold": + attrs = attrs._replace(bold=True) + elif part == "nobold": + attrs = attrs._replace(bold=False) + elif part == "italic": + attrs = attrs._replace(italic=True) + elif part == "noitalic": + attrs = attrs._replace(italic=False) + elif part == "underline": + attrs = attrs._replace(underline=True) + elif part == "nounderline": + attrs = attrs._replace(underline=False) + elif part == "strike": + attrs = attrs._replace(strike=True) + elif part == "nostrike": + attrs = attrs._replace(strike=False) + + # prompt_toolkit extensions. Not in Pygments. + elif part == "blink": + attrs = attrs._replace(blink=True) + elif part == "noblink": + attrs = attrs._replace(blink=False) + elif part == "reverse": + attrs = attrs._replace(reverse=True) + elif part == "noreverse": + attrs = attrs._replace(reverse=False) + elif part == "hidden": + attrs = attrs._replace(hidden=True) + elif part == "nohidden": + attrs = attrs._replace(hidden=False) + + # Pygments properties that we ignore. + elif part in ("roman", "sans", "mono"): + pass + elif part.startswith("border:"): + pass + + # Ignore pieces in between square brackets. This is internal stuff. + # Like '[transparent]' or '[set-cursor-position]'. + elif part.startswith("[") and part.endswith("]"): + pass + + # Colors. + elif part.startswith("bg:"): + attrs = attrs._replace(bgcolor=parse_color(part[3:])) + elif part.startswith("fg:"): # The 'fg:' prefix is optional. + attrs = attrs._replace(color=parse_color(part[3:])) + else: + attrs = attrs._replace(color=parse_color(part)) + + return attrs + + +CLASS_NAMES_RE = re.compile(r"^[a-z0-9.\s_-]*$") # This one can't contain a comma! + + +class Priority(Enum): + """ + The priority of the rules, when a style is created from a dictionary. + + In a `Style`, rules that are defined later will always override previous + defined rules, however in a dictionary, the key order was arbitrary before + Python 3.6. This means that the style could change at random between rules. + + We have two options: + + - `DICT_KEY_ORDER`: This means, iterate through the dictionary, and take + the key/value pairs in order as they come. This is a good option if you + have Python >3.6. Rules at the end will override rules at the beginning. + - `MOST_PRECISE`: keys that are defined with most precision will get higher + priority. (More precise means: more elements.) + """ + + DICT_KEY_ORDER = "KEY_ORDER" + MOST_PRECISE = "MOST_PRECISE" + + +# We don't support Python versions older than 3.6 anymore, so we can always +# depend on dictionary ordering. This is the default. +default_priority = Priority.DICT_KEY_ORDER + + +class Style(BaseStyle): + """ + Create a ``Style`` instance from a list of style rules. + + The `style_rules` is supposed to be a list of ('classnames', 'style') tuples. + The classnames are a whitespace separated string of class names and the + style string is just like a Pygments style definition, but with a few + additions: it supports 'reverse' and 'blink'. + + Later rules always override previous rules. + + Usage:: + + Style([ + ('title', '#ff0000 bold underline'), + ('something-else', 'reverse'), + ('class1 class2', 'reverse'), + ]) + + The ``from_dict`` classmethod is similar, but takes a dictionary as input. + """ + + def __init__(self, style_rules: List[Tuple[str, str]]) -> None: + class_names_and_attrs = [] + + # Loop through the rules in the order they were defined. + # Rules that are defined later get priority. + for class_names, style_str in style_rules: + assert CLASS_NAMES_RE.match(class_names), repr(class_names) + + # The order of the class names doesn't matter. + # (But the order of rules does matter.) + class_names_set = frozenset(class_names.lower().split()) + attrs = _parse_style_str(style_str) + + class_names_and_attrs.append((class_names_set, attrs)) + + self._style_rules = style_rules + self.class_names_and_attrs = class_names_and_attrs + + @property + def style_rules(self) -> List[Tuple[str, str]]: + return self._style_rules + + @classmethod + def from_dict( + cls, style_dict: Dict[str, str], priority: Priority = default_priority + ) -> "Style": + """ + :param style_dict: Style dictionary. + :param priority: `Priority` value. + """ + if priority == Priority.MOST_PRECISE: + + def key(item: Tuple[str, str]) -> int: + # Split on '.' and whitespace. Count elements. + return sum(len(i.split(".")) for i in item[0].split()) + + return cls(sorted(style_dict.items(), key=key)) + else: + return cls(list(style_dict.items())) + + def get_attrs_for_style_str( + self, style_str: str, default: Attrs = DEFAULT_ATTRS + ) -> Attrs: + """ + Get `Attrs` for the given style string. + """ + list_of_attrs = [default] + class_names: Set[str] = set() + + # Apply default styling. + for names, attr in self.class_names_and_attrs: + if not names: + list_of_attrs.append(attr) + + # Go from left to right through the style string. Things on the right + # take precedence. + for part in style_str.split(): + # This part represents a class. + # Do lookup of this class name in the style definition, as well + # as all class combinations that we have so far. + if part.startswith("class:"): + # Expand all class names (comma separated list). + new_class_names = [] + for p in part[6:].lower().split(","): + new_class_names.extend(_expand_classname(p)) + + for new_name in new_class_names: + # Build a set of all possible class combinations to be applied. + combos = set() + combos.add(frozenset([new_name])) + + for count in range(1, len(class_names) + 1): + for c2 in itertools.combinations(class_names, count): + combos.add(frozenset(c2 + (new_name,))) + + # Apply the styles that match these class names. + for names, attr in self.class_names_and_attrs: + if names in combos: + list_of_attrs.append(attr) + + class_names.add(new_name) + + # Process inline style. + else: + inline_attrs = _parse_style_str(part) + list_of_attrs.append(inline_attrs) + + return _merge_attrs(list_of_attrs) + + def invalidation_hash(self) -> Hashable: + return id(self.class_names_and_attrs) + + +_T = TypeVar("_T") + + +def _merge_attrs(list_of_attrs: List[Attrs]) -> Attrs: + """ + Take a list of :class:`.Attrs` instances and merge them into one. + Every `Attr` in the list can override the styling of the previous one. So, + the last one has highest priority. + """ + + def _or(*values: _T) -> _T: + "Take first not-None value, starting at the end." + for v in values[::-1]: + if v is not None: + return v + raise ValueError # Should not happen, there's always one non-null value. + + return Attrs( + color=_or("", *[a.color for a in list_of_attrs]), + bgcolor=_or("", *[a.bgcolor for a in list_of_attrs]), + bold=_or(False, *[a.bold for a in list_of_attrs]), + underline=_or(False, *[a.underline for a in list_of_attrs]), + strike=_or(False, *[a.strike for a in list_of_attrs]), + italic=_or(False, *[a.italic for a in list_of_attrs]), + blink=_or(False, *[a.blink for a in list_of_attrs]), + reverse=_or(False, *[a.reverse for a in list_of_attrs]), + hidden=_or(False, *[a.hidden for a in list_of_attrs]), + ) + + +def merge_styles(styles: List[BaseStyle]) -> "_MergedStyle": + """ + Merge multiple `Style` objects. + """ + styles = [s for s in styles if s is not None] + return _MergedStyle(styles) + + +class _MergedStyle(BaseStyle): + """ + Merge multiple `Style` objects into one. + This is supposed to ensure consistency: if any of the given styles changes, + then this style will be updated. + """ + + # NOTE: previously, we used an algorithm where we did not generate the + # combined style. Instead this was a proxy that called one style + # after the other, passing the outcome of the previous style as the + # default for the next one. This did not work, because that way, the + # priorities like described in the `Style` class don't work. + # 'class:aborted' was for instance never displayed in gray, because + # the next style specified a default color for any text. (The + # explicit styling of class:aborted should have taken priority, + # because it was more precise.) + def __init__(self, styles: List[BaseStyle]) -> None: + self.styles = styles + self._style: SimpleCache[Hashable, Style] = SimpleCache(maxsize=1) + + @property + def _merged_style(self) -> Style: + "The `Style` object that has the other styles merged together." + + def get() -> Style: + return Style(self.style_rules) + + return self._style.get(self.invalidation_hash(), get) + + @property + def style_rules(self) -> List[Tuple[str, str]]: + style_rules = [] + for s in self.styles: + style_rules.extend(s.style_rules) + return style_rules + + def get_attrs_for_style_str( + self, style_str: str, default: Attrs = DEFAULT_ATTRS + ) -> Attrs: + return self._merged_style.get_attrs_for_style_str(style_str, default) + + def invalidation_hash(self) -> Hashable: + return tuple(s.invalidation_hash() for s in self.styles) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/style_transformation.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/style_transformation.py new file mode 100644 index 0000000..91308f9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/styles/style_transformation.py @@ -0,0 +1,375 @@ +""" +Collection of style transformations. + +Think of it as a kind of color post processing after the rendering is done. +This could be used for instance to change the contrast/saturation; swap light +and dark colors or even change certain colors for other colors. + +When the UI is rendered, these transformations can be applied right after the +style strings are turned into `Attrs` objects that represent the actual +formatting. +""" +from abc import ABCMeta, abstractmethod +from colorsys import hls_to_rgb, rgb_to_hls +from typing import Callable, Hashable, Optional, Sequence, Tuple, Union + +from prompt_toolkit.cache import memoized +from prompt_toolkit.filters import FilterOrBool, to_filter +from prompt_toolkit.utils import AnyFloat, to_float, to_str + +from .base import ANSI_COLOR_NAMES, Attrs +from .style import parse_color + +__all__ = [ + "StyleTransformation", + "SwapLightAndDarkStyleTransformation", + "ReverseStyleTransformation", + "SetDefaultColorStyleTransformation", + "AdjustBrightnessStyleTransformation", + "DummyStyleTransformation", + "ConditionalStyleTransformation", + "DynamicStyleTransformation", + "merge_style_transformations", +] + + +class StyleTransformation(metaclass=ABCMeta): + """ + Base class for any style transformation. + """ + + @abstractmethod + def transform_attrs(self, attrs: Attrs) -> Attrs: + """ + Take an `Attrs` object and return a new `Attrs` object. + + Remember that the color formats can be either "ansi..." or a 6 digit + lowercase hexadecimal color (without '#' prefix). + """ + + def invalidation_hash(self) -> Hashable: + """ + When this changes, the cache should be invalidated. + """ + return f"{self.__class__.__name__}-{id(self)}" + + +class SwapLightAndDarkStyleTransformation(StyleTransformation): + """ + Turn dark colors into light colors and the other way around. + + This is meant to make color schemes that work on a dark background usable + on a light background (and the other way around). + + Notice that this doesn't swap foreground and background like "reverse" + does. It turns light green into dark green and the other way around. + Foreground and background colors are considered individually. + + Also notice that when is used somewhere and no colors are given + in particular (like what is the default for the bottom toolbar), then this + doesn't change anything. This is what makes sense, because when the + 'default' color is chosen, it's what works best for the terminal, and + reverse works good with that. + """ + + def transform_attrs(self, attrs: Attrs) -> Attrs: + """ + Return the `Attrs` used when opposite luminosity should be used. + """ + # Reverse colors. + attrs = attrs._replace(color=get_opposite_color(attrs.color)) + attrs = attrs._replace(bgcolor=get_opposite_color(attrs.bgcolor)) + + return attrs + + +class ReverseStyleTransformation(StyleTransformation): + """ + Swap the 'reverse' attribute. + + (This is still experimental.) + """ + + def transform_attrs(self, attrs: Attrs) -> Attrs: + return attrs._replace(reverse=not attrs.reverse) + + +class SetDefaultColorStyleTransformation(StyleTransformation): + """ + Set default foreground/background color for output that doesn't specify + anything. This is useful for overriding the terminal default colors. + + :param fg: Color string or callable that returns a color string for the + foreground. + :param bg: Like `fg`, but for the background. + """ + + def __init__( + self, fg: Union[str, Callable[[], str]], bg: Union[str, Callable[[], str]] + ) -> None: + + self.fg = fg + self.bg = bg + + def transform_attrs(self, attrs: Attrs) -> Attrs: + if attrs.bgcolor in ("", "default"): + attrs = attrs._replace(bgcolor=parse_color(to_str(self.bg))) + + if attrs.color in ("", "default"): + attrs = attrs._replace(color=parse_color(to_str(self.fg))) + + return attrs + + def invalidation_hash(self) -> Hashable: + return ( + "set-default-color", + to_str(self.fg), + to_str(self.bg), + ) + + +class AdjustBrightnessStyleTransformation(StyleTransformation): + """ + Adjust the brightness to improve the rendering on either dark or light + backgrounds. + + For dark backgrounds, it's best to increase `min_brightness`. For light + backgrounds it's best to decrease `max_brightness`. Usually, only one + setting is adjusted. + + This will only change the brightness for text that has a foreground color + defined, but no background color. It works best for 256 or true color + output. + + .. note:: Notice that there is no universal way to detect whether the + application is running in a light or dark terminal. As a + developer of an command line application, you'll have to make + this configurable for the user. + + :param min_brightness: Float between 0.0 and 1.0 or a callable that returns + a float. + :param max_brightness: Float between 0.0 and 1.0 or a callable that returns + a float. + """ + + def __init__( + self, min_brightness: AnyFloat = 0.0, max_brightness: AnyFloat = 1.0 + ) -> None: + + self.min_brightness = min_brightness + self.max_brightness = max_brightness + + def transform_attrs(self, attrs: Attrs) -> Attrs: + min_brightness = to_float(self.min_brightness) + max_brightness = to_float(self.max_brightness) + assert 0 <= min_brightness <= 1 + assert 0 <= max_brightness <= 1 + + # Don't do anything if the whole brightness range is acceptable. + # This also avoids turning ansi colors into RGB sequences. + if min_brightness == 0.0 and max_brightness == 1.0: + return attrs + + # If a foreground color is given without a background color. + no_background = not attrs.bgcolor or attrs.bgcolor == "default" + has_fgcolor = attrs.color and attrs.color != "ansidefault" + + if has_fgcolor and no_background: + # Calculate new RGB values. + r, g, b = self._color_to_rgb(attrs.color or "") + hue, brightness, saturation = rgb_to_hls(r, g, b) + brightness = self._interpolate_brightness( + brightness, min_brightness, max_brightness + ) + r, g, b = hls_to_rgb(hue, brightness, saturation) + new_color = f"{int(r * 255):02x}{int(g * 255):02x}{int(b * 255):02x}" + + attrs = attrs._replace(color=new_color) + + return attrs + + def _color_to_rgb(self, color: str) -> Tuple[float, float, float]: + """ + Parse `style.Attrs` color into RGB tuple. + """ + # Do RGB lookup for ANSI colors. + try: + from prompt_toolkit.output.vt100 import ANSI_COLORS_TO_RGB + + r, g, b = ANSI_COLORS_TO_RGB[color] + return r / 255.0, g / 255.0, b / 255.0 + except KeyError: + pass + + # Parse RRGGBB format. + return ( + int(color[0:2], 16) / 255.0, + int(color[2:4], 16) / 255.0, + int(color[4:6], 16) / 255.0, + ) + + # NOTE: we don't have to support named colors here. They are already + # transformed into RGB values in `style.parse_color`. + + def _interpolate_brightness( + self, value: float, min_brightness: float, max_brightness: float + ) -> float: + """ + Map the brightness to the (min_brightness..max_brightness) range. + """ + return min_brightness + (max_brightness - min_brightness) * value + + def invalidation_hash(self) -> Hashable: + return ( + "adjust-brightness", + to_float(self.min_brightness), + to_float(self.max_brightness), + ) + + +class DummyStyleTransformation(StyleTransformation): + """ + Don't transform anything at all. + """ + + def transform_attrs(self, attrs: Attrs) -> Attrs: + return attrs + + def invalidation_hash(self) -> Hashable: + # Always return the same hash for these dummy instances. + return "dummy-style-transformation" + + +class DynamicStyleTransformation(StyleTransformation): + """ + StyleTransformation class that can dynamically returns any + `StyleTransformation`. + + :param get_style_transformation: Callable that returns a + :class:`.StyleTransformation` instance. + """ + + def __init__( + self, get_style_transformation: Callable[[], Optional[StyleTransformation]] + ) -> None: + + self.get_style_transformation = get_style_transformation + + def transform_attrs(self, attrs: Attrs) -> Attrs: + style_transformation = ( + self.get_style_transformation() or DummyStyleTransformation() + ) + return style_transformation.transform_attrs(attrs) + + def invalidation_hash(self) -> Hashable: + style_transformation = ( + self.get_style_transformation() or DummyStyleTransformation() + ) + return style_transformation.invalidation_hash() + + +class ConditionalStyleTransformation(StyleTransformation): + """ + Apply the style transformation depending on a condition. + """ + + def __init__( + self, style_transformation: StyleTransformation, filter: FilterOrBool + ) -> None: + + self.style_transformation = style_transformation + self.filter = to_filter(filter) + + def transform_attrs(self, attrs: Attrs) -> Attrs: + if self.filter(): + return self.style_transformation.transform_attrs(attrs) + return attrs + + def invalidation_hash(self) -> Hashable: + return (self.filter(), self.style_transformation.invalidation_hash()) + + +class _MergedStyleTransformation(StyleTransformation): + def __init__(self, style_transformations: Sequence[StyleTransformation]) -> None: + self.style_transformations = style_transformations + + def transform_attrs(self, attrs: Attrs) -> Attrs: + for transformation in self.style_transformations: + attrs = transformation.transform_attrs(attrs) + return attrs + + def invalidation_hash(self) -> Hashable: + return tuple(t.invalidation_hash() for t in self.style_transformations) + + +def merge_style_transformations( + style_transformations: Sequence[StyleTransformation], +) -> StyleTransformation: + """ + Merge multiple transformations together. + """ + return _MergedStyleTransformation(style_transformations) + + +# Dictionary that maps ANSI color names to their opposite. This is useful for +# turning color schemes that are optimized for a black background usable for a +# white background. +OPPOSITE_ANSI_COLOR_NAMES = { + "ansidefault": "ansidefault", + "ansiblack": "ansiwhite", + "ansired": "ansibrightred", + "ansigreen": "ansibrightgreen", + "ansiyellow": "ansibrightyellow", + "ansiblue": "ansibrightblue", + "ansimagenta": "ansibrightmagenta", + "ansicyan": "ansibrightcyan", + "ansigray": "ansibrightblack", + "ansiwhite": "ansiblack", + "ansibrightred": "ansired", + "ansibrightgreen": "ansigreen", + "ansibrightyellow": "ansiyellow", + "ansibrightblue": "ansiblue", + "ansibrightmagenta": "ansimagenta", + "ansibrightcyan": "ansicyan", + "ansibrightblack": "ansigray", +} +assert set(OPPOSITE_ANSI_COLOR_NAMES.keys()) == set(ANSI_COLOR_NAMES) +assert set(OPPOSITE_ANSI_COLOR_NAMES.values()) == set(ANSI_COLOR_NAMES) + + +@memoized() +def get_opposite_color(colorname: Optional[str]) -> Optional[str]: + """ + Take a color name in either 'ansi...' format or 6 digit RGB, return the + color of opposite luminosity (same hue/saturation). + + This is used for turning color schemes that work on a light background + usable on a dark background. + """ + if colorname is None: # Because color/bgcolor can be None in `Attrs`. + return None + + # Special values. + if colorname in ("", "default"): + return colorname + + # Try ANSI color names. + try: + return OPPOSITE_ANSI_COLOR_NAMES[colorname] + except KeyError: + # Try 6 digit RGB colors. + r = int(colorname[:2], 16) / 255.0 + g = int(colorname[2:4], 16) / 255.0 + b = int(colorname[4:6], 16) / 255.0 + + h, l, s = rgb_to_hls(r, g, b) + + l = 1 - l + + r, g, b = hls_to_rgb(h, l, s) + + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + + return f"{r:02x}{g:02x}{b:02x}" diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/token.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/token.py new file mode 100644 index 0000000..76fd9c4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/token.py @@ -0,0 +1,8 @@ +""" +""" + +__all__ = [ + "ZeroWidthEscape", +] + +ZeroWidthEscape = "[ZeroWidthEscape]" diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/utils.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/utils.py new file mode 100644 index 0000000..8b501e5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/utils.py @@ -0,0 +1,325 @@ +import os +import signal +import sys +import threading +from collections import deque +from typing import ( + Callable, + ContextManager, + Deque, + Dict, + Generator, + Generic, + List, + Optional, + TypeVar, + Union, +) + +from wcwidth import wcwidth + +__all__ = [ + "Event", + "DummyContext", + "get_cwidth", + "suspend_to_background_supported", + "is_conemu_ansi", + "is_windows", + "in_main_thread", + "get_bell_environment_variable", + "get_term_environment_variable", + "take_using_weights", + "to_str", + "to_int", + "AnyFloat", + "to_float", + "is_dumb_terminal", +] + +# Used to ensure sphinx autodoc does not try to import platform-specific +# stuff when documenting win32.py modules. +SPHINX_AUTODOC_RUNNING = "sphinx.ext.autodoc" in sys.modules + +_Sender = TypeVar("_Sender", covariant=True) + + +class Event(Generic[_Sender]): + """ + Simple event to which event handlers can be attached. For instance:: + + class Cls: + def __init__(self): + # Define event. The first parameter is the sender. + self.event = Event(self) + + obj = Cls() + + def handler(sender): + pass + + # Add event handler by using the += operator. + obj.event += handler + + # Fire event. + obj.event() + """ + + def __init__( + self, sender: _Sender, handler: Optional[Callable[[_Sender], None]] = None + ) -> None: + self.sender = sender + self._handlers: List[Callable[[_Sender], None]] = [] + + if handler is not None: + self += handler + + def __call__(self) -> None: + "Fire event." + for handler in self._handlers: + handler(self.sender) + + def fire(self) -> None: + "Alias for just calling the event." + self() + + def add_handler(self, handler: Callable[[_Sender], None]) -> None: + """ + Add another handler to this callback. + (Handler should be a callable that takes exactly one parameter: the + sender object.) + """ + # Add to list of event handlers. + self._handlers.append(handler) + + def remove_handler(self, handler: Callable[[_Sender], None]) -> None: + """ + Remove a handler from this callback. + """ + if handler in self._handlers: + self._handlers.remove(handler) + + def __iadd__(self, handler: Callable[[_Sender], None]) -> "Event[_Sender]": + """ + `event += handler` notation for adding a handler. + """ + self.add_handler(handler) + return self + + def __isub__(self, handler: Callable[[_Sender], None]) -> "Event[_Sender]": + """ + `event -= handler` notation for removing a handler. + """ + self.remove_handler(handler) + return self + + +class DummyContext(ContextManager[None]): + """ + (contextlib.nested is not available on Py3) + """ + + def __enter__(self) -> None: + pass + + def __exit__(self, *a: object) -> None: + pass + + +class _CharSizesCache(Dict[str, int]): + """ + Cache for wcwidth sizes. + """ + + LONG_STRING_MIN_LEN = 64 # Minimum string length for considering it long. + MAX_LONG_STRINGS = 16 # Maximum number of long strings to remember. + + def __init__(self) -> None: + super().__init__() + # Keep track of the "long" strings in this cache. + self._long_strings: Deque[str] = deque() + + def __missing__(self, string: str) -> int: + # Note: We use the `max(0, ...` because some non printable control + # characters, like e.g. Ctrl-underscore get a -1 wcwidth value. + # It can be possible that these characters end up in the input + # text. + result: int + if len(string) == 1: + result = max(0, wcwidth(string)) + else: + result = sum(self[c] for c in string) + + # Store in cache. + self[string] = result + + # Rotate long strings. + # (It's hard to tell what we can consider short...) + if len(string) > self.LONG_STRING_MIN_LEN: + long_strings = self._long_strings + long_strings.append(string) + + if len(long_strings) > self.MAX_LONG_STRINGS: + key_to_remove = long_strings.popleft() + if key_to_remove in self: + del self[key_to_remove] + + return result + + +_CHAR_SIZES_CACHE = _CharSizesCache() + + +def get_cwidth(string: str) -> int: + """ + Return width of a string. Wrapper around ``wcwidth``. + """ + return _CHAR_SIZES_CACHE[string] + + +def suspend_to_background_supported() -> bool: + """ + Returns `True` when the Python implementation supports + suspend-to-background. This is typically `False' on Windows systems. + """ + return hasattr(signal, "SIGTSTP") + + +def is_windows() -> bool: + """ + True when we are using Windows. + """ + return sys.platform.startswith("win") # E.g. 'win32', not 'darwin' or 'linux2' + + +def is_windows_vt100_supported() -> bool: + """ + True when we are using Windows, but VT100 escape sequences are supported. + """ + # Import needs to be inline. Windows libraries are not always available. + from prompt_toolkit.output.windows10 import is_win_vt100_enabled + + return is_windows() and is_win_vt100_enabled() + + +def is_conemu_ansi() -> bool: + """ + True when the ConEmu Windows console is used. + """ + return is_windows() and os.environ.get("ConEmuANSI", "OFF") == "ON" + + +def in_main_thread() -> bool: + """ + True when the current thread is the main thread. + """ + return threading.current_thread().__class__.__name__ == "_MainThread" + + +def get_bell_environment_variable() -> bool: + """ + True if env variable is set to true (true, TRUE, TrUe, 1). + """ + value = os.environ.get("PROMPT_TOOLKIT_BELL", "true") + return value.lower() in ("1", "true") + + +def get_term_environment_variable() -> str: + "Return the $TERM environment variable." + return os.environ.get("TERM", "") + + +_T = TypeVar("_T") + + +def take_using_weights( + items: List[_T], weights: List[int] +) -> Generator[_T, None, None]: + """ + Generator that keeps yielding items from the items list, in proportion to + their weight. For instance:: + + # Getting the first 70 items from this generator should have yielded 10 + # times A, 20 times B and 40 times C, all distributed equally.. + take_using_weights(['A', 'B', 'C'], [5, 10, 20]) + + :param items: List of items to take from. + :param weights: Integers representing the weight. (Numbers have to be + integers, not floats.) + """ + assert len(items) == len(weights) + assert len(items) > 0 + + # Remove items with zero-weight. + items2 = [] + weights2 = [] + for item, w in zip(items, weights): + if w > 0: + items2.append(item) + weights2.append(w) + + items = items2 + weights = weights2 + + # Make sure that we have some items left. + if not items: + raise ValueError("Did't got any items with a positive weight.") + + # + already_taken = [0 for i in items] + item_count = len(items) + max_weight = max(weights) + + i = 0 + while True: + # Each iteration of this loop, we fill up until by (total_weight/max_weight). + adding = True + while adding: + adding = False + + for item_i, item, weight in zip(range(item_count), items, weights): + if already_taken[item_i] < i * weight / float(max_weight): + yield item + already_taken[item_i] += 1 + adding = True + + i += 1 + + +def to_str(value: Union[Callable[[], str], str]) -> str: + "Turn callable or string into string." + if callable(value): + return to_str(value()) + else: + return str(value) + + +def to_int(value: Union[Callable[[], int], int]) -> int: + "Turn callable or int into int." + if callable(value): + return to_int(value()) + else: + return int(value) + + +AnyFloat = Union[Callable[[], float], float] + + +def to_float(value: AnyFloat) -> float: + "Turn callable or float into float." + if callable(value): + return to_float(value()) + else: + return float(value) + + +def is_dumb_terminal(term: Optional[str] = None) -> bool: + """ + True if this terminal type is considered "dumb". + + If so, we should fall back to the simplest possible form of line editing, + without cursor positioning and color support. + """ + if term is None: + return is_dumb_terminal(os.environ.get("TERM", "")) + + return term.lower() in ["dumb", "unknown"] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/validation.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/validation.py new file mode 100644 index 0000000..8bdffff --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/validation.py @@ -0,0 +1,194 @@ +""" +Input validation for a `Buffer`. +(Validators will be called before accepting input.) +""" +from abc import ABCMeta, abstractmethod +from typing import Callable, Optional + +from prompt_toolkit.eventloop import run_in_executor_with_context + +from .document import Document +from .filters import FilterOrBool, to_filter + +__all__ = [ + "ConditionalValidator", + "ValidationError", + "Validator", + "ThreadedValidator", + "DummyValidator", + "DynamicValidator", +] + + +class ValidationError(Exception): + """ + Error raised by :meth:`.Validator.validate`. + + :param cursor_position: The cursor position where the error occurred. + :param message: Text. + """ + + def __init__(self, cursor_position: int = 0, message: str = "") -> None: + super().__init__(message) + self.cursor_position = cursor_position + self.message = message + + def __repr__(self) -> str: + return "{}(cursor_position={!r}, message={!r})".format( + self.__class__.__name__, + self.cursor_position, + self.message, + ) + + +class Validator(metaclass=ABCMeta): + """ + Abstract base class for an input validator. + + A validator is typically created in one of the following two ways: + + - Either by overriding this class and implementing the `validate` method. + - Or by passing a callable to `Validator.from_callable`. + + If the validation takes some time and needs to happen in a background + thread, this can be wrapped in a :class:`.ThreadedValidator`. + """ + + @abstractmethod + def validate(self, document: Document) -> None: + """ + Validate the input. + If invalid, this should raise a :class:`.ValidationError`. + + :param document: :class:`~prompt_toolkit.document.Document` instance. + """ + pass + + async def validate_async(self, document: Document) -> None: + """ + Return a `Future` which is set when the validation is ready. + This function can be overloaded in order to provide an asynchronous + implementation. + """ + try: + self.validate(document) + except ValidationError: + raise + + @classmethod + def from_callable( + cls, + validate_func: Callable[[str], bool], + error_message: str = "Invalid input", + move_cursor_to_end: bool = False, + ) -> "Validator": + """ + Create a validator from a simple validate callable. E.g.: + + .. code:: python + + def is_valid(text): + return text in ['hello', 'world'] + Validator.from_callable(is_valid, error_message='Invalid input') + + :param validate_func: Callable that takes the input string, and returns + `True` if the input is valid input. + :param error_message: Message to be displayed if the input is invalid. + :param move_cursor_to_end: Move the cursor to the end of the input, if + the input is invalid. + """ + return _ValidatorFromCallable(validate_func, error_message, move_cursor_to_end) + + +class _ValidatorFromCallable(Validator): + """ + Validate input from a simple callable. + """ + + def __init__( + self, func: Callable[[str], bool], error_message: str, move_cursor_to_end: bool + ) -> None: + + self.func = func + self.error_message = error_message + self.move_cursor_to_end = move_cursor_to_end + + def __repr__(self) -> str: + return f"Validator.from_callable({self.func!r})" + + def validate(self, document: Document) -> None: + if not self.func(document.text): + if self.move_cursor_to_end: + index = len(document.text) + else: + index = 0 + + raise ValidationError(cursor_position=index, message=self.error_message) + + +class ThreadedValidator(Validator): + """ + Wrapper that runs input validation in a thread. + (Use this to prevent the user interface from becoming unresponsive if the + input validation takes too much time.) + """ + + def __init__(self, validator: Validator) -> None: + self.validator = validator + + def validate(self, document: Document) -> None: + self.validator.validate(document) + + async def validate_async(self, document: Document) -> None: + """ + Run the `validate` function in a thread. + """ + + def run_validation_thread() -> None: + return self.validate(document) + + await run_in_executor_with_context(run_validation_thread) + + +class DummyValidator(Validator): + """ + Validator class that accepts any input. + """ + + def validate(self, document: Document) -> None: + pass # Don't raise any exception. + + +class ConditionalValidator(Validator): + """ + Validator that can be switched on/off according to + a filter. (This wraps around another validator.) + """ + + def __init__(self, validator: Validator, filter: FilterOrBool) -> None: + self.validator = validator + self.filter = to_filter(filter) + + def validate(self, document: Document) -> None: + # Call the validator only if the filter is active. + if self.filter(): + self.validator.validate(document) + + +class DynamicValidator(Validator): + """ + Validator class that can dynamically returns any Validator. + + :param get_validator: Callable that returns a :class:`.Validator` instance. + """ + + def __init__(self, get_validator: Callable[[], Optional[Validator]]) -> None: + self.get_validator = get_validator + + def validate(self, document: Document) -> None: + validator = self.get_validator() or DummyValidator() + validator.validate(document) + + async def validate_async(self, document: Document) -> None: + validator = self.get_validator() or DummyValidator() + await validator.validate_async(document) diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__init__.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__init__.py new file mode 100644 index 0000000..552d355 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__init__.py @@ -0,0 +1,60 @@ +""" +Collection of reusable components for building full screen applications. +These are higher level abstractions on top of the `prompt_toolkit.layout` +module. + +Most of these widgets implement the ``__pt_container__`` method, which makes it +possible to embed these in the layout like any other container. +""" +from .base import ( + Box, + Button, + Checkbox, + CheckboxList, + Frame, + HorizontalLine, + Label, + ProgressBar, + RadioList, + Shadow, + TextArea, + VerticalLine, +) +from .dialogs import Dialog +from .menus import MenuContainer, MenuItem +from .toolbars import ( + ArgToolbar, + CompletionsToolbar, + FormattedTextToolbar, + SearchToolbar, + SystemToolbar, + ValidationToolbar, +) + +__all__ = [ + # Base. + "TextArea", + "Label", + "Button", + "Frame", + "Shadow", + "Box", + "VerticalLine", + "HorizontalLine", + "CheckboxList", + "RadioList", + "Checkbox", + "ProgressBar", + # Toolbars. + "ArgToolbar", + "CompletionsToolbar", + "FormattedTextToolbar", + "SearchToolbar", + "SystemToolbar", + "ValidationToolbar", + # Dialogs. + "Dialog", + # Menus. + "MenuContainer", + "MenuItem", +] diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..bcfe221 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/base.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..eab96b7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/base.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/dialogs.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/dialogs.cpython-38.pyc new file mode 100644 index 0000000..10fe99e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/dialogs.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/menus.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/menus.cpython-38.pyc new file mode 100644 index 0000000..e9c36b8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/menus.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/toolbars.cpython-38.pyc b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/toolbars.cpython-38.pyc new file mode 100644 index 0000000..f71a99a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/__pycache__/toolbars.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/base.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/base.py new file mode 100644 index 0000000..885d23a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/base.py @@ -0,0 +1,978 @@ +""" +Collection of reusable components for building full screen applications. + +All of these widgets implement the ``__pt_container__`` method, which makes +them usable in any situation where we are expecting a `prompt_toolkit` +container object. + +.. warning:: + + At this point, the API for these widgets is considered unstable, and can + potentially change between minor releases (we try not too, but no + guarantees are made yet). The public API in + `prompt_toolkit.shortcuts.dialogs` on the other hand is considered stable. +""" +from functools import partial +from typing import Callable, Generic, List, Optional, Sequence, Tuple, TypeVar, Union + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest +from prompt_toolkit.buffer import Buffer, BufferAcceptHandler +from prompt_toolkit.completion import Completer, DynamicCompleter +from prompt_toolkit.document import Document +from prompt_toolkit.filters import ( + Condition, + FilterOrBool, + has_focus, + is_done, + is_true, + to_filter, +) +from prompt_toolkit.formatted_text import ( + AnyFormattedText, + StyleAndTextTuples, + Template, + to_formatted_text, +) +from prompt_toolkit.formatted_text.utils import fragment_list_to_text +from prompt_toolkit.history import History +from prompt_toolkit.key_binding.key_bindings import KeyBindings +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout.containers import ( + AnyContainer, + ConditionalContainer, + Container, + DynamicContainer, + Float, + FloatContainer, + HSplit, + VSplit, + Window, + WindowAlign, +) +from prompt_toolkit.layout.controls import ( + BufferControl, + FormattedTextControl, + GetLinePrefixCallable, +) +from prompt_toolkit.layout.dimension import AnyDimension +from prompt_toolkit.layout.dimension import Dimension as D +from prompt_toolkit.layout.dimension import to_dimension +from prompt_toolkit.layout.margins import ( + ConditionalMargin, + NumberedMargin, + ScrollbarMargin, +) +from prompt_toolkit.layout.processors import ( + AppendAutoSuggestion, + BeforeInput, + ConditionalProcessor, + PasswordProcessor, + Processor, +) +from prompt_toolkit.lexers import DynamicLexer, Lexer +from prompt_toolkit.mouse_events import MouseEvent, MouseEventType +from prompt_toolkit.utils import get_cwidth +from prompt_toolkit.validation import DynamicValidator, Validator + +from .toolbars import SearchToolbar + +__all__ = [ + "TextArea", + "Label", + "Button", + "Frame", + "Shadow", + "Box", + "VerticalLine", + "HorizontalLine", + "RadioList", + "CheckboxList", + "Checkbox", # backward compatibility + "ProgressBar", +] + +E = KeyPressEvent + + +class Border: + "Box drawing characters. (Thin)" + HORIZONTAL = "\u2500" + VERTICAL = "\u2502" + TOP_LEFT = "\u250c" + TOP_RIGHT = "\u2510" + BOTTOM_LEFT = "\u2514" + BOTTOM_RIGHT = "\u2518" + + +class TextArea: + """ + A simple input field. + + This is a higher level abstraction on top of several other classes with + sane defaults. + + This widget does have the most common options, but it does not intend to + cover every single use case. For more configurations options, you can + always build a text area manually, using a + :class:`~prompt_toolkit.buffer.Buffer`, + :class:`~prompt_toolkit.layout.BufferControl` and + :class:`~prompt_toolkit.layout.Window`. + + Buffer attributes: + + :param text: The initial text. + :param multiline: If True, allow multiline input. + :param completer: :class:`~prompt_toolkit.completion.Completer` instance + for auto completion. + :param complete_while_typing: Boolean. + :param accept_handler: Called when `Enter` is pressed (This should be a + callable that takes a buffer as input). + :param history: :class:`~prompt_toolkit.history.History` instance. + :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` + instance for input suggestions. + + BufferControl attributes: + + :param password: When `True`, display using asterisks. + :param focusable: When `True`, allow this widget to receive the focus. + :param focus_on_click: When `True`, focus after mouse click. + :param input_processors: `None` or a list of + :class:`~prompt_toolkit.layout.Processor` objects. + :param validator: `None` or a :class:`~prompt_toolkit.validation.Validator` + object. + + Window attributes: + + :param lexer: :class:`~prompt_toolkit.lexers.Lexer` instance for syntax + highlighting. + :param wrap_lines: When `True`, don't scroll horizontally, but wrap lines. + :param width: Window width. (:class:`~prompt_toolkit.layout.Dimension` object.) + :param height: Window height. (:class:`~prompt_toolkit.layout.Dimension` object.) + :param scrollbar: When `True`, display a scroll bar. + :param style: A style string. + :param dont_extend_width: When `True`, don't take up more width then the + preferred width reported by the control. + :param dont_extend_height: When `True`, don't take up more width then the + preferred height reported by the control. + :param get_line_prefix: None or a callable that returns formatted text to + be inserted before a line. It takes a line number (int) and a + wrap_count and returns formatted text. This can be used for + implementation of line continuations, things like Vim "breakindent" and + so on. + + Other attributes: + + :param search_field: An optional `SearchToolbar` object. + """ + + def __init__( + self, + text: str = "", + multiline: FilterOrBool = True, + password: FilterOrBool = False, + lexer: Optional[Lexer] = None, + auto_suggest: Optional[AutoSuggest] = None, + completer: Optional[Completer] = None, + complete_while_typing: FilterOrBool = True, + validator: Optional[Validator] = None, + accept_handler: Optional[BufferAcceptHandler] = None, + history: Optional[History] = None, + focusable: FilterOrBool = True, + focus_on_click: FilterOrBool = False, + wrap_lines: FilterOrBool = True, + read_only: FilterOrBool = False, + width: AnyDimension = None, + height: AnyDimension = None, + dont_extend_height: FilterOrBool = False, + dont_extend_width: FilterOrBool = False, + line_numbers: bool = False, + get_line_prefix: Optional[GetLinePrefixCallable] = None, + scrollbar: bool = False, + style: str = "", + search_field: Optional[SearchToolbar] = None, + preview_search: FilterOrBool = True, + prompt: AnyFormattedText = "", + input_processors: Optional[List[Processor]] = None, + ) -> None: + + if search_field is None: + search_control = None + elif isinstance(search_field, SearchToolbar): + search_control = search_field.control + + if input_processors is None: + input_processors = [] + + # Writeable attributes. + self.completer = completer + self.complete_while_typing = complete_while_typing + self.lexer = lexer + self.auto_suggest = auto_suggest + self.read_only = read_only + self.wrap_lines = wrap_lines + self.validator = validator + + self.buffer = Buffer( + document=Document(text, 0), + multiline=multiline, + read_only=Condition(lambda: is_true(self.read_only)), + completer=DynamicCompleter(lambda: self.completer), + complete_while_typing=Condition( + lambda: is_true(self.complete_while_typing) + ), + validator=DynamicValidator(lambda: self.validator), + auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), + accept_handler=accept_handler, + history=history, + ) + + self.control = BufferControl( + buffer=self.buffer, + lexer=DynamicLexer(lambda: self.lexer), + input_processors=[ + ConditionalProcessor( + AppendAutoSuggestion(), has_focus(self.buffer) & ~is_done + ), + ConditionalProcessor( + processor=PasswordProcessor(), filter=to_filter(password) + ), + BeforeInput(prompt, style="class:text-area.prompt"), + ] + + input_processors, + search_buffer_control=search_control, + preview_search=preview_search, + focusable=focusable, + focus_on_click=focus_on_click, + ) + + if multiline: + if scrollbar: + right_margins = [ScrollbarMargin(display_arrows=True)] + else: + right_margins = [] + if line_numbers: + left_margins = [NumberedMargin()] + else: + left_margins = [] + else: + height = D.exact(1) + left_margins = [] + right_margins = [] + + style = "class:text-area " + style + + # If no height was given, guarantee height of at least 1. + if height is None: + height = D(min=1) + + self.window = Window( + height=height, + width=width, + dont_extend_height=dont_extend_height, + dont_extend_width=dont_extend_width, + content=self.control, + style=style, + wrap_lines=Condition(lambda: is_true(self.wrap_lines)), + left_margins=left_margins, + right_margins=right_margins, + get_line_prefix=get_line_prefix, + ) + + @property + def text(self) -> str: + """ + The `Buffer` text. + """ + return self.buffer.text + + @text.setter + def text(self, value: str) -> None: + self.document = Document(value, 0) + + @property + def document(self) -> Document: + """ + The `Buffer` document (text + cursor position). + """ + return self.buffer.document + + @document.setter + def document(self, value: Document) -> None: + self.buffer.set_document(value, bypass_readonly=True) + + @property + def accept_handler(self) -> Optional[BufferAcceptHandler]: + """ + The accept handler. Called when the user accepts the input. + """ + return self.buffer.accept_handler + + @accept_handler.setter + def accept_handler(self, value: BufferAcceptHandler) -> None: + self.buffer.accept_handler = value + + def __pt_container__(self) -> Container: + return self.window + + +class Label: + """ + Widget that displays the given text. It is not editable or focusable. + + :param text: Text to display. Can be multiline. All value types accepted by + :class:`prompt_toolkit.layout.FormattedTextControl` are allowed, + including a callable. + :param style: A style string. + :param width: When given, use this width, rather than calculating it from + the text size. + :param dont_extend_width: When `True`, don't take up more width than + preferred, i.e. the length of the longest line of + the text, or value of `width` parameter, if + given. `True` by default + :param dont_extend_height: When `True`, don't take up more width than the + preferred height, i.e. the number of lines of + the text. `False` by default. + """ + + def __init__( + self, + text: AnyFormattedText, + style: str = "", + width: AnyDimension = None, + dont_extend_height: bool = True, + dont_extend_width: bool = False, + align: Union[WindowAlign, Callable[[], WindowAlign]] = WindowAlign.LEFT, + ) -> None: + + self.text = text + + def get_width() -> AnyDimension: + if width is None: + text_fragments = to_formatted_text(self.text) + text = fragment_list_to_text(text_fragments) + if text: + longest_line = max(get_cwidth(line) for line in text.splitlines()) + else: + return D(preferred=0) + return D(preferred=longest_line) + else: + return width + + self.formatted_text_control = FormattedTextControl(text=lambda: self.text) + + self.window = Window( + content=self.formatted_text_control, + width=get_width, + height=D(min=1), + style="class:label " + style, + dont_extend_height=dont_extend_height, + dont_extend_width=dont_extend_width, + align=align, + ) + + def __pt_container__(self) -> Container: + return self.window + + +class Button: + """ + Clickable button. + + :param text: The caption for the button. + :param handler: `None` or callable. Called when the button is clicked. No + parameters are passed to this callable. Use for instance Python's + `functools.partial` to pass parameters to this callable if needed. + :param width: Width of the button. + """ + + def __init__( + self, + text: str, + handler: Optional[Callable[[], None]] = None, + width: int = 12, + left_symbol: str = "<", + right_symbol: str = ">", + ) -> None: + + self.text = text + self.left_symbol = left_symbol + self.right_symbol = right_symbol + self.handler = handler + self.width = width + self.control = FormattedTextControl( + self._get_text_fragments, + key_bindings=self._get_key_bindings(), + focusable=True, + ) + + def get_style() -> str: + if get_app().layout.has_focus(self): + return "class:button.focused" + else: + return "class:button" + + # Note: `dont_extend_width` is False, because we want to allow buttons + # to take more space if the parent container provides more space. + # Otherwise, we will also truncate the text. + # Probably we need a better way here to adjust to width of the + # button to the text. + + self.window = Window( + self.control, + align=WindowAlign.CENTER, + height=1, + width=width, + style=get_style, + dont_extend_width=False, + dont_extend_height=True, + ) + + def _get_text_fragments(self) -> StyleAndTextTuples: + width = self.width - ( + get_cwidth(self.left_symbol) + get_cwidth(self.right_symbol) + ) + text = (f"{{:^{width}}}").format(self.text) + + def handler(mouse_event: MouseEvent) -> None: + if ( + self.handler is not None + and mouse_event.event_type == MouseEventType.MOUSE_UP + ): + self.handler() + + return [ + ("class:button.arrow", self.left_symbol, handler), + ("[SetCursorPosition]", ""), + ("class:button.text", text, handler), + ("class:button.arrow", self.right_symbol, handler), + ] + + def _get_key_bindings(self) -> KeyBindings: + "Key bindings for the Button." + kb = KeyBindings() + + @kb.add(" ") + @kb.add("enter") + def _(event: E) -> None: + if self.handler is not None: + self.handler() + + return kb + + def __pt_container__(self) -> Container: + return self.window + + +class Frame: + """ + Draw a border around any container, optionally with a title text. + + Changing the title and body of the frame is possible at runtime by + assigning to the `body` and `title` attributes of this class. + + :param body: Another container object. + :param title: Text to be displayed in the top of the frame (can be formatted text). + :param style: Style string to be applied to this widget. + """ + + def __init__( + self, + body: AnyContainer, + title: AnyFormattedText = "", + style: str = "", + width: AnyDimension = None, + height: AnyDimension = None, + key_bindings: Optional[KeyBindings] = None, + modal: bool = False, + ) -> None: + + self.title = title + self.body = body + + fill = partial(Window, style="class:frame.border") + style = "class:frame " + style + + top_row_with_title = VSplit( + [ + fill(width=1, height=1, char=Border.TOP_LEFT), + fill(char=Border.HORIZONTAL), + fill(width=1, height=1, char="|"), + # Notice: we use `Template` here, because `self.title` can be an + # `HTML` object for instance. + Label( + lambda: Template(" {} ").format(self.title), + style="class:frame.label", + dont_extend_width=True, + ), + fill(width=1, height=1, char="|"), + fill(char=Border.HORIZONTAL), + fill(width=1, height=1, char=Border.TOP_RIGHT), + ], + height=1, + ) + + top_row_without_title = VSplit( + [ + fill(width=1, height=1, char=Border.TOP_LEFT), + fill(char=Border.HORIZONTAL), + fill(width=1, height=1, char=Border.TOP_RIGHT), + ], + height=1, + ) + + @Condition + def has_title() -> bool: + return bool(self.title) + + self.container = HSplit( + [ + ConditionalContainer(content=top_row_with_title, filter=has_title), + ConditionalContainer(content=top_row_without_title, filter=~has_title), + VSplit( + [ + fill(width=1, char=Border.VERTICAL), + DynamicContainer(lambda: self.body), + fill(width=1, char=Border.VERTICAL), + # Padding is required to make sure that if the content is + # too small, the right frame border is still aligned. + ], + padding=0, + ), + VSplit( + [ + fill(width=1, height=1, char=Border.BOTTOM_LEFT), + fill(char=Border.HORIZONTAL), + fill(width=1, height=1, char=Border.BOTTOM_RIGHT), + ], + # specifying height here will increase the rendering speed. + height=1, + ), + ], + width=width, + height=height, + style=style, + key_bindings=key_bindings, + modal=modal, + ) + + def __pt_container__(self) -> Container: + return self.container + + +class Shadow: + """ + Draw a shadow underneath/behind this container. + (This applies `class:shadow` the the cells under the shadow. The Style + should define the colors for the shadow.) + + :param body: Another container object. + """ + + def __init__(self, body: AnyContainer) -> None: + self.container = FloatContainer( + content=body, + floats=[ + Float( + bottom=-1, + height=1, + left=1, + right=-1, + transparent=True, + content=Window(style="class:shadow"), + ), + Float( + bottom=-1, + top=1, + width=1, + right=-1, + transparent=True, + content=Window(style="class:shadow"), + ), + ], + ) + + def __pt_container__(self) -> Container: + return self.container + + +class Box: + """ + Add padding around a container. + + This also makes sure that the parent can provide more space than required by + the child. This is very useful when wrapping a small element with a fixed + size into a ``VSplit`` or ``HSplit`` object. The ``HSplit`` and ``VSplit`` + try to make sure to adapt respectively the width and height, possibly + shrinking other elements. Wrapping something in a ``Box`` makes it flexible. + + :param body: Another container object. + :param padding: The margin to be used around the body. This can be + overridden by `padding_left`, padding_right`, `padding_top` and + `padding_bottom`. + :param style: A style string. + :param char: Character to be used for filling the space around the body. + (This is supposed to be a character with a terminal width of 1.) + """ + + def __init__( + self, + body: AnyContainer, + padding: AnyDimension = None, + padding_left: AnyDimension = None, + padding_right: AnyDimension = None, + padding_top: AnyDimension = None, + padding_bottom: AnyDimension = None, + width: AnyDimension = None, + height: AnyDimension = None, + style: str = "", + char: Union[None, str, Callable[[], str]] = None, + modal: bool = False, + key_bindings: Optional[KeyBindings] = None, + ) -> None: + + if padding is None: + padding = D(preferred=0) + + def get(value: AnyDimension) -> D: + if value is None: + value = padding + return to_dimension(value) + + self.padding_left = get(padding_left) + self.padding_right = get(padding_right) + self.padding_top = get(padding_top) + self.padding_bottom = get(padding_bottom) + self.body = body + + self.container = HSplit( + [ + Window(height=self.padding_top, char=char), + VSplit( + [ + Window(width=self.padding_left, char=char), + body, + Window(width=self.padding_right, char=char), + ] + ), + Window(height=self.padding_bottom, char=char), + ], + width=width, + height=height, + style=style, + modal=modal, + key_bindings=None, + ) + + def __pt_container__(self) -> Container: + return self.container + + +_T = TypeVar("_T") + + +class _DialogList(Generic[_T]): + """ + Common code for `RadioList` and `CheckboxList`. + """ + + open_character: str = "" + close_character: str = "" + container_style: str = "" + default_style: str = "" + selected_style: str = "" + checked_style: str = "" + multiple_selection: bool = False + show_scrollbar: bool = True + + def __init__( + self, + values: Sequence[Tuple[_T, AnyFormattedText]], + default_values: Optional[Sequence[_T]] = None, + ) -> None: + assert len(values) > 0 + default_values = default_values or [] + + self.values = values + # current_values will be used in multiple_selection, + # current_value will be used otherwise. + keys: List[_T] = [value for (value, _) in values] + self.current_values: List[_T] = [ + value for value in default_values if value in keys + ] + self.current_value: _T = ( + default_values[0] + if len(default_values) and default_values[0] in keys + else values[0][0] + ) + + # Cursor index: take first selected item or first item otherwise. + if len(self.current_values) > 0: + self._selected_index = keys.index(self.current_values[0]) + else: + self._selected_index = 0 + + # Key bindings. + kb = KeyBindings() + + @kb.add("up") + def _up(event: E) -> None: + self._selected_index = max(0, self._selected_index - 1) + + @kb.add("down") + def _down(event: E) -> None: + self._selected_index = min(len(self.values) - 1, self._selected_index + 1) + + @kb.add("pageup") + def _pageup(event: E) -> None: + w = event.app.layout.current_window + if w.render_info: + self._selected_index = max( + 0, self._selected_index - len(w.render_info.displayed_lines) + ) + + @kb.add("pagedown") + def _pagedown(event: E) -> None: + w = event.app.layout.current_window + if w.render_info: + self._selected_index = min( + len(self.values) - 1, + self._selected_index + len(w.render_info.displayed_lines), + ) + + @kb.add("enter") + @kb.add(" ") + def _click(event: E) -> None: + self._handle_enter() + + @kb.add(Keys.Any) + def _find(event: E) -> None: + # We first check values after the selected value, then all values. + values = list(self.values) + for value in values[self._selected_index + 1 :] + values: + text = fragment_list_to_text(to_formatted_text(value[1])).lower() + + if text.startswith(event.data.lower()): + self._selected_index = self.values.index(value) + return + + # Control and window. + self.control = FormattedTextControl( + self._get_text_fragments, key_bindings=kb, focusable=True + ) + + self.window = Window( + content=self.control, + style=self.container_style, + right_margins=[ + ConditionalMargin( + margin=ScrollbarMargin(display_arrows=True), + filter=Condition(lambda: self.show_scrollbar), + ), + ], + dont_extend_height=True, + ) + + def _handle_enter(self) -> None: + if self.multiple_selection: + val = self.values[self._selected_index][0] + if val in self.current_values: + self.current_values.remove(val) + else: + self.current_values.append(val) + else: + self.current_value = self.values[self._selected_index][0] + + def _get_text_fragments(self) -> StyleAndTextTuples: + def mouse_handler(mouse_event: MouseEvent) -> None: + """ + Set `_selected_index` and `current_value` according to the y + position of the mouse click event. + """ + if mouse_event.event_type == MouseEventType.MOUSE_UP: + self._selected_index = mouse_event.position.y + self._handle_enter() + + result: StyleAndTextTuples = [] + for i, value in enumerate(self.values): + if self.multiple_selection: + checked = value[0] in self.current_values + else: + checked = value[0] == self.current_value + selected = i == self._selected_index + + style = "" + if checked: + style += " " + self.checked_style + if selected: + style += " " + self.selected_style + + result.append((style, self.open_character)) + + if selected: + result.append(("[SetCursorPosition]", "")) + + if checked: + result.append((style, "*")) + else: + result.append((style, " ")) + + result.append((style, self.close_character)) + result.append((self.default_style, " ")) + result.extend(to_formatted_text(value[1], style=self.default_style)) + result.append(("", "\n")) + + # Add mouse handler to all fragments. + for i in range(len(result)): + result[i] = (result[i][0], result[i][1], mouse_handler) + + result.pop() # Remove last newline. + return result + + def __pt_container__(self) -> Container: + return self.window + + +class RadioList(_DialogList[_T]): + """ + List of radio buttons. Only one can be checked at the same time. + + :param values: List of (value, label) tuples. + """ + + open_character = "(" + close_character = ")" + container_style = "class:radio-list" + default_style = "class:radio" + selected_style = "class:radio-selected" + checked_style = "class:radio-checked" + multiple_selection = False + + def __init__( + self, + values: Sequence[Tuple[_T, AnyFormattedText]], + default: Optional[_T] = None, + ) -> None: + if default is None: + default_values = None + else: + default_values = [default] + + super().__init__(values, default_values=default_values) + + +class CheckboxList(_DialogList[_T]): + """ + List of checkbox buttons. Several can be checked at the same time. + + :param values: List of (value, label) tuples. + """ + + open_character = "[" + close_character = "]" + container_style = "class:checkbox-list" + default_style = "class:checkbox" + selected_style = "class:checkbox-selected" + checked_style = "class:checkbox-checked" + multiple_selection = True + + +class Checkbox(CheckboxList[str]): + """Backward compatibility util: creates a 1-sized CheckboxList + + :param text: the text + """ + + show_scrollbar = False + + def __init__(self, text: AnyFormattedText = "", checked: bool = False) -> None: + values = [("value", text)] + CheckboxList.__init__(self, values=values) + self.checked = checked + + @property + def checked(self) -> bool: + return "value" in self.current_values + + @checked.setter + def checked(self, value: bool) -> None: + if value: + self.current_values = ["value"] + else: + self.current_values = [] + + +class VerticalLine: + """ + A simple vertical line with a width of 1. + """ + + def __init__(self) -> None: + self.window = Window( + char=Border.VERTICAL, style="class:line,vertical-line", width=1 + ) + + def __pt_container__(self) -> Container: + return self.window + + +class HorizontalLine: + """ + A simple horizontal line with a height of 1. + """ + + def __init__(self) -> None: + self.window = Window( + char=Border.HORIZONTAL, style="class:line,horizontal-line", height=1 + ) + + def __pt_container__(self) -> Container: + return self.window + + +class ProgressBar: + def __init__(self) -> None: + self._percentage = 60 + + self.label = Label("60%") + self.container = FloatContainer( + content=Window(height=1), + floats=[ + # We first draw the label, then the actual progress bar. Right + # now, this is the only way to have the colors of the progress + # bar appear on top of the label. The problem is that our label + # can't be part of any `Window` below. + Float(content=self.label, top=0, bottom=0), + Float( + left=0, + top=0, + right=0, + bottom=0, + content=VSplit( + [ + Window( + style="class:progress-bar.used", + width=lambda: D(weight=int(self._percentage)), + ), + Window( + style="class:progress-bar", + width=lambda: D(weight=int(100 - self._percentage)), + ), + ] + ), + ), + ], + ) + + @property + def percentage(self) -> int: + return self._percentage + + @percentage.setter + def percentage(self, value: int) -> None: + self._percentage = value + self.label.text = f"{value}%" + + def __pt_container__(self) -> Container: + return self.container diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/dialogs.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/dialogs.py new file mode 100644 index 0000000..920582b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/dialogs.py @@ -0,0 +1,106 @@ +""" +Collection of reusable components for building full screen applications. +""" +from typing import Optional, Sequence, Union + +from prompt_toolkit.filters import has_completions, has_focus +from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.key_binding.key_bindings import KeyBindings +from prompt_toolkit.layout.containers import ( + AnyContainer, + DynamicContainer, + HSplit, + VSplit, +) +from prompt_toolkit.layout.dimension import AnyDimension +from prompt_toolkit.layout.dimension import Dimension as D + +from .base import Box, Button, Frame, Shadow + +__all__ = [ + "Dialog", +] + + +class Dialog: + """ + Simple dialog window. This is the base for input dialogs, message dialogs + and confirmation dialogs. + + Changing the title and body of the dialog is possible at runtime by + assigning to the `body` and `title` attributes of this class. + + :param body: Child container object. + :param title: Text to be displayed in the heading of the dialog. + :param buttons: A list of `Button` widgets, displayed at the bottom. + """ + + def __init__( + self, + body: AnyContainer, + title: AnyFormattedText = "", + buttons: Optional[Sequence[Button]] = None, + modal: bool = True, + width: AnyDimension = None, + with_background: bool = False, + ) -> None: + + self.body = body + self.title = title + + buttons = buttons or [] + + # When a button is selected, handle left/right key bindings. + buttons_kb = KeyBindings() + if len(buttons) > 1: + first_selected = has_focus(buttons[0]) + last_selected = has_focus(buttons[-1]) + + buttons_kb.add("left", filter=~first_selected)(focus_previous) + buttons_kb.add("right", filter=~last_selected)(focus_next) + + frame_body: AnyContainer + if buttons: + frame_body = HSplit( + [ + # Add optional padding around the body. + Box( + body=DynamicContainer(lambda: self.body), + padding=D(preferred=1, max=1), + padding_bottom=0, + ), + # The buttons. + Box( + body=VSplit(buttons, padding=1, key_bindings=buttons_kb), + height=D(min=1, max=3, preferred=3), + ), + ] + ) + else: + frame_body = body + + # Key bindings for whole dialog. + kb = KeyBindings() + kb.add("tab", filter=~has_completions)(focus_next) + kb.add("s-tab", filter=~has_completions)(focus_previous) + + frame = Shadow( + body=Frame( + title=lambda: self.title, + body=frame_body, + style="class:dialog.body", + width=(None if with_background is None else width), + key_bindings=kb, + modal=modal, + ) + ) + + self.container: Union[Box, Shadow] + if with_background: + self.container = Box(body=frame, style="class:dialog", width=width) + else: + self.container = frame + + def __pt_container__(self) -> AnyContainer: + return self.container diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/menus.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/menus.py new file mode 100644 index 0000000..6827ebe --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/menus.py @@ -0,0 +1,374 @@ +from typing import Callable, Iterable, List, Optional, Sequence, Union + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.filters import Condition +from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple, StyleAndTextTuples +from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout.containers import ( + AnyContainer, + ConditionalContainer, + Container, + Float, + FloatContainer, + HSplit, + Window, +) +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.mouse_events import MouseEvent, MouseEventType +from prompt_toolkit.utils import get_cwidth +from prompt_toolkit.widgets import Shadow + +from .base import Border + +__all__ = [ + "MenuContainer", + "MenuItem", +] + +E = KeyPressEvent + + +class MenuContainer: + """ + :param floats: List of extra Float objects to display. + :param menu_items: List of `MenuItem` objects. + """ + + def __init__( + self, + body: AnyContainer, + menu_items: List["MenuItem"], + floats: Optional[List[Float]] = None, + key_bindings: Optional[KeyBindingsBase] = None, + ) -> None: + + self.body = body + self.menu_items = menu_items + self.selected_menu = [0] + + # Key bindings. + kb = KeyBindings() + + @Condition + def in_main_menu() -> bool: + return len(self.selected_menu) == 1 + + @Condition + def in_sub_menu() -> bool: + return len(self.selected_menu) > 1 + + # Navigation through the main menu. + + @kb.add("left", filter=in_main_menu) + def _left(event: E) -> None: + self.selected_menu[0] = max(0, self.selected_menu[0] - 1) + + @kb.add("right", filter=in_main_menu) + def _right(event: E) -> None: + self.selected_menu[0] = min( + len(self.menu_items) - 1, self.selected_menu[0] + 1 + ) + + @kb.add("down", filter=in_main_menu) + def _down(event: E) -> None: + self.selected_menu.append(0) + + @kb.add("c-c", filter=in_main_menu) + @kb.add("c-g", filter=in_main_menu) + def _cancel(event: E) -> None: + "Leave menu." + event.app.layout.focus_last() + + # Sub menu navigation. + + @kb.add("left", filter=in_sub_menu) + @kb.add("c-g", filter=in_sub_menu) + @kb.add("c-c", filter=in_sub_menu) + def _back(event: E) -> None: + "Go back to parent menu." + if len(self.selected_menu) > 1: + self.selected_menu.pop() + + @kb.add("right", filter=in_sub_menu) + def _submenu(event: E) -> None: + "go into sub menu." + if self._get_menu(len(self.selected_menu) - 1).children: + self.selected_menu.append(0) + + # If This item does not have a sub menu. Go up in the parent menu. + elif ( + len(self.selected_menu) == 2 + and self.selected_menu[0] < len(self.menu_items) - 1 + ): + self.selected_menu = [ + min(len(self.menu_items) - 1, self.selected_menu[0] + 1) + ] + if self.menu_items[self.selected_menu[0]].children: + self.selected_menu.append(0) + + @kb.add("up", filter=in_sub_menu) + def _up_in_submenu(event: E) -> None: + "Select previous (enabled) menu item or return to main menu." + # Look for previous enabled items in this sub menu. + menu = self._get_menu(len(self.selected_menu) - 2) + index = self.selected_menu[-1] + + previous_indexes = [ + i + for i, item in enumerate(menu.children) + if i < index and not item.disabled + ] + + if previous_indexes: + self.selected_menu[-1] = previous_indexes[-1] + elif len(self.selected_menu) == 2: + # Return to main menu. + self.selected_menu.pop() + + @kb.add("down", filter=in_sub_menu) + def _down_in_submenu(event: E) -> None: + "Select next (enabled) menu item." + menu = self._get_menu(len(self.selected_menu) - 2) + index = self.selected_menu[-1] + + next_indexes = [ + i + for i, item in enumerate(menu.children) + if i > index and not item.disabled + ] + + if next_indexes: + self.selected_menu[-1] = next_indexes[0] + + @kb.add("enter") + def _click(event: E) -> None: + "Click the selected menu item." + item = self._get_menu(len(self.selected_menu) - 1) + if item.handler: + event.app.layout.focus_last() + item.handler() + + # Controls. + self.control = FormattedTextControl( + self._get_menu_fragments, key_bindings=kb, focusable=True, show_cursor=False + ) + + self.window = Window(height=1, content=self.control, style="class:menu-bar") + + submenu = self._submenu(0) + submenu2 = self._submenu(1) + submenu3 = self._submenu(2) + + @Condition + def has_focus() -> bool: + return get_app().layout.current_window == self.window + + self.container = FloatContainer( + content=HSplit( + [ + # The titlebar. + self.window, + # The 'body', like defined above. + body, + ] + ), + floats=[ + Float( + xcursor=True, + ycursor=True, + content=ConditionalContainer( + content=Shadow(body=submenu), filter=has_focus + ), + ), + Float( + attach_to_window=submenu, + xcursor=True, + ycursor=True, + allow_cover_cursor=True, + content=ConditionalContainer( + content=Shadow(body=submenu2), + filter=has_focus + & Condition(lambda: len(self.selected_menu) >= 1), + ), + ), + Float( + attach_to_window=submenu2, + xcursor=True, + ycursor=True, + allow_cover_cursor=True, + content=ConditionalContainer( + content=Shadow(body=submenu3), + filter=has_focus + & Condition(lambda: len(self.selected_menu) >= 2), + ), + ), + # -- + ] + + (floats or []), + key_bindings=key_bindings, + ) + + def _get_menu(self, level: int) -> "MenuItem": + menu = self.menu_items[self.selected_menu[0]] + + for i, index in enumerate(self.selected_menu[1:]): + if i < level: + try: + menu = menu.children[index] + except IndexError: + return MenuItem("debug") + + return menu + + def _get_menu_fragments(self) -> StyleAndTextTuples: + focused = get_app().layout.has_focus(self.window) + + # This is called during the rendering. When we discover that this + # widget doesn't have the focus anymore. Reset menu state. + if not focused: + self.selected_menu = [0] + + # Generate text fragments for the main menu. + def one_item(i: int, item: MenuItem) -> Iterable[OneStyleAndTextTuple]: + def mouse_handler(mouse_event: MouseEvent) -> None: + hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE + if ( + mouse_event.event_type == MouseEventType.MOUSE_DOWN + or hover + and focused + ): + # Toggle focus. + app = get_app() + if not hover: + if app.layout.has_focus(self.window): + if self.selected_menu == [i]: + app.layout.focus_last() + else: + app.layout.focus(self.window) + self.selected_menu = [i] + + yield ("class:menu-bar", " ", mouse_handler) + if i == self.selected_menu[0] and focused: + yield ("[SetMenuPosition]", "", mouse_handler) + style = "class:menu-bar.selected-item" + else: + style = "class:menu-bar" + yield style, item.text, mouse_handler + + result: StyleAndTextTuples = [] + for i, item in enumerate(self.menu_items): + result.extend(one_item(i, item)) + + return result + + def _submenu(self, level: int = 0) -> Window: + def get_text_fragments() -> StyleAndTextTuples: + result: StyleAndTextTuples = [] + if level < len(self.selected_menu): + menu = self._get_menu(level) + if menu.children: + result.append(("class:menu", Border.TOP_LEFT)) + result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4))) + result.append(("class:menu", Border.TOP_RIGHT)) + result.append(("", "\n")) + try: + selected_item = self.selected_menu[level + 1] + except IndexError: + selected_item = -1 + + def one_item( + i: int, item: MenuItem + ) -> Iterable[OneStyleAndTextTuple]: + def mouse_handler(mouse_event: MouseEvent) -> None: + if item.disabled: + # The arrow keys can't interact with menu items that are disabled. + # The mouse shouldn't be able to either. + return + hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE + if ( + mouse_event.event_type == MouseEventType.MOUSE_UP + or hover + ): + app = get_app() + if not hover and item.handler: + app.layout.focus_last() + item.handler() + else: + self.selected_menu = self.selected_menu[ + : level + 1 + ] + [i] + + if i == selected_item: + yield ("[SetCursorPosition]", "") + style = "class:menu-bar.selected-item" + else: + style = "" + + yield ("class:menu", Border.VERTICAL) + if item.text == "-": + yield ( + style + "class:menu-border", + f"{Border.HORIZONTAL * (menu.width + 3)}", + mouse_handler, + ) + else: + yield ( + style, + f" {item.text}".ljust(menu.width + 3), + mouse_handler, + ) + + if item.children: + yield (style, ">", mouse_handler) + else: + yield (style, " ", mouse_handler) + + if i == selected_item: + yield ("[SetMenuPosition]", "") + yield ("class:menu", Border.VERTICAL) + + yield ("", "\n") + + for i, item in enumerate(menu.children): + result.extend(one_item(i, item)) + + result.append(("class:menu", Border.BOTTOM_LEFT)) + result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4))) + result.append(("class:menu", Border.BOTTOM_RIGHT)) + return result + + return Window(FormattedTextControl(get_text_fragments), style="class:menu") + + @property + def floats(self) -> Optional[List[Float]]: + return self.container.floats + + def __pt_container__(self) -> Container: + return self.container + + +class MenuItem: + def __init__( + self, + text: str = "", + handler: Optional[Callable[[], None]] = None, + children: Optional[List["MenuItem"]] = None, + shortcut: Optional[Sequence[Union[Keys, str]]] = None, + disabled: bool = False, + ) -> None: + + self.text = text + self.handler = handler + self.children = children or [] + self.shortcut = shortcut + self.disabled = disabled + self.selected_item = 0 + + @property + def width(self) -> int: + if self.children: + return max(get_cwidth(c.text) for c in self.children) + else: + return 0 diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/toolbars.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/toolbars.py new file mode 100644 index 0000000..e464be0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/widgets/toolbars.py @@ -0,0 +1,374 @@ +from typing import Any, Optional + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.enums import SYSTEM_BUFFER +from prompt_toolkit.filters import ( + Condition, + FilterOrBool, + emacs_mode, + has_arg, + has_completions, + has_focus, + has_validation_error, + to_filter, + vi_mode, + vi_navigation_mode, +) +from prompt_toolkit.formatted_text import ( + AnyFormattedText, + StyleAndTextTuples, + fragment_list_len, + to_formatted_text, +) +from prompt_toolkit.key_binding.key_bindings import ( + ConditionalKeyBindings, + KeyBindings, + KeyBindingsBase, + merge_key_bindings, +) +from prompt_toolkit.key_binding.key_processor import KeyPressEvent +from prompt_toolkit.key_binding.vi_state import InputMode +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout.containers import ConditionalContainer, Container, Window +from prompt_toolkit.layout.controls import ( + BufferControl, + FormattedTextControl, + SearchBufferControl, + UIContent, + UIControl, +) +from prompt_toolkit.layout.dimension import Dimension +from prompt_toolkit.layout.processors import BeforeInput +from prompt_toolkit.lexers import SimpleLexer +from prompt_toolkit.search import SearchDirection + +__all__ = [ + "ArgToolbar", + "CompletionsToolbar", + "FormattedTextToolbar", + "SearchToolbar", + "SystemToolbar", + "ValidationToolbar", +] + +E = KeyPressEvent + + +class FormattedTextToolbar(Window): + def __init__(self, text: AnyFormattedText, style: str = "", **kw: Any) -> None: + # Note: The style needs to be applied to the toolbar as a whole, not + # just the `FormattedTextControl`. + super().__init__( + FormattedTextControl(text, **kw), + style=style, + dont_extend_height=True, + height=Dimension(min=1), + ) + + +class SystemToolbar: + """ + Toolbar for a system prompt. + + :param prompt: Prompt to be displayed to the user. + """ + + def __init__( + self, + prompt: AnyFormattedText = "Shell command: ", + enable_global_bindings: FilterOrBool = True, + ) -> None: + + self.prompt = prompt + self.enable_global_bindings = to_filter(enable_global_bindings) + + self.system_buffer = Buffer(name=SYSTEM_BUFFER) + + self._bindings = self._build_key_bindings() + + self.buffer_control = BufferControl( + buffer=self.system_buffer, + lexer=SimpleLexer(style="class:system-toolbar.text"), + input_processors=[ + BeforeInput(lambda: self.prompt, style="class:system-toolbar") + ], + key_bindings=self._bindings, + ) + + self.window = Window( + self.buffer_control, height=1, style="class:system-toolbar" + ) + + self.container = ConditionalContainer( + content=self.window, filter=has_focus(self.system_buffer) + ) + + def _get_display_before_text(self) -> StyleAndTextTuples: + return [ + ("class:system-toolbar", "Shell command: "), + ("class:system-toolbar.text", self.system_buffer.text), + ("", "\n"), + ] + + def _build_key_bindings(self) -> KeyBindingsBase: + focused = has_focus(self.system_buffer) + + # Emacs + emacs_bindings = KeyBindings() + handle = emacs_bindings.add + + @handle("escape", filter=focused) + @handle("c-g", filter=focused) + @handle("c-c", filter=focused) + def _cancel(event: E) -> None: + "Hide system prompt." + self.system_buffer.reset() + event.app.layout.focus_last() + + @handle("enter", filter=focused) + async def _accept(event: E) -> None: + "Run system command." + await event.app.run_system_command( + self.system_buffer.text, + display_before_text=self._get_display_before_text(), + ) + self.system_buffer.reset(append_to_history=True) + event.app.layout.focus_last() + + # Vi. + vi_bindings = KeyBindings() + handle = vi_bindings.add + + @handle("escape", filter=focused) + @handle("c-c", filter=focused) + def _cancel_vi(event: E) -> None: + "Hide system prompt." + event.app.vi_state.input_mode = InputMode.NAVIGATION + self.system_buffer.reset() + event.app.layout.focus_last() + + @handle("enter", filter=focused) + async def _accept_vi(event: E) -> None: + "Run system command." + event.app.vi_state.input_mode = InputMode.NAVIGATION + event.app.run_system_command( + self.system_buffer.text, + display_before_text=self._get_display_before_text(), + ) + self.system_buffer.reset(append_to_history=True) + event.app.layout.focus_last() + + # Global bindings. (Listen to these bindings, even when this widget is + # not focussed.) + global_bindings = KeyBindings() + handle = global_bindings.add + + @handle(Keys.Escape, "!", filter=~focused & emacs_mode, is_global=True) + def _focus_me(event: E) -> None: + "M-'!' will focus this user control." + event.app.layout.focus(self.window) + + @handle("!", filter=~focused & vi_mode & vi_navigation_mode, is_global=True) + def _focus_me_vi(event: E) -> None: + "Focus." + event.app.vi_state.input_mode = InputMode.INSERT + event.app.layout.focus(self.window) + + return merge_key_bindings( + [ + ConditionalKeyBindings(emacs_bindings, emacs_mode), + ConditionalKeyBindings(vi_bindings, vi_mode), + ConditionalKeyBindings(global_bindings, self.enable_global_bindings), + ] + ) + + def __pt_container__(self) -> Container: + return self.container + + +class ArgToolbar: + def __init__(self) -> None: + def get_formatted_text() -> StyleAndTextTuples: + arg = get_app().key_processor.arg or "" + if arg == "-": + arg = "-1" + + return [ + ("class:arg-toolbar", "Repeat: "), + ("class:arg-toolbar.text", arg), + ] + + self.window = Window(FormattedTextControl(get_formatted_text), height=1) + + self.container = ConditionalContainer(content=self.window, filter=has_arg) + + def __pt_container__(self) -> Container: + return self.container + + +class SearchToolbar: + """ + :param vi_mode: Display '/' and '?' instead of I-search. + :param ignore_case: Search case insensitive. + """ + + def __init__( + self, + search_buffer: Optional[Buffer] = None, + vi_mode: bool = False, + text_if_not_searching: AnyFormattedText = "", + forward_search_prompt: AnyFormattedText = "I-search: ", + backward_search_prompt: AnyFormattedText = "I-search backward: ", + ignore_case: FilterOrBool = False, + ) -> None: + + if search_buffer is None: + search_buffer = Buffer() + + @Condition + def is_searching() -> bool: + return self.control in get_app().layout.search_links + + def get_before_input() -> AnyFormattedText: + if not is_searching(): + return text_if_not_searching + elif ( + self.control.searcher_search_state.direction == SearchDirection.BACKWARD + ): + return "?" if vi_mode else backward_search_prompt + else: + return "/" if vi_mode else forward_search_prompt + + self.search_buffer = search_buffer + + self.control = SearchBufferControl( + buffer=search_buffer, + input_processors=[ + BeforeInput(get_before_input, style="class:search-toolbar.prompt") + ], + lexer=SimpleLexer(style="class:search-toolbar.text"), + ignore_case=ignore_case, + ) + + self.container = ConditionalContainer( + content=Window(self.control, height=1, style="class:search-toolbar"), + filter=is_searching, + ) + + def __pt_container__(self) -> Container: + return self.container + + +class _CompletionsToolbarControl(UIControl): + def create_content(self, width: int, height: int) -> UIContent: + all_fragments: StyleAndTextTuples = [] + + complete_state = get_app().current_buffer.complete_state + if complete_state: + completions = complete_state.completions + index = complete_state.complete_index # Can be None! + + # Width of the completions without the left/right arrows in the margins. + content_width = width - 6 + + # Booleans indicating whether we stripped from the left/right + cut_left = False + cut_right = False + + # Create Menu content. + fragments: StyleAndTextTuples = [] + + for i, c in enumerate(completions): + # When there is no more place for the next completion + if fragment_list_len(fragments) + len(c.display_text) >= content_width: + # If the current one was not yet displayed, page to the next sequence. + if i <= (index or 0): + fragments = [] + cut_left = True + # If the current one is visible, stop here. + else: + cut_right = True + break + + fragments.extend( + to_formatted_text( + c.display_text, + style=( + "class:completion-toolbar.completion.current" + if i == index + else "class:completion-toolbar.completion" + ), + ) + ) + fragments.append(("", " ")) + + # Extend/strip until the content width. + fragments.append(("", " " * (content_width - fragment_list_len(fragments)))) + fragments = fragments[:content_width] + + # Return fragments + all_fragments.append(("", " ")) + all_fragments.append( + ("class:completion-toolbar.arrow", "<" if cut_left else " ") + ) + all_fragments.append(("", " ")) + + all_fragments.extend(fragments) + + all_fragments.append(("", " ")) + all_fragments.append( + ("class:completion-toolbar.arrow", ">" if cut_right else " ") + ) + all_fragments.append(("", " ")) + + def get_line(i: int) -> StyleAndTextTuples: + return all_fragments + + return UIContent(get_line=get_line, line_count=1) + + +class CompletionsToolbar: + def __init__(self) -> None: + self.container = ConditionalContainer( + content=Window( + _CompletionsToolbarControl(), height=1, style="class:completion-toolbar" + ), + filter=has_completions, + ) + + def __pt_container__(self) -> Container: + return self.container + + +class ValidationToolbar: + def __init__(self, show_position: bool = False) -> None: + def get_formatted_text() -> StyleAndTextTuples: + buff = get_app().current_buffer + + if buff.validation_error: + row, column = buff.document.translate_index_to_position( + buff.validation_error.cursor_position + ) + + if show_position: + text = "{} (line={} column={})".format( + buff.validation_error.message, + row + 1, + column + 1, + ) + else: + text = buff.validation_error.message + + return [("class:validation-toolbar", text)] + else: + return [] + + self.control = FormattedTextControl(get_formatted_text) + + self.container = ConditionalContainer( + content=Window(self.control, height=1), filter=has_validation_error + ) + + def __pt_container__(self) -> Container: + return self.container diff --git a/.venv/lib/python3.8/site-packages/prompt_toolkit/win32_types.py b/.venv/lib/python3.8/site-packages/prompt_toolkit/win32_types.py new file mode 100644 index 0000000..4ae2e39 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/prompt_toolkit/win32_types.py @@ -0,0 +1,227 @@ +from ctypes import Structure, Union, c_char, c_long, c_short, c_ulong +from ctypes.wintypes import BOOL, DWORD, LPVOID, WCHAR, WORD +from typing import TYPE_CHECKING + +# Input/Output standard device numbers. Note that these are not handle objects. +# It's the `windll.kernel32.GetStdHandle` system call that turns them into a +# real handle object. +STD_INPUT_HANDLE = c_ulong(-10) +STD_OUTPUT_HANDLE = c_ulong(-11) +STD_ERROR_HANDLE = c_ulong(-12) + + +class COORD(Structure): + """ + Struct in wincon.h + http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx + """ + + if TYPE_CHECKING: + X: int + Y: int + + _fields_ = [ + ("X", c_short), # Short + ("Y", c_short), # Short + ] + + def __repr__(self) -> str: + return "{}(X={!r}, Y={!r}, type_x={!r}, type_y={!r})".format( + self.__class__.__name__, + self.X, + self.Y, + type(self.X), + type(self.Y), + ) + + +class UNICODE_OR_ASCII(Union): + if TYPE_CHECKING: + AsciiChar: bytes + UnicodeChar: str + + _fields_ = [ + ("AsciiChar", c_char), + ("UnicodeChar", WCHAR), + ] + + +class KEY_EVENT_RECORD(Structure): + """ + http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx + """ + + if TYPE_CHECKING: + KeyDown: int + RepeatCount: int + VirtualKeyCode: int + VirtualScanCode: int + uChar: UNICODE_OR_ASCII + ControlKeyState: int + + _fields_ = [ + ("KeyDown", c_long), # bool + ("RepeatCount", c_short), # word + ("VirtualKeyCode", c_short), # word + ("VirtualScanCode", c_short), # word + ("uChar", UNICODE_OR_ASCII), # Unicode or ASCII. + ("ControlKeyState", c_long), # double word + ] + + +class MOUSE_EVENT_RECORD(Structure): + """ + http://msdn.microsoft.com/en-us/library/windows/desktop/ms684239(v=vs.85).aspx + """ + + if TYPE_CHECKING: + MousePosition: COORD + ButtonState: int + ControlKeyState: int + EventFlags: int + + _fields_ = [ + ("MousePosition", COORD), + ("ButtonState", c_long), # dword + ("ControlKeyState", c_long), # dword + ("EventFlags", c_long), # dword + ] + + +class WINDOW_BUFFER_SIZE_RECORD(Structure): + """ + http://msdn.microsoft.com/en-us/library/windows/desktop/ms687093(v=vs.85).aspx + """ + + if TYPE_CHECKING: + Size: COORD + + _fields_ = [("Size", COORD)] + + +class MENU_EVENT_RECORD(Structure): + """ + http://msdn.microsoft.com/en-us/library/windows/desktop/ms684213(v=vs.85).aspx + """ + + if TYPE_CHECKING: + CommandId: int + + _fields_ = [("CommandId", c_long)] # uint + + +class FOCUS_EVENT_RECORD(Structure): + """ + http://msdn.microsoft.com/en-us/library/windows/desktop/ms683149(v=vs.85).aspx + """ + + if TYPE_CHECKING: + SetFocus: int + + _fields_ = [("SetFocus", c_long)] # bool + + +class EVENT_RECORD(Union): + if TYPE_CHECKING: + KeyEvent: KEY_EVENT_RECORD + MouseEvent: MOUSE_EVENT_RECORD + WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD + MenuEvent: MENU_EVENT_RECORD + FocusEvent: FOCUS_EVENT_RECORD + + _fields_ = [ + ("KeyEvent", KEY_EVENT_RECORD), + ("MouseEvent", MOUSE_EVENT_RECORD), + ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD), + ("MenuEvent", MENU_EVENT_RECORD), + ("FocusEvent", FOCUS_EVENT_RECORD), + ] + + +class INPUT_RECORD(Structure): + """ + http://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx + """ + + if TYPE_CHECKING: + EventType: int + Event: EVENT_RECORD + + _fields_ = [("EventType", c_short), ("Event", EVENT_RECORD)] # word # Union. + + +EventTypes = { + 1: "KeyEvent", + 2: "MouseEvent", + 4: "WindowBufferSizeEvent", + 8: "MenuEvent", + 16: "FocusEvent", +} + + +class SMALL_RECT(Structure): + """struct in wincon.h.""" + + if TYPE_CHECKING: + Left: int + Top: int + Right: int + Bottom: int + + _fields_ = [ + ("Left", c_short), + ("Top", c_short), + ("Right", c_short), + ("Bottom", c_short), + ] + + +class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + + if TYPE_CHECKING: + dwSize: COORD + dwCursorPosition: COORD + wAttributes: int + srWindow: SMALL_RECT + dwMaximumWindowSize: COORD + + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", WORD), + ("srWindow", SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + + def __repr__(self) -> str: + return "CONSOLE_SCREEN_BUFFER_INFO({!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r})".format( + self.dwSize.Y, + self.dwSize.X, + self.dwCursorPosition.Y, + self.dwCursorPosition.X, + self.wAttributes, + self.srWindow.Top, + self.srWindow.Left, + self.srWindow.Bottom, + self.srWindow.Right, + self.dwMaximumWindowSize.Y, + self.dwMaximumWindowSize.X, + ) + + +class SECURITY_ATTRIBUTES(Structure): + """ + http://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx + """ + + if TYPE_CHECKING: + nLength: int + lpSecurityDescriptor: int + bInheritHandle: int # BOOL comes back as 'int'. + + _fields_ = [ + ("nLength", DWORD), + ("lpSecurityDescriptor", LPVOID), + ("bInheritHandle", BOOL), + ] diff --git a/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/LICENSE b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/LICENSE new file mode 100644 index 0000000..9c77274 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/LICENSE @@ -0,0 +1,16 @@ +Ptyprocess is under the ISC license, as code derived from Pexpect. + http://opensource.org/licenses/ISC + +Copyright (c) 2013-2014, Pexpect development team +Copyright (c) 2012, Noah Spurrier + +PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY PURPOSE +WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE COPYRIGHT NOTICE +AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. THE SOFTWARE IS PROVIDED +"AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT +SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING +OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/METADATA b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/METADATA new file mode 100644 index 0000000..ab1d4e0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/METADATA @@ -0,0 +1,37 @@ +Metadata-Version: 2.1 +Name: ptyprocess +Version: 0.7.0 +Summary: Run a subprocess in a pseudo terminal +Home-page: https://github.com/pexpect/ptyprocess +License: UNKNOWN +Author: Thomas Kluyver +Author-email: thomas@kluyver.me.uk +Description-Content-Type: text/x-rst +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: ISC License (ISCL) +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Terminals + +Launch a subprocess in a pseudo terminal (pty), and interact with both the +process and its pty. + +Sometimes, piping stdin and stdout is not enough. There might be a password +prompt that doesn't read from stdin, output that changes when it's going to a +pipe rather than a terminal, or curses-style interfaces that rely on a terminal. +If you need to automate these things, running the process in a pseudo terminal +(pty) is the answer. + +Interface:: + + p = PtyProcessUnicode.spawn(['python']) + p.read(20) + p.write('6+6\n') + p.read(20) + diff --git a/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/RECORD b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/RECORD new file mode 100644 index 0000000..1234183 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/RECORD @@ -0,0 +1,13 @@ +ptyprocess-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +ptyprocess-0.7.0.dist-info/LICENSE,sha256=yCLThbGnMymEYkF5m-zxhpC11Edkwb7WkwC1NqQFAwo,905 +ptyprocess-0.7.0.dist-info/METADATA,sha256=w8K5a12aVdpZWMNNCMGKCEn1ZgkCbMRtXJW4t3_PPgw,1312 +ptyprocess-0.7.0.dist-info/RECORD,, +ptyprocess-0.7.0.dist-info/WHEEL,sha256=NLqmsx-ZFZ6gDavYgh2oH0ZSN-KRmpcdEXIZDnYy9Pg,99 +ptyprocess/__init__.py,sha256=sn-W_1nNRTuIOi2aCEHVL06wCVJcR-LOZdgpXzwFuTU,138 +ptyprocess/__pycache__/__init__.cpython-38.pyc,, +ptyprocess/__pycache__/_fork_pty.cpython-38.pyc,, +ptyprocess/__pycache__/ptyprocess.cpython-38.pyc,, +ptyprocess/__pycache__/util.cpython-38.pyc,, +ptyprocess/_fork_pty.py,sha256=VVvMy8c4ZpjDMiIMSg8T1BQ1g3SBexDpey_cxi0n5aw,2362 +ptyprocess/ptyprocess.py,sha256=sk2sU2I22Yyl1gU3FjFmpWL3B43o0KqG3d3CI8r0Nq8,31686 +ptyprocess/util.py,sha256=rQAdDRZfoOiOn6vykWth0wI6FFKAp7aJtBSdt-KBWdU,2785 diff --git a/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/WHEEL new file mode 100644 index 0000000..3825653 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess-0.7.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: flit 3.0.0 +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/__init__.py b/.venv/lib/python3.8/site-packages/ptyprocess/__init__.py new file mode 100644 index 0000000..3a6268e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess/__init__.py @@ -0,0 +1,4 @@ +"""Run a subprocess in a pseudo terminal""" +from .ptyprocess import PtyProcess, PtyProcessUnicode, PtyProcessError + +__version__ = '0.7.0' diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..cb30652 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/_fork_pty.cpython-38.pyc b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/_fork_pty.cpython-38.pyc new file mode 100644 index 0000000..a8a08bb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/_fork_pty.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/ptyprocess.cpython-38.pyc b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/ptyprocess.cpython-38.pyc new file mode 100644 index 0000000..3989300 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/ptyprocess.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/util.cpython-38.pyc b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/util.cpython-38.pyc new file mode 100644 index 0000000..834be7f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/ptyprocess/__pycache__/util.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/_fork_pty.py b/.venv/lib/python3.8/site-packages/ptyprocess/_fork_pty.py new file mode 100644 index 0000000..a8d05fe --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess/_fork_pty.py @@ -0,0 +1,78 @@ +"""Substitute for the forkpty system call, to support Solaris. +""" +import os +import errno + +from pty import (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, CHILD) +from .util import PtyProcessError + +def fork_pty(): + '''This implements a substitute for the forkpty system call. This + should be more portable than the pty.fork() function. Specifically, + this should work on Solaris. + + Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to + resolve the issue with Python's pty.fork() not supporting Solaris, + particularly ssh. Based on patch to posixmodule.c authored by Noah + Spurrier:: + + http://mail.python.org/pipermail/python-dev/2003-May/035281.html + + ''' + + parent_fd, child_fd = os.openpty() + if parent_fd < 0 or child_fd < 0: + raise OSError("os.openpty() failed") + + pid = os.fork() + if pid == CHILD: + # Child. + os.close(parent_fd) + pty_make_controlling_tty(child_fd) + + os.dup2(child_fd, STDIN_FILENO) + os.dup2(child_fd, STDOUT_FILENO) + os.dup2(child_fd, STDERR_FILENO) + + else: + # Parent. + os.close(child_fd) + + return pid, parent_fd + +def pty_make_controlling_tty(tty_fd): + '''This makes the pseudo-terminal the controlling tty. This should be + more portable than the pty.fork() function. Specifically, this should + work on Solaris. ''' + + child_name = os.ttyname(tty_fd) + + # Disconnect from controlling tty, if any. Raises OSError of ENXIO + # if there was no controlling tty to begin with, such as when + # executed by a cron(1) job. + try: + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) + os.close(fd) + except OSError as err: + if err.errno != errno.ENXIO: + raise + + os.setsid() + + # Verify we are disconnected from controlling tty by attempting to open + # it again. We expect that OSError of ENXIO should always be raised. + try: + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) + os.close(fd) + raise PtyProcessError("OSError of errno.ENXIO should be raised.") + except OSError as err: + if err.errno != errno.ENXIO: + raise + + # Verify we can open child pty. + fd = os.open(child_name, os.O_RDWR) + os.close(fd) + + # Verify we now have a controlling tty. + fd = os.open("/dev/tty", os.O_WRONLY) + os.close(fd) diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/ptyprocess.py b/.venv/lib/python3.8/site-packages/ptyprocess/ptyprocess.py new file mode 100644 index 0000000..78d19fd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess/ptyprocess.py @@ -0,0 +1,842 @@ +import codecs +import errno +import fcntl +import io +import os +import pty +import resource +import signal +import struct +import sys +import termios +import time + +try: + import builtins # Python 3 +except ImportError: + import __builtin__ as builtins # Python 2 + +# Constants +from pty import (STDIN_FILENO, CHILD) + +from .util import which, PtyProcessError + +_platform = sys.platform.lower() + +# Solaris uses internal __fork_pty(). All others use pty.fork(). +_is_solaris = ( + _platform.startswith('solaris') or + _platform.startswith('sunos')) + +if _is_solaris: + use_native_pty_fork = False + from . import _fork_pty +else: + use_native_pty_fork = True + +PY3 = sys.version_info[0] >= 3 + +if PY3: + def _byte(i): + return bytes([i]) +else: + def _byte(i): + return chr(i) + + class FileNotFoundError(OSError): pass + class TimeoutError(OSError): pass + +_EOF, _INTR = None, None + +def _make_eof_intr(): + """Set constants _EOF and _INTR. + + This avoids doing potentially costly operations on module load. + """ + global _EOF, _INTR + if (_EOF is not None) and (_INTR is not None): + return + + # inherit EOF and INTR definitions from controlling process. + try: + from termios import VEOF, VINTR + fd = None + for name in 'stdin', 'stdout': + stream = getattr(sys, '__%s__' % name, None) + if stream is None or not hasattr(stream, 'fileno'): + continue + try: + fd = stream.fileno() + except ValueError: + continue + if fd is None: + # no fd, raise ValueError to fallback on CEOF, CINTR + raise ValueError("No stream has a fileno") + intr = ord(termios.tcgetattr(fd)[6][VINTR]) + eof = ord(termios.tcgetattr(fd)[6][VEOF]) + except (ImportError, OSError, IOError, ValueError, termios.error): + # unless the controlling process is also not a terminal, + # such as cron(1), or when stdin and stdout are both closed. + # Fall-back to using CEOF and CINTR. There + try: + from termios import CEOF, CINTR + (intr, eof) = (CINTR, CEOF) + except ImportError: + # ^C, ^D + (intr, eof) = (3, 4) + + _INTR = _byte(intr) + _EOF = _byte(eof) + +# setecho and setwinsize are pulled out here because on some platforms, we need +# to do this from the child before we exec() + +def _setecho(fd, state): + errmsg = 'setecho() may not be called on this platform (it may still be possible to enable/disable echo when spawning the child process)' + + try: + attr = termios.tcgetattr(fd) + except termios.error as err: + if err.args[0] == errno.EINVAL: + raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) + raise + + if state: + attr[3] = attr[3] | termios.ECHO + else: + attr[3] = attr[3] & ~termios.ECHO + + try: + # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and + # blocked on some platforms. TCSADRAIN would probably be ideal. + termios.tcsetattr(fd, termios.TCSANOW, attr) + except IOError as err: + if err.args[0] == errno.EINVAL: + raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) + raise + +def _setwinsize(fd, rows, cols): + # Some very old platforms have a bug that causes the value for + # termios.TIOCSWINSZ to be truncated. There was a hack here to work + # around this, but it caused problems with newer platforms so has been + # removed. For details see https://github.com/pexpect/pexpect/issues/39 + TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) + # Note, assume ws_xpixel and ws_ypixel are zero. + s = struct.pack('HHHH', rows, cols, 0, 0) + fcntl.ioctl(fd, TIOCSWINSZ, s) + +class PtyProcess(object): + '''This class represents a process running in a pseudoterminal. + + The main constructor is the :meth:`spawn` classmethod. + ''' + string_type = bytes + if PY3: + linesep = os.linesep.encode('ascii') + crlf = '\r\n'.encode('ascii') + + @staticmethod + def write_to_stdout(b): + try: + return sys.stdout.buffer.write(b) + except AttributeError: + # If stdout has been replaced, it may not have .buffer + return sys.stdout.write(b.decode('ascii', 'replace')) + else: + linesep = os.linesep + crlf = '\r\n' + write_to_stdout = sys.stdout.write + + encoding = None + + argv = None + env = None + launch_dir = None + + def __init__(self, pid, fd): + _make_eof_intr() # Ensure _EOF and _INTR are calculated + self.pid = pid + self.fd = fd + readf = io.open(fd, 'rb', buffering=0) + writef = io.open(fd, 'wb', buffering=0, closefd=False) + self.fileobj = io.BufferedRWPair(readf, writef) + + self.terminated = False + self.closed = False + self.exitstatus = None + self.signalstatus = None + # status returned by os.waitpid + self.status = None + self.flag_eof = False + # Used by close() to give kernel time to update process status. + # Time in seconds. + self.delayafterclose = 0.1 + # Used by terminate() to give kernel time to update process status. + # Time in seconds. + self.delayafterterminate = 0.1 + + @classmethod + def spawn( + cls, argv, cwd=None, env=None, echo=True, preexec_fn=None, + dimensions=(24, 80), pass_fds=()): + '''Start the given command in a child process in a pseudo terminal. + + This does all the fork/exec type of stuff for a pty, and returns an + instance of PtyProcess. + + If preexec_fn is supplied, it will be called with no arguments in the + child process before exec-ing the specified command. + It may, for instance, set signal handlers to SIG_DFL or SIG_IGN. + + Dimensions of the psuedoterminal used for the subprocess can be + specified as a tuple (rows, cols), or the default (24, 80) will be used. + + By default, all file descriptors except 0, 1 and 2 are closed. This + behavior can be overridden with pass_fds, a list of file descriptors to + keep open between the parent and the child. + ''' + # Note that it is difficult for this method to fail. + # You cannot detect if the child process cannot start. + # So the only way you can tell if the child process started + # or not is to try to read from the file descriptor. If you get + # EOF immediately then it means that the child is already dead. + # That may not necessarily be bad because you may have spawned a child + # that performs some task; creates no stdout output; and then dies. + + if not isinstance(argv, (list, tuple)): + raise TypeError("Expected a list or tuple for argv, got %r" % argv) + + # Shallow copy of argv so we can modify it + argv = argv[:] + command = argv[0] + + command_with_path = which(command) + if command_with_path is None: + raise FileNotFoundError('The command was not found or was not ' + + 'executable: %s.' % command) + command = command_with_path + argv[0] = command + + # [issue #119] To prevent the case where exec fails and the user is + # stuck interacting with a python child process instead of whatever + # was expected, we implement the solution from + # http://stackoverflow.com/a/3703179 to pass the exception to the + # parent process + + # [issue #119] 1. Before forking, open a pipe in the parent process. + exec_err_pipe_read, exec_err_pipe_write = os.pipe() + + if use_native_pty_fork: + pid, fd = pty.fork() + else: + # Use internal fork_pty, for Solaris + pid, fd = _fork_pty.fork_pty() + + # Some platforms must call setwinsize() and setecho() from the + # child process, and others from the master process. We do both, + # allowing IOError for either. + + if pid == CHILD: + # set window size + try: + _setwinsize(STDIN_FILENO, *dimensions) + except IOError as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY): + raise + + # disable echo if spawn argument echo was unset + if not echo: + try: + _setecho(STDIN_FILENO, False) + except (IOError, termios.error) as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY): + raise + + # [issue #119] 3. The child closes the reading end and sets the + # close-on-exec flag for the writing end. + os.close(exec_err_pipe_read) + fcntl.fcntl(exec_err_pipe_write, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + + # Do not allow child to inherit open file descriptors from parent, + # with the exception of the exec_err_pipe_write of the pipe + # and pass_fds. + # Impose ceiling on max_fd: AIX bugfix for users with unlimited + # nofiles where resource.RLIMIT_NOFILE is 2^63-1 and os.closerange() + # occasionally raises out of range error + max_fd = min(1048576, resource.getrlimit(resource.RLIMIT_NOFILE)[0]) + spass_fds = sorted(set(pass_fds) | {exec_err_pipe_write}) + for pair in zip([2] + spass_fds, spass_fds + [max_fd]): + os.closerange(pair[0]+1, pair[1]) + + if cwd is not None: + os.chdir(cwd) + + if preexec_fn is not None: + try: + preexec_fn() + except Exception as e: + ename = type(e).__name__ + tosend = '{}:0:{}'.format(ename, str(e)) + if PY3: + tosend = tosend.encode('utf-8') + + os.write(exec_err_pipe_write, tosend) + os.close(exec_err_pipe_write) + os._exit(1) + + try: + if env is None: + os.execv(command, argv) + else: + os.execvpe(command, argv, env) + except OSError as err: + # [issue #119] 5. If exec fails, the child writes the error + # code back to the parent using the pipe, then exits. + tosend = 'OSError:{}:{}'.format(err.errno, str(err)) + if PY3: + tosend = tosend.encode('utf-8') + os.write(exec_err_pipe_write, tosend) + os.close(exec_err_pipe_write) + os._exit(os.EX_OSERR) + + # Parent + inst = cls(pid, fd) + + # Set some informational attributes + inst.argv = argv + if env is not None: + inst.env = env + if cwd is not None: + inst.launch_dir = cwd + + # [issue #119] 2. After forking, the parent closes the writing end + # of the pipe and reads from the reading end. + os.close(exec_err_pipe_write) + exec_err_data = os.read(exec_err_pipe_read, 4096) + os.close(exec_err_pipe_read) + + # [issue #119] 6. The parent reads eof (a zero-length read) if the + # child successfully performed exec, since close-on-exec made + # successful exec close the writing end of the pipe. Or, if exec + # failed, the parent reads the error code and can proceed + # accordingly. Either way, the parent blocks until the child calls + # exec. + if len(exec_err_data) != 0: + try: + errclass, errno_s, errmsg = exec_err_data.split(b':', 2) + exctype = getattr(builtins, errclass.decode('ascii'), Exception) + + exception = exctype(errmsg.decode('utf-8', 'replace')) + if exctype is OSError: + exception.errno = int(errno_s) + except: + raise Exception('Subprocess failed, got bad error data: %r' + % exec_err_data) + else: + raise exception + + try: + inst.setwinsize(*dimensions) + except IOError as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY, errno.ENXIO): + raise + + return inst + + def __repr__(self): + clsname = type(self).__name__ + if self.argv is not None: + args = [repr(self.argv)] + if self.env is not None: + args.append("env=%r" % self.env) + if self.launch_dir is not None: + args.append("cwd=%r" % self.launch_dir) + + return "{}.spawn({})".format(clsname, ", ".join(args)) + + else: + return "{}(pid={}, fd={})".format(clsname, self.pid, self.fd) + + @staticmethod + def _coerce_send_string(s): + if not isinstance(s, bytes): + return s.encode('utf-8') + return s + + @staticmethod + def _coerce_read_string(s): + return s + + def __del__(self): + '''This makes sure that no system resources are left open. Python only + garbage collects Python objects. OS file descriptors are not Python + objects, so they must be handled explicitly. If the child file + descriptor was opened outside of this class (passed to the constructor) + then this does not close it. ''' + + if not self.closed: + # It is possible for __del__ methods to execute during the + # teardown of the Python VM itself. Thus self.close() may + # trigger an exception because os.close may be None. + try: + self.close() + # which exception, shouldn't we catch explicitly .. ? + except: + pass + + + def fileno(self): + '''This returns the file descriptor of the pty for the child. + ''' + return self.fd + + def close(self, force=True): + '''This closes the connection with the child application. Note that + calling close() more than once is valid. This emulates standard Python + behavior with files. Set force to True if you want to make sure that + the child is terminated (SIGKILL is sent if the child ignores SIGHUP + and SIGINT). ''' + if not self.closed: + self.flush() + self.fileobj.close() # Closes the file descriptor + # Give kernel time to update process status. + time.sleep(self.delayafterclose) + if self.isalive(): + if not self.terminate(force): + raise PtyProcessError('Could not terminate the child.') + self.fd = -1 + self.closed = True + #self.pid = None + + def flush(self): + '''This does nothing. It is here to support the interface for a + File-like object. ''' + + pass + + def isatty(self): + '''This returns True if the file descriptor is open and connected to a + tty(-like) device, else False. + + On SVR4-style platforms implementing streams, such as SunOS and HP-UX, + the child pty may not appear as a terminal device. This means + methods such as setecho(), setwinsize(), getwinsize() may raise an + IOError. ''' + + return os.isatty(self.fd) + + def waitnoecho(self, timeout=None): + '''This waits until the terminal ECHO flag is set False. This returns + True if the echo mode is off. This returns False if the ECHO flag was + not set False before the timeout. This can be used to detect when the + child is waiting for a password. Usually a child application will turn + off echo mode when it is waiting for the user to enter a password. For + example, instead of expecting the "password:" prompt you can wait for + the child to set ECHO off:: + + p = pexpect.spawn('ssh user@example.com') + p.waitnoecho() + p.sendline(mypassword) + + If timeout==None then this method to block until ECHO flag is False. + ''' + + if timeout is not None: + end_time = time.time() + timeout + while True: + if not self.getecho(): + return True + if timeout < 0 and timeout is not None: + return False + if timeout is not None: + timeout = end_time - time.time() + time.sleep(0.1) + + def getecho(self): + '''This returns the terminal echo mode. This returns True if echo is + on or False if echo is off. Child applications that are expecting you + to enter a password often set ECHO False. See waitnoecho(). + + Not supported on platforms where ``isatty()`` returns False. ''' + + try: + attr = termios.tcgetattr(self.fd) + except termios.error as err: + errmsg = 'getecho() may not be called on this platform' + if err.args[0] == errno.EINVAL: + raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) + raise + + self.echo = bool(attr[3] & termios.ECHO) + return self.echo + + def setecho(self, state): + '''This sets the terminal echo mode on or off. Note that anything the + child sent before the echo will be lost, so you should be sure that + your input buffer is empty before you call setecho(). For example, the + following will work as expected:: + + p = pexpect.spawn('cat') # Echo is on by default. + p.sendline('1234') # We expect see this twice from the child... + p.expect(['1234']) # ... once from the tty echo... + p.expect(['1234']) # ... and again from cat itself. + p.setecho(False) # Turn off tty echo + p.sendline('abcd') # We will set this only once (echoed by cat). + p.sendline('wxyz') # We will set this only once (echoed by cat) + p.expect(['abcd']) + p.expect(['wxyz']) + + The following WILL NOT WORK because the lines sent before the setecho + will be lost:: + + p = pexpect.spawn('cat') + p.sendline('1234') + p.setecho(False) # Turn off tty echo + p.sendline('abcd') # We will set this only once (echoed by cat). + p.sendline('wxyz') # We will set this only once (echoed by cat) + p.expect(['1234']) + p.expect(['1234']) + p.expect(['abcd']) + p.expect(['wxyz']) + + + Not supported on platforms where ``isatty()`` returns False. + ''' + _setecho(self.fd, state) + + self.echo = state + + def read(self, size=1024): + """Read and return at most ``size`` bytes from the pty. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + + Unlike Pexpect's ``read_nonblocking`` method, this doesn't try to deal + with the vagaries of EOF on platforms that do strange things, like IRIX + or older Solaris systems. It handles the errno=EIO pattern used on + Linux, and the empty-string return used on BSD platforms and (seemingly) + on recent Solaris. + """ + try: + s = self.fileobj.read1(size) + except (OSError, IOError) as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + self.flag_eof = True + raise EOFError('End Of File (EOF). Exception style platform.') + raise + if s == b'': + # BSD-style EOF (also appears to work on recent Solaris (OpenIndiana)) + self.flag_eof = True + raise EOFError('End Of File (EOF). Empty string style platform.') + + return s + + def readline(self): + """Read one line from the pseudoterminal, and return it as unicode. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + """ + try: + s = self.fileobj.readline() + except (OSError, IOError) as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + self.flag_eof = True + raise EOFError('End Of File (EOF). Exception style platform.') + raise + if s == b'': + # BSD-style EOF (also appears to work on recent Solaris (OpenIndiana)) + self.flag_eof = True + raise EOFError('End Of File (EOF). Empty string style platform.') + + return s + + def _writeb(self, b, flush=True): + n = self.fileobj.write(b) + if flush: + self.fileobj.flush() + return n + + def write(self, s, flush=True): + """Write bytes to the pseudoterminal. + + Returns the number of bytes written. + """ + return self._writeb(s, flush=flush) + + def sendcontrol(self, char): + '''Helper method that wraps send() with mnemonic access for sending control + character to the child (such as Ctrl-C or Ctrl-D). For example, to send + Ctrl-G (ASCII 7, bell, '\a'):: + + child.sendcontrol('g') + + See also, sendintr() and sendeof(). + ''' + char = char.lower() + a = ord(char) + if 97 <= a <= 122: + a = a - ord('a') + 1 + byte = _byte(a) + return self._writeb(byte), byte + d = {'@': 0, '`': 0, + '[': 27, '{': 27, + '\\': 28, '|': 28, + ']': 29, '}': 29, + '^': 30, '~': 30, + '_': 31, + '?': 127} + if char not in d: + return 0, b'' + + byte = _byte(d[char]) + return self._writeb(byte), byte + + def sendeof(self): + '''This sends an EOF to the child. This sends a character which causes + the pending parent output buffer to be sent to the waiting child + program without waiting for end-of-line. If it is the first character + of the line, the read() in the user program returns 0, which signifies + end-of-file. This means to work as expected a sendeof() has to be + called at the beginning of a line. This method does not send a newline. + It is the responsibility of the caller to ensure the eof is sent at the + beginning of a line. ''' + + return self._writeb(_EOF), _EOF + + def sendintr(self): + '''This sends a SIGINT to the child. It does not require + the SIGINT to be the first character on a line. ''' + + return self._writeb(_INTR), _INTR + + def eof(self): + '''This returns True if the EOF exception was ever raised. + ''' + + return self.flag_eof + + def terminate(self, force=False): + '''This forces a child process to terminate. It starts nicely with + SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This + returns True if the child was terminated. This returns False if the + child could not be terminated. ''' + + if not self.isalive(): + return True + try: + self.kill(signal.SIGHUP) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGCONT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGINT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + if force: + self.kill(signal.SIGKILL) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + return False + except OSError: + # I think there are kernel timing issues that sometimes cause + # this to happen. I think isalive() reports True, but the + # process is dead to the kernel. + # Make one last attempt to see if the kernel is up to date. + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + + def wait(self): + '''This waits until the child exits. This is a blocking call. This will + not read any data from the child, so this will block forever if the + child has unread output and has terminated. In other words, the child + may have printed output then called exit(), but, the child is + technically still alive until its output is read by the parent. ''' + + if self.isalive(): + pid, status = os.waitpid(self.pid, 0) + else: + return self.exitstatus + self.exitstatus = os.WEXITSTATUS(status) + if os.WIFEXITED(status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED(status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED(status): # pragma: no cover + # You can't call wait() on a child process in the stopped state. + raise PtyProcessError('Called wait() on a stopped child ' + + 'process. This is not supported. Is some other ' + + 'process attempting job control with our child pid?') + return self.exitstatus + + def isalive(self): + '''This tests if the child process is running or not. This is + non-blocking. If the child was terminated then this will read the + exitstatus or signalstatus of the child. This returns True if the child + process appears to be running or False if not. It can take literally + SECONDS for Solaris to return the right status. ''' + + if self.terminated: + return False + + if self.flag_eof: + # This is for Linux, which requires the blocking form + # of waitpid to get the status of a defunct process. + # This is super-lame. The flag_eof would have been set + # in read_nonblocking(), so this should be safe. + waitpid_options = 0 + else: + waitpid_options = os.WNOHANG + + try: + pid, status = os.waitpid(self.pid, waitpid_options) + except OSError as e: + # No child processes + if e.errno == errno.ECHILD: + raise PtyProcessError('isalive() encountered condition ' + + 'where "terminated" is 0, but there was no child ' + + 'process. Did someone else call waitpid() ' + + 'on our process?') + else: + raise + + # I have to do this twice for Solaris. + # I can't even believe that I figured this out... + # If waitpid() returns 0 it means that no child process + # wishes to report, and the value of status is undefined. + if pid == 0: + try: + ### os.WNOHANG) # Solaris! + pid, status = os.waitpid(self.pid, waitpid_options) + except OSError as e: # pragma: no cover + # This should never happen... + if e.errno == errno.ECHILD: + raise PtyProcessError('isalive() encountered condition ' + + 'that should never happen. There was no child ' + + 'process. Did someone else call waitpid() ' + + 'on our process?') + else: + raise + + # If pid is still 0 after two calls to waitpid() then the process + # really is alive. This seems to work on all platforms, except for + # Irix which seems to require a blocking call on waitpid or select, + # so I let read_nonblocking take care of this situation + # (unfortunately, this requires waiting through the timeout). + if pid == 0: + return True + + if pid == 0: + return True + + if os.WIFEXITED(status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED(status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED(status): + raise PtyProcessError('isalive() encountered condition ' + + 'where child process is stopped. This is not ' + + 'supported. Is some other process attempting ' + + 'job control with our child pid?') + return False + + def kill(self, sig): + """Send the given signal to the child application. + + In keeping with UNIX tradition it has a misleading name. It does not + necessarily kill the child unless you send the right signal. See the + :mod:`signal` module for constants representing signal numbers. + """ + + # Same as os.kill, but the pid is given for you. + if self.isalive(): + os.kill(self.pid, sig) + + def getwinsize(self): + """Return the window size of the pseudoterminal as a tuple (rows, cols). + """ + TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912) + s = struct.pack('HHHH', 0, 0, 0, 0) + x = fcntl.ioctl(self.fd, TIOCGWINSZ, s) + return struct.unpack('HHHH', x)[0:2] + + def setwinsize(self, rows, cols): + """Set the terminal window size of the child tty. + + This will cause a SIGWINCH signal to be sent to the child. This does not + change the physical window size. It changes the size reported to + TTY-aware applications like vi or curses -- applications that respond to + the SIGWINCH signal. + """ + return _setwinsize(self.fd, rows, cols) + + +class PtyProcessUnicode(PtyProcess): + """Unicode wrapper around a process running in a pseudoterminal. + + This class exposes a similar interface to :class:`PtyProcess`, but its read + methods return unicode, and its :meth:`write` accepts unicode. + """ + if PY3: + string_type = str + else: + string_type = unicode # analysis:ignore + + def __init__(self, pid, fd, encoding='utf-8', codec_errors='strict'): + super(PtyProcessUnicode, self).__init__(pid, fd) + self.encoding = encoding + self.codec_errors = codec_errors + self.decoder = codecs.getincrementaldecoder(encoding)(errors=codec_errors) + + def read(self, size=1024): + """Read at most ``size`` bytes from the pty, return them as unicode. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + + The size argument still refers to bytes, not unicode code points. + """ + b = super(PtyProcessUnicode, self).read(size) + return self.decoder.decode(b, final=False) + + def readline(self): + """Read one line from the pseudoterminal, and return it as unicode. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + """ + b = super(PtyProcessUnicode, self).readline() + return self.decoder.decode(b, final=False) + + def write(self, s): + """Write the unicode string ``s`` to the pseudoterminal. + + Returns the number of bytes written. + """ + b = s.encode(self.encoding) + return super(PtyProcessUnicode, self).write(b) diff --git a/.venv/lib/python3.8/site-packages/ptyprocess/util.py b/.venv/lib/python3.8/site-packages/ptyprocess/util.py new file mode 100644 index 0000000..aadbd62 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/ptyprocess/util.py @@ -0,0 +1,71 @@ +try: + from shutil import which # Python >= 3.3 +except ImportError: + import os, sys + + # This is copied from Python 3.4.1 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + # Check that a given file can be accessed with the correct mode. + # Additionally check that `file` is not a directory, as on Windows + # directories pass the os.access check. + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + if not os.curdir in path: + path.insert(0, os.curdir) + + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if not normdir in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None + + +class PtyProcessError(Exception): + """Generic error class for this package.""" diff --git a/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/LICENSE.txt b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/LICENSE.txt new file mode 100644 index 0000000..473e36e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Alex Hall + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/METADATA b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/METADATA new file mode 100644 index 0000000..931f69c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/METADATA @@ -0,0 +1,229 @@ +Metadata-Version: 2.1 +Name: pure-eval +Version: 0.2.2 +Summary: Safely evaluate AST nodes without side effects +Home-page: http://github.com/alexmojaki/pure_eval +Author: Alex Hall +Author-email: alex.mojaki@gmail.com +License: MIT +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Description-Content-Type: text/markdown +License-File: LICENSE.txt +Provides-Extra: tests +Requires-Dist: pytest ; extra == 'tests' + +# `pure_eval` + +[![Build Status](https://travis-ci.org/alexmojaki/pure_eval.svg?branch=master)](https://travis-ci.org/alexmojaki/pure_eval) [![Coverage Status](https://coveralls.io/repos/github/alexmojaki/pure_eval/badge.svg?branch=master)](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [![Supports Python versions 3.5+](https://img.shields.io/pypi/pyversions/pure_eval.svg)](https://pypi.python.org/pypi/pure_eval) + +This is a Python package that lets you safely evaluate certain AST nodes without triggering arbitrary code that may have unwanted side effects. + +It can be installed from PyPI: + + pip install pure_eval + +To demonstrate usage, suppose we have an object defined as follows: + +```python +class Rectangle: + def __init__(self, width, height): + self.width = width + self.height = height + + @property + def area(self): + print("Calculating area...") + return self.width * self.height + + +rect = Rectangle(3, 5) +``` + +Given the `rect` object, we want to evaluate whatever expressions we can in this source code: + +```python +source = "(rect.width, rect.height, rect.area)" +``` + +This library works with the AST, so let's parse the source code and peek inside: + +```python +import ast + +tree = ast.parse(source) +the_tuple = tree.body[0].value +for node in the_tuple.elts: + print(ast.dump(node)) +``` + +Output: + +```python +Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load()) +Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load()) +Attribute(value=Name(id='rect', ctx=Load()), attr='area', ctx=Load()) +``` + +Now to actually use the library. First construct an Evaluator: + +```python +from pure_eval import Evaluator + +evaluator = Evaluator({"rect": rect}) +``` + +The argument to `Evaluator` should be a mapping from variable names to their values. Or if you have access to the stack frame where `rect` is defined, you can instead use: + +```python +evaluator = Evaluator.from_frame(frame) +``` + +Now to evaluate some nodes, using `evaluator[node]`: + +```python +print("rect.width:", evaluator[the_tuple.elts[0]]) +print("rect:", evaluator[the_tuple.elts[0].value]) +``` + +Output: + +``` +rect.width: 3 +rect: <__main__.Rectangle object at 0x105b0dd30> +``` + +OK, but you could have done the same thing with `eval`. The useful part is that it will refuse to evaluate the property `rect.area` because that would trigger unknown code. If we try, it'll raise a `CannotEval` exception. + +```python +from pure_eval import CannotEval + +try: + print("rect.area:", evaluator[the_tuple.elts[2]]) # fails +except CannotEval as e: + print(e) # prints CannotEval +``` + +To find all the expressions that can be evaluated in a tree: + +```python +for node, value in evaluator.find_expressions(tree): + print(ast.dump(node), value) +``` + +Output: + +```python +Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load()) 3 +Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load()) 5 +Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30> +Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30> +Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30> +``` + +Note that this includes `rect` three times, once for each appearance in the source code. Since all these nodes are equivalent, we can group them together: + +```python +from pure_eval import group_expressions + +for nodes, values in group_expressions(evaluator.find_expressions(tree)): + print(len(nodes), "nodes with value:", values) +``` + +Output: + +``` +1 nodes with value: 3 +1 nodes with value: 5 +3 nodes with value: <__main__.Rectangle object at 0x10d374d30> +``` + +If we want to list all the expressions in a tree, we may want to filter out certain expressions whose values are obvious. For example, suppose we have a function `foo`: + +```python +def foo(): + pass +``` + +If we refer to `foo` by its name as usual, then that's not interesting: + +```python +from pure_eval import is_expression_interesting + +node = ast.parse('foo').body[0].value +print(ast.dump(node)) +print(is_expression_interesting(node, foo)) +``` + +Output: + +```python +Name(id='foo', ctx=Load()) +False +``` + +But if we refer to it by a different name, then it's interesting: + +```python +node = ast.parse('bar').body[0].value +print(ast.dump(node)) +print(is_expression_interesting(node, foo)) +``` + +Output: + +```python +Name(id='bar', ctx=Load()) +True +``` + +In general `is_expression_interesting` returns False for the following values: +- Literals (e.g. `123`, `'abc'`, `[1, 2, 3]`, `{'a': (), 'b': ([1, 2], [3])}`) +- Variables or attributes whose name is equal to the value's `__name__`, such as `foo` above or `self.foo` if it was a method. +- Builtins (e.g. `len`) referred to by their usual name. + +To make things easier, you can combine finding expressions, grouping them, and filtering out the obvious ones with: + +```python +evaluator.interesting_expressions_grouped(root) +``` + +To get the source code of an AST node, I recommend [asttokens](https://github.com/gristlabs/asttokens). + +Here's a complete example that brings it all together: + +```python +from asttokens import ASTTokens +from pure_eval import Evaluator + +source = """ +x = 1 +d = {x: 2} +y = d[x] +""" + +names = {} +exec(source, names) +atok = ASTTokens(source, parse=True) +for nodes, value in Evaluator(names).interesting_expressions_grouped(atok.tree): + print(atok.get_text(nodes[0]), "=", value) +``` + +Output: + +```python +x = 1 +d = {1: 2} +y = 2 +d[x] = 2 +``` + + diff --git a/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/RECORD b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/RECORD new file mode 100644 index 0000000..9ad8903 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/RECORD @@ -0,0 +1,17 @@ +pure_eval-0.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pure_eval-0.2.2.dist-info/LICENSE.txt,sha256=pHaiyw70xBRQNApXeii5GsTH9mkTay7hSAR_q9X8QYE,1066 +pure_eval-0.2.2.dist-info/METADATA,sha256=7ymWO8RGYzKetW67FHk45j-x3cig4U3pWAk2V-w5U38,6245 +pure_eval-0.2.2.dist-info/RECORD,, +pure_eval-0.2.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +pure_eval-0.2.2.dist-info/top_level.txt,sha256=aj24vzw3wc3XsL3Q0sHidIX6N7TaViEDr1R_R4Qbgjo,10 +pure_eval/__init__.py,sha256=epP1aW7z2hYB0FtC_x3w1AFlnhSgn4UVkRamFlVfXd4,285 +pure_eval/__pycache__/__init__.cpython-38.pyc,, +pure_eval/__pycache__/core.cpython-38.pyc,, +pure_eval/__pycache__/my_getattr_static.cpython-38.pyc,, +pure_eval/__pycache__/utils.cpython-38.pyc,, +pure_eval/__pycache__/version.cpython-38.pyc,, +pure_eval/core.py,sha256=bxshWghVwCawdVfrNdXLqHj5UOpaPz1aAkB0T3xh_3Q,15309 +pure_eval/my_getattr_static.py,sha256=JvAM30EQcf7RQKG3fYSJT1tiwEKbkPnpqPRWXjIoV8o,4076 +pure_eval/py.typed,sha256=w3xYJo71zA7pDnF65xr6u8FL8gvJ7GdAKMJSLuv2IPU,68 +pure_eval/utils.py,sha256=eSI-lVyBzYW1D2vRQPrvhXPhQpRArhv_GoBx1AIfWHc,4415 +pure_eval/version.py,sha256=pKh2_hmFNUwG0JxbL18qxdmi_nmFpgRbR8X6IPZr08o,21 diff --git a/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/top_level.txt new file mode 100644 index 0000000..e50c81f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval-0.2.2.dist-info/top_level.txt @@ -0,0 +1 @@ +pure_eval diff --git a/.venv/lib/python3.8/site-packages/pure_eval/__init__.py b/.venv/lib/python3.8/site-packages/pure_eval/__init__.py new file mode 100644 index 0000000..0040e31 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval/__init__.py @@ -0,0 +1,8 @@ +from .core import Evaluator, CannotEval, group_expressions, is_expression_interesting +from .my_getattr_static import getattr_static + +try: + from .version import __version__ +except ImportError: + # version.py is auto-generated with the git tag when building + __version__ = "???" diff --git a/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..2f4dd6e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/core.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/core.cpython-38.pyc new file mode 100644 index 0000000..b604ab8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/core.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/my_getattr_static.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/my_getattr_static.cpython-38.pyc new file mode 100644 index 0000000..42456f9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/my_getattr_static.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/utils.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000..90e4fca Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/utils.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/version.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/version.cpython-38.pyc new file mode 100644 index 0000000..42da8b9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pure_eval/__pycache__/version.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pure_eval/core.py b/.venv/lib/python3.8/site-packages/pure_eval/core.py new file mode 100644 index 0000000..748f051 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval/core.py @@ -0,0 +1,449 @@ +import ast +import builtins +import operator +from collections import ChainMap, OrderedDict, deque +from contextlib import suppress +from types import FrameType +from typing import Any, Tuple, Iterable, List, Mapping, Dict, Union, Set + +from pure_eval.my_getattr_static import getattr_static +from pure_eval.utils import ( + CannotEval, + has_ast_name, + copy_ast_without_context, + is_standard_types, + of_standard_types, + is_any, + of_type, + ensure_dict, +) + + +class Evaluator: + def __init__(self, names: Mapping[str, Any]): + """ + Construct a new evaluator with the given variable names. + This is a low level API, typically you will use `Evaluator.from_frame(frame)`. + + :param names: a mapping from variable names to their values. + """ + + self.names = names + self._cache = {} # type: Dict[ast.expr, Any] + + @classmethod + def from_frame(cls, frame: FrameType) -> 'Evaluator': + """ + Construct an Evaluator that can look up variables from the given frame. + + :param frame: a frame object, e.g. from a traceback or `inspect.currentframe().f_back`. + """ + + return cls(ChainMap( + ensure_dict(frame.f_locals), + ensure_dict(frame.f_globals), + ensure_dict(frame.f_builtins), + )) + + def __getitem__(self, node: ast.expr) -> Any: + """ + Find the value of the given node. + If it cannot be evaluated safely, this raises `CannotEval`. + The result is cached either way. + + :param node: an AST expression to evaluate + :return: the value of the node + """ + + if not isinstance(node, ast.expr): + raise TypeError("node should be an ast.expr, not {!r}".format(type(node).__name__)) + + with suppress(KeyError): + result = self._cache[node] + if result is CannotEval: + raise CannotEval + else: + return result + + try: + self._cache[node] = result = self._handle(node) + return result + except CannotEval: + self._cache[node] = CannotEval + raise + + def _handle(self, node: ast.expr) -> Any: + """ + This is where the evaluation happens. + Users should use `__getitem__`, i.e. `evaluator[node]`, + as it provides caching. + + :param node: an AST expression to evaluate + :return: the value of the node + """ + + with suppress(Exception): + return ast.literal_eval(node) + + if isinstance(node, ast.Name): + try: + return self.names[node.id] + except KeyError: + raise CannotEval + elif isinstance(node, ast.Attribute): + value = self[node.value] + attr = node.attr + return getattr_static(value, attr) + elif isinstance(node, ast.Subscript): + return self._handle_subscript(node) + elif isinstance(node, (ast.List, ast.Tuple, ast.Set, ast.Dict)): + return self._handle_container(node) + elif isinstance(node, ast.UnaryOp): + return self._handle_unary(node) + elif isinstance(node, ast.BinOp): + return self._handle_binop(node) + elif isinstance(node, ast.BoolOp): + return self._handle_boolop(node) + elif isinstance(node, ast.Compare): + return self._handle_compare(node) + elif isinstance(node, ast.Call): + return self._handle_call(node) + raise CannotEval + + def _handle_call(self, node): + if node.keywords: + raise CannotEval + func = self[node.func] + args = [self[arg] for arg in node.args] + + if ( + is_any( + func, + slice, + int, + range, + round, + complex, + list, + tuple, + abs, + hex, + bin, + oct, + bool, + ord, + float, + len, + chr, + ) + or len(args) == 0 + and is_any(func, set, dict, str, frozenset, bytes, bytearray, object) + or len(args) >= 2 + and is_any(func, str, divmod, bytes, bytearray, pow) + ): + args = [ + of_standard_types(arg, check_dict_values=False, deep=False) + for arg in args + ] + try: + return func(*args) + except Exception as e: + raise CannotEval from e + + if len(args) == 1: + arg = args[0] + if is_any(func, id, type): + try: + return func(arg) + except Exception as e: + raise CannotEval from e + if is_any(func, all, any, sum): + of_type(arg, tuple, frozenset, list, set, dict, OrderedDict, deque) + for x in arg: + of_standard_types(x, check_dict_values=False, deep=False) + try: + return func(arg) + except Exception as e: + raise CannotEval from e + + if is_any( + func, sorted, min, max, hash, set, dict, ascii, str, repr, frozenset + ): + of_standard_types(arg, check_dict_values=True, deep=True) + try: + return func(arg) + except Exception as e: + raise CannotEval from e + raise CannotEval + + def _handle_compare(self, node): + left = self[node.left] + result = True + + for op, right in zip(node.ops, node.comparators): + right = self[right] + + op_type = type(op) + op_func = { + ast.Eq: operator.eq, + ast.NotEq: operator.ne, + ast.Lt: operator.lt, + ast.LtE: operator.le, + ast.Gt: operator.gt, + ast.GtE: operator.ge, + ast.Is: operator.is_, + ast.IsNot: operator.is_not, + ast.In: (lambda a, b: a in b), + ast.NotIn: (lambda a, b: a not in b), + }[op_type] + + if op_type not in (ast.Is, ast.IsNot): + of_standard_types(left, check_dict_values=False, deep=True) + of_standard_types(right, check_dict_values=False, deep=True) + + try: + result = op_func(left, right) + except Exception as e: + raise CannotEval from e + if not result: + return result + left = right + + return result + + def _handle_boolop(self, node): + left = of_standard_types( + self[node.values[0]], check_dict_values=False, deep=False + ) + + for right in node.values[1:]: + # We need short circuiting so that the whole operation can be evaluated + # even if the right operand can't + if isinstance(node.op, ast.Or): + left = left or of_standard_types( + self[right], check_dict_values=False, deep=False + ) + else: + assert isinstance(node.op, ast.And) + left = left and of_standard_types( + self[right], check_dict_values=False, deep=False + ) + return left + + def _handle_binop(self, node): + op_type = type(node.op) + op = { + ast.Add: operator.add, + ast.Sub: operator.sub, + ast.Mult: operator.mul, + ast.Div: operator.truediv, + ast.FloorDiv: operator.floordiv, + ast.Mod: operator.mod, + ast.Pow: operator.pow, + ast.LShift: operator.lshift, + ast.RShift: operator.rshift, + ast.BitOr: operator.or_, + ast.BitXor: operator.xor, + ast.BitAnd: operator.and_, + }.get(op_type) + if not op: + raise CannotEval + left = self[node.left] + hash_type = is_any(type(left), set, frozenset, dict, OrderedDict) + left = of_standard_types(left, check_dict_values=False, deep=hash_type) + formatting = type(left) in (str, bytes) and op_type == ast.Mod + + right = of_standard_types( + self[node.right], + check_dict_values=formatting, + deep=formatting or hash_type, + ) + try: + return op(left, right) + except Exception as e: + raise CannotEval from e + + def _handle_unary(self, node: ast.UnaryOp): + value = of_standard_types( + self[node.operand], check_dict_values=False, deep=False + ) + op_type = type(node.op) + op = { + ast.USub: operator.neg, + ast.UAdd: operator.pos, + ast.Not: operator.not_, + ast.Invert: operator.invert, + }[op_type] + try: + return op(value) + except Exception as e: + raise CannotEval from e + + def _handle_subscript(self, node): + value = self[node.value] + of_standard_types( + value, check_dict_values=False, deep=is_any(type(value), dict, OrderedDict) + ) + index = node.slice + if isinstance(index, ast.Slice): + index = slice( + *[ + None if p is None else self[p] + for p in [index.lower, index.upper, index.step] + ] + ) + elif isinstance(index, ast.ExtSlice): + raise CannotEval + else: + if isinstance(index, ast.Index): + index = index.value + index = self[index] + of_standard_types(index, check_dict_values=False, deep=True) + + try: + return value[index] + except Exception: + raise CannotEval + + def _handle_container( + self, + node: Union[ast.List, ast.Tuple, ast.Set, ast.Dict] + ) -> Union[List, Tuple, Set, Dict]: + """Handle container nodes, including List, Set, Tuple and Dict""" + if isinstance(node, ast.Dict): + elts = node.keys + if None in elts: # ** unpacking inside {}, not yet supported + raise CannotEval + else: + elts = node.elts + elts = [self[elt] for elt in elts] + if isinstance(node, ast.List): + return elts + if isinstance(node, ast.Tuple): + return tuple(elts) + + # Set and Dict + if not all( + is_standard_types(elt, check_dict_values=False, deep=True) for elt in elts + ): + raise CannotEval + + if isinstance(node, ast.Set): + try: + return set(elts) + except TypeError: + raise CannotEval + + assert isinstance(node, ast.Dict) + + pairs = [(elt, self[val]) for elt, val in zip(elts, node.values)] + try: + return dict(pairs) + except TypeError: + raise CannotEval + + def find_expressions(self, root: ast.AST) -> Iterable[Tuple[ast.expr, Any]]: + """ + Find all expressions in the given tree that can be safely evaluated. + This is a low level API, typically you will use `interesting_expressions_grouped`. + + :param root: any AST node + :return: generator of pairs (tuples) of expression nodes and their corresponding values. + """ + + for node in ast.walk(root): + if not isinstance(node, ast.expr): + continue + + try: + value = self[node] + except CannotEval: + continue + + yield node, value + + def interesting_expressions_grouped(self, root: ast.AST) -> List[Tuple[List[ast.expr], Any]]: + """ + Find all interesting expressions in the given tree that can be safely evaluated, + grouping equivalent nodes together. + + For more control and details, see: + - Evaluator.find_expressions + - is_expression_interesting + - group_expressions + + :param root: any AST node + :return: A list of pairs (tuples) containing: + - A list of equivalent AST expressions + - The value of the first expression node + (which should be the same for all nodes, unless threads are involved) + """ + + return group_expressions( + pair + for pair in self.find_expressions(root) + if is_expression_interesting(*pair) + ) + + +def is_expression_interesting(node: ast.expr, value: Any) -> bool: + """ + Determines if an expression is potentially interesting, at least in my opinion. + Returns False for the following expressions whose value is generally obvious: + - Literals (e.g. 123, 'abc', [1, 2, 3], {'a': (), 'b': ([1, 2], [3])}) + - Variables or attributes whose name is equal to the value's __name__. + For example, a function `def foo(): ...` is not interesting when referred to + as `foo` as it usually would, but `bar` can be interesting if `bar is foo`. + Similarly the method `self.foo` is not interesting. + - Builtins (e.g. `len`) referred to by their usual name. + + This is a low level API, typically you will use `interesting_expressions_grouped`. + + :param node: an AST expression + :param value: the value of the node + :return: a boolean: True if the expression is interesting, False otherwise + """ + + with suppress(ValueError): + ast.literal_eval(node) + return False + + # TODO exclude inner modules, e.g. numpy.random.__name__ == 'numpy.random' != 'random' + # TODO exclude common module abbreviations, e.g. numpy as np, pandas as pd + if has_ast_name(value, node): + return False + + if ( + isinstance(node, ast.Name) + and getattr(builtins, node.id, object()) is value + ): + return False + + return True + + +def group_expressions(expressions: Iterable[Tuple[ast.expr, Any]]) -> List[Tuple[List[ast.expr], Any]]: + """ + Organise expression nodes and their values such that equivalent nodes are together. + Two nodes are considered equivalent if they have the same structure, + ignoring context (Load, Store, or Delete) and location (lineno, col_offset). + For example, this will group together the same variable name mentioned multiple times in an expression. + + This will not check the values of the nodes. Equivalent nodes should have the same values, + unless threads are involved. + + This is a low level API, typically you will use `interesting_expressions_grouped`. + + :param expressions: pairs of AST expressions and their values, as obtained from + `Evaluator.find_expressions`, or `(node, evaluator[node])`. + :return: A list of pairs (tuples) containing: + - A list of equivalent AST expressions + - The value of the first expression node + (which should be the same for all nodes, unless threads are involved) + """ + + result = {} + for node, value in expressions: + dump = ast.dump(copy_ast_without_context(node)) + result.setdefault(dump, ([], value))[0].append(node) + return list(result.values()) diff --git a/.venv/lib/python3.8/site-packages/pure_eval/my_getattr_static.py b/.venv/lib/python3.8/site-packages/pure_eval/my_getattr_static.py new file mode 100644 index 0000000..c750b1a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval/my_getattr_static.py @@ -0,0 +1,138 @@ +import types + +from pure_eval.utils import of_type, CannotEval + +_sentinel = object() + + +def _static_getmro(klass): + return type.__dict__['__mro__'].__get__(klass) + + +def _check_instance(obj, attr): + instance_dict = {} + try: + instance_dict = object.__getattribute__(obj, "__dict__") + except AttributeError: + pass + return dict.get(instance_dict, attr, _sentinel) + + +def _check_class(klass, attr): + for entry in _static_getmro(klass): + if _shadowed_dict(type(entry)) is _sentinel: + try: + return entry.__dict__[attr] + except KeyError: + pass + else: + break + return _sentinel + + +def _is_type(obj): + try: + _static_getmro(obj) + except TypeError: + return False + return True + + +def _shadowed_dict(klass): + dict_attr = type.__dict__["__dict__"] + for entry in _static_getmro(klass): + try: + class_dict = dict_attr.__get__(entry)["__dict__"] + except KeyError: + pass + else: + if not (type(class_dict) is types.GetSetDescriptorType and + class_dict.__name__ == "__dict__" and + class_dict.__objclass__ is entry): + return class_dict + return _sentinel + + +def getattr_static(obj, attr): + """Retrieve attributes without triggering dynamic lookup via the + descriptor protocol, __getattr__ or __getattribute__. + + Note: this function may not be able to retrieve all attributes + that getattr can fetch (like dynamically created attributes) + and may find attributes that getattr can't (like descriptors + that raise AttributeError). It can also return descriptor objects + instead of instance members in some cases. See the + documentation for details. + """ + instance_result = _sentinel + if not _is_type(obj): + klass = type(obj) + dict_attr = _shadowed_dict(klass) + if (dict_attr is _sentinel or + type(dict_attr) is types.MemberDescriptorType): + instance_result = _check_instance(obj, attr) + else: + raise CannotEval + else: + klass = obj + + klass_result = _check_class(klass, attr) + + if instance_result is not _sentinel and klass_result is not _sentinel: + if (_check_class(type(klass_result), '__get__') is not _sentinel and + _check_class(type(klass_result), '__set__') is not _sentinel): + return _resolve_descriptor(klass_result, obj, klass) + + if instance_result is not _sentinel: + return instance_result + if klass_result is not _sentinel: + get = _check_class(type(klass_result), '__get__') + if get is _sentinel: + return klass_result + else: + if obj is klass: + instance = None + else: + instance = obj + return _resolve_descriptor(klass_result, instance, klass) + + if obj is klass: + # for types we check the metaclass too + for entry in _static_getmro(type(klass)): + if _shadowed_dict(type(entry)) is _sentinel: + try: + result = entry.__dict__[attr] + get = _check_class(type(result), '__get__') + if get is not _sentinel: + raise CannotEval + return result + except KeyError: + pass + raise CannotEval + + +class _foo: + __slots__ = ['foo'] + method = lambda: 0 + + +slot_descriptor = _foo.foo +wrapper_descriptor = str.__dict__['__add__'] +method_descriptor = str.__dict__['startswith'] +user_method_descriptor = _foo.__dict__['method'] + +safe_descriptors_raw = [ + slot_descriptor, + wrapper_descriptor, + method_descriptor, + user_method_descriptor, +] + +safe_descriptor_types = list(map(type, safe_descriptors_raw)) + + +def _resolve_descriptor(d, instance, owner): + try: + return type(of_type(d, *safe_descriptor_types)).__get__(d, instance, owner) + except AttributeError as e: + raise CannotEval from e diff --git a/.venv/lib/python3.8/site-packages/pure_eval/py.typed b/.venv/lib/python3.8/site-packages/pure_eval/py.typed new file mode 100644 index 0000000..298c64a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. The pure_eval package uses inline types. diff --git a/.venv/lib/python3.8/site-packages/pure_eval/utils.py b/.venv/lib/python3.8/site-packages/pure_eval/utils.py new file mode 100644 index 0000000..a8a3730 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval/utils.py @@ -0,0 +1,201 @@ +from collections import OrderedDict, deque +from datetime import date, time, datetime +from decimal import Decimal +from fractions import Fraction +import ast +import enum +import typing + + +class CannotEval(Exception): + def __repr__(self): + return self.__class__.__name__ + + __str__ = __repr__ + + +def is_any(x, *args): + return any( + x is arg + for arg in args + ) + + +def of_type(x, *types): + if is_any(type(x), *types): + return x + else: + raise CannotEval + + +def of_standard_types(x, *, check_dict_values: bool, deep: bool): + if is_standard_types(x, check_dict_values=check_dict_values, deep=deep): + return x + else: + raise CannotEval + + +def is_standard_types(x, *, check_dict_values: bool, deep: bool): + try: + return _is_standard_types_deep(x, check_dict_values, deep)[0] + except RecursionError: + return False + + +def _is_standard_types_deep(x, check_dict_values: bool, deep: bool): + typ = type(x) + if is_any( + typ, + str, + int, + bool, + float, + bytes, + complex, + date, + time, + datetime, + Fraction, + Decimal, + type(None), + object, + ): + return True, 0 + + if is_any(typ, tuple, frozenset, list, set, dict, OrderedDict, deque, slice): + if typ in [slice]: + length = 0 + else: + length = len(x) + assert isinstance(deep, bool) + if not deep: + return True, length + + if check_dict_values and typ in (dict, OrderedDict): + items = (v for pair in x.items() for v in pair) + elif typ is slice: + items = [x.start, x.stop, x.step] + else: + items = x + for item in items: + if length > 100000: + return False, length + is_standard, item_length = _is_standard_types_deep( + item, check_dict_values, deep + ) + if not is_standard: + return False, length + length += item_length + return True, length + + return False, 0 + + +class _E(enum.Enum): + pass + + +class _C: + def foo(self): pass # pragma: nocover + + def bar(self): pass # pragma: nocover + + @classmethod + def cm(cls): pass # pragma: nocover + + @staticmethod + def sm(): pass # pragma: nocover + + +safe_name_samples = { + "len": len, + "append": list.append, + "__add__": list.__add__, + "insert": [].insert, + "__mul__": [].__mul__, + "fromkeys": dict.__dict__['fromkeys'], + "is_any": is_any, + "__repr__": CannotEval.__repr__, + "foo": _C().foo, + "bar": _C.bar, + "cm": _C.cm, + "sm": _C.sm, + "ast": ast, + "CannotEval": CannotEval, + "_E": _E, +} + +typing_annotation_samples = { + name: getattr(typing, name) + for name in "List Dict Tuple Set Callable Mapping".split() +} + +safe_name_types = tuple({ + type(f) + for f in safe_name_samples.values() +}) + + +typing_annotation_types = tuple({ + type(f) + for f in typing_annotation_samples.values() +}) + + +def eq_checking_types(a, b): + return type(a) is type(b) and a == b + + +def ast_name(node): + if isinstance(node, ast.Name): + return node.id + elif isinstance(node, ast.Attribute): + return node.attr + else: + return None + + +def safe_name(value): + typ = type(value) + if is_any(typ, *safe_name_types): + return value.__name__ + elif value is typing.Optional: + return "Optional" + elif value is typing.Union: + return "Union" + elif is_any(typ, *typing_annotation_types): + return getattr(value, "__name__", None) or getattr(value, "_name", None) + else: + return None + + +def has_ast_name(value, node): + value_name = safe_name(value) + if type(value_name) is not str: + return False + return eq_checking_types(ast_name(node), value_name) + + +def copy_ast_without_context(x): + if isinstance(x, ast.AST): + kwargs = { + field: copy_ast_without_context(getattr(x, field)) + for field in x._fields + if field != 'ctx' + if hasattr(x, field) + } + return type(x)(**kwargs) + elif isinstance(x, list): + return list(map(copy_ast_without_context, x)) + else: + return x + + +def ensure_dict(x): + """ + Handles invalid non-dict inputs + """ + try: + return dict(x) + except Exception: + return {} diff --git a/.venv/lib/python3.8/site-packages/pure_eval/version.py b/.venv/lib/python3.8/site-packages/pure_eval/version.py new file mode 100644 index 0000000..9dd16a3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pure_eval/version.py @@ -0,0 +1 @@ +__version__ = '0.2.2' \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/LICENSE b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/LICENSE new file mode 100644 index 0000000..72d9921 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/LICENSE @@ -0,0 +1,25 @@ +Copyright © 2006-2009 Johann C. Rocholl +Copyright © 2009-2014 Florent Xicluna +Copyright © 2014-2020 Ian Lee + +Licensed under the terms of the Expat License + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/METADATA b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/METADATA new file mode 100644 index 0000000..9190688 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/METADATA @@ -0,0 +1,1063 @@ +Metadata-Version: 2.1 +Name: pycodestyle +Version: 2.8.0 +Summary: Python style guide checker +Home-page: https://pycodestyle.pycqa.org/ +Author: Johann C. Rocholl +Author-email: johann@rocholl.net +Maintainer: Ian Lee +Maintainer-email: IanLee1521@gmail.com +License: Expat license +Project-URL: Changes, https://pycodestyle.pycqa.org/en/latest/developer.html#changes +Keywords: pycodestyle,pep8,PEP 8,PEP-8,PEP8 +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +License-File: LICENSE + +pycodestyle (formerly called pep8) - Python style guide checker +=============================================================== + +.. image:: https://github.com/PyCQA/pycodestyle/actions/workflows/main.yml/badge.svg + :target: https://github.com/PyCQA/pycodestyle/actions/workflows/main.yml + :alt: Build status + +.. image:: https://readthedocs.org/projects/pycodestyle/badge/?version=latest + :target: https://pycodestyle.pycqa.org + :alt: Documentation Status + +.. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg + :target: https://pypi.org/project/pycodestyle/ + :alt: Wheel Status + +.. image:: https://badges.gitter.im/PyCQA/pycodestyle.svg + :alt: Join the chat at https://gitter.im/PyCQA/pycodestyle + :target: https://gitter.im/PyCQA/pycodestyle?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +pycodestyle is a tool to check your Python code against some of the style +conventions in `PEP 8`_. + +.. _PEP 8: http://www.python.org/dev/peps/pep-0008/ + +.. note:: + + This package used to be called ``pep8`` but was renamed to ``pycodestyle`` + to reduce confusion. Further discussion can be found `in the issue where + Guido requested this + change `_, or in the + lightning talk at PyCon 2016 by @IanLee1521: + `slides `_ + `video `_. + +Features +-------- + +* Plugin architecture: Adding new checks is easy. + +* Parseable output: Jump to error location in your editor. + +* Small: Just one Python file, requires only stdlib. You can use just + the ``pycodestyle.py`` file for this purpose. + +* Comes with a comprehensive test suite. + +Installation +------------ + +You can install, upgrade, and uninstall ``pycodestyle.py`` with these commands:: + + $ pip install pycodestyle + $ pip install --upgrade pycodestyle + $ pip uninstall pycodestyle + +There's also a package for Debian/Ubuntu, but it's not always the +latest version. + +Example usage and output +------------------------ + +:: + + $ pycodestyle --first optparse.py + optparse.py:69:11: E401 multiple imports on one line + optparse.py:77:1: E302 expected 2 blank lines, found 1 + optparse.py:88:5: E301 expected 1 blank line, found 0 + optparse.py:222:34: W602 deprecated form of raising exception + optparse.py:347:31: E211 whitespace before '(' + optparse.py:357:17: E201 whitespace after '{' + optparse.py:472:29: E221 multiple spaces before operator + optparse.py:544:21: W601 .has_key() is deprecated, use 'in' + +You can also make ``pycodestyle.py`` show the source code for each error, and +even the relevant text from PEP 8:: + + $ pycodestyle --show-source --show-pep8 testsuite/E40.py + testsuite/E40.py:2:10: E401 multiple imports on one line + import os, sys + ^ + Imports should usually be on separate lines. + + Okay: import os\nimport sys + E401: import sys, os + + +Or you can display how often each error was found:: + + $ pycodestyle --statistics -qq Python-2.5/Lib + 232 E201 whitespace after '[' + 599 E202 whitespace before ')' + 631 E203 whitespace before ',' + 842 E211 whitespace before '(' + 2531 E221 multiple spaces before operator + 4473 E301 expected 1 blank line, found 0 + 4006 E302 expected 2 blank lines, found 1 + 165 E303 too many blank lines (4) + 325 E401 multiple imports on one line + 3615 E501 line too long (82 characters) + 612 W601 .has_key() is deprecated, use 'in' + 1188 W602 deprecated form of raising exception + +Links +----- + +* `Read the documentation `_ + +* `Fork me on GitHub `_ + + +Changelog +========= + +2.8.0 (2021-10-10) +------------------ + +Changes: + +* Drop python 3.4. PR #982. +* E712: fix false negative with multiple comparisons. PR #987. +* E211: fix false positives with ``match``. PR #989. +* E772: improve performance of bare except check. PR #992. +* Backport tokenize performance improvement from python 3.10. PR #993. +* E225: fix for lambdas containing positional-only args. PR #1012. +* Remove ``indent_size_str`` "setting". PR #995. +* E402: allow ``__all__`` to be typed. PR #1019. +* E225: fix false positives for ``*`` in ``case``. PR #1003. +* E201: detect tabs as whitespace. PR #1015. + + +2.7.0 (2021-03-14) +------------------ + +Changes: + +* Fix physical checks (such as W191) at end of file. PR #961. +* Add ``--indent-size`` option (defaulting to ``4``). PR #970. +* W605: fix escaped crlf false positive on windows. PR #976. + + +2.6.0 (2020-05-11) +------------------ + +Announcements: + +* Anthony Sottile (@asottile) joined the team as a core developer. :tada: + +Changes: + +* E306: fix detection inside ``async def``. PR #929. +* E301: fix regression disallowing decorated one-liners. PR #927. +* E714: fix false positive with chained ``is not``. PR #931. + + +2.6.0a1 (2020-04-23) +-------------------- + +New checks: + +* E225: require whitespace around ``and`` ``in`` ``is`` and ``or``. PR #847. + +Changes: + +* E117: fix indentation using tabs by treating as 8-space indents. PR #837. +* E721: fix false positive with names containg ``istype``. PR #850. +* E741: allow ``l`` as a named argument in a function call. PR #853. +* E302: fix false-negative with decorated functions. PR #859. +* W504: ellipsis (``...``) is no longer treated as a binary operator. PR #875. +* E402: allow ``with``, ``if``, ``elif``, ``else`` to guard imports. PR #834. +* Add support for assignment expressions ``:=`` (PEP 572). PR #879. +* Add support for positional-only arguments ``/`` (PEP 570). PR #872, #918. +* Add support for python 3.8. +* Add support for matrix multiplication operator ``@`` (PEP 465). PR #897. +* Support visual indent for continuation lines for ``with`` / ``assert`` / + ``raise``. PR #912. +* E302: allow two blank lines after a block of one-liners. PR #913. +* E302: allow two-and-fewer newlines at the top of the file. PR #919. + + +2.5.0 (2019-01-29) +------------------ + +New checks: + +* E117: Over-indented code blocks +* W505: Maximum doc-string length only when configured with --max-doc-length + +Changes: + +* Remove support for EOL Python 2.6 and 3.3. PR #720. +* Add E117 error for over-indented code blocks. +* Allow W605 to be silenced by `# noqa` and fix the position reported by W605 +* Allow users to omit blank lines around one-liner definitions of classes and + functions +* Include the function return annotation (``->``) as requiring surrounding + whitespace only on Python 3 +* Verify that only names can follow ``await``. Previously we allowed numbers + and strings. +* Add support for Python 3.7 +* Fix detection of annotated argument defaults for E252 +* Correct the position reported by W504 + + +2.4.0 (2018-04-10) +------------------ + +New checks: + +* Add W504 warning for checking that a break doesn't happen after a binary + operator. This check is ignored by default. PR #502. +* Add W605 warning for invalid escape sequences in string literals. PR #676. +* Add W606 warning for 'async' and 'await' reserved keywords being introduced + in Python 3.7. PR #684. +* Add E252 error for missing whitespace around equal sign in type annotated + function arguments with defaults values. PR #717. + +Changes: + +* An internal bisect search has replaced a linear search in order to improve + efficiency. PR #648. +* pycodestyle now uses PyPI trove classifiers in order to document supported + python versions on PyPI. PR #654. +* 'setup.cfg' '[wheel]' section has been renamed to '[bdist_wheel]', as + the former is legacy. PR #653. +* pycodestyle now handles very long lines much more efficiently for python + 3.2+. Fixes #643. PR #644. +* You can now write 'pycodestyle.StyleGuide(verbose=True)' instead of + 'pycodestyle.StyleGuide(verbose=True, paths=['-v'])' in order to achieve + verbosity. PR #663. +* The distribution of pycodestyle now includes the license text in order to + comply with open source licenses which require this. PR #694. +* 'maximum_line_length' now ignores shebang ('#!') lines. PR #736. +* Add configuration option for the allowed number of blank lines. It is + implemented as a top level dictionary which can be easily overwritten. Fixes + #732. PR #733. + +Bugs: + +* Prevent a 'DeprecationWarning', and a 'SyntaxError' in future python, caused + by an invalid escape sequence. PR #625. +* Correctly report E501 when the first line of a docstring is too long. + Resolves #622. PR #630. +* Support variable annotation when variable start by a keyword, such as class + variable type annotations in python 3.6. PR #640. +* pycodestyle internals have been changed in order to allow 'python3 -m + cProfile' to report correct metrics. PR #647. +* Fix a spelling mistake in the description of E722. PR #697. +* 'pycodestyle --diff' now does not break if your 'gitconfig' enables + 'mnemonicprefix'. PR #706. + +2.3.1 (2017-01-31) +------------------ + +Bugs: + +* Fix regression in detection of E302 and E306; #618, #620 + +2.3.0 (2017-01-30) +------------------ + +New Checks: + +* Add E722 warning for bare ``except`` clauses +* Report E704 for async function definitions (``async def``) + +Bugs: + +* Fix another E305 false positive for variables beginning with "class" or + "def" +* Fix detection of multiple spaces between ``async`` and ``def`` +* Fix handling of variable annotations. Stop reporting E701 on Python 3.6 for + variable annotations. + +2.2.0 (2016-11-14) +------------------ + +Announcements: + +* Added Make target to obtain proper tarball file permissions; #599 + +Bugs: + +* Fixed E305 regression caused by #400; #593 + +2.1.0 (2016-11-04) +------------------ + +Announcements: + +* Change all references to the pep8 project to say pycodestyle; #530 + +Changes: + +* Report E302 for blank lines before an "async def"; #556 +* Update our list of tested and supported Python versions which are 2.6, 2.7, + 3.2, 3.3, 3.4 and 3.5 as well as the nightly Python build and PyPy. +* Report E742 and E743 for functions and classes badly named 'l', 'O', or 'I'. +* Report E741 on 'global' and 'nonlocal' statements, as well as prohibited + single-letter variables. +* Deprecated use of `[pep8]` section name in favor of `[pycodestyle]`; #591 +* Report E722 when bare except clause is used; #579 + +Bugs: + +* Fix opt_type AssertionError when using Flake8 2.6.2 and pycodestyle; #561 +* Require two blank lines after toplevel def, class; #536 +* Remove accidentally quadratic computation based on the number of colons. This + will make pycodestyle faster in some cases; #314 + +2.0.0 (2016-05-31) +------------------ + +Announcements: + +* Repository renamed to `pycodestyle`; Issue #466 / #481. +* Added joint Code of Conduct as member of PyCQA; #483 + +Changes: + +* Added tox test support for Python 3.5 and pypy3 +* Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 +* Added W503 to the list of codes ignored by default ignore list; #498 +* Removed use of project level `.pep8` configuration file; #364 + +Bugs: + +* Fixed bug with treating `~` operator as binary; #383 / #384 +* Identify binary operators as unary; #484 / #485 + +1.7.0 (2016-01-12) +------------------ + +Announcements: + +* Repository moved to PyCQA Organization on GitHub: + https://github.com/pycqa/pep8 + +Changes: + +* Reverted the fix in #368, "options passed on command line are only ones + accepted" feature. This has many unintended consequences in pep8 and flake8 + and needs to be reworked when I have more time. +* Added support for Python 3.5. (Issue #420 & #459) +* Added support for multi-line config_file option parsing. (Issue #429) +* Improved parameter parsing. (Issues #420 & #456) + +Bugs: + +* Fixed BytesWarning on Python 3. (Issue #459) + +1.6.2 (2015-02-15) +------------------ + +Changes: + +* Added check for breaking around a binary operator. (Issue #197, Pull #305) + +Bugs: + +* Restored config_file parameter in process_options(). (Issue #380) + + +1.6.1 (2015-02-08) +------------------ + +Changes: + +* Assign variables before referenced. (Issue #287) + +Bugs: + +* Exception thrown due to unassigned ``local_dir`` variable. (Issue #377) + + +1.6.0 (2015-02-06) +------------------ + +News: + +* Ian Lee joined the project as a maintainer. + +Changes: + +* Report E731 for lambda assignment. (Issue #277) + +* Report E704 for one-liner def instead of E701. + Do not report this error in the default configuration. (Issue #277) + +* Replace codes E111, E112 and E113 with codes E114, E115 and E116 + for bad indentation of comments. (Issue #274) + +* Report E266 instead of E265 when the block comment starts with + multiple ``#``. (Issue #270) + +* Report E402 for import statements not at the top of the file. (Issue #264) + +* Do not enforce whitespaces around ``**`` operator. (Issue #292) + +* Strip whitespace from around paths during normalization. (Issue #339 / #343) + +* Update ``--format`` documentation. (Issue #198 / Pull Request #310) + +* Add ``.tox/`` to default excludes. (Issue #335) + +* Do not report E121 or E126 in the default configuration. (Issues #256 / #316) + +* Allow spaces around the equals sign in an annotated function. (Issue #357) + +* Allow trailing backslash if in an inline comment. (Issue #374) + +* If ``--config`` is used, only that configuration is processed. Otherwise, + merge the user and local configurations are merged. (Issue #368 / #369) + +Bug fixes: + +* Don't crash if Checker.build_tokens_line() returns None. (Issue #306) + +* Don't crash if os.path.expanduser() throws an ImportError. (Issue #297) + +* Missing space around keyword parameter equal not always reported, E251. + (Issue #323) + +* Fix false positive E711/E712/E713. (Issues #330 and #336) + +* Do not skip physical checks if the newline is escaped. (Issue #319) + +* Flush sys.stdout to avoid race conditions with printing. See flake8 bug: + https://gitlab.com/pycqa/flake8/issues/17 for more details. (Issue #363) + + +1.5.7 (2014-05-29) +------------------ + +Bug fixes: + +* Skip the traceback on "Broken pipe" signal. (Issue #275) + +* Do not exit when an option in ``setup.cfg`` or ``tox.ini`` + is not recognized. + +* Check the last line even if it does not end with a newline. (Issue #286) + +* Always open files in universal newlines mode in Python 2. (Issue #288) + + +1.5.6 (2014-04-14) +------------------ + +Bug fixes: + +* Check the last line even if it has no end-of-line. (Issue #273) + + +1.5.5 (2014-04-10) +------------------ + +Bug fixes: + +* Fix regression with E22 checks and inline comments. (Issue #271) + + +1.5.4 (2014-04-07) +------------------ + +Bug fixes: + +* Fix negative offset with E303 before a multi-line docstring. + (Issue #269) + + +1.5.3 (2014-04-04) +------------------ + +Bug fixes: + +* Fix wrong offset computation when error is on the last char + of a physical line. (Issue #268) + + +1.5.2 (2014-04-04) +------------------ + +Changes: + +* Distribute a universal wheel file. + +Bug fixes: + +* Report correct line number for E303 with comments. (Issue #60) + +* Do not allow newline after parameter equal. (Issue #252) + +* Fix line number reported for multi-line strings. (Issue #220) + +* Fix false positive E121/E126 with multi-line strings. (Issue #265) + +* Fix E501 not detected in comments with Python 2.5. + +* Fix caret position with ``--show-source`` when line contains tabs. + + +1.5.1 (2014-03-27) +------------------ + +Bug fixes: + +* Fix a crash with E125 on multi-line strings. (Issue #263) + + +1.5 (2014-03-26) +---------------- + +Changes: + +* Report E129 instead of E125 for visually indented line with same + indent as next logical line. (Issue #126) + +* Report E265 for space before block comment. (Issue #190) + +* Report E713 and E714 when operators ``not in`` and ``is not`` are + recommended. (Issue #236) + +* Allow long lines in multiline strings and comments if they cannot + be wrapped. (Issue #224). + +* Optionally disable physical line checks inside multiline strings, + using ``# noqa``. (Issue #242) + +* Change text for E121 to report "continuation line under-indented + for hanging indent" instead of indentation not being a + multiple of 4. + +* Report E131 instead of E121 / E126 if the hanging indent is not + consistent within the same continuation block. It helps when + error E121 or E126 is in the ``ignore`` list. + +* Report E126 instead of E121 when the continuation line is hanging + with extra indentation, even if indentation is not a multiple of 4. + +Bug fixes: + +* Allow the checkers to report errors on empty files. (Issue #240) + +* Fix ignoring too many checks when ``--select`` is used with codes + declared in a flake8 extension. (Issue #216) + +* Fix regression with multiple brackets. (Issue #214) + +* Fix ``StyleGuide`` to parse the local configuration if the + keyword argument ``paths`` is specified. (Issue #246) + +* Fix a false positive E124 for hanging indent. (Issue #254) + +* Fix a false positive E126 with embedded colon. (Issue #144) + +* Fix a false positive E126 when indenting with tabs. (Issue #204) + +* Fix behaviour when ``exclude`` is in the configuration file and + the current directory is not the project directory. (Issue #247) + +* The logical checks can return ``None`` instead of an empty iterator. + (Issue #250) + +* Do not report multiple E101 if only the first indentation starts + with a tab. (Issue #237) + +* Fix a rare false positive W602. (Issue #34) + + +1.4.6 (2013-07-02) +------------------ + +Changes: + +* Honor ``# noqa`` for errors E711 and E712. (Issue #180) + +* When both a ``tox.ini`` and a ``setup.cfg`` are present in the project + directory, merge their contents. The ``tox.ini`` file takes + precedence (same as before). (Issue #182) + +* Give priority to ``--select`` over ``--ignore``. (Issue #188) + +* Compare full path when excluding a file. (Issue #186) + +* New option ``--hang-closing`` to switch to the alternative style of + closing bracket indentation for hanging indent. Add error E133 for + closing bracket which is missing indentation. (Issue #103) + +* Accept both styles of closing bracket indentation for hanging indent. + Do not report error E123 in the default configuration. (Issue #103) + +Bug fixes: + +* Do not crash when running AST checks and the document contains null bytes. + (Issue #184) + +* Correctly report other E12 errors when E123 is ignored. (Issue #103) + +* Fix false positive E261/E262 when the file contains a BOM. (Issue #193) + +* Fix E701, E702 and E703 not detected sometimes. (Issue #196) + +* Fix E122 not detected in some cases. (Issue #201 and #208) + +* Fix false positive E121 with multiple brackets. (Issue #203) + + +1.4.5 (2013-03-06) +------------------ + +* When no path is specified, do not try to read from stdin. The feature + was added in 1.4.3, but it is not supported on Windows. Use ``-`` + filename argument to read from stdin. This usage is supported + since 1.3.4. (Issue #170) + +* Do not require ``setuptools`` in setup.py. It works around an issue + with ``pip`` and Python 3. (Issue #172) + +* Add ``__pycache__`` to the ignore list. + +* Change misleading message for E251. (Issue #171) + +* Do not report false E302 when the source file has a coding cookie or a + comment on the first line. (Issue #174) + +* Reorganize the tests and add tests for the API and for the command line + usage and options. (Issues #161 and #162) + +* Ignore all checks which are not explicitly selected when ``select`` is + passed to the ``StyleGuide`` constructor. + + +1.4.4 (2013-02-24) +------------------ + +* Report E227 or E228 instead of E225 for whitespace around bitwise, shift + or modulo operators. (Issue #166) + +* Change the message for E226 to make clear that it is about arithmetic + operators. + +* Fix a false positive E128 for continuation line indentation with tabs. + +* Fix regression with the ``--diff`` option. (Issue #169) + +* Fix the ``TestReport`` class to print the unexpected warnings and + errors. + + +1.4.3 (2013-02-22) +------------------ + +* Hide the ``--doctest`` and ``--testsuite`` options when installed. + +* Fix crash with AST checkers when the syntax is invalid. (Issue #160) + +* Read from standard input if no path is specified. + +* Initiate a graceful shutdown on ``Control+C``. + +* Allow changing the ``checker_class`` for the ``StyleGuide``. + + +1.4.2 (2013-02-10) +------------------ + +* Support AST checkers provided by third-party applications. + +* Register new checkers with ``register_check(func_or_cls, codes)``. + +* Allow constructing a ``StyleGuide`` with a custom parser. + +* Accept visual indentation without parenthesis after the ``if`` + statement. (Issue #151) + +* Fix UnboundLocalError when using ``# noqa`` with continued lines. + (Issue #158) + +* Re-order the lines for the ``StandardReport``. + +* Expand tabs when checking E12 continuation lines. (Issue #155) + +* Refactor the testing class ``TestReport`` and the specific test + functions into a separate test module. + + +1.4.1 (2013-01-18) +------------------ + +* Allow sphinx.ext.autodoc syntax for comments. (Issue #110) + +* Report E703 instead of E702 for the trailing semicolon. (Issue #117) + +* Honor ``# noqa`` in addition to ``# nopep8``. (Issue #149) + +* Expose the ``OptionParser`` factory for better extensibility. + + +1.4 (2012-12-22) +---------------- + +* Report E226 instead of E225 for optional whitespace around common + operators (``*``, ``**``, ``/``, ``+`` and ``-``). This new error + code is ignored in the default configuration because PEP 8 recommends + to "use your own judgement". (Issue #96) + +* Lines with a ``# nopep8`` at the end will not issue errors on line + length E501 or continuation line indentation E12*. (Issue #27) + +* Fix AssertionError when the source file contains an invalid line + ending ``"\r\r\n"``. (Issue #119) + +* Read the ``[pep8]`` section of ``tox.ini`` or ``setup.cfg`` if present. + (Issue #93 and #141) + +* Add the Sphinx-based documentation, and publish it + on https://pycodestyle.readthedocs.io/. (Issue #105) + + +1.3.4 (2012-12-18) +------------------ + +* Fix false positive E124 and E128 with comments. (Issue #100) + +* Fix error on stdin when running with bpython. (Issue #101) + +* Fix false positive E401. (Issue #104) + +* Report E231 for nested dictionary in list. (Issue #142) + +* Catch E271 at the beginning of the line. (Issue #133) + +* Fix false positive E126 for multi-line comments. (Issue #138) + +* Fix false positive E221 when operator is preceded by a comma. (Issue #135) + +* Fix ``--diff`` failing on one-line hunk. (Issue #137) + +* Fix the ``--exclude`` switch for directory paths. (Issue #111) + +* Use ``-`` filename to read from standard input. (Issue #128) + + +1.3.3 (2012-06-27) +------------------ + +* Fix regression with continuation line checker. (Issue #98) + + +1.3.2 (2012-06-26) +------------------ + +* Revert to the previous behaviour for ``--show-pep8``: + do not imply ``--first``. (Issue #89) + +* Add E902 for IO errors. (Issue #87) + +* Fix false positive for E121, and missed E124. (Issue #92) + +* Set a sensible default path for config file on Windows. (Issue #95) + +* Allow ``verbose`` in the configuration file. (Issue #91) + +* Show the enforced ``max-line-length`` in the error message. (Issue #86) + + +1.3.1 (2012-06-18) +------------------ + +* Explain which configuration options are expected. Accept and recommend + the options names with hyphen instead of underscore. (Issue #82) + +* Do not read the user configuration when used as a module + (except if ``config_file=True`` is passed to the ``StyleGuide`` constructor). + +* Fix wrong or missing cases for the E12 series. + +* Fix cases where E122 was missed. (Issue #81) + + +1.3 (2012-06-15) +---------------- + +.. warning:: + The internal API is backwards incompatible. + +* Remove global configuration and refactor the library around + a ``StyleGuide`` class; add the ability to configure various + reporters. (Issue #35 and #66) + +* Read user configuration from ``~/.config/pep8`` + and local configuration from ``./.pep8``. (Issue #22) + +* Fix E502 for backslash embedded in multi-line string. (Issue #68) + +* Fix E225 for Python 3 iterable unpacking (PEP 3132). (Issue #72) + +* Enable the new checkers from the E12 series in the default + configuration. + +* Suggest less error-prone alternatives for E712 errors. + +* Rewrite checkers to run faster (E22, E251, E27). + +* Fixed a crash when parsed code is invalid (too many + closing brackets). + +* Fix E127 and E128 for continuation line indentation. (Issue #74) + +* New option ``--format`` to customize the error format. (Issue #23) + +* New option ``--diff`` to check only modified code. The unified + diff is read from STDIN. Example: ``hg diff | pep8 --diff`` + (Issue #39) + +* Correctly report the count of failures and set the exit code to 1 + when the ``--doctest`` or the ``--testsuite`` fails. + +* Correctly detect the encoding in Python 3. (Issue #69) + +* Drop support for Python 2.3, 2.4 and 3.0. (Issue #78) + + +1.2 (2012-06-01) +---------------- + +* Add E121 through E128 for continuation line indentation. These + checks are disabled by default. If you want to force all checks, + use switch ``--select=E,W``. Patch by Sam Vilain. (Issue #64) + +* Add E721 for direct type comparisons. (Issue #47) + +* Add E711 and E712 for comparisons to singletons. (Issue #46) + +* Fix spurious E225 and E701 for function annotations. (Issue #29) + +* Add E502 for explicit line join between brackets. + +* Fix E901 when printing source with ``--show-source``. + +* Report all errors for each checker, instead of reporting only the + first occurrence for each line. + +* Option ``--show-pep8`` implies ``--first``. + + +1.1 (2012-05-24) +---------------- + +* Add E901 for syntax errors. (Issues #63 and #30) + +* Add E271, E272, E273 and E274 for extraneous whitespace around + keywords. (Issue #57) + +* Add ``tox.ini`` configuration file for tests. (Issue #61) + +* Add ``.travis.yml`` configuration file for continuous integration. + (Issue #62) + + +1.0.1 (2012-04-06) +------------------ + +* Fix inconsistent version numbers. + + +1.0 (2012-04-04) +---------------- + +* Fix W602 ``raise`` to handle multi-char names. (Issue #53) + + +0.7.0 (2012-03-26) +------------------ + +* Now ``--first`` prints only the first occurrence of each error. + The ``--repeat`` flag becomes obsolete because it is the default + behaviour. (Issue #6) + +* Allow specifying ``--max-line-length``. (Issue #36) + +* Make the shebang more flexible. (Issue #26) + +* Add testsuite to the bundle. (Issue #25) + +* Fixes for Jython. (Issue #49) + +* Add PyPI classifiers. (Issue #43) + +* Fix the ``--exclude`` option. (Issue #48) + +* Fix W602, accept ``raise`` with 3 arguments. (Issue #34) + +* Correctly select all tests if ``DEFAULT_IGNORE == ''``. + + +0.6.1 (2010-10-03) +------------------ + +* Fix inconsistent version numbers. (Issue #21) + + +0.6.0 (2010-09-19) +------------------ + +* Test suite reorganized and enhanced in order to check more failures + with fewer test files. Read the ``run_tests`` docstring for details + about the syntax. + +* Fix E225: accept ``print >>sys.stderr, "..."`` syntax. + +* Fix E501 for lines containing multibyte encoded characters. (Issue #7) + +* Fix E221, E222, E223, E224 not detected in some cases. (Issue #16) + +* Fix E211 to reject ``v = dic['a'] ['b']``. (Issue #17) + +* Exit code is always 1 if any error or warning is found. (Issue #10) + +* ``--ignore`` checks are now really ignored, especially in + conjunction with ``--count``. (Issue #8) + +* Blank lines with spaces yield W293 instead of W291: some developers + want to ignore this warning and indent the blank lines to paste their + code easily in the Python interpreter. + +* Fix E301: do not require a blank line before an indented block. (Issue #14) + +* Fix E203 to accept NumPy slice notation ``a[0, :]``. (Issue #13) + +* Performance improvements. + +* Fix decoding and checking non-UTF8 files in Python 3. + +* Fix E225: reject ``True+False`` when running on Python 3. + +* Fix an exception when the line starts with an operator. + +* Allow a new line before closing ``)``, ``}`` or ``]``. (Issue #5) + + +0.5.0 (2010-02-17) +------------------ + +* Changed the ``--count`` switch to print to sys.stderr and set + exit code to 1 if any error or warning is found. + +* E241 and E242 are removed from the standard checks. If you want to + include these checks, use switch ``--select=E,W``. (Issue #4) + +* Blank line is not mandatory before the first class method or nested + function definition, even if there's a docstring. (Issue #1) + +* Add the switch ``--version``. + +* Fix decoding errors with Python 3. (Issue #13 [1]_) + +* Add ``--select`` option which is mirror of ``--ignore``. + +* Add checks E261 and E262 for spaces before inline comments. + +* New check W604 warns about deprecated usage of backticks. + +* New check W603 warns about the deprecated operator ``<>``. + +* Performance improvement, due to rewriting of E225. + +* E225 now accepts: + + - no whitespace after unary operator or similar. (Issue #9 [1]_) + + - lambda function with argument unpacking or keyword defaults. + +* Reserve "2 blank lines" for module-level logical blocks. (E303) + +* Allow multi-line comments. (E302, issue #10 [1]_) + + +0.4.2 (2009-10-22) +------------------ + +* Decorators on classes and class methods are OK now. + + +0.4 (2009-10-20) +---------------- + +* Support for all versions of Python from 2.3 to 3.1. + +* New and greatly expanded self tests. + +* Added ``--count`` option to print the total number of errors and warnings. + +* Further improvements to the handling of comments and blank lines. + (Issue #1 [1]_ and others changes.) + +* Check all py files in directory when passed a directory (Issue + #2 [1]_). This also prevents an exception when traversing directories + with non ``*.py`` files. + +* E231 should allow commas to be followed by ``)``. (Issue #3 [1]_) + +* Spaces are no longer required around the equals sign for keyword + arguments or default parameter values. + + +.. [1] These issues refer to the `previous issue tracker`__. +.. __: http://github.com/cburroughs/pep8.py/issues + + +0.3.1 (2009-09-14) +------------------ + +* Fixes for comments: do not count them when checking for blank lines between + items. + +* Added setup.py for pypi upload and easy_installability. + + +0.2 (2007-10-16) +---------------- + +* Loads of fixes and improvements. + + +0.1 (2006-10-01) +---------------- + +* First release. + + diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/RECORD b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/RECORD new file mode 100644 index 0000000..946e46a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/RECORD @@ -0,0 +1,11 @@ +../../../bin/pycodestyle,sha256=Rpmq9bzt_sd3tKNbKX3P-7bEHkRDX0IvgScjnxaqmMs,249 +__pycache__/pycodestyle.cpython-38.pyc,, +pycodestyle-2.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pycodestyle-2.8.0.dist-info/LICENSE,sha256=93IpXoGvNHjTTojlLQdiACMOx91qOeEjvFyzWqZqva4,1254 +pycodestyle-2.8.0.dist-info/METADATA,sha256=jLxHgy1ywol0Yu2cxoVLIw4XMOlUaUUDZijfTUFV2uY,31230 +pycodestyle-2.8.0.dist-info/RECORD,, +pycodestyle-2.8.0.dist-info/WHEEL,sha256=WzZ8cwjh8l0jtULNjYq1Hpr-WCqCRgPr--TX4P5I1Wo,110 +pycodestyle-2.8.0.dist-info/entry_points.txt,sha256=6JU_7SAppC93MBSQi1_QxDwEQUyg6cgK71ab9q_Hxco,51 +pycodestyle-2.8.0.dist-info/namespace_packages.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +pycodestyle-2.8.0.dist-info/top_level.txt,sha256=rHbIEiXmvsJ016mFcLVcF_d-dKgP3VdfOB6CWbivZug,12 +pycodestyle.py,sha256=3-O2ixYLkFoim3-EN1XLtHL2-oJafe6BQKrY2nbkrA4,105016 diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/WHEEL new file mode 100644 index 0000000..b733a60 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/entry_points.txt b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/entry_points.txt new file mode 100644 index 0000000..71bd885 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +pycodestyle = pycodestyle:_main + diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/namespace_packages.txt b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/namespace_packages.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/namespace_packages.txt @@ -0,0 +1 @@ + diff --git a/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/top_level.txt new file mode 100644 index 0000000..282a93f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle-2.8.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pycodestyle diff --git a/.venv/lib/python3.8/site-packages/pycodestyle.py b/.venv/lib/python3.8/site-packages/pycodestyle.py new file mode 100644 index 0000000..16167df --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pycodestyle.py @@ -0,0 +1,2805 @@ +#!/usr/bin/env python +# pycodestyle.py - Check Python source code formatting, according to +# PEP 8 +# +# Copyright (C) 2006-2009 Johann C. Rocholl +# Copyright (C) 2009-2014 Florent Xicluna +# Copyright (C) 2014-2016 Ian Lee +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +r""" +Check Python source code formatting, according to PEP 8. + +For usage and a list of options, try this: +$ python pycodestyle.py -h + +This program and its regression test suite live here: +https://github.com/pycqa/pycodestyle + +Groups of errors and warnings: +E errors +W warnings +100 indentation +200 whitespace +300 blank lines +400 imports +500 line length +600 deprecation +700 statements +900 syntax error +""" +from __future__ import with_statement + +import bisect +import inspect +import keyword +import os +import re +import sys +import time +import tokenize +import warnings + +try: + from functools import lru_cache +except ImportError: + def lru_cache(maxsize=128): # noqa as it's a fake implementation. + """Does not really need a real a lru_cache, it's just + optimization, so let's just do nothing here. Python 3.2+ will + just get better performances, time to upgrade? + """ + return lambda function: function + +from fnmatch import fnmatch +from optparse import OptionParser + +try: + from configparser import RawConfigParser + from io import TextIOWrapper +except ImportError: + from ConfigParser import RawConfigParser + +# this is a performance hack. see https://bugs.python.org/issue43014 +if ( + sys.version_info < (3, 10) and + callable(getattr(tokenize, '_compile', None)) +): # pragma: no cover (>', '**', '*', '+', '-']) +ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) +WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) +# Warn for -> function annotation operator in py3.5+ (issue 803) +FUNCTION_RETURN_ANNOTATION_OP = ['->'] if sys.version_info >= (3, 5) else [] +ASSIGNMENT_EXPRESSION_OP = [':='] if sys.version_info >= (3, 8) else [] +WS_NEEDED_OPERATORS = frozenset([ + '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', + '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', + 'and', 'in', 'is', 'or'] + + FUNCTION_RETURN_ANNOTATION_OP + + ASSIGNMENT_EXPRESSION_OP) +WHITESPACE = frozenset(' \t') +NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) +SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) +# ERRORTOKEN is triggered by backticks in Python 3 +SKIP_COMMENTS = SKIP_TOKENS.union([tokenize.COMMENT, tokenize.ERRORTOKEN]) +BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] + +INDENT_REGEX = re.compile(r'([ \t]*)') +RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,') +RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,.*,\s*\w+\s*$') +ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') +DOCSTRING_REGEX = re.compile(r'u?r?["\']') +EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({][ \t]|[ \t][\]}),;:](?!=)') +WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') +COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)' + r'\s*(?(1)|(None|False|True))\b') +COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+)(\s*)') +LAMBDA_REGEX = re.compile(r'\blambda\b') +HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') +STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') +STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') +STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( + r'^\s*({0})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( + 'def', 'async def', + 'for', 'async for', + 'if', 'elif', 'else', + 'try', 'except', 'finally', + 'with', 'async with', + 'class', + 'while', + ))) +) +DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") +BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") + +_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} + + +def _get_parameters(function): + if sys.version_info >= (3, 3): + return [parameter.name + for parameter + in inspect.signature(function).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] + else: + return inspect.getargspec(function)[0] + + +def register_check(check, codes=None): + """Register a new check object.""" + def _add_check(check, kind, codes, args): + if check in _checks[kind]: + _checks[kind][check][0].extend(codes or []) + else: + _checks[kind][check] = (codes or [''], args) + if inspect.isfunction(check): + args = _get_parameters(check) + if args and args[0] in ('physical_line', 'logical_line'): + if codes is None: + codes = ERRORCODE_REGEX.findall(check.__doc__ or '') + _add_check(check, args[0], codes, args) + elif inspect.isclass(check): + if _get_parameters(check.__init__)[:2] == ['self', 'tree']: + _add_check(check, 'tree', codes, None) + return check + + +######################################################################## +# Plugins (check functions) for physical lines +######################################################################## + +@register_check +def tabs_or_spaces(physical_line, indent_char): + r"""Never mix tabs and spaces. + + The most popular way of indenting Python is with spaces only. The + second-most popular way is with tabs only. Code indented with a + mixture of tabs and spaces should be converted to using spaces + exclusively. When invoking the Python command line interpreter with + the -t option, it issues warnings about code that illegally mixes + tabs and spaces. When using -tt these warnings become errors. + These options are highly recommended! + + Okay: if a == 0:\n a = 1\n b = 1 + E101: if a == 0:\n a = 1\n\tb = 1 + """ + indent = INDENT_REGEX.match(physical_line).group(1) + for offset, char in enumerate(indent): + if char != indent_char: + return offset, "E101 indentation contains mixed spaces and tabs" + + +@register_check +def tabs_obsolete(physical_line): + r"""On new projects, spaces-only are strongly recommended over tabs. + + Okay: if True:\n return + W191: if True:\n\treturn + """ + indent = INDENT_REGEX.match(physical_line).group(1) + if '\t' in indent: + return indent.index('\t'), "W191 indentation contains tabs" + + +@register_check +def trailing_whitespace(physical_line): + r"""Trailing whitespace is superfluous. + + The warning returned varies on whether the line itself is blank, + for easier filtering for those who want to indent their blank lines. + + Okay: spam(1)\n# + W291: spam(1) \n# + W293: class Foo(object):\n \n bang = 12 + """ + physical_line = physical_line.rstrip('\n') # chr(10), newline + physical_line = physical_line.rstrip('\r') # chr(13), carriage return + physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L + stripped = physical_line.rstrip(' \t\v') + if physical_line != stripped: + if stripped: + return len(stripped), "W291 trailing whitespace" + else: + return 0, "W293 blank line contains whitespace" + + +@register_check +def trailing_blank_lines(physical_line, lines, line_number, total_lines): + r"""Trailing blank lines are superfluous. + + Okay: spam(1) + W391: spam(1)\n + + However the last line should end with a new line (warning W292). + """ + if line_number == total_lines: + stripped_last_line = physical_line.rstrip('\r\n') + if physical_line and not stripped_last_line: + return 0, "W391 blank line at end of file" + if stripped_last_line == physical_line: + return len(lines[-1]), "W292 no newline at end of file" + + +@register_check +def maximum_line_length(physical_line, max_line_length, multiline, + line_number, noqa): + r"""Limit all lines to a maximum of 79 characters. + + There are still many devices around that are limited to 80 character + lines; plus, limiting windows to 80 characters makes it possible to + have several windows side-by-side. The default wrapping on such + devices looks ugly. Therefore, please limit all lines to a maximum + of 79 characters. For flowing long blocks of text (docstrings or + comments), limiting the length to 72 characters is recommended. + + Reports error E501. + """ + line = physical_line.rstrip() + length = len(line) + if length > max_line_length and not noqa: + # Special case: ignore long shebang lines. + if line_number == 1 and line.startswith('#!'): + return + # Special case for long URLs in multi-line docstrings or + # comments, but still report the error when the 72 first chars + # are whitespaces. + chunks = line.split() + if ((len(chunks) == 1 and multiline) or + (len(chunks) == 2 and chunks[0] == '#')) and \ + len(line) - len(chunks[-1]) < max_line_length - 7: + return + if hasattr(line, 'decode'): # Python 2 + # The line could contain multi-byte characters + try: + length = len(line.decode('utf-8')) + except UnicodeError: + pass + if length > max_line_length: + return (max_line_length, "E501 line too long " + "(%d > %d characters)" % (length, max_line_length)) + + +######################################################################## +# Plugins (check functions) for logical lines +######################################################################## + + +def _is_one_liner(logical_line, indent_level, lines, line_number): + if not STARTSWITH_TOP_LEVEL_REGEX.match(logical_line): + return False + + line_idx = line_number - 1 + + if line_idx < 1: + prev_indent = 0 + else: + prev_indent = expand_indent(lines[line_idx - 1]) + + if prev_indent > indent_level: + return False + + while line_idx < len(lines): + line = lines[line_idx].strip() + if not line.startswith('@') and STARTSWITH_TOP_LEVEL_REGEX.match(line): + break + else: + line_idx += 1 + else: + return False # invalid syntax: EOF while searching for def/class + + next_idx = line_idx + 1 + while next_idx < len(lines): + if lines[next_idx].strip(): + break + else: + next_idx += 1 + else: + return True # line is last in the file + + return expand_indent(lines[next_idx]) <= indent_level + + +@register_check +def blank_lines(logical_line, blank_lines, indent_level, line_number, + blank_before, previous_logical, + previous_unindented_logical_line, previous_indent_level, + lines): + r"""Separate top-level function and class definitions with two blank + lines. + + Method definitions inside a class are separated by a single blank + line. + + Extra blank lines may be used (sparingly) to separate groups of + related functions. Blank lines may be omitted between a bunch of + related one-liners (e.g. a set of dummy implementations). + + Use blank lines in functions, sparingly, to indicate logical + sections. + + Okay: def a():\n pass\n\n\ndef b():\n pass + Okay: def a():\n pass\n\n\nasync def b():\n pass + Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass + Okay: default = 1\nfoo = 1 + Okay: classify = 1\nfoo = 1 + + E301: class Foo:\n b = 0\n def bar():\n pass + E302: def a():\n pass\n\ndef b(n):\n pass + E302: def a():\n pass\n\nasync def b(n):\n pass + E303: def a():\n pass\n\n\n\ndef b(n):\n pass + E303: def a():\n\n\n\n pass + E304: @decorator\n\ndef a():\n pass + E305: def a():\n pass\na() + E306: def a():\n def b():\n pass\n def c():\n pass + """ # noqa + top_level_lines = BLANK_LINES_CONFIG['top_level'] + method_lines = BLANK_LINES_CONFIG['method'] + + if not previous_logical and blank_before < top_level_lines: + return # Don't expect blank lines before the first line + if previous_logical.startswith('@'): + if blank_lines: + yield 0, "E304 blank lines found after function decorator" + elif (blank_lines > top_level_lines or + (indent_level and blank_lines == method_lines + 1) + ): + yield 0, "E303 too many blank lines (%d)" % blank_lines + elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line): + # allow a group of one-liners + if ( + _is_one_liner(logical_line, indent_level, lines, line_number) and + blank_before == 0 + ): + return + if indent_level: + if not (blank_before == method_lines or + previous_indent_level < indent_level or + DOCSTRING_REGEX.match(previous_logical) + ): + ancestor_level = indent_level + nested = False + # Search backwards for a def ancestor or tree root + # (top level). + for line in lines[line_number - top_level_lines::-1]: + if line.strip() and expand_indent(line) < ancestor_level: + ancestor_level = expand_indent(line) + nested = STARTSWITH_DEF_REGEX.match(line.lstrip()) + if nested or ancestor_level == 0: + break + if nested: + yield 0, "E306 expected %s blank line before a " \ + "nested definition, found 0" % (method_lines,) + else: + yield 0, "E301 expected %s blank line, found 0" % ( + method_lines,) + elif blank_before != top_level_lines: + yield 0, "E302 expected %s blank lines, found %d" % ( + top_level_lines, blank_before) + elif (logical_line and + not indent_level and + blank_before != top_level_lines and + previous_unindented_logical_line.startswith(('def ', 'class ')) + ): + yield 0, "E305 expected %s blank lines after " \ + "class or function definition, found %d" % ( + top_level_lines, blank_before) + + +@register_check +def extraneous_whitespace(logical_line): + r"""Avoid extraneous whitespace. + + Avoid extraneous whitespace in these situations: + - Immediately inside parentheses, brackets or braces. + - Immediately before a comma, semicolon, or colon. + + Okay: spam(ham[1], {eggs: 2}) + E201: spam( ham[1], {eggs: 2}) + E201: spam(ham[ 1], {eggs: 2}) + E201: spam(ham[1], { eggs: 2}) + E202: spam(ham[1], {eggs: 2} ) + E202: spam(ham[1 ], {eggs: 2}) + E202: spam(ham[1], {eggs: 2 }) + + E203: if x == 4: print x, y; x, y = y , x + E203: if x == 4: print x, y ; x, y = y, x + E203: if x == 4 : print x, y; x, y = y, x + """ + line = logical_line + for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): + text = match.group() + char = text.strip() + found = match.start() + if text[-1].isspace(): + # assert char in '([{' + yield found + 1, "E201 whitespace after '%s'" % char + elif line[found - 1] != ',': + code = ('E202' if char in '}])' else 'E203') # if char in ',;:' + yield found, "%s whitespace before '%s'" % (code, char) + + +@register_check +def whitespace_around_keywords(logical_line): + r"""Avoid extraneous whitespace around keywords. + + Okay: True and False + E271: True and False + E272: True and False + E273: True and\tFalse + E274: True\tand False + """ + for match in KEYWORD_REGEX.finditer(logical_line): + before, after = match.groups() + + if '\t' in before: + yield match.start(1), "E274 tab before keyword" + elif len(before) > 1: + yield match.start(1), "E272 multiple spaces before keyword" + + if '\t' in after: + yield match.start(2), "E273 tab after keyword" + elif len(after) > 1: + yield match.start(2), "E271 multiple spaces after keyword" + + +@register_check +def missing_whitespace_after_import_keyword(logical_line): + r"""Multiple imports in form from x import (a, b, c) should have + space between import statement and parenthesised name list. + + Okay: from foo import (bar, baz) + E275: from foo import(bar, baz) + E275: from importable.module import(bar, baz) + """ + line = logical_line + indicator = ' import(' + if line.startswith('from '): + found = line.find(indicator) + if -1 < found: + pos = found + len(indicator) - 1 + yield pos, "E275 missing whitespace after keyword" + + +@register_check +def missing_whitespace(logical_line): + r"""Each comma, semicolon or colon should be followed by whitespace. + + Okay: [a, b] + Okay: (3,) + Okay: a[1:4] + Okay: a[:4] + Okay: a[1:] + Okay: a[1:4:2] + E231: ['a','b'] + E231: foo(bar,baz) + E231: [{'a':'b'}] + """ + line = logical_line + for index in range(len(line) - 1): + char = line[index] + next_char = line[index + 1] + if char in ',;:' and next_char not in WHITESPACE: + before = line[:index] + if char == ':' and before.count('[') > before.count(']') and \ + before.rfind('{') < before.rfind('['): + continue # Slice syntax, no space required + if char == ',' and next_char == ')': + continue # Allow tuple with only one element: (3,) + if char == ':' and next_char == '=' and sys.version_info >= (3, 8): + continue # Allow assignment expression + yield index, "E231 missing whitespace after '%s'" % char + + +@register_check +def indentation(logical_line, previous_logical, indent_char, + indent_level, previous_indent_level, + indent_size): + r"""Use indent_size (PEP8 says 4) spaces per indentation level. + + For really old code that you don't want to mess up, you can continue + to use 8-space tabs. + + Okay: a = 1 + Okay: if a == 0:\n a = 1 + E111: a = 1 + E114: # a = 1 + + Okay: for item in items:\n pass + E112: for item in items:\npass + E115: for item in items:\n# Hi\n pass + + Okay: a = 1\nb = 2 + E113: a = 1\n b = 2 + E116: a = 1\n # b = 2 + """ + c = 0 if logical_line else 3 + tmpl = "E11%d %s" if logical_line else "E11%d %s (comment)" + if indent_level % indent_size: + yield 0, tmpl % ( + 1 + c, + "indentation is not a multiple of " + str(indent_size), + ) + indent_expect = previous_logical.endswith(':') + if indent_expect and indent_level <= previous_indent_level: + yield 0, tmpl % (2 + c, "expected an indented block") + elif not indent_expect and indent_level > previous_indent_level: + yield 0, tmpl % (3 + c, "unexpected indentation") + + if indent_expect: + expected_indent_amount = 8 if indent_char == '\t' else 4 + expected_indent_level = previous_indent_level + expected_indent_amount + if indent_level > expected_indent_level: + yield 0, tmpl % (7, 'over-indented') + + +@register_check +def continued_indentation(logical_line, tokens, indent_level, hang_closing, + indent_char, indent_size, noqa, verbose): + r"""Continuation lines indentation. + + Continuation lines should align wrapped elements either vertically + using Python's implicit line joining inside parentheses, brackets + and braces, or using a hanging indent. + + When using a hanging indent these considerations should be applied: + - there should be no arguments on the first line, and + - further indentation should be used to clearly distinguish itself + as a continuation line. + + Okay: a = (\n) + E123: a = (\n ) + + Okay: a = (\n 42) + E121: a = (\n 42) + E122: a = (\n42) + E123: a = (\n 42\n ) + E124: a = (24,\n 42\n) + E125: if (\n b):\n pass + E126: a = (\n 42) + E127: a = (24,\n 42) + E128: a = (24,\n 42) + E129: if (a or\n b):\n pass + E131: a = (\n 42\n 24) + """ + first_row = tokens[0][2][0] + nrows = 1 + tokens[-1][2][0] - first_row + if noqa or nrows == 1: + return + + # indent_next tells us whether the next block is indented; assuming + # that it is indented by 4 spaces, then we should not allow 4-space + # indents on the final continuation line; in turn, some other + # indents are allowed to have an extra 4 spaces. + indent_next = logical_line.endswith(':') + + row = depth = 0 + valid_hangs = (indent_size,) if indent_char != '\t' \ + else (indent_size, indent_size * 2) + # remember how many brackets were opened on each line + parens = [0] * nrows + # relative indents of physical lines + rel_indent = [0] * nrows + # for each depth, collect a list of opening rows + open_rows = [[0]] + # for each depth, memorize the hanging indentation + hangs = [None] + # visual indents + indent_chances = {} + last_indent = tokens[0][2] + visual_indent = None + last_token_multiline = False + # for each depth, memorize the visual indent column + indent = [last_indent[1]] + if verbose >= 3: + print(">>> " + tokens[0][4].rstrip()) + + for token_type, text, start, end, line in tokens: + + newline = row < start[0] - first_row + if newline: + row = start[0] - first_row + newline = not last_token_multiline and token_type not in NEWLINE + + if newline: + # this is the beginning of a continuation line. + last_indent = start + if verbose >= 3: + print("... " + line.rstrip()) + + # record the initial indent. + rel_indent[row] = expand_indent(line) - indent_level + + # identify closing bracket + close_bracket = (token_type == tokenize.OP and text in ']})') + + # is the indent relative to an opening bracket line? + for open_row in reversed(open_rows[depth]): + hang = rel_indent[row] - rel_indent[open_row] + hanging_indent = hang in valid_hangs + if hanging_indent: + break + if hangs[depth]: + hanging_indent = (hang == hangs[depth]) + # is there any chance of visual indent? + visual_indent = (not close_bracket and hang > 0 and + indent_chances.get(start[1])) + + if close_bracket and indent[depth]: + # closing bracket for visual indent + if start[1] != indent[depth]: + yield (start, "E124 closing bracket does not match " + "visual indentation") + elif close_bracket and not hang: + # closing bracket matches indentation of opening + # bracket's line + if hang_closing: + yield start, "E133 closing bracket is missing indentation" + elif indent[depth] and start[1] < indent[depth]: + if visual_indent is not True: + # visual indent is broken + yield (start, "E128 continuation line " + "under-indented for visual indent") + elif hanging_indent or (indent_next and + rel_indent[row] == 2 * indent_size): + # hanging indent is verified + if close_bracket and not hang_closing: + yield (start, "E123 closing bracket does not match " + "indentation of opening bracket's line") + hangs[depth] = hang + elif visual_indent is True: + # visual indent is verified + indent[depth] = start[1] + elif visual_indent in (text, str): + # ignore token lined up with matching one from a + # previous line + pass + else: + # indent is broken + if hang <= 0: + error = "E122", "missing indentation or outdented" + elif indent[depth]: + error = "E127", "over-indented for visual indent" + elif not close_bracket and hangs[depth]: + error = "E131", "unaligned for hanging indent" + else: + hangs[depth] = hang + if hang > indent_size: + error = "E126", "over-indented for hanging indent" + else: + error = "E121", "under-indented for hanging indent" + yield start, "%s continuation line %s" % error + + # look for visual indenting + if (parens[row] and + token_type not in (tokenize.NL, tokenize.COMMENT) and + not indent[depth]): + indent[depth] = start[1] + indent_chances[start[1]] = True + if verbose >= 4: + print("bracket depth %s indent to %s" % (depth, start[1])) + # deal with implicit string concatenation + elif (token_type in (tokenize.STRING, tokenize.COMMENT) or + text in ('u', 'ur', 'b', 'br')): + indent_chances[start[1]] = str + # visual indent after assert/raise/with + elif not row and not depth and text in ["assert", "raise", "with"]: + indent_chances[end[1] + 1] = True + # special case for the "if" statement because len("if (") == 4 + elif not indent_chances and not row and not depth and text == 'if': + indent_chances[end[1] + 1] = True + elif text == ':' and line[end[1]:].isspace(): + open_rows[depth].append(row) + + # keep track of bracket depth + if token_type == tokenize.OP: + if text in '([{': + depth += 1 + indent.append(0) + hangs.append(None) + if len(open_rows) == depth: + open_rows.append([]) + open_rows[depth].append(row) + parens[row] += 1 + if verbose >= 4: + print("bracket depth %s seen, col %s, visual min = %s" % + (depth, start[1], indent[depth])) + elif text in ')]}' and depth > 0: + # parent indents should not be more than this one + prev_indent = indent.pop() or last_indent[1] + hangs.pop() + for d in range(depth): + if indent[d] > prev_indent: + indent[d] = 0 + for ind in list(indent_chances): + if ind >= prev_indent: + del indent_chances[ind] + del open_rows[depth + 1:] + depth -= 1 + if depth: + indent_chances[indent[depth]] = True + for idx in range(row, -1, -1): + if parens[idx]: + parens[idx] -= 1 + break + assert len(indent) == depth + 1 + if start[1] not in indent_chances: + # allow lining up tokens + indent_chances[start[1]] = text + + last_token_multiline = (start[0] != end[0]) + if last_token_multiline: + rel_indent[end[0] - first_row] = rel_indent[row] + + if indent_next and expand_indent(line) == indent_level + indent_size: + pos = (start[0], indent[0] + indent_size) + if visual_indent: + code = "E129 visually indented line" + else: + code = "E125 continuation line" + yield pos, "%s with same indent as next logical line" % code + + +@register_check +def whitespace_before_parameters(logical_line, tokens): + r"""Avoid extraneous whitespace. + + Avoid extraneous whitespace in the following situations: + - before the open parenthesis that starts the argument list of a + function call. + - before the open parenthesis that starts an indexing or slicing. + + Okay: spam(1) + E211: spam (1) + + Okay: dict['key'] = list[index] + E211: dict ['key'] = list[index] + E211: dict['key'] = list [index] + """ + prev_type, prev_text, __, prev_end, __ = tokens[0] + for index in range(1, len(tokens)): + token_type, text, start, end, __ = tokens[index] + if ( + token_type == tokenize.OP and + text in '([' and + start != prev_end and + (prev_type == tokenize.NAME or prev_text in '}])') and + # Syntax "class A (B):" is allowed, but avoid it + (index < 2 or tokens[index - 2][1] != 'class') and + # Allow "return (a.foo for a in range(5))" + not keyword.iskeyword(prev_text) and + # 'match' and 'case' are only soft keywords + ( + sys.version_info < (3, 9) or + not keyword.issoftkeyword(prev_text) + ) + ): + yield prev_end, "E211 whitespace before '%s'" % text + prev_type = token_type + prev_text = text + prev_end = end + + +@register_check +def whitespace_around_operator(logical_line): + r"""Avoid extraneous whitespace around an operator. + + Okay: a = 12 + 3 + E221: a = 4 + 5 + E222: a = 4 + 5 + E223: a = 4\t+ 5 + E224: a = 4 +\t5 + """ + for match in OPERATOR_REGEX.finditer(logical_line): + before, after = match.groups() + + if '\t' in before: + yield match.start(1), "E223 tab before operator" + elif len(before) > 1: + yield match.start(1), "E221 multiple spaces before operator" + + if '\t' in after: + yield match.start(2), "E224 tab after operator" + elif len(after) > 1: + yield match.start(2), "E222 multiple spaces after operator" + + +@register_check +def missing_whitespace_around_operator(logical_line, tokens): + r"""Surround operators with a single space on either side. + + - Always surround these binary operators with a single space on + either side: assignment (=), augmented assignment (+=, -= etc.), + comparisons (==, <, >, !=, <=, >=, in, not in, is, is not), + Booleans (and, or, not). + + - If operators with different priorities are used, consider adding + whitespace around the operators with the lowest priorities. + + Okay: i = i + 1 + Okay: submitted += 1 + Okay: x = x * 2 - 1 + Okay: hypot2 = x * x + y * y + Okay: c = (a + b) * (a - b) + Okay: foo(bar, key='word', *args, **kwargs) + Okay: alpha[:-i] + + E225: i=i+1 + E225: submitted +=1 + E225: x = x /2 - 1 + E225: z = x **y + E225: z = 1and 1 + E226: c = (a+b) * (a-b) + E226: hypot2 = x*x + y*y + E227: c = a|b + E228: msg = fmt%(errno, errmsg) + """ + parens = 0 + need_space = False + prev_type = tokenize.OP + prev_text = prev_end = None + operator_types = (tokenize.OP, tokenize.NAME) + for token_type, text, start, end, line in tokens: + if token_type in SKIP_COMMENTS: + continue + if text in ('(', 'lambda'): + parens += 1 + elif text == ')': + parens -= 1 + if need_space: + if start != prev_end: + # Found a (probably) needed space + if need_space is not True and not need_space[1]: + yield (need_space[0], + "E225 missing whitespace around operator") + need_space = False + elif text == '>' and prev_text in ('<', '-'): + # Tolerate the "<>" operator, even if running Python 3 + # Deal with Python 3's annotated return value "->" + pass + elif ( + # def f(a, /, b): + # ^ + # def f(a, b, /): + # ^ + # f = lambda a, /: + # ^ + prev_text == '/' and text in {',', ')', ':'} or + # def f(a, b, /): + # ^ + prev_text == ')' and text == ':' + ): + # Tolerate the "/" operator in function definition + # For more info see PEP570 + pass + else: + if need_space is True or need_space[1]: + # A needed trailing space was not found + yield prev_end, "E225 missing whitespace around operator" + elif prev_text != '**': + code, optype = 'E226', 'arithmetic' + if prev_text == '%': + code, optype = 'E228', 'modulo' + elif prev_text not in ARITHMETIC_OP: + code, optype = 'E227', 'bitwise or shift' + yield (need_space[0], "%s missing whitespace " + "around %s operator" % (code, optype)) + need_space = False + elif token_type in operator_types and prev_end is not None: + if text == '=' and parens: + # Allow keyword args or defaults: foo(bar=None). + pass + elif text in WS_NEEDED_OPERATORS: + need_space = True + elif text in UNARY_OPERATORS: + # Check if the operator is used as a binary operator + # Allow unary operators: -123, -x, +1. + # Allow argument unpacking: foo(*args, **kwargs). + if prev_type == tokenize.OP and prev_text in '}])' or ( + prev_type != tokenize.OP and + prev_text not in KEYWORDS and ( + sys.version_info < (3, 9) or + not keyword.issoftkeyword(prev_text) + ) + ): + need_space = None + elif text in WS_OPTIONAL_OPERATORS: + need_space = None + + if need_space is None: + # Surrounding space is optional, but ensure that + # trailing space matches opening space + need_space = (prev_end, start != prev_end) + elif need_space and start == prev_end: + # A needed opening space was not found + yield prev_end, "E225 missing whitespace around operator" + need_space = False + prev_type = token_type + prev_text = text + prev_end = end + + +@register_check +def whitespace_around_comma(logical_line): + r"""Avoid extraneous whitespace after a comma or a colon. + + Note: these checks are disabled by default + + Okay: a = (1, 2) + E241: a = (1, 2) + E242: a = (1,\t2) + """ + line = logical_line + for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line): + found = m.start() + 1 + if '\t' in m.group(): + yield found, "E242 tab after '%s'" % m.group()[0] + else: + yield found, "E241 multiple spaces after '%s'" % m.group()[0] + + +@register_check +def whitespace_around_named_parameter_equals(logical_line, tokens): + r"""Don't use spaces around the '=' sign in function arguments. + + Don't use spaces around the '=' sign when used to indicate a + keyword argument or a default parameter value, except when + using a type annotation. + + Okay: def complex(real, imag=0.0): + Okay: return magic(r=real, i=imag) + Okay: boolean(a == b) + Okay: boolean(a != b) + Okay: boolean(a <= b) + Okay: boolean(a >= b) + Okay: def foo(arg: int = 42): + Okay: async def foo(arg: int = 42): + + E251: def complex(real, imag = 0.0): + E251: return magic(r = real, i = imag) + E252: def complex(real, image: float=0.0): + """ + parens = 0 + no_space = False + require_space = False + prev_end = None + annotated_func_arg = False + in_def = bool(STARTSWITH_DEF_REGEX.match(logical_line)) + + message = "E251 unexpected spaces around keyword / parameter equals" + missing_message = "E252 missing whitespace around parameter equals" + + for token_type, text, start, end, line in tokens: + if token_type == tokenize.NL: + continue + if no_space: + no_space = False + if start != prev_end: + yield (prev_end, message) + if require_space: + require_space = False + if start == prev_end: + yield (prev_end, missing_message) + if token_type == tokenize.OP: + if text in '([': + parens += 1 + elif text in ')]': + parens -= 1 + elif in_def and text == ':' and parens == 1: + annotated_func_arg = True + elif parens == 1 and text == ',': + annotated_func_arg = False + elif parens and text == '=': + if annotated_func_arg and parens == 1: + require_space = True + if start == prev_end: + yield (prev_end, missing_message) + else: + no_space = True + if start != prev_end: + yield (prev_end, message) + if not parens: + annotated_func_arg = False + + prev_end = end + + +@register_check +def whitespace_before_comment(logical_line, tokens): + r"""Separate inline comments by at least two spaces. + + An inline comment is a comment on the same line as a statement. + Inline comments should be separated by at least two spaces from the + statement. They should start with a # and a single space. + + Each line of a block comment starts with a # and a single space + (unless it is indented text inside the comment). + + Okay: x = x + 1 # Increment x + Okay: x = x + 1 # Increment x + Okay: # Block comment + E261: x = x + 1 # Increment x + E262: x = x + 1 #Increment x + E262: x = x + 1 # Increment x + E265: #Block comment + E266: ### Block comment + """ + prev_end = (0, 0) + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + inline_comment = line[:start[1]].strip() + if inline_comment: + if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: + yield (prev_end, + "E261 at least two spaces before inline comment") + symbol, sp, comment = text.partition(' ') + bad_prefix = symbol not in '#:' and (symbol.lstrip('#')[:1] or '#') + if inline_comment: + if bad_prefix or comment[:1] in WHITESPACE: + yield start, "E262 inline comment should start with '# '" + elif bad_prefix and (bad_prefix != '!' or start[0] > 1): + if bad_prefix != '#': + yield start, "E265 block comment should start with '# '" + elif comment: + yield start, "E266 too many leading '#' for block comment" + elif token_type != tokenize.NL: + prev_end = end + + +@register_check +def imports_on_separate_lines(logical_line): + r"""Place imports on separate lines. + + Okay: import os\nimport sys + E401: import sys, os + + Okay: from subprocess import Popen, PIPE + Okay: from myclas import MyClass + Okay: from foo.bar.yourclass import YourClass + Okay: import myclass + Okay: import foo.bar.yourclass + """ + line = logical_line + if line.startswith('import '): + found = line.find(',') + if -1 < found and ';' not in line[:found]: + yield found, "E401 multiple imports on one line" + + +@register_check +def module_imports_on_top_of_file( + logical_line, indent_level, checker_state, noqa): + r"""Place imports at the top of the file. + + Always put imports at the top of the file, just after any module + comments and docstrings, and before module globals and constants. + + Okay: import os + Okay: # this is a comment\nimport os + Okay: '''this is a module docstring'''\nimport os + Okay: r'''this is a module docstring'''\nimport os + Okay: + try:\n\timport x\nexcept ImportError:\n\tpass\nelse:\n\tpass\nimport y + Okay: + try:\n\timport x\nexcept ImportError:\n\tpass\nfinally:\n\tpass\nimport y + E402: a=1\nimport os + E402: 'One string'\n"Two string"\nimport os + E402: a=1\nfrom sys import x + + Okay: if x:\n import os + """ # noqa + def is_string_literal(line): + if line[0] in 'uUbB': + line = line[1:] + if line and line[0] in 'rR': + line = line[1:] + return line and (line[0] == '"' or line[0] == "'") + + allowed_keywords = ( + 'try', 'except', 'else', 'finally', 'with', 'if', 'elif') + + if indent_level: # Allow imports in conditional statement/function + return + if not logical_line: # Allow empty lines or comments + return + if noqa: + return + line = logical_line + if line.startswith('import ') or line.startswith('from '): + if checker_state.get('seen_non_imports', False): + yield 0, "E402 module level import not at top of file" + elif re.match(DUNDER_REGEX, line): + return + elif any(line.startswith(kw) for kw in allowed_keywords): + # Allow certain keywords intermixed with imports in order to + # support conditional or filtered importing + return + elif is_string_literal(line): + # The first literal is a docstring, allow it. Otherwise, report + # error. + if checker_state.get('seen_docstring', False): + checker_state['seen_non_imports'] = True + else: + checker_state['seen_docstring'] = True + else: + checker_state['seen_non_imports'] = True + + +@register_check +def compound_statements(logical_line): + r"""Compound statements (on the same line) are generally + discouraged. + + While sometimes it's okay to put an if/for/while with a small body + on the same line, never do this for multi-clause statements. + Also avoid folding such long lines! + + Always use a def statement instead of an assignment statement that + binds a lambda expression directly to a name. + + Okay: if foo == 'blah':\n do_blah_thing() + Okay: do_one() + Okay: do_two() + Okay: do_three() + + E701: if foo == 'blah': do_blah_thing() + E701: for x in lst: total += x + E701: while t < 10: t = delay() + E701: if foo == 'blah': do_blah_thing() + E701: else: do_non_blah_thing() + E701: try: something() + E701: finally: cleanup() + E701: if foo == 'blah': one(); two(); three() + E702: do_one(); do_two(); do_three() + E703: do_four(); # useless semicolon + E704: def f(x): return 2*x + E731: f = lambda x: 2*x + """ + line = logical_line + last_char = len(line) - 1 + found = line.find(':') + prev_found = 0 + counts = {char: 0 for char in '{}[]()'} + while -1 < found < last_char: + update_counts(line[prev_found:found], counts) + if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) + counts['['] <= counts[']'] and # [1:2] (slice) + counts['('] <= counts[')']) and # (annotation) + not (sys.version_info >= (3, 8) and + line[found + 1] == '=')): # assignment expression + lambda_kw = LAMBDA_REGEX.search(line, 0, found) + if lambda_kw: + before = line[:lambda_kw.start()].rstrip() + if before[-1:] == '=' and isidentifier(before[:-1].strip()): + yield 0, ("E731 do not assign a lambda expression, use a " + "def") + break + if STARTSWITH_DEF_REGEX.match(line): + yield 0, "E704 multiple statements on one line (def)" + elif STARTSWITH_INDENT_STATEMENT_REGEX.match(line): + yield found, "E701 multiple statements on one line (colon)" + prev_found = found + found = line.find(':', found + 1) + found = line.find(';') + while -1 < found: + if found < last_char: + yield found, "E702 multiple statements on one line (semicolon)" + else: + yield found, "E703 statement ends with a semicolon" + found = line.find(';', found + 1) + + +@register_check +def explicit_line_join(logical_line, tokens): + r"""Avoid explicit line join between brackets. + + The preferred way of wrapping long lines is by using Python's + implied line continuation inside parentheses, brackets and braces. + Long lines can be broken over multiple lines by wrapping expressions + in parentheses. These should be used in preference to using a + backslash for line continuation. + + E502: aaa = [123, \\n 123] + E502: aaa = ("bbb " \\n "ccc") + + Okay: aaa = [123,\n 123] + Okay: aaa = ("bbb "\n "ccc") + Okay: aaa = "bbb " \\n "ccc" + Okay: aaa = 123 # \\ + """ + prev_start = prev_end = parens = 0 + comment = False + backslash = None + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + comment = True + if start[0] != prev_start and parens and backslash and not comment: + yield backslash, "E502 the backslash is redundant between brackets" + if end[0] != prev_end: + if line.rstrip('\r\n').endswith('\\'): + backslash = (end[0], len(line.splitlines()[-1]) - 1) + else: + backslash = None + prev_start = prev_end = end[0] + else: + prev_start = start[0] + if token_type == tokenize.OP: + if text in '([{': + parens += 1 + elif text in ')]}': + parens -= 1 + + +_SYMBOLIC_OPS = frozenset("()[]{},:.;@=%~") | frozenset(("...",)) + + +def _is_binary_operator(token_type, text): + is_op_token = token_type == tokenize.OP + is_conjunction = text in ['and', 'or'] + # NOTE(sigmavirus24): Previously the not_a_symbol check was executed + # conditionally. Since it is now *always* executed, text may be + # None. In that case we get a TypeError for `text not in str`. + not_a_symbol = text and text not in _SYMBOLIC_OPS + # The % character is strictly speaking a binary operator, but the + # common usage seems to be to put it next to the format parameters, + # after a line break. + return ((is_op_token or is_conjunction) and not_a_symbol) + + +def _break_around_binary_operators(tokens): + """Private function to reduce duplication. + + This factors out the shared details between + :func:`break_before_binary_operator` and + :func:`break_after_binary_operator`. + """ + line_break = False + unary_context = True + # Previous non-newline token types and text + previous_token_type = None + previous_text = None + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + continue + if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: + line_break = True + else: + yield (token_type, text, previous_token_type, previous_text, + line_break, unary_context, start) + unary_context = text in '([{,;' + line_break = False + previous_token_type = token_type + previous_text = text + + +@register_check +def break_before_binary_operator(logical_line, tokens): + r""" + Avoid breaks before binary operators. + + The preferred place to break around a binary operator is after the + operator, not before it. + + W503: (width == 0\n + height == 0) + W503: (width == 0\n and height == 0) + W503: var = (1\n & ~2) + W503: var = (1\n / -2) + W503: var = (1\n + -1\n + -2) + + Okay: foo(\n -x) + Okay: foo(x\n []) + Okay: x = '''\n''' + '' + Okay: foo(x,\n -y) + Okay: foo(x, # comment\n -y) + """ + for context in _break_around_binary_operators(tokens): + (token_type, text, previous_token_type, previous_text, + line_break, unary_context, start) = context + if (_is_binary_operator(token_type, text) and line_break and + not unary_context and + not _is_binary_operator(previous_token_type, + previous_text)): + yield start, "W503 line break before binary operator" + + +@register_check +def break_after_binary_operator(logical_line, tokens): + r""" + Avoid breaks after binary operators. + + The preferred place to break around a binary operator is before the + operator, not after it. + + W504: (width == 0 +\n height == 0) + W504: (width == 0 and\n height == 0) + W504: var = (1 &\n ~2) + + Okay: foo(\n -x) + Okay: foo(x\n []) + Okay: x = '''\n''' + '' + Okay: x = '' + '''\n''' + Okay: foo(x,\n -y) + Okay: foo(x, # comment\n -y) + + The following should be W504 but unary_context is tricky with these + Okay: var = (1 /\n -2) + Okay: var = (1 +\n -1 +\n -2) + """ + prev_start = None + for context in _break_around_binary_operators(tokens): + (token_type, text, previous_token_type, previous_text, + line_break, unary_context, start) = context + if (_is_binary_operator(previous_token_type, previous_text) and + line_break and + not unary_context and + not _is_binary_operator(token_type, text)): + yield prev_start, "W504 line break after binary operator" + prev_start = start + + +@register_check +def comparison_to_singleton(logical_line, noqa): + r"""Comparison to singletons should use "is" or "is not". + + Comparisons to singletons like None should always be done + with "is" or "is not", never the equality operators. + + Okay: if arg is not None: + E711: if arg != None: + E711: if None == arg: + E712: if arg == True: + E712: if False == arg: + + Also, beware of writing if x when you really mean if x is not None + -- e.g. when testing whether a variable or argument that defaults to + None was set to some other value. The other value might have a type + (such as a container) that could be false in a boolean context! + """ + if noqa: + return + + for match in COMPARE_SINGLETON_REGEX.finditer(logical_line): + singleton = match.group(1) or match.group(3) + same = (match.group(2) == '==') + + msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton) + if singleton in ('None',): + code = 'E711' + else: + code = 'E712' + nonzero = ((singleton == 'True' and same) or + (singleton == 'False' and not same)) + msg += " or 'if %scond:'" % ('' if nonzero else 'not ') + yield match.start(2), ("%s comparison to %s should be %s" % + (code, singleton, msg)) + + +@register_check +def comparison_negative(logical_line): + r"""Negative comparison should be done using "not in" and "is not". + + Okay: if x not in y:\n pass + Okay: assert (X in Y or X is Z) + Okay: if not (X in Y):\n pass + Okay: zz = x is not y + E713: Z = not X in Y + E713: if not X.B in Y:\n pass + E714: if not X is Y:\n pass + E714: Z = not X.B is Y + """ + match = COMPARE_NEGATIVE_REGEX.search(logical_line) + if match: + pos = match.start(1) + if match.group(2) == 'in': + yield pos, "E713 test for membership should be 'not in'" + else: + yield pos, "E714 test for object identity should be 'is not'" + + +@register_check +def comparison_type(logical_line, noqa): + r"""Object type comparisons should always use isinstance(). + + Do not compare types directly. + + Okay: if isinstance(obj, int): + E721: if type(obj) is type(1): + + When checking if an object is a string, keep in mind that it might + be a unicode string too! In Python 2.3, str and unicode have a + common base class, basestring, so you can do: + + Okay: if isinstance(obj, basestring): + Okay: if type(a1) is type(b1): + """ + match = COMPARE_TYPE_REGEX.search(logical_line) + if match and not noqa: + inst = match.group(1) + if inst and isidentifier(inst) and inst not in SINGLETONS: + return # Allow comparison for types which are not obvious + yield match.start(), "E721 do not compare types, use 'isinstance()'" + + +@register_check +def bare_except(logical_line, noqa): + r"""When catching exceptions, mention specific exceptions when + possible. + + Okay: except Exception: + Okay: except BaseException: + E722: except: + """ + if noqa: + return + + match = BLANK_EXCEPT_REGEX.match(logical_line) + if match: + yield match.start(), "E722 do not use bare 'except'" + + +@register_check +def ambiguous_identifier(logical_line, tokens): + r"""Never use the characters 'l', 'O', or 'I' as variable names. + + In some fonts, these characters are indistinguishable from the + numerals one and zero. When tempted to use 'l', use 'L' instead. + + Okay: L = 0 + Okay: o = 123 + Okay: i = 42 + E741: l = 0 + E741: O = 123 + E741: I = 42 + + Variables can be bound in several other contexts, including class + and function definitions, 'global' and 'nonlocal' statements, + exception handlers, and 'with' and 'for' statements. + In addition, we have a special handling for function parameters. + + Okay: except AttributeError as o: + Okay: with lock as L: + Okay: foo(l=12) + Okay: for a in foo(l=12): + E741: except AttributeError as O: + E741: with lock as l: + E741: global I + E741: nonlocal l + E741: def foo(l): + E741: def foo(l=12): + E741: l = foo(l=12) + E741: for l in range(10): + E742: class I(object): + E743: def l(x): + """ + is_func_def = False # Set to true if 'def' is found + parameter_parentheses_level = 0 + idents_to_avoid = ('l', 'O', 'I') + prev_type, prev_text, prev_start, prev_end, __ = tokens[0] + for token_type, text, start, end, line in tokens[1:]: + ident = pos = None + # find function definitions + if prev_text == 'def': + is_func_def = True + # update parameter parentheses level + if parameter_parentheses_level == 0 and \ + prev_type == tokenize.NAME and \ + token_type == tokenize.OP and text == '(': + parameter_parentheses_level = 1 + elif parameter_parentheses_level > 0 and \ + token_type == tokenize.OP: + if text == '(': + parameter_parentheses_level += 1 + elif text == ')': + parameter_parentheses_level -= 1 + # identifiers on the lhs of an assignment operator + if token_type == tokenize.OP and '=' in text and \ + parameter_parentheses_level == 0: + if prev_text in idents_to_avoid: + ident = prev_text + pos = prev_start + # identifiers bound to values with 'as', 'for', + # 'global', or 'nonlocal' + if prev_text in ('as', 'for', 'global', 'nonlocal'): + if text in idents_to_avoid: + ident = text + pos = start + # function parameter definitions + if is_func_def: + if text in idents_to_avoid: + ident = text + pos = start + if prev_text == 'class': + if text in idents_to_avoid: + yield start, "E742 ambiguous class definition '%s'" % text + if prev_text == 'def': + if text in idents_to_avoid: + yield start, "E743 ambiguous function definition '%s'" % text + if ident: + yield pos, "E741 ambiguous variable name '%s'" % ident + prev_type = token_type + prev_text = text + prev_start = start + + +@register_check +def python_3000_has_key(logical_line, noqa): + r"""The {}.has_key() method is removed in Python 3: use the 'in' + operator. + + Okay: if "alph" in d:\n print d["alph"] + W601: assert d.has_key('alph') + """ + pos = logical_line.find('.has_key(') + if pos > -1 and not noqa: + yield pos, "W601 .has_key() is deprecated, use 'in'" + + +@register_check +def python_3000_raise_comma(logical_line): + r"""When raising an exception, use "raise ValueError('message')". + + The older form is removed in Python 3. + + Okay: raise DummyError("Message") + W602: raise DummyError, "Message" + """ + match = RAISE_COMMA_REGEX.match(logical_line) + if match and not RERAISE_COMMA_REGEX.match(logical_line): + yield match.end() - 1, "W602 deprecated form of raising exception" + + +@register_check +def python_3000_not_equal(logical_line): + r"""New code should always use != instead of <>. + + The older syntax is removed in Python 3. + + Okay: if a != 'no': + W603: if a <> 'no': + """ + pos = logical_line.find('<>') + if pos > -1: + yield pos, "W603 '<>' is deprecated, use '!='" + + +@register_check +def python_3000_backticks(logical_line): + r"""Use repr() instead of backticks in Python 3. + + Okay: val = repr(1 + 2) + W604: val = `1 + 2` + """ + pos = logical_line.find('`') + if pos > -1: + yield pos, "W604 backticks are deprecated, use 'repr()'" + + +@register_check +def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): + r"""Invalid escape sequences are deprecated in Python 3.6. + + Okay: regex = r'\.png$' + W605: regex = '\.png$' + """ + if noqa: + return + + # https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals + valid = [ + '\n', + '\\', + '\'', + '"', + 'a', + 'b', + 'f', + 'n', + 'r', + 't', + 'v', + '0', '1', '2', '3', '4', '5', '6', '7', + 'x', + + # Escape sequences only recognized in string literals + 'N', + 'u', + 'U', + ] + + for token_type, text, start, end, line in tokens: + if token_type == tokenize.STRING: + start_line, start_col = start + quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] + # Extract string modifiers (e.g. u or r) + quote_pos = text.index(quote) + prefix = text[:quote_pos].lower() + start = quote_pos + len(quote) + string = text[start:-len(quote)] + + if 'r' not in prefix: + pos = string.find('\\') + while pos >= 0: + pos += 1 + if string[pos] not in valid: + line = start_line + string.count('\n', 0, pos) + if line == start_line: + col = start_col + len(prefix) + len(quote) + pos + else: + col = pos - string.rfind('\n', 0, pos) - 1 + yield ( + (line, col - 1), + "W605 invalid escape sequence '\\%s'" % + string[pos], + ) + pos = string.find('\\', pos + 1) + + +@register_check +def python_3000_async_await_keywords(logical_line, tokens): + """'async' and 'await' are reserved keywords starting at Python 3.7. + + W606: async = 42 + W606: await = 42 + Okay: async def read(db):\n data = await db.fetch('SELECT ...') + """ + # The Python tokenize library before Python 3.5 recognizes + # async/await as a NAME token. Therefore, use a state machine to + # look for the possible async/await constructs as defined by the + # Python grammar: + # https://docs.python.org/3/reference/grammar.html + + state = None + for token_type, text, start, end, line in tokens: + error = False + + if token_type == tokenize.NL: + continue + + if state is None: + if token_type == tokenize.NAME: + if text == 'async': + state = ('async_stmt', start) + elif text == 'await': + state = ('await', start) + elif (token_type == tokenize.NAME and + text in ('def', 'for')): + state = ('define', start) + + elif state[0] == 'async_stmt': + if token_type == tokenize.NAME and text in ('def', 'with', 'for'): + # One of funcdef, with_stmt, or for_stmt. Return to + # looking for async/await names. + state = None + else: + error = True + elif state[0] == 'await': + if token_type == tokenize.NAME: + # An await expression. Return to looking for async/await + # names. + state = None + elif token_type == tokenize.OP and text == '(': + state = None + else: + error = True + elif state[0] == 'define': + if token_type == tokenize.NAME and text in ('async', 'await'): + error = True + else: + state = None + + if error: + yield ( + state[1], + "W606 'async' and 'await' are reserved keywords starting with " + "Python 3.7", + ) + state = None + + # Last token + if state is not None: + yield ( + state[1], + "W606 'async' and 'await' are reserved keywords starting with " + "Python 3.7", + ) + + +######################################################################## +@register_check +def maximum_doc_length(logical_line, max_doc_length, noqa, tokens): + r"""Limit all doc lines to a maximum of 72 characters. + + For flowing long blocks of text (docstrings or comments), limiting + the length to 72 characters is recommended. + + Reports warning W505 + """ + if max_doc_length is None or noqa: + return + + prev_token = None + skip_lines = set() + # Skip lines that + for token_type, text, start, end, line in tokens: + if token_type not in SKIP_COMMENTS.union([tokenize.STRING]): + skip_lines.add(line) + + for token_type, text, start, end, line in tokens: + # Skip lines that aren't pure strings + if token_type == tokenize.STRING and skip_lines: + continue + if token_type in (tokenize.STRING, tokenize.COMMENT): + # Only check comment-only lines + if prev_token is None or prev_token in SKIP_TOKENS: + lines = line.splitlines() + for line_num, physical_line in enumerate(lines): + if hasattr(physical_line, 'decode'): # Python 2 + # The line could contain multi-byte characters + try: + physical_line = physical_line.decode('utf-8') + except UnicodeError: + pass + if start[0] + line_num == 1 and line.startswith('#!'): + return + length = len(physical_line) + chunks = physical_line.split() + if token_type == tokenize.COMMENT: + if (len(chunks) == 2 and + length - len(chunks[-1]) < MAX_DOC_LENGTH): + continue + if len(chunks) == 1 and line_num + 1 < len(lines): + if (len(chunks) == 1 and + length - len(chunks[-1]) < MAX_DOC_LENGTH): + continue + if length > max_doc_length: + doc_error = (start[0] + line_num, max_doc_length) + yield (doc_error, "W505 doc line too long " + "(%d > %d characters)" + % (length, max_doc_length)) + prev_token = token_type + + +######################################################################## +# Helper functions +######################################################################## + + +if sys.version_info < (3,): + # Python 2: implicit encoding. + def readlines(filename): + """Read the source code.""" + with open(filename, 'rU') as f: + return f.readlines() + isidentifier = re.compile(r'[a-zA-Z_]\w*$').match + stdin_get_value = sys.stdin.read +else: + # Python 3 + def readlines(filename): + """Read the source code.""" + try: + with tokenize.open(filename) as f: + return f.readlines() + except (LookupError, SyntaxError, UnicodeError): + # Fall back if file encoding is improperly declared + with open(filename, encoding='latin-1') as f: + return f.readlines() + isidentifier = str.isidentifier + + def stdin_get_value(): + """Read the value from stdin.""" + return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + +noqa = lru_cache(512)(re.compile(r'# no(?:qa|pep8)\b', re.I).search) + + +def expand_indent(line): + r"""Return the amount of indentation. + + Tabs are expanded to the next multiple of 8. + + >>> expand_indent(' ') + 4 + >>> expand_indent('\t') + 8 + >>> expand_indent(' \t') + 8 + >>> expand_indent(' \t') + 16 + """ + line = line.rstrip('\n\r') + if '\t' not in line: + return len(line) - len(line.lstrip()) + result = 0 + for char in line: + if char == '\t': + result = result // 8 * 8 + 8 + elif char == ' ': + result += 1 + else: + break + return result + + +def mute_string(text): + """Replace contents with 'xxx' to prevent syntax matching. + + >>> mute_string('"abc"') + '"xxx"' + >>> mute_string("'''abc'''") + "'''xxx'''" + >>> mute_string("r'abc'") + "r'xxx'" + """ + # String modifiers (e.g. u or r) + start = text.index(text[-1]) + 1 + end = len(text) - 1 + # Triple quotes + if text[-3:] in ('"""', "'''"): + start += 2 + end -= 2 + return text[:start] + 'x' * (end - start) + text[end:] + + +def parse_udiff(diff, patterns=None, parent='.'): + """Return a dictionary of matching lines.""" + # For each file of the diff, the entry key is the filename, + # and the value is a set of row numbers to consider. + rv = {} + path = nrows = None + for line in diff.splitlines(): + if nrows: + if line[:1] != '-': + nrows -= 1 + continue + if line[:3] == '@@ ': + hunk_match = HUNK_REGEX.match(line) + (row, nrows) = [int(g or '1') for g in hunk_match.groups()] + rv[path].update(range(row, row + nrows)) + elif line[:3] == '+++': + path = line[4:].split('\t', 1)[0] + # Git diff will use (i)ndex, (w)ork tree, (c)ommit and + # (o)bject instead of a/b/c/d as prefixes for patches + if path[:2] in ('b/', 'w/', 'i/'): + path = path[2:] + rv[path] = set() + return { + os.path.join(parent, filepath): rows + for (filepath, rows) in rv.items() + if rows and filename_match(filepath, patterns) + } + + +def normalize_paths(value, parent=os.curdir): + """Parse a comma-separated list of paths. + + Return a list of absolute paths. + """ + if not value: + return [] + if isinstance(value, list): + return value + paths = [] + for path in value.split(','): + path = path.strip() + if '/' in path: + path = os.path.abspath(os.path.join(parent, path)) + paths.append(path.rstrip('/')) + return paths + + +def filename_match(filename, patterns, default=True): + """Check if patterns contains a pattern that matches filename. + + If patterns is unspecified, this always returns True. + """ + if not patterns: + return default + return any(fnmatch(filename, pattern) for pattern in patterns) + + +def update_counts(s, counts): + r"""Adds one to the counts of each appearance of characters in s, + for characters in counts""" + for char in s: + if char in counts: + counts[char] += 1 + + +def _is_eol_token(token): + return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' + + +######################################################################## +# Framework to run all checks +######################################################################## + + +class Checker(object): + """Load a Python source file, tokenize it, check coding style.""" + + def __init__(self, filename=None, lines=None, + options=None, report=None, **kwargs): + if options is None: + options = StyleGuide(kwargs).options + else: + assert not kwargs + self._io_error = None + self._physical_checks = options.physical_checks + self._logical_checks = options.logical_checks + self._ast_checks = options.ast_checks + self.max_line_length = options.max_line_length + self.max_doc_length = options.max_doc_length + self.indent_size = options.indent_size + self.multiline = False # in a multiline string? + self.hang_closing = options.hang_closing + self.indent_size = options.indent_size + self.verbose = options.verbose + self.filename = filename + # Dictionary where a checker can store its custom state. + self._checker_states = {} + if filename is None: + self.filename = 'stdin' + self.lines = lines or [] + elif filename == '-': + self.filename = 'stdin' + self.lines = stdin_get_value().splitlines(True) + elif lines is None: + try: + self.lines = readlines(filename) + except IOError: + (exc_type, exc) = sys.exc_info()[:2] + self._io_error = '%s: %s' % (exc_type.__name__, exc) + self.lines = [] + else: + self.lines = lines + if self.lines: + ord0 = ord(self.lines[0][0]) + if ord0 in (0xef, 0xfeff): # Strip the UTF-8 BOM + if ord0 == 0xfeff: + self.lines[0] = self.lines[0][1:] + elif self.lines[0][:3] == '\xef\xbb\xbf': + self.lines[0] = self.lines[0][3:] + self.report = report or options.report + self.report_error = self.report.error + self.noqa = False + + def report_invalid_syntax(self): + """Check if the syntax is valid.""" + (exc_type, exc) = sys.exc_info()[:2] + if len(exc.args) > 1: + offset = exc.args[1] + if len(offset) > 2: + offset = offset[1:3] + else: + offset = (1, 0) + self.report_error(offset[0], offset[1] or 0, + 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), + self.report_invalid_syntax) + + def readline(self): + """Get the next line from the input buffer.""" + if self.line_number >= self.total_lines: + return '' + line = self.lines[self.line_number] + self.line_number += 1 + if self.indent_char is None and line[:1] in WHITESPACE: + self.indent_char = line[0] + return line + + def run_check(self, check, argument_names): + """Run a check plugin.""" + arguments = [] + for name in argument_names: + arguments.append(getattr(self, name)) + return check(*arguments) + + def init_checker_state(self, name, argument_names): + """Prepare custom state for the specific checker plugin.""" + if 'checker_state' in argument_names: + self.checker_state = self._checker_states.setdefault(name, {}) + + def check_physical(self, line): + """Run all physical checks on a raw input line.""" + self.physical_line = line + for name, check, argument_names in self._physical_checks: + self.init_checker_state(name, argument_names) + result = self.run_check(check, argument_names) + if result is not None: + (offset, text) = result + self.report_error(self.line_number, offset, text, check) + if text[:4] == 'E101': + self.indent_char = line[0] + + def build_tokens_line(self): + """Build a logical line from tokens.""" + logical = [] + comments = [] + length = 0 + prev_row = prev_col = mapping = None + for token_type, text, start, end, line in self.tokens: + if token_type in SKIP_TOKENS: + continue + if not mapping: + mapping = [(0, start)] + if token_type == tokenize.COMMENT: + comments.append(text) + continue + if token_type == tokenize.STRING: + text = mute_string(text) + if prev_row: + (start_row, start_col) = start + if prev_row != start_row: # different row + prev_text = self.lines[prev_row - 1][prev_col - 1] + if prev_text == ',' or (prev_text not in '{[(' and + text not in '}])'): + text = ' ' + text + elif prev_col != start_col: # different column + text = line[prev_col:start_col] + text + logical.append(text) + length += len(text) + mapping.append((length, end)) + (prev_row, prev_col) = end + self.logical_line = ''.join(logical) + self.noqa = comments and noqa(''.join(comments)) + return mapping + + def check_logical(self): + """Build a line from tokens and run all logical checks on it.""" + self.report.increment_logical_line() + mapping = self.build_tokens_line() + if not mapping: + return + + mapping_offsets = [offset for offset, _ in mapping] + (start_row, start_col) = mapping[0][1] + start_line = self.lines[start_row - 1] + self.indent_level = expand_indent(start_line[:start_col]) + if self.blank_before < self.blank_lines: + self.blank_before = self.blank_lines + if self.verbose >= 2: + print(self.logical_line[:80].rstrip()) + for name, check, argument_names in self._logical_checks: + if self.verbose >= 4: + print(' ' + name) + self.init_checker_state(name, argument_names) + for offset, text in self.run_check(check, argument_names) or (): + if not isinstance(offset, tuple): + # As mappings are ordered, bisecting is a fast way + # to find a given offset in them. + token_offset, pos = mapping[bisect.bisect_left( + mapping_offsets, offset)] + offset = (pos[0], pos[1] + offset - token_offset) + self.report_error(offset[0], offset[1], text, check) + if self.logical_line: + self.previous_indent_level = self.indent_level + self.previous_logical = self.logical_line + if not self.indent_level: + self.previous_unindented_logical_line = self.logical_line + self.blank_lines = 0 + self.tokens = [] + + def check_ast(self): + """Build the file's AST and run all AST checks.""" + try: + tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) + except (ValueError, SyntaxError, TypeError): + return self.report_invalid_syntax() + for name, cls, __ in self._ast_checks: + checker = cls(tree, self.filename) + for lineno, offset, text, check in checker.run(): + if not self.lines or not noqa(self.lines[lineno - 1]): + self.report_error(lineno, offset, text, check) + + def generate_tokens(self): + """Tokenize file, run physical line checks and yield tokens.""" + if self._io_error: + self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) + tokengen = tokenize.generate_tokens(self.readline) + try: + prev_physical = '' + for token in tokengen: + if token[2][0] > self.total_lines: + return + self.noqa = token[4] and noqa(token[4]) + self.maybe_check_physical(token, prev_physical) + yield token + prev_physical = token[4] + except (SyntaxError, tokenize.TokenError): + self.report_invalid_syntax() + + def maybe_check_physical(self, token, prev_physical): + """If appropriate for token, check current physical line(s).""" + # Called after every token, but act only on end of line. + + # a newline token ends a single physical line. + if _is_eol_token(token): + # if the file does not end with a newline, the NEWLINE + # token is inserted by the parser, but it does not contain + # the previous physical line in `token[4]` + if token[4] == '': + self.check_physical(prev_physical) + else: + self.check_physical(token[4]) + elif token[0] == tokenize.STRING and '\n' in token[1]: + # Less obviously, a string that contains newlines is a + # multiline string, either triple-quoted or with internal + # newlines backslash-escaped. Check every physical line in + # the string *except* for the last one: its newline is + # outside of the multiline string, so we consider it a + # regular physical line, and will check it like any other + # physical line. + # + # Subtleties: + # - we don't *completely* ignore the last line; if it + # contains the magical "# noqa" comment, we disable all + # physical checks for the entire multiline string + # - have to wind self.line_number back because initially it + # points to the last line of the string, and we want + # check_physical() to give accurate feedback + if noqa(token[4]): + return + self.multiline = True + self.line_number = token[2][0] + _, src, (_, offset), _, _ = token + src = self.lines[self.line_number - 1][:offset] + src + for line in src.split('\n')[:-1]: + self.check_physical(line + '\n') + self.line_number += 1 + self.multiline = False + + def check_all(self, expected=None, line_offset=0): + """Run all checks on the input file.""" + self.report.init_file(self.filename, self.lines, expected, line_offset) + self.total_lines = len(self.lines) + if self._ast_checks: + self.check_ast() + self.line_number = 0 + self.indent_char = None + self.indent_level = self.previous_indent_level = 0 + self.previous_logical = '' + self.previous_unindented_logical_line = '' + self.tokens = [] + self.blank_lines = self.blank_before = 0 + parens = 0 + for token in self.generate_tokens(): + self.tokens.append(token) + token_type, text = token[0:2] + if self.verbose >= 3: + if token[2][0] == token[3][0]: + pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) + else: + pos = 'l.%s' % token[3][0] + print('l.%s\t%s\t%s\t%r' % + (token[2][0], pos, tokenize.tok_name[token[0]], text)) + if token_type == tokenize.OP: + if text in '([{': + parens += 1 + elif text in '}])': + parens -= 1 + elif not parens: + if token_type in NEWLINE: + if token_type == tokenize.NEWLINE: + self.check_logical() + self.blank_before = 0 + elif len(self.tokens) == 1: + # The physical line contains only this token. + self.blank_lines += 1 + del self.tokens[0] + else: + self.check_logical() + if self.tokens: + self.check_physical(self.lines[-1]) + self.check_logical() + return self.report.get_file_results() + + +class BaseReport(object): + """Collect the results of the checks.""" + + print_filename = False + + def __init__(self, options): + self._benchmark_keys = options.benchmark_keys + self._ignore_code = options.ignore_code + # Results + self.elapsed = 0 + self.total_errors = 0 + self.counters = dict.fromkeys(self._benchmark_keys, 0) + self.messages = {} + + def start(self): + """Start the timer.""" + self._start_time = time.time() + + def stop(self): + """Stop the timer.""" + self.elapsed = time.time() - self._start_time + + def init_file(self, filename, lines, expected, line_offset): + """Signal a new file.""" + self.filename = filename + self.lines = lines + self.expected = expected or () + self.line_offset = line_offset + self.file_errors = 0 + self.counters['files'] += 1 + self.counters['physical lines'] += len(lines) + + def increment_logical_line(self): + """Signal a new logical line.""" + self.counters['logical lines'] += 1 + + def error(self, line_number, offset, text, check): + """Report an error, according to options.""" + code = text[:4] + if self._ignore_code(code): + return + if code in self.counters: + self.counters[code] += 1 + else: + self.counters[code] = 1 + self.messages[code] = text[5:] + # Don't care about expected errors or warnings + if code in self.expected: + return + if self.print_filename and not self.file_errors: + print(self.filename) + self.file_errors += 1 + self.total_errors += 1 + return code + + def get_file_results(self): + """Return the count of errors and warnings for this file.""" + return self.file_errors + + def get_count(self, prefix=''): + """Return the total count of errors and warnings.""" + return sum(self.counters[key] + for key in self.messages if key.startswith(prefix)) + + def get_statistics(self, prefix=''): + """Get statistics for message codes that start with the prefix. + + prefix='' matches all errors and warnings + prefix='E' matches all errors + prefix='W' matches all warnings + prefix='E4' matches all errors that have to do with imports + """ + return ['%-7s %s %s' % (self.counters[key], key, self.messages[key]) + for key in sorted(self.messages) if key.startswith(prefix)] + + def print_statistics(self, prefix=''): + """Print overall statistics (number of errors and warnings).""" + for line in self.get_statistics(prefix): + print(line) + + def print_benchmark(self): + """Print benchmark numbers.""" + print('%-7.2f %s' % (self.elapsed, 'seconds elapsed')) + if self.elapsed: + for key in self._benchmark_keys: + print('%-7d %s per second (%d total)' % + (self.counters[key] / self.elapsed, key, + self.counters[key])) + + +class FileReport(BaseReport): + """Collect the results of the checks and print the filenames.""" + + print_filename = True + + +class StandardReport(BaseReport): + """Collect and print the results of the checks.""" + + def __init__(self, options): + super(StandardReport, self).__init__(options) + self._fmt = REPORT_FORMAT.get(options.format.lower(), + options.format) + self._repeat = options.repeat + self._show_source = options.show_source + self._show_pep8 = options.show_pep8 + + def init_file(self, filename, lines, expected, line_offset): + """Signal a new file.""" + self._deferred_print = [] + return super(StandardReport, self).init_file( + filename, lines, expected, line_offset) + + def error(self, line_number, offset, text, check): + """Report an error, according to options.""" + code = super(StandardReport, self).error(line_number, offset, + text, check) + if code and (self.counters[code] == 1 or self._repeat): + self._deferred_print.append( + (line_number, offset, code, text[5:], check.__doc__)) + return code + + def get_file_results(self): + """Print results and return the overall count for this file.""" + self._deferred_print.sort() + for line_number, offset, code, text, doc in self._deferred_print: + print(self._fmt % { + 'path': self.filename, + 'row': self.line_offset + line_number, 'col': offset + 1, + 'code': code, 'text': text, + }) + if self._show_source: + if line_number > len(self.lines): + line = '' + else: + line = self.lines[line_number - 1] + print(line.rstrip()) + print(re.sub(r'\S', ' ', line[:offset]) + '^') + if self._show_pep8 and doc: + print(' ' + doc.strip()) + + # stdout is block buffered when not stdout.isatty(). + # line can be broken where buffer boundary since other + # processes write to same file. + # flush() after print() to avoid buffer boundary. + # Typical buffer size is 8192. line written safely when + # len(line) < 8192. + sys.stdout.flush() + return self.file_errors + + +class DiffReport(StandardReport): + """Collect and print the results for the changed lines only.""" + + def __init__(self, options): + super(DiffReport, self).__init__(options) + self._selected = options.selected_lines + + def error(self, line_number, offset, text, check): + if line_number not in self._selected[self.filename]: + return + return super(DiffReport, self).error(line_number, offset, text, check) + + +class StyleGuide(object): + """Initialize a PEP-8 instance with few options.""" + + def __init__(self, *args, **kwargs): + # build options from the command line + self.checker_class = kwargs.pop('checker_class', Checker) + parse_argv = kwargs.pop('parse_argv', False) + config_file = kwargs.pop('config_file', False) + parser = kwargs.pop('parser', None) + # build options from dict + options_dict = dict(*args, **kwargs) + arglist = None if parse_argv else options_dict.get('paths', None) + verbose = options_dict.get('verbose', None) + options, self.paths = process_options( + arglist, parse_argv, config_file, parser, verbose) + if options_dict: + options.__dict__.update(options_dict) + if 'paths' in options_dict: + self.paths = options_dict['paths'] + + self.runner = self.input_file + self.options = options + + if not options.reporter: + options.reporter = BaseReport if options.quiet else StandardReport + + options.select = tuple(options.select or ()) + if not (options.select or options.ignore or + options.testsuite or options.doctest) and DEFAULT_IGNORE: + # The default choice: ignore controversial checks + options.ignore = tuple(DEFAULT_IGNORE.split(',')) + else: + # Ignore all checks which are not explicitly selected + options.ignore = ('',) if options.select else tuple(options.ignore) + options.benchmark_keys = BENCHMARK_KEYS[:] + options.ignore_code = self.ignore_code + options.physical_checks = self.get_checks('physical_line') + options.logical_checks = self.get_checks('logical_line') + options.ast_checks = self.get_checks('tree') + self.init_report() + + def init_report(self, reporter=None): + """Initialize the report instance.""" + self.options.report = (reporter or self.options.reporter)(self.options) + return self.options.report + + def check_files(self, paths=None): + """Run all checks on the paths.""" + if paths is None: + paths = self.paths + report = self.options.report + runner = self.runner + report.start() + try: + for path in paths: + if os.path.isdir(path): + self.input_dir(path) + elif not self.excluded(path): + runner(path) + except KeyboardInterrupt: + print('... stopped') + report.stop() + return report + + def input_file(self, filename, lines=None, expected=None, line_offset=0): + """Run all checks on a Python source file.""" + if self.options.verbose: + print('checking %s' % filename) + fchecker = self.checker_class( + filename, lines=lines, options=self.options) + return fchecker.check_all(expected=expected, line_offset=line_offset) + + def input_dir(self, dirname): + """Check all files in this directory and all subdirectories.""" + dirname = dirname.rstrip('/') + if self.excluded(dirname): + return 0 + counters = self.options.report.counters + verbose = self.options.verbose + filepatterns = self.options.filename + runner = self.runner + for root, dirs, files in os.walk(dirname): + if verbose: + print('directory ' + root) + counters['directories'] += 1 + for subdir in sorted(dirs): + if self.excluded(subdir, root): + dirs.remove(subdir) + for filename in sorted(files): + # contain a pattern that matches? + if ((filename_match(filename, filepatterns) and + not self.excluded(filename, root))): + runner(os.path.join(root, filename)) + + def excluded(self, filename, parent=None): + """Check if the file should be excluded. + + Check if 'options.exclude' contains a pattern matching filename. + """ + if not self.options.exclude: + return False + basename = os.path.basename(filename) + if filename_match(basename, self.options.exclude): + return True + if parent: + filename = os.path.join(parent, filename) + filename = os.path.abspath(filename) + return filename_match(filename, self.options.exclude) + + def ignore_code(self, code): + """Check if the error code should be ignored. + + If 'options.select' contains a prefix of the error code, + return False. Else, if 'options.ignore' contains a prefix of + the error code, return True. + """ + if len(code) < 4 and any(s.startswith(code) + for s in self.options.select): + return False + return (code.startswith(self.options.ignore) and + not code.startswith(self.options.select)) + + def get_checks(self, argument_name): + """Get all the checks for this category. + + Find all globally visible functions where the first argument + name starts with argument_name and which contain selected tests. + """ + checks = [] + for check, attrs in _checks[argument_name].items(): + (codes, args) = attrs + if any(not (code and self.ignore_code(code)) for code in codes): + checks.append((check.__name__, check, args)) + return sorted(checks) + + +def get_parser(prog='pycodestyle', version=__version__): + """Create the parser for the program.""" + parser = OptionParser(prog=prog, version=version, + usage="%prog [options] input ...") + parser.config_options = [ + 'exclude', 'filename', 'select', 'ignore', 'max-line-length', + 'max-doc-length', 'indent-size', 'hang-closing', 'count', 'format', + 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose'] + parser.add_option('-v', '--verbose', default=0, action='count', + help="print status messages, or debug with -vv") + parser.add_option('-q', '--quiet', default=0, action='count', + help="report only file names, or nothing with -qq") + parser.add_option('-r', '--repeat', default=True, action='store_true', + help="(obsolete) show all occurrences of the same error") + parser.add_option('--first', action='store_false', dest='repeat', + help="show first occurrence of each error") + parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, + help="exclude files or directories which match these " + "comma separated patterns (default: %default)") + parser.add_option('--filename', metavar='patterns', default='*.py', + help="when parsing directories, only check filenames " + "matching these comma separated patterns " + "(default: %default)") + parser.add_option('--select', metavar='errors', default='', + help="select errors and warnings (e.g. E,W6)") + parser.add_option('--ignore', metavar='errors', default='', + help="skip errors and warnings (e.g. E4,W) " + "(default: %s)" % DEFAULT_IGNORE) + parser.add_option('--show-source', action='store_true', + help="show source code for each error") + parser.add_option('--show-pep8', action='store_true', + help="show text of PEP 8 for each error " + "(implies --first)") + parser.add_option('--statistics', action='store_true', + help="count errors and warnings") + parser.add_option('--count', action='store_true', + help="print total number of errors and warnings " + "to standard error and set exit code to 1 if " + "total is not null") + parser.add_option('--max-line-length', type='int', metavar='n', + default=MAX_LINE_LENGTH, + help="set maximum allowed line length " + "(default: %default)") + parser.add_option('--max-doc-length', type='int', metavar='n', + default=None, + help="set maximum allowed doc line length and perform " + "these checks (unchecked if not set)") + parser.add_option('--indent-size', type='int', metavar='n', + default=INDENT_SIZE, + help="set how many spaces make up an indent " + "(default: %default)") + parser.add_option('--hang-closing', action='store_true', + help="hang closing bracket instead of matching " + "indentation of opening bracket's line") + parser.add_option('--format', metavar='format', default='default', + help="set the error format [default|pylint|]") + parser.add_option('--diff', action='store_true', + help="report changes only within line number ranges in " + "the unified diff received on STDIN") + group = parser.add_option_group("Testing Options") + if os.path.exists(TESTSUITE_PATH): + group.add_option('--testsuite', metavar='dir', + help="run regression tests from dir") + group.add_option('--doctest', action='store_true', + help="run doctest on myself") + group.add_option('--benchmark', action='store_true', + help="measure processing speed") + return parser + + +def read_config(options, args, arglist, parser): + """Read and parse configurations. + + If a config file is specified on the command line with the + "--config" option, then only it is used for configuration. + + Otherwise, the user configuration (~/.config/pycodestyle) and any + local configurations in the current directory or above will be + merged together (in that order) using the read method of + ConfigParser. + """ + config = RawConfigParser() + + cli_conf = options.config + + local_dir = os.curdir + + if USER_CONFIG and os.path.isfile(USER_CONFIG): + if options.verbose: + print('user configuration: %s' % USER_CONFIG) + config.read(USER_CONFIG) + + parent = tail = args and os.path.abspath(os.path.commonprefix(args)) + while tail: + if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): + local_dir = parent + if options.verbose: + print('local configuration: in %s' % parent) + break + (parent, tail) = os.path.split(parent) + + if cli_conf and os.path.isfile(cli_conf): + if options.verbose: + print('cli configuration: %s' % cli_conf) + config.read(cli_conf) + + pycodestyle_section = None + if config.has_section(parser.prog): + pycodestyle_section = parser.prog + elif config.has_section('pep8'): + pycodestyle_section = 'pep8' # Deprecated + warnings.warn('[pep8] section is deprecated. Use [pycodestyle].') + + if pycodestyle_section: + option_list = {o.dest: o.type or o.action for o in parser.option_list} + + # First, read the default values + (new_options, __) = parser.parse_args([]) + + # Second, parse the configuration + for opt in config.options(pycodestyle_section): + if opt.replace('_', '-') not in parser.config_options: + print(" unknown option '%s' ignored" % opt) + continue + if options.verbose > 1: + print(" %s = %s" % (opt, + config.get(pycodestyle_section, opt))) + normalized_opt = opt.replace('-', '_') + opt_type = option_list[normalized_opt] + if opt_type in ('int', 'count'): + value = config.getint(pycodestyle_section, opt) + elif opt_type in ('store_true', 'store_false'): + value = config.getboolean(pycodestyle_section, opt) + else: + value = config.get(pycodestyle_section, opt) + if normalized_opt == 'exclude': + value = normalize_paths(value, local_dir) + setattr(new_options, normalized_opt, value) + + # Third, overwrite with the command-line options + (options, __) = parser.parse_args(arglist, values=new_options) + options.doctest = options.testsuite = False + return options + + +def process_options(arglist=None, parse_argv=False, config_file=None, + parser=None, verbose=None): + """Process options passed either via arglist or command line args. + + Passing in the ``config_file`` parameter allows other tools, such as + flake8 to specify their own options to be processed in pycodestyle. + """ + if not parser: + parser = get_parser() + if not parser.has_option('--config'): + group = parser.add_option_group("Configuration", description=( + "The project options are read from the [%s] section of the " + "tox.ini file or the setup.cfg file located in any parent folder " + "of the path(s) being processed. Allowed options are: %s." % + (parser.prog, ', '.join(parser.config_options)))) + group.add_option('--config', metavar='path', default=config_file, + help="user config file location") + # Don't read the command line if the module is used as a library. + if not arglist and not parse_argv: + arglist = [] + # If parse_argv is True and arglist is None, arguments are + # parsed from the command line (sys.argv) + (options, args) = parser.parse_args(arglist) + options.reporter = None + + # If explicitly specified verbosity, override any `-v` CLI flag + if verbose is not None: + options.verbose = verbose + + if options.ensure_value('testsuite', False): + args.append(options.testsuite) + elif not options.ensure_value('doctest', False): + if parse_argv and not args: + if options.diff or any(os.path.exists(name) + for name in PROJECT_CONFIG): + args = ['.'] + else: + parser.error('input not specified') + options = read_config(options, args, arglist, parser) + options.reporter = parse_argv and options.quiet == 1 and FileReport + + options.filename = _parse_multi_options(options.filename) + options.exclude = normalize_paths(options.exclude) + options.select = _parse_multi_options(options.select) + options.ignore = _parse_multi_options(options.ignore) + + if options.diff: + options.reporter = DiffReport + stdin = stdin_get_value() + options.selected_lines = parse_udiff(stdin, options.filename, args[0]) + args = sorted(options.selected_lines) + + return options, args + + +def _parse_multi_options(options, split_token=','): + r"""Split and strip and discard empties. + + Turns the following: + + A, + B, + + into ["A", "B"] + """ + if options: + return [o.strip() for o in options.split(split_token) if o.strip()] + else: + return options + + +def _main(): + """Parse options and run checks on Python source.""" + import signal + + # Handle "Broken pipe" gracefully + try: + signal.signal(signal.SIGPIPE, lambda signum, frame: sys.exit(1)) + except AttributeError: + pass # not supported on Windows + + style_guide = StyleGuide(parse_argv=True) + options = style_guide.options + + if options.doctest or options.testsuite: + from testsuite.support import run_tests + report = run_tests(style_guide) + else: + report = style_guide.check_files() + + if options.statistics: + report.print_statistics() + + if options.benchmark: + report.print_benchmark() + + if options.testsuite and not options.quiet: + report.print_results() + + if report.total_errors: + if options.count: + sys.stderr.write(str(report.total_errors) + '\n') + sys.exit(1) + + +if __name__ == '__main__': + _main() diff --git a/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/LICENSE b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/LICENSE new file mode 100644 index 0000000..e4d553a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/LICENSE @@ -0,0 +1,21 @@ +Copyright 2005-2011 Divmod, Inc. +Copyright 2013-2014 Florent Xicluna + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/METADATA b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/METADATA new file mode 100644 index 0000000..1c20462 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/METADATA @@ -0,0 +1,118 @@ +Metadata-Version: 2.1 +Name: pyflakes +Version: 2.4.0 +Summary: passive checker of Python programs +Home-page: https://github.com/PyCQA/pyflakes +Author: A lot of people +Author-email: code-quality@python.org +License: MIT +Platform: UNKNOWN +Classifier: Development Status :: 6 - Mature +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development +Classifier: Topic :: Utilities +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +License-File: LICENSE + +======== +Pyflakes +======== + +A simple program which checks Python source files for errors. + +Pyflakes analyzes programs and detects various errors. It works by +parsing the source file, not importing it, so it is safe to use on +modules with side effects. It's also much faster. + +It is `available on PyPI `_ +and it supports all active versions of Python: 2.7 and 3.4 to 3.8. + + + +Installation +------------ + +It can be installed with:: + + $ pip install --upgrade pyflakes + + +Useful tips: + +* Be sure to install it for a version of Python which is compatible + with your codebase: for Python 2, ``pip2 install pyflakes`` and for + Python3, ``pip3 install pyflakes``. + +* You can also invoke Pyflakes with ``python3 -m pyflakes .`` or + ``python2 -m pyflakes .`` if you have it installed for both versions. + +* If you require more options and more flexibility, you could give a + look to Flake8_ too. + + +Design Principles +----------------- +Pyflakes makes a simple promise: it will never complain about style, +and it will try very, very hard to never emit false positives. + +Pyflakes is also faster than Pylint_. This is +largely because Pyflakes only examines the syntax tree of each file +individually. As a consequence, Pyflakes is more limited in the +types of things it can check. + +If you like Pyflakes but also want stylistic checks, you want +flake8_, which combines +Pyflakes with style checks against +`PEP 8`_ and adds +per-project configuration ability. + + +Mailing-list +------------ + +Share your feedback and ideas: `subscribe to the mailing-list +`_ + +Contributing +------------ + +Issues are tracked on `GitHub `_. + +Patches may be submitted via a `GitHub pull request`_ or via the mailing list +if you prefer. If you are comfortable doing so, please `rebase your changes`_ +so they may be applied to master with a fast-forward merge, and each commit is +a coherent unit of work with a well-written log message. If you are not +comfortable with this rebase workflow, the project maintainers will be happy to +rebase your commits for you. + +All changes should include tests and pass flake8_. + +.. image:: https://github.com/PyCQA/pyflakes/workflows/Test/badge.svg + :target: https://github.com/PyCQA/pyflakes/actions + :alt: GitHub Actions build status + +.. _Pylint: https://www.pylint.org/ +.. _flake8: https://pypi.org/project/flake8/ +.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/ +.. _`rebase your changes`: https://git-scm.com/book/en/v2/Git-Branching-Rebasing +.. _`GitHub pull request`: https://github.com/PyCQA/pyflakes/pulls + +Changelog +--------- + +Please see `NEWS.rst `_. + + diff --git a/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/RECORD b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/RECORD new file mode 100644 index 0000000..4d5fd11 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/RECORD @@ -0,0 +1,54 @@ +../../../bin/pyflakes,sha256=6eGg_feypLQOcKbe2DNy6EO9VYQL59igAEzxffXnM5w,248 +pyflakes-2.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pyflakes-2.4.0.dist-info/LICENSE,sha256=IsR1acwKrlMbNyNhNmgYrwHG_C4xR3BYSnhmySs6BGM,1093 +pyflakes-2.4.0.dist-info/METADATA,sha256=QHMV-AQCZ2FdZ4kKym5lvU9rEpG9tQjK5L_pQ1AZb60,3898 +pyflakes-2.4.0.dist-info/RECORD,, +pyflakes-2.4.0.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 +pyflakes-2.4.0.dist-info/entry_points.txt,sha256=ibYwCxqSCfn_Aie_yRgFv4X_Qz0X6_i5Av-Qn_5SQIQ,48 +pyflakes-2.4.0.dist-info/top_level.txt,sha256=wuK-mcws_v9MNkEyZaVwD2x9jdhkuRhdYZcqpBFliNM,9 +pyflakes/__init__.py,sha256=I1qJysfWW1Hf-R20wdbITNEESdCUgamBTo92IugxIOo,22 +pyflakes/__main__.py,sha256=9hmtTqo4qAQwd71f2ekWMIgb53frxGVwEabFWGG_WiE,105 +pyflakes/__pycache__/__init__.cpython-38.pyc,, +pyflakes/__pycache__/__main__.cpython-38.pyc,, +pyflakes/__pycache__/api.cpython-38.pyc,, +pyflakes/__pycache__/checker.cpython-38.pyc,, +pyflakes/__pycache__/messages.cpython-38.pyc,, +pyflakes/__pycache__/reporter.cpython-38.pyc,, +pyflakes/api.py,sha256=cuLdqdPReHWgzrWbTeMObXpsS-l-Lj4A1cz8UX5kkAM,6647 +pyflakes/checker.py,sha256=PRLjrJdRE5pi9lZ0_x9Lfa55qgu05c06hEz2fFJA9zI,84639 +pyflakes/messages.py,sha256=7TCRstN8c2Cet5bjALKmWU242j82kMtgo4cVcSw0NTM,10908 +pyflakes/reporter.py,sha256=xjjlUpLwRpVZR_qexAYc691LfjULpoLdmJOrfCqK4Es,2715 +pyflakes/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyflakes/scripts/__pycache__/__init__.cpython-38.pyc,, +pyflakes/scripts/__pycache__/pyflakes.cpython-38.pyc,, +pyflakes/scripts/pyflakes.py,sha256=mkM2-LUyi1i6Zt4-q7r66fJJu4JSnB50gWKTPFLJlmo,287 +pyflakes/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyflakes/test/__pycache__/__init__.cpython-38.pyc,, +pyflakes/test/__pycache__/harness.cpython-38.pyc,, +pyflakes/test/__pycache__/test_api.cpython-38.pyc,, +pyflakes/test/__pycache__/test_builtin.cpython-38.pyc,, +pyflakes/test/__pycache__/test_checker.cpython-38.pyc,, +pyflakes/test/__pycache__/test_code_segment.cpython-38.pyc,, +pyflakes/test/__pycache__/test_dict.cpython-38.pyc,, +pyflakes/test/__pycache__/test_doctests.cpython-38.pyc,, +pyflakes/test/__pycache__/test_imports.cpython-38.pyc,, +pyflakes/test/__pycache__/test_is_literal.cpython-38.pyc,, +pyflakes/test/__pycache__/test_match.cpython-38.pyc,, +pyflakes/test/__pycache__/test_other.cpython-38.pyc,, +pyflakes/test/__pycache__/test_return_with_arguments_inside_generator.cpython-38.pyc,, +pyflakes/test/__pycache__/test_type_annotations.cpython-38.pyc,, +pyflakes/test/__pycache__/test_undefined_names.cpython-38.pyc,, +pyflakes/test/harness.py,sha256=RVmRttuvt9RUMBqgVgMBsX9b1V_1cmbWQJPDCrCwTkk,2404 +pyflakes/test/test_api.py,sha256=iwovqMA6aKwTef80t-7bCiWq8R0mIYtOiY9bvP3iot8,27928 +pyflakes/test/test_builtin.py,sha256=1VGwORdJbtF3Ub04o80eobjOCgyJ7LqBjd9XrU1zxQs,871 +pyflakes/test/test_checker.py,sha256=fFd6EvNVdQkOY2HbBKowQEA5fs6B-NLZsjThwFCIqSA,6014 +pyflakes/test/test_code_segment.py,sha256=Ko7iAadmh4JGgcXTIvj699AHMQqK-fZIiWABALPihIo,4590 +pyflakes/test/test_dict.py,sha256=iz_lQLWtYy587pzC4nTuahvHfuLFZ-3on12dnTSg7A0,6050 +pyflakes/test/test_doctests.py,sha256=R3z7IevybQtuDfnCM3WXeziXe-AuIUCpFTqHpRdjrIo,13193 +pyflakes/test/test_imports.py,sha256=6hF0W0Se8G0P7jj68y4Z6QINVZM7fycnKTWV8rUInlU,34599 +pyflakes/test/test_is_literal.py,sha256=pj9XCSORmzFZ6fORj12_HGthWP6RN5MPeoyj3krwVxs,4573 +pyflakes/test/test_match.py,sha256=ScyFclKMdh8H2Z_kX7hxw-EVfqDs8wQ74qjReC-HoJE,2097 +pyflakes/test/test_other.py,sha256=FSwoOgLmOuMf4KOO48sSVgX9k1SjTuKgXSHo6XPdR5E,54214 +pyflakes/test/test_return_with_arguments_inside_generator.py,sha256=ay7XvfKNTSonRsrJdhDeVagVzdQy672TJbEOVJVs1jA,899 +pyflakes/test/test_type_annotations.py,sha256=J7ULmmGl7Q4JEXQ7p07r1JGa5mgkUOdd8WTUb9SBGYw,20098 +pyflakes/test/test_undefined_names.py,sha256=p_z5GW_8h59XHEEazq5Jrw9cNkLSAxjz3sklcEH_5vY,25805 diff --git a/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/WHEEL new file mode 100644 index 0000000..01b8fc7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/entry_points.txt b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/entry_points.txt new file mode 100644 index 0000000..7095e28 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +pyflakes = pyflakes.api:main + diff --git a/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/top_level.txt new file mode 100644 index 0000000..38675cb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes-2.4.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pyflakes diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__init__.py b/.venv/lib/python3.8/site-packages/pyflakes/__init__.py new file mode 100644 index 0000000..ba9b913 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/__init__.py @@ -0,0 +1 @@ +__version__ = '2.4.0' diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__main__.py b/.venv/lib/python3.8/site-packages/pyflakes/__main__.py new file mode 100644 index 0000000..68cd9ef --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/__main__.py @@ -0,0 +1,5 @@ +from pyflakes.api import main + +# python -m pyflakes +if __name__ == '__main__': + main(prog='pyflakes') diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f879860 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..6cbd12c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/api.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/api.cpython-38.pyc new file mode 100644 index 0000000..54e0a2b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/api.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/checker.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/checker.cpython-38.pyc new file mode 100644 index 0000000..632155d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/checker.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/messages.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/messages.cpython-38.pyc new file mode 100644 index 0000000..1037e6d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/messages.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/reporter.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/reporter.cpython-38.pyc new file mode 100644 index 0000000..3e3ec22 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/__pycache__/reporter.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/api.py b/.venv/lib/python3.8/site-packages/pyflakes/api.py new file mode 100644 index 0000000..ec3ef5a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/api.py @@ -0,0 +1,213 @@ +""" +API for the command-line I{pyflakes} tool. +""" +from __future__ import with_statement + +import ast +import os +import platform +import re +import sys + +from pyflakes import checker, __version__ +from pyflakes import reporter as modReporter + +__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main'] + +PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython([23](\.\d+)?|w)?[dmu]?\s') + + +def check(codeString, filename, reporter=None): + """ + Check the Python source given by C{codeString} for flakes. + + @param codeString: The Python source to check. + @type codeString: C{str} + + @param filename: The name of the file the source came from, used to report + errors. + @type filename: C{str} + + @param reporter: A L{Reporter} instance, where errors and warnings will be + reported. + + @return: The number of warnings emitted. + @rtype: C{int} + """ + if reporter is None: + reporter = modReporter._makeDefaultReporter() + # First, compile into an AST and handle syntax errors. + try: + tree = ast.parse(codeString, filename=filename) + except SyntaxError: + value = sys.exc_info()[1] + msg = value.args[0] + + (lineno, offset, text) = value.lineno, value.offset, value.text + + if checker.PYPY: + if text is None: + lines = codeString.splitlines() + if len(lines) >= lineno: + text = lines[lineno - 1] + if sys.version_info >= (3, ) and isinstance(text, bytes): + try: + text = text.decode('ascii') + except UnicodeDecodeError: + text = None + offset -= 1 + + # If there's an encoding problem with the file, the text is None. + if text is None: + # Avoid using msg, since for the only known case, it contains a + # bogus message that claims the encoding the file declared was + # unknown. + reporter.unexpectedError(filename, 'problem decoding source') + else: + reporter.syntaxError(filename, msg, lineno, offset, text) + return 1 + except Exception: + reporter.unexpectedError(filename, 'problem decoding source') + return 1 + # Okay, it's syntactically valid. Now check it. + file_tokens = checker.make_tokens(codeString) + w = checker.Checker(tree, file_tokens=file_tokens, filename=filename) + w.messages.sort(key=lambda m: m.lineno) + for warning in w.messages: + reporter.flake(warning) + return len(w.messages) + + +def checkPath(filename, reporter=None): + """ + Check the given path, printing out any warnings detected. + + @param reporter: A L{Reporter} instance, where errors and warnings will be + reported. + + @return: the number of warnings printed + """ + if reporter is None: + reporter = modReporter._makeDefaultReporter() + try: + with open(filename, 'rb') as f: + codestr = f.read() + except IOError: + msg = sys.exc_info()[1] + reporter.unexpectedError(filename, msg.args[1]) + return 1 + return check(codestr, filename, reporter) + + +def isPythonFile(filename): + """Return True if filename points to a Python file.""" + if filename.endswith('.py'): + return True + + # Avoid obvious Emacs backup files + if filename.endswith("~"): + return False + + max_bytes = 128 + + try: + with open(filename, 'rb') as f: + text = f.read(max_bytes) + if not text: + return False + except IOError: + return False + + return PYTHON_SHEBANG_REGEX.match(text) + + +def iterSourceCode(paths): + """ + Iterate over all Python source files in C{paths}. + + @param paths: A list of paths. Directories will be recursed into and + any .py files found will be yielded. Any non-directories will be + yielded as-is. + """ + for path in paths: + if os.path.isdir(path): + for dirpath, dirnames, filenames in os.walk(path): + for filename in filenames: + full_path = os.path.join(dirpath, filename) + if isPythonFile(full_path): + yield full_path + else: + yield path + + +def checkRecursive(paths, reporter): + """ + Recursively check all source files in C{paths}. + + @param paths: A list of paths to Python source files and directories + containing Python source files. + @param reporter: A L{Reporter} where all of the warnings and errors + will be reported to. + @return: The number of warnings found. + """ + warnings = 0 + for sourcePath in iterSourceCode(paths): + warnings += checkPath(sourcePath, reporter) + return warnings + + +def _exitOnSignal(sigName, message): + """Handles a signal with sys.exit. + + Some of these signals (SIGPIPE, for example) don't exist or are invalid on + Windows. So, ignore errors that might arise. + """ + import signal + + try: + sigNumber = getattr(signal, sigName) + except AttributeError: + # the signal constants defined in the signal module are defined by + # whether the C library supports them or not. So, SIGPIPE might not + # even be defined. + return + + def handler(sig, f): + sys.exit(message) + + try: + signal.signal(sigNumber, handler) + except ValueError: + # It's also possible the signal is defined, but then it's invalid. In + # this case, signal.signal raises ValueError. + pass + + +def _get_version(): + """ + Retrieve and format package version along with python version & OS used + """ + return ('%s Python %s on %s' % + (__version__, platform.python_version(), platform.system())) + + +def main(prog=None, args=None): + """Entry point for the script "pyflakes".""" + import argparse + + # Handle "Keyboard Interrupt" and "Broken pipe" gracefully + _exitOnSignal('SIGINT', '... stopped') + _exitOnSignal('SIGPIPE', 1) + + parser = argparse.ArgumentParser(prog=prog, + description='Check Python source files for errors') + parser.add_argument('-V', '--version', action='version', version=_get_version()) + parser.add_argument('path', nargs='*', + help='Path(s) of Python file(s) to check. STDIN if not given.') + args = parser.parse_args(args=args).path + reporter = modReporter._makeDefaultReporter() + if args: + warnings = checkRecursive(args, reporter) + else: + warnings = check(sys.stdin.read(), '', reporter) + raise SystemExit(warnings > 0) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/checker.py b/.venv/lib/python3.8/site-packages/pyflakes/checker.py new file mode 100644 index 0000000..45c7a4a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/checker.py @@ -0,0 +1,2402 @@ +""" +Main module. + +Implement the central Checker class. +Also, it models the Bindings and Scopes. +""" +import __future__ +import ast +import bisect +import collections +import contextlib +import doctest +import functools +import os +import re +import string +import sys +import tokenize + +from pyflakes import messages + +PY2 = sys.version_info < (3, 0) +PY35_PLUS = sys.version_info >= (3, 5) # Python 3.5 and above +PY36_PLUS = sys.version_info >= (3, 6) # Python 3.6 and above +PY38_PLUS = sys.version_info >= (3, 8) +try: + sys.pypy_version_info + PYPY = True +except AttributeError: + PYPY = False + +builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins')) + +parse_format_string = string.Formatter().parse + +if PY2: + tokenize_tokenize = tokenize.generate_tokens +else: + tokenize_tokenize = tokenize.tokenize + +if PY2: + def getNodeType(node_class): + # workaround str.upper() which is locale-dependent + return str(unicode(node_class.__name__).upper()) + + def get_raise_argument(node): + return node.type + +else: + def getNodeType(node_class): + return node_class.__name__.upper() + + def get_raise_argument(node): + return node.exc + + # Silence `pyflakes` from reporting `undefined name 'unicode'` in Python 3. + unicode = str + +# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) +if PY2: + def getAlternatives(n): + if isinstance(n, (ast.If, ast.TryFinally)): + return [n.body] + if isinstance(n, ast.TryExcept): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] +else: + def getAlternatives(n): + if isinstance(n, ast.If): + return [n.body] + if isinstance(n, ast.Try): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] + +if PY35_PLUS: + FOR_TYPES = (ast.For, ast.AsyncFor) + LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor) + FUNCTION_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef) +else: + FOR_TYPES = (ast.For,) + LOOP_TYPES = (ast.While, ast.For) + FUNCTION_TYPES = (ast.FunctionDef,) + +if PY36_PLUS: + ANNASSIGN_TYPES = (ast.AnnAssign,) +else: + ANNASSIGN_TYPES = () + +if PY38_PLUS: + def _is_singleton(node): # type: (ast.AST) -> bool + return ( + isinstance(node, ast.Constant) and + isinstance(node.value, (bool, type(Ellipsis), type(None))) + ) +elif not PY2: + def _is_singleton(node): # type: (ast.AST) -> bool + return isinstance(node, (ast.NameConstant, ast.Ellipsis)) +else: + def _is_singleton(node): # type: (ast.AST) -> bool + return ( + isinstance(node, ast.Name) and + node.id in {'True', 'False', 'Ellipsis', 'None'} + ) + + +def _is_tuple_constant(node): # type: (ast.AST) -> bool + return ( + isinstance(node, ast.Tuple) and + all(_is_constant(elt) for elt in node.elts) + ) + + +if PY38_PLUS: + def _is_constant(node): + return isinstance(node, ast.Constant) or _is_tuple_constant(node) +else: + _const_tps = (ast.Str, ast.Num) + if not PY2: + _const_tps += (ast.Bytes,) + + def _is_constant(node): + return ( + isinstance(node, _const_tps) or + _is_singleton(node) or + _is_tuple_constant(node) + ) + + +def _is_const_non_singleton(node): # type: (ast.AST) -> bool + return _is_constant(node) and not _is_singleton(node) + + +def _is_name_or_attr(node, name): # type: (ast.Ast, str) -> bool + return ( + (isinstance(node, ast.Name) and node.id == name) or + (isinstance(node, ast.Attribute) and node.attr == name) + ) + + +# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104 +TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') +# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413 +ASCII_NON_ALNUM = ''.join([chr(i) for i in range(128) if not chr(i).isalnum()]) +TYPE_IGNORE_RE = re.compile( + TYPE_COMMENT_RE.pattern + r'ignore([{}]|$)'.format(ASCII_NON_ALNUM)) +# https://github.com/python/typed_ast/blob/1.4.0/ast27/Grammar/Grammar#L147 +TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') + + +MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') +CONVERSION_FLAG_RE = re.compile('[#0+ -]*') +WIDTH_RE = re.compile(r'(?:\*|\d*)') +PRECISION_RE = re.compile(r'(?:\.(?:\*|\d*))?') +LENGTH_RE = re.compile('[hlL]?') +# https://docs.python.org/3/library/stdtypes.html#old-string-formatting +VALID_CONVERSIONS = frozenset('diouxXeEfFgGcrsa%') + + +def _must_match(regex, string, pos): + # type: (Pattern[str], str, int) -> Match[str] + match = regex.match(string, pos) + assert match is not None + return match + + +def parse_percent_format(s): # type: (str) -> Tuple[PercentFormat, ...] + """Parses the string component of a `'...' % ...` format call + + Copied from https://github.com/asottile/pyupgrade at v1.20.1 + """ + + def _parse_inner(): + # type: () -> Generator[PercentFormat, None, None] + string_start = 0 + string_end = 0 + in_fmt = False + + i = 0 + while i < len(s): + if not in_fmt: + try: + i = s.index('%', i) + except ValueError: # no more % fields! + yield s[string_start:], None + return + else: + string_end = i + i += 1 + in_fmt = True + else: + key_match = MAPPING_KEY_RE.match(s, i) + if key_match: + key = key_match.group(1) # type: Optional[str] + i = key_match.end() + else: + key = None + + conversion_flag_match = _must_match(CONVERSION_FLAG_RE, s, i) + conversion_flag = conversion_flag_match.group() or None + i = conversion_flag_match.end() + + width_match = _must_match(WIDTH_RE, s, i) + width = width_match.group() or None + i = width_match.end() + + precision_match = _must_match(PRECISION_RE, s, i) + precision = precision_match.group() or None + i = precision_match.end() + + # length modifier is ignored + i = _must_match(LENGTH_RE, s, i).end() + + try: + conversion = s[i] + except IndexError: + raise ValueError('end-of-string while parsing format') + i += 1 + + fmt = (key, conversion_flag, width, precision, conversion) + yield s[string_start:string_end], fmt + + in_fmt = False + string_start = i + + if in_fmt: + raise ValueError('end-of-string while parsing format') + + return tuple(_parse_inner()) + + +class _FieldsOrder(dict): + """Fix order of AST node fields.""" + + def _get_fields(self, node_class): + # handle iter before target, and generators before element + fields = node_class._fields + if 'iter' in fields: + key_first = 'iter'.find + elif 'generators' in fields: + key_first = 'generators'.find + else: + key_first = 'value'.find + return tuple(sorted(fields, key=key_first, reverse=True)) + + def __missing__(self, node_class): + self[node_class] = fields = self._get_fields(node_class) + return fields + + +def counter(items): + """ + Simplest required implementation of collections.Counter. Required as 2.6 + does not have Counter in collections. + """ + results = {} + for item in items: + results[item] = results.get(item, 0) + 1 + return results + + +def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): + """ + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. + + :param node: AST node to be iterated upon + :param omit: String or tuple of strings denoting the + attributes of the node to be omitted from + further parsing + :param _fields_order: Order of AST node fields + """ + for name in _fields_order[node.__class__]: + if omit and name in omit: + continue + field = getattr(node, name, None) + if isinstance(field, ast.AST): + yield field + elif isinstance(field, list): + for item in field: + if isinstance(item, ast.AST): + yield item + + +def convert_to_value(item): + if isinstance(item, ast.Str): + return item.s + elif hasattr(ast, 'Bytes') and isinstance(item, ast.Bytes): + return item.s + elif isinstance(item, ast.Tuple): + return tuple(convert_to_value(i) for i in item.elts) + elif isinstance(item, ast.Num): + return item.n + elif isinstance(item, ast.Name): + result = VariableKey(item=item) + constants_lookup = { + 'True': True, + 'False': False, + 'None': None, + } + return constants_lookup.get( + result.name, + result, + ) + elif (not PY2) and isinstance(item, ast.NameConstant): + # None, True, False are nameconstants in python3, but names in 2 + return item.value + else: + return UnhandledKeyType() + + +def is_notimplemented_name_node(node): + return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented' + + +class Binding(object): + """ + Represents the binding of a value to a name. + + The checker uses this to keep track of which names have been bound and + which names have not. See L{Assignment} for a special type of binding that + is checked with stricter rules. + + @ivar used: pair of (L{Scope}, node) indicating the scope and + the node that this binding was last used. + """ + + def __init__(self, name, source): + self.name = name + self.source = source + self.used = False + + def __str__(self): + return self.name + + def __repr__(self): + return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, + self.name, + self.source.lineno, + id(self)) + + def redefines(self, other): + return isinstance(other, Definition) and self.name == other.name + + +class Definition(Binding): + """ + A binding that defines a function or a class. + """ + + +class Builtin(Definition): + """A definition created for all Python builtins.""" + + def __init__(self, name): + super(Builtin, self).__init__(name, None) + + def __repr__(self): + return '<%s object %r at 0x%x>' % (self.__class__.__name__, + self.name, + id(self)) + + +class UnhandledKeyType(object): + """ + A dictionary key of a type that we cannot or do not check for duplicates. + """ + + +class VariableKey(object): + """ + A dictionary key which is a variable. + + @ivar item: The variable AST object. + """ + def __init__(self, item): + self.name = item.id + + def __eq__(self, compare): + return ( + compare.__class__ == self.__class__ and + compare.name == self.name + ) + + def __hash__(self): + return hash(self.name) + + +class Importation(Definition): + """ + A binding created by an import statement. + + @ivar fullName: The complete name given to the import statement, + possibly including multiple dotted components. + @type fullName: C{str} + """ + + def __init__(self, name, source, full_name=None): + self.fullName = full_name or name + self.redefined = [] + super(Importation, self).__init__(name, source) + + def redefines(self, other): + if isinstance(other, SubmoduleImportation): + # See note in SubmoduleImportation about RedefinedWhileUnused + return self.fullName == other.fullName + return isinstance(other, Definition) and self.name == other.name + + def _has_alias(self): + """Return whether importation needs an as clause.""" + return not self.fullName.split('.')[-1] == self.name + + @property + def source_statement(self): + """Generate a source statement equivalent to the import.""" + if self._has_alias(): + return 'import %s as %s' % (self.fullName, self.name) + else: + return 'import %s' % self.fullName + + def __str__(self): + """Return import full name with alias.""" + if self._has_alias(): + return self.fullName + ' as ' + self.name + else: + return self.fullName + + +class SubmoduleImportation(Importation): + """ + A binding created by a submodule import statement. + + A submodule import is a special case where the root module is implicitly + imported, without an 'as' clause, and the submodule is also imported. + Python does not restrict which attributes of the root module may be used. + + This class is only used when the submodule import is without an 'as' clause. + + pyflakes handles this case by registering the root module name in the scope, + allowing any attribute of the root module to be accessed. + + RedefinedWhileUnused is suppressed in `redefines` unless the submodule + name is also the same, to avoid false positives. + """ + + def __init__(self, name, source): + # A dot should only appear in the name when it is a submodule import + assert '.' in name and (not source or isinstance(source, ast.Import)) + package_name = name.split('.')[0] + super(SubmoduleImportation, self).__init__(package_name, source) + self.fullName = name + + def redefines(self, other): + if isinstance(other, Importation): + return self.fullName == other.fullName + return super(SubmoduleImportation, self).redefines(other) + + def __str__(self): + return self.fullName + + @property + def source_statement(self): + return 'import ' + self.fullName + + +class ImportationFrom(Importation): + + def __init__(self, name, source, module, real_name=None): + self.module = module + self.real_name = real_name or name + + if module.endswith('.'): + full_name = module + self.real_name + else: + full_name = module + '.' + self.real_name + + super(ImportationFrom, self).__init__(name, source, full_name) + + def __str__(self): + """Return import full name with alias.""" + if self.real_name != self.name: + return self.fullName + ' as ' + self.name + else: + return self.fullName + + @property + def source_statement(self): + if self.real_name != self.name: + return 'from %s import %s as %s' % (self.module, + self.real_name, + self.name) + else: + return 'from %s import %s' % (self.module, self.name) + + +class StarImportation(Importation): + """A binding created by a 'from x import *' statement.""" + + def __init__(self, name, source): + super(StarImportation, self).__init__('*', source) + # Each star importation needs a unique name, and + # may not be the module name otherwise it will be deemed imported + self.name = name + '.*' + self.fullName = name + + @property + def source_statement(self): + return 'from ' + self.fullName + ' import *' + + def __str__(self): + # When the module ends with a ., avoid the ambiguous '..*' + if self.fullName.endswith('.'): + return self.source_statement + else: + return self.name + + +class FutureImportation(ImportationFrom): + """ + A binding created by a from `__future__` import statement. + + `__future__` imports are implicitly used. + """ + + def __init__(self, name, source, scope): + super(FutureImportation, self).__init__(name, source, '__future__') + self.used = (scope, source) + + +class Argument(Binding): + """ + Represents binding a name as an argument. + """ + + +class Assignment(Binding): + """ + Represents binding a name with an explicit assignment. + + The checker will raise warnings for any Assignment that isn't used. Also, + the checker does not consider assignments in tuple/list unpacking to be + Assignments, rather it treats them as simple Bindings. + """ + + +class Annotation(Binding): + """ + Represents binding a name to a type without an associated value. + + As long as this name is not assigned a value in another binding, it is considered + undefined for most purposes. One notable exception is using the name as a type + annotation. + """ + + def redefines(self, other): + """An Annotation doesn't define any name, so it cannot redefine one.""" + return False + + +class FunctionDefinition(Definition): + pass + + +class ClassDefinition(Definition): + pass + + +class ExportBinding(Binding): + """ + A binding created by an C{__all__} assignment. If the names in the list + can be determined statically, they will be treated as names for export and + additional checking applied to them. + + The only recognized C{__all__} assignment via list/tuple concatenation is in the + following format: + + __all__ = ['a'] + ['b'] + ['c'] + + Names which are imported and not otherwise used but appear in the value of + C{__all__} will not have an unused import warning reported for them. + """ + + def __init__(self, name, source, scope): + if '__all__' in scope and isinstance(source, ast.AugAssign): + self.names = list(scope['__all__'].names) + else: + self.names = [] + + def _add_to_names(container): + for node in container.elts: + if isinstance(node, ast.Str): + self.names.append(node.s) + + if isinstance(source.value, (ast.List, ast.Tuple)): + _add_to_names(source.value) + # If concatenating lists or tuples + elif isinstance(source.value, ast.BinOp): + currentValue = source.value + while isinstance(currentValue.right, (ast.List, ast.Tuple)): + left = currentValue.left + right = currentValue.right + _add_to_names(right) + # If more lists are being added + if isinstance(left, ast.BinOp): + currentValue = left + # If just two lists are being added + elif isinstance(left, (ast.List, ast.Tuple)): + _add_to_names(left) + # All lists accounted for - done + break + # If not list concatenation + else: + break + super(ExportBinding, self).__init__(name, source) + + +class Scope(dict): + importStarred = False # set to True when import * is found + + def __repr__(self): + scope_cls = self.__class__.__name__ + return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self)) + + +class ClassScope(Scope): + pass + + +class FunctionScope(Scope): + """ + I represent a name scope for a function. + + @ivar globals: Names declared 'global' in this function. + """ + usesLocals = False + alwaysUsed = {'__tracebackhide__', '__traceback_info__', + '__traceback_supplement__'} + + def __init__(self): + super(FunctionScope, self).__init__() + # Simplify: manage the special locals as globals + self.globals = self.alwaysUsed.copy() + self.returnValue = None # First non-empty return + self.isGenerator = False # Detect a generator + + def unusedAssignments(self): + """ + Return a generator for the assignments which have not been used. + """ + for name, binding in self.items(): + if (not binding.used and + name != '_' and # see issue #202 + name not in self.globals and + not self.usesLocals and + isinstance(binding, Assignment)): + yield name, binding + + +class GeneratorScope(Scope): + pass + + +class ModuleScope(Scope): + """Scope for a module.""" + _futures_allowed = True + _annotations_future_enabled = False + + +class DoctestScope(ModuleScope): + """Scope for a doctest.""" + + +class DummyNode(object): + """Used in place of an `ast.AST` to set error message positions""" + def __init__(self, lineno, col_offset): + self.lineno = lineno + self.col_offset = col_offset + + +class DetectClassScopedMagic: + names = dir() + + +# Globally defined names which are not attributes of the builtins module, or +# are only present on some platforms. +_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] +# module scope annotation will store in `__annotations__`, see also PEP 526. +if PY36_PLUS: + _MAGIC_GLOBALS.append('__annotations__') + + +def getNodeName(node): + # Returns node.id, or node.name, or None + if hasattr(node, 'id'): # One of the many nodes with an id + return node.id + if hasattr(node, 'name'): # an ExceptHandler node + return node.name + if hasattr(node, 'rest'): # a MatchMapping node + return node.rest + + +TYPING_MODULES = frozenset(('typing', 'typing_extensions')) + + +def _is_typing_helper(node, is_name_match_fn, scope_stack): + """ + Internal helper to determine whether or not something is a member of a + typing module. This is used as part of working out whether we are within a + type annotation context. + + Note: you probably don't want to use this function directly. Instead see the + utils below which wrap it (`_is_typing` and `_is_any_typing_member`). + """ + + def _bare_name_is_attr(name): + for scope in reversed(scope_stack): + if name in scope: + return ( + isinstance(scope[name], ImportationFrom) and + scope[name].module in TYPING_MODULES and + is_name_match_fn(scope[name].real_name) + ) + + return False + + def _module_scope_is_typing(name): + for scope in reversed(scope_stack): + if name in scope: + return ( + isinstance(scope[name], Importation) and + scope[name].fullName in TYPING_MODULES + ) + + return False + + return ( + ( + isinstance(node, ast.Name) and + _bare_name_is_attr(node.id) + ) or ( + isinstance(node, ast.Attribute) and + isinstance(node.value, ast.Name) and + _module_scope_is_typing(node.value.id) and + is_name_match_fn(node.attr) + ) + ) + + +def _is_typing(node, typing_attr, scope_stack): + """ + Determine whether `node` represents the member of a typing module specified + by `typing_attr`. + + This is used as part of working out whether we are within a type annotation + context. + """ + return _is_typing_helper(node, lambda x: x == typing_attr, scope_stack) + + +def _is_any_typing_member(node, scope_stack): + """ + Determine whether `node` represents any member of a typing module. + + This is used as part of working out whether we are within a type annotation + context. + """ + return _is_typing_helper(node, lambda x: True, scope_stack) + + +def is_typing_overload(value, scope_stack): + return ( + isinstance(value.source, FUNCTION_TYPES) and + any( + _is_typing(dec, 'overload', scope_stack) + for dec in value.source.decorator_list + ) + ) + + +class AnnotationState: + NONE = 0 + STRING = 1 + BARE = 2 + + +def in_annotation(func): + @functools.wraps(func) + def in_annotation_func(self, *args, **kwargs): + with self._enter_annotation(): + return func(self, *args, **kwargs) + return in_annotation_func + + +def in_string_annotation(func): + @functools.wraps(func) + def in_annotation_func(self, *args, **kwargs): + with self._enter_annotation(AnnotationState.STRING): + return func(self, *args, **kwargs) + return in_annotation_func + + +def make_tokens(code): + # PY3: tokenize.tokenize requires readline of bytes + if not isinstance(code, bytes): + code = code.encode('UTF-8') + lines = iter(code.splitlines(True)) + # next(lines, b'') is to prevent an error in pypy3 + return tuple(tokenize_tokenize(lambda: next(lines, b''))) + + +class _TypeableVisitor(ast.NodeVisitor): + """Collect the line number and nodes which are deemed typeable by + PEP 484 + + https://www.python.org/dev/peps/pep-0484/#type-comments + """ + def __init__(self): + self.typeable_lines = [] # type: List[int] + self.typeable_nodes = {} # type: Dict[int, ast.AST] + + def _typeable(self, node): + # if there is more than one typeable thing on a line last one wins + self.typeable_lines.append(node.lineno) + self.typeable_nodes[node.lineno] = node + + self.generic_visit(node) + + visit_Assign = visit_For = visit_FunctionDef = visit_With = _typeable + visit_AsyncFor = visit_AsyncFunctionDef = visit_AsyncWith = _typeable + + +def _collect_type_comments(tree, tokens): + visitor = _TypeableVisitor() + visitor.visit(tree) + + type_comments = collections.defaultdict(list) + for tp, text, start, _, _ in tokens: + if ( + tp != tokenize.COMMENT or # skip non comments + not TYPE_COMMENT_RE.match(text) or # skip non-type comments + TYPE_IGNORE_RE.match(text) # skip ignores + ): + continue + + # search for the typeable node at or before the line number of the + # type comment. + # if the bisection insertion point is before any nodes this is an + # invalid type comment which is ignored. + lineno, _ = start + idx = bisect.bisect_right(visitor.typeable_lines, lineno) + if idx == 0: + continue + node = visitor.typeable_nodes[visitor.typeable_lines[idx - 1]] + type_comments[node].append((start, text)) + + return type_comments + + +class Checker(object): + """ + I check the cleanliness and sanity of Python code. + + @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements + of the list are two-tuples. The first element is the callable passed + to L{deferFunction}. The second element is a copy of the scope stack + at the time L{deferFunction} was called. + + @ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for + callables which are deferred assignment checks. + """ + + _ast_node_scope = { + ast.Module: ModuleScope, + ast.ClassDef: ClassScope, + ast.FunctionDef: FunctionScope, + ast.Lambda: FunctionScope, + ast.ListComp: GeneratorScope, + ast.SetComp: GeneratorScope, + ast.GeneratorExp: GeneratorScope, + ast.DictComp: GeneratorScope, + } + if PY35_PLUS: + _ast_node_scope[ast.AsyncFunctionDef] = FunctionScope + + nodeDepth = 0 + offset = None + _in_annotation = AnnotationState.NONE + _in_deferred = False + + builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) + _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') + if _customBuiltIns: + builtIns.update(_customBuiltIns.split(',')) + del _customBuiltIns + + # TODO: file_tokens= is required to perform checks on type comments, + # eventually make this a required positional argument. For now it + # is defaulted to `()` for api compatibility. + def __init__(self, tree, filename='(none)', builtins=None, + withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): + self._nodeHandlers = {} + self._deferredFunctions = [] + self._deferredAssignments = [] + self.deadScopes = [] + self.messages = [] + self.filename = filename + if builtins: + self.builtIns = self.builtIns.union(builtins) + self.withDoctest = withDoctest + try: + self.scopeStack = [Checker._ast_node_scope[type(tree)]()] + except KeyError: + raise RuntimeError('No scope implemented for the node %r' % tree) + self.exceptHandlers = [()] + self.root = tree + self._type_comments = _collect_type_comments(tree, file_tokens) + for builtin in self.builtIns: + self.addBinding(None, Builtin(builtin)) + self.handleChildren(tree) + self._in_deferred = True + self.runDeferred(self._deferredFunctions) + # Set _deferredFunctions to None so that deferFunction will fail + # noisily if called after we've run through the deferred functions. + self._deferredFunctions = None + self.runDeferred(self._deferredAssignments) + # Set _deferredAssignments to None so that deferAssignment will fail + # noisily if called after we've run through the deferred assignments. + self._deferredAssignments = None + del self.scopeStack[1:] + self.popScope() + self.checkDeadScopes() + + def deferFunction(self, callable): + """ + Schedule a function handler to be called just before completion. + + This is used for handling function bodies, which must be deferred + because code later in the file might modify the global scope. When + `callable` is called, the scope at the time this is called will be + restored, however it will contain any new bindings added to it. + """ + self._deferredFunctions.append((callable, self.scopeStack[:], self.offset)) + + def deferAssignment(self, callable): + """ + Schedule an assignment handler to be called just after deferred + function handlers. + """ + self._deferredAssignments.append((callable, self.scopeStack[:], self.offset)) + + def runDeferred(self, deferred): + """ + Run the callables in C{deferred} using their associated scope stack. + """ + for handler, scope, offset in deferred: + self.scopeStack = scope + self.offset = offset + handler() + + def _in_doctest(self): + return (len(self.scopeStack) >= 2 and + isinstance(self.scopeStack[1], DoctestScope)) + + @property + def futuresAllowed(self): + if not all(isinstance(scope, ModuleScope) + for scope in self.scopeStack): + return False + + return self.scope._futures_allowed + + @futuresAllowed.setter + def futuresAllowed(self, value): + assert value is False + if isinstance(self.scope, ModuleScope): + self.scope._futures_allowed = False + + @property + def annotationsFutureEnabled(self): + scope = self.scopeStack[0] + if not isinstance(scope, ModuleScope): + return False + return scope._annotations_future_enabled + + @annotationsFutureEnabled.setter + def annotationsFutureEnabled(self, value): + assert value is True + assert isinstance(self.scope, ModuleScope) + self.scope._annotations_future_enabled = True + + @property + def scope(self): + return self.scopeStack[-1] + + def popScope(self): + self.deadScopes.append(self.scopeStack.pop()) + + def checkDeadScopes(self): + """ + Look at scopes which have been fully examined and report names in them + which were imported but unused. + """ + for scope in self.deadScopes: + # imports in classes are public members + if isinstance(scope, ClassScope): + continue + + all_binding = scope.get('__all__') + if all_binding and not isinstance(all_binding, ExportBinding): + all_binding = None + + if all_binding: + all_names = set(all_binding.names) + undefined = [ + name for name in all_binding.names + if name not in scope + ] + else: + all_names = undefined = [] + + if undefined: + if not scope.importStarred and \ + os.path.basename(self.filename) != '__init__.py': + # Look for possible mistakes in the export list + for name in undefined: + self.report(messages.UndefinedExport, + scope['__all__'].source, name) + + # mark all import '*' as used by the undefined in __all__ + if scope.importStarred: + from_list = [] + for binding in scope.values(): + if isinstance(binding, StarImportation): + binding.used = all_binding + from_list.append(binding.fullName) + # report * usage, with a list of possible sources + from_list = ', '.join(sorted(from_list)) + for name in undefined: + self.report(messages.ImportStarUsage, + scope['__all__'].source, name, from_list) + + # Look for imported names that aren't used. + for value in scope.values(): + if isinstance(value, Importation): + used = value.used or value.name in all_names + if not used: + messg = messages.UnusedImport + self.report(messg, value.source, str(value)) + for node in value.redefined: + if isinstance(self.getParent(node), FOR_TYPES): + messg = messages.ImportShadowedByLoopVar + elif used: + continue + else: + messg = messages.RedefinedWhileUnused + self.report(messg, node, value.name, value.source) + + def pushScope(self, scopeClass=FunctionScope): + self.scopeStack.append(scopeClass()) + + def report(self, messageClass, *args, **kwargs): + self.messages.append(messageClass(self.filename, *args, **kwargs)) + + def getParent(self, node): + # Lookup the first parent which is not Tuple, List or Starred + while True: + node = node._pyflakes_parent + if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): + return node + + def getCommonAncestor(self, lnode, rnode, stop): + if ( + stop in (lnode, rnode) or + not ( + hasattr(lnode, '_pyflakes_parent') and + hasattr(rnode, '_pyflakes_parent') + ) + ): + return None + if lnode is rnode: + return lnode + + if (lnode._pyflakes_depth > rnode._pyflakes_depth): + return self.getCommonAncestor(lnode._pyflakes_parent, rnode, stop) + if (lnode._pyflakes_depth < rnode._pyflakes_depth): + return self.getCommonAncestor(lnode, rnode._pyflakes_parent, stop) + return self.getCommonAncestor( + lnode._pyflakes_parent, + rnode._pyflakes_parent, + stop, + ) + + def descendantOf(self, node, ancestors, stop): + for a in ancestors: + if self.getCommonAncestor(node, a, stop): + return True + return False + + def _getAncestor(self, node, ancestor_type): + parent = node + while True: + if parent is self.root: + return None + parent = self.getParent(parent) + if isinstance(parent, ancestor_type): + return parent + + def getScopeNode(self, node): + return self._getAncestor(node, tuple(Checker._ast_node_scope.keys())) + + def differentForks(self, lnode, rnode): + """True, if lnode and rnode are located on different forks of IF/TRY""" + ancestor = self.getCommonAncestor(lnode, rnode, self.root) + parts = getAlternatives(ancestor) + if parts: + for items in parts: + if self.descendantOf(lnode, items, ancestor) ^ \ + self.descendantOf(rnode, items, ancestor): + return True + return False + + def addBinding(self, node, value): + """ + Called when a binding is altered. + + - `node` is the statement responsible for the change + - `value` is the new value, a Binding instance + """ + # assert value.source in (node, node._pyflakes_parent): + for scope in self.scopeStack[::-1]: + if value.name in scope: + break + existing = scope.get(value.name) + + if (existing and not isinstance(existing, Builtin) and + not self.differentForks(node, existing.source)): + + parent_stmt = self.getParent(value.source) + if isinstance(existing, Importation) and isinstance(parent_stmt, FOR_TYPES): + self.report(messages.ImportShadowedByLoopVar, + node, value.name, existing.source) + + elif scope is self.scope: + if (isinstance(parent_stmt, ast.comprehension) and + not isinstance(self.getParent(existing.source), + (FOR_TYPES, ast.comprehension))): + self.report(messages.RedefinedInListComp, + node, value.name, existing.source) + elif not existing.used and value.redefines(existing): + if value.name != '_' or isinstance(existing, Importation): + if not is_typing_overload(existing, self.scopeStack): + self.report(messages.RedefinedWhileUnused, + node, value.name, existing.source) + + elif isinstance(existing, Importation) and value.redefines(existing): + existing.redefined.append(node) + + if value.name in self.scope: + # then assume the rebound name is used as a global or within a loop + value.used = self.scope[value.name].used + + # don't treat annotations as assignments if there is an existing value + # in scope + if value.name not in self.scope or not isinstance(value, Annotation): + self.scope[value.name] = value + + def _unknown_handler(self, node): + # this environment variable configures whether to error on unknown + # ast types. + # + # this is silent by default but the error is enabled for the pyflakes + # testsuite. + # + # this allows new syntax to be added to python without *requiring* + # changes from the pyflakes side. but will still produce an error + # in the pyflakes testsuite (so more specific handling can be added if + # needed). + if os.environ.get('PYFLAKES_ERROR_UNKNOWN'): + raise NotImplementedError('Unexpected type: {}'.format(type(node))) + else: + self.handleChildren(node) + + def getNodeHandler(self, node_class): + try: + return self._nodeHandlers[node_class] + except KeyError: + nodeType = getNodeType(node_class) + self._nodeHandlers[node_class] = handler = getattr( + self, nodeType, self._unknown_handler, + ) + return handler + + def handleNodeLoad(self, node): + name = getNodeName(node) + if not name: + return + + in_generators = None + importStarred = None + + # try enclosing function scopes and global scope + for scope in self.scopeStack[-1::-1]: + if isinstance(scope, ClassScope): + if not PY2 and name == '__class__': + return + elif in_generators is False: + # only generators used in a class scope can access the + # names of the class. this is skipped during the first + # iteration + continue + + binding = scope.get(name, None) + if isinstance(binding, Annotation) and not self._in_postponed_annotation: + continue + + if name == 'print' and isinstance(binding, Builtin): + parent = self.getParent(node) + if (isinstance(parent, ast.BinOp) and + isinstance(parent.op, ast.RShift)): + self.report(messages.InvalidPrintSyntax, node) + + try: + scope[name].used = (self.scope, node) + + # if the name of SubImportation is same as + # alias of other Importation and the alias + # is used, SubImportation also should be marked as used. + n = scope[name] + if isinstance(n, Importation) and n._has_alias(): + try: + scope[n.fullName].used = (self.scope, node) + except KeyError: + pass + except KeyError: + pass + else: + return + + importStarred = importStarred or scope.importStarred + + if in_generators is not False: + in_generators = isinstance(scope, GeneratorScope) + + if importStarred: + from_list = [] + + for scope in self.scopeStack[-1::-1]: + for binding in scope.values(): + if isinstance(binding, StarImportation): + # mark '*' imports as used for each scope + binding.used = (self.scope, node) + from_list.append(binding.fullName) + + # report * usage, with a list of possible sources + from_list = ', '.join(sorted(from_list)) + self.report(messages.ImportStarUsage, node, name, from_list) + return + + if name == '__path__' and os.path.basename(self.filename) == '__init__.py': + # the special name __path__ is valid only in packages + return + + if name in DetectClassScopedMagic.names and isinstance(self.scope, ClassScope): + return + + # protected with a NameError handler? + if 'NameError' not in self.exceptHandlers[-1]: + self.report(messages.UndefinedName, node, name) + + def handleNodeStore(self, node): + name = getNodeName(node) + if not name: + return + # if the name hasn't already been defined in the current scope + if isinstance(self.scope, FunctionScope) and name not in self.scope: + # for each function or module scope above us + for scope in self.scopeStack[:-1]: + if not isinstance(scope, (FunctionScope, ModuleScope)): + continue + # if the name was defined in that scope, and the name has + # been accessed already in the current scope, and hasn't + # been declared global + used = name in scope and scope[name].used + if used and used[0] is self.scope and name not in self.scope.globals: + # then it's probably a mistake + self.report(messages.UndefinedLocal, + scope[name].used[1], name, scope[name].source) + break + + parent_stmt = self.getParent(node) + if isinstance(parent_stmt, ANNASSIGN_TYPES) and parent_stmt.value is None: + binding = Annotation(name, node) + elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( + parent_stmt != node._pyflakes_parent and + not self.isLiteralTupleUnpacking(parent_stmt)): + binding = Binding(name, node) + elif name == '__all__' and isinstance(self.scope, ModuleScope): + binding = ExportBinding(name, node._pyflakes_parent, self.scope) + elif PY2 and isinstance(getattr(node, 'ctx', None), ast.Param): + binding = Argument(name, self.getScopeNode(node)) + else: + binding = Assignment(name, node) + self.addBinding(node, binding) + + def handleNodeDelete(self, node): + + def on_conditional_branch(): + """ + Return `True` if node is part of a conditional body. + """ + current = getattr(node, '_pyflakes_parent', None) + while current: + if isinstance(current, (ast.If, ast.While, ast.IfExp)): + return True + current = getattr(current, '_pyflakes_parent', None) + return False + + name = getNodeName(node) + if not name: + return + + if on_conditional_branch(): + # We cannot predict if this conditional branch is going to + # be executed. + return + + if isinstance(self.scope, FunctionScope) and name in self.scope.globals: + self.scope.globals.remove(name) + else: + try: + del self.scope[name] + except KeyError: + self.report(messages.UndefinedName, node, name) + + @contextlib.contextmanager + def _enter_annotation(self, ann_type=AnnotationState.BARE): + orig, self._in_annotation = self._in_annotation, ann_type + try: + yield + finally: + self._in_annotation = orig + + @property + def _in_postponed_annotation(self): + return ( + self._in_annotation == AnnotationState.STRING or + self.annotationsFutureEnabled + ) + + def _handle_type_comments(self, node): + for (lineno, col_offset), comment in self._type_comments.get(node, ()): + comment = comment.split(':', 1)[1].strip() + func_match = TYPE_FUNC_RE.match(comment) + if func_match: + parts = ( + func_match.group(1).replace('*', ''), + func_match.group(2).strip(), + ) + else: + parts = (comment,) + + for part in parts: + if PY2: + part = part.replace('...', 'Ellipsis') + self.deferFunction(functools.partial( + self.handleStringAnnotation, + part, DummyNode(lineno, col_offset), lineno, col_offset, + messages.CommentAnnotationSyntaxError, + )) + + def handleChildren(self, tree, omit=None): + self._handle_type_comments(tree) + for node in iter_child_nodes(tree, omit=omit): + self.handleNode(node, tree) + + def isLiteralTupleUnpacking(self, node): + if isinstance(node, ast.Assign): + for child in node.targets + [node.value]: + if not hasattr(child, 'elts'): + return False + return True + + def isDocstring(self, node): + """ + Determine if the given node is a docstring, as long as it is at the + correct place in the node tree. + """ + return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and + isinstance(node.value, ast.Str)) + + def getDocstring(self, node): + if isinstance(node, ast.Expr): + node = node.value + if not isinstance(node, ast.Str): + return (None, None) + + if PYPY or PY38_PLUS: + doctest_lineno = node.lineno - 1 + else: + # Computed incorrectly if the docstring has backslash + doctest_lineno = node.lineno - node.s.count('\n') - 1 + + return (node.s, doctest_lineno) + + def handleNode(self, node, parent): + if node is None: + return + if self.offset and getattr(node, 'lineno', None) is not None: + node.lineno += self.offset[0] + node.col_offset += self.offset[1] + if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or + self.isDocstring(node)): + self.futuresAllowed = False + self.nodeDepth += 1 + node._pyflakes_depth = self.nodeDepth + node._pyflakes_parent = parent + try: + handler = self.getNodeHandler(node.__class__) + handler(node) + finally: + self.nodeDepth -= 1 + + _getDoctestExamples = doctest.DocTestParser().get_examples + + def handleDoctests(self, node): + try: + if hasattr(node, 'docstring'): + docstring = node.docstring + + # This is just a reasonable guess. In Python 3.7, docstrings no + # longer have line numbers associated with them. This will be + # incorrect if there are empty lines between the beginning + # of the function and the docstring. + node_lineno = node.lineno + if hasattr(node, 'args'): + node_lineno = max([node_lineno] + + [arg.lineno for arg in node.args.args]) + else: + (docstring, node_lineno) = self.getDocstring(node.body[0]) + examples = docstring and self._getDoctestExamples(docstring) + except (ValueError, IndexError): + # e.g. line 6 of the docstring for has inconsistent + # leading whitespace: ... + return + if not examples: + return + + # Place doctest in module scope + saved_stack = self.scopeStack + self.scopeStack = [self.scopeStack[0]] + node_offset = self.offset or (0, 0) + self.pushScope(DoctestScope) + if '_' not in self.scopeStack[0]: + self.addBinding(None, Builtin('_')) + for example in examples: + try: + tree = ast.parse(example.source, "") + except SyntaxError: + e = sys.exc_info()[1] + if PYPY: + e.offset += 1 + position = (node_lineno + example.lineno + e.lineno, + example.indent + 4 + (e.offset or 0)) + self.report(messages.DoctestSyntaxError, node, position) + else: + self.offset = (node_offset[0] + node_lineno + example.lineno, + node_offset[1] + example.indent + 4) + self.handleChildren(tree) + self.offset = node_offset + self.popScope() + self.scopeStack = saved_stack + + @in_string_annotation + def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): + try: + tree = ast.parse(s) + except SyntaxError: + self.report(err, node, s) + return + + body = tree.body + if len(body) != 1 or not isinstance(body[0], ast.Expr): + self.report(err, node, s) + return + + parsed_annotation = tree.body[0].value + for descendant in ast.walk(parsed_annotation): + if ( + 'lineno' in descendant._attributes and + 'col_offset' in descendant._attributes + ): + descendant.lineno = ref_lineno + descendant.col_offset = ref_col_offset + + self.handleNode(parsed_annotation, node) + + @in_annotation + def handleAnnotation(self, annotation, node): + if isinstance(annotation, ast.Str): + # Defer handling forward annotation. + self.deferFunction(functools.partial( + self.handleStringAnnotation, + annotation.s, + node, + annotation.lineno, + annotation.col_offset, + messages.ForwardAnnotationSyntaxError, + )) + elif self.annotationsFutureEnabled: + fn = in_annotation(Checker.handleNode) + self.deferFunction(lambda: fn(self, annotation, node)) + else: + self.handleNode(annotation, node) + + def ignore(self, node): + pass + + # "stmt" type nodes + DELETE = PRINT = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = \ + ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \ + EXPR = ASSIGN = handleChildren + + PASS = ignore + + # "expr" type nodes + BOOLOP = UNARYOP = SET = \ + REPR = ATTRIBUTE = \ + STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren + + def SUBSCRIPT(self, node): + if _is_name_or_attr(node.value, 'Literal'): + with self._enter_annotation(AnnotationState.NONE): + self.handleChildren(node) + elif _is_name_or_attr(node.value, 'Annotated'): + self.handleNode(node.value, node) + + # py39+ + if isinstance(node.slice, ast.Tuple): + slice_tuple = node.slice + # = 1 + ): + with self._enter_annotation(): + self.handleNode(node.args[0], node) + + elif _is_typing(node.func, 'TypeVar', self.scopeStack): + + # TypeVar("T", "int", "str") + omit += ["args"] + annotated += [arg for arg in node.args[1:]] + + # TypeVar("T", bound="str") + omit += ["keywords"] + annotated += [k.value for k in node.keywords if k.arg == "bound"] + not_annotated += [ + (k, ["value"] if k.arg == "bound" else None) + for k in node.keywords + ] + + elif _is_typing(node.func, "TypedDict", self.scopeStack): + # TypedDict("a", {"a": int}) + if len(node.args) > 1 and isinstance(node.args[1], ast.Dict): + omit += ["args"] + annotated += node.args[1].values + not_annotated += [ + (arg, ["values"] if i == 1 else None) + for i, arg in enumerate(node.args) + ] + + # TypedDict("a", a=int) + omit += ["keywords"] + annotated += [k.value for k in node.keywords] + not_annotated += [(k, ["value"]) for k in node.keywords] + + elif _is_typing(node.func, "NamedTuple", self.scopeStack): + # NamedTuple("a", [("a", int)]) + if ( + len(node.args) > 1 and + isinstance(node.args[1], (ast.Tuple, ast.List)) and + all(isinstance(x, (ast.Tuple, ast.List)) and + len(x.elts) == 2 for x in node.args[1].elts) + ): + omit += ["args"] + annotated += [elt.elts[1] for elt in node.args[1].elts] + not_annotated += [(elt.elts[0], None) for elt in node.args[1].elts] + not_annotated += [ + (arg, ["elts"] if i == 1 else None) + for i, arg in enumerate(node.args) + ] + not_annotated += [(elt, "elts") for elt in node.args[1].elts] + + # NamedTuple("a", a=int) + omit += ["keywords"] + annotated += [k.value for k in node.keywords] + not_annotated += [(k, ["value"]) for k in node.keywords] + + if omit: + with self._enter_annotation(AnnotationState.NONE): + for na_node, na_omit in not_annotated: + self.handleChildren(na_node, omit=na_omit) + self.handleChildren(node, omit=omit) + + with self._enter_annotation(): + for annotated_node in annotated: + self.handleNode(annotated_node, node) + else: + self.handleChildren(node) + + def _handle_percent_format(self, node): + try: + placeholders = parse_percent_format(node.left.s) + except ValueError: + self.report( + messages.PercentFormatInvalidFormat, + node, + 'incomplete format', + ) + return + + named = set() + positional_count = 0 + positional = None + for _, placeholder in placeholders: + if placeholder is None: + continue + name, _, width, precision, conversion = placeholder + + if conversion == '%': + continue + + if conversion not in VALID_CONVERSIONS: + self.report( + messages.PercentFormatUnsupportedFormatCharacter, + node, + conversion, + ) + + if positional is None and conversion: + positional = name is None + + for part in (width, precision): + if part is not None and '*' in part: + if not positional: + self.report( + messages.PercentFormatStarRequiresSequence, + node, + ) + else: + positional_count += 1 + + if positional and name is not None: + self.report( + messages.PercentFormatMixedPositionalAndNamed, + node, + ) + return + elif not positional and name is None: + self.report( + messages.PercentFormatMixedPositionalAndNamed, + node, + ) + return + + if positional: + positional_count += 1 + else: + named.add(name) + + if ( + isinstance(node.right, (ast.List, ast.Tuple)) and + # does not have any *splats (py35+ feature) + not any( + isinstance(elt, getattr(ast, 'Starred', ())) + for elt in node.right.elts + ) + ): + substitution_count = len(node.right.elts) + if positional and positional_count != substitution_count: + self.report( + messages.PercentFormatPositionalCountMismatch, + node, + positional_count, + substitution_count, + ) + elif not positional: + self.report(messages.PercentFormatExpectedMapping, node) + + if ( + isinstance(node.right, ast.Dict) and + all(isinstance(k, ast.Str) for k in node.right.keys) + ): + if positional and positional_count > 1: + self.report(messages.PercentFormatExpectedSequence, node) + return + + substitution_keys = {k.s for k in node.right.keys} + extra_keys = substitution_keys - named + missing_keys = named - substitution_keys + if not positional and extra_keys: + self.report( + messages.PercentFormatExtraNamedArguments, + node, + ', '.join(sorted(extra_keys)), + ) + if not positional and missing_keys: + self.report( + messages.PercentFormatMissingArgument, + node, + ', '.join(sorted(missing_keys)), + ) + + def BINOP(self, node): + if ( + isinstance(node.op, ast.Mod) and + isinstance(node.left, ast.Str) + ): + self._handle_percent_format(node) + self.handleChildren(node) + + def STR(self, node): + if self._in_annotation: + fn = functools.partial( + self.handleStringAnnotation, + node.s, + node, + node.lineno, + node.col_offset, + messages.ForwardAnnotationSyntaxError, + ) + if self._in_deferred: + fn() + else: + self.deferFunction(fn) + + if PY38_PLUS: + def CONSTANT(self, node): + if isinstance(node.value, str): + return self.STR(node) + else: + NUM = BYTES = ELLIPSIS = CONSTANT = ignore + + # "slice" type nodes + SLICE = EXTSLICE = INDEX = handleChildren + + # expression contexts are node instances too, though being constants + LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore + + # same for operators + AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ + BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ + EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = \ + MATMULT = ignore + + def RAISE(self, node): + self.handleChildren(node) + + arg = get_raise_argument(node) + + if isinstance(arg, ast.Call): + if is_notimplemented_name_node(arg.func): + # Handle "raise NotImplemented(...)" + self.report(messages.RaiseNotImplemented, node) + elif is_notimplemented_name_node(arg): + # Handle "raise NotImplemented" + self.report(messages.RaiseNotImplemented, node) + + # additional node types + COMPREHENSION = KEYWORD = FORMATTEDVALUE = handleChildren + + _in_fstring = False + + def JOINEDSTR(self, node): + if ( + # the conversion / etc. flags are parsed as f-strings without + # placeholders + not self._in_fstring and + not any(isinstance(x, ast.FormattedValue) for x in node.values) + ): + self.report(messages.FStringMissingPlaceholders, node) + + self._in_fstring, orig = True, self._in_fstring + try: + self.handleChildren(node) + finally: + self._in_fstring = orig + + def DICT(self, node): + # Complain if there are duplicate keys with different values + # If they have the same value it's not going to cause potentially + # unexpected behaviour so we'll not complain. + keys = [ + convert_to_value(key) for key in node.keys + ] + + key_counts = counter(keys) + duplicate_keys = [ + key for key, count in key_counts.items() + if count > 1 + ] + + for key in duplicate_keys: + key_indices = [i for i, i_key in enumerate(keys) if i_key == key] + + values = counter( + convert_to_value(node.values[index]) + for index in key_indices + ) + if any(count == 1 for value, count in values.items()): + for key_index in key_indices: + key_node = node.keys[key_index] + if isinstance(key, VariableKey): + self.report(messages.MultiValueRepeatedKeyVariable, + key_node, + key.name) + else: + self.report( + messages.MultiValueRepeatedKeyLiteral, + key_node, + key, + ) + self.handleChildren(node) + + def IF(self, node): + if isinstance(node.test, ast.Tuple) and node.test.elts != []: + self.report(messages.IfTuple, node) + self.handleChildren(node) + + IFEXP = IF + + def ASSERT(self, node): + if isinstance(node.test, ast.Tuple) and node.test.elts != []: + self.report(messages.AssertTuple, node) + self.handleChildren(node) + + def GLOBAL(self, node): + """ + Keep track of globals declarations. + """ + global_scope_index = 1 if self._in_doctest() else 0 + global_scope = self.scopeStack[global_scope_index] + + # Ignore 'global' statement in global scope. + if self.scope is not global_scope: + + # One 'global' statement can bind multiple (comma-delimited) names. + for node_name in node.names: + node_value = Assignment(node_name, node) + + # Remove UndefinedName messages already reported for this name. + # TODO: if the global is not used in this scope, it does not + # become a globally defined name. See test_unused_global. + self.messages = [ + m for m in self.messages if not + isinstance(m, messages.UndefinedName) or + m.message_args[0] != node_name] + + # Bind name to global scope if it doesn't exist already. + global_scope.setdefault(node_name, node_value) + + # Bind name to non-global scopes, but as already "used". + node_value.used = (global_scope, node) + for scope in self.scopeStack[global_scope_index + 1:]: + scope[node_name] = node_value + + NONLOCAL = GLOBAL + + def GENERATOREXP(self, node): + self.pushScope(GeneratorScope) + self.handleChildren(node) + self.popScope() + + LISTCOMP = handleChildren if PY2 else GENERATOREXP + + DICTCOMP = SETCOMP = GENERATOREXP + + def NAME(self, node): + """ + Handle occurrence of Name (which can be a load/store/delete access.) + """ + # Locate the name in locals / function / globals scopes. + if isinstance(node.ctx, ast.Load): + self.handleNodeLoad(node) + if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and + isinstance(node._pyflakes_parent, ast.Call)): + # we are doing locals() call in current scope + self.scope.usesLocals = True + elif isinstance(node.ctx, ast.Store): + self.handleNodeStore(node) + elif PY2 and isinstance(node.ctx, ast.Param): + self.handleNodeStore(node) + elif isinstance(node.ctx, ast.Del): + self.handleNodeDelete(node) + else: + # Unknown context + raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) + + def CONTINUE(self, node): + # Walk the tree up until we see a loop (OK), a function or class + # definition (not OK), for 'continue', a finally block (not OK), or + # the top module scope (not OK) + n = node + while hasattr(n, '_pyflakes_parent'): + n, n_child = n._pyflakes_parent, n + if isinstance(n, LOOP_TYPES): + # Doesn't apply unless it's in the loop itself + if n_child not in n.orelse: + return + if isinstance(n, (ast.FunctionDef, ast.ClassDef)): + break + # Handle Try/TryFinally difference in Python < and >= 3.3 + if hasattr(n, 'finalbody') and isinstance(node, ast.Continue): + if n_child in n.finalbody and not PY38_PLUS: + self.report(messages.ContinueInFinally, node) + return + if isinstance(node, ast.Continue): + self.report(messages.ContinueOutsideLoop, node) + else: # ast.Break + self.report(messages.BreakOutsideLoop, node) + + BREAK = CONTINUE + + def RETURN(self, node): + if isinstance(self.scope, (ClassScope, ModuleScope)): + self.report(messages.ReturnOutsideFunction, node) + return + + if ( + node.value and + hasattr(self.scope, 'returnValue') and + not self.scope.returnValue + ): + self.scope.returnValue = node.value + self.handleNode(node.value, node) + + def YIELD(self, node): + if isinstance(self.scope, (ClassScope, ModuleScope)): + self.report(messages.YieldOutsideFunction, node) + return + + self.scope.isGenerator = True + self.handleNode(node.value, node) + + AWAIT = YIELDFROM = YIELD + + def FUNCTIONDEF(self, node): + for deco in node.decorator_list: + self.handleNode(deco, node) + self.LAMBDA(node) + self.addBinding(node, FunctionDefinition(node.name, node)) + # doctest does not process doctest within a doctest, + # or in nested functions. + if (self.withDoctest and + not self._in_doctest() and + not isinstance(self.scope, FunctionScope)): + self.deferFunction(lambda: self.handleDoctests(node)) + + ASYNCFUNCTIONDEF = FUNCTIONDEF + + def LAMBDA(self, node): + args = [] + annotations = [] + + if PY2: + def addArgs(arglist): + for arg in arglist: + if isinstance(arg, ast.Tuple): + addArgs(arg.elts) + else: + args.append(arg.id) + addArgs(node.args.args) + defaults = node.args.defaults + else: + if PY38_PLUS: + for arg in node.args.posonlyargs: + args.append(arg.arg) + annotations.append(arg.annotation) + for arg in node.args.args + node.args.kwonlyargs: + args.append(arg.arg) + annotations.append(arg.annotation) + defaults = node.args.defaults + node.args.kw_defaults + + # Only for Python3 FunctionDefs + is_py3_func = hasattr(node, 'returns') + + for arg_name in ('vararg', 'kwarg'): + wildcard = getattr(node.args, arg_name) + if not wildcard: + continue + args.append(wildcard if PY2 else wildcard.arg) + if is_py3_func: + if PY2: # Python 2.7 + argannotation = arg_name + 'annotation' + annotations.append(getattr(node.args, argannotation)) + else: # Python >= 3.4 + annotations.append(wildcard.annotation) + + if is_py3_func: + annotations.append(node.returns) + + if len(set(args)) < len(args): + for (idx, arg) in enumerate(args): + if arg in args[:idx]: + self.report(messages.DuplicateArgument, node, arg) + + for annotation in annotations: + self.handleAnnotation(annotation, node) + + for default in defaults: + self.handleNode(default, node) + + def runFunction(): + + self.pushScope() + + self.handleChildren(node, omit=['decorator_list', 'returns']) + + def checkUnusedAssignments(): + """ + Check to see if any assignments have not been used. + """ + for name, binding in self.scope.unusedAssignments(): + self.report(messages.UnusedVariable, binding.source, name) + self.deferAssignment(checkUnusedAssignments) + + if PY2: + def checkReturnWithArgumentInsideGenerator(): + """ + Check to see if there is any return statement with + arguments but the function is a generator. + """ + if self.scope.isGenerator and self.scope.returnValue: + self.report(messages.ReturnWithArgsInsideGenerator, + self.scope.returnValue) + self.deferAssignment(checkReturnWithArgumentInsideGenerator) + self.popScope() + + self.deferFunction(runFunction) + + def ARGUMENTS(self, node): + self.handleChildren(node, omit=('defaults', 'kw_defaults')) + if PY2: + scope_node = self.getScopeNode(node) + if node.vararg: + self.addBinding(node, Argument(node.vararg, scope_node)) + if node.kwarg: + self.addBinding(node, Argument(node.kwarg, scope_node)) + + def ARG(self, node): + self.addBinding(node, Argument(node.arg, self.getScopeNode(node))) + + def CLASSDEF(self, node): + """ + Check names used in a class definition, including its decorators, base + classes, and the body of its definition. Additionally, add its name to + the current scope. + """ + for deco in node.decorator_list: + self.handleNode(deco, node) + for baseNode in node.bases: + self.handleNode(baseNode, node) + if not PY2: + for keywordNode in node.keywords: + self.handleNode(keywordNode, node) + self.pushScope(ClassScope) + # doctest does not process doctest within a doctest + # classes within classes are processed. + if (self.withDoctest and + not self._in_doctest() and + not isinstance(self.scope, FunctionScope)): + self.deferFunction(lambda: self.handleDoctests(node)) + for stmt in node.body: + self.handleNode(stmt, node) + self.popScope() + self.addBinding(node, ClassDefinition(node.name, node)) + + def AUGASSIGN(self, node): + self.handleNodeLoad(node.target) + self.handleNode(node.value, node) + self.handleNode(node.target, node) + + def TUPLE(self, node): + if not PY2 and isinstance(node.ctx, ast.Store): + # Python 3 advanced tuple unpacking: a, *b, c = d. + # Only one starred expression is allowed, and no more than 1<<8 + # assignments are allowed before a stared expression. There is + # also a limit of 1<<24 expressions after the starred expression, + # which is impossible to test due to memory restrictions, but we + # add it here anyway + has_starred = False + star_loc = -1 + for i, n in enumerate(node.elts): + if isinstance(n, ast.Starred): + if has_starred: + self.report(messages.TwoStarredExpressions, node) + # The SyntaxError doesn't distinguish two from more + # than two. + break + has_starred = True + star_loc = i + if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24: + self.report(messages.TooManyExpressionsInStarredAssignment, node) + self.handleChildren(node) + + LIST = TUPLE + + def IMPORT(self, node): + for alias in node.names: + if '.' in alias.name and not alias.asname: + importation = SubmoduleImportation(alias.name, node) + else: + name = alias.asname or alias.name + importation = Importation(name, node, alias.name) + self.addBinding(node, importation) + + def IMPORTFROM(self, node): + if node.module == '__future__': + if not self.futuresAllowed: + self.report(messages.LateFutureImport, + node, [n.name for n in node.names]) + else: + self.futuresAllowed = False + + module = ('.' * node.level) + (node.module or '') + + for alias in node.names: + name = alias.asname or alias.name + if node.module == '__future__': + importation = FutureImportation(name, node, self.scope) + if alias.name not in __future__.all_feature_names: + self.report(messages.FutureFeatureNotDefined, + node, alias.name) + if alias.name == 'annotations': + self.annotationsFutureEnabled = True + elif alias.name == '*': + # Only Python 2, local import * is a SyntaxWarning + if not PY2 and not isinstance(self.scope, ModuleScope): + self.report(messages.ImportStarNotPermitted, + node, module) + continue + + self.scope.importStarred = True + self.report(messages.ImportStarUsed, node, module) + importation = StarImportation(module, node) + else: + importation = ImportationFrom(name, node, + module, alias.name) + self.addBinding(node, importation) + + def TRY(self, node): + handler_names = [] + # List the exception handlers + for i, handler in enumerate(node.handlers): + if isinstance(handler.type, ast.Tuple): + for exc_type in handler.type.elts: + handler_names.append(getNodeName(exc_type)) + elif handler.type: + handler_names.append(getNodeName(handler.type)) + + if handler.type is None and i < len(node.handlers) - 1: + self.report(messages.DefaultExceptNotLast, handler) + # Memorize the except handlers and process the body + self.exceptHandlers.append(handler_names) + for child in node.body: + self.handleNode(child, node) + self.exceptHandlers.pop() + # Process the other nodes: "except:", "else:", "finally:" + self.handleChildren(node, omit='body') + + TRYEXCEPT = TRY + + def EXCEPTHANDLER(self, node): + if PY2 or node.name is None: + self.handleChildren(node) + return + + # If the name already exists in the scope, modify state of existing + # binding. + if node.name in self.scope: + self.handleNodeStore(node) + + # 3.x: the name of the exception, which is not a Name node, but a + # simple string, creates a local that is only bound within the scope of + # the except: block. As such, temporarily remove the existing binding + # to more accurately determine if the name is used in the except: + # block. + + try: + prev_definition = self.scope.pop(node.name) + except KeyError: + prev_definition = None + + self.handleNodeStore(node) + self.handleChildren(node) + + # See discussion on https://github.com/PyCQA/pyflakes/pull/59 + + # We're removing the local name since it's being unbound after leaving + # the except: block and it's always unbound if the except: block is + # never entered. This will cause an "undefined name" error raised if + # the checked code tries to use the name afterwards. + # + # Unless it's been removed already. Then do nothing. + + try: + binding = self.scope.pop(node.name) + except KeyError: + pass + else: + if not binding.used: + self.report(messages.UnusedVariable, node, node.name) + + # Restore. + if prev_definition: + self.scope[node.name] = prev_definition + + def ANNASSIGN(self, node): + self.handleNode(node.target, node) + self.handleAnnotation(node.annotation, node) + if node.value: + # If the assignment has value, handle the *value* now. + self.handleNode(node.value, node) + + def COMPARE(self, node): + left = node.left + for op, right in zip(node.ops, node.comparators): + if ( + isinstance(op, (ast.Is, ast.IsNot)) and ( + _is_const_non_singleton(left) or + _is_const_non_singleton(right) + ) + ): + self.report(messages.IsLiteral, node) + left = right + + self.handleChildren(node) + + MATCH = MATCH_CASE = MATCHCLASS = MATCHOR = MATCHSEQUENCE = handleChildren + MATCHSINGLETON = MATCHVALUE = handleChildren + + def _match_target(self, node): + self.handleNodeStore(node) + self.handleChildren(node) + + MATCHAS = MATCHMAPPING = MATCHSTAR = _match_target diff --git a/.venv/lib/python3.8/site-packages/pyflakes/messages.py b/.venv/lib/python3.8/site-packages/pyflakes/messages.py new file mode 100644 index 0000000..1bb4ab0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/messages.py @@ -0,0 +1,371 @@ +""" +Provide the class Message and its subclasses. +""" + + +class Message(object): + message = '' + message_args = () + + def __init__(self, filename, loc): + self.filename = filename + self.lineno = loc.lineno + self.col = getattr(loc, 'col_offset', 0) + + def __str__(self): + return '%s:%s:%s %s' % (self.filename, self.lineno, self.col+1, + self.message % self.message_args) + + +class UnusedImport(Message): + message = '%r imported but unused' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class RedefinedWhileUnused(Message): + message = 'redefinition of unused %r from line %r' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class RedefinedInListComp(Message): + message = 'list comprehension redefines %r from line %r' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class ImportShadowedByLoopVar(Message): + message = 'import %r from line %r shadowed by loop variable' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class ImportStarNotPermitted(Message): + message = "'from %s import *' only allowed at module level" + + def __init__(self, filename, loc, modname): + Message.__init__(self, filename, loc) + self.message_args = (modname,) + + +class ImportStarUsed(Message): + message = "'from %s import *' used; unable to detect undefined names" + + def __init__(self, filename, loc, modname): + Message.__init__(self, filename, loc) + self.message_args = (modname,) + + +class ImportStarUsage(Message): + message = "%r may be undefined, or defined from star imports: %s" + + def __init__(self, filename, loc, name, from_list): + Message.__init__(self, filename, loc) + self.message_args = (name, from_list) + + +class UndefinedName(Message): + message = 'undefined name %r' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class DoctestSyntaxError(Message): + message = 'syntax error in doctest' + + def __init__(self, filename, loc, position=None): + Message.__init__(self, filename, loc) + if position: + (self.lineno, self.col) = position + self.message_args = () + + +class UndefinedExport(Message): + message = 'undefined name %r in __all__' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class UndefinedLocal(Message): + message = 'local variable %r {0} referenced before assignment' + + default = 'defined in enclosing scope on line %r' + builtin = 'defined as a builtin' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + if orig_loc is None: + self.message = self.message.format(self.builtin) + self.message_args = name + else: + self.message = self.message.format(self.default) + self.message_args = (name, orig_loc.lineno) + + +class DuplicateArgument(Message): + message = 'duplicate argument %r in function definition' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class MultiValueRepeatedKeyLiteral(Message): + message = 'dictionary key %r repeated with different values' + + def __init__(self, filename, loc, key): + Message.__init__(self, filename, loc) + self.message_args = (key,) + + +class MultiValueRepeatedKeyVariable(Message): + message = 'dictionary key variable %s repeated with different values' + + def __init__(self, filename, loc, key): + Message.__init__(self, filename, loc) + self.message_args = (key,) + + +class LateFutureImport(Message): + message = 'from __future__ imports must occur at the beginning of the file' + + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) + self.message_args = () + + +class FutureFeatureNotDefined(Message): + """An undefined __future__ feature name was imported.""" + message = 'future feature %s is not defined' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class UnusedVariable(Message): + """ + Indicates that a variable has been explicitly assigned to but not actually + used. + """ + message = 'local variable %r is assigned to but never used' + + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) + self.message_args = (names,) + + +class ReturnWithArgsInsideGenerator(Message): + """ + Indicates a return statement with arguments inside a generator. + """ + message = '\'return\' with argument inside generator' + + +class ReturnOutsideFunction(Message): + """ + Indicates a return statement outside of a function/method. + """ + message = '\'return\' outside function' + + +class YieldOutsideFunction(Message): + """ + Indicates a yield or yield from statement outside of a function/method. + """ + message = '\'yield\' outside function' + + +# For whatever reason, Python gives different error messages for these two. We +# match the Python error message exactly. +class ContinueOutsideLoop(Message): + """ + Indicates a continue statement outside of a while or for loop. + """ + message = '\'continue\' not properly in loop' + + +class BreakOutsideLoop(Message): + """ + Indicates a break statement outside of a while or for loop. + """ + message = '\'break\' outside loop' + + +class ContinueInFinally(Message): + """ + Indicates a continue statement in a finally block in a while or for loop. + """ + message = '\'continue\' not supported inside \'finally\' clause' + + +class DefaultExceptNotLast(Message): + """ + Indicates an except: block as not the last exception handler. + """ + message = 'default \'except:\' must be last' + + +class TwoStarredExpressions(Message): + """ + Two or more starred expressions in an assignment (a, *b, *c = d). + """ + message = 'two starred expressions in assignment' + + +class TooManyExpressionsInStarredAssignment(Message): + """ + Too many expressions in an assignment with star-unpacking + """ + message = 'too many expressions in star-unpacking assignment' + + +class IfTuple(Message): + """ + Conditional test is a non-empty tuple literal, which are always True. + """ + message = '\'if tuple literal\' is always true, perhaps remove accidental comma?' + + +class AssertTuple(Message): + """ + Assertion test is a non-empty tuple literal, which are always True. + """ + message = 'assertion is always true, perhaps remove parentheses?' + + +class ForwardAnnotationSyntaxError(Message): + message = 'syntax error in forward annotation %r' + + def __init__(self, filename, loc, annotation): + Message.__init__(self, filename, loc) + self.message_args = (annotation,) + + +class CommentAnnotationSyntaxError(Message): + message = 'syntax error in type comment %r' + + def __init__(self, filename, loc, annotation): + Message.__init__(self, filename, loc) + self.message_args = (annotation,) + + +class RaiseNotImplemented(Message): + message = "'raise NotImplemented' should be 'raise NotImplementedError'" + + +class InvalidPrintSyntax(Message): + message = 'use of >> is invalid with print function' + + +class IsLiteral(Message): + message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)' + + +class FStringMissingPlaceholders(Message): + message = 'f-string is missing placeholders' + + +class StringDotFormatExtraPositionalArguments(Message): + message = "'...'.format(...) has unused arguments at position(s): %s" + + def __init__(self, filename, loc, extra_positions): + Message.__init__(self, filename, loc) + self.message_args = (extra_positions,) + + +class StringDotFormatExtraNamedArguments(Message): + message = "'...'.format(...) has unused named argument(s): %s" + + def __init__(self, filename, loc, extra_keywords): + Message.__init__(self, filename, loc) + self.message_args = (extra_keywords,) + + +class StringDotFormatMissingArgument(Message): + message = "'...'.format(...) is missing argument(s) for placeholder(s): %s" + + def __init__(self, filename, loc, missing_arguments): + Message.__init__(self, filename, loc) + self.message_args = (missing_arguments,) + + +class StringDotFormatMixingAutomatic(Message): + message = "'...'.format(...) mixes automatic and manual numbering" + + +class StringDotFormatInvalidFormat(Message): + message = "'...'.format(...) has invalid format string: %s" + + def __init__(self, filename, loc, error): + Message.__init__(self, filename, loc) + self.message_args = (error,) + + +class PercentFormatInvalidFormat(Message): + message = "'...' %% ... has invalid format string: %s" + + def __init__(self, filename, loc, error): + Message.__init__(self, filename, loc) + self.message_args = (error,) + + +class PercentFormatMixedPositionalAndNamed(Message): + message = "'...' %% ... has mixed positional and named placeholders" + + +class PercentFormatUnsupportedFormatCharacter(Message): + message = "'...' %% ... has unsupported format character %r" + + def __init__(self, filename, loc, c): + Message.__init__(self, filename, loc) + self.message_args = (c,) + + +class PercentFormatPositionalCountMismatch(Message): + message = "'...' %% ... has %d placeholder(s) but %d substitution(s)" + + def __init__(self, filename, loc, n_placeholders, n_substitutions): + Message.__init__(self, filename, loc) + self.message_args = (n_placeholders, n_substitutions) + + +class PercentFormatExtraNamedArguments(Message): + message = "'...' %% ... has unused named argument(s): %s" + + def __init__(self, filename, loc, extra_keywords): + Message.__init__(self, filename, loc) + self.message_args = (extra_keywords,) + + +class PercentFormatMissingArgument(Message): + message = "'...' %% ... is missing argument(s) for placeholder(s): %s" + + def __init__(self, filename, loc, missing_arguments): + Message.__init__(self, filename, loc) + self.message_args = (missing_arguments,) + + +class PercentFormatExpectedMapping(Message): + message = "'...' %% ... expected mapping but got sequence" + + +class PercentFormatExpectedSequence(Message): + message = "'...' %% ... expected sequence but got mapping" + + +class PercentFormatStarRequiresSequence(Message): + message = "'...' %% ... `*` specifier requires sequence" diff --git a/.venv/lib/python3.8/site-packages/pyflakes/reporter.py b/.venv/lib/python3.8/site-packages/pyflakes/reporter.py new file mode 100644 index 0000000..0faef65 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/reporter.py @@ -0,0 +1,82 @@ +""" +Provide the Reporter class. +""" + +import re +import sys + + +class Reporter(object): + """ + Formats the results of pyflakes checks to users. + """ + + def __init__(self, warningStream, errorStream): + """ + Construct a L{Reporter}. + + @param warningStream: A file-like object where warnings will be + written to. The stream's C{write} method must accept unicode. + C{sys.stdout} is a good value. + @param errorStream: A file-like object where error output will be + written to. The stream's C{write} method must accept unicode. + C{sys.stderr} is a good value. + """ + self._stdout = warningStream + self._stderr = errorStream + + def unexpectedError(self, filename, msg): + """ + An unexpected error occurred trying to process C{filename}. + + @param filename: The path to a file that we could not process. + @ptype filename: C{unicode} + @param msg: A message explaining the problem. + @ptype msg: C{unicode} + """ + self._stderr.write("%s: %s\n" % (filename, msg)) + + def syntaxError(self, filename, msg, lineno, offset, text): + """ + There was a syntax error in C{filename}. + + @param filename: The path to the file with the syntax error. + @ptype filename: C{unicode} + @param msg: An explanation of the syntax error. + @ptype msg: C{unicode} + @param lineno: The line number where the syntax error occurred. + @ptype lineno: C{int} + @param offset: The column on which the syntax error occurred, or None. + @ptype offset: C{int} + @param text: The source code containing the syntax error. + @ptype text: C{unicode} + """ + line = text.splitlines()[-1] + if offset is not None: + if sys.version_info < (3, 8): + offset = offset - (len(text) - len(line)) + 1 + self._stderr.write('%s:%d:%d: %s\n' % + (filename, lineno, offset, msg)) + else: + self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg)) + self._stderr.write(line) + self._stderr.write('\n') + if offset is not None: + self._stderr.write(re.sub(r'\S', ' ', line[:offset - 1]) + + "^\n") + + def flake(self, message): + """ + pyflakes found something wrong with the code. + + @param: A L{pyflakes.messages.Message}. + """ + self._stdout.write(str(message)) + self._stdout.write('\n') + + +def _makeDefaultReporter(): + """ + Make a reporter that can be used when no reporter is specified. + """ + return Reporter(sys.stdout, sys.stderr) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/scripts/__init__.py b/.venv/lib/python3.8/site-packages/pyflakes/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pyflakes/scripts/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/scripts/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..470a7a1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/scripts/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/scripts/__pycache__/pyflakes.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/scripts/__pycache__/pyflakes.cpython-38.pyc new file mode 100644 index 0000000..44e91b8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/scripts/__pycache__/pyflakes.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/scripts/pyflakes.py b/.venv/lib/python3.8/site-packages/pyflakes/scripts/pyflakes.py new file mode 100644 index 0000000..4a18e79 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/scripts/pyflakes.py @@ -0,0 +1,8 @@ +""" +Implementation of the command-line I{pyflakes} tool. +""" +from __future__ import absolute_import + +# For backward compatibility +__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main'] +from pyflakes.api import check, checkPath, checkRecursive, iterSourceCode, main diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__init__.py b/.venv/lib/python3.8/site-packages/pyflakes/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..6887bec Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/harness.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/harness.cpython-38.pyc new file mode 100644 index 0000000..150e516 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/harness.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_api.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_api.cpython-38.pyc new file mode 100644 index 0000000..1e70c75 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_api.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_builtin.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_builtin.cpython-38.pyc new file mode 100644 index 0000000..bd40d34 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_builtin.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_checker.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_checker.cpython-38.pyc new file mode 100644 index 0000000..c846143 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_checker.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_code_segment.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_code_segment.cpython-38.pyc new file mode 100644 index 0000000..c58a01e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_code_segment.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_dict.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_dict.cpython-38.pyc new file mode 100644 index 0000000..810c615 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_dict.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_doctests.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_doctests.cpython-38.pyc new file mode 100644 index 0000000..07d1654 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_doctests.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_imports.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_imports.cpython-38.pyc new file mode 100644 index 0000000..07823bf Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_imports.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_is_literal.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_is_literal.cpython-38.pyc new file mode 100644 index 0000000..b48cf46 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_is_literal.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_match.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_match.cpython-38.pyc new file mode 100644 index 0000000..ba68f0f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_match.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_other.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_other.cpython-38.pyc new file mode 100644 index 0000000..a642b31 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_other.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_return_with_arguments_inside_generator.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_return_with_arguments_inside_generator.cpython-38.pyc new file mode 100644 index 0000000..c1dd7c1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_return_with_arguments_inside_generator.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_type_annotations.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_type_annotations.cpython-38.pyc new file mode 100644 index 0000000..8e9e180 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_type_annotations.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_undefined_names.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_undefined_names.cpython-38.pyc new file mode 100644 index 0000000..a98b50a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflakes/test/__pycache__/test_undefined_names.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/harness.py b/.venv/lib/python3.8/site-packages/pyflakes/test/harness.py new file mode 100644 index 0000000..b20ac79 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/harness.py @@ -0,0 +1,72 @@ +import ast +import textwrap +import unittest + +from pyflakes import checker + +__all__ = ['TestCase', 'skip', 'skipIf'] + +skip = unittest.skip +skipIf = unittest.skipIf + + +class TestCase(unittest.TestCase): + + withDoctest = False + + def flakes(self, input, *expectedOutputs, **kw): + tree = ast.parse(textwrap.dedent(input)) + file_tokens = checker.make_tokens(textwrap.dedent(input)) + if kw.get('is_segment'): + tree = tree.body[0] + kw.pop('is_segment') + w = checker.Checker( + tree, file_tokens=file_tokens, withDoctest=self.withDoctest, **kw + ) + outputs = [type(o) for o in w.messages] + expectedOutputs = list(expectedOutputs) + outputs.sort(key=lambda t: t.__name__) + expectedOutputs.sort(key=lambda t: t.__name__) + self.assertEqual(outputs, expectedOutputs, '''\ +for input: +%s +expected outputs: +%r +but got: +%s''' % (input, expectedOutputs, '\n'.join([str(o) for o in w.messages]))) + return w + + if not hasattr(unittest.TestCase, 'assertIs'): + + def assertIs(self, expr1, expr2, msg=None): + if expr1 is not expr2: + self.fail(msg or '%r is not %r' % (expr1, expr2)) + + if not hasattr(unittest.TestCase, 'assertIsInstance'): + + def assertIsInstance(self, obj, cls, msg=None): + """Same as self.assertTrue(isinstance(obj, cls)).""" + if not isinstance(obj, cls): + self.fail(msg or '%r is not an instance of %r' % (obj, cls)) + + if not hasattr(unittest.TestCase, 'assertNotIsInstance'): + + def assertNotIsInstance(self, obj, cls, msg=None): + """Same as self.assertFalse(isinstance(obj, cls)).""" + if isinstance(obj, cls): + self.fail(msg or '%r is an instance of %r' % (obj, cls)) + + if not hasattr(unittest.TestCase, 'assertIn'): + + def assertIn(self, member, container, msg=None): + """Just like self.assertTrue(a in b).""" + if member not in container: + self.fail(msg or '%r not found in %r' % (member, container)) + + if not hasattr(unittest.TestCase, 'assertNotIn'): + + def assertNotIn(self, member, container, msg=None): + """Just like self.assertTrue(a not in b).""" + if member in container: + self.fail(msg or + '%r unexpectedly found in %r' % (member, container)) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_api.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_api.py new file mode 100644 index 0000000..2c1cf19 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_api.py @@ -0,0 +1,845 @@ +""" +Tests for L{pyflakes.scripts.pyflakes}. +""" + +import contextlib +import os +import sys +import shutil +import subprocess +import tempfile + +from pyflakes.messages import UnusedImport +from pyflakes.reporter import Reporter +from pyflakes.api import ( + main, + checkPath, + checkRecursive, + iterSourceCode, +) +from pyflakes.test.harness import TestCase, skipIf + +if sys.version_info < (3,): + from cStringIO import StringIO +else: + from io import StringIO + unichr = chr + +try: + sys.pypy_version_info + PYPY = True +except AttributeError: + PYPY = False + +try: + WindowsError + WIN = True +except NameError: + WIN = False + +ERROR_HAS_COL_NUM = ERROR_HAS_LAST_LINE = sys.version_info >= (3, 2) or PYPY + + +def withStderrTo(stderr, f, *args, **kwargs): + """ + Call C{f} with C{sys.stderr} redirected to C{stderr}. + """ + (outer, sys.stderr) = (sys.stderr, stderr) + try: + return f(*args, **kwargs) + finally: + sys.stderr = outer + + +class Node(object): + """ + Mock an AST node. + """ + def __init__(self, lineno, col_offset=0): + self.lineno = lineno + self.col_offset = col_offset + + +class SysStreamCapturing(object): + + """ + Context manager capturing sys.stdin, sys.stdout and sys.stderr. + + The file handles are replaced with a StringIO object. + On environments that support it, the StringIO object uses newlines + set to os.linesep. Otherwise newlines are converted from \\n to + os.linesep during __exit__. + """ + + def _create_StringIO(self, buffer=None): + # Python 3 has a newline argument + try: + return StringIO(buffer, newline=os.linesep) + except TypeError: + self._newline = True + # Python 2 creates an input only stream when buffer is not None + if buffer is None: + return StringIO() + else: + return StringIO(buffer) + + def __init__(self, stdin): + self._newline = False + self._stdin = self._create_StringIO(stdin or '') + + def __enter__(self): + self._orig_stdin = sys.stdin + self._orig_stdout = sys.stdout + self._orig_stderr = sys.stderr + + sys.stdin = self._stdin + sys.stdout = self._stdout_stringio = self._create_StringIO() + sys.stderr = self._stderr_stringio = self._create_StringIO() + + return self + + def __exit__(self, *args): + self.output = self._stdout_stringio.getvalue() + self.error = self._stderr_stringio.getvalue() + + if self._newline and os.linesep != '\n': + self.output = self.output.replace('\n', os.linesep) + self.error = self.error.replace('\n', os.linesep) + + sys.stdin = self._orig_stdin + sys.stdout = self._orig_stdout + sys.stderr = self._orig_stderr + + +class LoggingReporter(object): + """ + Implementation of Reporter that just appends any error to a list. + """ + + def __init__(self, log): + """ + Construct a C{LoggingReporter}. + + @param log: A list to append log messages to. + """ + self.log = log + + def flake(self, message): + self.log.append(('flake', str(message))) + + def unexpectedError(self, filename, message): + self.log.append(('unexpectedError', filename, message)) + + def syntaxError(self, filename, msg, lineno, offset, line): + self.log.append(('syntaxError', filename, msg, lineno, offset, line)) + + +class TestIterSourceCode(TestCase): + """ + Tests for L{iterSourceCode}. + """ + + def setUp(self): + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def makeEmptyFile(self, *parts): + assert parts + fpath = os.path.join(self.tempdir, *parts) + open(fpath, 'a').close() + return fpath + + def test_emptyDirectory(self): + """ + There are no Python files in an empty directory. + """ + self.assertEqual(list(iterSourceCode([self.tempdir])), []) + + def test_singleFile(self): + """ + If the directory contains one Python file, C{iterSourceCode} will find + it. + """ + childpath = self.makeEmptyFile('foo.py') + self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath]) + + def test_onlyPythonSource(self): + """ + Files that are not Python source files are not included. + """ + self.makeEmptyFile('foo.pyc') + self.assertEqual(list(iterSourceCode([self.tempdir])), []) + + def test_recurses(self): + """ + If the Python files are hidden deep down in child directories, we will + find them. + """ + os.mkdir(os.path.join(self.tempdir, 'foo')) + apath = self.makeEmptyFile('foo', 'a.py') + self.makeEmptyFile('foo', 'a.py~') + os.mkdir(os.path.join(self.tempdir, 'bar')) + bpath = self.makeEmptyFile('bar', 'b.py') + cpath = self.makeEmptyFile('c.py') + self.assertEqual( + sorted(iterSourceCode([self.tempdir])), + sorted([apath, bpath, cpath])) + + def test_shebang(self): + """ + Find Python files that don't end with `.py`, but contain a Python + shebang. + """ + python = os.path.join(self.tempdir, 'a') + with open(python, 'w') as fd: + fd.write('#!/usr/bin/env python\n') + + self.makeEmptyFile('b') + + with open(os.path.join(self.tempdir, 'c'), 'w') as fd: + fd.write('hello\nworld\n') + + python2 = os.path.join(self.tempdir, 'd') + with open(python2, 'w') as fd: + fd.write('#!/usr/bin/env python2\n') + + python3 = os.path.join(self.tempdir, 'e') + with open(python3, 'w') as fd: + fd.write('#!/usr/bin/env python3\n') + + pythonw = os.path.join(self.tempdir, 'f') + with open(pythonw, 'w') as fd: + fd.write('#!/usr/bin/env pythonw\n') + + python3args = os.path.join(self.tempdir, 'g') + with open(python3args, 'w') as fd: + fd.write('#!/usr/bin/python3 -u\n') + + python2u = os.path.join(self.tempdir, 'h') + with open(python2u, 'w') as fd: + fd.write('#!/usr/bin/python2u\n') + + python3d = os.path.join(self.tempdir, 'i') + with open(python3d, 'w') as fd: + fd.write('#!/usr/local/bin/python3d\n') + + python38m = os.path.join(self.tempdir, 'j') + with open(python38m, 'w') as fd: + fd.write('#! /usr/bin/env python3.8m\n') + + python27 = os.path.join(self.tempdir, 'k') + with open(python27, 'w') as fd: + fd.write('#!/usr/bin/python2.7 \n') + + # Should NOT be treated as Python source + notfirst = os.path.join(self.tempdir, 'l') + with open(notfirst, 'w') as fd: + fd.write('#!/bin/sh\n#!/usr/bin/python\n') + + self.assertEqual( + sorted(iterSourceCode([self.tempdir])), + sorted([python, python2, python3, pythonw, python3args, python2u, + python3d, python38m, python27])) + + def test_multipleDirectories(self): + """ + L{iterSourceCode} can be given multiple directories. It will recurse + into each of them. + """ + foopath = os.path.join(self.tempdir, 'foo') + barpath = os.path.join(self.tempdir, 'bar') + os.mkdir(foopath) + apath = self.makeEmptyFile('foo', 'a.py') + os.mkdir(barpath) + bpath = self.makeEmptyFile('bar', 'b.py') + self.assertEqual( + sorted(iterSourceCode([foopath, barpath])), + sorted([apath, bpath])) + + def test_explicitFiles(self): + """ + If one of the paths given to L{iterSourceCode} is not a directory but + a file, it will include that in its output. + """ + epath = self.makeEmptyFile('e.py') + self.assertEqual(list(iterSourceCode([epath])), + [epath]) + + +class TestReporter(TestCase): + """ + Tests for L{Reporter}. + """ + + def test_syntaxError(self): + """ + C{syntaxError} reports that there was a syntax error in the source + file. It reports to the error stream and includes the filename, line + number, error message, actual line of source and a caret pointing to + where the error is. + """ + err = StringIO() + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, + 8 if sys.version_info >= (3, 8) else 7, + 'bad line of source') + self.assertEqual( + ("foo.py:3:8: a problem\n" + "bad line of source\n" + " ^\n"), + err.getvalue()) + + def test_syntaxErrorNoOffset(self): + """ + C{syntaxError} doesn't include a caret pointing to the error if + C{offset} is passed as C{None}. + """ + err = StringIO() + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, None, + 'bad line of source') + self.assertEqual( + ("foo.py:3: a problem\n" + "bad line of source\n"), + err.getvalue()) + + def test_multiLineSyntaxError(self): + """ + If there's a multi-line syntax error, then we only report the last + line. The offset is adjusted so that it is relative to the start of + the last line. + """ + err = StringIO() + lines = [ + 'bad line of source', + 'more bad lines of source', + ] + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7, + '\n'.join(lines)) + column = 25 if sys.version_info >= (3, 8) else 7 + self.assertEqual( + ("foo.py:3:%d: a problem\n" % column + + lines[-1] + "\n" + + " " * (column - 1) + "^\n"), + err.getvalue()) + + def test_unexpectedError(self): + """ + C{unexpectedError} reports an error processing a source file. + """ + err = StringIO() + reporter = Reporter(None, err) + reporter.unexpectedError('source.py', 'error message') + self.assertEqual('source.py: error message\n', err.getvalue()) + + def test_flake(self): + """ + C{flake} reports a code warning from Pyflakes. It is exactly the + str() of a L{pyflakes.messages.Message}. + """ + out = StringIO() + reporter = Reporter(out, None) + message = UnusedImport('foo.py', Node(42), 'bar') + reporter.flake(message) + self.assertEqual(out.getvalue(), "%s\n" % (message,)) + + +class CheckTests(TestCase): + """ + Tests for L{check} and L{checkPath} which check a file for flakes. + """ + + @contextlib.contextmanager + def makeTempFile(self, content): + """ + Make a temporary file containing C{content} and return a path to it. + """ + fd, name = tempfile.mkstemp() + try: + with os.fdopen(fd, 'wb') as f: + if not hasattr(content, 'decode'): + content = content.encode('ascii') + f.write(content) + yield name + finally: + os.remove(name) + + def assertHasErrors(self, path, errorList): + """ + Assert that C{path} causes errors. + + @param path: A path to a file to check. + @param errorList: A list of errors expected to be printed to stderr. + """ + err = StringIO() + count = withStderrTo(err, checkPath, path) + self.assertEqual( + (count, err.getvalue()), (len(errorList), ''.join(errorList))) + + def getErrors(self, path): + """ + Get any warnings or errors reported by pyflakes for the file at C{path}. + + @param path: The path to a Python file on disk that pyflakes will check. + @return: C{(count, log)}, where C{count} is the number of warnings or + errors generated, and log is a list of those warnings, presented + as structured data. See L{LoggingReporter} for more details. + """ + log = [] + reporter = LoggingReporter(log) + count = checkPath(path, reporter) + return count, log + + def test_legacyScript(self): + from pyflakes.scripts import pyflakes as script_pyflakes + self.assertIs(script_pyflakes.checkPath, checkPath) + + def test_missingTrailingNewline(self): + """ + Source which doesn't end with a newline shouldn't cause any + exception to be raised nor an error indicator to be returned by + L{check}. + """ + with self.makeTempFile("def foo():\n\tpass\n\t") as fName: + self.assertHasErrors(fName, []) + + def test_checkPathNonExisting(self): + """ + L{checkPath} handles non-existing files. + """ + count, errors = self.getErrors('extremo') + self.assertEqual(count, 1) + self.assertEqual( + errors, + [('unexpectedError', 'extremo', 'No such file or directory')]) + + def test_multilineSyntaxError(self): + """ + Source which includes a syntax error which results in the raised + L{SyntaxError.text} containing multiple lines of source are reported + with only the last line of that source. + """ + source = """\ +def foo(): + ''' + +def bar(): + pass + +def baz(): + '''quux''' +""" + + # Sanity check - SyntaxError.text should be multiple lines, if it + # isn't, something this test was unprepared for has happened. + def evaluate(source): + exec(source) + try: + evaluate(source) + except SyntaxError: + e = sys.exc_info()[1] + if not PYPY and sys.version_info < (3, 10): + self.assertTrue(e.text.count('\n') > 1) + else: + self.fail() + + with self.makeTempFile(source) as sourcePath: + if PYPY: + message = 'end of file (EOF) while scanning triple-quoted string literal' + elif sys.version_info >= (3, 10): + message = 'unterminated triple-quoted string literal (detected at line 8)' # noqa: E501 + else: + message = 'invalid syntax' + + if sys.version_info >= (3, 10): + column = 12 + elif sys.version_info >= (3, 8): + column = 8 + else: + column = 11 + self.assertHasErrors( + sourcePath, + ["""\ +%s:8:%d: %s + '''quux''' +%s^ +""" % (sourcePath, column, message, ' ' * (column - 1))]) + + def test_eofSyntaxError(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + with self.makeTempFile("def foo(") as sourcePath: + if PYPY: + msg = 'parenthesis is never closed' + elif sys.version_info >= (3, 10): + msg = "'(' was never closed" + else: + msg = 'unexpected EOF while parsing' + + if PYPY: + column = 7 + elif sys.version_info >= (3, 10): + column = 8 + else: + column = 9 + + spaces = ' ' * (column - 1) + expected = '{}:1:{}: {}\ndef foo(\n{}^\n'.format( + sourcePath, column, msg, spaces + ) + + self.assertHasErrors(sourcePath, [expected]) + + def test_eofSyntaxErrorWithTab(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + with self.makeTempFile("if True:\n\tfoo =") as sourcePath: + column = 6 if PYPY else 7 + last_line = '\t ^' if PYPY else '\t ^' + + self.assertHasErrors( + sourcePath, + ["""\ +%s:2:%s: invalid syntax +\tfoo = +%s +""" % (sourcePath, column, last_line)]) + + def test_nonDefaultFollowsDefaultSyntaxError(self): + """ + Source which has a non-default argument following a default argument + should include the line number of the syntax error. However these + exceptions do not include an offset. + """ + source = """\ +def foo(bar=baz, bax): + pass +""" + with self.makeTempFile(source) as sourcePath: + if ERROR_HAS_LAST_LINE: + if PYPY: + column = 7 + elif sys.version_info >= (3, 10): + column = 18 + elif sys.version_info >= (3, 9): + column = 21 + elif sys.version_info >= (3, 8): + column = 9 + else: + column = 8 + last_line = ' ' * (column - 1) + '^\n' + columnstr = '%d:' % column + else: + last_line = columnstr = '' + self.assertHasErrors( + sourcePath, + ["""\ +%s:1:%s non-default argument follows default argument +def foo(bar=baz, bax): +%s""" % (sourcePath, columnstr, last_line)]) + + def test_nonKeywordAfterKeywordSyntaxError(self): + """ + Source which has a non-keyword argument after a keyword argument should + include the line number of the syntax error. However these exceptions + do not include an offset. + """ + source = """\ +foo(bar=baz, bax) +""" + with self.makeTempFile(source) as sourcePath: + if ERROR_HAS_LAST_LINE: + if PYPY: + column = 12 + elif sys.version_info >= (3, 9): + column = 17 + elif sys.version_info >= (3, 8): + column = 14 + else: + column = 13 + last_line = ' ' * (column - 1) + '^\n' + columnstr = '%d:' % column + else: + last_line = columnstr = '' + + if sys.version_info >= (3, 5): + message = 'positional argument follows keyword argument' + else: + message = 'non-keyword arg after keyword arg' + + self.assertHasErrors( + sourcePath, + ["""\ +%s:1:%s %s +foo(bar=baz, bax) +%s""" % (sourcePath, columnstr, message, last_line)]) + + def test_invalidEscape(self): + """ + The invalid escape syntax raises ValueError in Python 2 + """ + ver = sys.version_info + # ValueError: invalid \x escape + with self.makeTempFile(r"foo = '\xyz'") as sourcePath: + if ver < (3,): + decoding_error = "%s: problem decoding source\n" % (sourcePath,) + else: + position_end = 1 + if PYPY: + column = 5 + elif ver >= (3, 9): + column = 13 + else: + column = 7 + # Column has been "fixed" since 3.2.4 and 3.3.1 + if ver < (3, 2, 4) or ver[:3] == (3, 3, 0): + position_end = 2 + + if ERROR_HAS_LAST_LINE: + last_line = '%s^\n' % (' ' * (column - 1)) + else: + last_line = '' + + decoding_error = """\ +%s:1:%d: (unicode error) 'unicodeescape' codec can't decode bytes \ +in position 0-%d: truncated \\xXX escape +foo = '\\xyz' +%s""" % (sourcePath, column, position_end, last_line) + + self.assertHasErrors( + sourcePath, [decoding_error]) + + @skipIf(sys.platform == 'win32', 'unsupported on Windows') + def test_permissionDenied(self): + """ + If the source file is not readable, this is reported on standard + error. + """ + if os.getuid() == 0: + self.skipTest('root user can access all files regardless of ' + 'permissions') + with self.makeTempFile('') as sourcePath: + os.chmod(sourcePath, 0) + count, errors = self.getErrors(sourcePath) + self.assertEqual(count, 1) + self.assertEqual( + errors, + [('unexpectedError', sourcePath, "Permission denied")]) + + def test_pyflakesWarning(self): + """ + If the source file has a pyflakes warning, this is reported as a + 'flake'. + """ + with self.makeTempFile("import foo") as sourcePath: + count, errors = self.getErrors(sourcePath) + self.assertEqual(count, 1) + self.assertEqual( + errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))]) + + def test_encodedFileUTF8(self): + """ + If source file declares the correct encoding, no error is reported. + """ + SNOWMAN = unichr(0x2603) + source = ("""\ +# coding: utf-8 +x = "%s" +""" % SNOWMAN).encode('utf-8') + with self.makeTempFile(source) as sourcePath: + self.assertHasErrors(sourcePath, []) + + def test_CRLFLineEndings(self): + """ + Source files with Windows CR LF line endings are parsed successfully. + """ + with self.makeTempFile("x = 42\r\n") as sourcePath: + self.assertHasErrors(sourcePath, []) + + def test_misencodedFileUTF8(self): + """ + If a source file contains bytes which cannot be decoded, this is + reported on stderr. + """ + SNOWMAN = unichr(0x2603) + source = ("""\ +# coding: ascii +x = "%s" +""" % SNOWMAN).encode('utf-8') + with self.makeTempFile(source) as sourcePath: + if PYPY and sys.version_info < (3, ): + message = ('\'ascii\' codec can\'t decode byte 0xe2 ' + 'in position 21: ordinal not in range(128)') + result = """\ +%s:0:0: %s +x = "\xe2\x98\x83" + ^\n""" % (sourcePath, message) + + else: + message = 'problem decoding source' + result = "%s: problem decoding source\n" % (sourcePath,) + + self.assertHasErrors( + sourcePath, [result]) + + def test_misencodedFileUTF16(self): + """ + If a source file contains bytes which cannot be decoded, this is + reported on stderr. + """ + SNOWMAN = unichr(0x2603) + source = ("""\ +# coding: ascii +x = "%s" +""" % SNOWMAN).encode('utf-16') + with self.makeTempFile(source) as sourcePath: + self.assertHasErrors( + sourcePath, ["%s: problem decoding source\n" % (sourcePath,)]) + + def test_checkRecursive(self): + """ + L{checkRecursive} descends into each directory, finding Python files + and reporting problems. + """ + tempdir = tempfile.mkdtemp() + try: + os.mkdir(os.path.join(tempdir, 'foo')) + file1 = os.path.join(tempdir, 'foo', 'bar.py') + with open(file1, 'wb') as fd: + fd.write("import baz\n".encode('ascii')) + file2 = os.path.join(tempdir, 'baz.py') + with open(file2, 'wb') as fd: + fd.write("import contraband".encode('ascii')) + log = [] + reporter = LoggingReporter(log) + warnings = checkRecursive([tempdir], reporter) + self.assertEqual(warnings, 2) + self.assertEqual( + sorted(log), + sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))), + ('flake', + str(UnusedImport(file2, Node(1), 'contraband')))])) + finally: + shutil.rmtree(tempdir) + + +class IntegrationTests(TestCase): + """ + Tests of the pyflakes script that actually spawn the script. + """ + def setUp(self): + self.tempdir = tempfile.mkdtemp() + self.tempfilepath = os.path.join(self.tempdir, 'temp') + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def getPyflakesBinary(self): + """ + Return the path to the pyflakes binary. + """ + import pyflakes + package_dir = os.path.dirname(pyflakes.__file__) + return os.path.join(package_dir, '..', 'bin', 'pyflakes') + + def runPyflakes(self, paths, stdin=None): + """ + Launch a subprocess running C{pyflakes}. + + @param paths: Command-line arguments to pass to pyflakes. + @param stdin: Text to use as stdin. + @return: C{(returncode, stdout, stderr)} of the completed pyflakes + process. + """ + env = dict(os.environ) + env['PYTHONPATH'] = os.pathsep.join(sys.path) + command = [sys.executable, self.getPyflakesBinary()] + command.extend(paths) + if stdin: + p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = p.communicate(stdin.encode('ascii')) + else: + p = subprocess.Popen(command, env=env, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = p.communicate() + rv = p.wait() + if sys.version_info >= (3,): + stdout = stdout.decode('utf-8') + stderr = stderr.decode('utf-8') + return (stdout, stderr, rv) + + def test_goodFile(self): + """ + When a Python source file is all good, the return code is zero and no + messages are printed to either stdout or stderr. + """ + open(self.tempfilepath, 'a').close() + d = self.runPyflakes([self.tempfilepath]) + self.assertEqual(d, ('', '', 0)) + + def test_fileWithFlakes(self): + """ + When a Python source file has warnings, the return code is non-zero + and the warnings are printed to stdout. + """ + with open(self.tempfilepath, 'wb') as fd: + fd.write("import contraband\n".encode('ascii')) + d = self.runPyflakes([self.tempfilepath]) + expected = UnusedImport(self.tempfilepath, Node(1), 'contraband') + self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1)) + + def test_errors_io(self): + """ + When pyflakes finds errors with the files it's given, (if they don't + exist, say), then the return code is non-zero and the errors are + printed to stderr. + """ + d = self.runPyflakes([self.tempfilepath]) + error_msg = '%s: No such file or directory%s' % (self.tempfilepath, + os.linesep) + self.assertEqual(d, ('', error_msg, 1)) + + def test_errors_syntax(self): + """ + When pyflakes finds errors with the files it's given, (if they don't + exist, say), then the return code is non-zero and the errors are + printed to stderr. + """ + with open(self.tempfilepath, 'wb') as fd: + fd.write("import".encode('ascii')) + d = self.runPyflakes([self.tempfilepath]) + error_msg = '{0}:1:{2}: invalid syntax{1}import{1} {3}^{1}'.format( + self.tempfilepath, os.linesep, 6 if PYPY else 7, '' if PYPY else ' ') + self.assertEqual(d, ('', error_msg, 1)) + + def test_readFromStdin(self): + """ + If no arguments are passed to C{pyflakes} then it reads from stdin. + """ + d = self.runPyflakes([], stdin='import contraband') + expected = UnusedImport('', Node(1), 'contraband') + self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1)) + + +class TestMain(IntegrationTests): + """ + Tests of the pyflakes main function. + """ + def runPyflakes(self, paths, stdin=None): + try: + with SysStreamCapturing(stdin) as capture: + main(args=paths) + except SystemExit as e: + self.assertIsInstance(e.code, bool) + rv = int(e.code) + return (capture.output, capture.error, rv) + else: + raise RuntimeError('SystemExit not raised') diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_builtin.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_builtin.py new file mode 100644 index 0000000..7150ddb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_builtin.py @@ -0,0 +1,41 @@ +""" +Tests for detecting redefinition of builtins. +""" +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skipIf + + +class TestBuiltins(TestCase): + + def test_builtin_unbound_local(self): + self.flakes(''' + def foo(): + a = range(1, 10) + range = a + return range + + foo() + + print(range) + ''', m.UndefinedLocal) + + def test_global_shadowing_builtin(self): + self.flakes(''' + def f(): + global range + range = None + print(range) + + f() + ''') + + @skipIf(version_info >= (3,), 'not an UnboundLocalError in Python 3') + def test_builtin_in_comprehension(self): + self.flakes(''' + def f(): + [range for range in range(1, 10)] + + f() + ''', m.UndefinedLocal) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_checker.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_checker.py new file mode 100644 index 0000000..b527572 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_checker.py @@ -0,0 +1,186 @@ +import ast +import sys + +from pyflakes import checker +from pyflakes.test.harness import TestCase, skipIf + + +class TypeableVisitorTests(TestCase): + """ + Tests of L{_TypeableVisitor} + """ + + @staticmethod + def _run_visitor(s): + """ + Run L{_TypeableVisitor} on the parsed source and return the visitor. + """ + tree = ast.parse(s) + visitor = checker._TypeableVisitor() + visitor.visit(tree) + return visitor + + def test_node_types(self): + """ + Test that the typeable node types are collected + """ + visitor = self._run_visitor( + """\ +x = 1 # assignment +for x in range(1): pass # for loop +def f(): pass # function definition +with a as b: pass # with statement +""" + ) + self.assertEqual(visitor.typeable_lines, [1, 2, 3, 4]) + self.assertIsInstance(visitor.typeable_nodes[1], ast.Assign) + self.assertIsInstance(visitor.typeable_nodes[2], ast.For) + self.assertIsInstance(visitor.typeable_nodes[3], ast.FunctionDef) + self.assertIsInstance(visitor.typeable_nodes[4], ast.With) + + def test_visitor_recurses(self): + """ + Test the common pitfall of missing `generic_visit` in visitors by + ensuring that nested nodes are reported + """ + visitor = self._run_visitor( + """\ +def f(): + x = 1 +""" + ) + self.assertEqual(visitor.typeable_lines, [1, 2]) + self.assertIsInstance(visitor.typeable_nodes[1], ast.FunctionDef) + self.assertIsInstance(visitor.typeable_nodes[2], ast.Assign) + + @skipIf(sys.version_info < (3, 5), 'async syntax introduced in py35') + def test_py35_node_types(self): + """ + Test that the PEP 492 node types are collected + """ + visitor = self._run_visitor( + """\ +async def f(): # async def + async for x in y: pass # async for + async with a as b: pass # async with +""" + ) + self.assertEqual(visitor.typeable_lines, [1, 2, 3]) + self.assertIsInstance(visitor.typeable_nodes[1], ast.AsyncFunctionDef) + self.assertIsInstance(visitor.typeable_nodes[2], ast.AsyncFor) + self.assertIsInstance(visitor.typeable_nodes[3], ast.AsyncWith) + + def test_last_node_wins(self): + """ + Test that when two typeable nodes are present on a line, the last + typeable one wins. + """ + visitor = self._run_visitor('x = 1; y = 1') + # detected both assignable nodes + self.assertEqual(visitor.typeable_lines, [1, 1]) + # but the assignment to `y` wins + self.assertEqual(visitor.typeable_nodes[1].targets[0].id, 'y') + + +class CollectTypeCommentsTests(TestCase): + """ + Tests of L{_collect_type_comments} + """ + + @staticmethod + def _collect(s): + """ + Run L{_collect_type_comments} on the parsed source and return the + mapping from nodes to comments. The return value is converted to + a set: {(node_type, tuple of comments), ...} + """ + tree = ast.parse(s) + tokens = checker.make_tokens(s) + ret = checker._collect_type_comments(tree, tokens) + return {(type(k), tuple(s for _, s in v)) for k, v in ret.items()} + + def test_bytes(self): + """ + Test that the function works for binary source + """ + ret = self._collect(b'x = 1 # type: int') + self.assertSetEqual(ret, {(ast.Assign, ('# type: int',))}) + + def test_text(self): + """ + Test that the function works for text source + """ + ret = self._collect(u'x = 1 # type: int') + self.assertEqual(ret, {(ast.Assign, ('# type: int',))}) + + def test_non_type_comment_ignored(self): + """ + Test that a non-type comment is ignored + """ + ret = self._collect('x = 1 # noqa') + self.assertSetEqual(ret, set()) + + def test_type_comment_before_typeable(self): + """ + Test that a type comment before something typeable is ignored. + """ + ret = self._collect('# type: int\nx = 1') + self.assertSetEqual(ret, set()) + + def test_type_ignore_comment_ignored(self): + """ + Test that `# type: ignore` comments are not collected. + """ + ret = self._collect('x = 1 # type: ignore') + self.assertSetEqual(ret, set()) + + def test_type_ignore_with_other_things_ignored(self): + """ + Test that `# type: ignore` comments with more content are also not + collected. + """ + ret = self._collect('x = 1 # type: ignore # noqa') + self.assertSetEqual(ret, set()) + ret = self._collect('x = 1 #type:ignore#noqa') + self.assertSetEqual(ret, set()) + + def test_type_comment_with_extra_still_collected(self): + ret = self._collect('x = 1 # type: int # noqa') + self.assertSetEqual(ret, {(ast.Assign, ('# type: int # noqa',))}) + + def test_type_comment_without_whitespace(self): + ret = self._collect('x = 1 #type:int') + self.assertSetEqual(ret, {(ast.Assign, ('#type:int',))}) + + def test_type_comment_starts_with_word_ignore(self): + ret = self._collect('x = 1 # type: ignore[T]') + self.assertSetEqual(ret, set()) + + def test_last_node_wins(self): + """ + Test that when two typeable nodes are present on a line, the last + typeable one wins. + """ + ret = self._collect('def f(): x = 1 # type: int') + self.assertSetEqual(ret, {(ast.Assign, ('# type: int',))}) + + def test_function_def_assigned_comments(self): + """ + Test that type comments for function arguments are all attributed to + the function definition. + """ + ret = self._collect( + """\ +def f( + a, # type: int + b, # type: str +): + # type: (...) -> None + pass +""" + ) + expected = {( + ast.FunctionDef, + ('# type: int', '# type: str', '# type: (...) -> None'), + )} + self.assertSetEqual(ret, expected) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_code_segment.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_code_segment.py new file mode 100644 index 0000000..131a74d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_code_segment.py @@ -0,0 +1,132 @@ +from sys import version_info + +from pyflakes import messages as m +from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope, + Argument, FunctionDefinition, Assignment) +from pyflakes.test.harness import TestCase, skipIf + + +class TestCodeSegments(TestCase): + """ + Tests for segments of a module + """ + + def test_function_segment(self): + self.flakes(''' + def foo(): + def bar(): + pass + ''', is_segment=True) + + self.flakes(''' + def foo(): + def bar(): + x = 0 + ''', m.UnusedVariable, is_segment=True) + + def test_class_segment(self): + self.flakes(''' + class Foo: + class Bar: + pass + ''', is_segment=True) + + self.flakes(''' + class Foo: + def bar(): + x = 0 + ''', m.UnusedVariable, is_segment=True) + + def test_scope_class(self): + checker = self.flakes(''' + class Foo: + x = 0 + def bar(a, b=1, *d, **e): + pass + ''', is_segment=True) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + class_scopes = [ + scope for scope in scopes if scope.__class__ is ClassScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + # Ensure module scope is not present because we are analysing + # the inner contents of Foo + self.assertEqual(len(module_scopes), 0) + self.assertEqual(len(class_scopes), 1) + self.assertEqual(len(function_scopes), 1) + + class_scope = class_scopes[0] + function_scope = function_scopes[0] + + self.assertIsInstance(class_scope, ClassScope) + self.assertIsInstance(function_scope, FunctionScope) + + self.assertIn('x', class_scope) + self.assertIn('bar', class_scope) + + self.assertIn('a', function_scope) + self.assertIn('b', function_scope) + self.assertIn('d', function_scope) + self.assertIn('e', function_scope) + + self.assertIsInstance(class_scope['bar'], FunctionDefinition) + self.assertIsInstance(class_scope['x'], Assignment) + + self.assertIsInstance(function_scope['a'], Argument) + self.assertIsInstance(function_scope['b'], Argument) + self.assertIsInstance(function_scope['d'], Argument) + self.assertIsInstance(function_scope['e'], Argument) + + def test_scope_function(self): + checker = self.flakes(''' + def foo(a, b=1, *d, **e): + def bar(f, g=1, *h, **i): + pass + ''', is_segment=True) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + # Ensure module scope is not present because we are analysing + # the inner contents of foo + self.assertEqual(len(module_scopes), 0) + self.assertEqual(len(function_scopes), 2) + + function_scope_foo = function_scopes[1] + function_scope_bar = function_scopes[0] + + self.assertIsInstance(function_scope_foo, FunctionScope) + self.assertIsInstance(function_scope_bar, FunctionScope) + + self.assertIn('a', function_scope_foo) + self.assertIn('b', function_scope_foo) + self.assertIn('d', function_scope_foo) + self.assertIn('e', function_scope_foo) + self.assertIn('bar', function_scope_foo) + + self.assertIn('f', function_scope_bar) + self.assertIn('g', function_scope_bar) + self.assertIn('h', function_scope_bar) + self.assertIn('i', function_scope_bar) + + self.assertIsInstance(function_scope_foo['bar'], FunctionDefinition) + self.assertIsInstance(function_scope_foo['a'], Argument) + self.assertIsInstance(function_scope_foo['b'], Argument) + self.assertIsInstance(function_scope_foo['d'], Argument) + self.assertIsInstance(function_scope_foo['e'], Argument) + + self.assertIsInstance(function_scope_bar['f'], Argument) + self.assertIsInstance(function_scope_bar['g'], Argument) + self.assertIsInstance(function_scope_bar['h'], Argument) + self.assertIsInstance(function_scope_bar['i'], Argument) + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_scope_async_function(self): + self.flakes('async def foo(): pass', is_segment=True) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_dict.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_dict.py new file mode 100644 index 0000000..b9059c2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_dict.py @@ -0,0 +1,213 @@ +""" +Tests for dict duplicate keys Pyflakes behavior. +""" + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skipIf + + +class Test(TestCase): + + def test_duplicate_keys(self): + self.flakes( + "{'yes': 1, 'yes': 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + @skipIf(version_info < (3,), + "bytes and strings with same 'value' are not equal in python3") + def test_duplicate_keys_bytes_vs_unicode_py3(self): + self.flakes("{b'a': 1, u'a': 2}") + + @skipIf(version_info < (3,), + "bytes and strings with same 'value' are not equal in python3") + def test_duplicate_values_bytes_vs_unicode_py3(self): + self.flakes( + "{1: b'a', 1: u'a'}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + @skipIf(version_info >= (3,), + "bytes and strings with same 'value' are equal in python2") + def test_duplicate_keys_bytes_vs_unicode_py2(self): + self.flakes( + "{b'a': 1, u'a': 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + @skipIf(version_info >= (3,), + "bytes and strings with same 'value' are equal in python2") + def test_duplicate_values_bytes_vs_unicode_py2(self): + self.flakes("{1: b'a', 1: u'a'}") + + def test_multiple_duplicate_keys(self): + self.flakes( + "{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_in_function(self): + self.flakes( + ''' + def f(thing): + pass + f({'yes': 1, 'yes': 2}) + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_in_lambda(self): + self.flakes( + "lambda x: {(0,1): 1, (0,1): 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_tuples(self): + self.flakes( + "{(0,1): 1, (0,1): 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_tuples_int_and_float(self): + self.flakes( + "{(0,1): 1, (0,1.0): 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_ints(self): + self.flakes( + "{1: 1, 1: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_bools(self): + self.flakes( + "{True: 1, True: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_bools_false(self): + # Needed to ensure 2.x correctly coerces these from variables + self.flakes( + "{False: 1, False: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_keys_none(self): + self.flakes( + "{None: 1, None: 2}", + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_variable_keys(self): + self.flakes( + ''' + a = 1 + {a: 1, a: 2} + ''', + m.MultiValueRepeatedKeyVariable, + m.MultiValueRepeatedKeyVariable, + ) + + def test_duplicate_variable_values(self): + self.flakes( + ''' + a = 1 + b = 2 + {1: a, 1: b} + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_variable_values_same_value(self): + # Current behaviour is not to look up variable values. This is to + # confirm that. + self.flakes( + ''' + a = 1 + b = 1 + {1: a, 1: b} + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_duplicate_key_float_and_int(self): + """ + These do look like different values, but when it comes to their use as + keys, they compare as equal and so are actually duplicates. + The literal dict {1: 1, 1.0: 1} actually becomes {1.0: 1}. + """ + self.flakes( + ''' + {1: 1, 1.0: 2} + ''', + m.MultiValueRepeatedKeyLiteral, + m.MultiValueRepeatedKeyLiteral, + ) + + def test_no_duplicate_key_error_same_value(self): + self.flakes(''' + {'yes': 1, 'yes': 1} + ''') + + def test_no_duplicate_key_errors(self): + self.flakes(''' + {'yes': 1, 'no': 2} + ''') + + def test_no_duplicate_keys_tuples_same_first_element(self): + self.flakes("{(0,1): 1, (0,2): 1}") + + def test_no_duplicate_key_errors_func_call(self): + self.flakes(''' + def test(thing): + pass + test({True: 1, None: 2, False: 1}) + ''') + + def test_no_duplicate_key_errors_bool_or_none(self): + self.flakes("{True: 1, None: 2, False: 1}") + + def test_no_duplicate_key_errors_ints(self): + self.flakes(''' + {1: 1, 2: 1} + ''') + + def test_no_duplicate_key_errors_vars(self): + self.flakes(''' + test = 'yes' + rest = 'yes' + {test: 1, rest: 2} + ''') + + def test_no_duplicate_key_errors_tuples(self): + self.flakes(''' + {(0,1): 1, (0,2): 1} + ''') + + def test_no_duplicate_key_errors_instance_attributes(self): + self.flakes(''' + class Test(): + pass + f = Test() + f.a = 1 + {f.a: 1, f.a: 1} + ''') diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_doctests.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_doctests.py new file mode 100644 index 0000000..836b248 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_doctests.py @@ -0,0 +1,465 @@ +import sys +import textwrap + +from pyflakes import messages as m +from pyflakes.checker import ( + DoctestScope, + FunctionScope, + ModuleScope, +) +from pyflakes.test.test_other import Test as TestOther +from pyflakes.test.test_imports import Test as TestImports +from pyflakes.test.test_undefined_names import Test as TestUndefinedNames +from pyflakes.test.harness import TestCase, skip + +try: + sys.pypy_version_info + PYPY = True +except AttributeError: + PYPY = False + + +class _DoctestMixin(object): + + withDoctest = True + + def doctestify(self, input): + lines = [] + for line in textwrap.dedent(input).splitlines(): + if line.strip() == '': + pass + elif (line.startswith(' ') or + line.startswith('except:') or + line.startswith('except ') or + line.startswith('finally:') or + line.startswith('else:') or + line.startswith('elif ') or + (lines and lines[-1].startswith(('>>> @', '... @')))): + line = "... %s" % line + else: + line = ">>> %s" % line + lines.append(line) + doctestificator = textwrap.dedent('''\ + def doctest_something(): + """ + %s + """ + ''') + return doctestificator % "\n ".join(lines) + + def flakes(self, input, *args, **kw): + return super(_DoctestMixin, self).flakes(self.doctestify(input), *args, **kw) + + +class Test(TestCase): + + withDoctest = True + + def test_scope_class(self): + """Check that a doctest is given a DoctestScope.""" + checker = self.flakes(""" + m = None + + def doctest_stuff(): + ''' + >>> d = doctest_stuff() + ''' + f = m + return f + """) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + doctest_scopes = [ + scope for scope in scopes if scope.__class__ is DoctestScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + self.assertEqual(len(module_scopes), 1) + self.assertEqual(len(doctest_scopes), 1) + + module_scope = module_scopes[0] + doctest_scope = doctest_scopes[0] + + self.assertIsInstance(doctest_scope, DoctestScope) + self.assertIsInstance(doctest_scope, ModuleScope) + self.assertNotIsInstance(doctest_scope, FunctionScope) + self.assertNotIsInstance(module_scope, DoctestScope) + + self.assertIn('m', module_scope) + self.assertIn('doctest_stuff', module_scope) + + self.assertIn('d', doctest_scope) + + self.assertEqual(len(function_scopes), 1) + self.assertIn('f', function_scopes[0]) + + def test_nested_doctest_ignored(self): + """Check that nested doctests are ignored.""" + checker = self.flakes(""" + m = None + + def doctest_stuff(): + ''' + >>> def function_in_doctest(): + ... \"\"\" + ... >>> ignored_undefined_name + ... \"\"\" + ... df = m + ... return df + ... + >>> function_in_doctest() + ''' + f = m + return f + """) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + doctest_scopes = [ + scope for scope in scopes if scope.__class__ is DoctestScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + self.assertEqual(len(module_scopes), 1) + self.assertEqual(len(doctest_scopes), 1) + + module_scope = module_scopes[0] + doctest_scope = doctest_scopes[0] + + self.assertIn('m', module_scope) + self.assertIn('doctest_stuff', module_scope) + self.assertIn('function_in_doctest', doctest_scope) + + self.assertEqual(len(function_scopes), 2) + + self.assertIn('f', function_scopes[0]) + self.assertIn('df', function_scopes[1]) + + def test_global_module_scope_pollution(self): + """Check that global in doctest does not pollute module scope.""" + checker = self.flakes(""" + def doctest_stuff(): + ''' + >>> def function_in_doctest(): + ... global m + ... m = 50 + ... df = 10 + ... m = df + ... + >>> function_in_doctest() + ''' + f = 10 + return f + + """) + + scopes = checker.deadScopes + module_scopes = [ + scope for scope in scopes if scope.__class__ is ModuleScope] + doctest_scopes = [ + scope for scope in scopes if scope.__class__ is DoctestScope] + function_scopes = [ + scope for scope in scopes if scope.__class__ is FunctionScope] + + self.assertEqual(len(module_scopes), 1) + self.assertEqual(len(doctest_scopes), 1) + + module_scope = module_scopes[0] + doctest_scope = doctest_scopes[0] + + self.assertIn('doctest_stuff', module_scope) + self.assertIn('function_in_doctest', doctest_scope) + + self.assertEqual(len(function_scopes), 2) + + self.assertIn('f', function_scopes[0]) + self.assertIn('df', function_scopes[1]) + self.assertIn('m', function_scopes[1]) + + self.assertNotIn('m', module_scope) + + def test_global_undefined(self): + self.flakes(""" + global m + + def doctest_stuff(): + ''' + >>> m + ''' + """, m.UndefinedName) + + def test_nested_class(self): + """Doctest within nested class are processed.""" + self.flakes(""" + class C: + class D: + ''' + >>> m + ''' + def doctest_stuff(self): + ''' + >>> m + ''' + return 1 + """, m.UndefinedName, m.UndefinedName) + + def test_ignore_nested_function(self): + """Doctest module does not process doctest in nested functions.""" + # 'syntax error' would cause a SyntaxError if the doctest was processed. + # However doctest does not find doctest in nested functions + # (https://bugs.python.org/issue1650090). If nested functions were + # processed, this use of m should cause UndefinedName, and the + # name inner_function should probably exist in the doctest scope. + self.flakes(""" + def doctest_stuff(): + def inner_function(): + ''' + >>> syntax error + >>> inner_function() + 1 + >>> m + ''' + return 1 + m = inner_function() + return m + """) + + def test_inaccessible_scope_class(self): + """Doctest may not access class scope.""" + self.flakes(""" + class C: + def doctest_stuff(self): + ''' + >>> m + ''' + return 1 + m = 1 + """, m.UndefinedName) + + def test_importBeforeDoctest(self): + self.flakes(""" + import foo + + def doctest_stuff(): + ''' + >>> foo + ''' + """) + + @skip("todo") + def test_importBeforeAndInDoctest(self): + self.flakes(''' + import foo + + def doctest_stuff(): + """ + >>> import foo + >>> foo + """ + + foo + ''', m.RedefinedWhileUnused) + + def test_importInDoctestAndAfter(self): + self.flakes(''' + def doctest_stuff(): + """ + >>> import foo + >>> foo + """ + + import foo + foo() + ''') + + def test_offsetInDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> x # line 5 + """ + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 12) + + def test_offsetInLambdasInDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> lambda: x # line 5 + """ + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 20) + + def test_offsetAfterDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> x = 5 + """ + + x + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 8) + self.assertEqual(exc.col, 0) + + def test_syntaxErrorInDoctest(self): + exceptions = self.flakes( + ''' + def doctest_stuff(): + """ + >>> from # line 4 + >>> fortytwo = 42 + >>> except Exception: + """ + ''', + m.DoctestSyntaxError, + m.DoctestSyntaxError, + m.DoctestSyntaxError).messages + exc = exceptions[0] + self.assertEqual(exc.lineno, 4) + if PYPY: + self.assertEqual(exc.col, 27) + elif sys.version_info >= (3, 8): + self.assertEqual(exc.col, 18) + else: + self.assertEqual(exc.col, 26) + + # PyPy error column offset is 0, + # for the second and third line of the doctest + # i.e. at the beginning of the line + exc = exceptions[1] + self.assertEqual(exc.lineno, 5) + if PYPY: + self.assertEqual(exc.col, 14) + else: + self.assertEqual(exc.col, 16) + exc = exceptions[2] + self.assertEqual(exc.lineno, 6) + if PYPY: + self.assertEqual(exc.col, 14) + elif sys.version_info >= (3, 8): + self.assertEqual(exc.col, 13) + else: + self.assertEqual(exc.col, 18) + + def test_indentationErrorInDoctest(self): + exc = self.flakes(''' + def doctest_stuff(): + """ + >>> if True: + ... pass + """ + ''', m.DoctestSyntaxError).messages[0] + self.assertEqual(exc.lineno, 5) + if PYPY: + self.assertEqual(exc.col, 14) + elif sys.version_info >= (3, 8): + self.assertEqual(exc.col, 13) + else: + self.assertEqual(exc.col, 16) + + def test_offsetWithMultiLineArgs(self): + (exc1, exc2) = self.flakes( + ''' + def doctest_stuff(arg1, + arg2, + arg3): + """ + >>> assert + >>> this + """ + ''', + m.DoctestSyntaxError, + m.UndefinedName).messages + self.assertEqual(exc1.lineno, 6) + if PYPY: + self.assertEqual(exc1.col, 20) + else: + self.assertEqual(exc1.col, 19) + self.assertEqual(exc2.lineno, 7) + self.assertEqual(exc2.col, 12) + + def test_doctestCanReferToFunction(self): + self.flakes(""" + def foo(): + ''' + >>> foo + ''' + """) + + def test_doctestCanReferToClass(self): + self.flakes(""" + class Foo(): + ''' + >>> Foo + ''' + def bar(self): + ''' + >>> Foo + ''' + """) + + def test_noOffsetSyntaxErrorInDoctest(self): + exceptions = self.flakes( + ''' + def buildurl(base, *args, **kwargs): + """ + >>> buildurl('/blah.php', ('a', '&'), ('b', '=') + '/blah.php?a=%26&b=%3D' + >>> buildurl('/blah.php', a='&', 'b'='=') + '/blah.php?b=%3D&a=%26' + """ + pass + ''', + m.DoctestSyntaxError, + m.DoctestSyntaxError).messages + exc = exceptions[0] + self.assertEqual(exc.lineno, 4) + exc = exceptions[1] + self.assertEqual(exc.lineno, 6) + + def test_singleUnderscoreInDoctest(self): + self.flakes(''' + def func(): + """A docstring + + >>> func() + 1 + >>> _ + 1 + """ + return 1 + ''') + + def test_globalUnderscoreInDoctest(self): + self.flakes(""" + from gettext import ugettext as _ + + def doctest_stuff(): + ''' + >>> pass + ''' + """, m.UnusedImport) + + +class TestOther(_DoctestMixin, TestOther): + """Run TestOther with each test wrapped in a doctest.""" + + +class TestImports(_DoctestMixin, TestImports): + """Run TestImports with each test wrapped in a doctest.""" + + +class TestUndefinedNames(_DoctestMixin, TestUndefinedNames): + """Run TestUndefinedNames with each test wrapped in a doctest.""" diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_imports.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_imports.py new file mode 100644 index 0000000..d5be269 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_imports.py @@ -0,0 +1,1230 @@ +from sys import version_info + +from pyflakes import messages as m +from pyflakes.checker import ( + FutureImportation, + Importation, + ImportationFrom, + StarImportation, + SubmoduleImportation, +) +from pyflakes.test.harness import TestCase, skip, skipIf + + +class TestImportationObject(TestCase): + + def test_import_basic(self): + binding = Importation('a', None, 'a') + assert binding.source_statement == 'import a' + assert str(binding) == 'a' + + def test_import_as(self): + binding = Importation('c', None, 'a') + assert binding.source_statement == 'import a as c' + assert str(binding) == 'a as c' + + def test_import_submodule(self): + binding = SubmoduleImportation('a.b', None) + assert binding.source_statement == 'import a.b' + assert str(binding) == 'a.b' + + def test_import_submodule_as(self): + # A submodule import with an as clause is not a SubmoduleImportation + binding = Importation('c', None, 'a.b') + assert binding.source_statement == 'import a.b as c' + assert str(binding) == 'a.b as c' + + def test_import_submodule_as_source_name(self): + binding = Importation('a', None, 'a.b') + assert binding.source_statement == 'import a.b as a' + assert str(binding) == 'a.b as a' + + def test_importfrom_relative(self): + binding = ImportationFrom('a', None, '.', 'a') + assert binding.source_statement == 'from . import a' + assert str(binding) == '.a' + + def test_importfrom_relative_parent(self): + binding = ImportationFrom('a', None, '..', 'a') + assert binding.source_statement == 'from .. import a' + assert str(binding) == '..a' + + def test_importfrom_relative_with_module(self): + binding = ImportationFrom('b', None, '..a', 'b') + assert binding.source_statement == 'from ..a import b' + assert str(binding) == '..a.b' + + def test_importfrom_relative_with_module_as(self): + binding = ImportationFrom('c', None, '..a', 'b') + assert binding.source_statement == 'from ..a import b as c' + assert str(binding) == '..a.b as c' + + def test_importfrom_member(self): + binding = ImportationFrom('b', None, 'a', 'b') + assert binding.source_statement == 'from a import b' + assert str(binding) == 'a.b' + + def test_importfrom_submodule_member(self): + binding = ImportationFrom('c', None, 'a.b', 'c') + assert binding.source_statement == 'from a.b import c' + assert str(binding) == 'a.b.c' + + def test_importfrom_member_as(self): + binding = ImportationFrom('c', None, 'a', 'b') + assert binding.source_statement == 'from a import b as c' + assert str(binding) == 'a.b as c' + + def test_importfrom_submodule_member_as(self): + binding = ImportationFrom('d', None, 'a.b', 'c') + assert binding.source_statement == 'from a.b import c as d' + assert str(binding) == 'a.b.c as d' + + def test_importfrom_star(self): + binding = StarImportation('a.b', None) + assert binding.source_statement == 'from a.b import *' + assert str(binding) == 'a.b.*' + + def test_importfrom_star_relative(self): + binding = StarImportation('.b', None) + assert binding.source_statement == 'from .b import *' + assert str(binding) == '.b.*' + + def test_importfrom_future(self): + binding = FutureImportation('print_function', None, None) + assert binding.source_statement == 'from __future__ import print_function' + assert str(binding) == '__future__.print_function' + + def test_unusedImport_underscore(self): + """ + The magic underscore var should be reported as unused when used as an + import alias. + """ + self.flakes('import fu as _', m.UnusedImport) + + +class Test(TestCase): + + def test_unusedImport(self): + self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport) + self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport) + + def test_unusedImport_relative(self): + self.flakes('from . import fu', m.UnusedImport) + self.flakes('from . import fu as baz', m.UnusedImport) + self.flakes('from .. import fu', m.UnusedImport) + self.flakes('from ... import fu', m.UnusedImport) + self.flakes('from .. import fu as baz', m.UnusedImport) + self.flakes('from .bar import fu', m.UnusedImport) + self.flakes('from ..bar import fu', m.UnusedImport) + self.flakes('from ...bar import fu', m.UnusedImport) + self.flakes('from ...bar import fu as baz', m.UnusedImport) + + checker = self.flakes('from . import fu', m.UnusedImport) + + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu', ) + + checker = self.flakes('from . import fu as baz', m.UnusedImport) + + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu as baz', ) + + def test_aliasedImport(self): + self.flakes('import fu as FU, bar as FU', + m.RedefinedWhileUnused, m.UnusedImport) + self.flakes('from moo import fu as FU, bar as FU', + m.RedefinedWhileUnused, m.UnusedImport) + + def test_aliasedImportShadowModule(self): + """Imported aliases can shadow the source of the import.""" + self.flakes('from moo import fu as moo; moo') + self.flakes('import fu as fu; fu') + self.flakes('import fu.bar as fu; fu') + + def test_usedImport(self): + self.flakes('import fu; print(fu)') + self.flakes('from baz import fu; print(fu)') + self.flakes('import fu; del fu') + + def test_usedImport_relative(self): + self.flakes('from . import fu; assert fu') + self.flakes('from .bar import fu; assert fu') + self.flakes('from .. import fu; assert fu') + self.flakes('from ..bar import fu as baz; assert baz') + + def test_redefinedWhileUnused(self): + self.flakes('import fu; fu = 3', m.RedefinedWhileUnused) + self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused) + self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused) + + def test_redefinedIf(self): + """ + Test that importing a module twice within an if + block does raise a warning. + """ + self.flakes(''' + i = 2 + if i==1: + import os + import os + os.path''', m.RedefinedWhileUnused) + + def test_redefinedIfElse(self): + """ + Test that importing a module twice in if + and else blocks does not raise a warning. + """ + self.flakes(''' + i = 2 + if i==1: + import os + else: + import os + os.path''') + + def test_redefinedTry(self): + """ + Test that importing a module twice in a try block + does raise a warning. + """ + self.flakes(''' + try: + import os + import os + except: + pass + os.path''', m.RedefinedWhileUnused) + + def test_redefinedTryExcept(self): + """ + Test that importing a module twice in a try + and except block does not raise a warning. + """ + self.flakes(''' + try: + import os + except: + import os + os.path''') + + def test_redefinedTryNested(self): + """ + Test that importing a module twice using a nested + try/except and if blocks does not issue a warning. + """ + self.flakes(''' + try: + if True: + if True: + import os + except: + import os + os.path''') + + def test_redefinedTryExceptMulti(self): + self.flakes(""" + try: + from aa import mixer + except AttributeError: + from bb import mixer + except RuntimeError: + from cc import mixer + except: + from dd import mixer + mixer(123) + """) + + def test_redefinedTryElse(self): + self.flakes(""" + try: + from aa import mixer + except ImportError: + pass + else: + from bb import mixer + mixer(123) + """, m.RedefinedWhileUnused) + + def test_redefinedTryExceptElse(self): + self.flakes(""" + try: + import funca + except ImportError: + from bb import funca + from bb import funcb + else: + from bbb import funcb + print(funca, funcb) + """) + + def test_redefinedTryExceptFinally(self): + self.flakes(""" + try: + from aa import a + except ImportError: + from bb import a + finally: + a = 42 + print(a) + """) + + def test_redefinedTryExceptElseFinally(self): + self.flakes(""" + try: + import b + except ImportError: + b = Ellipsis + from bb import a + else: + from aa import a + finally: + a = 42 + print(a, b) + """) + + def test_redefinedByFunction(self): + self.flakes(''' + import fu + def fu(): + pass + ''', m.RedefinedWhileUnused) + + def test_redefinedInNestedFunction(self): + """ + Test that shadowing a global name with a nested function definition + generates a warning. + """ + self.flakes(''' + import fu + def bar(): + def baz(): + def fu(): + pass + ''', m.RedefinedWhileUnused, m.UnusedImport) + + def test_redefinedInNestedFunctionTwice(self): + """ + Test that shadowing a global name with a nested function definition + generates a warning. + """ + self.flakes(''' + import fu + def bar(): + import fu + def baz(): + def fu(): + pass + ''', + m.RedefinedWhileUnused, m.RedefinedWhileUnused, + m.UnusedImport, m.UnusedImport) + + def test_redefinedButUsedLater(self): + """ + Test that a global import which is redefined locally, + but used later in another scope does not generate a warning. + """ + self.flakes(''' + import unittest, transport + + class GetTransportTestCase(unittest.TestCase): + def test_get_transport(self): + transport = 'transport' + self.assertIsNotNone(transport) + + class TestTransportMethodArgs(unittest.TestCase): + def test_send_defaults(self): + transport.Transport() + ''') + + def test_redefinedByClass(self): + self.flakes(''' + import fu + class fu: + pass + ''', m.RedefinedWhileUnused) + + def test_redefinedBySubclass(self): + """ + If an imported name is redefined by a class statement which also uses + that name in the bases list, no warning is emitted. + """ + self.flakes(''' + from fu import bar + class bar(bar): + pass + ''') + + def test_redefinedInClass(self): + """ + Test that shadowing a global with a class attribute does not produce a + warning. + """ + self.flakes(''' + import fu + class bar: + fu = 1 + print(fu) + ''') + + def test_importInClass(self): + """ + Test that import within class is a locally scoped attribute. + """ + self.flakes(''' + class bar: + import fu + ''') + + self.flakes(''' + class bar: + import fu + + fu + ''', m.UndefinedName) + + def test_usedInFunction(self): + self.flakes(''' + import fu + def fun(): + print(fu) + ''') + + def test_shadowedByParameter(self): + self.flakes(''' + import fu + def fun(fu): + print(fu) + ''', m.UnusedImport, m.RedefinedWhileUnused) + + self.flakes(''' + import fu + def fun(fu): + print(fu) + print(fu) + ''') + + def test_newAssignment(self): + self.flakes('fu = None') + + def test_usedInGetattr(self): + self.flakes('import fu; fu.bar.baz') + self.flakes('import fu; "bar".fu.baz', m.UnusedImport) + + def test_usedInSlice(self): + self.flakes('import fu; print(fu.bar[1:])') + + def test_usedInIfBody(self): + self.flakes(''' + import fu + if True: print(fu) + ''') + + def test_usedInIfConditional(self): + self.flakes(''' + import fu + if fu: pass + ''') + + def test_usedInElifConditional(self): + self.flakes(''' + import fu + if False: pass + elif fu: pass + ''') + + def test_usedInElse(self): + self.flakes(''' + import fu + if False: pass + else: print(fu) + ''') + + def test_usedInCall(self): + self.flakes('import fu; fu.bar()') + + def test_usedInClass(self): + self.flakes(''' + import fu + class bar: + bar = fu + ''') + + def test_usedInClassBase(self): + self.flakes(''' + import fu + class bar(object, fu.baz): + pass + ''') + + def test_notUsedInNestedScope(self): + self.flakes(''' + import fu + def bleh(): + pass + print(fu) + ''') + + def test_usedInFor(self): + self.flakes(''' + import fu + for bar in range(9): + print(fu) + ''') + + def test_usedInForElse(self): + self.flakes(''' + import fu + for bar in range(10): + pass + else: + print(fu) + ''') + + def test_redefinedByFor(self): + self.flakes(''' + import fu + for fu in range(2): + pass + ''', m.ImportShadowedByLoopVar) + + def test_shadowedByFor(self): + """ + Test that shadowing a global name with a for loop variable generates a + warning. + """ + self.flakes(''' + import fu + fu.bar() + for fu in (): + pass + ''', m.ImportShadowedByLoopVar) + + def test_shadowedByForDeep(self): + """ + Test that shadowing a global name with a for loop variable nested in a + tuple unpack generates a warning. + """ + self.flakes(''' + import fu + fu.bar() + for (x, y, z, (a, b, c, (fu,))) in (): + pass + ''', m.ImportShadowedByLoopVar) + # Same with a list instead of a tuple + self.flakes(''' + import fu + fu.bar() + for [x, y, z, (a, b, c, (fu,))] in (): + pass + ''', m.ImportShadowedByLoopVar) + + def test_usedInReturn(self): + self.flakes(''' + import fu + def fun(): + return fu + ''') + + def test_usedInOperators(self): + self.flakes('import fu; 3 + fu.bar') + self.flakes('import fu; 3 % fu.bar') + self.flakes('import fu; 3 - fu.bar') + self.flakes('import fu; 3 * fu.bar') + self.flakes('import fu; 3 ** fu.bar') + self.flakes('import fu; 3 / fu.bar') + self.flakes('import fu; 3 // fu.bar') + self.flakes('import fu; -fu.bar') + self.flakes('import fu; ~fu.bar') + self.flakes('import fu; 1 == fu.bar') + self.flakes('import fu; 1 | fu.bar') + self.flakes('import fu; 1 & fu.bar') + self.flakes('import fu; 1 ^ fu.bar') + self.flakes('import fu; 1 >> fu.bar') + self.flakes('import fu; 1 << fu.bar') + + def test_usedInAssert(self): + self.flakes('import fu; assert fu.bar') + + def test_usedInSubscript(self): + self.flakes('import fu; fu.bar[1]') + + def test_usedInLogic(self): + self.flakes('import fu; fu and False') + self.flakes('import fu; fu or False') + self.flakes('import fu; not fu.bar') + + def test_usedInList(self): + self.flakes('import fu; [fu]') + + def test_usedInTuple(self): + self.flakes('import fu; (fu,)') + + def test_usedInTry(self): + self.flakes(''' + import fu + try: fu + except: pass + ''') + + def test_usedInExcept(self): + self.flakes(''' + import fu + try: fu + except: pass + ''') + + def test_redefinedByExcept(self): + expected = [m.RedefinedWhileUnused] + if version_info >= (3,): + # The exc variable is unused inside the exception handler. + expected.append(m.UnusedVariable) + self.flakes(''' + import fu + try: pass + except Exception as fu: pass + ''', *expected) + + def test_usedInRaise(self): + self.flakes(''' + import fu + raise fu.bar + ''') + + def test_usedInYield(self): + self.flakes(''' + import fu + def gen(): + yield fu + ''') + + def test_usedInDict(self): + self.flakes('import fu; {fu:None}') + self.flakes('import fu; {1:fu}') + + def test_usedInParameterDefault(self): + self.flakes(''' + import fu + def f(bar=fu): + pass + ''') + + def test_usedInAttributeAssign(self): + self.flakes('import fu; fu.bar = 1') + + def test_usedInKeywordArg(self): + self.flakes('import fu; fu.bar(stuff=fu)') + + def test_usedInAssignment(self): + self.flakes('import fu; bar=fu') + self.flakes('import fu; n=0; n+=fu') + + def test_usedInListComp(self): + self.flakes('import fu; [fu for _ in range(1)]') + self.flakes('import fu; [1 for _ in range(1) if fu]') + + @skipIf(version_info >= (3,), + 'in Python 3 list comprehensions execute in a separate scope') + def test_redefinedByListComp(self): + self.flakes('import fu; [1 for fu in range(1)]', + m.RedefinedInListComp) + + def test_usedInTryFinally(self): + self.flakes(''' + import fu + try: pass + finally: fu + ''') + + self.flakes(''' + import fu + try: fu + finally: pass + ''') + + def test_usedInWhile(self): + self.flakes(''' + import fu + while 0: + fu + ''') + + self.flakes(''' + import fu + while fu: pass + ''') + + def test_usedInGlobal(self): + """ + A 'global' statement shadowing an unused import should not prevent it + from being reported. + """ + self.flakes(''' + import fu + def f(): global fu + ''', m.UnusedImport) + + def test_usedAndGlobal(self): + """ + A 'global' statement shadowing a used import should not cause it to be + reported as unused. + """ + self.flakes(''' + import foo + def f(): global foo + def g(): foo.is_used() + ''') + + def test_assignedToGlobal(self): + """ + Binding an import to a declared global should not cause it to be + reported as unused. + """ + self.flakes(''' + def f(): global foo; import foo + def g(): foo.is_used() + ''') + + @skipIf(version_info >= (3,), 'deprecated syntax') + def test_usedInBackquote(self): + self.flakes('import fu; `fu`') + + def test_usedInExec(self): + if version_info < (3,): + exec_stmt = 'exec "print 1" in fu.bar' + else: + exec_stmt = 'exec("print(1)", fu.bar)' + self.flakes('import fu; %s' % exec_stmt) + + def test_usedInLambda(self): + self.flakes('import fu; lambda: fu') + + def test_shadowedByLambda(self): + self.flakes('import fu; lambda fu: fu', + m.UnusedImport, m.RedefinedWhileUnused) + self.flakes('import fu; lambda fu: fu\nfu()') + + def test_usedInSliceObj(self): + self.flakes('import fu; "meow"[::fu]') + + def test_unusedInNestedScope(self): + self.flakes(''' + def bar(): + import fu + fu + ''', m.UnusedImport, m.UndefinedName) + + def test_methodsDontUseClassScope(self): + self.flakes(''' + class bar: + import fu + def fun(self): + fu + ''', m.UndefinedName) + + def test_nestedFunctionsNestScope(self): + self.flakes(''' + def a(): + def b(): + fu + import fu + ''') + + def test_nestedClassAndFunctionScope(self): + self.flakes(''' + def a(): + import fu + class b: + def c(self): + print(fu) + ''') + + def test_importStar(self): + """Use of import * at module level is reported.""" + self.flakes('from fu import *', m.ImportStarUsed, m.UnusedImport) + self.flakes(''' + try: + from fu import * + except: + pass + ''', m.ImportStarUsed, m.UnusedImport) + + checker = self.flakes('from fu import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('fu', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('fu.*', ) + + def test_importStar_relative(self): + """Use of import * from a relative import is reported.""" + self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport) + self.flakes(''' + try: + from .fu import * + except: + pass + ''', m.ImportStarUsed, m.UnusedImport) + + checker = self.flakes('from .fu import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('.fu', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu.*', ) + + checker = self.flakes('from .. import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('..', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('from .. import *', ) + + @skipIf(version_info < (3,), + 'import * below module level is a warning on Python 2') + def test_localImportStar(self): + """import * is only allowed at module level.""" + self.flakes(''' + def a(): + from fu import * + ''', m.ImportStarNotPermitted) + self.flakes(''' + class a: + from fu import * + ''', m.ImportStarNotPermitted) + + checker = self.flakes(''' + class a: + from .. import * + ''', m.ImportStarNotPermitted) + error = checker.messages[0] + assert error.message == "'from %s import *' only allowed at module level" + assert error.message_args == ('..', ) + + @skipIf(version_info > (3,), + 'import * below module level is an error on Python 3') + def test_importStarNested(self): + """All star imports are marked as used by an undefined variable.""" + self.flakes(''' + from fu import * + def f(): + from bar import * + x + ''', m.ImportStarUsed, m.ImportStarUsed, m.ImportStarUsage) + + def test_packageImport(self): + """ + If a dotted name is imported and used, no warning is reported. + """ + self.flakes(''' + import fu.bar + fu.bar + ''') + + def test_unusedPackageImport(self): + """ + If a dotted name is imported and not used, an unused import warning is + reported. + """ + self.flakes('import fu.bar', m.UnusedImport) + + def test_duplicateSubmoduleImport(self): + """ + If a submodule of a package is imported twice, an unused import warning + and a redefined while unused warning are reported. + """ + self.flakes(''' + import fu.bar, fu.bar + fu.bar + ''', m.RedefinedWhileUnused) + self.flakes(''' + import fu.bar + import fu.bar + fu.bar + ''', m.RedefinedWhileUnused) + + def test_differentSubmoduleImport(self): + """ + If two different submodules of a package are imported, no duplicate + import warning is reported for the package. + """ + self.flakes(''' + import fu.bar, fu.baz + fu.bar, fu.baz + ''') + self.flakes(''' + import fu.bar + import fu.baz + fu.bar, fu.baz + ''') + + def test_used_package_with_submodule_import(self): + """ + Usage of package marks submodule imports as used. + """ + self.flakes(''' + import fu + import fu.bar + fu.x + ''') + + self.flakes(''' + import fu.bar + import fu + fu.x + ''') + + def test_used_package_with_submodule_import_of_alias(self): + """ + Usage of package by alias marks submodule imports as used. + """ + self.flakes(''' + import foo as f + import foo.bar + f.bar.do_something() + ''') + + self.flakes(''' + import foo as f + import foo.bar.blah + f.bar.blah.do_something() + ''') + + def test_unused_package_with_submodule_import(self): + """ + When a package and its submodule are imported, only report once. + """ + checker = self.flakes(''' + import fu + import fu.bar + ''', m.UnusedImport) + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('fu.bar', ) + assert error.lineno == 5 if self.withDoctest else 3 + + def test_assignRHSFirst(self): + self.flakes('import fu; fu = fu') + self.flakes('import fu; fu, bar = fu') + self.flakes('import fu; [fu, bar] = fu') + self.flakes('import fu; fu += fu') + + def test_tryingMultipleImports(self): + self.flakes(''' + try: + import fu + except ImportError: + import bar as fu + fu + ''') + + def test_nonGlobalDoesNotRedefine(self): + self.flakes(''' + import fu + def a(): + fu = 3 + return fu + fu + ''') + + def test_functionsRunLater(self): + self.flakes(''' + def a(): + fu + import fu + ''') + + def test_functionNamesAreBoundNow(self): + self.flakes(''' + import fu + def fu(): + fu + fu + ''', m.RedefinedWhileUnused) + + def test_ignoreNonImportRedefinitions(self): + self.flakes('a = 1; a = 2') + + @skip("todo") + def test_importingForImportError(self): + self.flakes(''' + try: + import fu + except ImportError: + pass + ''') + + def test_importedInClass(self): + """Imports in class scope can be used through self.""" + self.flakes(''' + class c: + import i + def __init__(self): + self.i + ''') + + def test_importUsedInMethodDefinition(self): + """ + Method named 'foo' with default args referring to module named 'foo'. + """ + self.flakes(''' + import foo + + class Thing(object): + def foo(self, parser=foo.parse_foo): + pass + ''') + + def test_futureImport(self): + """__future__ is special.""" + self.flakes('from __future__ import division') + self.flakes(''' + "docstring is allowed before future import" + from __future__ import division + ''') + + def test_futureImportFirst(self): + """ + __future__ imports must come before anything else. + """ + self.flakes(''' + x = 5 + from __future__ import division + ''', m.LateFutureImport) + self.flakes(''' + from foo import bar + from __future__ import division + bar + ''', m.LateFutureImport) + + def test_futureImportUsed(self): + """__future__ is special, but names are injected in the namespace.""" + self.flakes(''' + from __future__ import division + from __future__ import print_function + + assert print_function is not division + ''') + + def test_futureImportUndefined(self): + """Importing undefined names from __future__ fails.""" + self.flakes(''' + from __future__ import print_statement + ''', m.FutureFeatureNotDefined) + + def test_futureImportStar(self): + """Importing '*' from __future__ fails.""" + self.flakes(''' + from __future__ import * + ''', m.FutureFeatureNotDefined) + + +class TestSpecialAll(TestCase): + """ + Tests for suppression of unused import warnings by C{__all__}. + """ + def test_ignoredInFunction(self): + """ + An C{__all__} definition does not suppress unused import warnings in a + function scope. + """ + self.flakes(''' + def foo(): + import bar + __all__ = ["bar"] + ''', m.UnusedImport, m.UnusedVariable) + + def test_ignoredInClass(self): + """ + An C{__all__} definition in a class does not suppress unused import warnings. + """ + self.flakes(''' + import bar + class foo: + __all__ = ["bar"] + ''', m.UnusedImport) + + def test_warningSuppressed(self): + """ + If a name is imported and unused but is named in C{__all__}, no warning + is reported. + """ + self.flakes(''' + import foo + __all__ = ["foo"] + ''') + self.flakes(''' + import foo + __all__ = ("foo",) + ''') + + def test_augmentedAssignment(self): + """ + The C{__all__} variable is defined incrementally. + """ + self.flakes(''' + import a + import c + __all__ = ['a'] + __all__ += ['b'] + if 1 < 3: + __all__ += ['c', 'd'] + ''', m.UndefinedExport, m.UndefinedExport) + + def test_list_concatenation_assignment(self): + """ + The C{__all__} variable is defined through list concatenation. + """ + self.flakes(''' + import sys + __all__ = ['a'] + ['b'] + ['c'] + ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) + + def test_tuple_concatenation_assignment(self): + """ + The C{__all__} variable is defined through tuple concatenation. + """ + self.flakes(''' + import sys + __all__ = ('a',) + ('b',) + ('c',) + ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) + + def test_all_with_attributes(self): + self.flakes(''' + from foo import bar + __all__ = [bar.__name__] + ''') + + def test_all_with_names(self): + # not actually valid, but shouldn't produce a crash + self.flakes(''' + from foo import bar + __all__ = [bar] + ''') + + def test_all_with_attributes_added(self): + self.flakes(''' + from foo import bar + from bar import baz + __all__ = [bar.__name__] + [baz.__name__] + ''') + + def test_all_mixed_attributes_and_strings(self): + self.flakes(''' + from foo import bar + from foo import baz + __all__ = ['bar', baz.__name__] + ''') + + def test_unboundExported(self): + """ + If C{__all__} includes a name which is not bound, a warning is emitted. + """ + self.flakes(''' + __all__ = ["foo"] + ''', m.UndefinedExport) + + # Skip this in __init__.py though, since the rules there are a little + # different. + for filename in ["foo/__init__.py", "__init__.py"]: + self.flakes(''' + __all__ = ["foo"] + ''', filename=filename) + + def test_importStarExported(self): + """ + Report undefined if import * is used + """ + self.flakes(''' + from math import * + __all__ = ['sin', 'cos'] + csc(1) + ''', m.ImportStarUsed, m.ImportStarUsage, m.ImportStarUsage, m.ImportStarUsage) + + def test_importStarNotExported(self): + """Report unused import when not needed to satisfy __all__.""" + self.flakes(''' + from foolib import * + a = 1 + __all__ = ['a'] + ''', m.ImportStarUsed, m.UnusedImport) + + def test_usedInGenExp(self): + """ + Using a global in a generator expression results in no warnings. + """ + self.flakes('import fu; (fu for _ in range(1))') + self.flakes('import fu; (1 for _ in range(1) if fu)') + + def test_redefinedByGenExp(self): + """ + Re-using a global name as the loop variable for a generator + expression results in a redefinition warning. + """ + self.flakes('import fu; (1 for fu in range(1))', + m.RedefinedWhileUnused, m.UnusedImport) + + def test_usedAsDecorator(self): + """ + Using a global name in a decorator statement results in no warnings, + but using an undefined name in a decorator statement results in an + undefined name warning. + """ + self.flakes(''' + from interior import decorate + @decorate + def f(): + return "hello" + ''') + + self.flakes(''' + from interior import decorate + @decorate('value') + def f(): + return "hello" + ''') + + self.flakes(''' + @decorate + def f(): + return "hello" + ''', m.UndefinedName) + + def test_usedAsClassDecorator(self): + """ + Using an imported name as a class decorator results in no warnings, + but using an undefined name as a class decorator results in an + undefined name warning. + """ + self.flakes(''' + from interior import decorate + @decorate + class foo: + pass + ''') + + self.flakes(''' + from interior import decorate + @decorate("foo") + class bar: + pass + ''') + + self.flakes(''' + @decorate + class foo: + pass + ''', m.UndefinedName) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_is_literal.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_is_literal.py new file mode 100644 index 0000000..fbbb205 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_is_literal.py @@ -0,0 +1,222 @@ +from pyflakes.messages import IsLiteral +from pyflakes.test.harness import TestCase + + +class Test(TestCase): + def test_is_str(self): + self.flakes(""" + x = 'foo' + if x is 'foo': + pass + """, IsLiteral) + + def test_is_bytes(self): + self.flakes(""" + x = b'foo' + if x is b'foo': + pass + """, IsLiteral) + + def test_is_unicode(self): + self.flakes(""" + x = u'foo' + if x is u'foo': + pass + """, IsLiteral) + + def test_is_int(self): + self.flakes(""" + x = 10 + if x is 10: + pass + """, IsLiteral) + + def test_is_true(self): + self.flakes(""" + x = True + if x is True: + pass + """) + + def test_is_false(self): + self.flakes(""" + x = False + if x is False: + pass + """) + + def test_is_not_str(self): + self.flakes(""" + x = 'foo' + if x is not 'foo': + pass + """, IsLiteral) + + def test_is_not_bytes(self): + self.flakes(""" + x = b'foo' + if x is not b'foo': + pass + """, IsLiteral) + + def test_is_not_unicode(self): + self.flakes(""" + x = u'foo' + if x is not u'foo': + pass + """, IsLiteral) + + def test_is_not_int(self): + self.flakes(""" + x = 10 + if x is not 10: + pass + """, IsLiteral) + + def test_is_not_true(self): + self.flakes(""" + x = True + if x is not True: + pass + """) + + def test_is_not_false(self): + self.flakes(""" + x = False + if x is not False: + pass + """) + + def test_left_is_str(self): + self.flakes(""" + x = 'foo' + if 'foo' is x: + pass + """, IsLiteral) + + def test_left_is_bytes(self): + self.flakes(""" + x = b'foo' + if b'foo' is x: + pass + """, IsLiteral) + + def test_left_is_unicode(self): + self.flakes(""" + x = u'foo' + if u'foo' is x: + pass + """, IsLiteral) + + def test_left_is_int(self): + self.flakes(""" + x = 10 + if 10 is x: + pass + """, IsLiteral) + + def test_left_is_true(self): + self.flakes(""" + x = True + if True is x: + pass + """) + + def test_left_is_false(self): + self.flakes(""" + x = False + if False is x: + pass + """) + + def test_left_is_not_str(self): + self.flakes(""" + x = 'foo' + if 'foo' is not x: + pass + """, IsLiteral) + + def test_left_is_not_bytes(self): + self.flakes(""" + x = b'foo' + if b'foo' is not x: + pass + """, IsLiteral) + + def test_left_is_not_unicode(self): + self.flakes(""" + x = u'foo' + if u'foo' is not x: + pass + """, IsLiteral) + + def test_left_is_not_int(self): + self.flakes(""" + x = 10 + if 10 is not x: + pass + """, IsLiteral) + + def test_left_is_not_true(self): + self.flakes(""" + x = True + if True is not x: + pass + """) + + def test_left_is_not_false(self): + self.flakes(""" + x = False + if False is not x: + pass + """) + + def test_chained_operators_is_true(self): + self.flakes(""" + x = 5 + if x is True < 4: + pass + """) + + def test_chained_operators_is_str(self): + self.flakes(""" + x = 5 + if x is 'foo' < 4: + pass + """, IsLiteral) + + def test_chained_operators_is_true_end(self): + self.flakes(""" + x = 5 + if 4 < x is True: + pass + """) + + def test_chained_operators_is_str_end(self): + self.flakes(""" + x = 5 + if 4 < x is 'foo': + pass + """, IsLiteral) + + def test_is_tuple_constant(self): + self.flakes('''\ + x = 5 + if x is (): + pass + ''', IsLiteral) + + def test_is_tuple_constant_containing_constants(self): + self.flakes('''\ + x = 5 + if x is (1, '2', True, (1.5, ())): + pass + ''', IsLiteral) + + def test_is_tuple_containing_variables_ok(self): + # a bit nonsensical, but does not trigger a SyntaxWarning + self.flakes('''\ + x = 5 + if x is (x,): + pass + ''') diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_match.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_match.py new file mode 100644 index 0000000..89826e3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_match.py @@ -0,0 +1,83 @@ +from sys import version_info + +from pyflakes.test.harness import TestCase, skipIf + + +@skipIf(version_info < (3, 10), "Python >= 3.10 only") +class TestMatch(TestCase): + def test_match_bindings(self): + self.flakes(''' + def f(): + x = 1 + match x: + case 1 as y: + print(f'matched as {y}') + ''') + self.flakes(''' + def f(): + x = [1, 2, 3] + match x: + case [1, y, 3]: + print(f'matched {y}') + ''') + self.flakes(''' + def f(): + x = {'foo': 1} + match x: + case {'foo': y}: + print(f'matched {y}') + ''') + + def test_match_pattern_matched_class(self): + self.flakes(''' + from a import B + + match 1: + case B(x=1) as y: + print(f'matched {y}') + ''') + self.flakes(''' + from a import B + + match 1: + case B(a, x=z) as y: + print(f'matched {y} {a} {z}') + ''') + + def test_match_placeholder(self): + self.flakes(''' + def f(): + match 1: + case _: + print('catchall!') + ''') + + def test_match_singleton(self): + self.flakes(''' + match 1: + case True: + print('true') + ''') + + def test_match_or_pattern(self): + self.flakes(''' + match 1: + case 1 | 2: + print('one or two') + ''') + + def test_match_star(self): + self.flakes(''' + x = [1, 2, 3] + match x: + case [1, *y]: + print(f'captured: {y}') + ''') + + def test_match_double_star(self): + self.flakes(''' + x = {'foo': 'bar', 'baz': 'womp'} + match x: + case {'foo': k1, **rest}: + print(f'{k1=} {rest=}') + ''') diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_other.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_other.py new file mode 100644 index 0000000..7a02468 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_other.py @@ -0,0 +1,2142 @@ +""" +Tests for various Pyflakes behavior. +""" + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skip, skipIf + + +class Test(TestCase): + + def test_duplicateArgs(self): + self.flakes('def fu(bar, bar): pass', m.DuplicateArgument) + + def test_localReferencedBeforeAssignment(self): + self.flakes(''' + a = 1 + def f(): + a; a=1 + f() + ''', m.UndefinedLocal, m.UnusedVariable) + + @skipIf(version_info >= (3,), + 'in Python 3 list comprehensions execute in a separate scope') + def test_redefinedInListComp(self): + """ + Test that shadowing a variable in a list comprehension raises + a warning. + """ + self.flakes(''' + a = 1 + [1 for a, b in [(1, 2)]] + ''', m.RedefinedInListComp) + self.flakes(''' + class A: + a = 1 + [1 for a, b in [(1, 2)]] + ''', m.RedefinedInListComp) + self.flakes(''' + def f(): + a = 1 + [1 for a, b in [(1, 2)]] + ''', m.RedefinedInListComp) + self.flakes(''' + [1 for a, b in [(1, 2)]] + [1 for a, b in [(1, 2)]] + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + [1 for a, b in [(1, 2)]] + ''') + + def test_redefinedInGenerator(self): + """ + Test that reusing a variable in a generator does not raise + a warning. + """ + self.flakes(''' + a = 1 + (1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + class A: + a = 1 + list(1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + def f(): + a = 1 + (1 for a, b in [(1, 2)]) + ''', m.UnusedVariable) + self.flakes(''' + (1 for a, b in [(1, 2)]) + (1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + (1 for a, b in [(1, 2)]) + ''') + + def test_redefinedInSetComprehension(self): + """ + Test that reusing a variable in a set comprehension does not raise + a warning. + """ + self.flakes(''' + a = 1 + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + class A: + a = 1 + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + def f(): + a = 1 + {1 for a, b in [(1, 2)]} + ''', m.UnusedVariable) + self.flakes(''' + {1 for a, b in [(1, 2)]} + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + {1 for a, b in [(1, 2)]} + ''') + + def test_redefinedInDictComprehension(self): + """ + Test that reusing a variable in a dict comprehension does not raise + a warning. + """ + self.flakes(''' + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + class A: + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + def f(): + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''', m.UnusedVariable) + self.flakes(''' + {1: 42 for a, b in [(1, 2)]} + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + {1: 42 for a, b in [(1, 2)]} + ''') + + def test_redefinedFunction(self): + """ + Test that shadowing a function definition with another one raises a + warning. + """ + self.flakes(''' + def a(): pass + def a(): pass + ''', m.RedefinedWhileUnused) + + def test_redefinedUnderscoreFunction(self): + """ + Test that shadowing a function definition named with underscore doesn't + raise anything. + """ + self.flakes(''' + def _(): pass + def _(): pass + ''') + + def test_redefinedUnderscoreImportation(self): + """ + Test that shadowing an underscore importation raises a warning. + """ + self.flakes(''' + from .i18n import _ + def _(): pass + ''', m.RedefinedWhileUnused) + + def test_redefinedClassFunction(self): + """ + Test that shadowing a function definition in a class suite with another + one raises a warning. + """ + self.flakes(''' + class A: + def a(): pass + def a(): pass + ''', m.RedefinedWhileUnused) + + def test_redefinedIfElseFunction(self): + """ + Test that shadowing a function definition twice in an if + and else block does not raise a warning. + """ + self.flakes(''' + if True: + def a(): pass + else: + def a(): pass + ''') + + def test_redefinedIfFunction(self): + """ + Test that shadowing a function definition within an if block + raises a warning. + """ + self.flakes(''' + if True: + def a(): pass + def a(): pass + ''', m.RedefinedWhileUnused) + + def test_redefinedTryExceptFunction(self): + """ + Test that shadowing a function definition twice in try + and except block does not raise a warning. + """ + self.flakes(''' + try: + def a(): pass + except: + def a(): pass + ''') + + def test_redefinedTryFunction(self): + """ + Test that shadowing a function definition within a try block + raises a warning. + """ + self.flakes(''' + try: + def a(): pass + def a(): pass + except: + pass + ''', m.RedefinedWhileUnused) + + def test_redefinedIfElseInListComp(self): + """ + Test that shadowing a variable in a list comprehension in + an if and else block does not raise a warning. + """ + self.flakes(''' + if False: + a = 1 + else: + [a for a in '12'] + ''') + + @skipIf(version_info >= (3,), + 'in Python 3 list comprehensions execute in a separate scope') + def test_redefinedElseInListComp(self): + """ + Test that shadowing a variable in a list comprehension in + an else (or if) block raises a warning. + """ + self.flakes(''' + if False: + pass + else: + a = 1 + [a for a in '12'] + ''', m.RedefinedInListComp) + + def test_functionDecorator(self): + """ + Test that shadowing a function definition with a decorated version of + that function does not raise a warning. + """ + self.flakes(''' + from somewhere import somedecorator + + def a(): pass + a = somedecorator(a) + ''') + + def test_classFunctionDecorator(self): + """ + Test that shadowing a function definition in a class suite with a + decorated version of that function does not raise a warning. + """ + self.flakes(''' + class A: + def a(): pass + a = classmethod(a) + ''') + + def test_modernProperty(self): + self.flakes(""" + class A: + @property + def t(self): + pass + @t.setter + def t(self, value): + pass + @t.deleter + def t(self): + pass + """) + + def test_unaryPlus(self): + """Don't die on unary +.""" + self.flakes('+1') + + def test_undefinedBaseClass(self): + """ + If a name in the base list of a class definition is undefined, a + warning is emitted. + """ + self.flakes(''' + class foo(foo): + pass + ''', m.UndefinedName) + + def test_classNameUndefinedInClassBody(self): + """ + If a class name is used in the body of that class's definition and + the name is not already defined, a warning is emitted. + """ + self.flakes(''' + class foo: + foo + ''', m.UndefinedName) + + def test_classNameDefinedPreviously(self): + """ + If a class name is used in the body of that class's definition and + the name was previously defined in some other way, no warning is + emitted. + """ + self.flakes(''' + foo = None + class foo: + foo + ''') + + def test_classRedefinition(self): + """ + If a class is defined twice in the same module, a warning is emitted. + """ + self.flakes(''' + class Foo: + pass + class Foo: + pass + ''', m.RedefinedWhileUnused) + + def test_functionRedefinedAsClass(self): + """ + If a function is redefined as a class, a warning is emitted. + """ + self.flakes(''' + def Foo(): + pass + class Foo: + pass + ''', m.RedefinedWhileUnused) + + def test_classRedefinedAsFunction(self): + """ + If a class is redefined as a function, a warning is emitted. + """ + self.flakes(''' + class Foo: + pass + def Foo(): + pass + ''', m.RedefinedWhileUnused) + + def test_classWithReturn(self): + """ + If a return is used inside a class, a warning is emitted. + """ + self.flakes(''' + class Foo(object): + return + ''', m.ReturnOutsideFunction) + + def test_moduleWithReturn(self): + """ + If a return is used at the module level, a warning is emitted. + """ + self.flakes(''' + return + ''', m.ReturnOutsideFunction) + + def test_classWithYield(self): + """ + If a yield is used inside a class, a warning is emitted. + """ + self.flakes(''' + class Foo(object): + yield + ''', m.YieldOutsideFunction) + + def test_moduleWithYield(self): + """ + If a yield is used at the module level, a warning is emitted. + """ + self.flakes(''' + yield + ''', m.YieldOutsideFunction) + + @skipIf(version_info < (3, 3), "Python >= 3.3 only") + def test_classWithYieldFrom(self): + """ + If a yield from is used inside a class, a warning is emitted. + """ + self.flakes(''' + class Foo(object): + yield from range(10) + ''', m.YieldOutsideFunction) + + @skipIf(version_info < (3, 3), "Python >= 3.3 only") + def test_moduleWithYieldFrom(self): + """ + If a yield from is used at the module level, a warning is emitted. + """ + self.flakes(''' + yield from range(10) + ''', m.YieldOutsideFunction) + + def test_continueOutsideLoop(self): + self.flakes(''' + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + def f(): + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + pass + else: + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + pass + else: + if 1: + if 2: + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + def f(): + continue + ''', m.ContinueOutsideLoop) + + self.flakes(''' + while True: + class A: + continue + ''', m.ContinueOutsideLoop) + + def test_continueInsideLoop(self): + self.flakes(''' + while True: + continue + ''') + + self.flakes(''' + for i in range(10): + continue + ''') + + self.flakes(''' + while True: + if 1: + continue + ''') + + self.flakes(''' + for i in range(10): + if 1: + continue + ''') + + self.flakes(''' + while True: + while True: + pass + else: + continue + else: + pass + ''') + + self.flakes(''' + while True: + try: + pass + finally: + while True: + continue + ''') + + @skipIf(version_info > (3, 8), "Python <= 3.8 only") + def test_continueInFinally(self): + # 'continue' inside 'finally' is a special syntax error + # that is removed in 3.8 + self.flakes(''' + while True: + try: + pass + finally: + continue + ''', m.ContinueInFinally) + + self.flakes(''' + while True: + try: + pass + finally: + if 1: + if 2: + continue + ''', m.ContinueInFinally) + + # Even when not in a loop, this is the error Python gives + self.flakes(''' + try: + pass + finally: + continue + ''', m.ContinueInFinally) + + def test_breakOutsideLoop(self): + self.flakes(''' + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + def f(): + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + pass + else: + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + pass + else: + if 1: + if 2: + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + def f(): + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + while True: + class A: + break + ''', m.BreakOutsideLoop) + + self.flakes(''' + try: + pass + finally: + break + ''', m.BreakOutsideLoop) + + def test_breakInsideLoop(self): + self.flakes(''' + while True: + break + ''') + + self.flakes(''' + for i in range(10): + break + ''') + + self.flakes(''' + while True: + if 1: + break + ''') + + self.flakes(''' + for i in range(10): + if 1: + break + ''') + + self.flakes(''' + while True: + while True: + pass + else: + break + else: + pass + ''') + + self.flakes(''' + while True: + try: + pass + finally: + while True: + break + ''') + + self.flakes(''' + while True: + try: + pass + finally: + break + ''') + + self.flakes(''' + while True: + try: + pass + finally: + if 1: + if 2: + break + ''') + + def test_defaultExceptLast(self): + """ + A default except block should be last. + + YES: + + try: + ... + except Exception: + ... + except: + ... + + NO: + + try: + ... + except: + ... + except Exception: + ... + """ + self.flakes(''' + try: + pass + except ValueError: + pass + ''') + + self.flakes(''' + try: + pass + except ValueError: + pass + except: + pass + ''') + + self.flakes(''' + try: + pass + except: + pass + ''') + + self.flakes(''' + try: + pass + except ValueError: + pass + else: + pass + ''') + + self.flakes(''' + try: + pass + except: + pass + else: + pass + ''') + + self.flakes(''' + try: + pass + except ValueError: + pass + except: + pass + else: + pass + ''') + + def test_defaultExceptNotLast(self): + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + else: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + else: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + else: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + else: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + finally: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast) + + self.flakes(''' + try: + pass + except: + pass + except ValueError: + pass + except: + pass + except ValueError: + pass + else: + pass + finally: + pass + ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + + @skipIf(version_info < (3,), "Python 3 only") + def test_starredAssignmentNoError(self): + """ + Python 3 extended iterable unpacking + """ + self.flakes(''' + a, *b = range(10) + ''') + + self.flakes(''' + *a, b = range(10) + ''') + + self.flakes(''' + a, *b, c = range(10) + ''') + + self.flakes(''' + (a, *b) = range(10) + ''') + + self.flakes(''' + (*a, b) = range(10) + ''') + + self.flakes(''' + (a, *b, c) = range(10) + ''') + + self.flakes(''' + [a, *b] = range(10) + ''') + + self.flakes(''' + [*a, b] = range(10) + ''') + + self.flakes(''' + [a, *b, c] = range(10) + ''') + + # Taken from test_unpack_ex.py in the cPython source + s = ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ + ", *rest = range(1<<8)" + self.flakes(s) + + s = "(" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ + ", *rest) = range(1<<8)" + self.flakes(s) + + s = "[" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \ + ", *rest] = range(1<<8)" + self.flakes(s) + + @skipIf(version_info < (3, ), "Python 3 only") + def test_starredAssignmentErrors(self): + """ + SyntaxErrors (not encoded in the ast) surrounding Python 3 extended + iterable unpacking + """ + # Taken from test_unpack_ex.py in the cPython source + s = ", ".join("a%d" % i for i in range(1 << 8)) + \ + ", *rest = range(1<<8 + 1)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "(" + ", ".join("a%d" % i for i in range(1 << 8)) + \ + ", *rest) = range(1<<8 + 1)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "[" + ", ".join("a%d" % i for i in range(1 << 8)) + \ + ", *rest] = range(1<<8 + 1)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ + ", *rest = range(1<<8 + 2)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "(" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ + ", *rest) = range(1<<8 + 2)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + s = "[" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \ + ", *rest] = range(1<<8 + 2)" + self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + # No way we can actually test this! + # s = "*rest, " + ", ".join("a%d" % i for i in range(1<<24)) + \ + # ", *rest = range(1<<24 + 1)" + # self.flakes(s, m.TooManyExpressionsInStarredAssignment) + + self.flakes(''' + a, *b, *c = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + a, *b, c, *d = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + *a, *b, *c = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + (a, *b, *c) = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + (a, *b, c, *d) = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + (*a, *b, *c) = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + [a, *b, *c] = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + [a, *b, c, *d] = range(10) + ''', m.TwoStarredExpressions) + + self.flakes(''' + [*a, *b, *c] = range(10) + ''', m.TwoStarredExpressions) + + @skip("todo: Too hard to make this warn but other cases stay silent") + def test_doubleAssignment(self): + """ + If a variable is re-assigned to without being used, no warning is + emitted. + """ + self.flakes(''' + x = 10 + x = 20 + ''', m.RedefinedWhileUnused) + + def test_doubleAssignmentConditionally(self): + """ + If a variable is re-assigned within a conditional, no warning is + emitted. + """ + self.flakes(''' + x = 10 + if True: + x = 20 + ''') + + def test_doubleAssignmentWithUse(self): + """ + If a variable is re-assigned to after being used, no warning is + emitted. + """ + self.flakes(''' + x = 10 + y = x * 2 + x = 20 + ''') + + def test_comparison(self): + """ + If a defined name is used on either side of any of the six comparison + operators, no warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x < y + x <= y + x == y + x != y + x >= y + x > y + ''') + + def test_identity(self): + """ + If a defined name is used on either side of an identity test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x is y + x is not y + ''') + + def test_containment(self): + """ + If a defined name is used on either side of a containment test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x in y + x not in y + ''') + + def test_loopControl(self): + """ + break and continue statements are supported. + """ + self.flakes(''' + for x in [1, 2]: + break + ''') + self.flakes(''' + for x in [1, 2]: + continue + ''') + + def test_ellipsis(self): + """ + Ellipsis in a slice is supported. + """ + self.flakes(''' + [1, 2][...] + ''') + + def test_extendedSlice(self): + """ + Extended slices are supported. + """ + self.flakes(''' + x = 3 + [1, 2][x,:] + ''') + + def test_varAugmentedAssignment(self): + """ + Augmented assignment of a variable is supported. + We don't care about var refs. + """ + self.flakes(''' + foo = 0 + foo += 1 + ''') + + def test_attrAugmentedAssignment(self): + """ + Augmented assignment of attributes is supported. + We don't care about attr refs. + """ + self.flakes(''' + foo = None + foo.bar += foo.baz + ''') + + def test_globalDeclaredInDifferentScope(self): + """ + A 'global' can be declared in one scope and reused in another. + """ + self.flakes(''' + def f(): global foo + def g(): foo = 'anything'; foo.is_used() + ''') + + def test_function_arguments(self): + """ + Test to traverse ARG and ARGUMENT handler + """ + self.flakes(''' + def foo(a, b): + pass + ''') + + self.flakes(''' + def foo(a, b, c=0): + pass + ''') + + self.flakes(''' + def foo(a, b, c=0, *args): + pass + ''') + + self.flakes(''' + def foo(a, b, c=0, *args, **kwargs): + pass + ''') + + @skipIf(version_info < (3, 3), "Python >= 3.3 only") + def test_function_arguments_python3(self): + self.flakes(''' + def foo(a, b, c=0, *args, d=0, **kwargs): + pass + ''') + + +class TestUnusedAssignment(TestCase): + """ + Tests for warning about unused assignments. + """ + + def test_unusedVariable(self): + """ + Warn when a variable in a function is assigned a value that's never + used. + """ + self.flakes(''' + def a(): + b = 1 + ''', m.UnusedVariable) + + def test_unusedUnderscoreVariable(self): + """ + Don't warn when the magic "_" (underscore) variable is unused. + See issue #202. + """ + self.flakes(''' + def a(unused_param): + _ = unused_param + ''') + + def test_unusedVariableAsLocals(self): + """ + Using locals() it is perfectly valid to have unused variables + """ + self.flakes(''' + def a(): + b = 1 + return locals() + ''') + + def test_unusedVariableNoLocals(self): + """ + Using locals() in wrong scope should not matter + """ + self.flakes(''' + def a(): + locals() + def a(): + b = 1 + return + ''', m.UnusedVariable) + + @skip("todo: Difficult because it doesn't apply in the context of a loop") + def test_unusedReassignedVariable(self): + """ + Shadowing a used variable can still raise an UnusedVariable warning. + """ + self.flakes(''' + def a(): + b = 1 + b.foo() + b = 2 + ''', m.UnusedVariable) + + def test_variableUsedInLoop(self): + """ + Shadowing a used variable cannot raise an UnusedVariable warning in the + context of a loop. + """ + self.flakes(''' + def a(): + b = True + while b: + b = False + ''') + + def test_assignToGlobal(self): + """ + Assigning to a global and then not using that global is perfectly + acceptable. Do not mistake it for an unused local variable. + """ + self.flakes(''' + b = 0 + def a(): + global b + b = 1 + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_assignToNonlocal(self): + """ + Assigning to a nonlocal and then not using that binding is perfectly + acceptable. Do not mistake it for an unused local variable. + """ + self.flakes(''' + b = b'0' + def a(): + nonlocal b + b = b'1' + ''') + + def test_assignToMember(self): + """ + Assigning to a member of another object and then not using that member + variable is perfectly acceptable. Do not mistake it for an unused + local variable. + """ + # XXX: Adding this test didn't generate a failure. Maybe not + # necessary? + self.flakes(''' + class b: + pass + def a(): + b.foo = 1 + ''') + + def test_assignInForLoop(self): + """ + Don't warn when a variable in a for loop is assigned to but not used. + """ + self.flakes(''' + def f(): + for i in range(10): + pass + ''') + + def test_assignInListComprehension(self): + """ + Don't warn when a variable in a list comprehension is + assigned to but not used. + """ + self.flakes(''' + def f(): + [None for i in range(10)] + ''') + + def test_generatorExpression(self): + """ + Don't warn when a variable in a generator expression is + assigned to but not used. + """ + self.flakes(''' + def f(): + (None for i in range(10)) + ''') + + def test_assignmentInsideLoop(self): + """ + Don't warn when a variable assignment occurs lexically after its use. + """ + self.flakes(''' + def f(): + x = None + for i in range(10): + if i > 2: + return x + x = i * 2 + ''') + + def test_tupleUnpacking(self): + """ + Don't warn when a variable included in tuple unpacking is unused. It's + very common for variables in a tuple unpacking assignment to be unused + in good Python code, so warning will only create false positives. + """ + self.flakes(''' + def f(tup): + (x, y) = tup + ''') + self.flakes(''' + def f(): + (x, y) = 1, 2 + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + if x > 1: + print(coords) + ''') + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + ''', m.UnusedVariable) + self.flakes(''' + def f(): + coords = (x, y) = 1, 2 + ''', m.UnusedVariable) + + def test_listUnpacking(self): + """ + Don't warn when a variable included in list unpacking is unused. + """ + self.flakes(''' + def f(tup): + [x, y] = tup + ''') + self.flakes(''' + def f(): + [x, y] = [1, 2] + ''', m.UnusedVariable, m.UnusedVariable) + + def test_closedOver(self): + """ + Don't warn when the assignment is used in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + return foo + return bar + ''') + + def test_doubleClosedOver(self): + """ + Don't warn when the assignment is used in an inner function, even if + that inner function itself is in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + def baz(): + return foo + return bar + ''') + + def test_tracebackhideSpecialVariable(self): + """ + Do not warn about unused local variable __tracebackhide__, which is + a special variable for py.test. + """ + self.flakes(""" + def helper(): + __tracebackhide__ = True + """) + + def test_ifexp(self): + """ + Test C{foo if bar else baz} statements. + """ + self.flakes("a = 'moo' if True else 'oink'") + self.flakes("a = foo if True else 'oink'", m.UndefinedName) + self.flakes("a = 'moo' if True else bar", m.UndefinedName) + + def test_if_tuple(self): + """ + Test C{if (foo,)} conditions. + """ + self.flakes("""if (): pass""") + self.flakes(""" + if ( + True + ): + pass + """) + self.flakes(""" + if ( + True, + ): + pass + """, m.IfTuple) + self.flakes(""" + x = 1 if ( + True, + ) else 2 + """, m.IfTuple) + + def test_withStatementNoNames(self): + """ + No warnings are emitted for using inside or after a nameless C{with} + statement a name defined beforehand. + """ + self.flakes(''' + from __future__ import with_statement + bar = None + with open("foo"): + bar + bar + ''') + + def test_withStatementSingleName(self): + """ + No warnings are emitted for using a name defined by a C{with} statement + within the suite or afterwards. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as bar: + bar + bar + ''') + + def test_withStatementAttributeName(self): + """ + No warnings are emitted for using an attribute as the target of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo.bar: + pass + ''') + + def test_withStatementSubscript(self): + """ + No warnings are emitted for using a subscript as the target of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo[0]: + pass + ''') + + def test_withStatementSubscriptUndefined(self): + """ + An undefined name warning is emitted if the subscript used as the + target of a C{with} statement is not defined. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo[bar]: + pass + ''', m.UndefinedName) + + def test_withStatementTupleNames(self): + """ + No warnings are emitted for using any of the tuple of names defined by + a C{with} statement within the suite or afterwards. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as (bar, baz): + bar, baz + bar, baz + ''') + + def test_withStatementListNames(self): + """ + No warnings are emitted for using any of the list of names defined by a + C{with} statement within the suite or afterwards. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as [bar, baz]: + bar, baz + bar, baz + ''') + + def test_withStatementComplicatedTarget(self): + """ + If the target of a C{with} statement uses any or all of the valid forms + for that part of the grammar (See + U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}), + the names involved are checked both for definedness and any bindings + created are respected in the suite of the statement and afterwards. + """ + self.flakes(''' + from __future__ import with_statement + c = d = e = g = h = i = None + with open('foo') as [(a, b), c[d], e.f, g[h:i]]: + a, b, c, d, e, g, h, i + a, b, c, d, e, g, h, i + ''') + + def test_withStatementSingleNameUndefined(self): + """ + An undefined name warning is emitted if the name first defined by a + C{with} statement is used before the C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + bar + with open('foo') as bar: + pass + ''', m.UndefinedName) + + def test_withStatementTupleNamesUndefined(self): + """ + An undefined name warning is emitted if a name first defined by the + tuple-unpacking form of the C{with} statement is used before the + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + baz + with open('foo') as (bar, baz): + pass + ''', m.UndefinedName) + + def test_withStatementSingleNameRedefined(self): + """ + A redefined name warning is emitted if a name bound by an import is + rebound by the name defined by a C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import bar + with open('foo') as bar: + pass + ''', m.RedefinedWhileUnused) + + def test_withStatementTupleNamesRedefined(self): + """ + A redefined name warning is emitted if a name bound by an import is + rebound by one of the names defined by the tuple-unpacking form of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import bar + with open('foo') as (bar, baz): + pass + ''', m.RedefinedWhileUnused) + + def test_withStatementUndefinedInside(self): + """ + An undefined name warning is emitted if a name is used inside the + body of a C{with} statement without first being bound. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as bar: + baz + ''', m.UndefinedName) + + def test_withStatementNameDefinedInBody(self): + """ + A name defined in the body of a C{with} statement can be used after + the body ends without warning. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as bar: + baz = 10 + baz + ''') + + def test_withStatementUndefinedInExpression(self): + """ + An undefined name warning is emitted if a name in the I{test} + expression of a C{with} statement is undefined. + """ + self.flakes(''' + from __future__ import with_statement + with bar as baz: + pass + ''', m.UndefinedName) + + self.flakes(''' + from __future__ import with_statement + with bar as bar: + pass + ''', m.UndefinedName) + + def test_dictComprehension(self): + """ + Dict comprehensions are properly handled. + """ + self.flakes(''' + a = {1: x for x in range(10)} + ''') + + def test_setComprehensionAndLiteral(self): + """ + Set comprehensions are properly handled. + """ + self.flakes(''' + a = {1, 2, 3} + b = {x for x in range(10)} + ''') + + def test_exceptionUsedInExcept(self): + self.flakes(''' + try: pass + except Exception as e: e + ''') + + self.flakes(''' + def download_review(): + try: pass + except Exception as e: e + ''') + + @skipIf(version_info < (3,), + "In Python 2 exception names stay bound after the exception handler") + def test_exceptionUnusedInExcept(self): + self.flakes(''' + try: pass + except Exception as e: pass + ''', m.UnusedVariable) + + def test_exceptionUnusedInExceptInFunction(self): + self.flakes(''' + def download_review(): + try: pass + except Exception as e: pass + ''', m.UnusedVariable) + + def test_exceptWithoutNameInFunction(self): + """ + Don't issue false warning when an unnamed exception is used. + Previously, there would be a false warning, but only when the + try..except was in a function + """ + self.flakes(''' + import tokenize + def foo(): + try: pass + except tokenize.TokenError: pass + ''') + + def test_exceptWithoutNameInFunctionTuple(self): + """ + Don't issue false warning when an unnamed exception is used. + This example catches a tuple of exception types. + """ + self.flakes(''' + import tokenize + def foo(): + try: pass + except (tokenize.TokenError, IndentationError): pass + ''') + + def test_augmentedAssignmentImportedFunctionCall(self): + """ + Consider a function that is called on the right part of an + augassign operation to be used. + """ + self.flakes(''' + from foo import bar + baz = 0 + baz += bar() + ''') + + def test_assert_without_message(self): + """An assert without a message is not an error.""" + self.flakes(''' + a = 1 + assert a + ''') + + def test_assert_with_message(self): + """An assert with a message is not an error.""" + self.flakes(''' + a = 1 + assert a, 'x' + ''') + + def test_assert_tuple(self): + """An assert of a non-empty tuple is always True.""" + self.flakes(''' + assert (False, 'x') + assert (False, ) + ''', m.AssertTuple, m.AssertTuple) + + def test_assert_tuple_empty(self): + """An assert of an empty tuple is always False.""" + self.flakes(''' + assert () + ''') + + def test_assert_static(self): + """An assert of a static value is not an error.""" + self.flakes(''' + assert True + assert 1 + ''') + + @skipIf(version_info < (3, 3), 'new in Python 3.3') + def test_yieldFromUndefined(self): + """ + Test C{yield from} statement + """ + self.flakes(''' + def bar(): + yield from foo() + ''', m.UndefinedName) + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_f_string(self): + """Test PEP 498 f-strings are treated as a usage.""" + self.flakes(''' + baz = 0 + print(f'\x7b4*baz\N{RIGHT CURLY BRACKET}') + ''') + + @skipIf(version_info < (3, 8), 'new in Python 3.8') + def test_assign_expr(self): + """Test PEP 572 assignment expressions are treated as usage / write.""" + self.flakes(''' + from foo import y + print(x := y) + print(x) + ''') + + +class TestStringFormatting(TestCase): + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_f_string_without_placeholders(self): + self.flakes("f'foo'", m.FStringMissingPlaceholders) + self.flakes(''' + f"""foo + bar + """ + ''', m.FStringMissingPlaceholders) + self.flakes(''' + print( + f'foo' + f'bar' + ) + ''', m.FStringMissingPlaceholders) + # this is an "escaped placeholder" but not a placeholder + self.flakes("f'{{}}'", m.FStringMissingPlaceholders) + # ok: f-string with placeholders + self.flakes(''' + x = 5 + print(f'{x}') + ''') + # ok: f-string with format specifiers + self.flakes(''' + x = 'a' * 90 + print(f'{x:.8}') + ''') + # ok: f-string with multiple format specifiers + self.flakes(''' + x = y = 5 + print(f'{x:>2} {y:>2}') + ''') + + def test_invalid_dot_format_calls(self): + self.flakes(''' + '{'.format(1) + ''', m.StringDotFormatInvalidFormat) + self.flakes(''' + '{} {1}'.format(1, 2) + ''', m.StringDotFormatMixingAutomatic) + self.flakes(''' + '{0} {}'.format(1, 2) + ''', m.StringDotFormatMixingAutomatic) + self.flakes(''' + '{}'.format(1, 2) + ''', m.StringDotFormatExtraPositionalArguments) + self.flakes(''' + '{}'.format(1, bar=2) + ''', m.StringDotFormatExtraNamedArguments) + self.flakes(''' + '{} {}'.format(1) + ''', m.StringDotFormatMissingArgument) + self.flakes(''' + '{2}'.format() + ''', m.StringDotFormatMissingArgument) + self.flakes(''' + '{bar}'.format() + ''', m.StringDotFormatMissingArgument) + # too much string recursion (placeholder-in-placeholder) + self.flakes(''' + '{:{:{}}}'.format(1, 2, 3) + ''', m.StringDotFormatInvalidFormat) + # ok: dotted / bracketed names need to handle the param differently + self.flakes("'{.__class__}'.format('')") + self.flakes("'{foo[bar]}'.format(foo={'bar': 'barv'})") + # ok: placeholder-placeholders + self.flakes(''' + print('{:{}} {}'.format(1, 15, 2)) + ''') + # ok: not a placeholder-placeholder + self.flakes(''' + print('{:2}'.format(1)) + ''') + # ok: not mixed automatic + self.flakes(''' + '{foo}-{}'.format(1, foo=2) + ''') + # ok: we can't determine statically the format args + self.flakes(''' + a = () + "{}".format(*a) + ''') + self.flakes(''' + k = {} + "{foo}".format(**k) + ''') + + def test_invalid_percent_format_calls(self): + self.flakes(''' + '%(foo)' % {'foo': 'bar'} + ''', m.PercentFormatInvalidFormat) + self.flakes(''' + '%s %(foo)s' % {'foo': 'bar'} + ''', m.PercentFormatMixedPositionalAndNamed) + self.flakes(''' + '%(foo)s %s' % {'foo': 'bar'} + ''', m.PercentFormatMixedPositionalAndNamed) + self.flakes(''' + '%j' % (1,) + ''', m.PercentFormatUnsupportedFormatCharacter) + self.flakes(''' + '%s %s' % (1,) + ''', m.PercentFormatPositionalCountMismatch) + self.flakes(''' + '%s %s' % (1, 2, 3) + ''', m.PercentFormatPositionalCountMismatch) + self.flakes(''' + '%(bar)s' % {} + ''', m.PercentFormatMissingArgument,) + self.flakes(''' + '%(bar)s' % {'bar': 1, 'baz': 2} + ''', m.PercentFormatExtraNamedArguments) + self.flakes(''' + '%(bar)s' % (1, 2, 3) + ''', m.PercentFormatExpectedMapping) + self.flakes(''' + '%s %s' % {'k': 'v'} + ''', m.PercentFormatExpectedSequence) + self.flakes(''' + '%(bar)*s' % {'bar': 'baz'} + ''', m.PercentFormatStarRequiresSequence) + # ok: single %s with mapping + self.flakes(''' + '%s' % {'foo': 'bar', 'baz': 'womp'} + ''') + # ok: does not cause a MemoryError (the strings aren't evaluated) + self.flakes(''' + "%1000000000000f" % 1 + ''') + # ok: %% should not count towards placeholder count + self.flakes(''' + '%% %s %% %s' % (1, 2) + ''') + # ok: * consumes one positional argument + self.flakes(''' + '%.*f' % (2, 1.1234) + '%*.*f' % (5, 2, 3.1234) + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_ok_percent_format_cannot_determine_element_count(self): + self.flakes(''' + a = [] + '%s %s' % [*a] + '%s %s' % (*a,) + ''') + self.flakes(''' + k = {} + '%(k)s' % {**k} + ''') + + +class TestAsyncStatements(TestCase): + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_asyncDef(self): + self.flakes(''' + async def bar(): + return 42 + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_asyncDefAwait(self): + self.flakes(''' + async def read_data(db): + await db.fetch('SELECT ...') + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_asyncDefUndefined(self): + self.flakes(''' + async def bar(): + return foo() + ''', m.UndefinedName) + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_asyncFor(self): + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + output.append(row) + return output + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_asyncForUnderscoreLoopVar(self): + self.flakes(''' + async def coro(it): + async for _ in it: + pass + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_loopControlInAsyncFor(self): + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + if row[0] == 'skip': + continue + output.append(row) + return output + ''') + + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + if row[0] == 'stop': + break + output.append(row) + return output + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_loopControlInAsyncForElse(self): + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + output.append(row) + else: + continue + return output + ''', m.ContinueOutsideLoop) + + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + output.append(row) + else: + break + return output + ''', m.BreakOutsideLoop) + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + @skipIf(version_info > (3, 8), "Python <= 3.8 only") + def test_continueInAsyncForFinally(self): + self.flakes(''' + async def read_data(db): + output = [] + async for row in db.cursor(): + try: + output.append(row) + finally: + continue + return output + ''', m.ContinueInFinally) + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_asyncWith(self): + self.flakes(''' + async def commit(session, data): + async with session.transaction(): + await session.update(data) + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_asyncWithItem(self): + self.flakes(''' + async def commit(session, data): + async with session.transaction() as trans: + await trans.begin() + ... + await trans.end() + ''') + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_matmul(self): + self.flakes(''' + def foo(a, b): + return a @ b + ''') + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_formatstring(self): + self.flakes(''' + hi = 'hi' + mom = 'mom' + f'{hi} {mom}' + ''') + + def test_raise_notimplemented(self): + self.flakes(''' + raise NotImplementedError("This is fine") + ''') + + self.flakes(''' + raise NotImplementedError + ''') + + self.flakes(''' + raise NotImplemented("This isn't gonna work") + ''', m.RaiseNotImplemented) + + self.flakes(''' + raise NotImplemented + ''', m.RaiseNotImplemented) + + +class TestIncompatiblePrintOperator(TestCase): + """ + Tests for warning about invalid use of print function. + """ + + def test_valid_print(self): + self.flakes(''' + print("Hello") + ''') + + def test_invalid_print_when_imported_from_future(self): + exc = self.flakes(''' + from __future__ import print_function + import sys + print >>sys.stderr, "Hello" + ''', m.InvalidPrintSyntax).messages[0] + + self.assertEqual(exc.lineno, 4) + self.assertEqual(exc.col, 0) + + def test_print_function_assignment(self): + """ + A valid assignment, tested for catching false positives. + """ + self.flakes(''' + from __future__ import print_function + log = print + log("Hello") + ''') + + def test_print_in_lambda(self): + self.flakes(''' + from __future__ import print_function + a = lambda: print + ''') + + def test_print_returned_in_function(self): + self.flakes(''' + from __future__ import print_function + def a(): + return print + ''') + + def test_print_as_condition_test(self): + self.flakes(''' + from __future__ import print_function + if print: pass + ''') diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_return_with_arguments_inside_generator.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_return_with_arguments_inside_generator.py new file mode 100644 index 0000000..fc1272a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_return_with_arguments_inside_generator.py @@ -0,0 +1,34 @@ + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skipIf + + +class Test(TestCase): + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_return(self): + self.flakes(''' + class a: + def b(): + for x in a.c: + if x: + yield x + return a + ''', m.ReturnWithArgsInsideGenerator) + + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_returnNone(self): + self.flakes(''' + def a(): + yield 12 + return None + ''', m.ReturnWithArgsInsideGenerator) + + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_returnYieldExpression(self): + self.flakes(''' + def a(): + b = yield a + return b + ''', m.ReturnWithArgsInsideGenerator) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_type_annotations.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_type_annotations.py new file mode 100644 index 0000000..f3b6c24 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_type_annotations.py @@ -0,0 +1,763 @@ +""" +Tests for behaviour related to type annotations. +""" + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skipIf + + +class TestTypeAnnotations(TestCase): + + def test_typingOverload(self): + """Allow intentional redefinitions via @typing.overload""" + self.flakes(""" + import typing + from typing import overload + + @overload + def f(s): # type: (None) -> None + pass + + @overload + def f(s): # type: (int) -> int + pass + + def f(s): + return s + + @typing.overload + def g(s): # type: (None) -> None + pass + + @typing.overload + def g(s): # type: (int) -> int + pass + + def g(s): + return s + """) + + def test_typingExtensionsOverload(self): + """Allow intentional redefinitions via @typing_extensions.overload""" + self.flakes(""" + import typing_extensions + from typing_extensions import overload + + @overload + def f(s): # type: (None) -> None + pass + + @overload + def f(s): # type: (int) -> int + pass + + def f(s): + return s + + @typing_extensions.overload + def g(s): # type: (None) -> None + pass + + @typing_extensions.overload + def g(s): # type: (int) -> int + pass + + def g(s): + return s + """) + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_typingOverloadAsync(self): + """Allow intentional redefinitions via @typing.overload (async)""" + self.flakes(""" + from typing import overload + + @overload + async def f(s): # type: (None) -> None + pass + + @overload + async def f(s): # type: (int) -> int + pass + + async def f(s): + return s + """) + + def test_overload_with_multiple_decorators(self): + self.flakes(""" + from typing import overload + dec = lambda f: f + + @dec + @overload + def f(x): # type: (int) -> int + pass + + @dec + @overload + def f(x): # type: (str) -> str + pass + + @dec + def f(x): return x + """) + + def test_overload_in_class(self): + self.flakes(""" + from typing import overload + + class C: + @overload + def f(self, x): # type: (int) -> int + pass + + @overload + def f(self, x): # type: (str) -> str + pass + + def f(self, x): return x + """) + + def test_aliased_import(self): + """Detect when typing is imported as another name""" + self.flakes(""" + import typing as t + + @t.overload + def f(s): # type: (None) -> None + pass + + @t.overload + def f(s): # type: (int) -> int + pass + + def f(s): + return s + """) + + def test_not_a_typing_overload(self): + """regression test for @typing.overload detection bug in 2.1.0""" + self.flakes(""" + def foo(x): + return x + + @foo + def bar(): + pass + + def bar(): + pass + """, m.RedefinedWhileUnused) + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_variable_annotations(self): + self.flakes(''' + name: str + age: int + ''') + self.flakes(''' + name: str = 'Bob' + age: int = 18 + ''') + self.flakes(''' + class C: + name: str + age: int + ''') + self.flakes(''' + class C: + name: str = 'Bob' + age: int = 18 + ''') + self.flakes(''' + def f(): + name: str + age: int + ''') + self.flakes(''' + def f(): + name: str = 'Bob' + age: int = 18 + foo: not_a_real_type = None + ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable, m.UndefinedName) + self.flakes(''' + def f(): + name: str + print(name) + ''', m.UndefinedName) + self.flakes(''' + from typing import Any + def f(): + a: Any + ''') + self.flakes(''' + foo: not_a_real_type + ''', m.UndefinedName) + self.flakes(''' + foo: not_a_real_type = None + ''', m.UndefinedName) + self.flakes(''' + class C: + foo: not_a_real_type + ''', m.UndefinedName) + self.flakes(''' + class C: + foo: not_a_real_type = None + ''', m.UndefinedName) + self.flakes(''' + def f(): + class C: + foo: not_a_real_type + ''', m.UndefinedName) + self.flakes(''' + def f(): + class C: + foo: not_a_real_type = None + ''', m.UndefinedName) + self.flakes(''' + from foo import Bar + bar: Bar + ''') + self.flakes(''' + from foo import Bar + bar: 'Bar' + ''') + self.flakes(''' + import foo + bar: foo.Bar + ''') + self.flakes(''' + import foo + bar: 'foo.Bar' + ''') + self.flakes(''' + from foo import Bar + def f(bar: Bar): pass + ''') + self.flakes(''' + from foo import Bar + def f(bar: 'Bar'): pass + ''') + self.flakes(''' + from foo import Bar + def f(bar) -> Bar: return bar + ''') + self.flakes(''' + from foo import Bar + def f(bar) -> 'Bar': return bar + ''') + self.flakes(''' + bar: 'Bar' + ''', m.UndefinedName) + self.flakes(''' + bar: 'foo.Bar' + ''', m.UndefinedName) + self.flakes(''' + from foo import Bar + bar: str + ''', m.UnusedImport) + self.flakes(''' + from foo import Bar + def f(bar: str): pass + ''', m.UnusedImport) + self.flakes(''' + def f(a: A) -> A: pass + class A: pass + ''', m.UndefinedName, m.UndefinedName) + self.flakes(''' + def f(a: 'A') -> 'A': return a + class A: pass + ''') + self.flakes(''' + a: A + class A: pass + ''', m.UndefinedName) + self.flakes(''' + a: 'A' + class A: pass + ''') + self.flakes(''' + T: object + def f(t: T): pass + ''', m.UndefinedName) + self.flakes(''' + T: object + def g(t: 'T'): pass + ''') + self.flakes(''' + a: 'A B' + ''', m.ForwardAnnotationSyntaxError) + self.flakes(''' + a: 'A; B' + ''', m.ForwardAnnotationSyntaxError) + self.flakes(''' + a: '1 + 2' + ''') + self.flakes(''' + a: 'a: "A"' + ''', m.ForwardAnnotationSyntaxError) + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_annotating_an_import(self): + self.flakes(''' + from a import b, c + b: c + print(b) + ''') + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_unused_annotation(self): + # Unused annotations are fine in module and class scope + self.flakes(''' + x: int + class Cls: + y: int + ''') + # TODO: this should print a UnusedVariable message + self.flakes(''' + def f(): + x: int + ''') + # This should only print one UnusedVariable message + self.flakes(''' + def f(): + x: int + x = 3 + ''', m.UnusedVariable) + + @skipIf(version_info < (3, 5), 'new in Python 3.5') + def test_annotated_async_def(self): + self.flakes(''' + class c: pass + async def func(c: c) -> None: pass + ''') + + @skipIf(version_info < (3, 7), 'new in Python 3.7') + def test_postponed_annotations(self): + self.flakes(''' + from __future__ import annotations + def f(a: A) -> A: pass + class A: + b: B + class B: pass + ''') + + self.flakes(''' + from __future__ import annotations + def f(a: A) -> A: pass + class A: + b: Undefined + class B: pass + ''', m.UndefinedName) + + self.flakes(''' + from __future__ import annotations + T: object + def f(t: T): pass + def g(t: 'T'): pass + ''') + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_type_annotation_clobbers_all(self): + self.flakes('''\ + from typing import TYPE_CHECKING, List + + from y import z + + if not TYPE_CHECKING: + __all__ = ("z",) + else: + __all__: List[str] + ''') + + def test_typeCommentsMarkImportsAsUsed(self): + self.flakes(""" + from mod import A, B, C, D, E, F, G + + + def f( + a, # type: A + ): + # type: (...) -> B + for b in a: # type: C + with b as c: # type: D + d = c.x # type: E + return d + + + def g(x): # type: (F) -> G + return x.y + """) + + def test_typeCommentsFullSignature(self): + self.flakes(""" + from mod import A, B, C, D + def f(a, b): + # type: (A, B[C]) -> D + return a + b + """) + + def test_typeCommentsStarArgs(self): + self.flakes(""" + from mod import A, B, C, D + def f(a, *b, **c): + # type: (A, *B, **C) -> D + return a + b + """) + + def test_typeCommentsFullSignatureWithDocstring(self): + self.flakes(''' + from mod import A, B, C, D + def f(a, b): + # type: (A, B[C]) -> D + """do the thing!""" + return a + b + ''') + + def test_typeCommentsAdditionalComment(self): + self.flakes(""" + from mod import F + + x = 1 # type: F # noqa + """) + + def test_typeCommentsNoWhitespaceAnnotation(self): + self.flakes(""" + from mod import F + + x = 1 #type:F + """) + + def test_typeCommentsInvalidDoesNotMarkAsUsed(self): + self.flakes(""" + from mod import F + + # type: F + """, m.UnusedImport) + + def test_typeCommentsSyntaxError(self): + self.flakes(""" + def f(x): # type: (F[) -> None + pass + """, m.CommentAnnotationSyntaxError) + + def test_typeCommentsSyntaxErrorCorrectLine(self): + checker = self.flakes("""\ + x = 1 + # type: definitely not a PEP 484 comment + """, m.CommentAnnotationSyntaxError) + self.assertEqual(checker.messages[0].lineno, 2) + + def test_typeCommentsAssignedToPreviousNode(self): + # This test demonstrates an issue in the implementation which + # associates the type comment with a node above it, however the type + # comment isn't valid according to mypy. If an improved approach + # which can detect these "invalid" type comments is implemented, this + # test should be removed / improved to assert that new check. + self.flakes(""" + from mod import F + x = 1 + # type: F + """) + + def test_typeIgnore(self): + self.flakes(""" + a = 0 # type: ignore + b = 0 # type: ignore[excuse] + c = 0 # type: ignore=excuse + d = 0 # type: ignore [excuse] + e = 0 # type: ignore whatever + """) + + def test_typeIgnoreBogus(self): + self.flakes(""" + x = 1 # type: ignored + """, m.UndefinedName) + + def test_typeIgnoreBogusUnicode(self): + error = (m.CommentAnnotationSyntaxError if version_info < (3,) + else m.UndefinedName) + self.flakes(""" + x = 2 # type: ignore\xc3 + """, error) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_return_annotation_is_class_scope_variable(self): + self.flakes(""" + from typing import TypeVar + class Test: + Y = TypeVar('Y') + + def t(self, x: Y) -> Y: + return x + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_return_annotation_is_function_body_variable(self): + self.flakes(""" + class Test: + def t(self) -> Y: + Y = 2 + return Y + """, m.UndefinedName) + + @skipIf(version_info < (3, 8), 'new in Python 3.8') + def test_positional_only_argument_annotations(self): + self.flakes(""" + from x import C + + def f(c: C, /): ... + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_partially_quoted_type_annotation(self): + self.flakes(""" + from queue import Queue + from typing import Optional + + def f() -> Optional['Queue[str]']: + return None + """) + + def test_partially_quoted_type_assignment(self): + self.flakes(""" + from queue import Queue + from typing import Optional + + MaybeQueue = Optional['Queue[str]'] + """) + + def test_nested_partially_quoted_type_assignment(self): + self.flakes(""" + from queue import Queue + from typing import Callable + + Func = Callable[['Queue[str]'], None] + """) + + def test_quoted_type_cast(self): + self.flakes(""" + from typing import cast, Optional + + maybe_int = cast('Optional[int]', 42) + """) + + def test_type_cast_literal_str_to_str(self): + # Checks that our handling of quoted type annotations in the first + # argument to `cast` doesn't cause issues when (only) the _second_ + # argument is a literal str which looks a bit like a type annotation. + self.flakes(""" + from typing import cast + + a_string = cast(str, 'Optional[int]') + """) + + def test_quoted_type_cast_renamed_import(self): + self.flakes(""" + from typing import cast as tsac, Optional as Maybe + + maybe_int = tsac('Maybe[int]', 42) + """) + + def test_quoted_TypeVar_constraints(self): + self.flakes(""" + from typing import TypeVar, Optional + + T = TypeVar('T', 'str', 'Optional[int]', bytes) + """) + + def test_quoted_TypeVar_bound(self): + self.flakes(""" + from typing import TypeVar, Optional, List + + T = TypeVar('T', bound='Optional[int]') + S = TypeVar('S', int, bound='List[int]') + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_literal_type_typing(self): + self.flakes(""" + from typing import Literal + + def f(x: Literal['some string']) -> None: + return None + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_literal_type_typing_extensions(self): + self.flakes(""" + from typing_extensions import Literal + + def f(x: Literal['some string']) -> None: + return None + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_missing_forward_type(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated['integer']) -> None: + return None + """, m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_missing_forward_type_multiple_args(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated['integer', 1]) -> None: + return None + """, m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_with_string_args(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated[int, '> 0']) -> None: + return None + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_with_string_args_in_union(self): + self.flakes(""" + from typing import Annotated, Union + + def f(x: Union[Annotated['int', '>0'], 'integer']) -> None: + return None + """, m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_literal_type_some_other_module(self): + """err on the side of false-negatives for types named Literal""" + self.flakes(""" + from my_module import compat + from my_module.compat import Literal + + def f(x: compat.Literal['some string']) -> None: + return None + def g(x: Literal['some string']) -> None: + return None + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_literal_union_type_typing(self): + self.flakes(""" + from typing import Literal + + def f(x: Literal['some string', 'foo bar']) -> None: + return None + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_deferred_twice_annotation(self): + self.flakes(""" + from queue import Queue + from typing import Optional + + + def f() -> "Optional['Queue[str]']": + return None + """) + + @skipIf(version_info < (3, 7), 'new in Python 3.7') + def test_partial_string_annotations_with_future_annotations(self): + self.flakes(""" + from __future__ import annotations + + from queue import Queue + from typing import Optional + + + def f() -> Optional['Queue[str]']: + return None + """) + + def test_idomiatic_typing_guards(self): + # typing.TYPE_CHECKING: python3.5.3+ + self.flakes(""" + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from t import T + + def f(): # type: () -> T + pass + """) + # False: the old, more-compatible approach + self.flakes(""" + if False: + from t import T + + def f(): # type: () -> T + pass + """) + # some choose to assign a constant and do it that way + self.flakes(""" + MYPY = False + + if MYPY: + from t import T + + def f(): # type: () -> T + pass + """) + + def test_typing_guard_for_protocol(self): + self.flakes(""" + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from typing import Protocol + else: + Protocol = object + + class C(Protocol): + def f(): # type: () -> int + pass + """) + + def test_typednames_correct_forward_ref(self): + self.flakes(""" + from typing import TypedDict, List, NamedTuple + + List[TypedDict("x", {})] + List[TypedDict("x", x=int)] + List[NamedTuple("a", a=int)] + List[NamedTuple("a", [("a", int)])] + """) + self.flakes(""" + from typing import TypedDict, List, NamedTuple, TypeVar + + List[TypedDict("x", {"x": "Y"})] + List[TypedDict("x", x="Y")] + List[NamedTuple("a", [("a", "Y")])] + List[NamedTuple("a", a="Y")] + List[TypedDict("x", {"x": List["a"]})] + List[TypeVar("A", bound="C")] + List[TypeVar("A", List["C"])] + """, *[m.UndefinedName]*7) + self.flakes(""" + from typing import NamedTuple, TypeVar, cast + from t import A, B, C, D, E + + NamedTuple("A", [("a", A["C"])]) + TypeVar("A", bound=A["B"]) + TypeVar("A", A["D"]) + cast(A["E"], []) + """) + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_namedtypes_classes(self): + self.flakes(""" + from typing import TypedDict, NamedTuple + class X(TypedDict): + y: TypedDict("z", {"zz":int}) + + class Y(NamedTuple): + y: NamedTuple("v", [("vv", int)]) + """) diff --git a/.venv/lib/python3.8/site-packages/pyflakes/test/test_undefined_names.py b/.venv/lib/python3.8/site-packages/pyflakes/test/test_undefined_names.py new file mode 100644 index 0000000..e0e628d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflakes/test/test_undefined_names.py @@ -0,0 +1,871 @@ +import ast +from sys import version_info + +from pyflakes import messages as m, checker +from pyflakes.test.harness import TestCase, skipIf, skip + + +class Test(TestCase): + def test_undefined(self): + self.flakes('bar', m.UndefinedName) + + def test_definedInListComp(self): + self.flakes('[a for a in range(10) if a]') + + @skipIf(version_info < (3,), + 'in Python 2 list comprehensions execute in the same scope') + def test_undefinedInListComp(self): + self.flakes(''' + [a for a in range(10)] + a + ''', + m.UndefinedName) + + @skipIf(version_info < (3,), + 'in Python 2 exception names stay bound after the except: block') + def test_undefinedExceptionName(self): + """Exception names can't be used after the except: block. + + The exc variable is unused inside the exception handler.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + pass + exc + ''', m.UndefinedName, m.UnusedVariable) + + def test_namesDeclaredInExceptBlocks(self): + """Locals declared in except: blocks can be used after the block. + + This shows the example in test_undefinedExceptionName is + different.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + e = exc + e + ''') + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringLocalVariable(self): + """Exception names obscure locals, can't be used after. + + Last line will raise UnboundLocalError on Python 3 after exiting + the except: block. Note next two examples for false positives to + watch out for.""" + self.flakes(''' + exc = 'Original value' + try: + raise ValueError('ve') + except ValueError as exc: + pass + exc + ''', + m.UndefinedName) + + @skipIf(version_info < (3,), + 'in Python 2 exception names stay bound after the except: block') + def test_undefinedExceptionNameObscuringLocalVariable2(self): + """Exception names are unbound after the `except:` block. + + Last line will raise UnboundLocalError on Python 3 but would print out + 've' on Python 2. The exc variable is unused inside the exception + handler.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + pass + print(exc) + exc = 'Original value' + ''', m.UndefinedName, m.UnusedVariable) + + def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self): + """Exception names obscure locals, can't be used after. Unless. + + Last line will never raise UnboundLocalError because it's only + entered if no exception was raised.""" + # The exc variable is unused inside the exception handler. + expected = [] if version_info < (3,) else [m.UnusedVariable] + self.flakes(''' + exc = 'Original value' + try: + raise ValueError('ve') + except ValueError as exc: + print('exception logged') + raise + exc + ''', *expected) + + def test_delExceptionInExcept(self): + """The exception name can be deleted in the except: block.""" + self.flakes(''' + try: + pass + except Exception as exc: + del exc + ''') + + def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self): + """Exception names obscure locals, can't be used after. Unless. + + Last line will never raise UnboundLocalError because `error` is + only falsy if the `except:` block has not been entered.""" + # The exc variable is unused inside the exception handler. + expected = [] if version_info < (3,) else [m.UnusedVariable] + self.flakes(''' + exc = 'Original value' + error = None + try: + raise ValueError('ve') + except ValueError as exc: + error = 'exception logged' + if error: + print(error) + else: + exc + ''', *expected) + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringGlobalVariable(self): + """Exception names obscure globals, can't be used after. + + Last line will raise UnboundLocalError on both Python 2 and + Python 3 because the existence of that exception name creates + a local scope placeholder for it, obscuring any globals, etc.""" + self.flakes(''' + exc = 'Original value' + def func(): + try: + pass # nothing is raised + except ValueError as exc: + pass # block never entered, exc stays unbound + exc + ''', + m.UndefinedLocal) + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringGlobalVariable2(self): + """Exception names obscure globals, can't be used after. + + Last line will raise NameError on Python 3 because the name is + locally unbound after the `except:` block, even if it's + nonlocal. We should issue an error in this case because code + only working correctly if an exception isn't raised, is invalid. + Unless it's explicitly silenced, see false positives below.""" + self.flakes(''' + exc = 'Original value' + def func(): + global exc + try: + raise ValueError('ve') + except ValueError as exc: + pass # block never entered, exc stays unbound + exc + ''', + m.UndefinedLocal) + + def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self): + """Exception names obscure globals, can't be used after. Unless. + + Last line will never raise NameError because it's only entered + if no exception was raised.""" + # The exc variable is unused inside the exception handler. + expected = [] if version_info < (3,) else [m.UnusedVariable] + self.flakes(''' + exc = 'Original value' + def func(): + global exc + try: + raise ValueError('ve') + except ValueError as exc: + print('exception logged') + raise + exc + ''', *expected) + + def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self): + """Exception names obscure globals, can't be used after. Unless. + + Last line will never raise NameError because `error` is only + falsy if the `except:` block has not been entered.""" + # The exc variable is unused inside the exception handler. + expected = [] if version_info < (3,) else [m.UnusedVariable] + self.flakes(''' + exc = 'Original value' + def func(): + global exc + error = None + try: + raise ValueError('ve') + except ValueError as exc: + error = 'exception logged' + if error: + print(error) + else: + exc + ''', *expected) + + def test_functionsNeedGlobalScope(self): + self.flakes(''' + class a: + def b(): + fu + fu = 1 + ''') + + def test_builtins(self): + self.flakes('range(10)') + + def test_builtinWindowsError(self): + """ + C{WindowsError} is sometimes a builtin name, so no warning is emitted + for using it. + """ + self.flakes('WindowsError') + + @skipIf(version_info < (3, 6), 'new feature in 3.6') + def test_moduleAnnotations(self): + """ + Use of the C{__annotations__} in module scope should not emit + an undefined name warning when version is greater than or equal to 3.6. + """ + self.flakes('__annotations__') + + def test_magicGlobalsFile(self): + """ + Use of the C{__file__} magic global should not emit an undefined name + warning. + """ + self.flakes('__file__') + + def test_magicGlobalsBuiltins(self): + """ + Use of the C{__builtins__} magic global should not emit an undefined + name warning. + """ + self.flakes('__builtins__') + + def test_magicGlobalsName(self): + """ + Use of the C{__name__} magic global should not emit an undefined name + warning. + """ + self.flakes('__name__') + + def test_magicGlobalsPath(self): + """ + Use of the C{__path__} magic global should not emit an undefined name + warning, if you refer to it from a file called __init__.py. + """ + self.flakes('__path__', m.UndefinedName) + self.flakes('__path__', filename='package/__init__.py') + + def test_magicModuleInClassScope(self): + """ + Use of the C{__module__} magic builtin should not emit an undefined + name warning if used in class scope. + """ + self.flakes('__module__', m.UndefinedName) + self.flakes(''' + class Foo: + __module__ + ''') + self.flakes(''' + class Foo: + def bar(self): + __module__ + ''', m.UndefinedName) + + @skipIf(version_info < (3, 3), "Python >= 3.3 only") + def test_magicQualnameInClassScope(self): + """ + Use of the C{__qualname__} magic builtin should not emit an undefined + name warning if used in class scope. + """ + self.flakes('__qualname__', m.UndefinedName) + self.flakes(''' + class Foo: + __qualname__ + ''') + self.flakes(''' + class Foo: + def bar(self): + __qualname__ + ''', m.UndefinedName) + + def test_globalImportStar(self): + """Can't find undefined names with import *.""" + self.flakes('from fu import *; bar', + m.ImportStarUsed, m.ImportStarUsage) + + @skipIf(version_info >= (3,), 'obsolete syntax') + def test_localImportStar(self): + """ + A local import * still allows undefined names to be found + in upper scopes. + """ + self.flakes(''' + def a(): + from fu import * + bar + ''', m.ImportStarUsed, m.UndefinedName, m.UnusedImport) + + @skipIf(version_info >= (3,), 'obsolete syntax') + def test_unpackedParameter(self): + """Unpacked function parameters create bindings.""" + self.flakes(''' + def a((bar, baz)): + bar; baz + ''') + + def test_definedByGlobal(self): + """ + "global" can make an otherwise undefined name in another function + defined. + """ + self.flakes(''' + def a(): global fu; fu = 1 + def b(): fu + ''') + self.flakes(''' + def c(): bar + def b(): global bar; bar = 1 + ''') + + def test_definedByGlobalMultipleNames(self): + """ + "global" can accept multiple names. + """ + self.flakes(''' + def a(): global fu, bar; fu = 1; bar = 2 + def b(): fu; bar + ''') + + def test_globalInGlobalScope(self): + """ + A global statement in the global scope is ignored. + """ + self.flakes(''' + global x + def foo(): + print(x) + ''', m.UndefinedName) + + def test_global_reset_name_only(self): + """A global statement does not prevent other names being undefined.""" + # Only different undefined names are reported. + # See following test that fails where the same name is used. + self.flakes(''' + def f1(): + s + + def f2(): + global m + ''', m.UndefinedName) + + @skip("todo") + def test_unused_global(self): + """An unused global statement does not define the name.""" + self.flakes(''' + def f1(): + m + + def f2(): + global m + ''', m.UndefinedName) + + def test_del(self): + """Del deletes bindings.""" + self.flakes('a = 1; del a; a', m.UndefinedName) + + def test_delGlobal(self): + """Del a global binding from a function.""" + self.flakes(''' + a = 1 + def f(): + global a + del a + a + ''') + + def test_delUndefined(self): + """Del an undefined name.""" + self.flakes('del a', m.UndefinedName) + + def test_delConditional(self): + """ + Ignores conditional bindings deletion. + """ + self.flakes(''' + context = None + test = True + if False: + del(test) + assert(test) + ''') + + def test_delConditionalNested(self): + """ + Ignored conditional bindings deletion even if they are nested in other + blocks. + """ + self.flakes(''' + context = None + test = True + if False: + with context(): + del(test) + assert(test) + ''') + + def test_delWhile(self): + """ + Ignore bindings deletion if called inside the body of a while + statement. + """ + self.flakes(''' + def test(): + foo = 'bar' + while False: + del foo + assert(foo) + ''') + + def test_delWhileTestUsage(self): + """ + Ignore bindings deletion if called inside the body of a while + statement and name is used inside while's test part. + """ + self.flakes(''' + def _worker(): + o = True + while o is not True: + del o + o = False + ''') + + def test_delWhileNested(self): + """ + Ignore bindings deletions if node is part of while's test, even when + del is in a nested block. + """ + self.flakes(''' + context = None + def _worker(): + o = True + while o is not True: + while True: + with context(): + del o + o = False + ''') + + def test_globalFromNestedScope(self): + """Global names are available from nested scopes.""" + self.flakes(''' + a = 1 + def b(): + def c(): + a + ''') + + def test_laterRedefinedGlobalFromNestedScope(self): + """ + Test that referencing a local name that shadows a global, before it is + defined, generates a warning. + """ + self.flakes(''' + a = 1 + def fun(): + a + a = 2 + return a + ''', m.UndefinedLocal) + + def test_laterRedefinedGlobalFromNestedScope2(self): + """ + Test that referencing a local name in a nested scope that shadows a + global declared in an enclosing scope, before it is defined, generates + a warning. + """ + self.flakes(''' + a = 1 + def fun(): + global a + def fun2(): + a + a = 2 + return a + ''', m.UndefinedLocal) + + def test_intermediateClassScopeIgnored(self): + """ + If a name defined in an enclosing scope is shadowed by a local variable + and the name is used locally before it is bound, an unbound local + warning is emitted, even if there is a class scope between the enclosing + scope and the local scope. + """ + self.flakes(''' + def f(): + x = 1 + class g: + def h(self): + a = x + x = None + print(x, a) + print(x) + ''', m.UndefinedLocal) + + def test_doubleNestingReportsClosestName(self): + """ + Test that referencing a local name in a nested scope that shadows a + variable declared in two different outer scopes before it is defined + in the innermost scope generates an UnboundLocal warning which + refers to the nearest shadowed name. + """ + exc = self.flakes(''' + def a(): + x = 1 + def b(): + x = 2 # line 5 + def c(): + x + x = 3 + return x + return x + return x + ''', m.UndefinedLocal).messages[0] + + # _DoctestMixin.flakes adds two lines preceding the code above. + expected_line_num = 7 if self.withDoctest else 5 + + self.assertEqual(exc.message_args, ('x', expected_line_num)) + + def test_laterRedefinedGlobalFromNestedScope3(self): + """ + Test that referencing a local name in a nested scope that shadows a + global, before it is defined, generates a warning. + """ + self.flakes(''' + def fun(): + a = 1 + def fun2(): + a + a = 1 + return a + return a + ''', m.UndefinedLocal) + + def test_undefinedAugmentedAssignment(self): + self.flakes( + ''' + def f(seq): + a = 0 + seq[a] += 1 + seq[b] /= 2 + c[0] *= 2 + a -= 3 + d += 4 + e[any] = 5 + ''', + m.UndefinedName, # b + m.UndefinedName, # c + m.UndefinedName, m.UnusedVariable, # d + m.UndefinedName, # e + ) + + def test_nestedClass(self): + """Nested classes can access enclosing scope.""" + self.flakes(''' + def f(foo): + class C: + bar = foo + def f(self): + return foo + return C() + + f(123).f() + ''') + + def test_badNestedClass(self): + """Free variables in nested classes must bind at class creation.""" + self.flakes(''' + def f(): + class C: + bar = foo + foo = 456 + return foo + f() + ''', m.UndefinedName) + + def test_definedAsStarArgs(self): + """Star and double-star arg names are defined.""" + self.flakes(''' + def f(a, *b, **c): + print(a, b, c) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_definedAsStarUnpack(self): + """Star names in unpack are defined.""" + self.flakes(''' + a, *b = range(10) + print(a, b) + ''') + self.flakes(''' + *a, b = range(10) + print(a, b) + ''') + self.flakes(''' + a, *b, c = range(10) + print(a, b, c) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_usedAsStarUnpack(self): + """ + Star names in unpack are used if RHS is not a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = range(10) + ''') + self.flakes(''' + def f(): + (*a, b) = range(10) + ''') + self.flakes(''' + def f(): + [a, *b, c] = range(10) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_unusedAsStarUnpack(self): + """ + Star names in unpack are unused if RHS is a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = any, all, 4, 2, 'un' + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (*a, b) = [bool, int, float, complex] + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + [a, *b, c] = 9, 8, 7, 6, 5, 4 + ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_keywordOnlyArgs(self): + """Keyword-only arg names are defined.""" + self.flakes(''' + def f(*, a, b=None): + print(a, b) + ''') + + self.flakes(''' + import default_b + def f(*, a, b=default_b): + print(a, b) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_keywordOnlyArgsUndefined(self): + """Typo in kwonly name.""" + self.flakes(''' + def f(*, a, b=default_c): + print(a, b) + ''', m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotationUndefined(self): + """Undefined annotations.""" + self.flakes(''' + from abc import note1, note2, note3, note4, note5 + def func(a: note1, *args: note2, + b: note3=12, **kw: note4) -> note5: pass + ''') + + self.flakes(''' + def func(): + d = e = 42 + def func(a: {1, d}) -> (lambda c: e): pass + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_metaClassUndefined(self): + self.flakes(''' + from abc import ABCMeta + class A(metaclass=ABCMeta): pass + ''') + + def test_definedInGenExp(self): + """ + Using the loop variable of a generator expression results in no + warnings. + """ + self.flakes('(a for a in [1, 2, 3] if a)') + + self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)') + + def test_undefinedInGenExpNested(self): + """ + The loop variables of generator expressions nested together are + not defined in the other generator. + """ + self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)', + m.UndefinedName) + + self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)', + m.UndefinedName) + + def test_undefinedWithErrorHandler(self): + """ + Some compatibility code checks explicitly for NameError. + It should not trigger warnings. + """ + self.flakes(''' + try: + socket_map + except NameError: + socket_map = {} + ''') + self.flakes(''' + try: + _memoryview.contiguous + except (NameError, AttributeError): + raise RuntimeError("Python >= 3.3 is required") + ''') + # If NameError is not explicitly handled, generate a warning + self.flakes(''' + try: + socket_map + except: + socket_map = {} + ''', m.UndefinedName) + self.flakes(''' + try: + socket_map + except Exception: + socket_map = {} + ''', m.UndefinedName) + + def test_definedInClass(self): + """ + Defined name for generator expressions and dict/set comprehension. + """ + self.flakes(''' + class A: + T = range(10) + + Z = (x for x in T) + L = [x for x in T] + B = dict((i, str(i)) for i in T) + ''') + + self.flakes(''' + class A: + T = range(10) + + X = {x for x in T} + Y = {x:x for x in T} + ''') + + def test_definedInClassNested(self): + """Defined name for nested generator expressions in a class.""" + self.flakes(''' + class A: + T = range(10) + + Z = (x for x in (a for a in T)) + ''') + + def test_undefinedInLoop(self): + """ + The loop variable is defined after the expression is computed. + """ + self.flakes(''' + for i in range(i): + print(i) + ''', m.UndefinedName) + self.flakes(''' + [42 for i in range(i)] + ''', m.UndefinedName) + self.flakes(''' + (42 for i in range(i)) + ''', m.UndefinedName) + + def test_definedFromLambdaInDictionaryComprehension(self): + """ + Defined name referenced from a lambda function within a dict/set + comprehension. + """ + self.flakes(''' + {lambda: id(x) for x in range(10)} + ''') + + def test_definedFromLambdaInGenerator(self): + """ + Defined name referenced from a lambda function within a generator + expression. + """ + self.flakes(''' + any(lambda: id(x) for x in range(10)) + ''') + + def test_undefinedFromLambdaInDictionaryComprehension(self): + """ + Undefined name referenced from a lambda function within a dict/set + comprehension. + """ + self.flakes(''' + {lambda: id(y) for x in range(10)} + ''', m.UndefinedName) + + def test_undefinedFromLambdaInComprehension(self): + """ + Undefined name referenced from a lambda function within a generator + expression. + """ + self.flakes(''' + any(lambda: id(y) for x in range(10)) + ''', m.UndefinedName) + + def test_dunderClass(self): + """ + `__class__` is defined in class scope under Python 3, but is not + in Python 2. + """ + code = ''' + class Test(object): + def __init__(self): + print(__class__.__name__) + self.x = 1 + + t = Test() + ''' + if version_info < (3,): + self.flakes(code, m.UndefinedName) + else: + self.flakes(code) + + +class NameTests(TestCase): + """ + Tests for some extra cases of name handling. + """ + def test_impossibleContext(self): + """ + A Name node with an unrecognized context results in a RuntimeError being + raised. + """ + tree = ast.parse("x = 10") + file_tokens = checker.make_tokens("x = 10") + # Make it into something unrecognizable. + tree.body[0].targets[0].ctx = object() + self.assertRaises(RuntimeError, checker.Checker, tree, file_tokens=file_tokens) diff --git a/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/METADATA b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/METADATA new file mode 100644 index 0000000..8e00eca --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/METADATA @@ -0,0 +1,456 @@ +Metadata-Version: 2.1 +Name: pyflyby +Version: 1.7.4 +Summary: pyflyby - Python development productivity tools, in particular automatic import management +Home-page: https://pypi.org/project/pyflyby/ +Author: Karl Chen +Author-email: quarl@8166.clguba.z.quarl.org +License: MIT +Project-URL: Documentation, https://deshaw.github.io/pyflyby/ +Project-URL: Source, https://github.com/deshaw/pyflyby +Keywords: pyflyby py autopython autoipython productivity automatic imports autoimporter tidy-imports +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Code Generators +Classifier: Topic :: Software Development :: Interpreters +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Requires-Python: >=2.5, !=3.0.*, !=3.1.*, !=3.2.*, !=3.2.*, !=3.3.*, !=3.4.*,, !=3.5.*, !=3.6.*, <4 +Requires-Dist: pyflakes +Requires-Dist: six + +######### + Pyflyby +######### + +.. image:: https://badge.fury.io/py/pyflyby.svg + :target: https://pypi.org/project/pyflyby/ + +.. image:: https://travis-ci.org/deshaw/pyflyby.png?branch=master + :target: https://travis-ci.org/deshaw/pyflyby + +Pyflyby is a set of Python programming productivity tools for Python 2.7 and 3.7+. + +For command-line interaction: + * ``py``: command-line multitool + +For IPython interaction: + * ``autoimporter``: automatically imports symbols when needed. + +For editing python source code: + * ``tidy-imports``: adds missing 'import's, removes unused 'import's, + and also reformats import blocks. + * ``find-import``: prints to stdout how to import a particular symbol. + * ``reformat-imports``: reformats ``import`` blocks + * ``collect-imports``: prints out all the imports in a given set of files. + * ``collect-exports``: prints out definitions in a given set of modules, + in the form of import statements. + * ``transform-imports``: renames imported modules/functions. + +Installation +============ + +.. code:: bash + + $ pip install pyflyby + +This creates an alias for your `ipython` named `py` which runs the `pyflyby` plug internally. + `pyflyby` has a dependency on `ipython`, if it isn't already installed do install it with: + +.. code:: bash + + $ pip install ipython + + +Quick start: Autoimporter + IPython +=================================== + +.. code:: bash + + $ py + In [1]: re.search("[a-z]+", "....hello...").group(0) + [PYFLYBY] import re + Out[1]: 'hello' + + In [2]: chisqprob(arange(5), 2) + [PYFLYBY] from numpy import arange + [PYFLYBY] from scipy.stats import chisqprob + Out[2]: [ 1. 0.6065 0.3679 0.2231 0.1353] + +To load pyflyby into an existing IPython session as a 1-off: + +.. code:: bash + + $ ipython + In [1]: %load_ext pyflyby + +To configure IPython/Jupyter Notebook to load pyflyby automatically: + +.. code:: bash + + $ py pyflyby.install_in_ipython_config_file + +or + +.. code:: bash + + $ echo 'c.InteractiveShellApp.extensions.append("pyflyby")' \ + >> ~/.ipython/profile_default/ipython_config.py + + $ ipython + In [1]: b64decode('aGVsbG8=') + [PYFLYBY] from base64 import b64decode + Out[1]: 'hello' + + +Quick start: ``py`` command-line multi-tool +=========================================== + +.. code:: bash + + $ py b64decode aGVsbG8= + [PYFLYBY] from base64 import b64decode + [PYFLYBY] b64decode('aGVsbG8=', altchars=None) + 'hello' + + $ py log2 sys.maxint + [PYFLYBY] from numpy import log2 + [PYFLYBY] import sys + [PYFLYBY] log2(9223372036854775807) + 63.0 + + $ py 'plot(cos(arange(30)))' + [PYFLYBY] from numpy import arange + [PYFLYBY] from numpy import cos + [PYFLYBY] from matplotlib.pyplot import plot + [PYFLYBY] plot(cos(arange(30))) + + + $ py 38497631 / 13951446 + 2.7594007818257693 + + $ py foo.py + +Quick start: ``tidy-imports`` +============================= + +To use ``tidy-imports``, just specify the filename(s) to tidy. + +For example: + +.. code:: + + $ echo 're.search("[a-z]+", "....hello..."), chisqprob(arange(5), 2)' > foo.py + + $ tidy-imports foo.py + --- /tmp/foo.py + +++ /tmp/foo.py + @@ -1 +1,9 @@ + +from __future__ import absolute_import, division, with_statement + + + +from numpy import arange + +from scipy.stats import chisqprob + +import re + + + re.search("[a-z]+", "....hello..."), chisqprob(arange(5), 2) + + Replace /tmp/foo.py? [y/N] + + +Quick start: import libraries +============================= + +Create a file named .pyflyby with lines such as + +.. code:: python + + from mypackage.mymodule import MyClass, my_function + import anotherpackage.anothermodule + +You can put this file in your home directory or in the same directory as your +``*.py`` files. + + +Details: automatic imports +========================== + +AUTOMATIC IMPORTS - never type "import" again! + +This module allows your "known imports" to work automatically in your IPython +interactive session without having to type the 'import' statements (and also +without having to slow down your Python startup with imports you only use +occasionally). + +Example:: + + In [1]: re.search("[a-z]+", "....hello...").group(0) + [PYFLYBY] import re + Out[1]: 'hello' + + In [2]: chisqprob(arange(5), 2) + [PYFLYBY] from numpy import arange + [PYFLYBY] from scipy.stats import chisqprob + Out[2]: [ 1. 0.6065 0.3679 0.2231 0.1353] + + In [3]: np.sin(arandom(5)) + [PYFLYBY] from numpy.random import random as arandom + [PYFLYBY] import numpy as np + Out[3]: [ 0.0282 0.0603 0.4653 0.8371 0.3347] + + In [4]: isinstance(42, Number) + [PYFLYBY] from numbers import Number + Out[4]: True + + +It just works +------------- + +Tab completion works, even on modules that are not yet imported. In the +following example, notice that numpy is imported when we need to know its +members, and only then:: + + $ ipython + In [1]: nump + In [1]: numpy + In [1]: numpy.arang + [PYFLYBY] import numpy + In [1]: numpy.arange + + +The IPython "?" magic help (pinfo/pinfo2) automatically imports symbols first +if necessary:: + + $ ipython + In [1]: arange? + [PYFLYBY] from numpy import arange + ... Docstring: arange([start,] stop[, step,], dtype=None) ... + +Other IPython magic commands work as well:: + + $ ipython + In [1]: %timeit np.cos(pi) + [PYFLYBY] import numpy as np + [PYFLYBY] from numpy import pi + 100000 loops, best of 3: 2.51 us per loop + + $ echo 'print arange(4)' > foo.py + $ ipython + In [1]: %run foo.py + [PYFLYBY] from numpy import arange + [0 1 2 3] + + +Implementation details +---------------------- + +The automatic importing happens at parse time, before code is executed. The +namespace never contains entries for names that are not yet imported. + +This method of importing at parse time contrasts with previous implementations +of automatic importing that use proxy objects. Those implementations using +proxy objects don't work as well, because it is impossible to make proxy +objects behave perfectly. For example, instance(x, T) will return the wrong +answer if either x or T is a proxy object. + + +Compatibility +------------- + +Tested with: + - Python 2.6, 2.7, 3.7, 3.8 + - IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, + 3.1, 3.2, 4.0., 7.11 (latest) + - IPython (text console), IPython Notebook, Spyder + + +Details: import libraries +========================= + +Pyflyby uses "import libraries" that tell how to import a given symbol. + +An import library file is simply a python source file containing 'import' (or +'from ... import ...') lines. These can be generated automatically with +``collect-imports`` and ``collect-exports``. + +Known imports +------------- + +Find-imports, ``tidy-imports``, and autoimport consult the database of known +imports to figure out where to get an import. For example, if the +imports database contains:: + + from numpy import arange, NaN + +then when you type the following in IPython:: + + print(arange(10)) + +the autoimporter would automatically execute ``from numpy import arange``. + +The database can be one file or multiple files. This makes it easy to have +project-specific known_imports along with global and per-user defaults. + +The ``PYFLYBY_PATH`` environment variable specifies which files to read. +This is a colon-separated list of filenames or directory names. The default +is:: + + PYFLYBY_PATH=/etc/pyflyby:~/.pyflyby:.../.pyflyby + +If you set:: + + PYFLYBY_PATH=/foo1/bar1:/foo2/bar2 + +then this replaces the default. + +You can use a hyphen to include the default in the path. If you set:: + + PYFLYBY_PATH=/foo1/bar1:-:/foo2/bar2 + +then this reads ``/foo1/bar1``, then the default locations, then ``/foo2/bar2``. + +In ``$PYFLYBY_PATH``, ``.../.pyflyby`` (with _three_ dots) means that all ancestor +directories are searched for a member named ".pyflyby". + +For example, suppose the following files exist:: + + /etc/pyflyby/stuff.py + /u/quarl/.pyflyby/blah1.py + /u/quarl/.pyflyby/more/blah2.py + /proj/share/mypythonstuff/.pyflyby + /proj/share/mypythonstuff/foo/bar/.pyflyby/baz.py + /.pyflyby + +Further, suppose: + + * ``/proj`` is on a separate file system from ``/``. + * ``$HOME=/u/quarl`` + +Then ``tidy-imports /proj/share/mypythonstuff/foo/bar/quux/zot.py`` will by +default use the following:: + + /etc/pyflyby/stuff.py + /u/quarl/.pyflyby/blah1.py + /u/quarl/.pyflyby/more/blah2.py + /proj/share/mypythonstuff/foo/bar/.pyflyby/baz.py + /proj/share/mypythonstuff/.pyflyby (a file) + +.. note:: + + * ``/.pyflyby`` is not included, because traversal stops at file system + boundaries, and in this example, ``/proj`` is on a different file system than + ``/``. + * ``.pyflyby`` (in ``$HOME`` or near the target file) can be a file or a directory. + If it is a directory, then it is recursively searched for ``*.py`` files. + * The order usually doesn't matter, but if there are "forget" instructions + (see below), then the order matters. In the default ``$PYFLYBY_PATH``, + .../.pyflyby is placed last so that per-directory configuration can + override per-user configuration, which can override systemwide + configuration. + + +Forgetting imports +------------------ + +Occasionally you may have reason to tell pyflyby to "forget" entries from the +database of known imports. + +You can put the following in any file reachable from ``$PYFLYBY_PATH``:: + + __forget_imports__ = ["from numpy import NaN"] + +This is useful if you want to use a set of imports maintained by someone else +except for a few particular imports. + +Entries in ``$PYFLYBY_PATH`` are processed left-to-right in the order specified, +so put the files containing these at the end of your ``$PYFLYBY_PATH``. By +default, ``tidy-imports`` and friends process ``/etc/pyflyby``, then ``~/.pyflyby``, +then the per-directory ``.pyflyby``. + + +Mandatory imports +----------------- + +Within a certain project you may have a policy to always include certain +imports. For example, maybe you always want to do ``from __future__ import +division`` in all files. + +You can put the following in any file reachable from ``$PYFLYBY_PATH``:: + + __mandatory_imports__ = ["from __future__ import division"] + +To undo mandatory imports inherited from other ``.pyflyby`` files, use +``__forget_imports__`` (see above). + + +Canonicalize imports +-------------------- + +Sometimes you want every run of ``tidy-imports`` to automatically rename an import +to a new name. + +You can put the following in any file reachable from ``$PYFLYBY_PATH``:: + + __canonical_imports__ = {"oldmodule.oldfunction": "newmodule.newfunction"} + +This is equivalent to running:: + + tidy-imports --transform=oldmodule.oldfunction=newmodule.newfunction + + +Soapbox: avoid "star" imports +============================= + +When programming in Python, a good software engineering practice is to avoid +using ``from foopackage import *`` in production code. + +This style is a maintenance nightmare: + + * It becomes difficult to figure out where various symbols + (functions/classes/etc) come from. + + * It's hard to tell what gets shadowed by what. + + * When the package changes in trivial ways, your code will be affected. + Consider the following example: Suppose ``foopackage.py`` contains ``import + sys``, and ``myprogram.py`` contains ``from foopackage import *; if + some_condition: sys.exit(0)``. If ``foopackage.py`` changes so that ``import + sys`` is removed, ``myprogram.py`` is now broken because it's missing ``import + sys``. + +To fix such code, you can run ``tidy-imports --replace-star-imports`` to +automatically replace star imports with the specific needed imports. + + +Emacs support +============= + +* To get a ``M-x tidy-imports`` command in GNU Emacs, add to your ``~/.emacs``:: + + (load "/path/to/pyflyby/lib/emacs/pyflyby.el") + + +- Pyflyby.el doesn't yet work with XEmacs; patches welcome. + + +Authorship +========== + +This plugin was contributed back to the community by the `D. E. Shaw group +`_. + +.. image:: https://www.deshaw.com/assets/logos/blue_logo_417x125.png + :target: https://www.deshaw.com + :height: 75 px + +Pyflyby is written by Karl Chen + + +License +======= + +Pyflyby is released under a very permissive license, the MIT/X11 license; see +LICENSE.txt. + + diff --git a/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/RECORD b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/RECORD new file mode 100644 index 0000000..dc61ca1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/RECORD @@ -0,0 +1,87 @@ +../../../bin/collect-exports,sha256=ZwRqhJV18T21hloWXI00c7uIPXsWhNGhMY1SikVFCNY,2952 +../../../bin/collect-imports,sha256=aPDJwOymXC_CO-4Y0MCEYP9VSRiiBjGUuK1RI9iZ6Ck,2192 +../../../bin/find-import,sha256=8AU_SH5ej8-4XuNceO7RMjAQLlY4z_EQs6QH3QOmwgI,1022 +../../../bin/list-bad-xrefs,sha256=V3Px5EHl29I6L6IuP3to_UoVKaOuE8t9RU_-He2MFbs,1116 +../../../bin/prune-broken-imports,sha256=zPcJddQHQuR4vvAmXZ4Z7iftn-IXsAh8kWCtvtWhyhI,912 +../../../bin/py,sha256=LFK-kg-VHU5YXvq1tpvLWY1Nch-u86mRrf1EdX1KYp4,253 +../../../bin/py3,sha256=LFK-kg-VHU5YXvq1tpvLWY1Nch-u86mRrf1EdX1KYp4,253 +../../../bin/pyflyby-diff,sha256=kaf6vZa8mTp3riyRdq-aDVmp8MBaYYI5yN9nOfPRcs0,882 +../../../bin/reformat-imports,sha256=6doohtKlFyY_gZcrCPv0F4OLh5WaQ9zUAGKhgIx9zgU,741 +../../../bin/replace-star-imports,sha256=uHTRKmGabarr6aJc9rDYP3NAcQ2FCg8oY-IxUpmywRs,964 +../../../bin/tidy-imports,sha256=YfCnQTh2cB8JnPZagIG1LSALe09zRl_II0MHoBfMiDQ,6992 +../../../bin/transform-imports,sha256=McbKzCljYDvpJuRbeF9irixUlZSfVU1huopz4WHP_CE,1501 +../../../etc/pyflyby/__pycache__/canonical.cpython-38.pyc,, +../../../etc/pyflyby/__pycache__/common.cpython-38.pyc,, +../../../etc/pyflyby/__pycache__/forget.cpython-38.pyc,, +../../../etc/pyflyby/__pycache__/mandatory.cpython-38.pyc,, +../../../etc/pyflyby/__pycache__/numpy.cpython-38.pyc,, +../../../etc/pyflyby/__pycache__/std.cpython-38.pyc,, +../../../etc/pyflyby/canonical.py,sha256=AEkiB4K19Pw4eKCns5dhnFXMmOJnRuwmly9RYihUwXM,270 +../../../etc/pyflyby/common.py,sha256=MQyqrSOVFM9Fj0mj3M87wzTWllkpZYCjDclxghkC0fs,569 +../../../etc/pyflyby/forget.py,sha256=N0xucS19eeoSS-BKXUI4bgwYrk4yWBOX3cVY0EC4KQE,369 +../../../etc/pyflyby/mandatory.py,sha256=-E52J_y2-Xu-YOHzoXiGVlCXcZqkEK4abVv7dQSNSgY,352 +../../../etc/pyflyby/numpy.py,sha256=NC_1iVRpxSdcwRxeIHDG4wLvSFUn_gyF7mvyobmdqhc,10464 +../../../etc/pyflyby/std.py,sha256=6EtRrjfaitwADH7S5OO85cTUauY4pVEL0cyOyC4WT8M,17409 +../../../libexec/pyflyby/colordiff,sha256=kaf6vZa8mTp3riyRdq-aDVmp8MBaYYI5yN9nOfPRcs0,882 +../../../libexec/pyflyby/diff-colorize,sha256=Wn-mlWt8G3SD04YlwsUa_FhD-3dotOSq6sgn4B3IfHE,5643 +../../../share/doc/pyflyby/LICENSE.txt,sha256=cSDlWua5QEFNdBRChGEKcS_5ABQ2220A3OkP10Q9RA0,1188 +../../../share/doc/pyflyby/TODO.txt,sha256=LgRdtqVhOudF2I2RIBosYFF7PkOc5dJzhe41t1UNHao,5686 +../../../share/doc/pyflyby/testing.txt,sha256=3IP6tJjrFjWTju1YZiu6vqn5yokEEb4I5mwJbSBWG-o,186 +../../../share/emacs/site-lisp/pyflyby.el,sha256=iKXqFc7kAweJ_TwQUhV3G-SxABme_Idk-iCwzLTmbbY,4122 +pyflyby-1.7.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pyflyby-1.7.4.dist-info/METADATA,sha256=tFWSk9-2CRntoaMIKgAGFLyTuLPcywDjXJwnUIQY79w,13136 +pyflyby-1.7.4.dist-info/RECORD,, +pyflyby-1.7.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyflyby-1.7.4.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92 +pyflyby-1.7.4.dist-info/entry_points.txt,sha256=jgjzp8LlN_YNHPMi1eAan9n7bs1ZNPM6SUgzItIY-Go,66 +pyflyby-1.7.4.dist-info/top_level.txt,sha256=8qkns6b7GoPxK3kDelOerfZmj_nJyMB9O4J0quvGgE4,8 +pyflyby/__init__.py,sha256=JWHZghPX3lEWtA8R8Pt6UoEpKK51pPFjImFlR-w2P0s,2712 +pyflyby/__main__.py,sha256=JUyCvZLfytCbEPtS14K4WjePOeBfb3atO8viAWFD4zk,296 +pyflyby/__pycache__/__init__.cpython-38.pyc,, +pyflyby/__pycache__/__main__.cpython-38.pyc,, +pyflyby/__pycache__/_autoimp.cpython-38.pyc,, +pyflyby/__pycache__/_cmdline.cpython-38.pyc,, +pyflyby/__pycache__/_comms.cpython-38.pyc,, +pyflyby/__pycache__/_dbg.cpython-38.pyc,, +pyflyby/__pycache__/_docxref.cpython-38.pyc,, +pyflyby/__pycache__/_file.cpython-38.pyc,, +pyflyby/__pycache__/_flags.cpython-38.pyc,, +pyflyby/__pycache__/_format.cpython-38.pyc,, +pyflyby/__pycache__/_idents.cpython-38.pyc,, +pyflyby/__pycache__/_importclns.cpython-38.pyc,, +pyflyby/__pycache__/_importdb.cpython-38.pyc,, +pyflyby/__pycache__/_imports2s.cpython-38.pyc,, +pyflyby/__pycache__/_importstmt.cpython-38.pyc,, +pyflyby/__pycache__/_interactive.cpython-38.pyc,, +pyflyby/__pycache__/_livepatch.cpython-38.pyc,, +pyflyby/__pycache__/_log.cpython-38.pyc,, +pyflyby/__pycache__/_modules.cpython-38.pyc,, +pyflyby/__pycache__/_parse.cpython-38.pyc,, +pyflyby/__pycache__/_py.cpython-38.pyc,, +pyflyby/__pycache__/_util.cpython-38.pyc,, +pyflyby/__pycache__/_version.cpython-38.pyc,, +pyflyby/__pycache__/autoimport.cpython-38.pyc,, +pyflyby/__pycache__/importdb.cpython-38.pyc,, +pyflyby/_autoimp.py,sha256=gH_lNRH0OjGlYo7v13kCURZLlTS6NLWGKUyTpdABRQU,81154 +pyflyby/_cmdline.py,sha256=eUJm-fp6qufCxis_wiM0EhktUZamBNU58F_h7EQcd_o,20147 +pyflyby/_comms.py,sha256=wnwq05P34YwG_30QKIQugLirrasGsEwSTN64FbzP4c4,4472 +pyflyby/_dbg.py,sha256=V32rhwjw14oeYf9iNyH1Q-zxwV_gQHDRlUt8Hmpz468,44411 +pyflyby/_docxref.py,sha256=31Y5ucF81bmLZotRq0TEo0JkYYX-4hFgKp77lO4o_7M,14280 +pyflyby/_file.py,sha256=YXWq1ABNhX0KR8CNEbSl3UZx30m3ipN_kfvWh3hHI0c,22927 +pyflyby/_flags.py,sha256=zzRrSCNTR4XTGl4lDi9qi5zjNhl5O6tVYEVIRoZEi1I,7455 +pyflyby/_format.py,sha256=ODBH5xOMoEa-sVJS3Q6_T-Bt9E9TBlXIsplJUJ_STZE,6733 +pyflyby/_idents.py,sha256=5X3l4gOK3WXikw8RU6IaIFpUzLWpieqL2vXKG-q_2uA,7829 +pyflyby/_importclns.py,sha256=Om_EegtAlNsKGwQRZqFpOtEI-QfHa8RPUP97tLKzuUw,21860 +pyflyby/_importdb.py,sha256=tyELhqVt4fDpiFk7YqtowUpcwgw7dTtwBspI3-fa2EQ,23556 +pyflyby/_imports2s.py,sha256=k-b9JzB6gghEBc9XK0ulEk9G69rjwQ96STJ5xtXbskc,23606 +pyflyby/_importstmt.py,sha256=cwORCJ3YooSnFR0cbL4GY_CE6ljxiw8wC97UgPu2D5g,17557 +pyflyby/_interactive.py,sha256=IUJOXw2ab7IXZvwiz4JM1x2alyjHw29Y2cfbneVhPOo,104127 +pyflyby/_livepatch.py,sha256=e1OwrEreThpZEdwczN53Sif8LU3J8DHCsshXF-2ssRM,30111 +pyflyby/_log.py,sha256=_SHo3O1sZnsRMzO8RMHo2FmmgJbHe2DhB0CqPZayZqE,7915 +pyflyby/_modules.py,sha256=yQTKXLPawPDEQpLSqXktTtC7DLvnZcNYwUWFS9Frehc,15434 +pyflyby/_parse.py,sha256=kZqa9hq456O0DqYjgr2KsoVNzEBBzB8NDljSY3z1Hic,56005 +pyflyby/_py.py,sha256=AalKG_vpTnGCRpDMuVCG-NpHNXrt-O_ZQoBnYClTwZM,75780 +pyflyby/_util.py,sha256=angI9Ry5EJTOXIiD6EgaaS1gZC79VdF-kI3jnQfT0p8,15506 +pyflyby/_version.py,sha256=arqEapTL36B2P6_b-F-Yq_LOEnWPeW0Ywb7z2qVEFYg,266 +pyflyby/autoimport.py,sha256=8uh7fJW87ateigZcD2RPgk1ezenxyhVnUB7MGDBVFVk,591 +pyflyby/importdb.py,sha256=Y6vye0OMJDxPL0a1ooj-zJiO1MaoNQDL3yGv-OU4-io,600 diff --git a/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/REQUESTED b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/WHEEL new file mode 100644 index 0000000..385faab --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/entry_points.txt b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/entry_points.txt new file mode 100644 index 0000000..c0a4c18 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +py=pyflyby._py:py_main +py3=pyflyby._py:py_main + diff --git a/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/top_level.txt new file mode 100644 index 0000000..c34d74d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby-1.7.4.dist-info/top_level.txt @@ -0,0 +1 @@ +pyflyby diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__init__.py b/.venv/lib/python3.8/site-packages/pyflyby/__init__.py new file mode 100644 index 0000000..d8b120d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/__init__.py @@ -0,0 +1,55 @@ +# pyflyby/__init__.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from pyflyby._autoimp import (auto_eval, auto_import, + find_missing_imports) +from pyflyby._dbg import (add_debug_functions_to_builtins, + attach_debugger, debug_on_exception, + debug_statement, debugger, + enable_exception_handler_debugger, + enable_faulthandler, + enable_signal_handler_debugger, + print_traceback, remote_print_stack) +from pyflyby._file import Filename +from pyflyby._flags import CompilerFlags +from pyflyby._importdb import ImportDB +from pyflyby._imports2s import (canonicalize_imports, + reformat_import_statements, + remove_broken_imports, + replace_star_imports, + transform_imports) +from pyflyby._importstmt import (Import, ImportStatement, + NonImportStatementError) +from pyflyby._interactive import (disable_auto_importer, + enable_auto_importer, + install_in_ipython_config_file, + load_ipython_extension, + unload_ipython_extension) +from pyflyby._livepatch import livepatch, xreload +from pyflyby._log import logger +from pyflyby._parse import PythonBlock, PythonStatement +from pyflyby._version import __version__ + +# Deprecated: +from pyflyby._dbg import (breakpoint, debug_exception, + debug_statement, + enable_exception_handler, + enable_signal_handler_breakpoint, + waitpoint) + + +# Promote the function & classes that we've chosen to expose publicly to be +# known as pyflyby.Foo instead of pyflyby._module.Foo. +for x in list(globals().values()): + if getattr(x, "__module__", "").startswith("pyflyby."): + x.__module__ = "pyflyby" +del x + + +# Discourage "from pyflyby import *". +# Use the tidy-imports/autoimporter instead! +__all__ = [] diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__main__.py b/.venv/lib/python3.8/site-packages/pyflyby/__main__.py new file mode 100644 index 0000000..7768fd8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/__main__.py @@ -0,0 +1,10 @@ +# pyflyby/__main__.py +# Copyright (C) 2014, 2015 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +if __name__ == "__main__": + from pyflyby._py import py_main + py_main() diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..92b41f8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..b4d1f7e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_autoimp.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_autoimp.cpython-38.pyc new file mode 100644 index 0000000..a518b2a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_autoimp.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_cmdline.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_cmdline.cpython-38.pyc new file mode 100644 index 0000000..c96c203 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_cmdline.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_comms.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_comms.cpython-38.pyc new file mode 100644 index 0000000..4387a13 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_comms.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_dbg.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_dbg.cpython-38.pyc new file mode 100644 index 0000000..a3a14b0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_dbg.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_docxref.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_docxref.cpython-38.pyc new file mode 100644 index 0000000..6f5e5d4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_docxref.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_file.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_file.cpython-38.pyc new file mode 100644 index 0000000..6923101 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_file.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_flags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_flags.cpython-38.pyc new file mode 100644 index 0000000..619cd12 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_flags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_format.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_format.cpython-38.pyc new file mode 100644 index 0000000..239f9bb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_format.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_idents.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_idents.cpython-38.pyc new file mode 100644 index 0000000..29f4cdd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_idents.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importclns.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importclns.cpython-38.pyc new file mode 100644 index 0000000..f335af0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importclns.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importdb.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importdb.cpython-38.pyc new file mode 100644 index 0000000..b61bd73 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importdb.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_imports2s.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_imports2s.cpython-38.pyc new file mode 100644 index 0000000..bdc126f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_imports2s.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importstmt.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importstmt.cpython-38.pyc new file mode 100644 index 0000000..d651881 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_importstmt.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_interactive.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_interactive.cpython-38.pyc new file mode 100644 index 0000000..e9be248 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_interactive.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_livepatch.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_livepatch.cpython-38.pyc new file mode 100644 index 0000000..c81a097 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_livepatch.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_log.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_log.cpython-38.pyc new file mode 100644 index 0000000..7cf0505 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_log.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_modules.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_modules.cpython-38.pyc new file mode 100644 index 0000000..a700374 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_modules.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_parse.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_parse.cpython-38.pyc new file mode 100644 index 0000000..b007c17 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_parse.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_py.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_py.cpython-38.pyc new file mode 100644 index 0000000..c4ec9f1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_py.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_util.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_util.cpython-38.pyc new file mode 100644 index 0000000..533ff6c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_util.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_version.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_version.cpython-38.pyc new file mode 100644 index 0000000..967ddb6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/_version.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/autoimport.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/autoimport.cpython-38.pyc new file mode 100644 index 0000000..9571202 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/autoimport.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/importdb.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/importdb.cpython-38.pyc new file mode 100644 index 0000000..73883f2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pyflyby/__pycache__/importdb.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_autoimp.py b/.venv/lib/python3.8/site-packages/pyflyby/_autoimp.py new file mode 100644 index 0000000..2310c61 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_autoimp.py @@ -0,0 +1,2056 @@ +# pyflyby/_autoimp.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018, 2019 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import ast +import contextlib +import copy +import six +from six import PY2, PY3, exec_, reraise +from six.moves import builtins +import sys +import types + +if PY3: + from collections.abc import Sequence +else: + # This is deprecated in Python 3 + from collections import Sequence + + +from pyflyby._file import FileText, Filename +from pyflyby._flags import CompilerFlags +from pyflyby._idents import (BadDottedIdentifierError, + DottedIdentifier, brace_identifiers) +from pyflyby._importdb import ImportDB +from pyflyby._importstmt import Import +from pyflyby._log import logger +from pyflyby._modules import ModuleHandle +from pyflyby._parse import PythonBlock, infer_compile_mode + + +NoneType = type(None) +EllipsisType = type(Ellipsis) + +class _ClassScope(dict): + pass + + + def __repr__(self): + return "_ClassScope(" + repr(super()) + ")" + + +_builtins2 = {"__file__": None} + + +class ScopeStack(Sequence): + """ + A stack of namespace scopes, as a tuple of ``dict`` s. + + Each entry is a ``dict``. + + Ordered from most-global to most-local. + Builtins are always included. + Duplicates are removed. + """ + + _cached_has_star_import = False + + def __init__(self, arg, _class_delayed=None): + """ + Interpret argument as a ``ScopeStack``. + + :type arg: + ``ScopeStack``, ``dict``, ``list`` of ``dict`` + :param arg: + Input namespaces + :rtype: + ``ScopeStack`` + """ + if isinstance(arg, ScopeStack): + scopes = list(arg._tup) + elif isinstance(arg, dict): + scopes = [arg] + elif isinstance(arg, (tuple, list)): + scopes = list(arg) + else: + raise TypeError( + "ScopeStack: expected a sequence of dicts; got a %s" + % (type(arg).__name__,)) + if not len(scopes): + raise TypeError("ScopeStack: no scopes given") + if not all(isinstance(scope, dict) for scope in scopes): + raise TypeError("ScopeStack: Expected list of dicts; got a sequence of %r" + % ([type(x).__name__ for x in scopes])) + scopes = [builtins.__dict__, _builtins2] + scopes + result = [] + seen = set() + # Keep only unique items, checking uniqueness by object identity. + for scope in scopes: + if id(scope) in seen: + continue + seen.add(id(scope)) + result.append(scope) + tup = tuple(result) + self._tup = tup + + # class name definitions scope may need to be delayed. + # so we store them separately, and if they are present in methods def, we can readd them + if _class_delayed is None: + _class_delayed = {} + self._class_delayed = _class_delayed + + def __getitem__(self, item): + if isinstance(item, slice): + return self.__class__(self._tup[item]) + return self._tup[item] + + def __len__(self): + return len(self._tup) + + def with_new_scope( + self, include_class_scopes=False, new_class_scope=False, unhide_classdef=False + ): + """ + Return a new ``ScopeStack`` with an additional empty scope. + + :param include_class_scopes: + Whether to include previous scopes that are meant for ClassDefs. + :param new_class_scope: + Whether the new scope is for a ClassDef. + :param unhide_classdef: + Unhide class definitiion scope (when we enter a method) + :rtype: + ``ScopeStack`` + """ + if include_class_scopes: + scopes = tuple(self) + else: + scopes = tuple(s for s in self + if not isinstance(s, _ClassScope)) + if new_class_scope: + new_scope = _ClassScope() + else: + new_scope = {} + cls = type(self) + if unhide_classdef and self._class_delayed: + scopes = tuple([self._class_delayed]) + scopes + result = cls(scopes + (new_scope,), _class_delayed=self._class_delayed) + return result + + def clone_top(self): + """ + Return a new ``ScopeStack`` referencing the same namespaces as ``self``, + but cloning the topmost namespace (and aliasing the others). + """ + scopes = list(self) + scopes[-1] = copy.copy(scopes[-1]) + cls = type(self) + return cls(scopes) + + def merged_to_two(self): + """ + Return a 2-tuple of dicts. + + These can be used for functions that take a ``globals`` and ``locals`` + argument, such as ``eval``. + + If there is only one entry, then return it twice. + + If there are more than two entries, then create a new dict that merges + the more-global ones. The most-local stack will alias the dict from + the existing ScopeStack. + + :rtype: + ``tuple`` of (``dict``, ``dict``) + """ + assert len(self) >= 1 + if len(self) == 1: + return (self[0], self[0]) + if len(self) == 2: + return tuple(self) + d = {} + for scope in self[:-1]: + d.update(scope) + # Return as a 2-tuple. We don't cast the result to ScopeStack because + # it may add __builtins__ again, creating something of length 3. + return (d, self[-1]) + + + def has_star_import(self): + """ + Return whether there are any star-imports in this ScopeStack. + Only relevant in AST-based static analysis mode. + """ + if self._cached_has_star_import: + return True + if any('*' in scope for scope in self): + # There was a star import. Cache that fact before returning. We + # can cache a positive result because a star import can't be undone. + self._cached_has_star_import = True + return True + else: + # There was no star import yet. We can't cache that fact because + # there might be a star import later. + return False + + def __repr__(self): + scopes_reprs = [ + "{:2}".format(i) + " : " + repr(namespace) + for i, namespace in enumerate(self) + ][1:] + + return ( + "<{class_name} object at 0x{hex_id} with namespaces: [\n".format( + class_name=self.__class__.__name__, hex_id=id(self) + ) + + " 0 : {builtins namespace elided.}\n" + + "\n".join(scopes_reprs) + + "\n]>" + ) + + +def symbol_needs_import(fullname, namespaces): + """ + Return whether ``fullname`` is a symbol that needs to be imported, given + the current namespace scopes. + + A symbol needs importing if it is not previously imported or otherwise + assigned. ``namespaces`` normally includes builtins and globals as well as + symbols imported/assigned locally within the scope. + + If the user requested "foo.bar.baz", and we see that "foo.bar" exists + and is not a module, we assume nothing under foo.bar needs import. + This is intentional because (1) the import would not match what is + already in the namespace, and (2) we don't want to do call + getattr(foo.bar, "baz"), since that could invoke code that is slow or + has side effects. + + :type fullname: + ``DottedIdentifier`` + :param fullname: + Fully-qualified symbol name, e.g. "os.path.join". + :type namespaces: + ``list`` of ``dict`` + :param namespaces: + Stack of namespaces to search for existing items. + :rtype: + ``bool`` + :return: + ``True`` if ``fullname`` needs import, else ``False`` + """ + namespaces = ScopeStack(namespaces) + fullname = DottedIdentifier(fullname) + partial_names = fullname.prefixes[::-1] + # Iterate over local scopes. + for ns_idx, ns in reversed(list(enumerate(namespaces))): + # Iterate over partial names: "foo.bar.baz.quux", "foo.bar.baz", ... + for partial_name in partial_names: + # Check if this partial name was imported/assigned in this + # scope. In the common case, there will only be one namespace + # in the namespace stack, i.e. the user globals. + try: + var = ns[str(partial_name)] + except KeyError: + continue + # If we're doing static analysis where we also care about which + # imports are unused, then mark the used ones now. + if isinstance(var, _UseChecker): + var.used = True + # Suppose the user accessed fullname="foo.bar.baz.quux" and + # suppose we see "foo.bar" was imported (or otherwise assigned) in + # the scope vars (most commonly this means it was imported + # globally). Let's check if foo.bar already has a "baz". + prefix_len = len(partial_name.parts) + suffix_parts = fullname.parts[prefix_len:] + pname = str(partial_name) + for part in suffix_parts: + # Check if the var so far is a module -- in fact that it's + # *the* module of a given name. That is, for var == + # foo.bar.baz, check if var is sys.modules['foo.bar.baz']. We + # used to just check if isinstance(foo.bar.baz, ModuleType). + # However, that naive check is wrong for these situations: + # - A module that contains an import of anything other than a + # submodule with its exact name. For example, suppose + # foo.bar contains 'import sqlalchemy'. + # foo.bar.sqlalchemy is of ModuleType, but that doesn't + # mean that we could import foo.bar.sqlalchemy.orm. + # Similar case if foo.bar contains 'from . import baz as + # baz2'. Mistaking these doesn't break much, but might as + # well avoid an unnecessary import attempt. + # - A "proxy module". Suppose foo.bar replaces itself with + # an object with a __getattr__, using + # 'sys.modules[__name__] = ...' Submodules are still + # importable, but sys.modules['foo.bar'] would not be of + # type ModuleType. + if var is not sys.modules.get(pname, object()): + # The variable is not a module. (If this came from a + # local assignment then ``var`` will just be "None" + # here to indicate we know it was assigned but don't + # know about its type.) Thus nothing under it needs + # import. + logger.debug("symbol_needs_import(%r): %s is in namespace %d (under %r) and not a global module, so it doesn't need import", fullname, pname, ns_idx, partial_name) + return False + try: + var = getattr(var, part) + except AttributeError: + # We saw that "foo.bar" is imported, and is a module, but + # it does not have a "baz" attribute. Thus, as far as we + # know so far, foo.bar.baz requires import. But continue + # on to the next scope. + logger.debug("symbol_needs_import(%r): %s is a module in namespace %d (under %r), but has no %r attribute", fullname, pname, ns_idx, partial_name, part) + break # continue outer loop + pname = "%s.%s" % (pname, part) + else: + # We saw that "foo.bar" is imported, and checked that + # foo.bar has an attribute "baz", which has an + # attribute "quux" - so foo.bar.baz.quux does not need + # to be imported. + assert pname == str(fullname) + logger.debug("symbol_needs_import(%r): found it in namespace %d (under %r), so it doesn't need import", fullname, ns_idx, partial_name) + return False + # We didn't find any scope that defined the name. Therefore it needs + # import. + logger.debug( + "symbol_needs_import(%r): no match found in namespaces %s; it needs import", + fullname, + namespaces, + ) + return True + + +class _UseChecker(object): + """ + An object that can check whether it was used. + """ + used = False + + def __init__(self, name, source, lineno): + self.name = name + self.source = source # generally an Import + self.lineno = lineno + + +class _MissingImportFinder(object): + """ + A helper class to be used only by `_find_missing_imports_in_ast`. + + This class visits every AST node and collects symbols that require + importing. A symbol requires importing if it is not already imported or + otherwise defined/assigned in this scope. + + For attributes like "foo.bar.baz", we need to be more sophisticated: + + Suppose the user imports "foo.bar" and then accesses "foo.bar.baz.quux". + Baz may be already available just by importing foo.bar, or it may require + further import. We decide as follows. If foo.bar is not a module, then + we assume whatever's under it can't be imported. If foo.bar is a module + but does not have a 'baz' attribute, then it does require import. + + """ + + def __init__(self, scopestack, find_unused_imports=False, + parse_docstrings=False): + """ + Construct the AST visitor. + + :type scopestack: + `ScopeStack` + :param scopestack: + Initial scope stack. + """ + # Create a stack of namespaces. The caller should pass in a list that + # includes the globals dictionary. ScopeStack() will make sure this + # includes builtins. + scopestack = ScopeStack(scopestack) + # Add an empty namespace to the stack. This facilitates adding stuff + # to scopestack[-1] without ever modifying user globals. + scopestack = scopestack.with_new_scope() + self.scopestack = scopestack + # Create data structure to hold the result. + # missing_imports is a list of (lineno, DottedIdentifier) tuples. + self.missing_imports = [] + # unused_imports is a list of (lineno, Import) tuples, if enabled. + self.unused_imports = [] if find_unused_imports else None + self.parse_docstrings = parse_docstrings + # Function bodies that we need to check after defining names in this + # function scope. + self._deferred_load_checks = [] + # Whether we're currently in a FunctionDef. + self._in_FunctionDef = False + # Current lineno. + self._lineno = None + self._in_class_def = 0 + + def find_missing_imports(self, node): + self._scan_node(node) + return sorted(set(imp for lineno,imp in self.missing_imports)) + + def _scan_node(self, node): + oldscopestack = self.scopestack + myglobals = self.scopestack[-1] + try: + self.visit(node) + self._finish_deferred_load_checks() + assert self.scopestack is oldscopestack + assert self.scopestack[-1] is myglobals + finally: + self.scopestack = oldscopestack + + def scan_for_import_issues(self, codeblock): + # See global `scan_for_import_issues` + codeblock = PythonBlock(codeblock) + node = codeblock.ast_node + self._scan_node(node) + # Get missing imports now, before handling docstrings. We don't want + # references in doctests to be noted as missing-imports. For now we + # just let the code accumulate into self.missing_imports and ignore + # the result. + missing_imports = sorted(self.missing_imports) + if self.parse_docstrings and self.unused_imports is not None: + doctest_blocks = codeblock.get_doctests() + # Parse each doctest. Don't report missing imports in doctests, + # but do treat existing imports as 'used' if they are used in + # doctests. The linenos are currently wrong, but we don't use + # them so it's not important to fix. + for block in doctest_blocks: + # There are doctests. Parse them. + # Doctest blocks inherit the global scope after parsing all + # non-doctest code, and each doctest block individually creates a new + # scope (not shared between doctest blocks). + # TODO: Theoretically we should clone the entire scopestack, + # not just add a new scope, in case the doctest uses 'global'. + # Currently we don't support the 'global' keyword anyway so + # this doesn't matter yet, and it's uncommon to use 'global' + # in a doctest, so this is low priority to fix. + oldstack = self.scopestack + self.scopestack = self.scopestack.with_new_scope() + self._scan_node(block.ast_node) + self.scopestack = oldstack + # Find literal brace identifiers like "... `Foo` ...". + # TODO: Do this inline: (1) faster; (2) can use proper scope of vars + # Once we do that, use _check_load() with new args + # check_missing_imports=False, check_unused_imports=True + literal_brace_identifiers = set( + iden + for f in codeblock.string_literals() + for iden in brace_identifiers(f.s)) + if literal_brace_identifiers: + for ident in literal_brace_identifiers: + try: + ident = DottedIdentifier(ident) + except BadDottedIdentifierError: + continue + symbol_needs_import(ident, self.scopestack) + self._scan_unused_imports() + return missing_imports, self.unused_imports + + def visit(self, node): + """ + Visit a node. + + :type node: + ``ast.AST`` or ``list`` of ``ast.AST`` + """ + # Modification of ast.NodeVisitor.visit(). Support list inputs. + logger.debug("_MissingImportFinder.visit(%r)", node) + lineno = getattr(node, 'lineno', None) + if lineno: + self._lineno = lineno + if isinstance(node, list): + for item in node: + self.visit(item) + elif isinstance(node, ast.AST): + method = 'visit_' + node.__class__.__name__ + if not hasattr(self, method): + logger.debug( + "_MissingImportFinder has no method %r, using generic_visit", method + ) + + visitor = getattr(self, method, self.generic_visit) + return visitor(node) + else: + raise TypeError("unexpected %s" % (type(node).__name__,)) + + def generic_visit(self, node): + """ + Generic visitor that visits all of the node's field values, in the + order declared by ``node._fields``. + + Called if no explicit visitor function exists for a node. + """ + # Modification of ast.NodeVisitor.generic_visit: recurse to visit() + # even for lists, and be more explicit about type checking. + for field, value in ast.iter_fields(node): + if isinstance(value, ast.AST): + self.visit(value) + elif isinstance(value, list): + if all(isinstance(v, str) for v in value): + pass + elif all(isinstance(v, ast.AST) for v in value): + self.visit(value) + else: + raise TypeError( + "unexpected %s" % + (', '.join(type(v).__name__ for v in value))) + elif isinstance(value, (six.integer_types, float, complex, + str, six.text_type, NoneType, bytes, + EllipsisType)): + pass + else: + raise TypeError( + "unexpected %s for %s.%s" + % (type(value).__name__, type(node).__name__, field)) + + + @contextlib.contextmanager + def _NewScopeCtx(self, **kwargs): + """ + Context manager that temporarily pushes a new empty namespace onto the + stack of namespaces. + """ + prev_scopestack = self.scopestack + new_scopestack = prev_scopestack.with_new_scope(**kwargs) + self.scopestack = new_scopestack + try: + yield + finally: + assert self.scopestack is new_scopestack + self.scopestack = prev_scopestack + + @contextlib.contextmanager + def _UpScopeCtx(self): + """ + Context manager that temporarily moves up one in the scope stack + """ + if len(self.scopestack) < 2: + raise ValueError("There must be at least two scopes on the stack to move up a scope.") + prev_scopestack = self.scopestack + new_scopestack = prev_scopestack[:-1] + try: + self.scopestack = new_scopestack + yield + finally: + assert self.scopestack is new_scopestack + self.scopestack = prev_scopestack + + def visit_Assign(self, node): + # Visit an assignment statement (lhs = rhs). This implementation of + # visit_Assign is just like the generic one, but we make sure we visit + # node.value (RHS of assignment operator), then node.targets (LHS of + # assignment operator). The default would have been to visit LHS, + # then RHS. The reason we need to visit RHS first is the following. + # If the code is 'foo = foo + 1', we want to first process the Load + # for foo (RHS) before we process the Store for foo (LHS). If we + # visited LHS then RHS, we would have a bug in the following sample + # code: + # from bar import foo # L1 + # foo = foo + 1 # L2 + # The good RHS-then-LHS visit-order would see the Load('foo') on L2, + # understand that it got used before the Store('foo') overwrote it. + # The bad LHS-then-RHS visit-order would visit Store('foo') on L2, and + # think that foo was never referenced before it was overwritten, and + # therefore think that the 'import foo' on L1 could be removed. + self.visit(node.value) + self.visit(node.targets) + self._visit__all__(node) + + def _visit__all__(self, node): + if self._in_FunctionDef: + return + if (len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) + and node.targets[0].id == '__all__'): + if not isinstance(node.value, ast.List): + logger.warning("Don't know how to handle __all__ as (%s)" % node.value) + return + if not all(isinstance(e, ast.Str) for e in node.value.elts): + logger.warning("Don't know how to handle __all__ with list elements other than str") + return + for e in node.value.elts: + self._visit_Load(e.s) + + def visit_ClassDef(self, node): + logger.debug("visit_ClassDef(%r)", node) + if PY3: + assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list') + else: + assert node._fields == ('name', 'bases', 'body', 'decorator_list') + self.visit(node.bases) + self.visit(node.decorator_list) + # The class's name is only visible to others (not to the body to the + # class), but is accessible in the methods themselves. See https://github.com/deshaw/pyflyby/issues/147 + if PY3: + self.visit(node.keywords) + + # we only care about the first defined class, + # we don't detect issues with nested classes. + if self._in_class_def == 0: + self.scopestack._class_delayed[node.name] = None + with self._NewScopeCtx(new_class_scope=True): + if not self._in_class_def: + self._in_class_def += 1 + self._visit_Store(node.name) + self.visit(node.body) + self._in_class_def -= 1 + self._visit_Store(node.name) + + def visit_AsyncFunctionDef(self, node): + return self.visit_FunctionDef(node) + + def visit_FunctionDef(self, node): + # Visit a function definition. + # - Visit args and decorator list normally. + # - Visit function body in a special mode where we defer checking + # loads until later, and don't load names from the parent ClassDef + # scope. + # - Store the name in the current scope (but not visibly to + # args/decorator_list). + if PY2: + assert node._fields == ('name', 'args', 'body', 'decorator_list'), node._fields + elif sys.version_info >= (3, 8): + assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields + else: + assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns'), node._fields + with self._NewScopeCtx(include_class_scopes=True): + self.visit(node.args) + self.visit(node.decorator_list) + if PY3: + if node.returns: + self.visit(node.returns) + if sys.version_info >= (3, 8): + self._visit_typecomment(node.type_comment) + old_in_FunctionDef = self._in_FunctionDef + self._in_FunctionDef = True + with self._NewScopeCtx(unhide_classdef=True): + self.visit(node.body) + self._in_FunctionDef = old_in_FunctionDef + self._visit_Store(node.name) + + def visit_Lambda(self, node): + # Like FunctionDef, but without the decorator_list or name. + assert node._fields == ('args', 'body'), node._fields + with self._NewScopeCtx(include_class_scopes=True): + self.visit(node.args) + old_in_FunctionDef = self._in_FunctionDef + self._in_FunctionDef = True + with self._NewScopeCtx(): + self.visit(node.body) + self._in_FunctionDef = old_in_FunctionDef + + def _visit_typecomment(self, typecomment): + """ + Warning, when a type comment the node is a string, not an ast node. + We also get two types of type comments: + + + The signature one just after a function definition + + def foo(a): + # type: int -> None + pass + + And the variable annotation ones: + + def foo(a #type: int + ): + pass + + ast parse "func_type" mode only support the first one. + + """ + if typecomment is None: + return + if '->' in typecomment: + node = ast.parse(typecomment, mode='func_type') + else: + node = ast.parse(typecomment) + + self.visit(node) + + def visit_arguments(self, node): + if PY2: + assert node._fields == ('args', 'vararg', 'kwarg', 'defaults'), node._fields + elif sys.version_info >= (3, 8): + assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields + else: + assert node._fields == ('args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields + # Argument/parameter list. Note that the defaults should be + # considered "Load"s from the upper scope, and the argument names are + # "Store"s in the function scope. + + # E.g. consider: + # def f(x=y, y=x): pass + # Both x and y should be considered undefined (unless they were indeed + # defined before the def). + # We assume visit_arguments is always called from a _NewScopeCtx + # context + with self._UpScopeCtx(): + self.visit(node.defaults) + if PY3: + for i in node.kw_defaults: + if i: + self.visit(i) + # Store arg names. + self.visit(node.args) + if PY3: + self.visit(node.kwonlyargs) + if sys.version_info >= (3, 8): + self.visit(node.posonlyargs) + # Store vararg/kwarg names. + self._visit_Store(node.vararg) + self._visit_Store(node.kwarg) + + def visit_ExceptHandler(self, node): + assert node._fields == ('type', 'name', 'body') + if node.type: + self.visit(node.type) + if node.name: + # ExceptHandler.name is a string in Python 3 and a Name with Store in + # Python 2 + if PY3: + self._visit_Store(node.name) + else: + self.visit(node.name) + self.visit(node.body) + + def visit_Dict(self, node): + assert node._fields == ('keys', 'values') + # In Python 3, keys can be None, indicating a ** expression + for key in node.keys: + if key: + self.visit(key) + self.visit(node.values) + + def visit_comprehension(self, node): + # Visit a "comprehension" node, which is a component of list + # comprehensions and generator expressions. + self.visit(node.iter) + def visit_target(target): + if isinstance(target, ast.Name): + self._visit_Store(target.id) + elif isinstance(target, (ast.Tuple, ast.List)): + for elt in target.elts: + visit_target(elt) + else: + # Unusual stuff like: + # [f(x) for x[0] in mylist] + # [f(x) for x.foo in mylist] + # [f(x) for x.foo[0].foo in mylist] + self.visit(target) + visit_target(node.target) + self.visit(node.ifs) + + def visit_ListComp(self, node): + # Visit a list comprehension node. + # This is basically the same as the generic visit, except that we + # visit the comprehension node(s) before the elt node. + # (generic_visit() would visit the elt first, because that comes first + # in ListComp._fields). + # For Python2, we intentionally don't enter a new scope here, because + # a list comprehensive _does_ leak variables out of its scope (unlike + # generator expressions). + # For Python3, we do need to enter a new scope here. + if PY3: + with self._NewScopeCtx(include_class_scopes=True): + self.visit(node.generators) + self.visit(node.elt) + else: + self.visit(node.generators) + self.visit(node.elt) + + def visit_DictComp(self, node): + # Visit a dict comprehension node. + # This is similar to the generic visit, except: + # - We visit the comprehension node(s) before the elt node. + # - We create a new scope for the variables. + # We do enter a new scope (for both py2 and py3). A dict comprehension + # does _not_ leak variables out of its scope (unlike py2 list + # comprehensions). + with self._NewScopeCtx(include_class_scopes=True): + self.visit(node.generators) + self.visit(node.key) + self.visit(node.value) + + def visit_SetComp(self, node): + # Visit a set comprehension node. + # We do enter a new scope (for both py2 and py3). A set comprehension + # does _not_ leak variables out of its scope (unlike py2 list + # comprehensions). + with self._NewScopeCtx(include_class_scopes=True): + self.visit(node.generators) + self.visit(node.elt) + + def visit_GeneratorExp(self, node): + # Visit a generator expression node. + # We do enter a new scope (for both py2 and py3). A generator + # expression does _not_ leak variables out of its scope (unlike py2 + # list comprehensions). + with self._NewScopeCtx(include_class_scopes=True): + self.visit(node.generators) + self.visit(node.elt) + + def visit_ImportFrom(self, node): + modulename = "." * node.level + (node.module or "") + logger.debug("visit_ImportFrom(%r, ...)", modulename) + for alias_node in node.names: + self.visit_alias(alias_node, modulename) + + def visit_alias(self, node, modulename=None): + # Visit an import alias node. + # TODO: Currently we treat 'import foo' the same as if the user did + # 'foo = 123', i.e. we treat it as a black box (non-module). This is + # to avoid actually importing it yet. But this means we won't know + # whether foo.bar is available so we won't auto-import it. Maybe we + # should give up on not importing it and just import it in a scratch + # namespace, so we can check. + self._visit_StoreImport(node, modulename) + self.generic_visit(node) + + def visit_Name(self, node): + logger.debug("visit_Name(%r)", node.id) + self._visit_fullname(node.id, node.ctx) + + def visit_arg(self, node): + assert not PY2 + if sys.version_info >= (3, 8): + assert node._fields == ('arg', 'annotation', 'type_comment'), node._fields + else: + assert node._fields == ('arg', 'annotation'), node._fields + if node.annotation: + self.visit(node.annotation) + # Treat it like a Name node would from Python 2 + self._visit_fullname(node.arg, ast.Param()) + if sys.version_info >= (3, 8): + self._visit_typecomment(node.type_comment) + + def visit_Attribute(self, node): + name_revparts = [] + n = node + while isinstance(n, ast.Attribute): + name_revparts.append(n.attr) + n = n.value + if not isinstance(n, ast.Name): + # Attribute of a non-symbol, e.g. (a+b).c + # We do nothing about "c", but we do recurse on (a+b) since those + # may have symbols we care about. + self.generic_visit(node) + return + name_revparts.append(n.id) + name_parts = name_revparts[::-1] + fullname = ".".join(name_parts) + logger.debug("visit_Attribute(%r): fullname=%r, ctx=%r", node.attr, fullname, node.ctx) + self._visit_fullname(fullname, node.ctx) + + def _visit_fullname(self, fullname, ctx): + if isinstance(ctx, (ast.Store, ast.Param)): + self._visit_Store(fullname) + elif isinstance(ctx, ast.Load): + self._visit_Load(fullname) + + def _visit_StoreImport(self, node, modulename): + name = node.asname or node.name + logger.debug("_visit_StoreImport(asname=%r,name=%r)", + node.asname, node.name) + is_star = node.name == '*' + if is_star: + logger.debug("Got star import: line %s: 'from %s import *'", + self._lineno, modulename) + if not node.asname and not is_star: + # Handle leading prefixes so we don't think they're unused + for prefix in DottedIdentifier(node.name).prefixes[:-1]: + self._visit_Store(str(prefix), None) + if self.unused_imports is None or is_star or modulename == "__future__": + value = None + else: + imp = Import.from_split((modulename, node.name, name)) + logger.debug("_visit_StoreImport(): imp = %r", imp) + # Keep track of whether we've used this import. + value = _UseChecker(name, imp, self._lineno) + self._visit_Store(name, value) + + def _visit_Store(self, fullname, value=None): + logger.debug("_visit_Store(%r)", fullname) + if fullname is None: + return + scope = self.scopestack[-1] + if PY3 and isinstance(fullname, ast.arg): + fullname = fullname.arg + if self.unused_imports is not None: + if fullname != '*': + # If we're storing "foo.bar.baz = 123", then "foo" and + # "foo.bar" have now been used and the import should not be + # removed. + for ancestor in DottedIdentifier(fullname).prefixes[:-1]: + if symbol_needs_import(ancestor, self.scopestack): + m = (self._lineno, DottedIdentifier(fullname)) + if m not in self.missing_imports: + self.missing_imports.append(m) + # If we're redefining something, and it has not been used, then + # record it as unused. + oldvalue = scope.get(fullname) + if isinstance(oldvalue, _UseChecker) and not oldvalue.used: + self.unused_imports.append((oldvalue.lineno, oldvalue.source)) + scope[fullname] = value + + def visit_Delete(self, node): + scope = self.scopestack[-1] + for target in node.targets: + if isinstance(target, ast.Name): + # 'del foo' + if target.id not in scope: + # 'del x' without 'x' in current scope. Should we warn? + continue + del scope[target.id] + elif isinstance(target, ast.Attribute): + # 'del foo.bar.baz', 'del foo().bar', etc + # We ignore the 'del ...bar' part and just visit the + # left-hand-side of the delattr. We need to do this explicitly + # instead of relying on a generic_visit on ``node`` itself. + # Reason: We want visit_Attribute to process a getattr for + # 'foo.bar'. + self.visit(target.value) + else: + # 'del foo.bar[123]' (ast.Subscript), etc. + # We can generically-visit the entire target node here. + self.visit(target) + # Don't call generic_visit(node) here. Reason: We already visit the + # parts above, if relevant. + + def _visit_Load(self, fullname): + logger.debug("_visit_Load(%r)", fullname) + if self._in_FunctionDef: + # We're in a FunctionDef. We need to defer checking whether this + # references undefined names. The reason is that globals (or + # stores in a parent function scope) may be stored later. + # For example, bar() is defined later after the body of foo(), but + # still available to foo() when it is called: + # def foo(): + # return bar() + # def bar(): + # return 42 + # foo() + # To support this, we clone the top of the scope stack and alias + # the other scopes in the stack. Later stores in the same scope + # shouldn't count, e.g. x should be considered undefined in the + # following example: + # def foo(): + # print x + # x = 1 + # On the other hand, we intentionally alias the other scopes + # rather than cloning them, because the point is to allow them to + # be modified until we do the check at the end. + + if symbol_needs_import(fullname, self.scopestack): + data = (fullname, self.scopestack.clone_top(), self._lineno) + self._deferred_load_checks.append(data) + else: + # We're not in a FunctionDef. Deferring would give us the same + # result; we do the check now to avoid the overhead of cloning the + # stack. + self._check_load(fullname, self.scopestack, self._lineno) + + def _check_load(self, fullname, scopestack, lineno): + # Check if the symbol needs import. (As a side effect, if the object + # is a _UseChecker, this will mark it as used. TODO: It would be + # better to refactor symbol_needs_import so that it just returns the + # object it found, and we mark it as used here.) + fullname = DottedIdentifier(fullname) + if symbol_needs_import(fullname, scopestack) and not scopestack.has_star_import(): + self.missing_imports.append((lineno,fullname)) + + def _finish_deferred_load_checks(self): + for fullname, scopestack, lineno in self._deferred_load_checks: + self._check_load(fullname, scopestack, lineno) + self._deferred_load_checks = [] + + def _scan_unused_imports(self): + # If requested, then check which of our imports were unused. + # For now we only scan the top level. If we wanted to support + # non-global unused-import checking, then we should check this + # whenever popping a scopestack. + unused_imports = self.unused_imports + if unused_imports is None: + return + scope = self.scopestack[-1] + for name, value in six.iteritems(scope): + if not isinstance(value, _UseChecker): + continue + if value.used: + continue + unused_imports.append(( value.lineno, value.source )) + unused_imports.sort() + + +def scan_for_import_issues(codeblock, find_unused_imports=True, parse_docstrings=False): + """ + Find missing and unused imports, by lineno. + + >>> arg = "import numpy, aa.bb as cc\\nnumpy.arange(x)\\narange(x)" + >>> missing, unused = scan_for_import_issues(arg) + >>> missing + [(2, DottedIdentifier('x')), (3, DottedIdentifier('arange')), (3, DottedIdentifier('x'))] + >>> unused + [(1, Import('from aa import bb as cc'))] + + :type codeblock: + ``PythonBlock`` + :type namespaces: + ``dict`` or ``list`` of ``dict`` + :param parse_docstrings: + Whether to parse docstrings. + Compare the following examples. When parse_docstrings=True, 'bar' is + not considered unused because there is a string that references it in + braces:: + + >>> scan_for_import_issues("import foo as bar, baz\\n'{bar}'\\n") + ([], [(1, Import('import baz')), (1, Import('import foo as bar'))]) + >>> scan_for_import_issues("import foo as bar, baz\\n'{bar}'\\n", parse_docstrings=True) + ([], [(1, Import('import baz'))]) + + """ + logger.debug("scan_for_import_issues()") + codeblock = PythonBlock(codeblock) + namespaces = ScopeStack([{}]) + finder = _MissingImportFinder(namespaces, + find_unused_imports=find_unused_imports, + parse_docstrings=parse_docstrings) + return finder.scan_for_import_issues(codeblock) + + +def _find_missing_imports_in_ast(node, namespaces): + """ + Find missing imports in an AST node. + Helper function to `find_missing_imports`. + + >>> node = ast.parse("import numpy; numpy.arange(x) + arange(x)") + >>> _find_missing_imports_in_ast(node, [{}]) + [DottedIdentifier('arange'), DottedIdentifier('x')] + + :type node: + ``ast.AST`` + :type namespaces: + ``dict`` or ``list`` of ``dict`` + :rtype: + ``list`` of ``DottedIdentifier`` + """ + if not isinstance(node, ast.AST): + raise TypeError + # Traverse the abstract syntax tree. + if logger.debug_enabled: + logger.debug("ast=%s", ast.dump(node)) + return _MissingImportFinder(namespaces).find_missing_imports(node) + +# TODO: maybe we should replace _find_missing_imports_in_ast with +# _find_missing_imports_in_code(compile(node)). The method of parsing opcodes +# is simpler, because Python takes care of the scoping issue for us and we +# don't have to worry about locals. It does, however, depend on CPython +# implementation details, whereas the AST is well-defined by the language. + + +def _find_missing_imports_in_code(co, namespaces): + """ + Find missing imports in a code object. + Helper function to `find_missing_imports`. + + >>> f = lambda: foo.bar(x) + baz(y) + >>> [str(m) for m in _find_missing_imports_in_code(f.__code__, [{}])] + ['baz', 'foo.bar', 'x', 'y'] + + >>> f = lambda x: (lambda: x+y) + >>> _find_missing_imports_in_code(f.__code__, [{}]) + [DottedIdentifier('y')] + + :type co: + ``types.CodeType`` + :type namespaces: + ``dict`` or ``list`` of ``dict`` + :rtype: + ``list`` of ``str`` + """ + loads_without_stores = set() + _find_loads_without_stores_in_code(co, loads_without_stores) + missing_imports = [ + DottedIdentifier(fullname) for fullname in sorted(loads_without_stores) + if symbol_needs_import(fullname, namespaces) + ] + return missing_imports + + +def _find_loads_without_stores_in_code(co, loads_without_stores): + """ + Find global LOADs without corresponding STOREs, by disassembling code. + Recursive helper for `_find_missing_imports_in_code`. + + :type co: + ``types.CodeType`` + :param co: + Code object, e.g. ``function.__code__`` + :type loads_without_stores: + ``set`` + :param loads_without_stores: + Mutable set to which we add loads without stores. + :return: + ``None`` + """ + if not isinstance(co, types.CodeType): + raise TypeError( + "_find_loads_without_stores_in_code(): expected a CodeType; got a %s" + % (type(co).__name__,)) + # Initialize local constants for fast access. + from opcode import HAVE_ARGUMENT, EXTENDED_ARG, opmap + LOAD_ATTR = opmap['LOAD_ATTR'] + LOAD_METHOD = opmap['LOAD_METHOD'] if PY3 else None + LOAD_GLOBAL = opmap['LOAD_GLOBAL'] + LOAD_NAME = opmap['LOAD_NAME'] + STORE_ATTR = opmap['STORE_ATTR'] + STORE_GLOBAL = opmap['STORE_GLOBAL'] + STORE_NAME = opmap['STORE_NAME'] + # Keep track of the partial name so far that started with a LOAD_GLOBAL. + # If ``pending`` is not None, then it is a list representing the name + # components we've seen so far. + pending = None + # Disassemble the code. Look for LOADs and STOREs. This code is based on + # ``dis.disassemble``. + # + # Scenarios: + # + # * Function-level load a toplevel global + # def f(): + # aa + # => LOAD_GLOBAL; other (not LOAD_ATTR or STORE_ATTR) + # * Function-level load an attribute of global + # def f(): + # aa.bb.cc + # => LOAD_GLOBAL; LOAD_ATTR; LOAD_ATTR; other + # * Function-level store a toplevel global + # def f(): + # global aa + # aa = 42 + # => STORE_GLOBAL + # * Function-level store an attribute of global + # def f(): + # aa.bb.cc = 42 + # => LOAD_GLOBAL, LOAD_ATTR, STORE_ATTR + # * Function-level load a local + # def f(): + # aa = 42 + # return aa + # => LOAD_FAST or LOAD_NAME + # * Function-level store a local + # def f(): + # aa = 42 + # => STORE_FAST or STORE_NAME + # * Function-level load an attribute of a local + # def f(): + # aa = 42 + # return aa.bb.cc + # => LOAD_FAST; LOAD_ATTR; LOAD_ATTR + # * Function-level store an attribute of a local + # def f(): + # aa == 42 + # aa.bb.cc = 99 + # => LOAD_FAST; LOAD_ATTR; STORE_ATTR + # * Function-level load an attribute of an expression other than a name + # def f(): + # foo().bb.cc + # => [CALL_FUNCTION, etc]; LOAD_ATTR; LOAD_ATTR + # * Function-level store an attribute of an expression other than a name + # def f(): + # foo().bb.cc = 42 + # => [CALL_FUNCTION, etc]; LOAD_ATTR; STORE_ATTR + # * Function-level import + # def f(): + # import aa.bb.cc + # => IMPORT_NAME "aa.bb.cc", STORE_FAST "aa" + # * Module-level load of a top-level global + # aa + # => LOAD_NAME + # * Module-level store of a top-level global + # aa = 42 + # => STORE_NAME + # * Module-level load of an attribute of a global + # aa.bb.cc + # => LOAD_NAME, LOAD_ATTR, LOAD_ATTR + # * Module-level store of an attribute of a global + # aa.bb.cc = 42 + # => LOAD_NAME, LOAD_ATTR, STORE_ATTR + # * Module-level import + # import aa.bb.cc + # IMPORT_NAME "aa.bb.cc", STORE_NAME "aa" + # * Closure + # def f(): + # aa = 42 + # return lambda: aa + # f: STORE_DEREF, LOAD_CLOSURE, MAKE_CLOSURE + # g = f(): LOAD_DEREF + bytecode = co.co_code + n = len(bytecode) + i = 0 + extended_arg = 0 + stores = set() + loads_after_label = set() + loads_before_label_without_stores = set() + # Find the earliest target of a backward jump. + earliest_backjump_label = _find_earliest_backjump_label(bytecode) + # Loop through bytecode. + while i < n: + c = bytecode[i] + op = _op(c) + i += 1 + if op >= HAVE_ARGUMENT: + if PY2: + oparg = _op(bytecode[i]) + _op(bytecode[i+1])*256 + extended_arg + extended_arg = 0 + i = i+2 + if op == EXTENDED_ARG: + extended_arg = oparg*65536 + continue + else: + oparg = bytecode[i] | extended_arg + extended_arg = 0 + if op == EXTENDED_ARG: + extended_arg = (oparg << 8) + continue + i += 1 + + if pending is not None: + if op == STORE_ATTR: + # {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* {STORE_ATTR} + pending.append(co.co_names[oparg]) + fullname = ".".join(pending) + pending = None + stores.add(fullname) + continue + if op in [LOAD_ATTR, LOAD_METHOD]: + # {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* so far; + # possibly more LOAD_ATTR/STORE_ATTR will follow + pending.append(co.co_names[oparg]) + continue + # {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* (and no more + # LOAD_ATTR/STORE_ATTR) + fullname = ".".join(pending) + pending = None + if i >= earliest_backjump_label: + loads_after_label.add(fullname) + elif fullname not in stores: + loads_before_label_without_stores.add(fullname) + # Fall through. + + if op in [LOAD_GLOBAL, LOAD_NAME]: + pending = [co.co_names[oparg]] + continue + + if op in [STORE_GLOBAL, STORE_NAME]: + stores.add(co.co_names[oparg]) + continue + + # We don't need to worry about: LOAD_FAST, STORE_FAST, LOAD_CLOSURE, + # LOAD_DEREF, STORE_DEREF. LOAD_FAST and STORE_FAST refer to local + # variables; LOAD_CLOSURE, LOAD_DEREF, and STORE_DEREF relate to + # closure variables. In both cases we know these are not missing + # imports. It's convenient that these are separate opcodes, because + # then we don't need to deal with them manually. + + # Record which variables we saw that were loaded in this module without a + # corresponding store. We handle two cases. + # + # 1. Load-before-store; no loops (i.e. no backward jumps). + # Example A:: + # foo.bar() + # import foo + # In the above example A, "foo" was used before it was imported. We + # consider it a candidate for auto-import. + # Example B: + # if condition1(): # L1 + # import foo1 # L2 + # foo1.bar() + foo2.bar() # L3 + # import foo2 # L4 + # In the above example B, "foo2" was used before it was imported; the + # fact that there is a jump target at L3 is irrelevant because it is + # the target of a forward jump; there is no way that foo2 can be + # imported (L4) before foo2 is used (L3). + # On the other hand, we don't know whether condition1 will be true, + # so we assume L2 will be executed and therefore don't consider the + # use of "foo1" at L3 to be problematic. + # + # 2. Load-before-store; with loops (backward jumps). Example: + # for i in range(10): + # if i > 0: + # print x + # else: + # x = "hello" + # In the above example, "x" is actually always stored before load, + # even though in a linear reading of the bytecode we would see the + # store before any loads. + # + # It would be impossible to perfectly follow conditional code, because + # code could be arbitrarily complicated and would require a flow control + # analysis that solves the halting problem. We do the best we can and + # handle case 1 as a common case. + # + # Case 1: If we haven't seen a label, then we know that any load + # before a preceding store is definitely too early. + # Case 2: If we have seen a label, then we consider any preceding + # or subsequent store to potentially match the load. + loads_without_stores.update( loads_before_label_without_stores ) # case 1 + loads_without_stores.update( loads_after_label - stores ) # case 2 + + # The ``pending`` variable should have been reset at this point, because a + # function should always end with a RETURN_VALUE opcode and therefore not + # end in a LOAD_ATTR. + assert pending is None + + # Recurse on inner function definitions, lambdas, generators, etc. + for arg in co.co_consts: + if isinstance(arg, types.CodeType): + _find_loads_without_stores_in_code(arg, loads_without_stores) + + +def _op(c): + # bytecode is bytes in Python 3, which when indexed gives integers + if PY2: + return ord(c) + return c + + +def _find_earliest_backjump_label(bytecode): + """ + Find the earliest target of a backward jump. + + These normally represent loops. + + For example, given the source code:: + + >>> def f(): + ... if foo1(): + ... foo2() + ... else: + ... foo3() + ... foo4() + ... while foo5(): # L7 + ... foo6() + + In python 2.6, the disassembled bytecode is:: + + >>> import dis + >>> dis.dis(f) # doctest: +SKIP + 2 0 LOAD_GLOBAL 0 (foo1) + 3 CALL_FUNCTION 0 + 6 JUMP_IF_FALSE 11 (to 20) + 9 POP_TOP + + 3 10 LOAD_GLOBAL 1 (foo2) + 13 CALL_FUNCTION 0 + 16 POP_TOP + 17 JUMP_FORWARD 8 (to 28) + >> 20 POP_TOP + + 5 21 LOAD_GLOBAL 2 (foo3) + 24 CALL_FUNCTION 0 + 27 POP_TOP + + 6 >> 28 LOAD_GLOBAL 3 (foo4) + 31 CALL_FUNCTION 0 + 34 POP_TOP + + 7 35 SETUP_LOOP 22 (to 60) + >> 38 LOAD_GLOBAL 4 (foo5) + 41 CALL_FUNCTION 0 + 44 JUMP_IF_FALSE 11 (to 58) + 47 POP_TOP + + 8 48 LOAD_GLOBAL 5 (foo6) + 51 CALL_FUNCTION 0 + 54 POP_TOP + 55 JUMP_ABSOLUTE 38 + >> 58 POP_TOP + 59 POP_BLOCK + >> 60 LOAD_CONST 0 (None) + 63 RETURN_VALUE + + The earliest target of a backward jump would be the 'while' loop at L7, at + bytecode offset 38:: + + >>> _find_earliest_backjump_label(f.__code__.co_code) # doctest: +SKIP + 38 + + Note that in this example there are earlier targets of jumps at bytecode + offsets 20 and 28, but those are targets of _forward_ jumps, and the + clients of this function care about the earliest _backward_ jump. + + If there are no backward jumps, return an offset that points after the end + of the bytecode. + + :type bytecode: + ``bytes`` + :param bytecode: + Compiled bytecode, e.g. ``function.__code__.co_code``. + :rtype: + ``int`` + :return: + The earliest target of a backward jump, as an offset into the bytecode. + """ + # Code based on dis.findlabels(). + from opcode import HAVE_ARGUMENT, hasjrel, hasjabs + if not isinstance(bytecode, bytes): + raise TypeError + n = len(bytecode) + earliest_backjump_label = n + i = 0 + while i < n: + c = bytecode[i] + op = _op(c) + i += 1 + if op < HAVE_ARGUMENT: + continue + oparg = _op(bytecode[i]) + _op(bytecode[i+1])*256 + i += 2 + label = None + if op in hasjrel: + label = i+oparg + elif op in hasjabs: + label = oparg + else: + # No label + continue + if label >= i: + # Label is a forward jump + continue + # Found a backjump label. Keep track of the earliest one. + earliest_backjump_label = min(earliest_backjump_label, label) + return earliest_backjump_label + + +def find_missing_imports(arg, namespaces): + """ + Find symbols in the given code that require import. + + We consider a symbol to require import if we see an access ("Load" in AST + terminology) without an import or assignment ("Store" in AST terminology) + in the same lexical scope. + + For example, if we use an empty list of namespaces, then "os.path.join" is + a symbol that requires import:: + + >>> [str(m) for m in find_missing_imports("os.path.join", namespaces=[{}])] + ['os.path.join'] + + But if the global namespace already has the "os" module imported, then we + know that ``os`` has a "path" attribute, which has a "join" attribute, so + nothing needs import:: + + >>> import os + >>> find_missing_imports("os.path.join", namespaces=[{"os":os}]) + [] + + Builtins are always included:: + + >>> [str(m) for m in find_missing_imports("os, sys, eval", [{"os": os}])] + ['sys'] + + All symbols that are not defined are included:: + + >>> [str(m) for m in find_missing_imports("numpy.arange(x) + arange(y)", [{"y": 3}])] + ['arange', 'numpy.arange', 'x'] + + If something is imported/assigned/etc within the scope, then we assume it + doesn't require importing:: + + >>> [str(m) for m in find_missing_imports("import numpy; numpy.arange(x) + arange(x)", [{}])] + ['arange', 'x'] + + >>> [str(m) for m in find_missing_imports("from numpy import pi; numpy.pi + pi + x", [{}])] + ['numpy.pi', 'x'] + + >>> [str(m) for m in find_missing_imports("for x in range(3): print(numpy.arange(x))", [{}])] + ['numpy.arange'] + + >>> [str(m) for m in find_missing_imports("foo1 = func(); foo1.bar + foo2.bar", [{}])] + ['foo2.bar', 'func'] + + >>> [str(m) for m in find_missing_imports("a.b.y = 1; a.b.x, a.b.y, a.b.z", [{}])] + ['a.b.x', 'a.b.z'] + + find_missing_imports() parses the AST, so it understands scoping. In the + following example, ``x`` is never undefined:: + + >>> find_missing_imports("(lambda x: x*x)(7)", [{}]) + [] + + but this example, ``x`` is undefined at global scope:: + + >>> [str(m) for m in find_missing_imports("(lambda x: x*x)(7) + x", [{}])] + ['x'] + + The (unintuitive) rules for generator expressions and list comprehensions + in Python 2 are handled correctly:: + + >>> # Python 3 + >>> [str(m) for m in find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])] # doctest: +SKIP + ['y', 'z'] + + >>> # Python 2 + >>> [str(m) for m in find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])] # doctest: +SKIP + ['z'] + + >>> [str(m) for m in find_missing_imports("(x+y+z for x,y in [(1,2)]), y", [{}])] + ['y', 'z'] + + Only fully-qualified names starting at top-level are included:: + + >>> [str(m) for m in find_missing_imports("( ( a . b ) . x ) . y + ( c + d ) . x . y", [{}])] + ['a.b.x.y', 'c', 'd'] + + :type arg: + ``str``, ``ast.AST``, `PythonBlock`, ``callable``, or ``types.CodeType`` + :param arg: + Python code, either as source text, a parsed AST, or compiled code; can + be as simple as a single qualified name, or as complex as an entire + module text. + :type namespaces: + ``dict`` or ``list`` of ``dict`` + :param namespaces: + Stack of namespaces of symbols that exist per scope. + :rtype: + ``list`` of ``DottedIdentifier`` + """ + namespaces = ScopeStack(namespaces) + if isinstance(arg, (DottedIdentifier, six.string_types)): + try: + arg = DottedIdentifier(arg) + except BadDottedIdentifierError: + pass + else: + # The string is a single identifier. Check directly whether it + # needs import. This is an optimization to not bother parsing an + # AST. + if symbol_needs_import(arg, namespaces): + return [arg] + else: + return [] + # Parse the string into an AST. + kw = {} if sys.version_info < (3, 8) else {'type_comments': True} + node = ast.parse(arg, **kw) # may raise SyntaxError + # Get missing imports from AST. + return _find_missing_imports_in_ast(node, namespaces) + elif isinstance(arg, PythonBlock): + return _find_missing_imports_in_ast(arg.ast_node, namespaces) + elif isinstance(arg, ast.AST): + return _find_missing_imports_in_ast(arg, namespaces) + elif isinstance(arg, types.CodeType): + return _find_missing_imports_in_code(arg, namespaces) + elif callable(arg): + # Find the code object. + try: + co = arg.__code__ + except AttributeError: + # User-defined callable + try: + co = arg.__call__.__code__ + except AttributeError: + # Built-in function; no auto importing needed. + return [] + # Get missing imports from code object. + return _find_missing_imports_in_code(co, namespaces) + else: + raise TypeError( + "find_missing_imports(): expected a string, AST node, or code object; got a %s" + % (type(arg).__name__,)) + + +def get_known_import(fullname, db=None): + """ + Get the deepest known import. + + For example, suppose: + + - The user accessed "foo.bar.baz", + - We know imports for "foo", "foo.bar", and "foo.bar.quux". + + Then we return "import foo.bar". + + :type fullname: + `DottedIdentifier` + :param fullname: + Fully-qualified name, such as "scipy.interpolate" + """ + # Get the import database. + db = ImportDB.interpret_arg(db, target_filename=".") + fullname = DottedIdentifier(fullname) + # Look for the "deepest" import we know about. Suppose the user + # accessed "foo.bar.baz". If we have an auto-import for "foo.bar", + # then import that. (Presumably, the auto-import for "foo", if it + # exists, refers to the same foo.) + for partial_name in fullname.prefixes[::-1]: + try: + result = db.by_fullname_or_import_as[str(partial_name)] + logger.debug("get_known_import(%r): found %r", fullname, result) + return result + except KeyError: + logger.debug("get_known_import(%r): no known import for %r", fullname, partial_name) + pass + logger.debug("get_known_import(%r): found nothing", fullname) + return None + + +_IMPORT_FAILED = set() +""" +Set of imports we've already attempted and failed. +""" + + +def clear_failed_imports_cache(): + """ + Clear the cache of previously failed imports. + """ + if _IMPORT_FAILED: + logger.debug("Clearing all %d entries from cache of failed imports", + len(_IMPORT_FAILED)) + _IMPORT_FAILED.clear() + + +def _try_import(imp, namespace): + """ + Try to execute an import. Import the result into the namespace + ``namespace``. + + Print to stdout what we're about to do. + + Only import into ``namespace`` if we won't clobber an existing definition. + + :type imp: + ``Import`` or ``str`` + :param imp: + The import to execute, e.g. "from numpy import arange" + :type namespace: + ``dict`` + :param namespace: + Namespace to import into. + :return: + ``True`` on success, ``False`` on failure + """ + # TODO: generalize "imp" to any python statement whose toplevel is a + # single Store (most importantly import and assignment, but could also + # include def & cdef). For things other than imports, we would want to + # first run handle_auto_imports() on the code. + imp = Import(imp) + if imp in _IMPORT_FAILED: + logger.debug("Not attempting previously failed %r", imp) + return False + impas = imp.import_as + name0 = impas.split(".", 1)[0] + stmt = str(imp) + logger.info(stmt) + # Do the import in a temporary namespace, then copy it to ``namespace`` + # manually. We do this instead of just importing directly into + # ``namespace`` for the following reason: Suppose the user wants "foo.bar", + # but "foo" already exists in the global namespace. In order to import + # "foo.bar" we need to import its parent module "foo". We only want to do + # the "foo.bar" import if what we import as "foo" is the same as the + # preexisting "foo". OTOH, we _don't_ want to do the "foo.bar" import if + # the user had for some reason done "import fool as foo". So we (1) + # import into a scratch namespace, (2) check that the top-level matches, + # then (3) copy into the user's namespace if it didn't already exist. + scratch_namespace = {} + try: + exec_(stmt, scratch_namespace) + imported = scratch_namespace[name0] + except Exception as e: + logger.warning("Error attempting to %r: %s: %s", stmt, type(e).__name__, e, + exc_info=True) + _IMPORT_FAILED.add(imp) + return False + try: + preexisting = namespace[name0] + except KeyError: + # The top-level symbol didn't previously exist in the user's global + # namespace. Add it. + namespace[name0] = imported + else: + # The top-level symbol already existed in the user's global namespace. + # Check that it matched. + if preexisting is not imported: + logger.info(" => Failed: pre-existing %r (%r) differs from imported %r", + name0, preexisting, name0) + return False + return True + + +def auto_import_symbol(fullname, namespaces, db=None, autoimported=None, post_import_hook=None): + """ + Try to auto-import a single name. + + :type fullname: + ``str`` + :param fullname: + Fully-qualified module name, e.g. "sqlalchemy.orm". + :type namespaces: + ``list`` of ``dict``, e.g. [globals()]. + :param namespaces: + Namespaces to check. Namespace[-1] is the namespace to import into. + :type db: + `ImportDB` + :param db: + Import database to use. + :param autoimported: + If not ``None``, then a dictionary of identifiers already attempted. + ``auto_import`` will not attempt to auto-import symbols already in this + dictionary, and will add attempted symbols to this dictionary, with + value ``True`` if the autoimport succeeded, or ``False`` if the autoimport + did not succeed. + :rtype: + ``bool`` + :param post_import_hook: + A callable that is invoked if an import was successfully made. + It is invoked with the `Import` object representing the successful import + :type post_import_hook: + ``callable`` + :return: + ``True`` if the symbol was already in the namespace, or the auto-import + succeeded; ``False`` if the auto-import failed. + """ + namespaces = ScopeStack(namespaces) + if not symbol_needs_import(fullname, namespaces): + return True + if autoimported is None: + autoimported = {} + if DottedIdentifier(fullname) in autoimported: + logger.debug("auto_import_symbol(%r): already attempted", fullname) + return False + # See whether there's a known import for this name. This is mainly + # important for things like "from numpy import arange". Imports such as + # "import sqlalchemy.orm" will also be handled by this, although it's less + # important, since we're going to attempt that import anyway if it looks + # like a "sqlalchemy" package is importable. + imports = get_known_import(fullname, db=db) + # successful_import will store last successfully executed import statement + # to be passed to post_import_hook + successful_import = None + logger.debug("auto_import_symbol(%r): get_known_import() => %r", + fullname, imports) + if imports is None: + # No known imports. + pass + else: + assert len(imports) >= 1 + if len(imports) > 1: + # Doh, multiple imports. + logger.info("Multiple candidate imports for %s. Please pick one:", fullname) + for imp in imports: + logger.info(" %s", imp) + autoimported[DottedIdentifier(fullname)] = False + return False + imp, = imports + if symbol_needs_import(imp.import_as, namespaces=namespaces): + # We're ready for some real action. The input code references a + # name/attribute that (a) is not locally assigned, (b) is not a + # global, (c) is not yet imported, (d) is a known auto-import, (e) + # has only one definition + # TODO: label which known_imports file the autoimport came from + if not _try_import(imp, namespaces[-1]): + # Failed; don't do anything else. + autoimported[DottedIdentifier(fullname)] = False + return False + # Succeeded. + successful_import = imp + autoimported[DottedIdentifier(imp.import_as)] = True + if imp.import_as == fullname: + if post_import_hook: + post_import_hook(imp) + # We got what we wanted, so nothing more to do. + return True + if imp.import_as != imp.fullname: + if post_import_hook: + post_import_hook(imp) + # This is not just an 'import foo.bar'; rather, it's a 'import + # foo.bar as baz' or 'from foo import bar'. So don't go any + # further. + return True + # Fall through. + # We haven't yet imported what we want. Either there was no entry in the + # known imports database, or it wasn't "complete" (e.g. the user wanted + # "foo.bar.baz", and the known imports database only knew about "import + # foo.bar"). For each component that may need importing, check if the + # loader thinks it should be importable, and if so import it. + for pmodule in ModuleHandle(fullname).ancestors: + if not symbol_needs_import(pmodule.name, namespaces): + continue + pmodule_name = DottedIdentifier(pmodule.name) + if pmodule_name in autoimported: + if not autoimported[pmodule_name]: + logger.debug("auto_import_symbol(%r): stopping because " + "already previously failed to autoimport %s", + fullname, pmodule_name) + return False + if not pmodule.exists: + logger.debug("auto_import_symbol(%r): %r doesn't exist according to pkgutil", + fullname, pmodule) + autoimported[pmodule_name] = False + return False + imp_stmt = "import %s" % pmodule_name + result = _try_import(imp_stmt, namespaces[-1]) + autoimported[pmodule_name] = result + if not result: + return False + else: + successful_import = Import(imp_stmt) + if post_import_hook and successful_import: + post_import_hook(successful_import) + return True + + +def auto_import(arg, namespaces, db=None, autoimported=None, post_import_hook=None): + """ + Parse ``arg`` for symbols that need to be imported and automatically import + them. + + :type arg: + ``str``, ``ast.AST``, `PythonBlock`, ``callable``, or ``types.CodeType`` + :param arg: + Python code, either as source text, a parsed AST, or compiled code; can + be as simple as a single qualified name, or as complex as an entire + module text. + :type namespaces: + ``dict`` or ``list`` of ``dict`` + :param namespaces: + Namespaces to check. Namespace[-1] is the namespace to import into. + :type db: + `ImportDB` + :param db: + Import database to use. + :type autoimported: + ``dict`` + :param autoimported: + If not ``None``, then a dictionary of identifiers already attempted. + ``auto_import`` will not attempt to auto-import symbols already in this + dictionary, and will add attempted symbols to this dictionary, with + value ``True`` if the autoimport succeeded, or ``False`` if the autoimport + did not succeed. + :rtype: + ``bool`` + :param post_import_hook: + A callable invoked on each successful import. This is passed to + `auto_import_symbol` + :type post_import_hook: + ``callable`` + :return: + ``True`` if all symbols are already in the namespace or successfully + auto-imported; ``False`` if any auto-imports failed. + """ + namespaces = ScopeStack(namespaces) + if isinstance(arg, PythonBlock): + filename = arg.filename + else: + filename = "." + try: + fullnames = find_missing_imports(arg, namespaces) + except SyntaxError: + logger.debug("syntax error parsing %r", arg) + return False + logger.debug("Missing imports: %r", fullnames) + if not fullnames: + return True + if autoimported is None: + autoimported = {} + db = ImportDB.interpret_arg(db, target_filename=filename) + ok = True + for fullname in fullnames: + ok &= auto_import_symbol(fullname, namespaces, db, autoimported, post_import_hook=post_import_hook) + return ok + + +def auto_eval(arg, filename=None, mode=None, + flags=None, auto_flags=True, globals=None, locals=None, + db=None): + """ + Evaluate/execute the given code, automatically importing as needed. + + ``auto_eval`` will default the compilation ``mode`` to "eval" if possible:: + + >>> auto_eval("b64decode('aGVsbG8=')") + b"!" + [PYFLYBY] from base64 import b64decode + b'hello!' + + ``auto_eval`` will default the compilation ``mode`` to "exec" if the input + is not a single expression:: + + >>> auto_eval("if True: print(b64decode('aGVsbG8=').decode('utf-8'))") + [PYFLYBY] from base64 import b64decode + hello + + This is roughly equivalent to "auto_import(arg); eval(arg)", but handles + details better and more efficiently. + + :type arg: + ``str``, ``ast.AST``, ``code``, `Filename`, `FileText`, `PythonBlock` + :param arg: + Code to evaluate. + :type filename: + ``str`` + :param filename: + Filename for compilation error messages. If ``None``, defaults to + ``arg.filename`` if relevant, else ``""``. + :type mode: + ``str`` + :param mode: + Compilation mode: ``None``, "exec", "single", or "eval". "exec", + "single", and "eval" work as the built-in ``compile`` function do. + If ``None``, then default to "eval" if the input is a string with a + single expression, else "exec". + :type flags: + ``CompilerFlags`` or convertible (``int``, ``list`` of ``str``, etc.) + :param flags: + Compilation feature flags, e.g. ["division", "with_statement"]. If + ``None``, defaults to no flags. Does not inherit flags from parent + scope. + :type auto_flags: + ``bool`` + :param auto_flags: + Whether to try other flags if ``flags`` causes SyntaxError. + :type globals: + ``dict`` + :param globals: + Globals for evaluation. If ``None``, use an empty dictionary. + :type locals: + ``dict`` + :param locals: + Locals for evaluation. If ``None``, use ``globals``. + :type db: + `ImportDB` + :param db: + Import database to use. + :return: + Result of evaluation (for mode="eval") + """ + if isinstance(flags, int): + assert isinstance(flags, CompilerFlags) + if isinstance(arg, (six.string_types, Filename, FileText, PythonBlock)): + block = PythonBlock(arg, filename=filename, flags=flags, + auto_flags=auto_flags) + flags = block.flags + filename = block.filename + arg = block.parse(mode=mode) + elif isinstance(arg, (ast.AST, types.CodeType)): + pass + else: + raise TypeError( + "auto_eval(): expected some form of code; got a %s" + % (type(arg).__name__,)) + # Canonicalize other args. + if filename: + filename = Filename(filename) + else: + filename = None + if globals is None: + globals = {} + if locals is None: + locals = globals + db = ImportDB.interpret_arg(db, target_filename=filename) + namespaces = [globals, locals] + # Import as needed. + auto_import(arg, namespaces, db) + # Compile from AST to code object. + if isinstance(arg, types.CodeType): + code = arg + else: + # Infer mode from ast object. + mode = infer_compile_mode(arg) + # Compile ast node => code object. This step is necessary because + # eval() doesn't work on AST objects. We don't need to pass ``flags`` + # to compile() because flags are irrelevant when we already have an + # AST node. + code = compile(arg, str(filename or ""), mode) + # Evaluate/execute. + return eval(code, globals, locals) + + +class LoadSymbolError(Exception): + + def __str__(self): + r = ": ".join(map(str, self.args)) + e = getattr(self, "__cause__", None) + if e: + r += ": %s: %s" % (type(e).__name__, e) + return r + + +def load_symbol(fullname, namespaces, autoimport=False, db=None, + autoimported=None): + """ + Load the symbol ``fullname``. + + >>> import os + >>> load_symbol("os.path.join.__name__", {"os": os}) + 'join' + + >>> load_symbol("os.path.join.asdf", {"os": os}) + Traceback (most recent call last): + ... + pyflyby._autoimp.LoadSymbolError: os.path.join.asdf: AttributeError: 'function' object has no attribute 'asdf' + + >>> load_symbol("os.path.join", {}) + Traceback (most recent call last): + ... + pyflyby._autoimp.LoadSymbolError: os.path.join: NameError: os + + :type fullname: + ``str`` + :param fullname: + Fully-qualified symbol name, e.g. "os.path.join". + :type namespaces: + ``dict`` or ``list`` of ``dict`` + :param namespaces: + Namespaces to check. + :param autoimport: + If ``False`` (default), the symbol must already be imported. + If ``True``, then auto-import the symbol first. + :type db: + `ImportDB` + :param db: + Import database to use when ``autoimport=True``. + :param autoimported: + If not ``None``, then a dictionary of identifiers already attempted. + ``auto_import`` will not attempt to auto-import symbols already in this + dictionary, and will add attempted symbols to this dictionary, with + value ``True`` if the autoimport succeeded, or ``False`` if the autoimport + did not succeed. + :return: + Object. + :raise LoadSymbolError: + Object was not found or there was another exception. + """ + namespaces = ScopeStack(namespaces) + if autoimport: + # Auto-import the symbol first. + # We do the lookup as a separate step after auto-import. (An + # alternative design could be to have auto_import_symbol() return the + # symbol if possible. We don't do that because most users of + # auto_import_symbol() don't need to follow down arbitrary (possibly + # non-module) attributes.) + auto_import_symbol(fullname, namespaces, db, autoimported=autoimported) + name_parts = fullname.split(".") + name0 = name_parts[0] + for namespace in namespaces: + try: + obj = namespace[name0] + except KeyError: + pass + else: + for n in name_parts[1:]: + try: + # Do the getattr. This may raise AttributeError or + # other exception. + obj = getattr(obj, n) + except Exception as e: + e2 = LoadSymbolError(fullname) + e2.__cause__ = e + reraise(LoadSymbolError, e2, sys.exc_info()[2]) + return obj + else: + # Not found in any namespace. + e2 = LoadSymbolError(fullname) + e2.__cause__ = NameError(name0) + raise e2 diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_cmdline.py b/.venv/lib/python3.8/site-packages/pyflyby/_cmdline.py new file mode 100644 index 0000000..be46a65 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_cmdline.py @@ -0,0 +1,515 @@ +# pyflyby/_cmdline.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import optparse +import os +import signal +import six +from six import reraise +from six.moves import input +import sys +from textwrap import dedent +import traceback + + +from pyflyby._file import (FileText, Filename, atomic_write_file, + expand_py_files_from_args, read_file) +from pyflyby._importstmt import ImportFormatParams +from pyflyby._log import logger +from pyflyby._util import cached_attribute, indent + + +def hfmt(s): + return dedent(s).strip() + +def maindoc(): + import __main__ + return (__main__.__doc__ or '').strip() + + +def _sigpipe_handler(*args): + # The parent process piped our stdout and closed the pipe before we + # finished writing, e.g. "tidy-imports ... | head" or "tidy-imports ... | + # less". Exit quietly - squelch the "close failed in file object + # destructor" message would otherwise be raised. + raise SystemExit(1) + + +def parse_args(addopts=None, import_format_params=False, modify_action_params=False): + """ + Do setup for a top-level script and parse arguments. + """ + ### Setup. + # Register a SIGPIPE handler. + signal.signal(signal.SIGPIPE, _sigpipe_handler) + ### Parse args. + parser = optparse.OptionParser(usage='\n'+maindoc()) + + def log_level_callbacker(level): + def callback(option, opt_str, value, parser): + logger.set_level(level) + return callback + + def debug_callback(option, opt_str, value, parser): + logger.set_level("DEBUG") + + parser.add_option("--debug", action="callback", + callback=debug_callback, + help="Debug mode (noisy and fail fast).") + + parser.add_option("--verbose", action="callback", + callback=log_level_callbacker("DEBUG"), + help="Be noisy.") + + parser.add_option("--quiet", action="callback", + callback=log_level_callbacker("ERROR"), + help="Be quiet.") + + parser.add_option("--version", action="callback", + callback=lambda *args: print_version_and_exit(), + help="Print pyflyby version and exit.") + + if modify_action_params: + group = optparse.OptionGroup(parser, "Action options") + action_diff = action_external_command('pyflyby-diff') + def parse_action(v): + V = v.strip().upper() + if V == 'PRINT': + return action_print + elif V == 'REPLACE': + return action_replace + elif V == 'QUERY': + return action_query() + elif V == "DIFF": + return action_diff + elif V.startswith("QUERY:"): + return action_query(v[6:]) + elif V.startswith("EXECUTE:"): + return action_external_command(v[8:]) + elif V == "IFCHANGED": + return action_ifchanged + else: + raise Exception( + "Bad argument %r to --action; " + "expected PRINT or REPLACE or QUERY or IFCHANGED " + "or EXECUTE:..." % (v,)) + + def set_actions(actions): + actions = tuple(actions) + parser.values.actions = actions + + def action_callback(option, opt_str, value, parser): + action_args = value.split(',') + set_actions([parse_action(v) for v in action_args]) + + def action_callbacker(actions): + def callback(option, opt_str, value, parser): + set_actions(actions) + return callback + + group.add_option( + "--actions", type='string', action='callback', + callback=action_callback, + metavar='PRINT|REPLACE|IFCHANGED|QUERY|DIFF|EXECUTE:mycommand', + help=hfmt(''' + Comma-separated list of action(s) to take. If PRINT, print + the changed file to stdout. If REPLACE, then modify the + file in-place. If EXECUTE:mycommand, then execute + 'mycommand oldfile tmpfile'. If DIFF, then execute + 'pyflyby-diff'. If QUERY, then query user to continue. + If IFCHANGED, then continue actions only if file was + changed.''')) + group.add_option( + "--print", "-p", action='callback', + callback=action_callbacker([action_print]), + help=hfmt(''' + Equivalent to --action=PRINT (default when stdin or stdout is + not a tty) ''')) + group.add_option( + "--diff", "-d", action='callback', + callback=action_callbacker([action_diff]), + help=hfmt('''Equivalent to --action=DIFF''')) + group.add_option( + "--replace", "-r", action='callback', + callback=action_callbacker([action_ifchanged, action_replace]), + help=hfmt('''Equivalent to --action=IFCHANGED,REPLACE''')) + group.add_option( + "--diff-replace", "-R", action='callback', + callback=action_callbacker([action_ifchanged, action_diff, action_replace]), + help=hfmt('''Equivalent to --action=IFCHANGED,DIFF,REPLACE''')) + actions_interactive = [ + action_ifchanged, action_diff, + action_query("Replace {filename}?"), action_replace] + group.add_option( + "--interactive", "-i", action='callback', + callback=action_callbacker(actions_interactive), + help=hfmt(''' + Equivalent to --action=IFCHANGED,DIFF,QUERY,REPLACE (default + when stdin & stdout are ttys) ''')) + if os.isatty(0) and os.isatty(1): + default_actions = actions_interactive + else: + default_actions = [action_print] + parser.set_default('actions', tuple(default_actions)) + parser.add_option_group(group) + + parser.add_option( + '--symlinks', action='callback', nargs=1, type=str, + dest='symlinks', callback=symlink_callback, help="--symlinks should be one of: " + symlinks_help, + ) + parser.set_defaults(symlinks='error') + + if import_format_params: + group = optparse.OptionGroup(parser, "Pretty-printing options") + group.add_option('--align-imports', '--align', type='str', default="32", + metavar='N', + help=hfmt(''' + Whether and how to align the 'import' keyword in + 'from modulename import aliases...'. If 0, then + don't align. If 1, then align within each block + of imports. If an integer > 1, then align at + that column, wrapping with a backslash if + necessary. If a comma-separated list of integers + (tab stops), then pick the column that results in + the fewest number of lines total per block.''')) + group.add_option('--from-spaces', type='int', default=3, metavar='N', + help=hfmt(''' + The number of spaces after the 'from' keyword. + (Must be at least 1; default is 3.)''')) + group.add_option('--separate-from-imports', action='store_true', + default=False, + help=hfmt(''' + Separate 'from ... import ...' + statements from 'import ...' statements.''')) + group.add_option('--no-separate-from-imports', action='store_false', + dest='separate_from_imports', + help=hfmt(''' + (Default) Don't separate 'from ... import ...' + statements from 'import ...' statements.''')) + group.add_option('--align-future', action='store_true', + default=False, + help=hfmt(''' + Align the 'from __future__ import ...' statement + like others.''')) + group.add_option('--no-align-future', action='store_false', + dest='align_future', + help=hfmt(''' + (Default) Don't align the 'from __future__ import + ...' statement.''')) + group.add_option('--width', type='int', default=79, metavar='N', + help=hfmt(''' + Maximum line length (default: 79).''')) + group.add_option('--black', action='store_true', default=False, + help=hfmt(''' + Use black to format imports. If this option is + used, all other formatting options are ignored.''')) + group.add_option('--hanging-indent', type='choice', default='never', + choices=['never','auto','always'], + metavar='never|auto|always', + dest='hanging_indent', + help=hfmt(''' + How to wrap import statements that don't fit on + one line. + If --hanging-indent=always, then always indent + imported tokens at column 4 on the next line. + If --hanging-indent=never (default), then align + import tokens after "import (" (by default column + 40); do so even if some symbols are so long that + this would exceed the width (by default 79)). + If --hanging-indent=auto, then use hanging indent + only if it is necessary to prevent exceeding the + width (by default 79). + ''')) + def uniform_callback(option, opt_str, value, parser): + parser.values.separate_from_imports = False + parser.values.from_spaces = 3 + parser.values.align_imports = '32' + group.add_option('--uniform', '-u', action="callback", + callback=uniform_callback, + help=hfmt(''' + (Default) Shortcut for --no-separate-from-imports + --from-spaces=3 --align-imports=32.''')) + def unaligned_callback(option, opt_str, value, parser): + parser.values.separate_from_imports = True + parser.values.from_spaces = 1 + parser.values.align_imports = '0' + group.add_option('--unaligned', '-n', action="callback", + callback=unaligned_callback, + help=hfmt(''' + Shortcut for --separate-from-imports + --from-spaces=1 --align-imports=0.''')) + + parser.add_option_group(group) + if addopts is not None: + addopts(parser) + # This is the only way to provide a default value for an option with a + # callback. + if modify_action_params: + args = ["--symlinks=error"] + sys.argv[1:] + else: + args = None + options, args = parser.parse_args(args=args) + if import_format_params: + align_imports_args = [int(x.strip()) + for x in options.align_imports.split(",")] + if len(align_imports_args) == 1 and align_imports_args[0] == 1: + align_imports = True + elif len(align_imports_args) == 1 and align_imports_args[0] == 0: + align_imports = False + else: + align_imports = tuple(sorted(set(align_imports_args))) + options.params = ImportFormatParams( + align_imports =align_imports, + from_spaces =options.from_spaces, + separate_from_imports =options.separate_from_imports, + max_line_length =options.width, + use_black =options.black, + align_future =options.align_future, + hanging_indent =options.hanging_indent, + ) + return options, args + + +def _default_on_error(filename): + raise SystemExit("bad filename %s" % (filename,)) + +def filename_args(args, on_error=_default_on_error): + """ + Return list of filenames given command-line arguments. + + :rtype: + ``list`` of `Filename` + """ + if args: + return expand_py_files_from_args(args, on_error) + elif not os.isatty(0): + return [Filename.STDIN] + else: + syntax() + + +def print_version_and_exit(extra=None): + from pyflyby._version import __version__ + msg = "pyflyby %s" % (__version__,) + progname = os.path.realpath(sys.argv[0]) + if os.path.exists(progname): + msg += " (%s)" % (os.path.basename(progname),) + print(msg) + if extra: + print(extra) + raise SystemExit(0) + + +def syntax(message=None, usage=None): + if message: + logger.error(message) + outmsg = ((usage or maindoc()) + + '\n\nFor usage, see: %s --help' % (sys.argv[0],)) + print(outmsg, file=sys.stderr) + raise SystemExit(1) + + +class AbortActions(Exception): + pass + + +class Modifier(object): + def __init__(self, modifier, filename): + self.modifier = modifier + self.filename = filename + self._tmpfiles = [] + + @cached_attribute + def input_content(self): + return read_file(self.filename) + + # TODO: refactor to avoid having these heavy-weight things inside a + # cached_attribute, which causes annoyance while debugging. + @cached_attribute + def output_content(self): + return FileText(self.modifier(self.input_content), filename=self.filename) + + def _tempfile(self): + from tempfile import NamedTemporaryFile + f = NamedTemporaryFile() + self._tmpfiles.append(f) + return f, Filename(f.name) + + + @cached_attribute + def output_content_filename(self): + f, fname = self._tempfile() + if six.PY3: + f.write(bytes(self.output_content.joined, "utf-8")) + else: + f.write(self.output_content.joined.encode('utf-8')) + f.flush() + return fname + + @cached_attribute + def input_content_filename(self): + if isinstance(self.filename, Filename): + return self.filename + # If the input was stdin, and the user wants a diff, then we need to + # write it to a temp file. + f, fname = self._tempfile() + if six.PY3: + f.write(bytes(self.input_content, "utf-8")) + else: + f.write(self.input_content) + f.flush() + return fname + + + def __del__(self): + for f in self._tmpfiles: + f.close() + + +def process_actions(filenames, actions, modify_function, + reraise_exceptions=()): + errors = [] + def on_error_filename_arg(arg): + print("%s: bad filename %s" % (sys.argv[0], arg), file=sys.stderr) + errors.append("%s: bad filename" % (arg,)) + filenames = filename_args(filenames, on_error=on_error_filename_arg) + for filename in filenames: + try: + m = Modifier(modify_function, filename) + for action in actions: + action(m) + except AbortActions: + continue + except reraise_exceptions: + raise + except Exception as e: + errors.append("%s: %s: %s" % (filename, type(e).__name__, e)) + type_e = type(e) + try: + tb = sys.exc_info()[2] + if str(filename) not in str(e): + try: + e = type_e("While processing %s: %s" % (filename, e)) + pass + except TypeError: + # Exception takes more than one argument + pass + if logger.debug_enabled: + reraise(type_e, e, tb) + traceback.print_exception(type(e), e, tb) + finally: + tb = None # avoid refcycles involving tb + continue + if errors: + msg = "\n%s: encountered the following problems:\n" % (sys.argv[0],) + for e in errors: + lines = e.splitlines() + msg += " " + lines[0] + '\n'.join( + (" %s"%line for line in lines[1:])) + raise SystemExit(msg) + + +def action_print(m): + output_content = m.output_content + sys.stdout.write(output_content.joined) + + +def action_ifchanged(m): + if m.output_content.joined == m.input_content.joined: + logger.debug("unmodified: %s", m.filename) + raise AbortActions + + +def action_replace(m): + if m.filename == Filename.STDIN: + raise Exception("Can't replace stdio in-place") + logger.info("%s: *** modified ***", m.filename) + atomic_write_file(m.filename, m.output_content) + + +def action_external_command(command): + import subprocess + def action(m): + bindir = os.path.dirname(os.path.realpath(sys.argv[0])) + env = os.environ + env['PATH'] = env['PATH'] + ":" + bindir + fullcmd = "%s %s %s" % ( + command, m.input_content_filename, m.output_content_filename) + logger.debug("Executing external command: %s", fullcmd) + ret = subprocess.call(fullcmd, shell=True, env=env) + logger.debug("External command returned %d", ret) + return action + + +def action_query(prompt="Proceed?"): + def action(m): + p = prompt.format(filename=m.filename) + print() + print("%s [y/N] " % (p), end="") + try: + if input().strip().lower().startswith('y'): + return True + except KeyboardInterrupt: + print("KeyboardInterrupt", file=sys.stderr) + raise SystemExit(1) + print("Aborted") + raise AbortActions + return action + +def symlink_callback(option, opt_str, value, parser): + parser.values.actions = tuple(i for i in parser.values.actions if i not in + symlink_callbacks.values()) + if value in symlink_callbacks: + parser.values.actions = (symlink_callbacks[value],) + parser.values.actions + else: + raise optparse.OptionValueError("--symlinks must be one of 'error', 'follow', 'skip', or 'replace'. Got %r" % value) + +symlinks_help = """\ +--symlinks=error (default; gives an error on symlinks), +--symlinks=follow (follows symlinks), +--symlinks=skip (skips symlinks), +--symlinks=replace (replaces symlinks with the target file\ +""" + +# Warning, the symlink actions will only work if they are run first. +# Otherwise, output_content may already be cached +def symlink_error(m): + if m.filename == Filename.STDIN: + return symlink_follow(m) + if m.filename.islink: + raise SystemExit("""\ +Error: %s appears to be a symlink. Use one of the following options to allow symlinks: +%s +""" % (m.filename, indent(symlinks_help, ' '))) + +def symlink_follow(m): + if m.filename == Filename.STDIN: + return + if m.filename.islink: + logger.info("Following symlink %s" % m.filename) + m.filename = m.filename.realpath + +def symlink_skip(m): + if m.filename == Filename.STDIN: + return symlink_follow(m) + if m.filename.islink: + logger.info("Skipping symlink %s" % m.filename) + raise AbortActions + +def symlink_replace(m): + if m.filename == Filename.STDIN: + return symlink_follow(m) + if m.filename.islink: + logger.info("Replacing symlink %s" % m.filename) + # The current behavior automatically replaces symlinks, so do nothing + +symlink_callbacks = { + 'error': symlink_error, + 'follow': symlink_follow, + 'skip': symlink_skip, + 'replace': symlink_replace, +} diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_comms.py b/.venv/lib/python3.8/site-packages/pyflyby/_comms.py new file mode 100644 index 0000000..9af1c6a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_comms.py @@ -0,0 +1,136 @@ +from __future__ import absolute_import, division, print_function + +from pyflyby._log import logger +from pyflyby._imports2s import SourceToSourceFileImportsTransformation +from pyflyby._importstmt import Import +import six + +# These are comm targets that the frontend (lab/notebook) is expected to +# open. At this point, we handle only missing imports and +# formatting imports + +MISSING_IMPORTS = "pyflyby.missing_imports" +FORMATTING_IMPORTS = "pyflyby.format_imports" +INIT_COMMS = "pyflyby.init_comms" +PYFLYBY_START_MSG = "# THIS CELL WAS AUTO-GENERATED BY PYFLYBY\n" +PYFLYBY_END_MSG = "# END AUTO-GENERATED BLOCK\n" + +pyflyby_comm_targets= [MISSING_IMPORTS, FORMATTING_IMPORTS] + +# A map of the comms opened with a given target name. +comms = {} + +# TODO: Document the expected contract for the different +# custom comm messages + + +def in_jupyter(): + from IPython.core.getipython import get_ipython + ip = get_ipython() + if ip is None: + logger.debug("get_ipython() doesn't exist. Comm targets can only" + "be added in an Jupyter notebook/lab/console environment") + return False + else: + try: + ip.kernel.comm_manager + except AttributeError: + logger.debug("Comm targets can only be added in Jupyter " + "notebook/lab/console environment") + return False + else: + return True + + +def _register_target(target_name): + from IPython.core.getipython import get_ipython + ip = get_ipython() + comm_manager = ip.kernel.comm_manager + comm_manager.register_target(target_name, comm_open_handler) + + +def initialize_comms(): + if in_jupyter(): + for target in pyflyby_comm_targets: + _register_target(target) + from ipykernel.comm import Comm + comm = Comm(target_name=INIT_COMMS) + msg = {"type": INIT_COMMS} + logger.debug("Requesting frontend to (re-)initialize comms") + comm.send(msg) + + +def remove_comms(): + for target_name, comm in six.iteritems(comms): + comm.close() + logger.debug("Closing comm for " + target_name) + +def send_comm_message(target_name, msg): + if in_jupyter(): + try: + comm = comms[target_name] + except KeyError: + logger.debug("Comm with target_name " + target_name + " hasn't been opened") + else: + # Help the frontend distinguish between multiple types + # of custom comm messages + msg["type"] = target_name + comm.send(msg) + logger.debug("Sending comm message for target " + target_name) + + +def comm_close_handler(comm, message): + comm_id = message["comm_id"] + for target, comm in six.iterkeys(comms): + if comm.comm_id == comm_id: + comms.pop(target) + + +def _reformat_helper(input_code, imports): + from pyflyby._imports2s import reformat_import_statements + + if PYFLYBY_START_MSG in input_code: + before, bmarker, middle = input_code.partition(PYFLYBY_START_MSG) + else: + before, bmarker, middle = "", "", input_code + + if PYFLYBY_END_MSG in middle: + middle, emarker, after = middle.partition(PYFLYBY_END_MSG) + else: + middle, emarker, after = middle, "", "" + + if imports is not None: + transform = SourceToSourceFileImportsTransformation(middle) + + if isinstance(imports, str): + imports = [imports] + + for imp in imports: + assert isinstance(imp, str) + if not imp.strip(): + continue + transform.add_import(Import(imp)) + middle = str(transform.output()) + + return reformat_import_statements(before + bmarker + middle + emarker + after) + +def comm_open_handler(comm, message): + """ + Handles comm_open message for pyflyby custom comm messages. + https://jupyter-client.readthedocs.io/en/stable/messaging.html#opening-a-comm. + + Handler for all PYFLYBY custom comm messages that are opened by the frontend + (at this point, just the jupyterlab frontend does this). + + """ + + comm.on_close(comm_close_handler) + comms[message["content"]["target_name"]] = comm + + @comm.on_msg + def _recv(msg): + data = msg["content"]["data"] + if data["type"] == FORMATTING_IMPORTS: + imports = data.get('imports', None) + fmt_code = _reformat_helper(data["input_code"], imports) + comm.send({"formatted_code": str(fmt_code), "type": FORMATTING_IMPORTS}) diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_dbg.py b/.venv/lib/python3.8/site-packages/pyflyby/_dbg.py new file mode 100644 index 0000000..2e6d99f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_dbg.py @@ -0,0 +1,1307 @@ +# pyflyby/_dbg.py. +# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from contextlib import contextmanager +import errno +from functools import wraps +import os +import pwd +import signal +import sys +import time +from types import CodeType, FrameType, TracebackType + +import six +from six.moves import builtins + +if six.PY3: + from collections.abc import Callable +else: + from collections import Callable + +from pyflyby._file import Filename + + +""" +Used by wait_for_debugger_to_attach to record whether we're waiting to attach, +and if so what. +""" +_waiting_for_debugger = None + + +_ORIG_SYS_EXCEPTHOOK = sys.excepthook + +def _reset_excepthook(): + if _ORIG_SYS_EXCEPTHOOK: + sys.excepthook = _ORIG_SYS_EXCEPTHOOK + return True + return False + + +def _override_excepthook(hook): + """ + Override sys.excepthook with `hook` but also support resetting. + + Users should call this function instead of directly overiding + sys.excepthook. This is helpful in resetting sys.excepthook in certain cases. + """ + global _ORIG_SYS_EXCEPTHOOK + _ORIG_SYS_EXCEPTHOOK = hook + sys.excepthook = hook + + +class _NoTtyError(Exception): + pass + + +_memoized_dev_tty_fd = Ellipsis +def _dev_tty_fd(): + """ + Return a file descriptor opened to /dev/tty. + Memoized. + """ + global _memoized_dev_tty_fd + if _memoized_dev_tty_fd is Ellipsis: + try: + _memoized_dev_tty_fd = os.open("/dev/tty", os.O_RDWR) + except OSError: + _memoized_dev_tty_fd = None + if _memoized_dev_tty_fd is None: + raise _NoTtyError + return _memoized_dev_tty_fd + + +def tty_is_usable(): + """ + Return whether /dev/tty is usable. + + In interactive sessions, /dev/tty is usable; in non-interactive sessions, + /dev/tty is not usable:: + + $ ssh -t localhost py -q pyflyby._dbg.tty_is_usable + True + + $ ssh -T localhost py -q pyflyby._dbg.tty_is_usable + False + + tty_is_usable() is useful for deciding whether we are in an interactive + terminal. In an interactive terminal we can enter the debugger directly; + in a non-interactive terminal, we need to wait_for_debugger_to_attach. + + Note that this is different from doing e.g. isatty(0). isatty would + return False if a program was piped, even though /dev/tty is usable. + """ + try: + _dev_tty_fd() + return True + except _NoTtyError: + return False + + +@contextmanager +def _FdCtx(target_fd, src_fd): + assert target_fd != src_fd + saved_fd = os.dup(target_fd) + assert saved_fd > 2, "saved_fd == %d" % (saved_fd,) + assert saved_fd != target_fd and saved_fd != src_fd + os.dup2(src_fd, target_fd) + try: + yield + finally: + os.dup2(saved_fd, target_fd) + + +_in_StdioCtx = [] + +@contextmanager +def _StdioCtx(tty="/dev/tty"): + ''' + Within the context, force fd {0, 1, 2}, sys.__{stdin,stdout,stderr}__, + sys.{stdin,stdout,stderr} to fd. This allows us to use the debugger even + if stdio is otherwise redirected. + + :type tty: + ``int`` or ``str`` + :param tty: + Tty to use. Either a file descriptor or a name of a tty. + ''' + from ._interactive import UpdateIPythonStdioCtx + to_close = None + if isinstance(tty, int): + fd = tty + elif isinstance(tty, str): + if tty == "/dev/tty": + fd = _dev_tty_fd() + else: + fd = os.open(tty, os.O_RDWR) + to_close = fd + else: + raise TypeError("_StdioCtx(): tty should be an int or str") + if _in_StdioCtx and _in_StdioCtx[-1] == fd: + # Same context; do nothing. + assert to_close is None + yield + return + if not fd > 2: + raise ValueError("_StdioCtx: unsafe to use fd<=2; fd==%d" % (fd,)) + _in_StdioCtx.append(fd) + saved_stdin = sys.stdin + saved_stdin__ = sys.__stdin__ + saved_stdout = sys.stdout + saved_stdout__ = sys.__stdout__ + saved_stderr = sys.stderr + saved_stderr__ = sys.__stderr__ + try: + sys.stdout.flush(); sys.__stdout__.flush() + sys.stderr.flush(); sys.__stderr__.flush() + from ._util import nested + with nested(_FdCtx(0, fd), _FdCtx(1, fd), _FdCtx(2, fd)): + with nested(os.fdopen(0, 'r'), + os.fdopen(1, 'w'), + os.fdopen(2, 'w', 1)) as (fd0, fd1, fd2): + sys.stdin = sys.__stdin__ = fd0 + sys.stdout = sys.__stdout__ = fd1 + sys.stderr = sys.__stderr__ = fd2 + # Update IPython's stdin/stdout/stderr temporarily. + with UpdateIPythonStdioCtx(): + yield + finally: + assert _in_StdioCtx and _in_StdioCtx[-1] == fd + _in_StdioCtx.pop(-1) + sys.stdin = saved_stdin + sys.__stdin__ = saved_stdin__ + sys.stdout = saved_stdout + sys.__stdout__ = saved_stdout__ + sys.stderr = saved_stderr + sys.__stderr__ = saved_stderr__ + if to_close is not None: + try: + os.close(to_close) + except (OSError, IOError): + pass + + +@contextmanager +def _ExceptHookCtx(): + ''' + Context manager that restores ``sys.excepthook`` upon exit. + ''' + saved_excepthook = sys.excepthook + try: + # TODO: should we set sys.excepthook = sys.__excepthook__ ? + yield + finally: + sys.excepthook = saved_excepthook + + +@contextmanager +def _DisplayHookCtx(): + ''' + Context manager that resets ``sys.displayhook`` to the default value upon + entry, and restores the pre-context value upon exit. + ''' + saved_displayhook = sys.displayhook + try: + sys.displayhook = sys.__displayhook__ + yield + finally: + sys.displayhook = saved_displayhook + + +def print_traceback(*exc_info): + """ + Print a traceback, using IPython's ultraTB if possible. + + Output goes to /dev/tty. + + :param exc_info: + 3 arguments as returned by sys.exc_info(). + """ + from pyflyby._interactive import print_verbose_tb + if not exc_info: + exc_info = sys.exc_info() + with _StdioCtx(): + print_verbose_tb(*exc_info) + + +@contextmanager +def _DebuggerCtx(tty="/dev/tty"): + """ + A context manager that sets up the environment (stdio, sys hooks) for a + debugger, initializes IPython if necessary, and creates a debugger instance. + + :return: + Context manager that yields a Pdb instance. + """ + from pyflyby._interactive import new_IPdb_instance + with _StdioCtx(tty): + with _ExceptHookCtx(): + with _DisplayHookCtx(): + pdb = new_IPdb_instance() + pdb.reset() + yield pdb + + +def _get_caller_frame(): + ''' + Get the closest frame from outside this module. + + :rtype: + ``FrameType`` + ''' + this_filename = _get_caller_frame.__code__.co_filename + f = sys._getframe() + while (f.f_back and ( + f.f_code.co_filename == this_filename or + (f.f_back.f_back and + f.f_code.co_filename == "" and + f.f_code.co_name == "" and + f.f_back.f_code.co_filename == this_filename))): + f = f.f_back + if f.f_code.co_filename == "" and f.f_code.co_name == "": + # Skip an extra string eval frame for attaching a debugger. + # TODO: pass in a frame or maximum number of string frames to skip. + # We shouldn't skip "" if it comes from regular user code. + f = f.f_back + return f + + +def _debug_exception(*exc_info, **kwargs): + """ + Debug an exception -- print a stack trace and enter the debugger. + + Suitable to be assigned to sys.excepthook. + """ + from pyflyby._interactive import print_verbose_tb + tty = kwargs.pop("tty", "/dev/tty") + if kwargs: + raise TypeError("debug_exception(): unexpected kwargs %s" + % (', '.join(sorted(kwargs.keys())))) + if not exc_info: + exc_info = sys.exc_info() + if len(exc_info) == 1 and type(exc_info[0]) is tuple: + exc_info = exc_info[0] + if len(exc_info) == 1 and type(exc_info[0]) is TracebackType: + # Allow the input to be just the traceback. The exception instance is + # only used for printing the traceback. It's not needed by the + # debugger. + # We don't know the exception in this case. For now put "", "". This + # will cause print_verbose_tb to include a line with just a colon. + # TODO: avoid that line. + exc_info = ("", "", exc_info) + with _DebuggerCtx(tty=tty) as pdb: + print_verbose_tb(*exc_info) + pdb.interaction(None, exc_info[2]) + + +def _debug_code(arg, globals=None, locals=None, auto_import=True, tty="/dev/tty"): + """ + Run code under the debugger. + + :type arg: + ``str``, ``Callable``, ``CodeType``, ``PythonStatement``, ``PythonBlock``, + ``FileText`` + """ + if globals is None or locals is None: + caller_frame = _get_caller_frame() + if globals is None: + globals = caller_frame.f_globals + if locals is None: + locals = caller_frame.f_locals + del caller_frame + with _DebuggerCtx(tty=tty) as pdb: + print("Entering debugger. Use 'n' to step, 'c' to run, 'q' to stop.") + print("") + from ._parse import PythonStatement, PythonBlock, FileText + if isinstance(arg, (six.string_types, PythonStatement, PythonBlock, FileText)): + # Compile the block so that we can get the right compile mode. + arg = PythonBlock(arg) + # TODO: enter text into linecache + autoimp_arg = arg + code = arg.compile() + elif isinstance(arg, CodeType): + autoimp_arg = arg + code = arg + elif isinstance(arg, Callable): + # TODO: check argspec to make sure it's a zero-arg callable. + code = arg.__code__ + autoimp_arg = code + else: + raise TypeError( + "debug_code(): expected a string/callable/lambda; got a %s" + % (type(arg).__name__,)) + if auto_import: + from ._autoimp import auto_import as auto_import_f + auto_import_f(autoimp_arg, [globals, locals]) + return pdb.runeval(code, globals=globals, locals=locals) + + +_CURRENT_FRAME = object() + + +def debugger(*args, **kwargs): + ''' + Entry point for debugging. + + ``debugger()`` can be used in the following ways:: + + 1. Breakpoint mode, entering debugger in executing code:: + >> def foo(): + .. bar() + .. debugger() + .. baz() + + This allow stepping through code after the debugger() call - i.e. between + bar() and baz(). This is similar to 'import pdb; pdb.set_trace()':: + + 2. Debug a python statement:: + + >> def foo(x): + .. ... + >> X = 5 + + >> debugger("foo(X)") + + The auto-importer is run on the given python statement. + + 3. Debug a callable:: + + >> def foo(x=5): + .. ... + + >> debugger(foo) + >> debugger(lambda: foo(6)) + + 4. Debug an exception:: + + >> try: + .. ... + .. except: + .. debugger(sys.exc_info()) + + + If the process is waiting on for a debugger to attach to debug a frame or + exception traceback, then calling debugger(None) will debug that target. + If it is frame, then the user can step through code. If it is an + exception traceback, then the debugger will operate in post-mortem mode + with no stepping allowed. The process will continue running after this + debug session's "continue". + + ``debugger()`` is suitable to be called interactively, from scripts, in + sys.excepthook, and in signal handlers. + + :param args: + What to debug: + - If a string or callable, then run it under the debugger. + - If a frame, then debug the frame. + - If a traceback, then debug the traceback. + - If a 3-tuple as returned by sys.exc_info(), then debug the traceback. + - If the process is waiting to for a debugger to attach, then attach + the debugger there. This is only relevant when an external process + is attaching a debugger. + - If nothing specified, then enter the debugger at the statement + following the call to debug(). + :kwarg tty: + Tty to connect to. If ``None`` (default): if /dev/tty is usable, then + use it; else call wait_for_debugger_to_attach() instead (unless + wait_for_attach==False). + :kwarg on_continue: + Function to call upon exiting the debugger and continuing with regular + execution. + :kwarg wait_for_attach: + Whether to wait for a remote terminal to attach (with 'py -d PID'). + If ``True``, then always wait for a debugger to attach. + If ``False``, then never wait for a debugger to attach; debug in the + current terminal. + If unset, then defaults to true only when ``tty`` is unspecified and + /dev/tty is not usable. + :kwarg background: + If ``False``, then pause execution to debug. + If ``True``, then fork a process and wait for a debugger to attach in the + forked child. + ''' + from ._parse import PythonStatement, PythonBlock, FileText + if len(args) == 1: + arg = args[0] + elif len(args) == 0: + arg = None + else: + arg = args + tty = kwargs.pop("tty" , None) + on_continue = kwargs.pop("on_continue" , lambda: None) + globals = kwargs.pop("globals" , None) + locals = kwargs.pop("locals" , None) + wait_for_attach = kwargs.pop("wait_for_attach", Ellipsis) + background = kwargs.pop("background" , False) + if kwargs: + raise TypeError("debugger(): unexpected kwargs %s" + % (', '.join(sorted(kwargs)))) + if arg is None and tty is not None and wait_for_attach != True: + # If _waiting_for_debugger is not None, then attach to that + # (whether it's a frame, traceback, etc). + global _waiting_for_debugger + arg = _waiting_for_debugger + _waiting_for_debugger = None + if arg is None: + # Debug current frame. + arg = _CURRENT_FRAME + if arg is _CURRENT_FRAME: + arg = _get_caller_frame() + if background: + # Fork a process and wait for a debugger to attach in the background. + # Todo: implement on_continue() + wait_for_debugger_to_attach(arg, background=True) + return + if wait_for_attach == True: + wait_for_debugger_to_attach(arg) + return + if tty is None: + if tty_is_usable(): + tty = "/dev/tty" + elif wait_for_attach != False: + # If the tty isn't usable, then default to waiting for the + # debugger to attach from another (interactive) terminal. + # Todo: implement on_continue() + # TODO: capture globals/locals when relevant. + wait_for_debugger_to_attach(arg) + return + if isinstance(arg, (six.string_types, PythonStatement, PythonBlock, FileText, + CodeType, Callable)): + _debug_code(arg, globals=globals, locals=locals, tty=tty) + on_continue() + return + if (isinstance(arg, TracebackType) or + type(arg) is tuple and len(arg) == 3 and type(arg[2]) is TracebackType): + _debug_exception(arg, tty=tty) + on_continue() + return + if not isinstance(arg, FrameType): + raise TypeError( + "debugger(): expected a frame/traceback/str/code; got %s" + % (arg,)) + frame = arg + if globals is not None or locals is not None: + raise NotImplementedError( + "debugger(): globals/locals only relevant when debugging code") + pdb_context = _DebuggerCtx(tty) + pdb = pdb_context.__enter__() + print("Entering debugger. Use 'n' to step, 'c' to continue running, 'q' to quit Python completely.") + def set_continue(): + # Continue running code outside the debugger. + pdb.stopframe = pdb.botframe + pdb.returnframe = None + sys.settrace(None) + print("Continuing execution.") + pdb_context.__exit__(None, None, None) + on_continue() + def set_quit(): + # Quit the program. Note that if we're inside IPython, then this + # won't actually exit IPython. We do want to call the context + # __exit__ here to make sure we restore sys.displayhook, etc. + # TODO: raise something else here if in IPython + pdb_context.__exit__(None, None, None) + raise SystemExit("Quitting as requested while debugging.") + pdb.set_continue = set_continue + pdb.set_quit = set_quit + pdb.do_EOF = pdb.do_continue + pdb.set_trace(frame) + # Note: set_trace() installs a tracer and returns; that means we can't use + # context managers around set_trace(): the __exit__() would be called + # right away, not after continuing/quitting. + # We also want this to be the very last thing called in the function (and + # not in a nested function). This way the very next thing the user sees + # is his own code. + + + +_cached_py_commandline = None +def _find_py_commandline(): + global _cached_py_commandline + if _cached_py_commandline is not None: + return _cached_py_commandline + import pyflyby + pkg_path = Filename(pyflyby.__path__[0]).real + assert pkg_path.base == "pyflyby" + d = pkg_path.dir + if d.base == "bin": + # Running from source tree + bindir = d + else: + # Installed by setup.py + while d.dir != d: + d = d.dir + bindir = d / "bin" + if bindir.exists: + break + else: + raise ValueError( + "Couldn't find 'py' script: " + "couldn't find 'bin' dir from package path %s" % (pkg_path,)) + candidate = bindir / "py" + if not candidate.exists: + raise ValueError( + "Couldn't find 'py' script: expected it at %s" % (candidate,)) + if not candidate.isexecutable: + raise ValueError( + "Found 'py' script at %s but it's not executable" % (candidate,)) + _cached_py_commandline = candidate + return candidate + + + +class DebuggerAttachTimeoutError(Exception): + pass + + +def _sleep_until_debugger_attaches(arg, timeout=86400): + assert arg is not None + global _waiting_for_debugger + try: + deadline = time.time() + timeout + _waiting_for_debugger = arg + while _waiting_for_debugger is not None: + if time.time() > deadline: + raise DebuggerAttachTimeoutError + time.sleep(0.5) + finally: + _waiting_for_debugger = None + + +def wait_for_debugger_to_attach(arg, mailto=None, background=False, timeout=86400): + """ + Send email to user and wait for debugger to attach. + + :param arg: + What to debug. Should be a sys.exc_info() result or a sys._getframe() + result. + :param mailto: + Recipient to email. Defaults to $USER or current user. + :param background: + If True, fork a child process. The parent process continues immediately + without waiting. The child process waits for a debugger to attach, and + exits when the debugging session completes. + :param timeout: + Maximum number of seconds to wait for user to attach debugger. + """ + import traceback + if background: + originalpid = os.getpid() + if os.fork() != 0: + return + else: + originalpid = None + try: + # Reset the exception hook after the first exception. + # + # In case the code injected by the remote client causes some error in + # the debugged process, another email is sent for the new exception. This can + # lead to an infinite loop of sending mail for each successive exceptions + # everytime a remote client tries to connect. Our process might never get + # a chance to exit and the remote client might just hang. + # + if not _reset_excepthook(): + raise ValueError("Couldn't reset sys.excepthook. Aborting remote " + "debugging.") + # Send email. + _send_email_with_attach_instructions(arg, mailto, originalpid=originalpid) + # Sleep until the debugger to attaches. + _sleep_until_debugger_attaches(arg, timeout=timeout) + except: + traceback.print_exception(*sys.exc_info()) + finally: + if background: + # Exit. Note that the original process already continued. + # We do this in a 'finally' to make sure that we always exit + # here. We don't want to do cleanup actions (finally clauses, + # atexit functions) in the parent, since that can affect the + # parent (e.g. deleting temp files while the parent process is + # still using them). + os._exit(1) + + +def debug_on_exception(function, background=False): + """ + Decorator that wraps a function so that we enter a debugger upon exception. + """ + @wraps(function) + def wrapped_function(*args, **kwargs): + try: + return function(*args, **kwargs) + except: + debugger(sys.exc_info(), background=background) + raise + return wrapped_function + + +def _send_email_with_attach_instructions(arg, mailto, originalpid): + from email.mime.text import MIMEText + import smtplib + import socket + import traceback + # Prepare variables we'll use in the email. + d = dict() + user = pwd.getpwuid(os.geteuid()).pw_name + argv = ' '.join(sys.argv) + d.update( + argv =argv , + argv_abbrev=argv[:40] , + event ="breakpoint" , + exc =None , + exctype =None , + hostname =socket.getfqdn() , + originalpid=originalpid , + pid =os.getpid() , + py =_find_py_commandline(), + time =time.strftime("%Y-%m-%d %H:%M:%S %Z", time.localtime()), + traceback =None , + username =user , + ) + tb = None + frame = None + stacktrace = None + if isinstance(arg, FrameType): + frame = arg + stacktrace = ''.join(traceback.format_stack(frame)) + elif isinstance(arg, TracebackType): + frame = d['tb'].tb_frame + stacktrace = ''.join(traceback.format_tb(arg)) + elif isinstance(arg, tuple) and len(arg) == 3 and isinstance(arg[2], TracebackType): + d.update( + exctype=arg[0].__name__, + exc =arg[1] , + event =arg[0].__name__, + ) + tb = arg[2] + while tb.tb_next: + tb = tb.tb_next + frame = tb.tb_frame + stacktrace = ''.join(traceback.format_tb(arg[2])) + ( + " %s: %s\n" % (arg[0].__name__, arg[1])) + if not frame: + frame = _get_caller_frame() + d.update( + function = frame.f_code.co_name , + filename = frame.f_code.co_filename, + line = frame.f_lineno , + ) + d.update( + filename_abbrev = _abbrev_filename(d['filename']), + ) + if tb: + d['stacktrace'] = tb and ''.join(" %s\n" % (line,) for line in stacktrace.splitlines()) + # Construct a template for the email body. + template = [] + template += [ + "While running {argv_abbrev}, {event} in {function} at {filename}:{line}", + "", + "Please run:", + " ssh -t {hostname} {py} -d {pid}", + "", + ] + if d['originalpid']: + template += [ + "As process {originalpid}, I have forked to process {pid} and am waiting for a debugger to attach." + ] + else: + template += [ + "As process {pid}, I am waiting for a debugger to attach." + ] + template += [ + "", + "Details:", + " Time : {time}", + " Host : {hostname}", + ] + if d['originalpid']: + template += [ + " Original process : {originalpid}", + " Forked process : {pid}", + ] + else: + template += [ + " Process : {pid}", + ] + template += [ + " Username : {username}", + " Command line : {argv}", + ] + if d['exc']: + template += [ + " Exception : {exctype}: {exc}", + ] + if d.get('stacktrace'): + template += [ + " Traceback :", + "{stacktrace}", + ] + # Build email body. + email_body = '\n'.join(template).format(**d) + # Print to stderr. + prefixed = "".join("[PYFLYBY] %s\n" % line + for line in email_body.splitlines()) + sys.stderr.write(prefixed) + # Send email. + if mailto is None: + mailto = os.getenv("USER") or user + msg = MIMEText(email_body) + msg['Subject'] = ( + "ssh {hostname} py -d {pid}" + " # {event} in {argv_abbrev} in {function} at {filename_abbrev}:{line}" + ).format(**d) + msg['From'] = user + msg['To'] = mailto + s = smtplib.SMTP("localhost") + s.sendmail(user, [mailto], msg.as_string()) + s.quit() + + +def _abbrev_filename(filename): + splt = filename.rsplit("/", 4) + if len(splt) >= 4: + splt[:2] = ["..."] + return '/'.join(splt) + + +def syscall_marker(msg): + """ + Execute a dummy syscall that is visible in truss/strace. + """ + try: + s = ("/### %s" % (msg,)).ljust(70) + os.stat(s) + except OSError: + pass + + +_ORIG_PID = os.getpid() + +def _signal_handler_debugger(signal_number, interrupted_frame): + if os.getpid() != _ORIG_PID: + # We're in a forked subprocess. Ignore this SIGQUIT. + return + fd_tty = _dev_tty_fd() + os.write(fd_tty, b"\nIntercepted SIGQUIT; entering debugger. Resend ^\\ to dump core (and 'stty sane' to reset terminal settings).\n\n") + frame = _get_caller_frame() + enable_signal_handler_debugger(False) + debugger( + frame, + on_continue=enable_signal_handler_debugger) + signal.signal(signal.SIGQUIT, _signal_handler_debugger) + + +def enable_signal_handler_debugger(enable=True): + r''' + Install a signal handler for SIGQUIT so that Control-\ or external SIGQUIT + enters debugger. Suitable to be called from site.py. + ''' + # Idea from bzrlib.breakin + # (http://bazaar.launchpad.net/~bzr/bzr/trunk/annotate/head:/bzrlib/breakin.py) + if enable: + signal.signal(signal.SIGQUIT, _signal_handler_debugger) + else: + signal.signal(signal.SIGQUIT, signal.SIG_DFL) + + +def enable_exception_handler_debugger(): + ''' + Enable ``sys.excepthook = debugger`` so that we automatically enter + the debugger upon uncaught exceptions. + ''' + _override_excepthook(debugger) + + +# Handle SIGTERM with traceback+exit. +def _sigterm_handler(signum, frame): + # faulthandler.dump_traceback(all_threads=True) + import traceback + traceback.print_stack() + # raise SigTermReceived + signal.signal(signum, signal.SIG_DFL) + os.kill(os.getpid(), signum) + os._exit(99) # shouldn't get here + + +def enable_sigterm_handler(on_existing_handler='raise'): + """ + Install a handler for SIGTERM that causes Python to print a stack trace + before exiting. + + :param on_existing_handler: + What to do when a SIGTERM handler was already registered. + - If ``"raise"``, then keep the existing handler and raise an exception. + - If ``"keep_existing"``, then silently keep the existing handler. + - If ``"warn_and_override"``, then override the existing handler and log a warning. + - If ``"silently_override"``, then silently override the existing handler. + """ + old_handler = signal.signal(signal.SIGTERM, _sigterm_handler) + if old_handler == signal.SIG_DFL or old_handler == _sigterm_handler: + return + if on_existing_handler == "silently_override": + return + if on_existing_handler == "warn_and_override": + from ._log import logger + logger.warning("enable_sigterm_handler(): Overriding existing SIGTERM handler") + return + signal.signal(signal.SIGTERM, old_handler) + if on_existing_handler == "keep_existing": + return + elif on_existing_handler == "raise": + raise ValueError( + "enable_sigterm_handler(on_existing_handler='raise'): SIGTERM handler already exists" + repr(old_handler)) + else: + raise ValueError( + "enable_sigterm_handler(): SIGTERM handler already exists, " + "and invalid on_existing_handler=%r" + % (on_existing_handler,)) + + +def enable_faulthandler(): + try: + import faulthandler + except ImportError: + pass + else: + # Print Python user-level stack trace upon SIGSEGV/etc. + faulthandler.enable() + + +def add_debug_functions_to_builtins(): + ''' + Install debugger(), etc. in the builtin global namespace. + ''' + functions_to_add = [ + 'debugger', + 'debug_on_exception', + 'print_traceback', + ] + # DEPRECATED: In the future, the following will not be added to builtins. + # Use debugger() instead. + functions_to_add += [ + 'breakpoint', + 'debug_exception', + 'debug_statement', + 'waitpoint', + ] + for name in functions_to_add: + setattr(builtins, name, globals()[name]) + +# TODO: allow attaching remotely (winpdb/rpdb2) upon sigquit. Or rpc like http://code.activestate.com/recipes/576515/ +# TODO: http://sourceware.org/gdb/wiki/PythonGdb + + +def get_executable(pid): + """ + Get the full path for the target process. + + :type pid: + ``int`` + :rtype: + `Filename` + """ + uname = os.uname()[0] + if uname == 'Linux': + result = os.readlink('/proc/%d/exe' % (pid,)) + elif uname == 'SunOS': + result = os.readlink('/proc/%d/path/a.out' % (pid,)) + else: + # Use psutil to try to answer this. This should also work for the + # above cases too, but it's simple enough to implement it directly and + # avoid this dependency on those platforms. + import psutil + result = psutil.Process(pid).exe() + result = Filename(result).real + if not result.isfile: + raise ValueError("Couldn't get executable for pid %s" % (pid,)) + if not result.isreadable: + raise ValueError("Executable %s for pid %s is not readable" + % (result, pid)) + return result + + +_gdb_safe_chars = ( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + r"0123456789,./-_=+:;'[]{}\|`~!@#%^&*()<>? ") + +def _escape_for_gdb(string): + """ + Escape a string to make it safe for passing to gdb. + """ + result = [] + for char in string: + if char in _gdb_safe_chars: + result.append(char) + else: + result.append("\\%s" % (oct(ord(char)),)) + return ''.join(result) + + +_memoized_dev_null = None +def _dev_null(): + """ + Return a file object opened for reading/writing to /dev/null. + Memoized. + + :rtype: + ``file`` + """ + global _memoized_dev_null + if _memoized_dev_null is None: + _memoized_dev_null = open("/dev/null", 'w+') + return _memoized_dev_null + + +def inject(pid, statements, wait=True, show_gdb_output=False): + """ + Execute ``statements`` in a running Python process. + + :type pid: + ``int`` + :param pid: + Id of target process + :type statements: + Iterable of strings + :param statements: + Python statements to execute. + :return: + Then process ID of the gdb process if ``wait`` is False; ``None`` if + ``wait`` is True. + """ + import subprocess + os.kill(pid, 0) # raises OSError "No such process" unless pid exists + if isinstance(statements, six.string_types): + statements = (statements,) + else: + statements = tuple(statements) + for statement in statements: + if not isinstance(statement, six.string_types): + raise TypeError( + "Expected iterable of strings, not %r" % (type(statement),)) + # Based on + # https://github.com/lmacken/pyrasite/blob/master/pyrasite/inject.py + # TODO: add error checking + # TODO: consider using lldb, especially on Darwin. + gdb_commands = ( + [ 'PyGILState_Ensure()' ] + + [ 'PyRun_SimpleString("%s")' % (_escape_for_gdb(statement),) + for statement in statements ] + + [ 'PyGILState_Release($1)' ]) + python_path = get_executable(pid) + if "python" not in python_path.base: + raise ValueError( + "pid %s uses executable %s, which does not appear to be python" + % (pid, python_path)) + # TODO: check that gdb is found and that the version is new enough (7.x) + # + # A note about --interpreter=mi: mi stands for Machine Interface and it's + # the blessed way to control gdb from a pipe, since the output is much + # easier to parse than the normal human-oriented output (it is also worth + # noting that at the moment we are never parsig the output, but it's still + # a good practice to use --interpreter=mi). + command = ( + ['gdb', str(python_path), '-p', str(pid), '-batch', '--interpreter=mi'] + + [ '-eval-command=call %s' % (c,) for c in gdb_commands ]) + output = None if show_gdb_output else _dev_null() + process = subprocess.Popen(command, + stdin=_dev_null(), + stdout=output, + stderr=output) + if wait: + retcode = process.wait() + if retcode: + raise Exception( + "Gdb command %r failed (exit code %r)" + % (command, retcode)) + else: + return process.pid + + +import tty + + +# Copy of tty.setraw that does not set ISIG, +# in order to keep CTRL-C sending Keybord Interrupt. +def setraw_but_sigint(fd, when=tty.TCSAFLUSH): + """Put terminal into a raw mode.""" + mode = tty.tcgetattr(fd) + mode[tty.IFLAG] = mode[tty.IFLAG] & ~( + tty.BRKINT | tty.ICRNL | tty.INPCK | tty.ISTRIP | tty.IXON + ) + mode[tty.OFLAG] = mode[tty.OFLAG] & ~(tty.OPOST) + mode[tty.CFLAG] = mode[tty.CFLAG] & ~(tty.CSIZE | tty.PARENB) + mode[tty.CFLAG] = mode[tty.CFLAG] | tty.CS8 + mode[tty.LFLAG] = mode[tty.LFLAG] & ~( + tty.ECHO | tty.ICANON | tty.IEXTEN + ) # NOT ISIG HERE. + mode[tty.CC][tty.VMIN] = 1 + mode[tty.CC][tty.VTIME] = 0 + tty.tcsetattr(fd, when, mode) + + +class Pty(object): + def __init__(self): + import pty + self.master_fd, self.slave_fd = pty.openpty() + self.ttyname = os.ttyname(self.slave_fd) + + def communicate(self): + import tty + import pty + try: + mode = tty.tcgetattr(pty.STDIN_FILENO) + setraw_but_sigint(pty.STDIN_FILENO) + restore = True + except tty.error: + restore = False + try: + pty._copy(self.master_fd) + except KeyboardInterrupt: + print('^C\r') # we need the \r because we are still in raw mode + finally: + if restore: + tty.tcsetattr(pty.STDIN_FILENO, tty.TCSAFLUSH, mode) + os.close(self.master_fd) + + +def process_exists(pid): + """ + Return whether ``pid`` exists. + + :type pid: + ``int`` + :rtype: + ``bool`` + """ + try: + os.kill(pid, 0) + return True + except OSError as e: + if e.errno == errno.ESRCH: + return False + raise + + +def kill_process(pid, kill_signals): + """ + Kill process ``pid`` using various signals. + + :param kill_signals: + Sequence of (signal, delay) tuples. Each signal is tried in sequence, + waiting up to ``delay`` seconds before trying the next signal. + """ + for sig, delay in kill_signals: + start_time = time.time() + try: + os.kill(pid, sig) + except OSError as e: + if e.errno == errno.ESRCH: + return True + raise + deadline = start_time + delay + while time.time() < deadline: + if not process_exists(pid): + return True + time.sleep(0.05) + + +def attach_debugger(pid): + """ + Attach command-line debugger to a running process. + + :param pid: + Process id of target process. + """ + import pyflyby + import signal + class SigUsr1(Exception): + pass + def sigusr1_handler(*args): + raise SigUsr1 + signal.signal(signal.SIGUSR1, sigusr1_handler) + terminal = Pty() + pyflyby_lib_path = os.path.dirname(pyflyby.__path__[0]) + # Inject a call to 'debugger()' into target process. + # Set on_continue to signal ourselves that we're done. + on_continue = "lambda: __import__('os').kill(%d, %d)" % (os.getpid(), signal.SIGUSR1) + + # Use Python import machinery to import pyflyby from its directory. + # + # Adding the path to sys.path might have side effects. For e.g., a package + # with the same name as a built-in module could exist in `pyflyby_dir`. + # Adding `pyflyby_dir` to sys.path will make the package get imported from + # `pyflyby_dir` instead of deferring this decision to the user Python + # environment. + # + # As a concrete example, `typing` module is a package as well a built-in + # module from Python version >= 3.5 + if six.PY2: + statements = [( + "location = __import__('imp').find_module('pyflyby', ['{pyflyby_dir}'])" + .format(pyflyby_dir=pyflyby_lib_path)), + "pyflyby = __import__('pkgutil').ImpLoader('pyflyby', *location).load_module('pyflyby')" + ] + else: + statements = [ + "loader = __import__('importlib').machinery.PathFinder.find_module(" + "fullname='pyflyby', path=['{pyflyby_dir}'])".format( + pyflyby_dir=pyflyby_lib_path), + "pyflyby = loader.load_module('pyflyby')" + ] + statements.append( + ("pyflyby.debugger(tty=%r, on_continue=%s)" + % (terminal.ttyname, on_continue)) + ) + + gdb_pid = inject(pid, statements=";".join(statements), wait=False) + # Fork a watchdog process to make sure we exit if the target process or + # gdb process exits, and make sure the gdb process exits if we exit. + parent_pid = os.getpid() + watchdog_pid = os.fork() + if watchdog_pid == 0: + while True: + try: + if not process_exists(gdb_pid): + kill_process( + parent_pid, + [(signal.SIGUSR1, 5), (signal.SIGTERM, 15), + (signal.SIGKILL, 60)]) + break + if not process_exists(pid): + start_time = time.time() + os.kill(parent_pid, signal.SIGUSR1) + kill_process( + gdb_pid, + [(0, 5), (signal.SIGTERM, 15), (signal.SIGKILL, 60)]) + kill_process( + parent_pid, + [(0, (5 + time.time() - start_time)), + (signal.SIGTERM, 15), (signal.SIGKILL, 60)]) + break + if not process_exists(parent_pid): + kill_process( + gdb_pid, + [(0, 5), (signal.SIGTERM, 15), (signal.SIGKILL, 60)]) + break + time.sleep(0.1) + except KeyboardInterrupt: + # if the user pressed CTRL-C the parent process is about to + # die, so we will detect the death in the next iteration of + # the loop and exit cleanly after killing also gdb + pass + os._exit(0) + # Communicate with pseudo tty. + try: + terminal.communicate() + except SigUsr1: + print("Debugging complete.") + pass + + +def remote_print_stack(pid, output=1): + """ + Tell a target process to print a stack trace. + + This currently only handles the main thread. + TODO: handle multiple threads. + + :param pid: + PID of target process. + :type output: + ``int``, ``file``, or ``str`` + :param output: + Output file descriptor. + """ + # Interpret ``output`` argument as a file-like object, file descriptor, or + # filename. + if hasattr(output, 'write'): # file-like object + output_fh = output + try: + output.flush() + except Exception: + pass + try: + output_fd = output.fileno() + except Exception: + output_fd = None + try: + output_fn = Filename(output.name) + except Exception: + pass + elif isinstance(output, int): + output_fh = None + output_fn = None + output_fd = output + elif isinstance(output, (str, Filename)): + output_fh = None + output_fn = Filename(output) + output_fd = None + else: + raise TypeError( + "remote_print_stack_trace(): expected file/str/int; got %s" + % (type(output).__name__,)) + temp_file = None + remote_fn = output_fn + if remote_fn is None and output_fd is not None: + remote_fn = Filename("/proc/%d/fd/%d" % (os.getpid(), output_fd)) + # Figure out whether the target process will be able to open output_fn for + # writing. Since the target process would need to be running as the same + # user as this process for us to be able to attach a debugger, we can + # simply check whether we ourselves can open the file. Typically output + # will be fd 1 and we will have access to write to it. However, if we're + # sudoed, we won't be able to re-open it via the proc symlink, even though + # we already currently have it open. Another case is ``output`` is a + # file-like object that isn't a real file, e.g. a StringO. In each case + # we we don't have a usable filename for the remote process yet. To + # address these situations, we create a temporary file for the remote + # process to write to. + if remote_fn is None or not remote_fn.iswritable: + if not output_fh or output_fd: + assert remote_fn is not None + raise OSError(errno.EACCESS, "Can't write to %s" % output_fn) + # We can still use the /proc/$pid/fd approach with an unnamed temp + # file. If it turns out there are situations where that doesn't work, + # we can switch to using a NamedTemporaryFile. + from tempfile import TemporaryFile + temp_file = TemporaryFile() + remote_fn = Filename( + "/proc/%d/fd/%d" % (os.getpid(), temp_file.fileno())) + assert remote_fn.iswritable + # *** Do the code injection *** + _remote_print_stack_to_file(pid, remote_fn) + # Copy from temp file to the requested output. + if temp_file is not None: + data = temp_file.read() + temp_file.close() + if output_fh is not None: + output_fh.write(data) + output_fh.flush() + elif output_fd is not None: + with os.fdopen(output_fd, 'w') as f: + f.write(data) + else: + raise AssertionError("unreacahable") + + +def _remote_print_stack_to_file(pid, filename): + inject(pid, [ + "import traceback", + "with open(%r,'w') as f: traceback.print_stack(file=f)" % str(filename) + ], wait=True) + + + +# Deprecated wrapper for wait_for_debugger_to_attach(). +def waitpoint(frame=None, mailto=None, background=False, timeout=86400): + if frame is None: + frame = _get_caller_frame() + wait_for_debugger_to_attach(frame, mailto=mailto, + background=background, timeout=timeout) + +breakpoint = debugger # deprecated alias +debug_statement = debugger # deprecated alias +debug_exception = debugger # deprecated alias +enable_signal_handler_breakpoint = enable_signal_handler_debugger # deprecated alias +enable_exception_handler = enable_exception_handler_debugger # deprecated alias diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_docxref.py b/.venv/lib/python3.8/site-packages/pyflyby/_docxref.py new file mode 100644 index 0000000..4280d19 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_docxref.py @@ -0,0 +1,382 @@ +# pyflyby/_docxref.py. + +# Module for checking Epydoc cross-references. + +# Portions of the code below are derived from Epydoc, which is distributed +# under the MIT license: +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and any associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the +# following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# The software is provided "as is", without warranty of any kind, express or +# implied, including but not limited to the warranties of merchantability, +# fitness for a particular purpose and noninfringement. In no event shall +# the authors or copyright holders be liable for any claim, damages or other +# liability, whether in an action of contract, tort or otherwise, arising +# from, out of or in connection with the software or the use or other +# dealings in the software. + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import re +import six +from six.moves import builtins +from textwrap import dedent + +from epydoc.apidoc import (ClassDoc, ModuleDoc, PropertyDoc, + RoutineDoc, UNKNOWN, VariableDoc) +from epydoc.docbuilder import build_doc_index +from epydoc.markup.plaintext import ParsedPlaintextDocstring + +from pyflyby._file import Filename +from pyflyby._idents import DottedIdentifier +from pyflyby._log import logger +from pyflyby._modules import ModuleHandle +from pyflyby._util import cached_attribute, memoize, prefixes + +# If someone references numpy.*, just assume it's OK - it's not worth +# following into numpy because it's too slow. +ASSUME_MODULES_OK = set(['numpy']) + +@memoize +def map_strings_to_line_numbers(module): + """ + Walk ``module.ast``, looking at all string literals. Return a map from + string literals to line numbers (1-index). + + :rtype: + ``dict`` from ``str`` to (``int``, ``str``) + """ + d = {} + for field in module.block.string_literals(): + # Dedent because epydoc dedents strings and we need to look up by + # those. But keep track of original version because we need to count + # exact line numbers. + s = dedent(field.s).strip() + start_lineno = field.startpos.lineno + d[s] = (start_lineno, field.s) + return d + + +def get_string_linenos(module, searchstring, within_string): + """ + Return the line numbers (1-indexed) within ``filename`` that contain + ``searchstring``. Only consider string literals (i.e. not comments). + First look for exact matches of ``within_string`` (modulo indenting) and + then search within that. Only if the ``within_string`` is not found, + search the entire file. + + [If there's a comment on the same line as a string that also contains the + searchstring, we'll get confused.] + """ + module = ModuleHandle(module) + regexp = re.compile(searchstring) + map = map_strings_to_line_numbers(module) + results = [] + def scan_within_string(results, start_lineno, orig_full_string): + for i, line in enumerate(orig_full_string.splitlines()): + if regexp.search(line): + results.append( start_lineno + i ) + try: + lineno, orig_full_string = map[within_string.strip()] + except KeyError: + pass + else: + # We found the larger string exactly within the ast. + scan_within_string(results, lineno, orig_full_string) + if results: + return tuple(results) + # We could continue down if this ever happened. + raise Exception( + "Found superstring in %r but not substring %r within superstring" + % (module.filename, searchstring)) + # Try a full text search. + for lineno, orig_full_string in map.values(): + scan_within_string(results, lineno, orig_full_string) + if results: + return tuple(sorted(results)) + raise Exception( + "Could not find %r anywhere in %r" % (searchstring, module.filename)) + + +def describe_xref(identifier, container): + module = ModuleHandle(str(container.defining_module.canonical_name)) + assert module.filename == Filename(container.defining_module.filename) + linenos = get_string_linenos( + module, + "(L{|<)%s" % (identifier,), + container.docstring) + return (module, linenos, str(container.canonical_name), identifier) + + + +def safe_build_doc_index(modules): + # build_doc_index isn't re-entrant due to crappy caching! >:( + from epydoc.docintrospecter import clear_cache + clear_cache() + from epydoc.docparser import _moduledoc_cache + _moduledoc_cache.clear() + # Build a new DocIndex. It swallows exceptions and returns None on error! + # >:( + result = build_doc_index(modules) + if result is None: + raise Exception("Failed to build doc index on %r" % (modules,)) + return result + + +class ExpandedDocIndex(object): + """ + A wrapper around DocIndex that automatically expands with more modules as + needed. + """ + # TODO: this is kludgy and inefficient since it re-reads modules. + def __init__(self, modules): + self.modules = set([ModuleHandle(m) for m in modules]) + + def add_module(self, module): + """ + Adds ``module`` and recreates the DocIndex with the updated set of + modules. + + :return: + Whether anything was added. + """ + module = ModuleHandle(module) + for prefix in module.ancestors: + if prefix in self.modules: + # The module, or a prefix of it, was already added. + return False + + for existing_module in sorted(self.modules): + if existing_module.startswith(module): + # This supersedes an existing module. + assert existing_module != module + self.modules.remove(existing_module) + + logger.debug("Expanding docindex to include %r", module) + self.modules.add(module) + del self.docindex + return True + + def find(self, a, b): + return self.docindex.find(a, b) + + def get_vardoc(self, a): + return self.docindex.get_vardoc(a) + + @cached_attribute + def docindex(self): + return safe_build_doc_index( + [str(m.name) for m in sorted(self.modules)]) + + +def remove_epydoc_sym_suffix(s): + """ + Remove trailing "'" that Epydoc annoyingly adds to 'shadowed' names. + + >>> remove_epydoc_sym_suffix("a.b'.c'.d") + 'a.b.c.d' + + """ + return re.sub(r"'([.]|$)", r'\1', s) + +class XrefScanner(object): + + def __init__(self, modules): + self.modules = modules + self.docindex = safe_build_doc_index(modules) + + @cached_attribute + def expanded_docindex(self): + return ExpandedDocIndex(self.modules) + + def scan(self): + self._failed_xrefs = [] + valdocs = sorted(self.docindex.reachable_valdocs( + imports=False, packages=False, bases=False, submodules=False, + subclasses=False, private=True + )) + for doc in valdocs: + if isinstance(doc, ClassDoc): + self.scan_class(doc) + elif isinstance(doc, ModuleDoc): + self.scan_module(doc) + return tuple(sorted(self._failed_xrefs)) + + def scan_module(self, doc): + self.descr(doc) + if doc.is_package is True: + for submodule in doc.submodules: + self.scan_module(submodule) + # self.scan_module_list(doc) + self.scan_details_list(doc, "function") + self.scan_details_list(doc, "other") + + def scan_class(self, doc): + self.descr(doc) + self.scan_details_list(doc, "method") + self.scan_details_list(doc, "classvariable") + self.scan_details_list(doc, "instancevariable") + self.scan_details_list(doc, "property") + + def scan_details_list(self, doc, value_type): + detailed = True + if isinstance(doc, ClassDoc): + var_docs = doc.select_variables(value_type=value_type, + imported=False, inherited=False, + public=None, + detailed=detailed) + else: + var_docs = doc.select_variables(value_type=value_type, + imported=False, + public=None, + detailed=detailed) + for var_doc in var_docs: + self.scan_details(var_doc) + + def scan_details(self, var_doc): + self.descr(var_doc) + if isinstance(var_doc.value, RoutineDoc): + self.return_type(var_doc) + self.return_descr(var_doc) + for (arg_names, arg_descr) in var_doc.value.arg_descrs: + self.scan_docstring(arg_descr, var_doc.value) + for arg in var_doc.value.arg_types: + self.scan_docstring( + var_doc.value.arg_types[arg], var_doc.value) + elif isinstance(var_doc.value, PropertyDoc): + prop_doc = var_doc.value + self.return_type(prop_doc.fget) + self.return_type(prop_doc.fset) + self.return_type(prop_doc.fdel) + else: + self.type_descr(var_doc) + + def _scan_attr(self, attr, api_doc): + if api_doc in (None, UNKNOWN): + return '' + pds = getattr(api_doc, attr, None) # pds = ParsedDocstring. + if pds not in (None, UNKNOWN): + self.scan_docstring(pds, api_doc) + elif isinstance(api_doc, VariableDoc): + self._scan_attr(attr, api_doc.value) + + def summary(self, api_doc): + self._scan_attr('summary', api_doc) + + def descr(self, api_doc): + self._scan_attr('descr', api_doc) + + def type_descr(self, api_doc): + self._scan_attr('type_descr', api_doc) + + def return_type(self, api_doc): + self._scan_attr('return_type', api_doc) + + def return_descr(self, api_doc): + self._scan_attr('return_descr', api_doc) + + def check_xref(self, identifier, container): + """ + Check that ``identifier`` cross-references a proper symbol. + + Look in modules that we weren't explicitly asked to look in, if + needed. + """ + if identifier in builtins.__dict__: + return True + def check_container(): + if self.expanded_docindex.find(identifier, container) is not None: + return True + if isinstance(container, RoutineDoc): + tcontainer = self.expanded_docindex.get_vardoc( + container.canonical_name) + doc = self.expanded_docindex.find(identifier, tcontainer) + while (doc is not None and tcontainer not in (None, UNKNOWN) + and tcontainer.overrides not in (None, UNKNOWN)): + tcontainer = tcontainer.overrides + doc = self.expanded_docindex.find(identifier, tcontainer) + return doc is not None + return False + def check_defining_module(x): + if x is None: + return False + defining_module_name = remove_epydoc_sym_suffix(str( + x.defining_module.canonical_name)) + if defining_module_name in ASSUME_MODULES_OK: + return True + if self.expanded_docindex.add_module(defining_module_name): + if check_container(): + return True + return False + if check_container(): + return True + if (isinstance(container, RoutineDoc) and + identifier in container.all_args()): + return True + if check_defining_module(container): + return True + # If the user has imported foo.bar.baz as baz and now uses + # ``baz.quux``, we need to add the module foo.bar.baz. + for prefix in reversed(list(prefixes( + DottedIdentifier(remove_epydoc_sym_suffix(identifier))))): + if check_defining_module( + self.docindex.find(str(prefix), container)): + return True + try: + module = ModuleHandle.containing(identifier) + except ImportError: + pass + else: + if str(module.name) in ASSUME_MODULES_OK: + return True + if self.expanded_docindex.add_module(module): + if check_container(): + return True + return False + + def scan_docstring(self, parsed_docstring, container): + if parsed_docstring in (None, UNKNOWN): return '' + if isinstance(parsed_docstring, ParsedPlaintextDocstring): + return '' + + def scan_tree(tree): + if isinstance(tree, six.string_types): + return tree + variables = [scan_tree(child) for child in tree.children] + if tree.tag == 'link': + identifier = variables[1] + if not self.check_xref(identifier, container): + self._failed_xrefs.append( + describe_xref(identifier, container) ) + return '?' + elif tree.tag == 'indexed': + return '?' + elif tree.tag in ('epytext', 'section', 'tag', 'arg', + 'name', 'target', 'html', 'para'): + return ''.join(variables) + return '?' + + scan_tree(parsed_docstring._tree) + + +def find_bad_doc_cross_references(names): + """ + Find docstring cross references that fail to resolve. + + :type names: + Sequence of module names or filenames. + :return: + Sequence of ``(module, linenos, container_name, identifier)`` tuples. + """ + xrs = XrefScanner(names) + return xrs.scan() diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_file.py b/.venv/lib/python3.8/site-packages/pyflyby/_file.py new file mode 100644 index 0000000..73ad177 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_file.py @@ -0,0 +1,725 @@ +# pyflyby/_file.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from functools import total_ordering +import io +import os +import re +import six +import sys + +from six import string_types + +from pyflyby._util import cached_attribute, cmp, memoize + +class UnsafeFilenameError(ValueError): + pass + + +# TODO: statcache + +@total_ordering +class Filename(object): + """ + A filename. + + >>> Filename('/etc/passwd') + Filename('/etc/passwd') + + """ + def __new__(cls, arg): + if isinstance(arg, cls): + return arg + if isinstance(arg, six.string_types): + return cls._from_filename(arg) + raise TypeError + + @classmethod + def _from_filename(cls, filename): + if not isinstance(filename, six.string_types): + raise TypeError + filename = str(filename) + if not filename: + raise UnsafeFilenameError("(empty string)") + if re.search("[^a-zA-Z0-9_=+{}/.,~@-]", filename): + raise UnsafeFilenameError(filename) + if re.search("(^|/)~", filename): + raise UnsafeFilenameError(filename) + self = object.__new__(cls) + self._filename = os.path.abspath(filename) + return self + + def __str__(self): + return self._filename + + def __repr__(self): + return "%s(%r)" % (type(self).__name__, self._filename) + + def __truediv__(self, x): + return type(self)(os.path.join(self._filename, x)) + + def __hash__(self): + return hash(self._filename) + + def __eq__(self, o): + if self is o: + return True + if not isinstance(o, Filename): + return NotImplemented + return self._filename == o._filename + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, o): + if not isinstance(o, Filename): + return NotImplemented + return self._filename < o._filename + + def __cmp__(self, o): + if self is o: + return 0 + if not isinstance(o, Filename): + return NotImplemented + return cmp(self._filename, o._filename) + + @cached_attribute + def ext(self): + """ + Returns the extension of this filename, including the dot. + Returns ``None`` if no extension. + + :rtype: + ``str`` or ``None`` + """ + lhs, dot, rhs = self._filename.rpartition('.') + if not dot: + return None + return dot + rhs + + @cached_attribute + def base(self): + return os.path.basename(self._filename) + + @cached_attribute + def dir(self): + return type(self)(os.path.dirname(self._filename)) + + @cached_attribute + def real(self): + return type(self)(os.path.realpath(self._filename)) + + @property + def realpath(self): + return type(self)(os.path.realpath(self._filename)) + + @property + def exists(self): + return os.path.exists(self._filename) + + @property + def islink(self): + return os.path.islink(self._filename) + + @property + def isdir(self): + return os.path.isdir(self._filename) + + @property + def isfile(self): + return os.path.isfile(self._filename) + + @property + def isreadable(self): + return os.access(self._filename, os.R_OK) + + @property + def iswritable(self): + return os.access(self._filename, os.W_OK) + + @property + def isexecutable(self): + return os.access(self._filename, os.X_OK) + + def startswith(self, prefix): + prefix = Filename(prefix) + if self == prefix: + return True + return self._filename.startswith("%s/" % (prefix,)) + + def list(self, ignore_unsafe=True): + filenames = [os.path.join(self._filename, f) + for f in sorted(os.listdir(self._filename))] + result = [] + for f in filenames: + try: + f = Filename(f) + except UnsafeFilenameError: + if ignore_unsafe: + continue + else: + raise + result.append(f) + return result + + @property + def ancestors(self): + """ + Return ancestors of self, from self to /. + + >>> Filename("/aa/bb").ancestors + (Filename('/aa/bb'), Filename('/aa'), Filename('/')) + + :rtype: + ``tuple`` of ``Filename`` s + """ + result = [self] + while True: + dir = result[-1].dir + if dir == result[-1]: + break + result.append(dir) + return tuple(result) + + +@memoize +def _get_PATH(): + PATH = os.environ.get("PATH", "").split(os.pathsep) + result = [] + for path in PATH: + if not path: + continue + try: + result.append(Filename(path)) + except UnsafeFilenameError: + continue + return tuple(result) + + +def which(program): + """ + Find ``program`` on $PATH. + + :type program: + ``str`` + :rtype: + `Filename` + :return: + Program on $PATH, or ``None`` if not found. + """ + # See if it exists in the current directory. + candidate = Filename(program) + if candidate.isreadable: + return candidate + for path in _get_PATH(): + candidate = path / program + if candidate.isexecutable: + return candidate + return None + + + +Filename.STDIN = Filename("/dev/stdin") + +@total_ordering +class FilePos(object): + """ + A (lineno, colno) position within a `FileText`. + Both lineno and colno are 1-indexed. + """ + + def __new__(cls, *args): + if len(args) == 0: + return cls._ONE_ONE + if len(args) == 1: + arg, = args + if isinstance(arg, cls): + return arg + elif arg is None: + return cls._ONE_ONE + elif isinstance(arg, tuple): + args = arg + # Fall through + else: + raise TypeError + lineno, colno = cls._intint(args) + if lineno == colno == 1: + return cls._ONE_ONE # space optimization + if lineno < 1: + raise ValueError( + "FilePos: invalid lineno=%d; should be >= 1" % lineno,) + if colno < 1: + raise ValueError( + "FilePos: invalid colno=%d; should be >= 1" % colno,) + return cls._from_lc(lineno, colno) + + @staticmethod + def _intint(args): + if (type(args) is tuple and + len(args) == 2 and + type(args[0]) is type(args[1]) is int): + return args + else: + raise TypeError("Expected (int,int); got %r" % (args,)) + + @classmethod + def _from_lc(cls, lineno, colno): + self = object.__new__(cls) + self.lineno = lineno + self.colno = colno + return self + + def __add__(self, delta): + ''' + "Add" a coordinate (line,col) delta to this ``FilePos``. + + Note that addition here may be a non-obvious. If there is any line + movement, then the existing column number is ignored, and the new + column is the new column delta + 1 (to convert into 1-based numbers). + + :rtype: + `FilePos` + ''' + ldelta, cdelta = self._intint(delta) + assert ldelta >= 0 and cdelta >= 0 + if ldelta == 0: + return FilePos(self.lineno, self.colno + cdelta) + else: + return FilePos(self.lineno + ldelta, 1 + cdelta) + + def __str__(self): + return "(%d,%d)" % (self.lineno, self.colno) + + def __repr__(self): + return "FilePos%s" % (self,) + + @property + def _data(self): + return (self.lineno, self.colno) + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, FilePos): + return NotImplemented + return self._data == other._data + + def __ne__(self, other): + return not (self == other) + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, FilePos): + return NotImplemented + return cmp(self._data, other._data) + + # The rest are defined by total_ordering + def __lt__(self, other): + if self is other: + return 0 + if not isinstance(other, FilePos): + return NotImplemented + return self._data < other._data + + def __hash__(self): + return hash(self._data) + + + +FilePos._ONE_ONE = FilePos._from_lc(1, 1) + + +@total_ordering +class FileText(object): + """ + Represents a contiguous sequence of lines from a file. + """ + + def __new__(cls, arg, filename=None, startpos=None): + """ + Return a new ``FileText`` instance. + + :type arg: + ``FileText``, ``Filename``, ``str``, or tuple of ``str`` + :param arg: + If a sequence of lines, then each should end with a newline and have + no other newlines. Otherwise, something that can be interpreted or + converted into a sequence of lines. + :type filename: + `Filename` + :param filename: + Filename to attach to this ``FileText``, if not already given by + ``arg``. + :type startpos: + ``FilePos`` + :param startpos: + Starting file position (lineno & colno) of this ``FileText``, if not + already given by ``arg``. + :rtype: + ``FileText`` + """ + if isinstance(arg, cls): + if filename is startpos is None: + return arg + return arg.alter(filename=filename, startpos=startpos) + elif isinstance(arg, Filename): + return cls(read_file(arg), filename=filename, startpos=startpos) + elif hasattr(arg, "__text__"): + return FileText(arg.__text__(), filename=filename, startpos=startpos) + elif isinstance(arg, six.string_types): + self = object.__new__(cls) + self.joined = arg + else: + raise TypeError("%s: unexpected %s" + % (cls.__name__, type(arg).__name__)) + if filename is not None: + filename = Filename(filename) + startpos = FilePos(startpos) + self.filename = filename + self.startpos = startpos + return self + + @classmethod + def _from_lines(cls, lines, filename, startpos): + assert type(lines) is tuple + assert len(lines) > 0 + assert isinstance(lines[0], string_types) + assert not lines[-1].endswith("\n") + self = object.__new__(cls) + self.lines = lines + self.filename = filename + self.startpos = startpos + return self + + @cached_attribute + def lines(self): + r""" + Lines that have been split by newline. + + These strings do NOT contain '\n'. + + If the input file ended in '\n', then the last item will be the empty + string. This is to avoid having to check lines[-1].endswith('\n') + everywhere. + + :rtype: + ``tuple`` of ``str`` + """ + # Used if only initialized with 'joined'. + # We use str.split() instead of str.splitlines() because the latter + # doesn't distinguish between strings that end in newline or not + # (or requires extra work to process if we use splitlines(True)). + return tuple(self.joined.split('\n')) + + @cached_attribute + def joined(self): # used if only initialized with 'lines' + return '\n'.join(self.lines) + + @classmethod + def from_filename(cls, filename): + return cls.from_lines(Filename(filename)) + + def alter(self, filename=None, startpos=None): + if filename is not None: + filename = Filename(filename) + else: + filename = self.filename + if startpos is not None: + startpos = FilePos(startpos) + else: + startpos = self.startpos + if filename == self.filename and startpos == self.startpos: + return self + else: + result = object.__new__(type(self)) + result.lines = self.lines + result.joined = self.joined + result.filename = filename + result.startpos = startpos + return result + + @cached_attribute + def endpos(self): + """ + The position after the last character in the text. + + :rtype: + ``FilePos`` + """ + startpos = self.startpos + lines = self.lines + lineno = startpos.lineno + len(lines) - 1 + if len(lines) == 1: + colno = startpos.colno + len(lines[-1]) + else: + colno = 1 + len(lines[-1]) + return FilePos(lineno, colno) + + def _lineno_to_index(self, lineno): + lineindex = lineno - self.startpos.lineno + # Check that the lineindex is in range. We don't allow pointing at + # the line after the last line because we already ensured that + # self.lines contains an extra empty string if necessary, to indicate + # a trailing newline in the file. + if not 0 <= lineindex < len(self.lines): + raise IndexError( + "Line number %d out of range [%d, %d)" + % (lineno, self.startpos.lineno, self.endpos.lineno)) + return lineindex + + def _colno_to_index(self, lineindex, colno): + coloffset = self.startpos.colno if lineindex == 0 else 1 + colindex = colno - coloffset + line = self.lines[lineindex] + # Check that the colindex is in range. We do allow pointing at the + # character after the last (non-newline) character in the line. + if not 0 <= colindex <= len(line): + raise IndexError( + "Column number %d on line %d out of range [%d, %d]" + % (colno, lineindex+self.startpos.lineno, + coloffset, coloffset+len(line))) + return colindex + + def __getitem__(self, arg): + """ + Return the line(s) with the given line number(s). + If slicing, returns an instance of ``FileText``. + + Note that line numbers are indexed based on ``self.startpos.lineno`` + (which is 1 at the start of the file). + + >>> FileText("a\\nb\\nc\\nd")[2] + 'b' + + >>> FileText("a\\nb\\nc\\nd")[2:4] + FileText('b\\nc\\n', startpos=(2,1)) + + >>> FileText("a\\nb\\nc\\nd")[0] + Traceback (most recent call last): + ... + IndexError: Line number 0 out of range [1, 4) + + When slicing, the input arguments can also be given as ``FilePos`` + arguments or (lineno,colno) tuples. These are 1-indexed at the start + of the file. + + >>> FileText("a\\nb\\nc\\nd")[(2,2):4] + FileText('\\nc\\n', startpos=(2,2)) + + :rtype: + ``str`` or `FileText` + """ + L = self._lineno_to_index + C = self._colno_to_index + if isinstance(arg, slice): + if arg.step is not None and arg.step != 1: + raise ValueError("steps not supported") + # Interpret start (lineno,colno) into indexes. + if arg.start is None: + start_lineindex = 0 + start_colindex = 0 + elif isinstance(arg.start, int): + start_lineindex = L(arg.start) + start_colindex = 0 + else: + startpos = FilePos(arg.start) + start_lineindex = L(startpos.lineno) + start_colindex = C(start_lineindex, startpos.colno) + # Interpret stop (lineno,colno) into indexes. + if arg.stop is None: + stop_lineindex = len(self.lines) + stop_colindex = len(self.lines[-1]) + elif isinstance(arg.stop, int): + stop_lineindex = L(arg.stop) + stop_colindex = 0 + else: + stoppos = FilePos(arg.stop) + stop_lineindex = L(stoppos.lineno) + stop_colindex = C(stop_lineindex, stoppos.colno) + # {start,stop}_{lineindex,colindex} are now 0-indexed + # [open,closed) ranges. + assert 0 <= start_lineindex <= stop_lineindex < len(self.lines) + assert 0 <= start_colindex <= len(self.lines[start_lineindex]) + assert 0 <= stop_colindex <= len(self.lines[stop_lineindex]) + # Optimization: return entire range + if (start_lineindex == 0 and + start_colindex == 0 and + stop_lineindex == len(self.lines)-1 and + stop_colindex == len(self.lines[-1])): + return self + # Get the lines we care about. We always include an extra entry + # at the end which we'll chop to the desired number of characters. + result_split = list(self.lines[start_lineindex:stop_lineindex+1]) + # Clip the starting and ending strings. We do the end clip first + # in case the result has only one line. + result_split[-1] = result_split[-1][:stop_colindex] + result_split[0] = result_split[0][start_colindex:] + # Compute the new starting line and column numbers. + result_lineno = start_lineindex + self.startpos.lineno + if start_lineindex == 0: + result_colno = start_colindex + self.startpos.colno + else: + result_colno = start_colindex + 1 + result_startpos = FilePos(result_lineno, result_colno) + return FileText._from_lines(tuple(result_split), + filename=self.filename, + startpos=result_startpos) + elif isinstance(arg, int): + # Return a single line. + lineindex = L(arg) + return self.lines[lineindex] + else: + raise TypeError("bad type %r" % (type(arg),)) + + @classmethod + def concatenate(cls, args): + """ + Concatenate a bunch of `FileText` arguments. Uses the ``filename`` + and ``startpos`` from the first argument. + + :rtype: + `FileText` + """ + args = [FileText(x) for x in args] + if len(args) == 1: + return args[0] + return FileText( + ''.join([l.joined for l in args]), + filename=args[0].filename, + startpos=args[0].startpos) + + def __repr__(self): + r = "%s(%r" % (type(self).__name__, self.joined,) + if self.filename is not None: + r += ", filename=%r" % (str(self.filename),) + if self.startpos != FilePos(): + r += ", startpos=%s" % (self.startpos,) + r += ")" + return r + + def __str__(self): + return self.joined + + def __eq__(self, o): + if self is o: + return True + if not isinstance(o, FileText): + return NotImplemented + return (self.filename == o.filename and + self.joined == o.joined and + self.startpos == o.startpos) + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, o): + if not isinstance(o, FileText): + return NotImplemented + return ((self.filename, self.joined, self.startpos) < + (o .filename, o .joined, o .startpos)) + + def __cmp__(self, o): + if self is o: + return 0 + if not isinstance(o, FileText): + return NotImplemented + return cmp((self.filename, self.joined, self.startpos), + (o .filename, o .joined, o .startpos)) + + def __hash__(self): + h = hash((self.filename, self.joined, self.startpos)) + self.__hash__ = lambda: h + return h + + +def read_file(filename): + filename = Filename(filename) + if filename == Filename.STDIN: + data = sys.stdin.read() + else: + with io.open(str(filename), 'r') as f: + data = f.read() + return FileText(data, filename=filename) + +def write_file(filename, data): + filename = Filename(filename) + data = FileText(data) + with open(str(filename), 'w') as f: + f.write(data.joined) + +def atomic_write_file(filename, data): + filename = Filename(filename) + data = FileText(data) + temp_filename = Filename("%s.tmp.%s" % (filename, os.getpid(),)) + write_file(temp_filename, data) + try: + st = os.stat(str(filename)) # OSError if file didn't exit before + os.chmod(str(temp_filename), st.st_mode) + os.chown(str(temp_filename), -1, st.st_gid) # OSError if not member of group + except OSError: + pass + os.rename(str(temp_filename), str(filename)) + +def expand_py_files_from_args(pathnames, on_error=lambda filename: None): + """ + Enumerate ``*.py`` files, recursively. + + Arguments that are files are always included. + Arguments that are directories are recursively searched for ``*.py`` files. + + :type pathnames: + ``list`` of `Filename` s + :type on_error: + callable + :param on_error: + Function that is called for arguments directly specified in ``pathnames`` + that don't exist or are otherwise inaccessible. + :rtype: + ``list`` of `Filename` s + """ + if not isinstance(pathnames, (tuple, list)): + pathnames = [pathnames] + pathnames = [Filename(f) for f in pathnames] + result = [] + # Check for problematic arguments. Note that we intentionally only do + # this for directly specified arguments, not for recursively traversed + # arguments. + stack = [] + for pathname in reversed(pathnames): + if pathname.isfile: + stack.append((pathname, True)) + elif pathname.isdir: + stack.append((pathname, False)) + else: + on_error(pathname) + while stack: + pathname, isfile = stack.pop(-1) + if isfile: + result.append(pathname) + continue + for f in reversed(pathname.list()): + # Check inclusions/exclusions for recursion. Note that we + # intentionally do this in the recursive step rather than the + # base step because if the user specification includes + # e.g. .pyflyby, we do want to include it; however, we don't + # want to recurse into .pyflyby ourselves. + if f.base.startswith("."): + continue + if f.base == "__pycache__": + continue + if f.isfile: + if f.ext == ".py": + stack.append((f, True)) + elif f.isdir: + stack.append((f, False)) + else: + # Silently ignore non-files/dirs from traversal. + pass + return result diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_flags.py b/.venv/lib/python3.8/site-packages/pyflyby/_flags.py new file mode 100644 index 0000000..e58fac8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_flags.py @@ -0,0 +1,236 @@ +# pyflyby/_flags.py. +# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import __future__ +import ast +import operator +import six +from six.moves import reduce +import warnings + +from pyflyby._util import cached_attribute + +# Initialize mappings from compiler_flag to feature name and vice versa. +_FLAG2NAME = {} +_NAME2FLAG = {} +for name in __future__.all_feature_names: + flag = getattr(__future__, name).compiler_flag + _FLAG2NAME[flag] = name + _NAME2FLAG[name] = flag +for name in dir(ast): + if name.startswith('PyCF'): + flag_name = name[len('PyCF_'):].lower() + flag = getattr(ast, name) + _FLAG2NAME[flag] = flag_name + _NAME2FLAG[flag_name] = flag +_FLAGNAME_ITEMS = sorted(_FLAG2NAME.items()) +_ALL_FLAGS = reduce(operator.or_, _FLAG2NAME.keys()) + + + +class CompilerFlags(int): + """ + Representation of Python "compiler flags", i.e. features from __future__. + + >>> print(CompilerFlags(0x18000).__interactive_display__()) # doctest: +SKIP + CompilerFlags(0x18000) # from __future__ import with_statement, print_function + + >>> print(CompilerFlags(0x10000, 0x8000).__interactive_display__()) # doctest: +SKIP + CompilerFlags(0x18000) # from __future__ import with_statement, print_function + + >>> print(CompilerFlags('with_statement', 'print_function').__interactive_display__()) # doctest: +SKIP + CompilerFlags(0x18000) # from __future__ import with_statement, print_function + + This can be used as an argument to the built-in compile() function. For + instance, in Python 2:: + + >>> compile("print('x', file=None)", "?", "exec", flags=0, dont_inherit=1) #doctest:+SKIP + Traceback (most recent call last): + + ... + SyntaxError: invalid syntax + + >>> compile("print('x', file=None)", "?", "exec", flags=CompilerFlags("print_function"), dont_inherit=1) #doctest:+ELLIPSIS + + + """ + + def __new__(cls, *args): + """ + Construct a new ``CompilerFlags`` instance. + + :param args: + Any number (zero or more) ``CompilerFlags`` s, ``int`` s, or ``str`` s, + which are bitwise-ORed together. + :rtype: + `CompilerFlags` + """ + if len(args) == 0: + return cls._ZERO + elif len(args) == 1: + arg, = args + if isinstance(arg, cls): + return arg + elif arg is None: + return cls._ZERO + elif isinstance(arg, int): + warnings.warn('creating CompilerFlags from integers is deprecated, ' + ' flags values change between Python versions. If you are sure use .from_int', + DeprecationWarning, stacklevel=2) + return cls.from_int(arg) + elif isinstance(arg, six.string_types): + return cls.from_str(arg) + elif isinstance(arg, ast.AST): + return cls.from_ast(arg) + elif isinstance(arg, (tuple, list)): + return cls(*arg) + else: + raise TypeError("CompilerFlags: unknown type %s" + % (type(arg).__name__,)) + else: + flags = [] + for x in args: + if isinstance(x, cls): + flags.append(int(x)) + elif isinstance(x, int): + warnings.warn( + "creating CompilerFlags from integers is deprecated, " + " flags values change between Python versions. If you are sure use .from_int", + DeprecationWarning, + stacklevel=2, + ) + flags.append(x) + elif isinstance(x, str): + flags.append(int(cls(x))) + else: + raise ValueError + + #assert flags == [0x10000, 0x8000], flags + + return cls.from_int(reduce(operator.or_, flags)) + + @classmethod + def from_int(cls, arg): + if arg == -1: + return cls._UNKNOWN # Instance optimization + if arg == 0: + return cls._ZERO # Instance optimization + self = int.__new__(cls, arg) + bad_flags = int(self) & ~_ALL_FLAGS + if bad_flags: + raise ValueError( + "CompilerFlags: unknown flag value(s) %s %s" % (bin(bad_flags), hex(bad_flags))) + return self + + @classmethod + def from_str(cls, arg): + try: + flag = _NAME2FLAG[arg] + except KeyError: + raise ValueError( + "CompilerFlags: unknown flag %r" % (arg,)) + return cls.from_int(flag) + + @classmethod + def from_ast(cls, nodes): + """ + Parse the compiler flags from AST node(s). + + :type nodes: + ``ast.AST`` or sequence thereof + :rtype: + ``CompilerFlags`` + """ + if isinstance(nodes, ast.Module): + nodes = nodes.body + elif isinstance(nodes, ast.AST): + nodes = [nodes] + flags = [] + for node in nodes: + if not isinstance(node, ast.ImportFrom): + # Got a non-import; stop looking further. + break + if not node.module == "__future__": + # Got a non-__future__-import; stop looking further. + break + # Get the feature names. + names = [n.name for n in node.names] + flags.extend(names) + return cls(flags) + + @cached_attribute + def names(self): + return tuple( + n + for f, n in _FLAGNAME_ITEMS + if f & self) + + def __or__(self, o): + if o == 0: + return self + if not isinstance(o, CompilerFlags): + o = CompilerFlags(o) + if self == 0: + return o + return CompilerFlags.from_int(int(self) | int(o)) + + def __ror__(self, o): + return self | o + + def __and__(self, o): + if not isinstance(o, int): + o = CompilerFlags(o) + return CompilerFlags.from_int(int(self) & int(o)) + + def __rand__(self, o): + return self & o + + def __xor__(self, o): + if not isinstance(o, CompilerFlags): + o = CompilerFlags.from_int(o) + return CompilerFlags.from_int(int(self) ^ int(o)) + + def __rxor__(self, o): + return self ^ o + + def __repr__(self): + return "CompilerFlags(%s)" % (hex(self),) + + def __str__(self): + return hex(self) + + def __interactive_display__(self): + s = repr(self) + if self != 0: + s += " # from __future__ import " + ", ".join(self.names) + return s + + +CompilerFlags._ZERO = int.__new__(CompilerFlags, 0) +CompilerFlags._UNKNOWN = int.__new__(CompilerFlags, -1) + +# flags that _may_ exists on future versions. +_future_flags = { + "nested_scopes", + "generators", + "division", + "absolute_import", + "with_statement", + "print_function", + "unicode_literals", + "barry_as_FLUFL", + "generator_stop", + "annotations", + "allow_top_level_await", + "only_ast", + "type_comments", +} +for k in _future_flags: + setattr(CompilerFlags, k, CompilerFlags._UNKNOWN) + +for k, v in _NAME2FLAG.items(): + setattr(CompilerFlags, k, CompilerFlags.from_int(v)) diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_format.py b/.venv/lib/python3.8/site-packages/pyflyby/_format.py new file mode 100644 index 0000000..c9f6142 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_format.py @@ -0,0 +1,180 @@ +# pyflyby/_format.py. +# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import six + + +class FormatParams(object): + max_line_length = 79 + wrap_paren = True + indent = 4 + hanging_indent = 'never' + use_black = False + + def __new__(cls, *args, **kwargs): + if not kwargs and len(args) == 1 and isinstance(args[0], cls): + return args[0] + self = object.__new__(cls) + # TODO: be more careful here + dicts = [] + for arg in args: + if arg is None: + pass + elif isinstance(arg, cls): + dicts.append(arg.__dict__) + else: + raise TypeError + if kwargs: + dicts.append(kwargs) + for kwargs in dicts: + for key, value in six.iteritems(kwargs): + if hasattr(self, key): + setattr(self, key, value) + else: + raise ValueError("bad kwarg %r" % (key,)) + return self + + +def fill(tokens, sep=(", ", ""), prefix="", suffix="", newline="\n", + max_line_length=80): + r""" + Given a sequences of strings, fill them into a single string with up to + ``max_line_length`` characters each. + + >>> fill(["'hello world'", "'hello two'"], + ... prefix=("print ", " "), suffix=(" \\", ""), + ... max_line_length=25) + "print 'hello world', \\\n 'hello two'\n" + + :param tokens: + Sequence of strings to fill. There must be at least one token. + :param sep: + Separator string to append to each token. If a 2-element tuple, then + indicates the separator between tokens and the separator after the last + token. Trailing whitespace is removed from each line before appending + the suffix, but not from between tokens on the same line. + :param prefix: + String to prepend at the beginning of each line. If a 2-element tuple, + then indicates the prefix for the first line and prefix for subsequent + lines. + :param suffix: + String to append to the end of each line. If a 2-element tuple, then + indicates the suffix for all lines except the last, and the suffix for + the last line. + :return: + Filled string. + """ + N = max_line_length + assert len(tokens) > 0 + if isinstance(prefix, tuple): + first_prefix, cont_prefix = prefix + else: + first_prefix = cont_prefix = prefix + if isinstance(suffix, tuple): + nonterm_suffix, term_suffix = suffix + else: + nonterm_suffix = term_suffix = suffix + if isinstance(sep, tuple): + nonterm_sep, term_sep = sep + else: + nonterm_sep = term_sep = sep + lines = [first_prefix + tokens[0]] + for token, is_last in zip(tokens[1:], [False]*(len(tokens)-2) + [True]): + suffix = term_suffix if is_last else nonterm_suffix + sep = (term_sep if is_last else nonterm_sep).rstrip() + # Does the next token fit? + if len(lines[-1] + nonterm_sep + token + sep + suffix) <= N: + # Yes; add it. + lines[-1] += nonterm_sep + token + else: + # No; break into new line. + lines[-1] += nonterm_sep.rstrip() + nonterm_suffix + newline + lines.append(cont_prefix + token) + lines[-1] += term_sep.rstrip() + term_suffix + newline + return ''.join(lines) + + +def pyfill(prefix, tokens, params=FormatParams()): + """ + Fill a Python statement. + + >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"]), end='') + print foo.bar, baz, quux, quuuuux + >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"], + ... FormatParams(max_line_length=15, hanging_indent='auto')), end='') + print (foo.bar, + baz, + quux, + quuuuux) + >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"], + ... FormatParams(max_line_length=14, hanging_indent='auto')), end='') + print ( + foo.bar, + baz, quux, + quuuuux) + + :param prefix: + Prefix for first line. + :param tokens: + Sequence of string tokens + :type params: + `FormatParams` + :rtype: + ``str`` + """ + N = params.max_line_length + if params.wrap_paren: + # Check how we will break up the tokens. + len_full = sum(len(tok) for tok in tokens) + 2 * (len(tokens)-1) + if len(prefix) + len_full <= N: + # The entire thing fits on one line; no parens needed. We check + # this first because breaking into lines adds paren overhead. + # + # Output looks like: + # from foo import abc, defgh, ijkl, mnopq, rst + return prefix + ", ".join(tokens) + "\n" + if params.hanging_indent == "never": + hanging_indent = False + elif params.hanging_indent == "always": + hanging_indent = True + elif params.hanging_indent == "auto": + # Decide automatically whether to do hanging-indent mode. If any + # line would exceed the max_line_length, then do hanging indent; + # else don't. + # + # In order to use non-hanging-indent mode, the first line would + # have an overhead of 2 because of "(" and ",". We check the + # longest token since even if the first token fits, we still want + # to avoid later tokens running over N. + maxtoklen = max(len(token) for token in tokens) + hanging_indent = (len(prefix) + maxtoklen + 2 > N) + else: + raise ValueError("bad params.hanging_indent=%r" + % (params.hanging_indent,)) + if hanging_indent: + # Hanging indent mode. We need a single opening paren and + # continue all imports on separate lines. + # + # Output looks like: + # from foo import ( + # abc, defgh, ijkl, + # mnopq, rst) + return (prefix + "(\n" + + fill(tokens, max_line_length=N, + prefix=(" " * params.indent), suffix=("", ")"))) + else: + # Non-hanging-indent mode. + # + # Output looks like: + # from foo import (abc, defgh, + # ijkl, mnopq, + # rst) + pprefix = prefix + "(" + return fill(tokens, max_line_length=N, + prefix=(pprefix, " " * len(pprefix)), suffix=("", ")")) + else: + raise NotImplementedError diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_idents.py b/.venv/lib/python3.8/site-packages/pyflyby/_idents.py new file mode 100644 index 0000000..24a2316 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_idents.py @@ -0,0 +1,256 @@ +# pyflyby/_idents.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from functools import total_ordering +from keyword import kwlist +import re +import six + +from pyflyby._util import cached_attribute, cmp + + +# Don't consider "print" a keyword, in order to be compatible with user code +# that uses "from __future__ import print_function". +_my_kwlist = list(kwlist) +if six.PY2: + _my_kwlist.remove("print") +_my_iskeyword = frozenset(_my_kwlist).__contains__ + + +# TODO: use DottedIdentifier.prefixes +def dotted_prefixes(dotted_name, reverse=False): + """ + Return the prefixes of a dotted name. + + >>> dotted_prefixes("aa.bb.cc") + ['aa', 'aa.bb', 'aa.bb.cc'] + + >>> dotted_prefixes("aa.bb.cc", reverse=True) + ['aa.bb.cc', 'aa.bb', 'aa'] + + :type dotted_name: + ``str`` + :param reverse: + If False (default), return shortest to longest. If True, return longest + to shortest. + :rtype: + ``list`` of ``str`` + """ + name_parts = dotted_name.split(".") + if reverse: + idxes = range(len(name_parts), 0, -1) + else: + idxes = range(1, len(name_parts)+1) + result = ['.'.join(name_parts[:i]) or '.' for i in idxes] + return result + + +_name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") +_dotted_name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*([.][a-zA-Z_][a-zA-Z0-9_]*)*$") +_dotted_name_prefix_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*([.][a-zA-Z_][a-zA-Z0-9_]*)*[.]?$") + + +def is_identifier(s, dotted=False, prefix=False): + """ + Return whether ``s`` is a valid Python identifier name. + + >>> is_identifier("foo") + True + + >>> is_identifier("foo+bar") + False + + >>> is_identifier("from") + False + + By default, we check whether ``s`` is a single valid identifier, meaning + dots are not allowed. If ``dotted=True``, then we check each dotted + component:: + + >>> is_identifier("foo.bar") + False + + >>> is_identifier("foo.bar", dotted=True) + True + + >>> is_identifier("foo..bar", dotted=True) + False + + >>> is_identifier("foo.from", dotted=True) + False + + By default, the string must comprise a valid identifier. If + ``prefix=True``, then allow strings that are prefixes of valid identifiers. + Prefix=False excludes the empty string, strings with a trailing dot, and + strings with a trailing keyword component, but prefix=True does not + exclude these. + + >>> is_identifier("foo.bar.", dotted=True) + False + + >>> is_identifier("foo.bar.", dotted=True, prefix=True) + True + + >>> is_identifier("foo.or", dotted=True) + False + + >>> is_identifier("foo.or", dotted=True, prefix=True) + True + + :type s: + ``str`` + :param dotted: + If ``False`` (default), then the input must be a single name such as + "foo". If ``True``, then the input can be a single name or a dotted name + such as "foo.bar.baz". + :param prefix: + If ``False`` (Default), then the input must be a valid identifier. If + ``True``, then the input can be a valid identifier or the prefix of a + valid identifier. + :rtype: + ``bool`` + """ + if not isinstance(s, six.string_types): + raise TypeError("is_identifier(): expected a string; got a %s" + % (type(s).__name__,)) + if six.PY3: + if prefix: + return is_identifier(s + '_', dotted=dotted, prefix=False) + if dotted: + return all(is_identifier(w, dotted=False) for w in s.split('.')) + return s.isidentifier() and not _my_iskeyword(s) + + if prefix: + if not s: + return True + if dotted: + return bool( + _dotted_name_prefix_re.match(s) and + not any(_my_iskeyword(w) for w in s.split(".")[:-1])) + else: + return bool(_name_re.match(s)) + else: + if dotted: + # Use a regular expression that works for dotted names. (As an + # alternate implementation, one could imagine calling + # all(is_identifier(w) for w in s.split(".")). We don't do that + # because s could be a long text string.) + return bool( + _dotted_name_re.match(s) and + not any(_my_iskeyword(w) for w in s.split("."))) + else: + return bool(_name_re.match(s) and not _my_iskeyword(s)) + + +def brace_identifiers(text): + """ + Parse a string and yield all tokens of the form "{some_token}". + + >>> list(brace_identifiers("{salutation}, {your_name}.")) + ['salutation', 'your_name'] + """ + if isinstance(text, bytes): + text = text.decode('utf-8', errors='replace') + for match in re.finditer("{([a-zA-Z_][a-zA-Z0-9_]*)}", text): + yield match.group(1) + + +class BadDottedIdentifierError(ValueError): + pass + + +# TODO: Use in various places, esp where e.g. dotted_prefixes is used. +@total_ordering +class DottedIdentifier(object): + def __new__(cls, arg): + if isinstance(arg, cls): + return arg + if isinstance(arg, six.string_types): + return cls._from_name(arg) + if isinstance(arg, (tuple, list)): + return cls._from_name(".".join(arg)) + raise TypeError("DottedIdentifier: unexpected %s" + % (type(arg).__name__,)) + + @classmethod + def _from_name(cls, name): + self = object.__new__(cls) + self.name = str(name) + if not is_identifier(self.name, dotted=True): + if len(self.name) > 20: + raise BadDottedIdentifierError("Invalid python symbol name") + else: + raise BadDottedIdentifierError("Invalid python symbol name %r" + % (name,)) + self.parts = tuple(self.name.split('.')) + return self + + @cached_attribute + def parent(self): + if len(self.parts) > 1: + return DottedIdentifier('.'.join(self.parts[:-1])) + else: + return None + + @cached_attribute + def prefixes(self): + parts = self.parts + idxes = range(1, len(parts)+1) + result = ['.'.join(parts[:i]) for i in idxes] + return tuple(DottedIdentifier(x) for x in result) + + def startswith(self, o): + o = type(self)(o) + return self.parts[:len(o.parts)] == o.parts + + def __getitem__(self, x): + return type(self)(self.parts[x]) + + def __len__(self): + return len(self.parts) + + def __iter__(self): + return (type(self)(x) for x in self.parts) + + def __add__(self, suffix): + return type(self)("%s.%s") % (self, suffix) + + def __str__(self): + return self.name + + def __repr__(self): + return "%s(%r)" % (type(self).__name__, self.name) + + def __hash__(self): + return hash(self.name) + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, DottedIdentifier): + return NotImplemented + return self.name == other.name + + def __ne__(self, other): + if self is other: + return False + if not isinstance(other, DottedIdentifier): + return NotImplemented + return self.name != other.name + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, DottedIdentifier): + return NotImplemented + return self.name < other.name + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, DottedIdentifier): + return NotImplemented + return cmp(self.name, other.name) diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_importclns.py b/.venv/lib/python3.8/site-packages/pyflyby/_importclns.py new file mode 100644 index 0000000..b8d0c16 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_importclns.py @@ -0,0 +1,631 @@ +# pyflyby/_importclns.py. +# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from collections import defaultdict +from functools import total_ordering +import six + +from pyflyby._flags import CompilerFlags +from pyflyby._idents import dotted_prefixes, is_identifier +from pyflyby._importstmt import (Import, ImportFormatParams, + ImportStatement, + NonImportStatementError) +from pyflyby._parse import PythonBlock +from pyflyby._util import (cached_attribute, cmp, partition, + stable_unique) + + +class NoSuchImportError(ValueError): + pass + + +class ConflictingImportsError(ValueError): + pass + +@total_ordering +class ImportSet(object): + r""" + Representation of a set of imports organized into import statements. + + >>> ImportSet(''' + ... from m1 import f1 + ... from m2 import f1 + ... from m1 import f2 + ... import m3.m4 as m34 + ... ''') + ImportSet(''' + from m1 import f1, f2 + from m2 import f1 + from m3 import m4 as m34 + ''') + + An ``ImportSet`` is an immutable data structure. + """ + + def __new__(cls, arg, ignore_nonimports=False, ignore_shadowed=False): + """ + Return as an `ImportSet`. + + :param ignore_nonimports: + If ``False``, complain about non-imports. If ``True``, ignore + non-imports. + :param ignore_shadowed: + Whether to ignore shadowed imports. If ``False``, then keep all + unique imports, even if they shadow each other. Note that an + ``ImportSet`` is unordered; an ``ImportSet`` with conflicts will only + be useful for very specific cases (e.g. set of imports to forget + from known-imports database), and not useful for outputting as code. + If ``ignore_shadowed`` is ``True``, then earlier shadowed imports are + ignored. + :rtype: + `ImportSet` + """ + if isinstance(arg, cls): + if ignore_shadowed: + return cls._from_imports(arg._importset, ignore_shadowed=True) + else: + return arg + return cls._from_args( + arg, + ignore_nonimports=ignore_nonimports, + ignore_shadowed=ignore_shadowed) + + @classmethod + def _from_imports(cls, imports, ignore_shadowed=False): + """ + :type imports: + Sequence of `Import` s + :param ignore_shadowed: + See `ImportSet.__new__`. + :rtype: + `ImportSet` + """ + # Canonicalize inputs. + imports = [Import(imp) for imp in imports] + if ignore_shadowed: + # Filter by overshadowed imports. Later imports take precedence. + by_import_as = {} + for imp in imports: + if imp.import_as == "*": + # Keep all unique star imports. + by_import_as[imp] = imp + else: + by_import_as[imp.import_as] = imp + filtered_imports = by_import_as.values() + else: + filtered_imports = imports + # Construct and return. + self = object.__new__(cls) + self._importset = frozenset(filtered_imports) + return self + + @classmethod + def _from_args(cls, args, ignore_nonimports=False, ignore_shadowed=False): + """ + :type args: + ``tuple`` or ``list`` of `ImportStatement` s, `PythonStatement` s, + `PythonBlock` s, `FileText`, and/or `Filename` s + :param ignore_nonimports: + If ``False``, complain about non-imports. If ``True``, ignore + non-imports. + :param ignore_shadowed: + See `ImportSet.__new__`. + :rtype: + `ImportSet` + """ + if not isinstance(args, (tuple, list)): + args = [args] + # Filter empty arguments to allow the subsequent optimizations to work + # more often. + args = [a for a in args if a] + if not args: + return cls._EMPTY + # If we only got one ``ImportSet``, just return it. + if len(args) == 1 and type(args[0]) is cls and not ignore_shadowed: + return args[0] + # Collect all `Import` s from arguments. + imports = [] + for arg in args: + if isinstance(arg, Import): + imports.append(arg) + elif isinstance(arg, ImportSet): + imports.extend(arg.imports) + elif isinstance(arg, ImportStatement): + imports.extend(arg.imports) + elif isinstance(arg, str) and is_identifier(arg, dotted=True): + imports.append(Import(arg)) + else: # PythonBlock, PythonStatement, Filename, FileText, str + block = PythonBlock(arg) + for statement in block.statements: + # Ignore comments/blanks. + if statement.is_comment_or_blank: + pass + elif statement.is_import: + imports.extend(ImportStatement(statement).imports) + elif ignore_nonimports: + pass + else: + raise NonImportStatementError( + "Got non-import statement %r" % (statement,)) + return cls._from_imports(imports, ignore_shadowed=ignore_shadowed) + + def with_imports(self, other): + """ + Return a new `ImportSet` that is the union of ``self`` and + ``new_imports``. + + >>> impset = ImportSet('from m import t1, t2, t3') + >>> impset.with_imports('import m.t2a as t2b') + ImportSet(''' + from m import t1, t2, t2a as t2b, t3 + ''') + + :type other: + `ImportSet` (or convertible) + :rtype: + `ImportSet` + """ + other = ImportSet(other) + return type(self)._from_imports(self._importset | other._importset) + + def without_imports(self, removals): + """ + Return a copy of self without the given imports. + + >>> imports = ImportSet('from m import t1, t2, t3, t4') + >>> imports.without_imports(['from m import t3']) + ImportSet(''' + from m import t1, t2, t4 + ''') + + :type removals: + `ImportSet` (or convertible) + :rtype: + `ImportSet` + """ + removals = ImportSet(removals) + if not removals: + return self # Optimization + # Preprocess star imports to remove. + star_module_removals = set( + [imp.split.module_name + for imp in removals if imp.split.member_name == "*"]) + # Filter imports. + new_imports = [] + for imp in self: + if imp in removals: + continue + if star_module_removals and imp.split.module_name: + prefixes = dotted_prefixes(imp.split.module_name) + if any(pfx in star_module_removals for pfx in prefixes): + continue + new_imports.append(imp) + # Return. + if len(new_imports) == len(self): + return self # Space optimization + return type(self)._from_imports(new_imports) + + @cached_attribute + def _by_module_name(self): + """ + :return: + (mapping from name to __future__ imports, + mapping from name to non-'from' imports, + mapping from name to 'from' imports) + """ + ftr_imports = defaultdict(set) + pkg_imports = defaultdict(set) + frm_imports = defaultdict(set) + for imp in self._importset: + module_name, member_name, import_as = imp.split + if module_name is None: + pkg_imports[member_name].add(imp) + elif module_name == '__future__': + ftr_imports[module_name].add(imp) + else: + frm_imports[module_name].add(imp) + return tuple( + dict( (k, frozenset(v)) + for k, v in six.iteritems(imports)) + for imports in [ftr_imports, pkg_imports, frm_imports]) + + def get_statements(self, separate_from_imports=True): + """ + Canonicalized `ImportStatement` s. + These have been merged by module and sorted. + + >>> importset = ImportSet(''' + ... import a, b as B, c, d.dd as DD + ... from __future__ import division + ... from _hello import there + ... from _hello import * + ... from _hello import world + ... ''') + + >>> for s in importset.get_statements(): print(s) + from __future__ import division + import a + import b as B + import c + from _hello import * + from _hello import there, world + from d import dd as DD + + :rtype: + ``tuple`` of `ImportStatement` s + """ + groups = self._by_module_name + if not separate_from_imports: + def union_dicts(*dicts): + result = {} + for label, dict in enumerate(dicts): + for k, v in six.iteritems(dict): + result[(k, label)] = v + return result + groups = [groups[0], union_dicts(*groups[1:])] + result = [] + for importgroup in groups: + for _, imports in sorted(importgroup.items()): + star_imports, nonstar_imports = ( + partition(imports, lambda imp: imp.import_as == "*")) + assert len(star_imports) <= 1 + if star_imports: + result.append(ImportStatement(star_imports)) + if nonstar_imports: + result.append(ImportStatement(sorted(nonstar_imports))) + return tuple(result) + + @cached_attribute + def statements(self): + """ + Canonicalized `ImportStatement` s. + These have been merged by module and sorted. + + :rtype: + ``tuple`` of `ImportStatement` s + """ + return self.get_statements(separate_from_imports=True) + + @cached_attribute + def imports(self): + """ + Canonicalized imports, in the same order as ``self.statements``. + + :rtype: + ``tuple`` of `Import` s + """ + return tuple( + imp + for importgroup in self._by_module_name + for _, imports in sorted(importgroup.items()) + for imp in sorted(imports)) + + @cached_attribute + def by_import_as(self): + """ + Map from ``import_as`` to `Import`. + + >>> ImportSet('from aa.bb import cc as dd').by_import_as + {'dd': (Import('from aa.bb import cc as dd'),)} + + :rtype: + ``dict`` mapping from ``str`` to tuple of `Import` s + """ + d = defaultdict(list) + for imp in self._importset: + d[imp.import_as].append(imp) + return dict( (k, tuple(sorted(stable_unique(v)))) + for k, v in six.iteritems(d) ) + + @cached_attribute + def member_names(self): + r""" + Map from parent module/package ``fullname`` to known member names. + + >>> impset = ImportSet("import numpy.linalg.info\nfrom sys import exit as EXIT") + >>> import pprint + >>> pprint.pprint(impset.member_names) + {'': ('EXIT', 'numpy', 'sys'), + 'numpy': ('linalg',), + 'numpy.linalg': ('info',), + 'sys': ('exit',)} + + This is used by the autoimporter module for implementing tab completion. + + :rtype: + ``dict`` mapping from ``str`` to tuple of ``str`` + """ + d = defaultdict(set) + for imp in self._importset: + if '.' not in imp.import_as: + d[""].add(imp.import_as) + prefixes = dotted_prefixes(imp.fullname) + d[""].add(prefixes[0]) + for prefix in prefixes[1:]: + splt = prefix.rsplit(".", 1) + d[splt[0]].add(splt[1]) + return dict( (k, tuple(sorted(v))) + for k, v in six.iteritems(d) ) + + @cached_attribute + def conflicting_imports(self): + r""" + Returns imports that conflict with each other. + + >>> ImportSet('import b\nfrom f import a as b\n').conflicting_imports + ('b',) + + >>> ImportSet('import b\nfrom f import a\n').conflicting_imports + () + + :rtype: + ``bool`` + """ + return tuple( + k + for k, v in six.iteritems(self.by_import_as) + if len(v) > 1 and k != "*") + + @cached_attribute + def flags(self): + """ + If this contains __future__ imports, then the bitwise-ORed of the + compiler_flag values associated with the features. Otherwise, 0. + """ + imports = self._by_module_name[0].get("__future__", []) + return CompilerFlags(*[imp.flags for imp in imports]) + + def __repr__(self): + printed = self.pretty_print(allow_conflicts=True) + lines = "".join(" "+line for line in printed.splitlines(True)) + return "%s('''\n%s''')" % (type(self).__name__, lines) + + def pretty_print(self, params=None, allow_conflicts=False): + """ + Pretty-print a block of import statements into a single string. + + :type params: + `ImportFormatParams` + :rtype: + ``str`` + """ + params = ImportFormatParams(params) + # TODO: instead of complaining about conflicts, just filter out the + # shadowed imports at construction time. + if not allow_conflicts and self.conflicting_imports: + raise ConflictingImportsError( + "Refusing to pretty-print because of conflicting imports: " + + '; '.join( + "%r imported as %r" % ( + [imp.fullname for imp in self.by_import_as[i]], i) + for i in self.conflicting_imports)) + from_spaces = max(1, params.from_spaces) + def do_align(statement): + return statement.fromname != '__future__' or params.align_future + def pp(statement, import_column): + if do_align(statement): + return statement.pretty_print( + params=params, import_column=import_column, + from_spaces=from_spaces) + else: + return statement.pretty_print( + params=params, import_column=None, from_spaces=1) + statements = self.get_statements( + separate_from_imports=params.separate_from_imports) + def isint(x): return isinstance(x, int) and not isinstance(x, bool) + if not statements: + import_column = None + elif isinstance(params.align_imports, bool): + if params.align_imports: + fromimp_stmts = [ + s for s in statements if s.fromname and do_align(s)] + if fromimp_stmts: + import_column = ( + max(len(s.fromname) for s in fromimp_stmts) + + from_spaces + 5) + else: + import_column = None + else: + import_column = None + elif isinstance(params.align_imports, int): + import_column = params.align_imports + elif isinstance(params.align_imports, (tuple, list, set)): + # If given a set of candidate alignment columns, then try each + # alignment column and pick the one that yields the fewest number + # of output lines. + if not all(isinstance(x, int) for x in params.align_imports): + raise TypeError("expected set of integers; got %r" + % (params.align_imports,)) + candidates = sorted(set(params.align_imports)) + if len(candidates) == 0: + raise ValueError("list of zero candidate alignment columns specified") + elif len(candidates) == 1: + # Optimization. + import_column = next(iter(candidates)) + else: + def argmin(map): + items = iter(sorted(map.items())) + min_k, min_v = next(items) + for k, v in items: + if v < min_v: + min_k = k + min_v = v + return min_k + def count_lines(import_column): + return sum( + s.pretty_print( + params=params, import_column=import_column, + from_spaces=from_spaces).count("\n") + for s in statements) + # Construct a map from alignment column to total number of + # lines. + col2length = dict((c, count_lines(c)) for c in candidates) + # Pick the column that yields the fewest lines. Break ties by + # picking the smaller column. + import_column = argmin(col2length) + else: + raise TypeError( + "ImportSet.pretty_print(): unexpected params.align_imports type %s" + % (type(params.align_imports).__name__,)) + return ''.join(pp(statement, import_column) for statement in statements) + + def __contains__(self, x): + return x in self._importset + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, ImportSet): + return NotImplemented + return self._importset == other._importset + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, ImportSet): + return NotImplemented + return self._importset < other._importset + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, ImportSet): + return NotImplemented + return cmp(self._importset, other._importset) + + def __hash__(self): + return hash(self._importset) + + def __len__(self): + return len(self.imports) + + def __iter__(self): + return iter(self.imports) + + +ImportSet._EMPTY = ImportSet._from_imports([]) + + +@total_ordering +class ImportMap(object): + r""" + A map from import fullname identifier to fullname identifier. + + >>> ImportMap({'a.b': 'aa.bb', 'a.b.c': 'aa.bb.cc'}) + ImportMap({'a.b': 'aa.bb', 'a.b.c': 'aa.bb.cc'}) + + An ``ImportMap`` is an immutable data structure. + """ + + def __new__(cls, arg): + if isinstance(arg, cls): + return arg + if isinstance(arg, (tuple, list)): + return cls._merge(arg) + if isinstance(arg, dict): + if not len(arg): + return cls._EMPTY + return cls._from_map(arg) + else: + raise TypeError("ImportMap: expected a dict, not a %s" + % (type(arg).__name__,)) + + @classmethod + def _from_map(cls, arg): + data = dict((Import(k).fullname, Import(v).fullname) + for k, v in arg.items()) + self = object.__new__(cls) + self._data = data + return self + + @classmethod + def _merge(cls, maps): + maps = [cls(m) for m in maps] + maps = [m for m in maps if m] + if not maps: + return cls._EMPTY + data = {} + for map in maps: + data.update(map._data) + return cls(data) + + def __getitem__(self, k): + k = Import(k).fullname + return self._data.__getitem__(k) + + def __iter__(self): + return iter(self._data) + + def items(self): + return self._data.items() + + def iteritems(self): + return six.iteritems(self._data) + + def iterkeys(self): + return six.iterkeys(self._data) + + def keys(self): + return self._data.keys() + + def values(self): + return self._data.values() + + def __len__(self): + return len(self._data) + + def without_imports(self, removals): + """ + Return a copy of self without the given imports. + Matches both keys and values. + """ + removals = ImportSet(removals) + if not removals: + return self # Optimization + cls = type(self) + result = [(k, v) for k, v in self._data.items() + if Import(k) not in removals and Import(v) not in removals] + if len(result) == len(self._data): + return self # Space optimization + return cls(dict(result)) + + def __repr__(self): + s = ", ".join("%r: %r" % (k,v) for k,v in sorted(self.items())) + return "ImportMap({%s})" % s + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, ImportMap): + return NotImplemented + return self._data == other._data + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, ImportMap): + return NotImplemented + return self._data < other._data + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, ImportMap): + return NotImplemented + return cmp(self._data, other._data) + + def __hash__(self): + h = hash(self._data) + self.__hash__ = lambda: h + return h + + +ImportMap._EMPTY = ImportMap._from_map({}) diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_importdb.py b/.venv/lib/python3.8/site-packages/pyflyby/_importdb.py new file mode 100644 index 0000000..e8973fd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_importdb.py @@ -0,0 +1,581 @@ +# pyflyby/_importdb.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from collections import defaultdict +import os +import re +import six + +from pyflyby._file import Filename, expand_py_files_from_args +from pyflyby._idents import dotted_prefixes +from pyflyby._importclns import ImportMap, ImportSet +from pyflyby._importstmt import Import, ImportStatement +from pyflyby._log import logger +from pyflyby._parse import PythonBlock +from pyflyby._util import cached_attribute, memoize, stable_unique + + +@memoize +def _find_etc_dirs(): + result = [] + dirs = Filename(__file__).real.dir.ancestors[:-1] + for dir in dirs: + candidate = dir / "etc/pyflyby" + if candidate.isdir: + result.append(candidate) + break + global_dir = Filename("/etc/pyflyby") + if global_dir.exists: + result.append(global_dir) + return result + + +def _get_env_var(env_var_name, default): + ''' + Get an environment variable and split on ":", replacing ``-`` with the + default. + ''' + assert re.match("^[A-Z_]+$", env_var_name) + assert isinstance(default, (tuple, list)) + value = list(filter(None, os.environ.get(env_var_name, '').split(':'))) + if not value: + return default + # Replace '-' with ``default`` + try: + idx = value.index('-') + except ValueError: + pass + else: + value[idx:idx+1] = default + return value + + +def _get_python_path(env_var_name, default_path, target_dirname): + ''' + Expand an environment variable specifying pyflyby input config files. + + - Default to ``default_path`` if the environment variable is undefined. + - Process colon delimiters. + - Replace "-" with ``default_path``. + - Expand triple dots. + - Recursively traverse directories. + + :rtype: + ``tuple`` of ``Filename`` s + ''' + pathnames = _get_env_var(env_var_name, default_path) + if pathnames == ["EMPTY"]: + # The special code PYFLYBY_PATH=EMPTY means we intentionally want to + # use an empty PYFLYBY_PATH (and don't fall back to the default path, + # nor warn about an empty path). + return () + for p in pathnames: + if re.match("/|[.]/|[.][.][.]/|~/", p): + continue + raise ValueError( + "{env_var_name} components should start with / or ./ or ~/ or .../. " + "Use {env_var_name}=./{p} instead of {env_var_name}={p} if you really " + "want to use the current directory." + .format(env_var_name=env_var_name, p=p)) + pathnames = [os.path.expanduser(p) for p in pathnames] + pathnames = _expand_tripledots(pathnames, target_dirname) + pathnames = [Filename(fn) for fn in pathnames] + pathnames = stable_unique(pathnames) + pathnames = expand_py_files_from_args(pathnames) + if not pathnames: + logger.warning( + "No import libraries found (%s=%r, default=%r)" + % (env_var_name, os.environ.get(env_var_name), default_path)) + return tuple(pathnames) + + +# TODO: stop memoizing here after using StatCache. Actually just inline into +# _ancestors_on_same_partition +@memoize +def _get_st_dev(filename): + filename = Filename(filename) + try: + return os.stat(str(filename)).st_dev + except OSError: + return None + + +def _ancestors_on_same_partition(filename): + """ + Generate ancestors of ``filename`` that exist and are on the same partition + as the first existing ancestor of ``filename``. + + For example, suppose a partition is mounted on /u/homer; /u is a different + partition. Suppose /u/homer/aa exists but /u/homer/aa/bb does not exist. + Then:: + + >>> _ancestors_on_same_partition(Filename("/u/homer/aa/bb/cc")) # doctest: +SKIP + [Filename("/u/homer", Filename("/u/homer/aa")] + + :rtype: + ``list`` of ``Filename`` + """ + result = [] + dev = None + for f in filename.ancestors: + this_dev = _get_st_dev(f) + if this_dev is None: + continue + if dev is None: + dev = this_dev + elif dev != this_dev: + break + result.append(f) + return result + + +def _expand_tripledots(pathnames, target_dirname): + """ + Expand pathnames of the form ``".../foo/bar"`` as "../../foo/bar", + "../foo/bar", "./foo/bar" etc., up to the oldest ancestor with the same + st_dev. + + For example, suppose a partition is mounted on /u/homer; /u is a different + partition. Then:: + + >>> _expand_tripledots(["/foo", ".../tt"], "/u/homer/aa") # doctest: +SKIP + [Filename("/foo"), Filename("/u/homer/tt"), Filename("/u/homer/aa/tt")] + + :type pathnames: + sequence of ``str`` (not ``Filename``) + :type target_dirname: + `Filename` + :rtype: + ``list`` of `Filename` + """ + target_dirname = Filename(target_dirname) + if not isinstance(pathnames, (tuple, list)): + pathnames = [pathnames] + result = [] + for pathname in pathnames: + if not pathname.startswith(".../"): + result.append(Filename(pathname)) + continue + suffix = pathname[4:] + expanded = [ + p / suffix for p in _ancestors_on_same_partition(target_dirname) ] + result.extend(expanded[::-1]) + return result + + +class ImportDB(object): + """ + A database of known, mandatory, canonical imports. + + @iattr known_imports: + Set of known imports. For use by tidy-imports and autoimporter. + @iattr mandatory_imports: + Set of imports that must be added by tidy-imports. + @iattr canonical_imports: + Map of imports that tidy-imports transforms on every run. + @iattr forget_imports: + Set of imports to remove from known_imports, mandatory_imports, + canonical_imports. + """ + + def __new__(cls, *args): + if len(args) != 1: + raise TypeError + arg, = args + if isinstance(arg, cls): + return arg + if isinstance(arg, ImportSet): + return cls._from_data(arg, [], [], []) + return cls._from_args(arg) # PythonBlock, Filename, etc + + + _default_cache = {} + + @classmethod + def clear_default_cache(cls): + """ + Clear the class cache of default ImportDBs. + + Subsequent calls to ImportDB.get_default() will not reuse previously + cached results. Existing ImportDB instances are not affected by this + call. + """ + if cls._default_cache: + if logger.debug_enabled: + allpyfiles = set() + for tup in cls._default_cache: + if tup[0] != 2: + continue + for tup2 in tup[1:]: + for f in tup2: + assert isinstance(f, Filename) + if f.ext == '.py': + allpyfiles.add(f) + nfiles = len(allpyfiles) + logger.debug("ImportDB: Clearing default cache of %d files", + nfiles) + cls._default_cache.clear() + + @classmethod + def get_default(cls, target_filename): + """ + Return the default import library for the given target filename. + + This will read various .../.pyflyby files as specified by + $PYFLYBY_PATH as well as older deprecated environment variables. + + Memoized. + + :param target_filename: + The target filename for which to get the import database. Note that + the target filename itself is not read. Instead, the target + filename is relevant because we look for .../.pyflyby based on the + target filename. + :rtype: + `ImportDB` + """ + # We're going to canonicalize target_filenames in a number of steps. + # At each step, see if we've seen the input so far. We do the cache + # checking incrementally since the steps involve syscalls. Since this + # is going to potentially be executed inside the IPython interactive + # loop, we cache as much as possible. + # TODO: Consider refreshing periodically. Check if files have + # been touched, and if so, return new data. Check file timestamps at + # most once every 60 seconds. + cache_keys = [] + target_filename = Filename(target_filename or ".") + if target_filename.startswith("/dev"): + target_filename = Filename(".") + target_dirname = target_filename + # TODO: with StatCache + while True: + cache_keys.append((1, + target_dirname, + os.getenv("PYFLYBY_PATH"), + os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"), + os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH"))) + try: + return cls._default_cache[cache_keys[-1]] + except KeyError: + pass + if target_dirname.isdir: + break + target_dirname = target_dirname.dir + target_dirname = target_dirname.real + if target_dirname != cache_keys[-1][0]: + cache_keys.append((1, + target_dirname, + os.getenv("PYFLYBY_PATH"), + os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"), + os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH"))) + try: + return cls._default_cache[cache_keys[-1]] + except KeyError: + pass + DEFAULT_PYFLYBY_PATH = [] + DEFAULT_PYFLYBY_PATH += [str(p) for p in _find_etc_dirs()] + DEFAULT_PYFLYBY_PATH += [ + ".../.pyflyby", + "~/.pyflyby", + ] + logger.debug("DEFAULT_PYFLYBY_PATH=%s", DEFAULT_PYFLYBY_PATH) + filenames = _get_python_path("PYFLYBY_PATH", DEFAULT_PYFLYBY_PATH, + target_dirname) + mandatory_imports_filenames = () + if "SUPPORT DEPRECATED BEHAVIOR": + PYFLYBY_PATH = _get_env_var("PYFLYBY_PATH", DEFAULT_PYFLYBY_PATH) + # If the old deprecated environment variables are set, then heed + # them. + if os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"): + # Use PYFLYBY_PATH as the default for + # PYFLYBY_KNOWN_IMPORTS_PATH. Note that the default is + # relevant even though we only enter this code path when the + # variable is set to anything, because the env var can + # reference "-" to include the default. + # Before pyflyby version 0.8, the default value would have + # been + # [d/"known_imports" for d in PYFLYBY_PATH] + # Instead of using that, we just use PYFLYBY_PATH directly as + # the default. This simplifies things and avoids need for a + # "known_imports=>." symlink for backwards compatibility. It + # means that ~/.pyflyby/**/*.py (as opposed to only + # ~/.pyflyby/known_imports/**/*.py) would be included. + # Although this differs slightly from the old behavior, it + # matches the behavior of the newer PYFLYBY_PATH; matching the + # new behavior seems higher utility than exactly matching the + # old behavior. Files under ~/.pyflyby/mandatory_imports will + # be included in known_imports as well, but that should not + # cause any problems. + default_path = PYFLYBY_PATH + # Expand $PYFLYBY_KNOWN_IMPORTS_PATH. + filenames = _get_python_path( + "PYFLYBY_KNOWN_IMPORTS_PATH", default_path, target_dirname) + logger.debug( + "The environment variable PYFLYBY_KNOWN_IMPORTS_PATH is deprecated. " + "Use PYFLYBY_PATH.") + if os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH"): + # Compute the "default" path. + # Note that we still calculate the erstwhile default value, + # even though it's no longer the defaults, in order to still + # allow the "-" in the variable. + default_path = [ + os.path.join(d,"mandatory_imports") for d in PYFLYBY_PATH] + # Expand $PYFLYBY_MANDATORY_IMPORTS_PATH. + mandatory_imports_filenames = _get_python_path( + "PYFLYBY_MANDATORY_IMPORTS_PATH", + default_path, target_dirname) + logger.debug( + "The environment variable PYFLYBY_MANDATORY_IMPORTS_PATH is deprecated. " + "Use PYFLYBY_PATH and write __mandatory_imports__=['...'] in your files.") + cache_keys.append((2, filenames, mandatory_imports_filenames)) + try: + return cls._default_cache[cache_keys[-1]] + except KeyError: + pass + result = cls._from_filenames(filenames, mandatory_imports_filenames) + for k in cache_keys: + cls._default_cache[k] = result + return result + + @classmethod + def interpret_arg(cls, arg, target_filename): + if arg is None: + return cls.get_default(target_filename) + else: + return cls(arg) + + @classmethod + def _from_data(cls, known_imports, mandatory_imports, + canonical_imports, forget_imports): + self = object.__new__(cls) + self.forget_imports = ImportSet(forget_imports ) + self.known_imports = ImportSet(known_imports ).without_imports(forget_imports) + self.mandatory_imports = ImportSet(mandatory_imports).without_imports(forget_imports) + # TODO: provide more fine-grained control about canonical_imports. + self.canonical_imports = ImportMap(canonical_imports).without_imports(forget_imports) + return self + + @classmethod + def _from_args(cls, args): + # TODO: support merging input ImportDBs. For now we support + # `PythonBlock` s and convertibles such as `Filename`. + return cls._from_code(args) + + @classmethod + def _from_code(cls, blocks, + _mandatory_imports_blocks_deprecated=(), + _forget_imports_blocks_deprecated=(), + ): + """ + Load an import database from code. + + >>> ImportDB._from_code(''' + ... import foo, bar as barf + ... from xx import yy + ... __mandatory_imports__ = ['__future__.division', + ... 'import aa . bb . cc as dd'] + ... __forget_imports__ = ['xx.yy', 'from xx import zz'] + ... __canonical_imports__ = {'bad.baad': 'good.goood'} + ... ''') + ImportDB(''' + import bar as barf + import foo + + __mandatory_imports__ = [ + 'from __future__ import division', + 'from aa.bb import cc as dd', + ] + + __canonical_imports__ = { + 'bad.baad': 'good.goood', + } + + __forget_imports__ = [ + 'from xx import yy', + 'from xx import zz', + ] + ''') + + :rtype: + `ImportDB` + """ + if not isinstance(blocks, (tuple, list)): + blocks = [blocks] + if not isinstance(_mandatory_imports_blocks_deprecated, (tuple, list)): + _mandatory_imports_blocks_deprecated = [_mandatory_imports_blocks_deprecated] + if not isinstance(_forget_imports_blocks_deprecated, (tuple, list)): + _forget_imports_blocks_deprecated = [_forget_imports_blocks_deprecated] + known_imports = [] + mandatory_imports = [] + canonical_imports = [] + forget_imports = [] + blocks = [PythonBlock(b) for b in blocks] + for block in blocks: + for statement in block.statements: + if statement.is_comment_or_blank: + continue + if statement.is_import: + known_imports.extend(ImportStatement(statement).imports) + continue + try: + name, value = statement.get_assignment_literal_value() + if name == "__mandatory_imports__": + mandatory_imports.append(cls._parse_import_set(value)) + elif name == "__canonical_imports__": + canonical_imports.append(cls._parse_import_map(value)) + elif name == "__forget_imports__": + forget_imports.append(cls._parse_import_set(value)) + else: + raise ValueError( + "Unknown assignment to %r (expected one of " + "__mandatory_imports__, __canonical_imports__, " + "__forget_imports__)" % (name,)) + except ValueError as e: + raise ValueError( + "While parsing %s: error in %r: %s" + % (block.filename, statement, e)) + for block in _mandatory_imports_blocks_deprecated: + mandatory_imports.append(ImportSet(block)) + for block in _forget_imports_blocks_deprecated: + forget_imports.append(ImportSet(block)) + return cls._from_data(known_imports, + mandatory_imports, + canonical_imports, + forget_imports) + + @classmethod + def _from_filenames(cls, filenames, _mandatory_filenames_deprecated=[]): + """ + Load an import database from filenames. + + This function exists to support deprecated behavior. + When we stop supporting the old behavior, we will delete this function. + + :type filenames: + Sequence of `Filename` s + :param filenames: + Filenames of files to read. + :rtype: + `ImportDB` + """ + if not isinstance(filenames, (tuple, list)): + filenames = [filenames] + filenames = [Filename(f) for f in filenames] + logger.debug("ImportDB: loading [%s], mandatory=[%s]", + ', '.join(map(str, filenames)), + ', '.join(map(str, _mandatory_filenames_deprecated))) + if "SUPPORT DEPRECATED BEHAVIOR": + # Before 2014-10, pyflyby read the following: + # * known_imports from $PYFLYBY_PATH/known_imports/**/*.py or + # $PYFLYBY_KNOWN_IMPORTS_PATH/**/*.py, + # * mandatory_imports from $PYFLYBY_PATH/mandatory_imports/**/*.py or + # $PYFLYBY_MANDATORY_IMPORTS_PATH/**/*.py, and + # * forget_imports from $PYFLYBY_PATH/known_imports/**/__remove__.py + # After 2014-10, pyflyby reads the following: + # * $PYFLYBY_PATH/**/*.py + # (with directives inside the file) + # For backwards compatibility, for now we continue supporting the + # old, deprecated behavior. + blocks = [] + mandatory_imports_blocks = [ + Filename(f) for f in _mandatory_filenames_deprecated] + forget_imports_blocks = [] + for filename in filenames: + if filename.base == "__remove__.py": + forget_imports_blocks.append(filename) + elif "mandatory_imports" in str(filename).split("/"): + mandatory_imports_blocks.append(filename) + else: + blocks.append(filename) + return cls._from_code( + blocks, mandatory_imports_blocks, forget_imports_blocks) + else: + return cls._from_code(filenames) + + @classmethod + def _parse_import_set(cls, arg): + if isinstance(arg, six.string_types): + arg = [arg] + if not isinstance(arg, (tuple, list)): + raise ValueError("Expected a list, not a %s" % (type(arg).__name__,)) + for item in arg: + if not isinstance(item, six.string_types): + raise ValueError( + "Expected a list of str, not %s" % (type(item).__name__,)) + return ImportSet(arg) + + @classmethod + def _parse_import_map(cls, arg): + if isinstance(arg, six.string_types): + arg = [arg] + if not isinstance(arg, dict): + raise ValueError("Expected a dict, not a %s" % (type(arg).__name__,)) + for k, v in arg.items(): + if not isinstance(k, six.string_types): + raise ValueError( + "Expected a dict of str, not %s" % (type(k).__name__,)) + if not isinstance(v, six.string_types): + raise ValueError( + "Expected a dict of str, not %s" % (type(v).__name__,)) + return ImportMap(arg) + + @cached_attribute + def by_fullname_or_import_as(self): + """ + Map from ``fullname`` and ``import_as`` to `Import` s. + + >>> import pprint + >>> db = ImportDB('from aa.bb import cc as dd') + >>> pprint.pprint(db.by_fullname_or_import_as) + {'aa': (Import('import aa'),), + 'aa.bb': (Import('import aa.bb'),), + 'dd': (Import('from aa.bb import cc as dd'),)} + + :rtype: + ``dict`` mapping from ``str`` to tuple of `Import` s + """ + # TODO: make known_imports take into account the below forget_imports, + # then move this function into ImportSet + d = defaultdict(set) + for imp in self.known_imports.imports: + # Given an import like "from foo.bar import quux as QUUX", add the + # following entries: + # - "QUUX" => "from foo.bar import quux as QUUX" + # - "foo.bar" => "import foo.bar" + # - "foo" => "import foo" + # We don't include an entry labeled "quux" because the user has + # implied he doesn't want to pollute the global namespace with + # "quux", only "QUUX". + d[imp.import_as].add(imp) + for prefix in dotted_prefixes(imp.fullname)[:-1]: + d[prefix].add(Import.from_parts(prefix, prefix)) + return dict( (k, tuple(sorted(v - set(self.forget_imports.imports)))) + for k, v in six.iteritems(d)) + + def __repr__(self): + printed = self.pretty_print() + lines = "".join(" "+line for line in printed.splitlines(True)) + return "%s('''\n%s''')" % (type(self).__name__, lines) + + def pretty_print(self): + s = self.known_imports.pretty_print() + if self.mandatory_imports: + s += "\n__mandatory_imports__ = [\n" + for imp in self.mandatory_imports.imports: + s += " '%s',\n" % imp + s += "]\n" + if self.canonical_imports: + s += "\n__canonical_imports__ = {\n" + for k, v in sorted(self.canonical_imports.items()): + s += " '%s': '%s',\n" % (k, v) + s += "}\n" + if self.forget_imports: + s += "\n__forget_imports__ = [\n" + for imp in self.forget_imports.imports: + s += " '%s',\n" % imp + s += "]\n" + return s diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_imports2s.py b/.venv/lib/python3.8/site-packages/pyflyby/_imports2s.py new file mode 100644 index 0000000..b81450c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_imports2s.py @@ -0,0 +1,606 @@ +# pyflyby/_imports2s.py. +# Copyright (C) 2011-2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from pyflyby._autoimp import scan_for_import_issues +from pyflyby._file import FileText, Filename +from pyflyby._flags import CompilerFlags +from pyflyby._importclns import ImportSet, NoSuchImportError +from pyflyby._importdb import ImportDB +from pyflyby._importstmt import ImportFormatParams, ImportStatement +from pyflyby._log import logger +from pyflyby._parse import PythonBlock +from pyflyby._util import ImportPathCtx, Inf, NullCtx, memoize +import re +from six import exec_ + + +class SourceToSourceTransformationBase(object): + def __new__(cls, arg): + if isinstance(arg, cls): + return arg + if isinstance(arg, (PythonBlock, FileText, Filename, str)): + return cls._from_source_code(arg) + raise TypeError("%s: got unexpected %s" + % (cls.__name__, type(arg).__name__)) + + @classmethod + def _from_source_code(cls, codeblock): + self = object.__new__(cls) + self.input = PythonBlock(codeblock) + self.preprocess() + return self + + def preprocess(self): + pass + + def pretty_print(self, params=None): + raise NotImplementedError + + def output(self, params=None): + """ + Pretty-print and return as a `PythonBlock`. + + :rtype: + `PythonBlock` + """ + result = self.pretty_print(params=params) + result = PythonBlock(result, filename=self.input.filename) + return result + + +class SourceToSourceTransformation(SourceToSourceTransformationBase): + def preprocess(self): + self.output = self.input + + def pretty_print(self, params=None): + return self.output.text + + +class SourceToSourceImportBlockTransformation(SourceToSourceTransformationBase): + def preprocess(self): + self.importset = ImportSet(self.input, ignore_shadowed=True) + + def pretty_print(self, params=None): + params = ImportFormatParams(params) + return self.importset.pretty_print(params) + + +class LineNumberNotFoundError(Exception): + pass + +class LineNumberAmbiguousError(Exception): + pass + +class NoImportBlockError(Exception): + pass + +class ImportAlreadyExistsError(Exception): + pass + +class SourceToSourceFileImportsTransformation(SourceToSourceTransformationBase): + def preprocess(self): + # Group into blocks of imports and non-imports. Get a sequence of all + # imports for the transformers to operate on. + self.blocks = [] + self.import_blocks = [] + for is_imports, subblock in self.input.groupby(lambda ps: ps.is_import): + if is_imports: + trans = SourceToSourceImportBlockTransformation(subblock) + self.import_blocks.append(trans) + else: + trans = SourceToSourceTransformation(subblock) + self.blocks.append(trans) + + def pretty_print(self, params=None): + params = ImportFormatParams(params) + result = [block.pretty_print(params=params) for block in self.blocks] + return FileText.concatenate(result) + + def find_import_block_by_lineno(self, lineno): + """ + Find the import block containing the given line number. + + :type lineno: + ``int`` + :rtype: + `SourceToSourceImportBlockTransformation` + """ + results = [ + b + for b in self.import_blocks + if b.input.startpos.lineno <= lineno <= b.input.endpos.lineno] + if len(results) == 0: + raise LineNumberNotFoundError(lineno) + if len(results) > 1: + raise LineNumberAmbiguousError(lineno) + return results[0] + + def remove_import(self, imp, lineno): + """ + Remove the given import. + + :type imp: + `Import` + :type lineno: + ``int`` + """ + block = self.find_import_block_by_lineno(lineno) + try: + imports = block.importset.by_import_as[imp.import_as] + except KeyError: + raise NoSuchImportError + assert len(imports) + if len(imports) > 1: + raise Exception("Multiple imports to remove: %r" % (imports,)) + imp = imports[0] + block.importset = block.importset.without_imports([imp]) + return imp + + def select_import_block_by_closest_prefix_match(self, imp, max_lineno): + """ + Heuristically pick an import block that ``imp`` "fits" best into. The + selection is based on the block that contains the import with the + longest common prefix. + + :type imp: + `Import` + :param max_lineno: + Only return import blocks earlier than ``max_lineno``. + :rtype: + `SourceToSourceImportBlockTransformation` + """ + # Create a data structure that annotates blocks with data by which + # we'll sort. + annotated_blocks = [ + ( (max([0] + [len(imp.prefix_match(oimp)) + for oimp in block.importset.imports]), + block.input.endpos.lineno), + block ) + for block in self.import_blocks + if block.input.endpos.lineno <= max_lineno ] + if not annotated_blocks: + raise NoImportBlockError() + annotated_blocks.sort() + if imp.split.module_name == '__future__': + # For __future__ imports, only add to an existing block that + # already contains __future__ import(s). If there are no existing + # import blocks containing __future__, don't return any result + # here, so that we will add a new one at the top. + if not annotated_blocks[-1][0][0] > 0: + raise NoImportBlockError + return annotated_blocks[-1][1] + + def insert_new_blocks_after_comments(self, blocks): + blocks = [SourceToSourceTransformationBase(block) for block in blocks] + if isinstance(self.blocks[0], SourceToSourceImportBlockTransformation): + # Kludge. We should add an "output" attribute to + # SourceToSourceImportBlockTransformation and enumerate over that, + # instead of enumerating over the input below. + self.blocks[0:0] = blocks + return + # Get the "statements" in the first block. + statements = self.blocks[0].input.statements + # Find the insertion point. + for idx, statement in enumerate(statements): + if not statement.is_comment_or_blank_or_string_literal: + if idx == 0: + # First block starts with a noncomment, so insert before + # it. + self.blocks[0:0] = blocks + else: + # Found a non-comment after comment, so break it up and + # insert in the middle. + self.blocks[:1] = ( + [SourceToSourceTransformation( + PythonBlock.concatenate(statements[:idx], + assume_contiguous=True))] + + blocks + + [SourceToSourceTransformation( + PythonBlock.concatenate(statements[idx:], + assume_contiguous=True))]) + break + else: + # First block is entirely comments, so just insert after it. + self.blocks[1:1] = blocks + + def insert_new_import_block(self): + """ + Adds a new empty imports block. It is added before the first + non-comment statement. Intended to be used when the input contains no + import blocks (before uses). + """ + block = SourceToSourceImportBlockTransformation("") + sepblock = SourceToSourceTransformation("") + sepblock.output = PythonBlock("\n") + self.insert_new_blocks_after_comments([block, sepblock]) + self.import_blocks.insert(0, block) + return block + + def add_import(self, imp, lineno=Inf): + """ + Add the specified import. Picks an existing global import block to + add to, or if none found, creates a new one near the beginning of the + module. + + :type imp: + `Import` + :param lineno: + Line before which to add the import. ``Inf`` means no constraint. + """ + try: + block = self.select_import_block_by_closest_prefix_match( + imp, lineno) + except NoImportBlockError: + block = self.insert_new_import_block() + if imp in block.importset.imports: + raise ImportAlreadyExistsError(imp) + block.importset = block.importset.with_imports([imp]) + + +def reformat_import_statements(codeblock, params=None): + r""" + Reformat each top-level block of import statements within a block of code. + Blank lines, comments, etc. are left alone and separate blocks of imports. + + Parse the entire code block into an ast, group into consecutive import + statements and other lines. Each import block consists entirely of + 'import' (or 'from ... import') statements. Other lines, including blanks + and comment lines, are not touched. + + >>> print(reformat_import_statements( + ... 'from foo import bar2 as bar2x, bar1\n' + ... 'import foo.bar3 as bar3x\n' + ... 'import foo.bar4\n' + ... '\n' + ... 'import foo.bar0 as bar0\n').text.joined) + import foo.bar4 + from foo import bar1, bar2 as bar2x, bar3 as bar3x + + from foo import bar0 + + + :type codeblock: + `PythonBlock` or convertible (``str``) + :type params: + `ImportFormatParams` + :rtype: + `PythonBlock` + """ + params = ImportFormatParams(params) + transformer = SourceToSourceFileImportsTransformation(codeblock) + return transformer.output(params=params) + + +def ImportPathForRelativeImportsCtx(codeblock): + """ + Context manager that temporarily modifies ``sys.path`` so that relative + imports for the given ``codeblock`` work as expected. + + :type codeblock: + `PythonBlock` + """ + codeblock = PythonBlock(codeblock) + if not codeblock.filename: + return NullCtx() + if codeblock.flags & CompilerFlags("absolute_import"): + return NullCtx() + return ImportPathCtx(str(codeblock.filename.dir)) + + +def fix_unused_and_missing_imports(codeblock, + add_missing=True, + remove_unused="AUTOMATIC", + add_mandatory=True, + db=None, + params=None): + r""" + Check for unused and missing imports, and fix them automatically. + + Also formats imports. + + In the example below, ``m1`` and ``m3`` are unused, so are automatically + removed. ``np`` was undefined, so an ``import numpy as np`` was + automatically added. + + >>> codeblock = PythonBlock( + ... 'from foo import m1, m2, m3, m4\n' + ... 'm2, m4, np.foo', filename="/tmp/foo.py") + + >>> print(fix_unused_and_missing_imports(codeblock, add_mandatory=False)) + [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m1' + [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m3' + [PYFLYBY] /tmp/foo.py: added 'import numpy as np' + import numpy as np + from foo import m2, m4 + m2, m4, np.foo + + :type codeblock: + `PythonBlock` or convertible (``str``) + :rtype: + `PythonBlock` + """ + codeblock = PythonBlock(codeblock) + if remove_unused == "AUTOMATIC": + fn = codeblock.filename + remove_unused = not (fn and + (fn.base == "__init__.py" + or ".pyflyby" in str(fn).split("/"))) + elif remove_unused is True or remove_unused is False: + pass + else: + raise ValueError("Invalid remove_unused=%r" % (remove_unused,)) + params = ImportFormatParams(params) + db = ImportDB.interpret_arg(db, target_filename=codeblock.filename) + # Do a first pass reformatting the imports to get rid of repeated or + # shadowed imports, e.g. L1 here: + # import foo # L1 + # import foo # L2 + # foo # L3 + codeblock = reformat_import_statements(codeblock, params=params) + + filename = codeblock.filename + transformer = SourceToSourceFileImportsTransformation(codeblock) + missing_imports, unused_imports = scan_for_import_issues( + codeblock, find_unused_imports=remove_unused, parse_docstrings=True) + logger.debug("missing_imports = %r", missing_imports) + logger.debug("unused_imports = %r", unused_imports) + if remove_unused and unused_imports: + # Go through imports to remove. [This used to be organized by going + # through import blocks and removing all relevant blocks from there, + # but if one removal caused problems the whole thing would fail. The + # CPU cost of calling without_imports() multiple times isn't worth + # that.] + # TODO: don't remove unused mandatory imports. [This isn't + # implemented yet because this isn't necessary for __future__ imports + # since they aren't reported as unused, and those are the only ones we + # have by default right now.] + for lineno, imp in unused_imports: + try: + imp = transformer.remove_import(imp, lineno) + except NoSuchImportError: + logger.error( + "%s: couldn't remove import %r", filename, imp,) + except LineNumberNotFoundError as e: + logger.error( + "%s: unused import %r on line %d not global", + filename, str(imp), e.args[0]) + else: + logger.info("%s: removed unused '%s'", filename, imp) + + if add_missing and missing_imports: + missing_imports.sort(key=lambda k: (k[1], k[0])) + known = db.known_imports.by_import_as + # Decide on where to put each import to be added. Find the import + # block with the longest common prefix. Tie-break by preferring later + # blocks. + added_imports = set() + for lineno, ident in missing_imports: + import_as = ident.parts[0] + try: + imports = known[import_as] + except KeyError: + logger.warning( + "%s:%s: undefined name %r and no known import for it", + filename, lineno, import_as) + continue + if len(imports) != 1: + logger.error("%s: don't know which of %r to use", + filename, imports) + continue + imp_to_add = imports[0] + if imp_to_add in added_imports: + continue + transformer.add_import(imp_to_add, lineno) + added_imports.add(imp_to_add) + logger.info("%s: added %r", filename, + imp_to_add.pretty_print().strip()) + + if add_mandatory: + # Todo: allow not adding to empty __init__ files? + mandatory = db.mandatory_imports.imports + for imp in mandatory: + try: + transformer.add_import(imp) + except ImportAlreadyExistsError: + pass + else: + logger.info("%s: added mandatory %r", + filename, imp.pretty_print().strip()) + + return transformer.output(params=params) + + +def remove_broken_imports(codeblock, params=None): + """ + Try to execute each import, and remove the ones that don't work. + + Also formats imports. + + :type codeblock: + `PythonBlock` or convertible (``str``) + :rtype: + `PythonBlock` + """ + codeblock = PythonBlock(codeblock) + params = ImportFormatParams(params) + filename = codeblock.filename + transformer = SourceToSourceFileImportsTransformation(codeblock) + for block in transformer.import_blocks: + broken = [] + for imp in list(block.importset.imports): + ns = {} + try: + exec_(imp.pretty_print(), ns) + except Exception as e: + logger.info("%s: Could not import %r; removing it: %s: %s", + filename, imp.fullname, type(e).__name__, e) + broken.append(imp) + block.importset = block.importset.without_imports(broken) + return transformer.output(params=params) + + +def replace_star_imports(codeblock, params=None): + r""" + Replace lines such as:: + + from foo.bar import * + with + from foo.bar import f1, f2, f3 + + Note that this requires involves actually importing ``foo.bar``, which may + have side effects. (TODO: rewrite to avoid this?) + + The result includes all imports from the ``email`` module. The result + excludes shadowed imports. In this example: + + 1. The original ``MIMEAudio`` import is shadowed, so it is removed. + 2. The ``MIMEImage`` import in the ``email`` module is shadowed by a + subsequent import, so it is omitted. + + >>> codeblock = PythonBlock('from keyword import *', filename="/tmp/x.py") + + >>> print(replace_star_imports(codeblock)) # doctest: +SKIP + [PYFLYBY] /tmp/x.py: replaced 'from keyword import *' with 2 imports + from keyword import iskeyword, kwlist + + + Usually you'll want to remove unused imports after replacing star imports. + + :type codeblock: + `PythonBlock` or convertible (``str``) + :rtype: + `PythonBlock` + """ + from pyflyby._modules import ModuleHandle + params = ImportFormatParams(params) + codeblock = PythonBlock(codeblock) + filename = codeblock.filename + transformer = SourceToSourceFileImportsTransformation(codeblock) + for block in transformer.import_blocks: + # Iterate over the import statements in ``block.input``. We do this + # instead of using ``block.importset`` because the latter doesn't + # preserve the order of inputs. The order is important for + # determining what's shadowed. + imports = [ + imp + for s in block.input.statements + for imp in ImportStatement(s).imports + ] + # Process "from ... import *" statements. + new_imports = [] + for imp in imports: + if imp.split.member_name != "*": + new_imports.append(imp) + elif imp.split.module_name.startswith("."): + # The source contains e.g. "from .foo import *". Right now we + # don't have a good way to figure out the absolute module + # name, so we can't get at foo. That said, there's a decent + # chance that this is inside an __init__ anyway, which is one + # of the few justifiable use cases for star imports in library + # code. + logger.warning("%s: can't replace star imports in relative import: %s", + filename, imp.pretty_print().strip()) + new_imports.append(imp) + else: + module = ModuleHandle(imp.split.module_name) + try: + with ImportPathForRelativeImportsCtx(codeblock): + exports = module.exports + except Exception as e: + logger.warning( + "%s: couldn't import '%s' to enumerate exports, " + "leaving unchanged: '%s'. %s: %s", + filename, module.name, imp, type(e).__name__, e) + new_imports.append(imp) + continue + if not exports: + # We found nothing in the target module. This probably + # means that module itself is just importing things from + # other modules. Currently we intentionally exclude those + # imports since usually we don't want them. TODO: do + # something better here. + logger.warning("%s: found nothing to import from %s, ", + "leaving unchanged: '%s'", + filename, module, imp) + new_imports.append(imp) + else: + new_imports.extend(exports) + logger.info("%s: replaced %r with %d imports", filename, + imp.pretty_print().strip(), len(exports)) + block.importset = ImportSet(new_imports, ignore_shadowed=True) + return transformer.output(params=params) + + +def transform_imports(codeblock, transformations, params=None): + """ + Transform imports as specified by ``transformations``. + + transform_imports() perfectly replaces all imports in top-level import + blocks. + + For the rest of the code body, transform_imports() does a crude textual + string replacement. This is imperfect but handles most cases. There may + be some false positives, but this is difficult to avoid. Generally we do + want to do replacements even within in strings and comments. + + >>> result = transform_imports("from m import x", {"m.x": "m.y.z"}) + >>> print(result.text.joined.strip()) + from m.y import z as x + + :type codeblock: + `PythonBlock` or convertible (``str``) + :type transformations: + ``dict`` from ``str`` to ``str`` + :param transformations: + A map of import prefixes to replace, e.g. {"aa.bb": "xx.yy"} + :rtype: + `PythonBlock` + """ + codeblock = PythonBlock(codeblock) + params = ImportFormatParams(params) + transformer = SourceToSourceFileImportsTransformation(codeblock) + @memoize + def transform_import(imp): + # Transform a block of imports. + # TODO: optimize + # TODO: handle transformations containing both a.b=>x and a.b.c=>y + for k, v in transformations.items(): + imp = imp.replace(k, v) + return imp + def transform_block(block): + # Do a crude string replacement in the PythonBlock. + block = PythonBlock(block) + s = block.text.joined + for k, v in transformations.items(): + s = re.sub("\\b%s\\b" % (re.escape(k)), v, s) + return PythonBlock(s, flags=block.flags) + # Loop over transformer blocks. + for block in transformer.blocks: + if isinstance(block, SourceToSourceImportBlockTransformation): + input_imports = block.importset.imports + output_imports = [ transform_import(imp) for imp in input_imports ] + block.importset = ImportSet(output_imports, ignore_shadowed=True) + else: + block.output = transform_block(block.input) + return transformer.output(params=params) + + +def canonicalize_imports(codeblock, params=None, db=None): + """ + Transform ``codeblock`` as specified by ``__canonical_imports__`` in the + global import library. + + :type codeblock: + `PythonBlock` or convertible (``str``) + :rtype: + `PythonBlock` + """ + codeblock = PythonBlock(codeblock) + params = ImportFormatParams(params) + db = ImportDB.interpret_arg(db, target_filename=codeblock.filename) + transformations = db.canonical_imports + return transform_imports(codeblock, transformations, params=params) diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_importstmt.py b/.venv/lib/python3.8/site-packages/pyflyby/_importstmt.py new file mode 100644 index 0000000..2ae6b6a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_importstmt.py @@ -0,0 +1,534 @@ +# pyflyby/_importstmt.py. +# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import ast +from collections import namedtuple +from functools import total_ordering + +from pyflyby._flags import CompilerFlags +from pyflyby._format import FormatParams, pyfill +from pyflyby._idents import is_identifier +from pyflyby._parse import PythonStatement +from pyflyby._util import (Inf, cached_attribute, cmp, + longest_common_prefix) + + +class ImportFormatParams(FormatParams): + align_imports = True + """ + Whether and how to align 'from modulename import aliases...'. If ``True``, + then the 'import' keywords will be aligned within a block. If an integer, + then the 'import' keyword will always be at that column. They will be + wrapped if necessary. + """ + + from_spaces = 1 + """ + The number of spaces after the 'from' keyword. (Must be at least 1.) + """ + + separate_from_imports = True + """ + Whether all 'from ... import ...' in an import block should come after + 'import ...' statements. ``separate_from_imports = False`` works well with + ``from_spaces = 3``. ('from __future__ import ...' always comes first.) + """ + + align_future = False + """ + Whether 'from __future__ import ...' statements should be aligned with + others. If False, uses a single space after the 'from' and 'import' + keywords. + """ + + +class NonImportStatementError(TypeError): + """ + Unexpectedly got a statement that wasn't an import. + """ + +ImportSplit = namedtuple("ImportSplit", + "module_name member_name import_as") +""" +Representation of a single import at the token level:: + + from [...] import as + +If is ``None``, then there is no "from" clause; instead just:: + import as +""" + +@total_ordering +class Import(object): + """ + Representation of the desire to import a single name into the current + namespace. + + >>> Import.from_parts(".foo.bar", "bar") + Import('from .foo import bar') + + >>> Import("from . import foo") + Import('from . import foo') + + >>> Import("from . import foo").fullname + '.foo' + + >>> Import("import foo . bar") + Import('import foo.bar') + + >>> Import("import foo . bar as baz") + Import('from foo import bar as baz') + + >>> Import("import foo . bar as bar") + Import('from foo import bar') + + >>> Import("foo.bar") + Import('from foo import bar') + + """ + def __new__(cls, arg): + if isinstance(arg, cls): + return arg + if isinstance(arg, ImportSplit): + return cls.from_split(arg) + if isinstance(arg, (ImportStatement, PythonStatement)): + return cls._from_statement(arg) + if isinstance(arg, str): + return cls._from_identifier_or_statement(arg) + raise TypeError + + @classmethod + def from_parts(cls, fullname, import_as): + if not isinstance(fullname, str): + raise TypeError + if not isinstance(import_as, str): + raise TypeError + self = object.__new__(cls) + self.fullname = fullname + self.import_as = import_as + return self + + @classmethod + def _from_statement(cls, statement): + """ + :type statement: + `ImportStatement` or convertible (`PythonStatement`, ``str``) + :rtype: + `Import` + """ + statement = ImportStatement(statement) + imports = statement.imports + if len(imports) != 1: + raise ValueError( + "Got %d imports instead of 1 in %r" % (len(imports), statement)) + return imports[0] + + @classmethod + def _from_identifier_or_statement(cls, arg): + """ + Parse either a raw identifier or a statement. + + >>> Import._from_identifier_or_statement('foo.bar.baz') + Import('from foo.bar import baz') + + >>> Import._from_identifier_or_statement('import foo.bar.baz') + Import('import foo.bar.baz') + + :rtype: + `Import` + """ + if is_identifier(arg, dotted=True): + return cls.from_parts(arg, arg.split('.')[-1]) + else: + return cls._from_statement(arg) + + @cached_attribute + def split(self): + """ + Split this `Import` into a ``ImportSplit`` which represents the + token-level ``module_name``, ``member_name``, ``import_as``. + + Note that at the token level, ``import_as`` can be ``None`` to represent + that the import statement doesn't have an "as ..." clause, whereas the + ``import_as`` attribute on an ``Import`` object is never ``None``. + + >>> Import.from_parts(".foo.bar", "bar").split + ImportSplit(module_name='.foo', member_name='bar', import_as=None) + + >>> Import("from . import foo").split + ImportSplit(module_name='.', member_name='foo', import_as=None) + + >>> Import.from_parts(".foo", "foo").split + ImportSplit(module_name='.', member_name='foo', import_as=None) + + >>> Import.from_parts("foo.bar", "foo.bar").split + ImportSplit(module_name=None, member_name='foo.bar', import_as=None) + + :rtype: + `ImportSplit` + """ + if self.import_as == self.fullname: + return ImportSplit(None, self.fullname, None) + level = 0 + qname = self.fullname + for level, char in enumerate(qname): + if char != '.': + break + prefix = qname[:level] + qname = qname[level:] + if '.' in qname: + module_name, member_name = qname.rsplit(".", 1) + else: + module_name = '' + member_name = qname + module_name = prefix + module_name + import_as = self.import_as + if import_as == member_name: + import_as = None + return ImportSplit(module_name or None, member_name, import_as) + + @classmethod + def from_split(cls, impsplit): + """ + Construct an `Import` instance from ``module_name``, ``member_name``, + ``import_as``. + + :rtype: + `Import` + """ + impsplit = ImportSplit(*impsplit) + module_name, member_name, import_as = impsplit + if import_as is None: + import_as = member_name + if module_name is None: + result = cls.from_parts(member_name, import_as) + else: + fullname = "%s%s%s" % ( + module_name, + "" if module_name.endswith(".") else ".", + member_name) + result = cls.from_parts(fullname, import_as) + # result.split will usually be the same as impsplit, but could be + # different if the input was 'import foo.bar as baz', which we + # canonicalize to 'from foo import bar as baz'. + return result + + def prefix_match(self, imp): + """ + Return the longest common prefix between ``self`` and ``imp``. + + >>> Import("import ab.cd.ef").prefix_match(Import("import ab.cd.xy")) + ('ab', 'cd') + + :type imp: + `Import` + :rtype: + ``tuple`` of ``str`` + """ + imp = Import(imp) + n1 = self.fullname.split('.') + n2 = imp.fullname.split('.') + return tuple(longest_common_prefix(n1, n2)) + + def replace(self, prefix, replacement): + """ + Return a new ``Import`` that replaces ``prefix`` with ``replacement``. + + >>> Import("from aa.bb import cc").replace("aa.bb", "xx.yy") + Import('from xx.yy import cc') + + >>> Import("from aa import bb").replace("aa.bb", "xx.yy") + Import('from xx import yy as bb') + + :rtype: + ``Import`` + """ + prefix_parts = prefix.split('.') + replacement_parts = replacement.split('.') + fullname_parts = self.fullname.split('.') + if fullname_parts[:len(prefix_parts)] != prefix_parts: + # No prefix match. + return self + fullname_parts[:len(prefix_parts)] = replacement_parts + import_as_parts = self.import_as.split('.') + if import_as_parts[:len(prefix_parts)] == prefix_parts: + import_as_parts[:len(prefix_parts)] = replacement_parts + return self.from_parts('.'.join(fullname_parts), + '.'.join(import_as_parts)) + + @cached_attribute + def flags(self): + """ + If this is a __future__ import, then the compiler_flag associated with + it. Otherwise, 0. + """ + if self.split.module_name == "__future__": + return CompilerFlags(self.split.member_name) + else: + return CompilerFlags.from_int(0) + + @property + def _data(self): + return (self.fullname, self.import_as) + + def pretty_print(self, params=FormatParams()): + return ImportStatement([self]).pretty_print(params) + + def __str__(self): + return self.pretty_print(FormatParams(max_line_length=Inf)).rstrip() + + def __repr__(self): + return "%s(%r)" % (type(self).__name__, str(self)) + + def __hash__(self): + return hash(self._data) + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, Import): + return NotImplemented + return cmp(self._data, other._data) + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, Import): + return NotImplemented + return self._data == other._data + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if self is other: + return False + if not isinstance(other, Import): + return NotImplemented + return self._data < other._data + + +@total_ordering +class ImportStatement(object): + """ + Token-level representation of an import statement containing multiple + imports from a single module. Corresponds to an ``ast.ImportFrom`` or + ``ast.Import``. + """ + def __new__(cls, arg): + if isinstance(arg, cls): + return arg + if isinstance(arg, (PythonStatement, str)): + return cls._from_statement(arg) + if isinstance(arg, (ast.ImportFrom, ast.Import)): + return cls._from_ast_node(arg) + if isinstance(arg, Import): + return cls._from_imports([arg]) + if isinstance(arg, (tuple, list)) and len(arg): + if isinstance(arg[0], Import): + return cls._from_imports(arg) + raise TypeError + + @classmethod + def from_parts(cls, fromname, aliases): + self = object.__new__(cls) + self.fromname = fromname + if not len(aliases): + raise ValueError + def interpret_alias(arg): + if isinstance(arg, str): + return (arg, None) + if not isinstance(arg, tuple): + raise TypeError + if not len(arg) == 2: + raise TypeError + if not isinstance(arg[0], str): + raise TypeError + if not (arg[1] is None or isinstance(arg[1], str)): + raise TypeError + return arg + self.aliases = tuple(interpret_alias(a) for a in aliases) + return self + + @classmethod + def _from_statement(cls, statement): + """ + >>> ImportStatement._from_statement("from foo import bar, bar2, bar") + ImportStatement('from foo import bar, bar2, bar') + + >>> ImportStatement._from_statement("from foo import bar as bar") + ImportStatement('from foo import bar as bar') + + >>> ImportStatement._from_statement("from foo.bar import baz") + ImportStatement('from foo.bar import baz') + + >>> ImportStatement._from_statement("import foo.bar") + ImportStatement('import foo.bar') + + >>> ImportStatement._from_statement("from .foo import bar") + ImportStatement('from .foo import bar') + + >>> ImportStatement._from_statement("from . import bar, bar2") + ImportStatement('from . import bar, bar2') + + :type statement: + `PythonStatement` + :rtype: + `ImportStatement` + """ + statement = PythonStatement(statement) + return cls._from_ast_node(statement.ast_node) + + @classmethod + def _from_ast_node(cls, node): + """ + Construct an `ImportStatement` from an `ast` node. + + :rtype: + `ImportStatement` + """ + if isinstance(node, ast.ImportFrom): + if isinstance(node.module, str): + module = node.module + elif node.module is None: + # In python2.7, ast.parse("from . import blah") yields + # node.module = None. In python2.6, it's the empty string. + module = '' + else: + raise TypeError("unexpected node.module=%s" + % type(node.module).__name__) + fromname = '.' * node.level + module + elif isinstance(node, ast.Import): + fromname = None + else: + raise NonImportStatementError + aliases = [ (alias.name, alias.asname) for alias in node.names ] + return cls.from_parts(fromname, aliases) + + @classmethod + def _from_imports(cls, imports): + """ + Construct an `ImportStatement` from a sequence of ``Import`` s. They + must all have the same ``fromname``. + + :type imports: + Sequence of `Import` s + :rtype: + `ImportStatement` + """ + if not all(isinstance(imp, Import) for imp in imports): + raise TypeError + if not len(imports) > 0: + raise ValueError + module_names = set(imp.split.module_name for imp in imports) + if len(module_names) > 1: + raise Exception( + "Inconsistent module names %r" % (sorted(module_names),)) + fromname = list(module_names)[0] + aliases = [ imp.split[1:] for imp in imports ] + return cls.from_parts(fromname, aliases) + + @cached_attribute + def imports(self): + """ + Return a sequence of `Import` s. + + :rtype: + ``tuple`` of `Import` s + """ + return tuple( + Import.from_split((self.fromname, alias[0], alias[1])) + for alias in self.aliases) + + @cached_attribute + def flags(self): + """ + If this is a __future__ import, then the bitwise-ORed of the + compiler_flag values associated with the features. Otherwise, 0. + """ + return CompilerFlags(*[imp.flags for imp in self.imports]) + + def pretty_print(self, params=FormatParams(), + import_column=None, from_spaces=1): + """ + Pretty-print into a single string. + + :type params: + `FormatParams` + :param modulename_ljust: + Number of characters to left-justify the 'from' name. + :rtype: + ``str`` + """ + s0 = '' + s = '' + assert from_spaces >= 1 + if self.fromname is not None: + s += "from%s%s " % (' ' * from_spaces, self.fromname) + if import_column is not None: + if len(s) > import_column: + # The caller wants the 'import' statement lined up left of + # where the current end of the line is. So wrap it + # specially like this:: + # from foo import ... + # from foo.bar.baz \ + # import ... + s0 = s + '\\\n' + s = ' ' * import_column + else: + s = s.ljust(import_column) + s += "import " + tokens = [] + for importname, asname in self.aliases: + if asname is not None: + t = "%s as %s" % (importname, asname) + else: + t = "%s" % (importname,) + tokens.append(t) + res = s0 + pyfill(s, tokens, params=params) + if params.use_black: + import black + mode = black.FileMode() + return black.format_str(res, mode=mode) + return res + + @property + def _data(self): + return (self.fromname, self.aliases) + + def __str__(self): + return self.pretty_print(FormatParams(max_line_length=Inf)).rstrip() + + def __repr__(self): + return "%s(%r)" % (type(self).__name__, str(self)) + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, ImportStatement): + return NotImplemented + return cmp(self._data, other._data) + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, ImportStatement): + return NotImplemented + return self._data == other._data + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, ImportStatement): + return NotImplemented + return self._data < other._data + + def __hash__(self): + return hash(self._data) diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_interactive.py b/.venv/lib/python3.8/site-packages/pyflyby/_interactive.py new file mode 100644 index 0000000..e3441ff --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_interactive.py @@ -0,0 +1,2597 @@ +# pyflyby/_interactive.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import ast +from contextlib import contextmanager +import errno +import inspect +import os +import re +import subprocess +import sys + +import six +from six import PY2, text_type as unicode +from six.moves import builtins + +from pyflyby._autoimp import (LoadSymbolError, ScopeStack, auto_eval, + auto_import, + clear_failed_imports_cache, + load_symbol) +from pyflyby._comms import (initialize_comms, remove_comms, + send_comm_message, MISSING_IMPORTS) +from pyflyby._file import Filename, atomic_write_file, read_file +from pyflyby._idents import is_identifier +from pyflyby._importdb import ImportDB +from pyflyby._log import logger +from pyflyby._modules import ModuleHandle +from pyflyby._parse import PythonBlock +from pyflyby._util import (AdviceCtx, Aspect, CwdCtx, + FunctionWithGlobals, NullCtx, advise, + indent) + + +if False: + __original__ = None # for pyflakes + + +# TODO: also support arbitrary code (in the form of a lambda and/or +# assignment) as new way to do "lazy" creations, e.g. foo = a.b.c(d.e+f.g()) + + +class NoIPythonPackageError(Exception): + """ + Exception raised when the IPython package is not installed in the system. + """ + + +class NoActiveIPythonAppError(Exception): + """ + Exception raised when there is no current IPython application instance. + """ + + +def _get_or_create_ipython_terminal_app(): + """ + Create/get the singleton IPython terminal application. + + :rtype: + ``TerminalIPythonApp`` + :raise NoIPythonPackageError: + IPython is not installed in the system. + """ + try: + import IPython + except ImportError as e: + raise NoIPythonPackageError(e) + # The following has been tested on IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3. + try: + TerminalIPythonApp = IPython.terminal.ipapp.TerminalIPythonApp + except AttributeError: + pass + else: + return TerminalIPythonApp.instance() + # The following has been tested on IPython 0.11, 0.12, 0.13. + try: + TerminalIPythonApp = IPython.frontend.terminal.ipapp.TerminalIPythonApp + except AttributeError: + pass + else: + return TerminalIPythonApp.instance() + # The following has been tested on IPython 0.10. + if hasattr(IPython, "ipapi"): + return _IPython010TerminalApplication.instance() + raise RuntimeError( + "Couldn't get TerminalIPythonApp class. " + "Is your IPython version too old (or too new)? " + "IPython.__version__=%r" % (IPython.__version__)) + + +def _app_is_initialized(app): + """ + Return whether ``app.initialize()`` has been called. + + :type app: + `IPython.Application` + :rtype: + ``bool`` + """ + # There's no official way to tell whether app.initialize() has been called + # before. We guess whether the app has been initialized by checking + # whether all traits have values. + # + # There's a method app.initialized(), but it doesn't do what we want. It + # does not return whether app.initialize() has been called - rather, + # type(app).initialized() returns whether an instance of the class has + # ever been constructed, i.e. app.initialized() always returns True. + cache_name = "__is_initialized_54283907" + if cache_name in app.__dict__: + return True + if all(n in app._trait_values for n in app.trait_names()): + app.__dict__[cache_name] = True + return True + else: + return False + + + +class _IPython010TerminalApplication(object): + """ + Shim class that mimics IPython 0.11+ application classes, for use in + IPython 0.10. + """ + + # IPython.ipapi.launch_instance() => IPython.Shell.start() creates an + # instance of "IPShell". IPShell has an attribute named "IP" which is an + # "InteractiveShell". + + _instance = None + + @classmethod + def instance(cls): + if cls._instance is not None: + self = cls._instance + self.init_shell() + return self + import IPython + if not hasattr(IPython, "ipapi"): + raise RuntimeError("Inappropriate version of IPython %r" + % (IPython.__version__,)) + self = cls._instance = cls() + self.init_shell() + return self + + def init_shell(self): + import IPython + ipapi = IPython.ipapi.get() # IPApi instance + if ipapi is not None: + self.shell = ipapi.IP # InteractiveShell instance + else: + self.shell = None + + def initialize(self, argv=None): + import IPython + logger.debug("Creating IPython 0.10 session") + self._session = IPython.ipapi.make_session() # IPShell instance + self.init_shell() + assert self._session is not None + + def start(self): + self._session.mainloop() + + + +class _DummyIPythonEmbeddedApp(object): + """ + Small wrapper around an `InteractiveShellEmbed`. + """ + + def __init__(self, shell): + self.shell = shell + + + +def _get_or_create_ipython_kernel_app(): + """ + Create/get the singleton IPython kernel application. + + :rtype: + ``callable`` + :return: + The function that can be called to start the kernel application. + """ + import IPython + # The following has been tested on IPython 4.0 + try: + from ipykernel.kernelapp import IPKernelApp + except ImportError: + pass + else: + return IPKernelApp.instance() + # The following has been tested on IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, + # 2.4, 3.0, 3.1, 3.2 + try: + from IPython.kernel.zmq.kernelapp import IPKernelApp + except ImportError: + pass + else: + return IPKernelApp.instance() + # The following has been tested on IPython 0.12, 0.13 + try: + from IPython.zmq.ipkernel import IPKernelApp + except ImportError: + pass + else: + return IPKernelApp.instance() + raise RuntimeError( + "Couldn't get IPKernelApp class. " + "Is your IPython version too old (or too new)? " + "IPython.__version__=%r" % (IPython.__version__)) + + +def get_ipython_terminal_app_with_autoimporter(): + """ + Return an initialized ``TerminalIPythonApp``. + + If a ``TerminalIPythonApp`` has already been created, then use it (whether + we are inside that app or not). If there isn't already one, then create + one. Enable the auto importer, if it hasn't already been enabled. If the + app hasn't been initialized yet, then initialize() it (but don't start() + it). + + :rtype: + ``TerminalIPythonApp`` + :raise NoIPythonPackageError: + IPython is not installed in the system. + """ + app = _get_or_create_ipython_terminal_app() + AutoImporter(app).enable() + if not _app_is_initialized(app): + old_display_banner = app.display_banner + try: + app.display_banner = False + app.initialize([]) + finally: + app.display_banner = old_display_banner + return app + + +def start_ipython_with_autoimporter(argv=None, app=None, _user_ns=None): + """ + Start IPython (terminal) with autoimporter enabled. + """ + if app is None: + subcmd = argv and argv[0] + if subcmd == 'console': + # The following has been tested on IPython 5.8 / Jupyter console 5.2. + # Note: jupyter_console.app.JupyterApp also appears to work in some + # contexts, but that actually execs the script jupyter-console which + # uses ZMQTerminalIPythonApp. The exec makes the target use whatever + # shebang line is in that script, which may be a different python + # major version than what we're currently running. We want to avoid + # the exec in general (as a library function) and avoid changing + # python versions. + try: + from ipkernel.app import IPKernelApp + except (ImportError, AttributeError): + pass + else: + app = IPKernelApp.instance() + argv = argv[1:] + elif subcmd == 'notebook': + try: + from notebook.notebookapp import NotebookApp + except (ImportError, AttributeError): + pass + else: + app = NotebookApp.instance() + argv = argv[1:] + if app is None: + app = _get_or_create_ipython_terminal_app() + if _user_ns is not None: + # Tested with IPython 1.2, 2.0, 2.1, 2.2, 2.3. 2.4, 3.0, 3.1, 3.2 + # TODO: support older versions of IPython. + # FIXME TODO: fix attaching debugger to IPython started this way. It + # has to do with assigning user_ns. Apparently if user_ns["__name__"] + # is "__main__" (which IPython defaults to, and we want to use + # anyway), then user_module must be a true ModuleType in order for + # attaching to work correctly. If you specify user_ns but not + # user_module, then user_module is a DummyModule rather than a true + # ModuleType (since ModuleType.__dict__ is read-only). Thus, if we + # specify user_ns, we should specify user_module also. However, while + # user_module is a constructor parameter to InteractiveShell, + # IPythonTerminalApp doesn't pass that parameter to it. We can't + # assign after initialize() because user_module and user_ns are + # already used during initialization. One workaround idea is to let + # IPython initialize without specifying either user_ns or user_module, + # and then patch in members. However, that has the downside of + # breaking func_globals of lambdas, e.g. if a script does 'def f(): + # global x; x=4', then we run it with 'py -i', our globals dict won't + # be the same dict. We should create a true ModuleType anyway even if + # not using IPython. We might need to resort to advising + # init_create_namespaces etc. depending on IPython version. + if getattr(app, 'shell', None) is not None: + app.shell.user_ns.update(_user_ns) + else: + app.user_ns = _user_ns + return _initialize_and_start_app_with_autoimporter(app, argv) + + +def start_ipython_kernel_with_autoimporter(argv=None): + """ + Start IPython kernel with autoimporter enabled. + """ + app = _get_or_create_ipython_kernel_app() + return _initialize_and_start_app_with_autoimporter(app, argv) + + +def _initialize_and_start_app_with_autoimporter(app, argv): + """ + Initialize and start an IPython app, with autoimporting enabled. + + :type app: + `BaseIPythonApplication` + """ + # Enable the auto importer. + AutoImporter(app).enable() + # Save the value of the "_" name in the user namespace, to avoid + # initialize() clobbering it. + user_ns = getattr(app, "user_ns", None) + saved_user_ns = {} + if user_ns is not None: + for k in ["_"]: + try: + saved_user_ns[k] = user_ns[k] + except KeyError: + pass + # Initialize the app. + if not _app_is_initialized(app): + app.initialize(argv) + if user_ns is not None: + user_ns.update(saved_user_ns) + # Start the app mainloop. + return app.start() + + +def run_ipython_line_magic(arg): + """ + Run IPython magic command. + If necessary, start an IPython terminal app to do so. + """ + import IPython + if not arg.startswith("%"): + arg = "%" + arg + app = _get_or_create_ipython_terminal_app() + AutoImporter(app).enable() + # TODO: only initialize if not already initialized. + if not _app_is_initialized(app): + app.initialize([]) + ip = app.shell + if hasattr(ip, "magic"): + # IPython 0.11+. + # The following has been tested on IPython 0.11, 0.12, 0.13, 1.0, 1.2, + # 2.0, 2.1, 2.2, 2.3. + # TODO: may want to wrap in one or two layers of dummy functions to make + # sure run_line_magic() doesn't inspect our locals. + return ip.magic(arg) + elif hasattr(ip, "runlines"): + # IPython 0.10 + return ip.runlines(arg) + else: + raise RuntimeError( + "Couldn't run IPython magic. " + "Is your IPython version too old (or too new)? " + "IPython.__version__=%r" % (IPython.__version__)) + + +def _python_can_import_pyflyby(expected_path, sys_path_entry=None): + """ + Try to figure out whether python (when started from scratch) can get the + same pyflyby package as the current process. + """ + with CwdCtx("/"): + cmd = 'import pyflyby; print(pyflyby.__path__[0])' + if sys_path_entry is not None: + impcmd = "import sys; sys.path.insert(0, %r)\n" % (sys_path_entry,) + cmd = impcmd + cmd + proc = subprocess.Popen( + [sys.executable, '-c', cmd], + stdin=open("/dev/null"), + stdout=subprocess.PIPE, + stderr=open("/dev/null",'w')) + result = proc.communicate()[0].strip() + if not result: + return False + try: + return os.path.samefile(result, expected_path) + except OSError: + return False + + +def install_in_ipython_config_file(): + """ + Install the call to 'pyflyby.enable_auto_importer()' to the default + IPython startup file. + + This makes all "ipython" sessions behave like "autoipython", i.e. start + with the autoimporter already enabled. + """ + import IPython + # The following has been tested on IPython 4.0, 5.0 + try: + IPython.paths + except AttributeError: + pass + else: + _install_in_ipython_config_file_40() + return + # The following has been tested on IPython 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, + # 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. + try: + IPython.core.profiledir.ProfileDir.startup_dir + except AttributeError: + pass + else: + _install_in_ipython_config_file_012() + return + # The following has been tested on IPython 0.11. + try: + IPython.core.profiledir.ProfileDir + except AttributeError: + pass + else: + _install_in_ipython_config_file_011() + return + try: + IPython.genutils.get_ipython_dir + except AttributeError: + pass + else: + _install_in_ipython_config_file_010() + return + raise RuntimeError( + "Couldn't install pyflyby autoimporter in IPython. " + "Is your IPython version too old (or too new)? " + "IPython.__version__=%r" % (IPython.__version__)) + + +def _generate_enabler_code(): + """ + Generate code for enabling the auto importer. + + :rtype: + ``str`` + """ + funcdef = ( + "import pyflyby\n" + "pyflyby.enable_auto_importer()\n" + ) + # Check whether we need to include the path in sys.path, and if so, add + # that to the contents. + import pyflyby + pyflyby_path = pyflyby.__path__[0] + if not _python_can_import_pyflyby(pyflyby_path): + path_entry = os.path.dirname(os.path.realpath(pyflyby_path)) + assert _python_can_import_pyflyby(pyflyby_path, path_entry) + funcdef = ( + "import sys\n" + "saved_sys_path = sys.path[:]\n" + "try:\n" + " sys.path.insert(0, %r)\n" % (path_entry,) + + indent(funcdef, " ") + + "finally:\n" + " sys.path = saved_sys_path\n" + ) + # Wrap the code in a temporary function, call it, then delete the + # function. This avoids polluting the user's global namespace. Although + # the global name "pyflyby" will almost always end up meaning the module + # "pyflyby" anyway, if the user types it, there's still value in not + # polluting the namespace in case something enumerates over globals(). + # For the function name we use a name that's unlikely to be used by the + # user. + contents = ( + "def __pyflyby_enable_auto_importer_60321389():\n" + + indent(funcdef, " ") + + "__pyflyby_enable_auto_importer_60321389()\n" + "del __pyflyby_enable_auto_importer_60321389\n" + ) + return contents + + +def _install_in_ipython_config_file_40(): + """ + Implementation of `install_in_ipython_config_file` for IPython 4.0+. + """ + import IPython + ipython_dir = Filename(IPython.paths.get_ipython_dir()) + if not ipython_dir.isdir: + raise RuntimeError( + "Couldn't find IPython config dir. Tried %s" % (ipython_dir,)) + + # Add to extensions list in ~/.ipython/profile_default/ipython_config.py + config_fn = ipython_dir / "profile_default" / "ipython_config.py" + if not config_fn.exists: + subprocess.call(['ipython', 'profile', 'create']) + if not config_fn.exists: + raise RuntimeError( + "Couldn't find IPython config file. Tried %s" % (config_fn,)) + old_config_blob = read_file(config_fn) + # This is the line we'll add. + line_to_add = 'c.InteractiveShellApp.extensions.append("pyflyby")' + non_comment_lines = [re.sub("#.*", "", line) for line in old_config_blob.lines] + if any(line_to_add in line for line in non_comment_lines): + logger.info("[NOTHING TO DO] File %s already loads pyflyby", config_fn) + elif any("pyflyby" in line for line in non_comment_lines): + logger.info("[NOTHING TO DO] File %s already references pyflyby in some nonstandard way, assuming you configured it manually", config_fn) + else: + # Add pyflyby to config file. + lines_to_add = [line_to_add] + # Check whether we need to include the path in sys.path, and if so, add + # that to the contents. This is only needed if pyflyby is running out + # of a home directory rather than site-packages/virtualenv/etc. + # TODO: we should use tidy-imports to insert the 'import sys', if + # needed, at the top, rather than always appending it at the bottom. + import pyflyby + pyflyby_path = pyflyby.__path__[0] + if not _python_can_import_pyflyby(pyflyby_path): + path_entry = os.path.dirname(os.path.realpath(pyflyby_path)) + assert _python_can_import_pyflyby(pyflyby_path, path_entry) + lines_to_add = [ + "import sys", + "sys.path.append(%r)" % (path_entry,) + ] + lines_to_add + lines_to_add.insert(0, "# Pyflyby") + blob_to_add = "\n\n" + "\n".join(lines_to_add) + "\n" + new_config_blob = old_config_blob.joined.rstrip() + blob_to_add + atomic_write_file(config_fn, new_config_blob) + logger.info("[DONE] Appended to %s: %s", config_fn, line_to_add) + + # Delete file installed with older approach. + startup_dir = ipython_dir / "profile_default" / "startup" + old_fn = startup_dir / "50-pyflyby.py" + if old_fn.exists: + trash_dir = old_fn.dir / ".TRASH" + trash_fn = trash_dir / old_fn.base + try: + os.mkdir(str(trash_dir)) + except EnvironmentError as e: + if e.errno == errno.EEXIST: + pass + else: + raise RuntimeError("Couldn't mkdir %s: %s: %s" + % (trash_dir, type(e).__name__, e)) + try: + os.rename(str(old_fn), str(trash_fn)) + except EnvironmentError as e: + raise RuntimeError("Couldn't rename %s to %s: %s: %s" + % (old_fn, trash_fn, type(e).__name__, e)) + logger.info("[DONE] Removed old file %s (moved to %s)", old_fn, trash_fn) + + +def _install_in_ipython_config_file_012(): + """ + Implementation of `install_in_ipython_config_file` for IPython 0.12+. + Tested with IPython 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, + 3.1, 3.2, 4.0. + """ + import IPython + ipython_dir = Filename(IPython.utils.path.get_ipython_dir()) + if not ipython_dir.isdir: + raise RuntimeError( + "Couldn't find IPython config dir. Tried %s" % (ipython_dir,)) + startup_dir = ipython_dir / "profile_default" / "startup" + if not startup_dir.isdir: + raise RuntimeError( + "Couldn't find IPython startup dir. Tried %s" % (startup_dir,)) + fn = startup_dir / "50-pyflyby.py" + if fn.exists: + logger.info("Doing nothing, because %s already exists", fn) + return + argv = sys.argv[:] + argv[0] = os.path.realpath(argv[0]) + argv = ' '.join(argv) + header = ( + "# File: {fn}\n" + "#\n" + "# Generated by {argv}\n" + "#\n" + "# This file causes IPython to enable the Pyflyby Auto Importer.\n" + "#\n" + "# To uninstall, just delete this file.\n" + "#\n" + ).format(**locals()) + contents = header + _generate_enabler_code() + logger.info("Installing pyflyby auto importer in your IPython startup") + logger.info("Writing to %s:\n%s", fn, contents) + atomic_write_file(fn, contents) + + +def _install_in_ipython_config_file_011(): + """ + Implementation of `install_in_ipython_config_file` for IPython 0.11. + """ + import IPython + ipython_dir = Filename(IPython.utils.path.get_ipython_dir()) + fn = ipython_dir / "profile_default" / "ipython_config.py" + if not fn.exists: + raise RuntimeError( + "Couldn't find IPython startup file. Tried %s" % (fn,)) + old_contents = read_file(fn).joined + if re.search(r"^ *(pyflyby[.])?enable_auto_importer[(][)]", old_contents, re.M): + logger.info("Doing nothing, because already installed in %s", fn) + return + header = ( + "\n" + "\n" + "#\n" + "# Enable the Pyflyby Auto Importer.\n" + ) + new_contents = header + _generate_enabler_code() + contents = old_contents.rstrip() + new_contents + logger.info("Installing pyflyby auto importer in your IPython startup") + logger.info("Appending to %s:\n%s", fn, new_contents) + atomic_write_file(fn, contents) + + +def _install_in_ipython_config_file_010(): + """ + Implementation of `install_in_ipython_config_file` for IPython 0.10. + """ + import IPython + ipython_dir = Filename(IPython.genutils.get_ipython_dir()) + fn = ipython_dir / "ipy_user_conf.py" + if not fn.exists: + raise RuntimeError( + "Couldn't find IPython config file. Tried %s" % (fn,)) + old_contents = read_file(fn).joined + if re.search(r"^ *(pyflyby[.])?enable_auto_importer[(][)]", old_contents, re.M): + logger.info("Doing nothing, because already installed in %s", fn) + return + header = ( + "\n" + "\n" + "#\n" + "# Enable the Pyflyby Auto Importer.\n" + ) + new_contents = header + _generate_enabler_code() + contents = old_contents.rstrip() + new_contents + logger.info("Installing pyflyby auto importer in your IPython startup") + logger.info("Appending to %s:\n%s", fn, new_contents) + atomic_write_file(fn, contents) + + +def _ipython_in_multiline(ip): + """ + Return ``False`` if the user has entered only one line of input so far, + including the current line, or ``True`` if it is the second or later line. + + :type ip: + ``InteractiveShell`` + :rtype: + ``bool`` + """ + if hasattr(ip, "input_splitter"): + # IPython 0.11+. Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, + # 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. + return bool(ip.input_splitter.source) + elif hasattr(ip, "buffer"): + # IPython 0.10 + return bool(ip.buffer) + else: + # IPython version too old or too new? + return False + + +def InterceptPrintsDuringPromptCtx(ip): + """ + Decorator that hooks our logger so that:: + + 1. Before the first print, if any, print an extra newline. + 2. Upon context exit, if any lines were printed, redisplay the prompt. + + :type ip: + ``InteractiveShell`` + """ + if not ip: + return NullCtx() + + if not hasattr(ip, 'readline'): + if type(sys.stdout).__module__.startswith("prompt_toolkit."): + # prompt_toolkit replaces stdout with a proxy that takes + # care of redrawing the prompt correctly. + return NullCtx() + + if not hasattr(ip, "prompts_class"): + # This could be a Jupyter console/notebook. + return NullCtx() + + def pre(): + sys.stdout.write("\n") + sys.stdout.flush() + def post(): + # Re-display the current line. + sys.stdout.write("\n") + t = ip.prompts_class(ip).in_prompt_tokens() + ip.pt_cli.print_tokens(t) + sys.stdout.write(ip.pt_cli.current_buffer.document.current_line) + sys.stdout.flush() + return logger.HookCtx(pre=pre, post=post) + + readline = ip.readline + if not hasattr(readline, "redisplay"): + # May be IPython Notebook. + return NullCtx() + redisplay = readline.redisplay + get_prompt = None + if type(ip).__module__ == "rlipython.shell": + # IPython 5.4 with + # interactive_shell_class=rlipython.TerminalInteractiveShell + def get_prompt_rlipython(): + pdb_instance = _get_pdb_if_is_in_pdb() + if pdb_instance is not None: + return pdb_instance.prompt + elif _ipython_in_multiline(ip): + return ip.prompt_in2 + else: + return ip.separate_in + ip.prompt_in1.format(ip.execution_count) + get_prompt = get_prompt_rlipython + elif hasattr(ip, "prompt_manager"): + # IPython >= 0.12 (known to work including up to 1.2, 2.1) + prompt_manager = ip.prompt_manager + def get_prompt_ipython_012(): + pdb_instance = _get_pdb_if_is_in_pdb() + if pdb_instance is not None: + return pdb_instance.prompt + elif _ipython_in_multiline(ip): + return prompt_manager.render("in2") + else: + return ip.separate_in + prompt_manager.render("in") + get_prompt = get_prompt_ipython_012 + elif hasattr(ip.hooks, "generate_prompt"): + # IPython 0.10, 0.11 + generate_prompt = ip.hooks.generate_prompt + def get_prompt_ipython_010(): + pdb_instance = _get_pdb_if_is_in_pdb() + if pdb_instance is not None: + return pdb_instance.prompt + elif _ipython_in_multiline(ip): + return generate_prompt(True) + else: + if hasattr(ip, "outputcache"): + # IPython 0.10 (but not 0.11+): + # Decrement the prompt_count since it otherwise + # auto-increments. (It's hard to avoid the + # auto-increment as it happens as a side effect of + # __str__!) + ip.outputcache.prompt_count -= 1 + return generate_prompt(False) + get_prompt = get_prompt_ipython_010 + else: + # Too old or too new IPython version? + return NullCtx() + def pre(): + sys.stdout.write("\n") + sys.stdout.flush() + def post(): + # Re-display the current line. + prompt = get_prompt() + prompt = prompt.replace("\x01", "").replace("\x02", "") + line = readline.get_line_buffer()[:readline.get_endidx()] + sys.stdout.write(prompt + line) + redisplay() + sys.stdout.flush() + return logger.HookCtx(pre=pre, post=post) + + +def _get_ipython_app(): + """ + Get an IPython application instance, if we are inside an IPython session. + + If there isn't already an IPython application, raise an exception; don't + create one. + + If there is a subapp, return it. + + :rtype: + `BaseIPythonApplication` or an object that mimics some of its behavior + """ + try: + IPython = sys.modules['IPython'] + except KeyError: + # The 'IPython' module isn't already loaded, so we're not in an + # IPython session. Don't import it. + raise NoActiveIPythonAppError( + "No active IPython application (IPython not even imported yet)") + # The following has been tested on IPython 0.11, 0.12, 0.13, 1.0, 1.2, + # 2.0, 2.1, 2.2, 2.3. + try: + App = IPython.core.application.BaseIPythonApplication + except AttributeError: + pass + else: + app = App._instance + if app is not None: + if app.subapp is not None: + return app.subapp + else: + return app + # If we're inside an embedded shell, then there will be an active + # InteractiveShellEmbed but no application. In that case, create a + # fake application. + # (An alternative implementation would be to use + # IPython.core.interactiveshell.InteractiveShell._instance. However, + # that doesn't work with older versions of IPython, where the embedded + # shell is not a singleton.) + if hasattr(builtins, "get_ipython"): + shell = builtins.get_ipython() + else: + shell = None + if shell is not None: + return _DummyIPythonEmbeddedApp(shell) + # No active IPython app/shell. + raise NoActiveIPythonAppError("No active IPython application") + # The following has been tested on IPython 0.10. + if hasattr(IPython, "ipapi"): + return _IPython010TerminalApplication.instance() + raise NoActiveIPythonAppError( + "Could not figure out how to get active IPython application for IPython version %s" + % (IPython.__version__,)) + + +def _ipython_namespaces(ip): + """ + Return the (global) namespaces used for IPython. + + The ordering follows IPython convention of most-local to most-global. + + :type ip: + ``InteractiveShell`` + :rtype: + ``list`` + :return: + List of (name, namespace_dict) tuples. + """ + # This list is copied from IPython 2.2's InteractiveShell._ofind(). + # Earlier versions of IPython (back to 1.x) also include + # ip.alias_manager.alias_table at the end. This doesn't work in IPython + # 2.2 and isn't necessary anyway in earlier versions of IPython. + return [ ('Interactive' , ip.user_ns), + ('Interactive (global)', ip.user_global_ns), + ('Python builtin' , builtins.__dict__), + ] + + +# TODO class NamespaceList(tuple): + + +_IS_PDB_IGNORE_PKGS = frozenset([ + 'IPython', + 'cmd', + 'contextlib', + 'prompt_toolkit', + 'pyflyby', + 'rlipython', + 'asyncio', +]) + +_IS_PDB_IGNORE_PKGS_OTHER_THREADS = frozenset([ + 'IPython', + 'cmd', + 'contextlib', + 'prompt_toolkit', + 'pyflyby', + 'threading', +]) + +def _get_pdb_if_is_in_pdb(): + """ + Return the current Pdb instance, if we're currently called from Pdb. + + :rtype: + ``pdb.Pdb`` or ``NoneType`` + """ + # This is kludgy. Todo: Is there a better way to do this? + pframe, pkgname = _skip_frames(sys._getframe(1), _IS_PDB_IGNORE_PKGS) + if pkgname == "threading": + # _skip_frames skipped all the way back to threading.__bootstrap. + # prompt_toolkit calls completion in a separate thread. + # Search all other threads for pdb. + # TODO: make this less kludgy. + import threading + current_tid = threading.current_thread().ident + pframes = [_skip_frames(frame, _IS_PDB_IGNORE_PKGS_OTHER_THREADS) + for tid, frame in sys._current_frames().items() + if tid != current_tid] + else: + pframes = [(pframe, pkgname)] + logger.debug("_get_pdb_if_is_in_pdb(): pframes = %r", pframes) + del pframe, pkgname + pdb_frames = [pframe for pframe,pkgname in pframes + if pkgname == "pdb"] + if not pdb_frames: + return None + # Found a pdb frame. + pdb_frame = pdb_frames[0] + import pdb + + pdb_instance = pdb_frame.f_locals.get("self", None) + if (type(pdb_instance).__name__ == "Pdb" or + isinstance(pdb_instance, pdb.Pdb)): + return pdb_instance + else: + return None + + +def _skip_frames(frame, ignore_pkgs): + # import traceback;print("".join(traceback.format_stack(frame))) + while True: + if frame is None: + return None, None + modname = frame.f_globals.get("__name__", None) or "" + pkgname = modname.split(".",1)[0] + # logger.debug("_skip_frames: frame: %r %r", frame, modname) + if pkgname in ignore_pkgs: + frame = frame.f_back + continue + break + # logger.debug("_skip_frames: => %r %r", frame, pkgname) + return frame, pkgname + + +def get_global_namespaces(ip): + """ + Get the global interactive namespaces. + + :type ip: + ``InteractiveShell`` + :param ip: + IPython shell or ``None`` to assume not in IPython. + :rtype: + ``list`` of ``dict`` + """ + # logger.debug("get_global_namespaces()") + pdb_instance = _get_pdb_if_is_in_pdb() + # logger.debug("get_global_namespaces(): pdb_instance=%r", pdb_instance) + if pdb_instance: + frame = pdb_instance.curframe + return [frame.f_globals, pdb_instance.curframe_locals] + elif ip: + return [ns for nsname, ns in _ipython_namespaces(ip)][::-1] + else: + import __main__ + return [builtins.__dict__, __main__.__dict__] + + +def complete_symbol(fullname, namespaces, db=None, autoimported=None, ip=None, + allow_eval=False): + """ + Enumerate possible completions for ``fullname``. + + Includes globals and auto-importable symbols. + + >>> complete_symbol("threadi", [{}]) # doctest:+ELLIPSIS + [...'threading'...] + + Completion works on attributes, even on modules not yet imported - modules + are auto-imported first if not yet imported:: + + >>> ns = {} + >>> complete_symbol("threading.Threa", namespaces=[ns]) + [PYFLYBY] import threading + ['threading.Thread', 'threading.ThreadError'] + + >>> 'threading' in ns + True + + >>> complete_symbol("threading.Threa", namespaces=[ns]) + ['threading.Thread', 'threading.ThreadError'] + + We only need to import *parent* modules (packages) of the symbol being + completed. If the user asks to complete "foo.bar.quu", we need to + import foo.bar, but we don't need to import foo.bar.quux. + + :type fullname: + ``str`` + :param fullname: + String to complete. ("Full" refers to the fact that it should contain + dots starting from global level.) + :type namespaces: + ``dict`` or ``list`` of ``dict`` + :param namespaces: + Namespaces of (already-imported) globals. + :type db: + `importDB` + :param db: + Import database to use. + :type ip: + ``InteractiveShell`` + :param ip: + IPython shell instance if in IPython; ``None`` to assume not in IPython. + :param allow_eval: + Whether to allow evaluating code, which is necessary to allow completing + e.g. 'foo[0].bar' or 'foo().bar'. Note that IPython will only + pass such strings if IPCompleter.greedy is configured to True by the + user. + :rtype: + ``list`` of ``str`` + :return: + Completion candidates. + """ + namespaces = ScopeStack(namespaces) + logger.debug("complete_symbol(%r)", fullname) + splt = fullname.rsplit(".", 1) + attrname = splt[-1] + # The right-hand-side of the split (the part to complete, possibly the + # fullname) must be a prefix of a valid symbol. Otherwise don't bother + # generating completions. + # As for the left-hand-side of the split, load_symbol() will validate it + # or evaluate it depending on ``allow_eval``. + if not is_identifier(attrname, prefix=True): + return [] + # Get the database of known imports. + db = ImportDB.interpret_arg(db, target_filename=".") + known = db.known_imports + if len(splt) == 1: + # Check global names, including global-level known modules and + # importable modules. + results = set() + for ns in namespaces: + for name in ns: + if '.' not in name: + results.add(name) + results.update(known.member_names.get("", [])) + results.update([str(m) for m in ModuleHandle.list()]) + assert all('.' not in r for r in results) + results = sorted([r for r in results if r.startswith(attrname)]) + elif len(splt) == 2: + # Check members, including known sub-modules and importable sub-modules. + pname = splt[0] + if allow_eval: + # Evaluate the parent, with autoimporting. + ns_g, ns_l = namespaces.merged_to_two() + # TODO: only catch exceptions around the eval, not the other stuff + # (loading import db, etc). + try: + parent = auto_eval(pname, globals=ns_g, locals=ns_l, db=db) + except Exception as e: + logger.debug("complete_symbol(%r): couldn't evaluate %r: %s: %s", + fullname, pname, type(e).__name__, e) + return [] + else: + try: + parent = load_symbol(pname, namespaces, autoimport=True, db=db, + autoimported=autoimported) + except LoadSymbolError as e2: + # Even after attempting auto-import, the symbol is still + # unavailable, or some other error occurred. Nothing to complete. + e = getattr(e2, "__cause__", e2) + logger.debug("complete_symbol(%r): couldn't load symbol %r: %s: %s", + fullname, pname, type(e).__name__, e) + return [] + logger.debug("complete_symbol(%r): %s == %r", fullname, pname, parent) + results = set() + # Add current attribute members. + results.update(_list_members_for_completion(parent, ip)) + # Is the parent a package/module? + if sys.modules.get(pname, Ellipsis) is parent and parent.__name__ == pname: + # Add known_imports entries from the database. + results.update(known.member_names.get(pname, [])) + # Get the module handle. Note that we use ModuleHandle() on the + # *name* of the module (``pname``) instead of the module instance + # (``parent``). Using the module instance normally works, but + # breaks if the module hackily replaced itself with a pseudo + # module (e.g. https://github.com/josiahcarlson/mprop). + pmodule = ModuleHandle(pname) + # Add importable submodules. + results.update([m.name.parts[-1] for m in pmodule.submodules]) + results = sorted([r for r in results if r.startswith(attrname)]) + results = ["%s.%s" % (pname, r) for r in results] + else: + raise AssertionError + if six.PY2: + results = [unicode(s) for s in results] + logger.debug("complete_symbol(%r) => %r", fullname, results) + return results + + +def _list_members_for_completion(obj, ip): + """ + Enumerate the existing member attributes of an object. + This emulates the regular Python/IPython completion items. + + It does not include not-yet-imported submodules. + + :param obj: + Object whose member attributes to enumerate. + :rtype: + ``list`` of ``str`` + """ + if ip is None: + words = dir(obj) + else: + try: + limit_to__all__ = ip.Completer.limit_to__all__ + except AttributeError: + limit_to__all__ = False + if limit_to__all__ and hasattr(obj, '__all__'): + words = getattr(obj, '__all__') + elif "IPython.core.error" in sys.modules: + from IPython.utils import generics + from IPython.utils.dir2 import dir2 + from IPython.core.error import TryNext + words = dir2(obj) + try: + words = generics.complete_object(obj, words) + except TryNext: + pass + else: + words = dir(obj) + return [w for w in words if isinstance(w, six.string_types)] + + +def _auto_import_in_pdb_frame(pdb_instance, arg): + frame = pdb_instance.curframe + namespaces = [ frame.f_globals, pdb_instance.curframe_locals ] + filename = frame.f_code.co_filename + if not filename or filename.startswith("<"): + filename = "." + db = ImportDB.get_default(filename) + auto_import(arg, namespaces=namespaces, db=db) + + +def _enable_pdb_hooks(pdb_instance): + # Enable hooks in pdb.Pdb. + # Should be called after pdb.Pdb.__init__(). + logger.debug("_enable_pdb_hooks(%r)", pdb_instance) + # Patch Pdb._getval() to use auto_eval. + # This supports 'ipdb> p foo'. + @advise(pdb_instance._getval) + def _getval_with_autoimport(arg): + logger.debug("Pdb._getval(%r)", arg) + _auto_import_in_pdb_frame(pdb_instance, arg) + return __original__(arg) + # Patch Pdb.default() to use auto_import. + # This supports 'ipdb> foo()'. + @advise(pdb_instance.default) + def default_with_autoimport(arg): + logger.debug("Pdb.default(%r)", arg) + if arg.startswith("!"): + arg = arg[1:] + _auto_import_in_pdb_frame(pdb_instance, arg) + return __original__(arg) + + +def _enable_terminal_pdb_hooks(pdb_instance, auto_importer=None): + # Should be called after TerminalPdb.__init__(). + # Tested with IPython 5.8 with prompt_toolkit. + logger.debug("_enable_terminal_pdb_hooks(%r)", pdb_instance) + ptcomp = getattr(pdb_instance, "_ptcomp", None) + completer = getattr(ptcomp, "ipy_completer", None) + logger.debug("_enable_terminal_pdb_hooks(): completer=%r", completer) + if completer is not None and auto_importer is not None: + auto_importer._enable_completer_hooks(completer) + + +def _get_IPdb_class(): + """ + Get the IPython (core) Pdb class. + """ + try: + import IPython + except ImportError: + raise NoIPythonPackageError() + try: + # IPython 0.11+. Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, + # 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0 + from IPython.core import debugger + return debugger.Pdb + except ImportError: + pass + try: + # IPython 0.10 + from IPython import Debugger + return Debugger.Pdb + except ImportError: + pass + # IPython exists but couldn't figure out how to get Pdb. + raise RuntimeError( + "Couldn't get IPython Pdb. " + "Is your IPython version too old (or too new)? " + "IPython.__version__=%r" % (IPython.__version__)) + + +def _get_TerminalPdb_class(): + """ + Get the IPython TerminalPdb class. + """ + # The TerminalPdb subclasses the (core) Pdb class. If the TerminalPdb + # class is being used, then in that case we only need to advise + # TerminalPdb stuff, not (core) Pdb stuff. However, in some cases the + # TerminalPdb class is not used even if it exists, so we advise the (core) + # Pdb class separately. + try: + import IPython + del IPython + except ImportError: + raise NoIPythonPackageError() + try: + from IPython.terminal.debugger import TerminalPdb + return TerminalPdb + except ImportError: + pass + raise RuntimeError("Couldn't get TerminalPdb") + + +def new_IPdb_instance(): + """ + Create a new Pdb instance. + + If IPython is available, then use IPython's Pdb. Initialize a new IPython + terminal application if necessary. + + If the IPython package is not installed in the system, then use regular Pdb. + + Enable the auto importer. + + :rtype: + `Pdb` + """ + logger.debug("new_IPdb_instance()") + try: + app = get_ipython_terminal_app_with_autoimporter() + except Exception as e: + if isinstance(e, NoIPythonPackageError) or e.__class__.__name__ == "MultipleInstanceError": + logger.debug("%s: %s", type(e).__name__, e) + from pdb import Pdb + pdb_instance = Pdb() + _enable_pdb_hooks(pdb_instance) + _enable_terminal_pdb_hooks(pdb_instance) + return pdb_instance + else: + raise + pdb_class = _get_IPdb_class() + logger.debug("new_IPdb_instance(): pdb_class=%s", pdb_class) + color_scheme = _get_ipython_color_scheme(app) + pdb_instance = pdb_class(color_scheme) + _enable_pdb_hooks(pdb_instance) + _enable_terminal_pdb_hooks(pdb_instance) + return pdb_instance + + +def _get_ipython_color_scheme(app): + """ + Get the configured IPython color scheme. + + :type app: + `TerminalIPythonApp` + :param app: + An initialized IPython terminal application. + :rtype: + ``str`` + """ + try: + # Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, + # 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. + return app.shell.colors + except AttributeError: + pass + try: + # Tested with IPython 0.10. + import IPython + ipapi = IPython.ipapi.get() + return ipapi.options.colors + except AttributeError: + pass + import IPython + raise RuntimeError( + "Couldn't get IPython colors. " + "Is your IPython version too old (or too new)? " + "IPython.__version__=%r" % (IPython.__version__)) + + +def print_verbose_tb(*exc_info): + """ + Print a traceback, using IPython's ultraTB if possible. + + :param exc_info: + 3 arguments as returned by sys.exc_info(). + """ + if not exc_info: + exc_info = sys.exc_info() + elif len(exc_info) == 1 and isinstance(exc_info[0], tuple): + exc_info, = exc_info + if len(exc_info) != 3: + raise TypeError( + "Expected 3 items for exc_info; got %d" % len(exc_info)) + try: + # Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, + # 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. + from IPython.core.ultratb import VerboseTB + except ImportError: + try: + # Tested with IPython 0.10. + from IPython.ultraTB import VerboseTB + except ImportError: + VerboseTB = None + exc_type, exc_value, exc_tb = exc_info + # TODO: maybe use ip.showtraceback() instead? + if VerboseTB is not None: + VerboseTB(include_vars=False)(exc_type, exc_value, exc_tb) + else: + import traceback + def red(x): + return "\033[0m\033[31;1m%s\033[0m" % (x,) + exc_name = exc_type + try: + exc_name = exc_name.__name__ + except AttributeError: + pass + exc_name = str(exc_name) + print(red("---------------------------------------------------------------------------")) + print(red(exc_name.ljust(42)) + "Traceback (most recent call last)") + traceback.print_tb(exc_tb) + print() + print("%s: %s" % (red(exc_name), exc_value), + file=sys.stderr) + print() + + +@contextmanager +def UpdateIPythonStdioCtx(): + """ + Context manager that updates IPython's cached stdin/stdout/stderr handles + to match the current values of sys.stdin/sys.stdout/sys.stderr. + """ + if "IPython" not in sys.modules: + yield + return + if "IPython.utils.io" in sys.modules: + # Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, + # 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. + module = sys.modules["IPython.utils.io"] + container = module + IOStream = module.IOStream + elif "IPython.genutils" in sys.modules: + # Tested with IPython 0.10. + module = sys.modules["IPython.genutils"] + container = module.Term + IOStream = module.IOStream + else: + # IPython version too old or too new? + # For now just silently do nothing. + yield + return + old_stdin = container.stdin + old_stdout = container.stdout + old_stderr = container.stderr + try: + container.stdin = IOStream(sys.stdin) + container.stdout = IOStream(sys.stdout) + container.stderr = IOStream(sys.stderr) + yield + finally: + container.stdin = old_stdin + container.stdout = old_stdout + container.stderr = old_stderr + + + +class _EnableState(object): + DISABLING = "DISABLING" + DISABLED = "DISABLED" + ENABLING = "ENABLING" + ENABLED = "ENABLED" + + +class AutoImporter(object): + """ + Auto importer enable state. + + The state is attached to an IPython "application". + """ + + def __new__(cls, arg=Ellipsis): + """ + Get the AutoImporter for the given app, or create and assign one. + + :type arg: + `AutoImporter`, `BaseIPythonApplication`, `InteractiveShell` + """ + if isinstance(arg, AutoImporter): + return arg + # Check the type of the arg. Avoid isinstance because it's so hard + # to know where to import something from. + # Todo: make this more robust. + if arg is Ellipsis: + app = _get_ipython_app() + return cls._from_app(app) + clsname = type(arg).__name__ + if "App" in clsname: + return cls._from_app(arg) + elif "Shell" in clsname: + # If given an ``InteractiveShell`` argument, then get its parent app. + # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, + # 3.2, 4.0. + if hasattr(arg, 'parent') and getattr(arg.parent, 'shell', None) is arg: + app = arg.parent + return cls._from_app(app) + # Tested with IPython 0.10, 0.11, 0.12, 0.13. + app = _get_ipython_app() + if app.shell is arg: + return cls._from_app(app) + raise ValueError( + "Got a shell instance %r but couldn't match it to an app" + % (arg,)) + else: + raise TypeError("AutoImporter(): unexpected %s" % (clsname,)) + + @classmethod + def _from_app(cls, app): + subapp = getattr(app, "subapp", None) + if subapp is not None: + app = subapp + try: + self = app.auto_importer + except AttributeError: + pass + else: + assert isinstance(self, cls) + return self + # Create a new instance and assign to the app. + self = cls._construct(app) + app.auto_importer = self + return self + + @classmethod + def _construct(cls, app): + """ + Create a new AutoImporter for ``app``. + + :type app: + `IPython.core.application.BaseIPythonApplication` + """ + self = object.__new__(cls) + self.app = app + logger.debug("Constructing %r for app=%r, subapp=%r", self, app, + getattr(app, "subapp", None)) + # Functions to call to disable the auto importer. + self._disablers = [] + # Current enabling state. + self._state = _EnableState.DISABLED + # Whether there has been an error implying a bug in pyflyby code or a + # problem with the import database. + self._errored = False + # A reference to the IPython shell object. + self._ip = None + # The AST transformer, if any (IPython 1.0+). + self._ast_transformer = None + # Dictionary of things we've attempted to autoimport for this cell. + self._autoimported_this_cell = {} + return self + + def enable(self, even_if_previously_errored=False): + """ + Turn on the auto-importer in the current IPython session. + """ + # Check that we are not enabled/enabling yet. + if self._state is _EnableState.DISABLED: + pass + elif self._state is _EnableState.ENABLED: + logger.debug("Already enabled") + return + elif self._state is _EnableState.ENABLING: + logger.debug("Already enabling") + return + elif self._state is _EnableState.DISABLING: + logger.debug("Still disabling (run disable() to completion first)") + return + else: + raise AssertionError + self.reset_state_new_cell() + # Check if previously errored. + if self._errored: + if even_if_previously_errored: + self._errored = False + else: + # Be conservative: Once we've had problems, don't try again + # this session. Exceptions in the interactive loop can be + # annoying to deal with. + logger.warning( + "Not reattempting to enable auto importer after earlier " + "error") + return + import IPython + logger.debug("Enabling auto importer for IPython version %s, pid=%r", + IPython.__version__, os.getpid()) + logger.debug("enable(): state %s=>ENABLING", self._state) + self._errored = False + self._state = _EnableState.ENABLING + self._safe_call(self._enable_internal) + + def _continue_enable(self): + if self._state != _EnableState.ENABLING: + logger.debug("_enable_internal(): state = %s", self._state) + return + logger.debug("Continuing enabling auto importer") + self._safe_call(self._enable_internal) + + def _enable_internal(self): + # Main enabling entry point. This function can get called multiple + # times, depending on what's been initialized so far. + app = self.app + assert app is not None + if getattr(app, "subapp", None) is not None: + app = app.subapp + self.app = app + logger.debug("app = %r", app) + ok = True + ok &= self._enable_ipython_bugfixes() + ok &= self._enable_initializer_hooks(app) + ok &= self._enable_kernel_manager_hook(app) + ok &= self._enable_shell_hooks(app) + if ok: + logger.debug("_enable_internal(): success! state: %s=>ENABLED", + self._state) + self._state = _EnableState.ENABLED + elif self._pending_initializers: + logger.debug("_enable_internal(): did what we can for now; " + "will enable more after further IPython initialization. " + "state=%s", self._state) + else: + logger.debug("_enable_internal(): did what we can, but not " + "fully successful. state: %s=>ENABLED", + self._state) + self._state = _EnableState.ENABLED + + def _enable_initializer_hooks(self, app): + # Hook initializers. There are various things we want to hook, and + # the hooking needs to be done at different times, depending on the + # IPython version and the "app". For example, for most versions of + # IPython, terminal app, many things need to be done after + # initialize()/init_shell(); on the other hand, in some cases + # (e.g. IPython console), we need to do stuff *inside* the + # initialization function. + # Thus, we take a brute force approach: add hooks to a bunch of + # places, if they seem to not have run yet, and each time add any + # hooks that are ready to be added. + ok = True + pending = False + ip = getattr(app, "shell", None) + if ip is None: + if hasattr(app, "init_shell"): + @self._advise(app.init_shell) + def init_shell_enable_auto_importer(): + __original__() + logger.debug("init_shell() completed") + ip = app.shell + if ip is None: + logger.debug("Aborting enabling AutoImporter: " + "even after init_shell(), " + "still no shell in app=%r", app) + return + self._continue_enable() + elif not hasattr(app, "shell") and hasattr(app, "kernel_manager"): + logger.debug("No shell applicable; ok because using kernel manager") + pass + else: + logger.debug("App shell missing and no init_shell() to advise") + ok = False + if hasattr(app, "initialize_subcommand"): + # Hook the subapp, if any. This requires some cleverness: + # 'ipython console' requires us to do some stuff *before* + # initialize() is called on the new app, while 'ipython + # notebook' requires us to do stuff *after* initialize() is + # called. + @self._advise(app.initialize_subcommand) + def init_subcmd_enable_auto_importer(*args, **kwargs): + logger.debug("initialize_subcommand()") + from IPython.core.application import Application + @advise((Application, "instance")) + def app_instance_enable_auto_importer(cls, *args, **kwargs): + logger.debug("%s.instance()", cls.__name__) + app = __original__(cls, *args, **kwargs) + if app != self.app: + self.app = app + self._continue_enable() + return app + try: + __original__(*args, **kwargs) + finally: + app_instance_enable_auto_importer.unadvise() + self._continue_enable() + pending = True + if (hasattr(ip, "post_config_initialization") and + not hasattr(ip, "rl_next_input")): + # IPython 0.10 might not be ready to hook yet because we're called + # from the config phase, and certain stuff (like Completer) is set + # up in post-config. Re-run after post_config_initialization. + # Kludge: post_config_initialization() sets ip.rl_next_input=None, + # so detect whether it's been run by checking for that attribute. + @self._advise(ip.post_config_initialization) + def post_config_enable_auto_importer(): + __original__() + logger.debug("post_config_initialization() completed") + if not hasattr(ip, "rl_next_input"): + # Post-config initialization failed? + return + self._continue_enable() + pending = True + self._pending_initializers = pending + return ok + + def _enable_kernel_manager_hook(self, app): + # For IPython notebook, by the time we get here, there's generally a + # kernel_manager already assigned, but kernel_manager.start_kernel() + # hasn't been called yet. Hook app.kernel_manager.start_kernel(). + kernel_manager = getattr(app, "kernel_manager", None) + ok = True + if kernel_manager is not None: + ok &= self._enable_start_kernel_hook(kernel_manager) + # For IPython console, a single function constructs the kernel_manager + # and then immediately calls kernel_manager.start_kernel(). The + # easiest way to intercept start_kernel() is by installing a hook + # after the kernel_manager is constructed. + if getattr(app, "kernel_manager_class", None) is not None: + @self._advise((app, "kernel_manager_class")) + def kernel_manager_class_with_autoimport(*args, **kwargs): + logger.debug("kernel_manager_class_with_autoimport()") + kernel_manager = __original__(*args, **kwargs) + self._enable_start_kernel_hook(kernel_manager) + return kernel_manager + # It's OK if no kernel_manager nor kernel_manager_class; this is the + # typical case, when using regular IPython terminal console (not + # IPython notebook/console). + return True + + def _enable_start_kernel_hook(self, kernel_manager): + # Various IPython versions have different 'main' commands called from + # here, e.g. + # IPython 2: IPython.kernel.zmq.kernelapp.main + # IPython 3: IPython.kernel.__main__ + # IPython 4: ipykernel.__main__ + # These essentially all do 'kernelapp.launch_new_instance()' (imported + # from different places). We hook the guts of that to enable the + # autoimporter. + new_cmd = [ + '-c', + 'from pyflyby._interactive import start_ipython_kernel_with_autoimporter; ' + 'start_ipython_kernel_with_autoimporter()' + ] + try: + # Tested with Jupyter/IPython 4.0 + from jupyter_client.manager import KernelManager as JupyterKernelManager + except ImportError: + pass + else: + @self._advise(kernel_manager.start_kernel) + def start_kernel_with_autoimport_jupyter(*args, **kwargs): + logger.debug("start_kernel()") + # Advise format_kernel_cmd(), which is the function that + # computes the command line for a subprocess to run a new + # kernel. Note that we advise the method on the class, rather + # than this instance of kernel_manager, because start_kernel() + # actually creates a *new* KernelInstance for this. + @advise(JupyterKernelManager.format_kernel_cmd) + def format_kernel_cmd_with_autoimport(*args, **kwargs): + result = __original__(*args, **kwargs) + logger.debug("intercepting format_kernel_cmd(): orig = %r", result) + if (len(result) >= 3 and + result[1] == '-m' and + result[2] in ['ipykernel', 'ipykernel_launcher']): + result[1:3] = new_cmd + logger.debug("intercepting format_kernel_cmd(): new = %r", result) + return result + else: + logger.debug("intercepting format_kernel_cmd(): unexpected output; not modifying it") + return result + try: + return __original__(*args, **kwargs) + finally: + format_kernel_cmd_with_autoimport.unadvise() + return True + try: + # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, + # 3.2. + from IPython.kernel.manager import KernelManager as IPythonKernelManager + except ImportError: + pass + else: + @self._advise(kernel_manager.start_kernel) + def start_kernel_with_autoimport_ipython(*args, **kwargs): + logger.debug("start_kernel()") + # Advise format_kernel_cmd(), which is the function that + # computes the command line for a subprocess to run a new + # kernel. Note that we advise the method on the class, rather + # than this instance of kernel_manager, because start_kernel() + # actually creates a *new* KernelInstance for this. + @advise(IPythonKernelManager.format_kernel_cmd) + def format_kernel_cmd_with_autoimport(*args, **kwargs): + result = __original__(*args, **kwargs) + logger.debug("intercepting format_kernel_cmd(): orig = %r", result) + if result[1:3] in [ + # IPython 3.x + ['-m', 'IPython.kernel'], + # IPython 1.x, 2.x + ['-c', 'from IPython.kernel.zmq.kernelapp import main; main()'], + ]: + result[1:3] = new_cmd + logger.debug("intercepting format_kernel_cmd(): new = %r", result) + return result + else: + logger.debug("intercepting format_kernel_cmd(): unexpected output; not modifying it") + return result + try: + return __original__(*args, **kwargs) + finally: + format_kernel_cmd_with_autoimport.unadvise() + return True + # Tested with IPython 0.12, 0.13 + try: + import IPython.zmq.ipkernel + except ImportError: + pass + else: + @self._advise(kernel_manager.start_kernel) + def start_kernel_with_autoimport013(*args, **kwargs): + logger.debug("start_kernel()") + @advise((IPython.zmq.ipkernel, 'base_launch_kernel')) + def base_launch_kernel_with_autoimport(cmd, *args, **kwargs): + logger.debug("base_launch_kernel()") + expected_cmd = 'from IPython.zmq.ipkernel import main; main()' + if cmd != expected_cmd: + logger.debug("unexpected command, not modifying it: %r", cmd) + else: + cmd = ( + 'from pyflyby._interactive import start_ipython_kernel_with_autoimporter; ' + 'start_ipython_kernel_with_autoimporter()') + return __original__(cmd, *args, **kwargs) + try: + return __original__(*args, **kwargs) + finally: + base_launch_kernel_with_autoimport.unadvise() + return True + logger.debug("Couldn't enable start_kernel hook") + return False + + def _enable_shell_hooks(self, app): + """ + Enable hooks to run auto_import before code execution. + """ + # Check again in case this was registered delayed + if self._state != _EnableState.ENABLING: + return False + try: + ip = app.shell + except AttributeError: + logger.debug("_enable_shell_hooks(): no shell at all") + return True + if ip is None: + logger.debug("_enable_shell_hooks(): no shell yet") + return False + logger.debug("Enabling IPython shell hooks, shell=%r", ip) + self._ip = ip + # Notes on why we hook what we hook: + # + # There are many different places within IPython we can consider + # hooking/advising, depending on the version: + # * ip.input_transformer_manager.logical_line_transforms + # * ip.compile.ast_parse (IPython 0.12+) + # * ip.run_ast_nodes (IPython 0.11+) + # * ip.runsource (IPython 0.10) + # * ip.prefilter_manager.checks + # * ip.prefilter_manager.handlers["auto"] + # * ip.ast_transformers + # * ip.hooks['pre_run_code_hook'] + # * ip._ofind + # + # We choose to hook in two places: (1) _ofind and (2) + # ast_transformers. The motivation follows. We want to handle + # auto-imports for all of these input cases: + # (1) "foo.bar" + # (2) "arbitrarily_complicated_stuff((lambda: foo.bar)())" + # (3) "foo.bar?", "foo.bar??" (pinfo/pinfo2) + # (4) "foo.bar 1, 2" => "foo.bar(1, 2)" (autocall) + # + # Case 1 is the easiest and can be handled by nearly any method. Case + # 2 must be done either as a prefilter or as an AST transformer. + # Cases 3 and 4 must be done either as an input line transformer or by + # monkey-patching _ofind, because by the time the + # prefilter/ast_transformer is called, it's too late. + # + # To handle case 2, we use an AST transformer (for IPython > 1.0), or + # monkey-patch one of the compilation steps (ip.compile for IPython + # 0.10 and ip.run_ast_nodes for IPython 0.11-0.13). + # prefilter_manager.checks() is the "supported" way to add a + # pre-execution hook, but it only works for single lines, not for + # multi-line cells. (There is no explanation in the IPython source + # for why prefilter hooks are seemingly intentionally skipped for + # multi-line cells). + # + # To handle cases 3/4 (pinfo/autocall), we choose to advise _ofind. + # This is a private function that is called by both pinfo and autocall + # code paths. (Alternatively, we could have added something to the + # logical_line_transforms. The downside of that is that we would need + # to re-implement all the parsing perfectly matching IPython. + # Although monkey-patching is in general bad, it seems the lesser of + # the two evils in this case.) + # + # Since we have two invocations of auto_import(), case 1 is + # handled twice. That's fine, because it runs quickly. + ok = True + ok &= self._enable_reset_hook(ip) + ok &= self._enable_ofind_hook(ip) + ok &= self._enable_ast_hook(ip) + ok &= self._enable_time_hook(ip) + ok &= self._enable_timeit_hook(ip) + ok &= self._enable_prun_hook(ip) + ok &= self._enable_completion_hook(ip) + ok &= self._enable_run_hook(ip) + ok &= self._enable_debugger_hook(ip) + ok &= self._enable_ipython_shell_bugfixes(ip) + return ok + + def _enable_reset_hook(self, ip): + # Register a hook that resets autoimporter state per input cell. + # The only per-input-cell state we currently have is the recording of + # which autoimports we've attempted but failed. We keep track of this + # to avoid multiple error messages for a single import, in case of + # overlapping hooks. + # Note: Some of the below approaches (both registering an + # input_transformer_manager hook or advising reset()) cause the reset + # function to get called twice per cell. This seems like an + # unintentional repeated call in IPython itself. This is harmless for + # us, since doing an extra reset shouldn't hurt. + if hasattr(ip, "input_transformers_post"): + # In IPython 7.0+, the input transformer API changed. + def reset_auto_importer_state(line): + # There is a bug in IPython that causes the transformer to be + # called multiple times + # (https://github.com/ipython/ipython/issues/11714). Until it + # is fixed, workaround it by skipping one of the calls. + stack = inspect.stack() + if any([ + stack[3].function == 'run_cell_async', + # These are the other places it is called. + # stack[3].function == 'should_run_async', + # stack[1].function == 'check_complete' + ]): + return line + logger.debug("reset_auto_importer_state(%r)", line) + self.reset_state_new_cell() + return line + # on IPython 7.17 (July 2020) or above, the check_complete + # path of the code will not call transformer that have this magic attribute + # when trying to check whether the code is complete. + reset_auto_importer_state.has_side_effect = True + ip.input_transformers_cleanup.append(reset_auto_importer_state) + return True + elif hasattr(ip, "input_transformer_manager"): + # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, + # 3.2, 4.0. + class ResetAutoImporterState(object): + def push(self_, line): + return line + def reset(self_): + logger.debug("ResetAutoImporterState.reset()") + self.reset_state_new_cell() + t = ResetAutoImporterState() + transforms = ip.input_transformer_manager.python_line_transforms + transforms.append(t) + def unregister_input_transformer(): + try: + transforms.remove(t) + except ValueError: + logger.info( + "Couldn't remove python_line_transformer hook") + self._disablers.append(unregister_input_transformer) + return True + elif hasattr(ip, "input_splitter"): + # Tested with IPython 0.13. Also works with later versions, but + # for those versions, we can use a real hook instead of advising. + @self._advise(ip.input_splitter.reset) + def reset_input_splitter_and_autoimporter_state(): + logger.debug("reset_input_splitter_and_autoimporter_state()") + self.reset_state_new_cell() + return __original__() + return True + elif hasattr(ip, "resetbuffer"): + # Tested with IPython 0.10. + @self._advise(ip.resetbuffer) + def resetbuffer_and_autoimporter_state(): + logger.debug("resetbuffer_and_autoimporter_state") + self.reset_state_new_cell() + return __original__() + return True + else: + logger.debug("Couldn't enable reset hook") + return False + + def _enable_ofind_hook(self, ip): + """ + Enable a hook of _ofind(), which is used for pinfo, autocall, etc. + """ + # Advise _ofind. + if hasattr(ip, "_ofind"): + # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.3, + # 2.4, 3.0, 3.1, 3.2, 4.0. + @self._advise(ip._ofind) + def ofind_with_autoimport(oname, namespaces=None): + logger.debug("_ofind(oname=%r, namespaces=%r)", oname, namespaces) + is_multiline = False + if hasattr(ip, "buffer"): + # In IPython 0.10, _ofind() gets called for each line of a + # multiline input. Skip them. + is_multiline = len(ip.buffer) > 0 + if namespaces is None: + namespaces = _ipython_namespaces(ip) + if not is_multiline and is_identifier(oname, dotted=True): + self.auto_import(str(oname), [ns for nsname,ns in namespaces][::-1]) + result = __original__(oname, namespaces=namespaces) + return result + return True + else: + logger.debug("Couldn't enable ofind hook") + return False + + def _enable_ast_hook(self, ip): + """ + Enable a hook somewhere in the source => parsed AST => compiled code + pipeline. + """ + # Register an AST transformer. + if hasattr(ip, 'ast_transformers'): + logger.debug("Registering an ast_transformer") + # First choice: register a formal ast_transformer. + # Tested with IPython 1.0, 1.2, 2.0, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. + class _AutoImporter_ast_transformer(object): + """ + A NodeVisitor-like wrapper around ``auto_import_for_ast`` for + the API that IPython 1.x's ``ast_transformers`` needs. + """ + def visit(self_, node): + # We don't actually transform the node; we just use + # the ast_transformers mechanism instead of the + # prefilter mechanism as an optimization to avoid + # re-parsing the text into an AST. + # + # We use raise_on_error=False to avoid propagating any + # exceptions here. That would cause IPython to try to + # remove the ast_transformer. On error, we've already + # done that ourselves. + logger.debug("_AutoImporter_ast_transformer.visit()") + self.auto_import(node, raise_on_error=False) + return node + self._ast_transformer = t = _AutoImporter_ast_transformer() + ip.ast_transformers.append(t) + def unregister_ast_transformer(): + try: + ip.ast_transformers.remove(t) + except ValueError: + logger.info( + "Couldn't remove ast_transformer hook - already gone?") + self._ast_transformer = None + self._disablers.append(unregister_ast_transformer) + return True + elif hasattr(ip, "run_ast_nodes"): + # Second choice: advise the run_ast_nodes() function. Tested with + # IPython 0.11, 0.12, 0.13. This is the most robust way available + # for those versions. + # (ip.compile.ast_parse also works in IPython 0.12-0.13; no major + # flaw, but might as well use the same mechanism that works in + # 0.11.) + @self._advise(ip.run_ast_nodes) + def run_ast_nodes_with_autoimport(nodelist, *args, **kwargs): + logger.debug("run_ast_nodes") + ast_node = ast.Module(nodelist) + self.auto_import(ast_node) + return __original__(nodelist, *args, **kwargs) + return True + elif hasattr(ip, 'compile'): + # Third choice: Advise ip.compile. + # Tested with IPython 0.10. + # We don't hook prefilter because that gets called once per line, + # not per multiline code. + # We don't hook runsource because that gets called incrementally + # with partial multiline source until the source is complete. + @self._advise((ip, "compile")) + def compile_with_autoimport(source, filename="", + symbol="single"): + result = __original__(source, filename, symbol) + if result is None: + # The original ip.compile is an instance of + # codeop.CommandCompiler. CommandCompiler.__call__ + # returns None if the source is a possibly incomplete + # multiline block of code. In that case we don't + # autoimport yet. + pass + else: + # Got full code that our caller, runsource, will execute. + self.auto_import(source) + return result + return True + else: + logger.debug("Couldn't enable parse hook") + return False + + def _enable_time_hook(self, ip): + """ + Enable a hook so that %time will autoimport. + """ + # For IPython 1.0+, the ast_transformer takes care of it. + if self._ast_transformer: + return True + # Otherwise, we advise %time to temporarily override the compile() + # builtin within it. + if hasattr(ip, 'magics_manager'): + # Tested with IPython 0.13. (IPython 1.0+ also has + # magics_manager, but for those versions, ast_transformer takes + # care of %time.) + line_magics = ip.magics_manager.magics['line'] + @self._advise((line_magics, 'time')) + def time_with_autoimport(*args, **kwargs): + logger.debug("time_with_autoimport()") + wrapped = FunctionWithGlobals( + __original__, compile=self.compile_with_autoimport) + return wrapped(*args, **kwargs) + return True + elif hasattr(ip, 'magic_time'): + # Tested with IPython 0.10, 0.11, 0.12 + @self._advise(ip.magic_time) + def magic_time_with_autoimport(*args, **kwargs): + logger.debug("time_with_autoimport()") + wrapped = FunctionWithGlobals( + __original__, compile=self.compile_with_autoimport) + return wrapped(*args, **kwargs) + return True + else: + logger.debug("Couldn't enable time hook") + return False + + def _enable_timeit_hook(self, ip): + """ + Enable a hook so that %timeit will autoimport. + """ + # For IPython 1.0+, the ast_transformer takes care of it. + if self._ast_transformer: + return True + # Otherwise, we advise %timeit to temporarily override the compile() + # builtin within it. + if hasattr(ip, 'magics_manager'): + # Tested with IPython 0.13. (IPython 1.0+ also has + # magics_manager, but for those versions, ast_transformer takes + # care of %timeit.) + line_magics = ip.magics_manager.magics['line'] + @self._advise((line_magics, 'timeit')) + def timeit_with_autoimport(*args, **kwargs): + logger.debug("timeit_with_autoimport()") + wrapped = FunctionWithGlobals( + __original__, compile=self.compile_with_autoimport) + return wrapped(*args, **kwargs) + return True + elif hasattr(ip, 'magic_timeit'): + # Tested with IPython 0.10, 0.11, 0.12 + @self._advise(ip.magic_timeit) + def magic_timeit_with_autoimport(*args, **kwargs): + logger.debug("timeit_with_autoimport()") + wrapped = FunctionWithGlobals( + __original__, compile=self.compile_with_autoimport) + return wrapped(*args, **kwargs) + return True + else: + logger.debug("Couldn't enable timeit hook") + return False + + def _enable_prun_hook(self, ip): + """ + Enable a hook so that %prun will autoimport. + """ + if hasattr(ip, 'magics_manager'): + # Tested with IPython 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, + # 3.1, 3.2, 4.0. + line_magics = ip.magics_manager.magics['line'] + execmgr = six.get_method_self(line_magics['prun'])#.im_self + if hasattr(execmgr, "_run_with_profiler"): + @self._advise(execmgr._run_with_profiler) + def run_with_profiler_with_autoimport(code, opts, namespace): + logger.debug("run_with_profiler_with_autoimport()") + self.auto_import(code, [namespace]) + return __original__(code, opts, namespace) + return True + else: + # Tested with IPython 0.13. + class ProfileFactory_with_autoimport(object): + def Profile(self_, *args): + import profile + p = profile.Profile() + @advise(p.runctx) + def runctx_with_autoimport(cmd, globals, locals): + self.auto_import(cmd, [globals, locals]) + return __original__(cmd, globals, locals) + return p + @self._advise((line_magics, 'prun')) + def prun_with_autoimport(*args, **kwargs): + logger.debug("prun_with_autoimport()") + wrapped = FunctionWithGlobals( + __original__, profile=ProfileFactory_with_autoimport()) + return wrapped(*args, **kwargs) + return True + elif hasattr(ip, "magic_prun"): + # Tested with IPython 0.10, 0.11, 0.12. + class ProfileFactory_with_autoimport(object): + def Profile(self_, *args): + import profile + p = profile.Profile() + @advise(p.runctx) + def runctx_with_autoimport(cmd, globals, locals): + self.auto_import(cmd, [globals, locals]) + return __original__(cmd, globals, locals) + return p + @self._advise(ip.magic_prun) + def magic_prun_with_autoimport(*args, **kwargs): + logger.debug("magic_prun_with_autoimport()") + wrapped = FunctionWithGlobals( + __original__, profile=ProfileFactory_with_autoimport()) + return wrapped(*args, **kwargs) + return True + else: + logger.debug("Couldn't enable prun hook") + return False + + def _enable_completer_hooks(self, completer): + # Hook a completer instance. + # + # This is called: + # - initially when enabling pyflyby + # - each time we enter the debugger, since each Pdb instance has its + # own completer + # + # There are a few different places within IPython we can consider + # hooking/advising: + # * ip.completer.custom_completers / ip.set_hook("complete_command") + # * ip.completer.python_matches + # * ip.completer.global_matches + # * ip.completer.attr_matches + # * ip.completer.python_func_kw_matches + # + # The "custom_completers" list, which set_hook("complete_command") + # manages, is not useful because that only works for specific commands. + # (A "command" refers to the first word on a line, such as "cd".) + # + # We choose to advise global_matches() and attr_matches(), which are + # called to enumerate global and non-global attribute symbols + # respectively. (python_matches() calls these two. We advise + # global_matches() and attr_matches() instead of python_matches() + # because a few other functions call global_matches/attr_matches + # directly.) + logger.debug("_enable_completer_hooks(%r)", completer) + if hasattr(completer, "global_matches"): + # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.3, + # 2.4, 3.0, 3.1, 3.2, 4.0, 5.8. + try: + completer.shell.pt_cli + is_pt = True + except AttributeError: + is_pt = False + if is_pt: + def get_completer_namespaces(): + return [completer.namespace, completer.global_namespace] + else: + def get_completer_namespaces(): + # For non-prompt_toolkit, (1) completer.namespace is not + # reliable inside pdb, and (2) _get_pdb_if_is_in_pdb() is + # reliable inside pdb because no threading. + # Use get_global_namespaces(), which relies on + # _get_pdb_if_is_in_pdb(). + return None + if getattr(completer, 'use_jedi', False): + # IPython 6.0+ uses jedi completion by default, which bypasses + # the global and attr matchers. For now we manually reenable + # them. A TODO would be to hook the Jedi completer itself. + if completer.python_matches not in completer.matchers: + @self._advise(type(completer).matchers) + def matchers_with_python_matches(completer): + return __original__.fget(completer)+[completer.python_matches] + + @self._advise(completer.global_matches) + def global_matches_with_autoimport(fullname): + if len(fullname) == 0: + return [] + logger.debug("global_matches_with_autoimport(%r)", fullname) + namespaces = get_completer_namespaces() + return self.complete_symbol(fullname, namespaces, on_error=__original__) + @self._advise(completer.attr_matches) + def attr_matches_with_autoimport(fullname): + logger.debug("attr_matches_with_autoimport(%r)", fullname) + namespaces = get_completer_namespaces() + return self.complete_symbol(fullname, namespaces, on_error=__original__) + + return True + elif hasattr(completer, "complete_request"): + # This is a ZMQCompleter, so nothing to do. + return True + else: + logger.debug("Couldn't enable completion hook") + return False + + def _enable_completion_hook(self, ip): + """ + Enable a tab-completion hook. + """ + return self._enable_completer_hooks(getattr(ip, "Completer", None)) + + def _enable_run_hook(self, ip): + """ + Enable a hook so that %run will autoimport. + """ + if hasattr(ip, "safe_execfile"): + # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.3, + # 2.4, 3.0, 3.1, 3.2, 4.0. + @self._advise(ip.safe_execfile) + def safe_execfile_with_autoimport(filename, + globals=None, locals=None, + **kwargs): + logger.debug("safe_execfile %r", filename) + if globals is None: + globals = {} + if locals is None: + locals = globals + namespaces = [globals, locals] + try: + block = PythonBlock(Filename(filename)) + ast_node = block.ast_node + self.auto_import(ast_node, namespaces) + except Exception as e: + logger.error("%s: %s", type(e).__name__, e) + return __original__(filename, *namespaces, **kwargs) + return True + else: + logger.debug("Couldn't enable execfile hook") + return False + + def _enable_debugger_hook(self, ip): + try: + Pdb = _get_IPdb_class() + except Exception as e: + logger.debug("Couldn't locate Pdb class: %s: %s", + type(e).__name__, e) + return False + try: + TerminalPdb = _get_TerminalPdb_class() + except Exception as e: + logger.debug("Couldn't locate TerminalPdb class: %s: %s", + type(e).__name__, e) + TerminalPdb = None + @contextmanager + def HookPdbCtx(): + def Pdb_with_autoimport(self_pdb, *args): + __original__(self_pdb, *args) + _enable_pdb_hooks(self_pdb) + def TerminalPdb_with_autoimport(self_pdb, *args): + __original__(self_pdb, *args) + _enable_terminal_pdb_hooks(self_pdb, self) + with AdviceCtx(Pdb.__init__, Pdb_with_autoimport): + if TerminalPdb is None: + yield + else: + with AdviceCtx(TerminalPdb.__init__, TerminalPdb_with_autoimport): + yield + iptb = getattr(ip, "InteractiveTB", None) + ok = True + if hasattr(iptb, "debugger"): + # Hook ip.InteractiveTB.debugger(). This implements auto + # importing for "%debug" (postmortem mode). + # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, + # 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. + @self._advise(iptb.debugger) + def debugger_with_autoimport(*args, **kwargs): + with HookPdbCtx(): + return __original__(*args, **kwargs) + else: + ok = False + if hasattr(ip, 'magics_manager'): + # Hook ExecutionMagics._run_with_debugger(). This implements auto + # importing for "%debug ". + # Tested with IPython 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, + # 3.1, 3.2, 4.0, 5.8. + line_magics = ip.magics_manager.magics['line'] + execmgr = six.get_method_self(line_magics['debug']) + if hasattr(execmgr, "_run_with_debugger"): + @self._advise(execmgr._run_with_debugger) + def run_with_debugger_with_autoimport(code, code_ns, + filename=None, + *args, **kwargs): + db = ImportDB.get_default(filename or ".") + auto_import(code, namespaces=[code_ns], db=db) + with HookPdbCtx(): + return __original__(code, code_ns, filename, + *args, **kwargs + ) + else: + # IPython 0.13 and earlier don't have "%debug ". + pass + else: + ok = False + return ok + + + def _enable_ipython_shell_bugfixes(self, ip): + """ + Enable some advice that's actually just fixing bugs in IPython. + """ + # IPython 2.x on Python 2.x has a bug where 'run -n' doesn't work + # because it uses Unicode for the module name. This is a bug in + # IPython itself ("run -n" is plain broken for ipython-2.x on + # python-2.x); we patch it here. + if (PY2 and + hasattr(ip, "new_main_mod")): + try: + args = inspect.getargspec(ip.new_main_mod).args + except Exception: + # getargspec fails if we already advised. + # For now just skip under the assumption that we already + # advised (or the code changed in some way that doesn't + # require advising?) + # Minor todo: Ideally we would be relying on _advise to check + # that we haven't already advised. + args = None + if args == ["self","filename","modname"]: + @self._advise(ip.new_main_mod) + def new_main_mod_fix_str(filename, modname): + if six.PY2: + if type(modname) is unicode: + modname = str(modname) + return __original__(filename, modname) + return True + + def _enable_ipython_bugfixes(self): + """ + Enable some advice that's actually just fixing bugs in IPython. + """ + ok = True + ok &= self._enable_ipython_bugfixes_LevelFormatter() + return ok + + def _enable_ipython_bugfixes_LevelFormatter(self): + # New versions of IPython complain if you import 'IPython.config'. + # Old versions of IPython already have it imported. + if 'IPython.config' not in sys.modules: + return True + try: + from IPython.config.application import LevelFormatter + except ImportError: + return True + if (not issubclass(LevelFormatter, object) and + "super" in LevelFormatter.format.__func__.__code__.co_names and + "logging" not in LevelFormatter.format.__func__.__code__.co_names): + # In IPython 1.0, LevelFormatter uses super(), which assumes + # that logging.Formatter is a subclass of object. However, + # this is only true in Python 2.7+, not in Python 2.6. So + # Python 2.6 + IPython 1.0 causes problems. IPython 1.2 + # already includes this fix. + from logging import Formatter + @self._advise(LevelFormatter.format) + def format_patched(self, record): + if record.levelno >= self.highlevel_limit: + record.highlevel = self.highlevel_format % record.__dict__ + else: + record.highlevel = "" + return Formatter.format(self, record) + return True + + def disable(self): + """ + Turn off auto-importer in the current IPython session. + """ + if self._state is _EnableState.DISABLED: + logger.debug("disable(): already disabled") + return + logger.debug("disable(): state: %s=>DISABLING", self._state) + self._state = _EnableState.DISABLING + while self._disablers: + f = self._disablers.pop(-1) + try: + f() + except Exception as e: + self._errored = True + logger.error("Error while disabling: %s: %s", type(e).__name__, e) + if logger.debug_enabled: + raise + else: + logger.info( + "Set the env var PYFLYBY_LOG_LEVEL=DEBUG to debug.") + logger.debug("disable(): state: %s=>DISABLED", self._state) + self._state = _EnableState.DISABLED + + def _safe_call(self, function, *args, **kwargs): + on_error = kwargs.pop("on_error", None) + raise_on_error = kwargs.pop("raise_on_error", "if_debug") + if self._errored: + # If we previously errored, then we should already have + # unregistered the hook that led to here. However, in some corner + # cases we can get called one more time. If so, go straight to + # the on_error case. + pass + else: + try: + return function(*args, **kwargs) + except Exception as e: + # Something went wrong. Remember that we've had a problem. + self._errored = True + logger.error("%s: %s", type(e).__name__, e) + if not logger.debug_enabled: + logger.info( + "Set the env var PYFLYBY_LOG_LEVEL=DEBUG to debug.") + logger.warning("Disabling pyflyby auto importer.") + # Disable everything. If something's broken, chances are + # other stuff is broken too. + try: + self.disable() + except Exception as e2: + logger.error("Error trying to disable: %s: %s", + type(e2).__name__, e2) + # Raise or print traceback in debug mode. + if raise_on_error == True: + raise + elif raise_on_error == 'if_debug': + if logger.debug_enabled: + if type(e) == SyntaxError: + # The traceback for SyntaxError tends to get + # swallowed, so print it out now. + import traceback + traceback.print_exc() + raise + elif raise_on_error == False: + if logger.debug_enabled: + import traceback + traceback.print_exc() + else: + logger.error("internal error: invalid raise_on_error=%r", + raise_on_error) + # Return what user wanted to in case of error. + if on_error: + return on_error(*args, **kwargs) + else: + return None # just to be explicit + + def reset_state_new_cell(self): + # Reset the state for a new cell. + if logger.debug_enabled: + autoimported = self._autoimported_this_cell + logger.debug("reset_state_new_cell(): previously autoimported: " + "succeeded=%s, failed=%s", + sorted([k for k,v in autoimported.items() if v]), + sorted([k for k,v in autoimported.items() if not v])) + self._autoimported_this_cell = {} + + def auto_import(self, arg, namespaces=None, + raise_on_error='if_debug', on_error=None): + if namespaces is None: + namespaces = get_global_namespaces(self._ip) + + def post_import_hook(imp): + send_comm_message(MISSING_IMPORTS, {"missing_imports": str(imp)}) + + return self._safe_call( + auto_import, arg, namespaces, + autoimported=self._autoimported_this_cell, + raise_on_error=raise_on_error, on_error=on_error, + post_import_hook=post_import_hook) + + def complete_symbol(self, fullname, namespaces, + raise_on_error='if_debug', on_error=None): + with InterceptPrintsDuringPromptCtx(self._ip): + if namespaces is None: + namespaces = get_global_namespaces(self._ip) + if on_error is not None: + def on_error1(fullname, namespaces, autoimported, ip, allow_eval): + return on_error(fullname) + else: + on_error1 = None + return self._safe_call( + complete_symbol, fullname, namespaces, + autoimported=self._autoimported_this_cell, + ip=self._ip, allow_eval=True, + raise_on_error=raise_on_error, on_error=on_error1) + + def compile_with_autoimport(self, src, filename, mode, flags=0): + logger.debug("compile_with_autoimport(%r)", src) + ast_node = compile(src, filename, mode, flags|ast.PyCF_ONLY_AST, + dont_inherit=True) + self.auto_import(ast_node) + if flags & ast.PyCF_ONLY_AST: + return ast_node + else: + return compile(ast_node, filename, mode, flags, dont_inherit=True) + + def _advise(self, joinpoint): + def advisor(f): + aspect = Aspect(joinpoint) + if aspect.advise(f, once=True): + self._disablers.append(aspect.unadvise) + return advisor + + + +def enable_auto_importer(if_no_ipython='raise'): + """ + Turn on the auto-importer in the current IPython application. + + :param if_no_ipython: + If we are not inside IPython and if_no_ipython=='ignore', then silently + do nothing. + If we are not inside IPython and if_no_ipython=='raise', then raise + NoActiveIPythonAppError. + """ + try: + app = _get_ipython_app() + except NoActiveIPythonAppError: + if if_no_ipython=='ignore': + return + else: + raise + auto_importer = AutoImporter(app) + auto_importer.enable() + + +def disable_auto_importer(): + """ + Turn off the auto-importer in the current IPython application. + """ + try: + app = _get_ipython_app() + except NoActiveIPythonAppError: + return + auto_importer = AutoImporter(app) + auto_importer.disable() + + +def load_ipython_extension(arg=Ellipsis): + """ + Turn on pyflyby features, including the auto-importer, for the given + IPython shell. + + Clear the ImportDB cache of known-imports. + + This function is used by IPython's extension mechanism. + + To load pyflyby in an existing IPython session, run:: + + In [1]: %load_ext pyflyby + + To refresh the imports database (if you modified ~/.pyflyby), run:: + + In [1]: %reload_ext pyflyby + + To load pyflyby automatically on IPython startup, appendto + ~/.ipython/profile_default/ipython_config.py:: + c.InteractiveShellApp.extensions.append("pyflyby") + + :type arg: + ``InteractiveShell`` + :see: + http://ipython.org/ipython-doc/dev/config/extensions/index.html + """ + logger.debug("load_ipython_extension() called for %s", + os.path.dirname(__file__)) + # Turn on the auto-importer. + auto_importer = AutoImporter(arg) + auto_importer.enable(even_if_previously_errored=True) + # Clear ImportDB cache. + ImportDB.clear_default_cache() + # Clear the set of errored imports. + clear_failed_imports_cache() + # Enable debugging tools. These aren't IPython-specific, and are better + # put in usercustomize.py. But this is a convenient way for them to be + # loaded. They're fine to run again even if they've already been run via + # usercustomize.py. + from ._dbg import (enable_faulthandler, + enable_signal_handler_debugger, + enable_sigterm_handler, + add_debug_functions_to_builtins) + enable_faulthandler() + enable_signal_handler_debugger() + enable_sigterm_handler(on_existing_handler='keep_existing') + add_debug_functions_to_builtins() + initialize_comms() + + +def unload_ipython_extension(arg=Ellipsis): + """ + Turn off pyflyby features, including the auto-importer. + + This function is used by IPython's extension mechanism. + + To unload interactively, run:: + + In [1]: %unload_ext pyflyby + """ + logger.debug("unload_ipython_extension() called for %s", + os.path.dirname(__file__)) + auto_importer = AutoImporter(arg) + auto_importer.disable() + remove_comms() + # TODO: disable signal handlers etc. diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_livepatch.py b/.venv/lib/python3.8/site-packages/pyflyby/_livepatch.py new file mode 100644 index 0000000..a1445a7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_livepatch.py @@ -0,0 +1,807 @@ +# pyflyby/_livepatch.py +# Copyright (C) 2011, 2012, 2013, 2014, 2015 Karl Chen. + +r""" +livepatch/xreload: Alternative to reload(). + +xreload performs a "live patch" of the modules/classes/functions/etc that have +already been loaded in memory. It does so by executing the module in a +scratch namespace, and then patching classes, methods and functions in-place. +New objects are copied into the target namespace. + +This addresses cases where one module imported functions from another +module. + +For example, suppose m1.py contains:: + + from m2 import foo + def print_foo(): + return foo() + +and m2.py contains:: + + def foo(): + return 42 + +If you edit m2.py and modify ``foo``, then reload(m2) on its own would not do +what you want. You would also need to reload(m1) after reload(m2). This is +because the built-in reload affects the module being reloaded, but references +to the old module remain. On the other hand, xreload() patches the existing +m2.foo, so that live references to it are updated. + +In table form:: + + Undesired effect: reload(m2) + Undesired effect: reload(m1); reload(m2) + Desired effect: reload(m2); reload(m1) + + Desired effect: xreload(m2) + Desired effect: xreload(m1); xreload(m2) + Desired effect: xreload(m2); xreload(m1) + +Even with just two modules, we can see that xreload() is an improvement. When +working with a large set of interdependent modules, it becomes infeasible to +know the precise sequence of reload() calls that would be necessary. +xreload() really shines in that case. + +This implementation of xreload() was originally based the following +mailing-list post by Guido van Rossum: + + https://mail.python.org/pipermail/edu-sig/2007-February/007787.html + +Customizing behavior +==================== + +If a class/function/module/etc has an attribute __livepatch__, then this +function is called *instead* of performing the regular livepatch mechanism. + +The __livepatch__() function is called with the following arguments: + + - ``old`` : The object to be updated with contents of ``new`` + - ``new`` : The object whose contents to put into ``old`` + - ``do_livepatch``: A function that can be called to do the standard + livepatch, replacing the contents of ``old`` with ``new``. + If it's not possible to livepatch ``old``, it returns + ``new``. The ``do_livepatch`` function takes no arguments. + Calling the ``do_livepatch`` function is roughly + equivalent to calling ``pyflyby.livepatch(old, new, + modname=modname, heed_hook=False)``. + - ``modname`` : The module currently being updated. Recursively called + updates should keep track of the module being updated to + avoid touching other modules. + +These arguments are matched by *name* and are passed only if the +``__livepatch__`` function is declared to take such named arguments or it takes +\**kwargs. If the ``__livepatch__`` function takes \**kwargs, it should ignore +unknown arguments, in case new parameters are added in the future. + +If the object being updated is an object instance, and ``__livepatch__`` is a +method, then the function is bound to the new object, i.e. the ``self`` +parameter is the same as ``new``. + +If the ``__livepatch__`` function successfully patched the ``old`` object, then +it should return ``old``. If it is unable to patch, it should return ``new``. + +Examples +-------- + +By default, any attributes on an existing function are updated with ones from +the new function. If you want a memoized function to keep its cache across +xreload, you could implement that like this:: + + def memoize(function): + cache = {} + def wrapped_fn(*args): + try: + return cache[args] + except KeyError: + result = function(*args) + cache[args] = result + return result + wrapped_fn.cache = cache + def my_livepatch(old, new, do_livepatch): + keep_cache = dict(old.cache) + result = do_livepatch() + result.cache.update(keep_cache) + return result + wrapped_fn.__livepatch__ = my_livepatch + return wrapped_fn + +XXX change example b/c cache is already cleared by default +XXX maybe global cache + + class MyObj(...): + def __livepatch__(self, old): + self.__dict__.update(old.__dict__) + return self + + class MyObj(...): + def __init__(self): + self._my_cache = {} + + def __livepatch__(self, old, do_livepatch): + keep_cache = dict(old._my_cache) + result = do_livepatch() + result._my_cache.update(keep_cache) + return result + +XXX test + +""" + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import ast +import os +import re +import six +import sys +import time +import types + +from six import PY2 +from six.moves import reload_module + +import inspect +from pyflyby._log import logger + + +# Keep track of when the process was started. +if os.uname()[0] == 'Linux': + _PROCESS_START_TIME = os.stat("/proc/%d"%os.getpid()).st_ctime +else: + try: + import psutil + except ImportError: + # Todo: better fallback + _PROCESS_START_TIME = time.time() + else: + _PROCESS_START_TIME = psutil.Process(os.getpid()).create_time() + + +class UnknownModuleError(ImportError): + pass + + +def livepatch(old, new, modname=None, + visit_stack=(), cache=None, assume_type=None, + heed_hook=True): + """ + Livepatch ``old`` with contents of ``new``. + + If ``old`` can't be livepatched, then return ``new``. + + :param old: + The object to be updated + :param new: + The object used as the source for the update. + :type modname: + ``str`` + :param modname: + Only livepatch ``old`` if it was defined in the given fully-qualified + module name. If ``None``, then update regardless of module. + :param assume_type: + Update as if both ``old`` and ``new`` were of type ``assume_type``. If + ``None``, then ``old`` and ``new`` must have the same type. + For internal use. + :param cache: + Cache of already-updated objects. Map from (id(old), id(new)) to result. + :param visit_stack: + Ids of objects that are currently being updated. + Used to deal with reference cycles. + For internal use. + :param heed_hook: + If ``True``, heed the ``__livepatch__`` hook on ``new``, if any. + If ``False``, ignore any ``__livepatch__`` hook on ``new``. + :return: + Either live-patched ``old``, or ``new``. + """ + if old is new: + return new + # If we're already visiting this object (due to a reference cycle), then + # don't recurse again. + if id(old) in visit_stack: + return old + if cache is None: + cache = {} + cachekey = (id(old), id(new)) + try: + return cache[cachekey] + except KeyError: + pass + visit_stack += (id(old),) + def do_livepatch(): + new_modname = _get_definition_module(new) + if modname and new_modname and new_modname != modname: + # Ignore objects that have been imported from another module. + # Just update their references. + return new + if assume_type is not None: + use_type = assume_type + else: + oldtype = type(old) + newtype = type(new) + if oldtype is newtype: + # Easy, common case: Type didn't change. + use_type = oldtype + elif (oldtype.__name__ == newtype.__name__ and + oldtype.__module__ == newtype.__module__ == modname and + getattr(sys.modules[modname], + newtype.__name__, None) is newtype and + oldtype is livepatch( + oldtype, newtype, modname=modname, + visit_stack=visit_stack, cache=cache)): + # Type of this object was defined in this module. This + # includes metaclasses defined in the same module. + use_type = oldtype + else: + # If the type changed, then give up. + return new + try: + mro = type.mro(use_type) + except TypeError: + mro = [use_type, object] # old-style class + # Dispatch on type. Include parent classes (in C3 linearized + # method resolution order), in particular so that this works on + # classes with custom metaclasses that subclass ``type``. + for t in mro: + try: + update = _LIVEPATCH_DISPATCH_TABLE[t] + break + except KeyError: + pass + else: + # We should have found at least ``object`` + raise AssertionError("unreachable") + # Dispatch. + return update(old, new, modname=modname, + cache=cache, visit_stack=visit_stack) + if heed_hook: + hook = (getattr(new, "__livepatch__", None) or + getattr(new, "__reload_update__", None)) + # XXX if unbound method or a descriptor, then we should ignore it. + # XXX test for that. + else: + hook = None + if hook is None: + # No hook is defined or the caller instructed us to ignore it. + # Do the standard livepatch. + result = do_livepatch() + else: + # Call a hook for updating. + # Build dict of optional kwargs. + avail_kwargs = dict( + old=old, + new=new, + do_livepatch=do_livepatch, + modname=modname, + cache=cache, + visit_stack=visit_stack) + # Find out which optional kwargs the hook wants. + kwargs = {} + if PY2: + argspec = inspect.getargspec(hook) + else: + argspec = inspect.getfullargspec(hook) + argnames = argspec.args + if hasattr(hook, "__func__"): + # Skip 'self' arg. + argnames = argnames[1:] + # Pick kwargs that are wanted and available. + args = [] + kwargs = {} + for n in argnames: + try: + kwargs[n] = avail_kwargs[n] + if argspec.keywords if PY2 else argspec.varkw: + break + except KeyError: + # For compatibility, allow first argument to be 'old' with any + # name, as long as there's no other arg 'old'. + # We intentionally allow this even if the user specified + # **kwargs. + if not args and not kwargs and 'old' not in argnames: + args.append(old) + else: + # Rely on default being set. If a default isn't set, the + # user will get a TypeError. + pass + if argspec.keywords if PY2 else argspec.varkw: + # Use all available kwargs. + kwargs = avail_kwargs + # Call hook. + result = hook(*args, **kwargs) + cache[cachekey] = result + return result + + +def _livepatch__module(old_mod, new_mod, modname, cache, visit_stack): + """ + Livepatch a module. + """ + result = livepatch(old_mod.__dict__, new_mod.__dict__, + modname=modname, + cache=cache, visit_stack=visit_stack) + assert result is old_mod.__dict__ + return old_mod + + +def _livepatch__dict(old_dict, new_dict, modname, cache, visit_stack): + """ + Livepatch a dict. + """ + oldnames = set(old_dict) + newnames = set(new_dict) + # Add newly introduced names. + for name in newnames - oldnames: + old_dict[name] = new_dict[name] + # Delete names that are no longer current. + for name in oldnames - newnames: + del old_dict[name] + # Livepatch existing entries. + updated_names = sorted(oldnames & newnames, key=str) + for name in updated_names: + old = old_dict[name] + updated = livepatch(old, new_dict[name], + modname=modname, + cache=cache, visit_stack=visit_stack) + if updated is not old: + old_dict[name] = updated + return old_dict + + +def _livepatch__function(old_func, new_func, modname, cache, visit_stack): + """ + Livepatch a function. + """ + # If the name differs, then don't update the existing function - this + # is probably a reassigned function. + if old_func.__name__ != new_func.__name__: + return new_func + # Check if the function's closure is compatible. If not, then return the + # new function without livepatching. Note that cell closures can't be + # modified; we can only livepatch cell values. + old_closure = old_func.__closure__ or () + new_closure = new_func.__closure__ or () + if len(old_closure) != len(new_closure): + return new_func + if old_func.__code__.co_freevars != new_func.__code__.co_freevars: + return new_func + for oldcell, newcell in zip(old_closure, new_closure): + oldcellv = oldcell.cell_contents + newcellv = newcell.cell_contents + if type(oldcellv) != type(newcellv): + return new_func + if isinstance(oldcellv, ( + types.FunctionType, types.MethodType, six.class_types, dict)): + # Updateable type. (Todo: make this configured globally.) + continue + try: + if oldcellv is newcellv or oldcellv == newcellv: + continue + except Exception: + pass + # Non-updateable and not the same as before. + return new_func + # Update function code, defaults, doc. + old_func.__code__ = new_func.__code__ + old_func.__defaults__ = new_func.__defaults__ + old_func.__doc__ = new_func.__doc__ + # Update dict. + livepatch(old_func.__dict__, new_func.__dict__, + modname=modname, cache=cache, visit_stack=visit_stack) + # Update the __closure__. We can't set __closure__ because it's a + # read-only attribute; we can only livepatch its cells' values. + for oldcell, newcell in zip(old_closure, new_closure): + oldcellv = oldcell.cell_contents + newcellv = newcell.cell_contents + livepatch(oldcellv, newcellv, + modname=modname, cache=cache, visit_stack=visit_stack) + return old_func + + +def _livepatch__method(old_method, new_method, modname, cache, visit_stack): + """ + Livepatch a method. + """ + _livepatch__function(old_method.__func__, new_method.__func__, + modname=modname, + cache=cache, visit_stack=visit_stack) + return old_method + + +def _livepatch__setattr(oldobj, newobj, name, modname, cache, visit_stack): + """ + Livepatch something via setattr, i.e.:: + + oldobj.{name} = livepatch(oldobj.{name}, newobj.{name}, ...) + """ + newval = getattr(newobj, name) + assert type(newval) is not types.MemberDescriptorType + try: + oldval = getattr(oldobj, name) + except AttributeError: + # This shouldn't happen, but just ignore it. + setattr(oldobj, name, newval) + return + # If it's the same object, then skip. Note that if even if 'newval == + # oldval', as long as they're not the same object instance, we still + # livepatch. We want mutable data structures get livepatched instead of + # replaced. Avoiding calling '==' also avoids the risk of user code + # having defined '==' to do something unexpected. + if newval is oldval: + return + # Livepatch the member object. + newval = livepatch( + oldval, newval, modname=modname, cache=cache, visit_stack=visit_stack) + # If the livepatch succeeded then we don't need to setattr. It should be + # a no-op but we avoid it just to minimize any chance of setattr causing + # problems in corner cases. + if newval is oldval: + return + # Livepatch failed, so we have to update the container with the new member + # value. + setattr(oldobj, name, newval) + + +def _livepatch__class(oldclass, newclass, modname, cache, visit_stack): + """ + Livepatch a class. + + This is similar to _livepatch__dict(oldclass.__dict__, newclass.__dict__). + However, we can't just operate on the dict, because class dictionaries are + special objects that don't allow setitem, even though we can setattr on + the class. + """ + # Collect the names to update. + olddict = oldclass.__dict__ + newdict = newclass.__dict__ + # Make sure slottiness hasn't changed -- i.e. if class was changed to have + # slots, or changed to not have slots, or if the slot names changed in any + # way, then we can't livepatch the class. + # Note that this is about whether instances of this class are affected by + # __slots__ or not. The class type itself will always use a __dict__. + if olddict.get("__slots__") != newdict.get("__slots__"): + return newclass + oldnames = set(olddict) + newnames = set(newdict) + for name in oldnames - newnames: + delattr(oldclass, name) + for name in newnames - oldnames: + setattr(oldclass, name, newdict[name]) + oldclass.__bases__ = newclass.__bases__ + names = oldnames & newnames + names.difference_update(olddict.get("__slots__", [])) + names.discard("__slots__") + names.discard("__dict__") + # Python < 3.3 doesn't support modifying __doc__ on classes with + # non-custom metaclasses. Attempt to do it and ignore failures. + # http://bugs.python.org/issue12773 + names.discard("__doc__") + try: + oldclass.__doc__ = newclass.__doc__ + except AttributeError: + pass + # Loop over attributes to be updated. + for name in sorted(names): + _livepatch__setattr( + oldclass, newclass, name, modname, cache, visit_stack) + return oldclass + + +def _livepatch__object(oldobj, newobj, modname, cache, visit_stack): + """ + Livepatch a general object. + """ + # It's not obvious whether ``oldobj`` and ``newobj`` are actually supposed + # to represent the same object. For now, we take a middle ground of + # livepatching iff the class was also defined in the same module. In that + # case at least we know that the object was defined in this module and + # therefore more likely that we should livepatch. + if modname and _get_definition_module(type(oldobj)) != modname: + return newobj + if hasattr(type(oldobj), "__slots__"): + assert oldobj.__slots__ == newobj.__slots__ + for name in newobj.__slots__: + hasold = hasattr(oldobj, name) + hasnew = hasattr(newobj, name) + if hasold and hasnew: + _livepatch__setattr(oldobj, newobj, name, + modname, cache, visit_stack) + elif hasold and not hasnew: + delattr(oldobj, name) + elif not hasold and hasnew: + setattr(oldobj, getattr(newobj, name)) + elif not hasold and not hasnew: + pass + else: + raise AssertionError + return oldobj + elif type(getattr(oldobj, "__dict__", None)) is dict: + livepatch( + oldobj.__dict__, newobj.__dict__, + modname=modname, cache=cache, visit_stack=visit_stack) + return oldobj + else: + return newobj + + +if six.PY2: + _LIVEPATCH_DISPATCH_TABLE = { + object : _livepatch__object, + dict : _livepatch__dict, + type : _livepatch__class, + types.ClassType : _livepatch__class, + types.FunctionType: _livepatch__function, + types.MethodType : _livepatch__method, + types.ModuleType : _livepatch__module, + } +elif six.PY3: + _LIVEPATCH_DISPATCH_TABLE = { + object : _livepatch__object, + dict : _livepatch__dict, + type : _livepatch__class, + types.FunctionType: _livepatch__function, + types.MethodType : _livepatch__method, + types.ModuleType : _livepatch__module, + } + + +def _get_definition_module(obj): + """ + Get the name of the module that an object is defined in, or ``None`` if + unknown. + + For classes and functions, this returns the ``__module__`` attribute. + + For object instances, this returns ``None``, ignoring the ``__module__`` + attribute. The reason is that the ``__module__`` attribute on an instance + just gives the module that the class was defined in, which is not + necessarily the module where the instance was constructed. + + :rtype: + ``str`` + """ + if isinstance(obj, (type, six.class_types, types.FunctionType, + types.MethodType)): + return getattr(obj, "__module__", None) + else: + return None + + +def _format_age(t): + secs = time.time() - t + if secs > 120: + return "%dm%ds" %(secs//60, secs%60) + else: + return "%ds" %(secs,) + + +def _interpret_module(arg): + def mod_fn(module): + return getattr(module, "__file__", None) + if isinstance(arg, six.string_types): + try: + return sys.modules[arg] + except KeyError: + pass + if arg.startswith("/"): + fn = os.path.realpath(arg) + if fn.endswith(".pyc") or fn.endswith(".pyo"): + fn = fn[:-1] + if fn.endswith(".py"): + relevant_fns = set([fn, fn+"c", fn+"o"]) + else: + relevant_fns = set([fn]) + found_modules = [ + m for _,m in sorted(sys.modules.items()) + if os.path.realpath(mod_fn(m) or "/") in relevant_fns ] + if not found_modules: + raise UnknownModuleError( + "No loaded module uses path %s" % (fn,)) + if len(found_modules) > 1: + raise UnknownModuleError( + "Multiple loaded modules use path %s: %r" + % (fn, found_modules)) + return found_modules[0] + if arg.endswith(".py") and "/" not in arg: + name = arg[:-3] + relevant_bns = set([arg, arg+"c", arg+"o"]) + found_modules = [ + m for n,m in sorted(sys.modules.items()) + if (n==name or + os.path.basename(mod_fn(m) or "/") in relevant_bns)] + if not found_modules: + raise UnknownModuleError( + "No loaded module named %s" % (name,)) + if len(found_modules) > 1: + raise UnknownModuleError( + "Multiple loaded modules named %s: %r" + % (name, found_modules)) + return found_modules[0] + raise UnknownModuleError(arg) + if isinstance(arg, types.ModuleType): + return arg + try: + # Allow fake modules. + if sys.modules[arg.__name__] is arg: + return arg + except Exception: + pass + raise TypeError("Expected module, module name, or filename; got %s" + % (type(arg).__name__)) + + +def _xreload_module(module, filename, force=False): + """ + Reload a module in place, using livepatch. + + :type module: + ``ModuleType`` + :param module: + Module to reload. + :param force: + Whether to reload even if the module has not been modified since the + previous load. If ``False``, then do nothing. If ``True``, then reload. + """ + import linecache + if not filename or not filename.endswith(".py"): + # If there's no *.py source file for this module, then fallback to + # built-in reload(). + return reload_module(module) + # Compare mtime of the file with the load time of the module. If the file + # wasn't touched, we don't need to do anything. + try: + mtime = os.stat(filename).st_mtime + except OSError: + logger.info("Can't find %s", filename) + return None + if not force: + try: + old_loadtime = module.__loadtime__ + except AttributeError: + # We only have a __loadtime__ attribute if we were the ones that + # loaded it. Otherwise, fall back to the process start time as a + # conservative bound. + old_loadtime = _PROCESS_START_TIME + if old_loadtime > mtime: + logger.debug( + "NOT reloading %s (file %s modified %s ago but loaded %s ago)", + module.__name__, filename, _format_age(mtime), + _format_age(old_loadtime)) + return None + # Keep track of previously imported source. If the file's timestamp + # was touched, but the content unchanged, we can avoid reloading. + cached_lines = linecache.cache.get(filename, (None,None,None,None))[2] + else: + cached_lines = None + # Re-read source for module from disk, and update the linecache. + source = ''.join(linecache.updatecache(filename)) + # Skip reload if the content didn't change. + if cached_lines is not None and source == ''.join(cached_lines): + logger.debug( + "NOT reloading %s (file %s touched %s ago but content unchanged)", + module.__name__, filename, _format_age(mtime)) + return module + logger.info("Reloading %s (modified %s ago) from %s", + module.__name__, _format_age(mtime), filename) + # Compile into AST. We do this as a separate step from compiling to byte + # code so that we can get the module docstring. + astnode = compile(source, filename, "exec", ast.PyCF_ONLY_AST, 1) + # Get the new docstring. + try: + doc = astnode.body[0].value.s + except (AttributeError, IndexError): + doc = None + # Compile into code. + code = compile(astnode, filename, "exec", 0, 1) + # Execute the code. We do so in a temporary namespace so that if this + # fails, nothing changes. It's important to set __name__ so that relative + # imports work correctly. + new_mod = types.ModuleType(module.__name__) + new_mod.__file__ = filename + new_mod.__doc__ = doc + if hasattr(module, "__path__"): + new_mod.__path__ = module.__path__ + MISSING = object() + saved_mod = sys.modules.get(module.__name__, MISSING) + try: + # Temporarily put the temporary module in sys.modules, in case the + # code references sys.modules[__name__] for some reason. Normally on + # success, we will revert this what that was there before (which + # normally should be ``module``). If an error occurs, we'll also + # revert. If the user has defined a __livepatch__ hook at the module + # level, it's possible for result to not be the old module. + sys.modules[module.__name__] = new_mod + # *** Execute new code *** + exec(code, new_mod.__dict__) + # Normally ``module`` is of type ``ModuleType``. However, in some + # cases, the module might have done a "proxy module" trick where the + # module is replaced by a proxy object of some other type. Regardless + # of the actual type, we do the update as ``module`` were of type + # ``ModuleType``. + assume_type = types.ModuleType + # Livepatch the module. + result = livepatch(module, new_mod, module.__name__, + assume_type=assume_type) + sys.modules[module.__name__] = result + except: + # Either the module failed executing or the livepatch failed. + # Revert to previous state. + # Note that this isn't perfect because it's possible that the module + # modified some global state in other modules. + if saved_mod is MISSING: + del sys.modules[module.__name__] + else: + sys.modules[module.__name__] = saved_mod + raise + # Update the time we last loaded the module. We intentionally use mtime + # here instead of time.time(). If we are on NFS, it's possible for the + # filer's mtime and time.time() to not be synchronized. We will be + # comparing to mtime next time, so if we use only mtime, we'll be fine. + module.__loadtime__ = mtime + return module + + +def _get_module_py_file(module): + filename = getattr(module, "__file__", None) + if not filename: + return None + filename = re.sub("[.]py[co]$", ".py", filename) + return filename + + +def xreload(*args): + """ + Reload module(s). + + This function is more useful than the built-in reload(). xreload() uses a + "live patch" approach that modifies existing functions, classes, and + objects in-place. + + This addresses cases where one module imported functions from another + module. + + For example, suppose m1.py contains:: + + from m2 import foo + def print_foo(): + return foo() + + and m2.py contains:: + + def foo(): + return 42 + + If you edit m2.py and modify ``foo``, then reload(m2) on its own would not + do what you want. The built-in reload affects the module being reloaded, + but references to the old module remain. On the other hand, xreload() + patches the existing m2.foo, so that live references to it are updated. + + :type args: + ``str`` s and/or ``ModuleType`` s + :param args: + Module(s) to reload. If no argument is specified, then reload all + recently modified modules. + """ + if not args: + for name, module in sorted(sys.modules.items()): + if name == "__main__": + continue + filename = _get_module_py_file(module) + if not filename: + continue + _xreload_module(module, filename) + return + # Treat xreload(list_of_module) like xreload(*list_of_modules). We + # intentionally do this after the above check so that xreload([]) does + # nothing. + if len(args) == 1 and isinstance(args[0], (tuple, list)): + args = args[0] + for arg in args: + module = _interpret_module(arg) + # Get the *.py filename for this module. + filename = _get_module_py_file(module) + # Reload the module. + _xreload_module(module, filename) diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_log.py b/.venv/lib/python3.8/site-packages/pyflyby/_log.py new file mode 100644 index 0000000..8795223 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_log.py @@ -0,0 +1,240 @@ +# pyflyby/_log.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from contextlib import contextmanager +import logging +from logging import Handler, Logger +import os +from six.moves import builtins +import sys + + +class _PyflybyHandler(Handler): + + _pre_log_function = None + _logged_anything_during_context = False + + _interactive_prefix = "\033[0m\033[33m[PYFLYBY]\033[0m " + _noninteractive_prefix = "[PYFLYBY] " + + def emit(self, record): + """ + Emit a log record. + """ + try: + # Call pre-log hook. + if self._pre_log_function is not None: + if not self._logged_anything_during_context: + self._pre_log_function() + self._logged_anything_during_context = True + # Format (currently a no-op). + msg = self.format(record) + # Add prefix per line. + if _is_ipython() or _is_interactive(sys.stderr): + prefix = self._interactive_prefix + else: + prefix = self._noninteractive_prefix + msg = ''.join(["%s%s\n" % (prefix, line) for line in msg.splitlines()]) + # First, flush stdout, to make sure that stdout and stderr don't get + # interleaved. Normally this is automatic, but when stdout is piped, + # it can be necessary to force a flush to avoid interleaving. + sys.stdout.flush() + # Write log message. + if sys.stderr.__class__.__module__.startswith("prompt_toolkit"): + with _PromptToolkitStdoutProxyRawCtx(sys.stderr): + sys.stderr.write(msg) + sys.stderr.flush() + else: + sys.stderr.write(msg) + # Flush now - we don't want any interleaving of stdout/stderr. + sys.stderr.flush() + except (KeyboardInterrupt, SystemExit): + raise + except: + self.handleError(record) + + @contextmanager + def HookCtx(self, pre, post): + """ + Enter a context where: + * ``pre`` is called before the first time a log record is emitted + during the context, and + * ``post`` is called at the end of the context, if any log records + were emitted during the context. + + :type pre: + ``callable`` + :param pre: + Function to call before the first time something is logged during + this context. + :type post: + ``callable`` + :param post: + Function to call before returning from the context, if anything was + logged during the context. + """ + assert self._pre_log_function is None + self._pre_log_function = pre + try: + yield + finally: + if self._logged_anything_during_context: + post() + self._logged_anything_during_context = False + self._pre_log_function = None + + +def _is_interactive(file): + filemod = type(file).__module__ + if filemod.startswith("IPython.") or filemod.startswith("prompt_toolkit."): + # Inside IPython notebook/kernel + return True + try: + fileno = file.fileno() + except Exception: + return False # dunno + return os.isatty(fileno) + + +def _is_ipython(): + """ + Returns true if we're currently running inside IPython. + """ + # This currently only works for versions of IPython that are modern enough + # to install 'builtins.get_ipython()'. + if 'IPython' not in sys.modules: + return False + if not hasattr(builtins, "get_ipython"): + return False + ip = builtins.get_ipython() + if ip is None: + return False + return True + + +@contextmanager +def _PromptToolkitStdoutProxyRawCtx(proxy): + """ + Hack to defeat the "feature" where + prompt_toolkit.interface._StdoutProxy(sys.stderr) causes ANSI escape codes + to not be written. + """ + # prompt_toolkit replaces sys.stderr with a proxy object. This proxy + # object replaces ESC (\xb1) with '?'. That breaks our colorization of + # the [PYFLYBY] log prefix. To work around this, we need to temporarily + # set _StdoutProxy._raw to True during the write() call. However, the + # write() call actually just stores a lambda to be executed later, and + # that lambda references self._raw by reference. So we can't just set + # _raw before we call sys.stderr.write(), since the _raw variable is not + # read yet at that point. We need to hook the internals so that we store + # a wrapped lambda which temporarily sets _raw to True. Yuck, this is so + # brittle. Tested with prompt_toolkit 1.0.15. + if not hasattr(type(proxy), '_do') or not hasattr(proxy, '_raw'): + yield + return + MISSING = object() + prev = proxy.__dict__.get('_do', MISSING) + original_do = proxy._do + def wrapped_do_raw(self, func): + def wrapped_func(): + prev_raw = self._raw + try: + self._raw = True + func() + finally: + self._raw = prev_raw + original_do(wrapped_func) + try: + proxy._do = wrapped_do_raw.__get__(proxy) + yield + finally: + if prev is MISSING: + proxy.__dict__.pop('_do', None) + else: + proxy.__dict__ = prev + + +@contextmanager +def _NoRegisterLoggerHandlerInHandlerListCtx(): + """ + Work around a bug in the ``logging`` module for Python 2.x-3.2. + + The Python stdlib ``logging`` module has a bug where you sometimes get the + following warning at exit:: + + Exception TypeError: "'NoneType' object is not callable" in ignored + + This is caused by shutdown ordering affecting which globals in the logging + module are available to the _removeHandlerRef function. + + Python 3.3 fixes this. + + For earlier versions of Python, this context manager works around the + issue by avoiding registering a handler in the _handlerList. This means + that we no longer call "flush()" from the atexit callback. However, that + was a no-op anyway, and even if we needed it, we could call it ourselves + atexit. + + :see: + http://bugs.python.org/issue9501 + """ + if not hasattr(logging, "_handlerList"): + yield + return + if sys.version_info >= (3, 3): + yield + return + try: + orig_handlerList = logging._handlerList[:] + yield + finally: + logging._handlerList[:] = orig_handlerList + + + +class PyflybyLogger(Logger): + + _LEVELS = dict( (k, getattr(logging, k)) + for k in ['DEBUG', 'INFO', 'WARNING', 'ERROR'] ) + + def __init__(self, name, level): + Logger.__init__(self, name) + with _NoRegisterLoggerHandlerInHandlerListCtx(): + handler = _PyflybyHandler() + self.addHandler(handler) + self.set_level(level) + + def set_level(self, level): + """ + Set the pyflyby logger's level to ``level``. + + :type level: + ``str`` + """ + if isinstance(level, int): + level_num = level + else: + try: + level_num = self._LEVELS[level.upper()] + except KeyError: + raise ValueError("Bad log level %r" % (level,)) + Logger.setLevel(self, level_num) + + @property + def debug_enabled(self): + return self.level <= logging.DEBUG + + @property + def info_enabled(self): + return self.level <= logging.INFO + + def HookCtx(self, pre, post): + return self.handlers[0].HookCtx(pre, post) + + +logger = PyflybyLogger('pyflyby', os.getenv("PYFLYBY_LOG_LEVEL") or "INFO") diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_modules.py b/.venv/lib/python3.8/site-packages/pyflyby/_modules.py new file mode 100644 index 0000000..b71e4b0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_modules.py @@ -0,0 +1,429 @@ +# pyflyby/_modules.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from functools import total_ordering +import os +import re +import six +from six import reraise +import sys +import types + +from pyflyby._file import FileText, Filename +from pyflyby._idents import DottedIdentifier, is_identifier +from pyflyby._log import logger +from pyflyby._util import (ExcludeImplicitCwdFromPathCtx, + cached_attribute, cmp, memoize, + prefixes) + + +class ErrorDuringImportError(ImportError): + """ + Exception raised by import_module if the module exists but an exception + occurred while attempting to import it. That nested exception could be + ImportError, e.g. if a module tries to import another module that doesn't + exist. + """ + + +@memoize +def import_module(module_name): + module_name = str(module_name) + logger.debug("Importing %r", module_name) + try: + result = __import__(module_name, fromlist=['dummy']) + if result.__name__ != module_name: + logger.debug("Note: import_module(%r).__name__ == %r", + module_name, result.__name__) + return result + except ImportError as e: + # We got an ImportError. Figure out whether this is due to the module + # not existing, or whether the module exists but caused an ImportError + # (perhaps due to trying to import another problematic module). + # Do this by looking at the exception traceback. If the previous + # frame in the traceback is this function (because locals match), then + # it should be the internal import machinery reporting that the module + # doesn't exist. Re-raise the exception as-is. + # If some sys.meta_path or other import hook isn't compatible with + # such a check, here are some things we could do: + # - Use pkgutil.find_loader() after the fact to check if the module + # is supposed to exist. Note that we shouldn't rely solely on + # this before attempting to import, because find_loader() doesn't + # work with meta_path. + # - Write a memoized global function that compares in the current + # environment the difference between attempting to import a + # non-existent module vs a problematic module, and returns a + # function that uses the working discriminators. + real_importerror1 = type(e) is ImportError + real_importerror2 = (sys.exc_info()[2].tb_frame.f_locals is locals()) + m = re.match("^No module named (.*)$", str(e)) + real_importerror3 = (m and m.group(1) == module_name + or module_name.endswith("."+m.group(1))) + logger.debug("import_module(%r): real ImportError: %s %s %s", + module_name, + real_importerror1, real_importerror2, real_importerror3) + if real_importerror1 and real_importerror2 and real_importerror3: + raise + reraise(ErrorDuringImportError( + "Error while attempting to import %s: %s: %s" + % (module_name, type(e).__name__, e)), None, sys.exc_info()[2]) + except Exception as e: + reraise(ErrorDuringImportError( + "Error while attempting to import %s: %s: %s" + % (module_name, type(e).__name__, e)), None, sys.exc_info()[2]) + + +def _my_iter_modules(path, prefix=''): + # Modified version of pkgutil.ImpImporter.iter_modules(), patched to + # handle inaccessible subdirectories. + if path is None: + return + try: + filenames = os.listdir(path) + except OSError: + return # silently ignore inaccessible paths + filenames.sort() # handle packages before same-named modules + yielded = {} + import inspect + for fn in filenames: + modname = inspect.getmodulename(fn) + if modname=='__init__' or modname in yielded: + continue + subpath = os.path.join(path, fn) + ispkg = False + try: + if not modname and os.path.isdir(path) and '.' not in fn: + modname = fn + for fn in os.listdir(subpath): + subname = inspect.getmodulename(fn) + if subname=='__init__': + ispkg = True + break + else: + continue # not a package + except OSError: + continue # silently ignore inaccessible subdirectories + if modname and '.' not in modname: + yielded[modname] = 1 + yield prefix + modname, ispkg + + +def pyc_to_py(filename): + if filename.endswith(".pyc") or filename.endswith(".pyo"): + filename = filename[:-1] + return filename + + +@total_ordering +class ModuleHandle(object): + """ + A handle to a module. + """ + + def __new__(cls, arg): + if isinstance(arg, cls): + return arg + if isinstance(arg, Filename): + return cls._from_filename(arg) + if isinstance(arg, (six.string_types, DottedIdentifier)): + return cls._from_modulename(arg) + if isinstance(arg, types.ModuleType): + return cls._from_module(arg) + raise TypeError("ModuleHandle: unexpected %s" % (type(arg).__name__,)) + + _cls_cache = {} + + @classmethod + def _from_modulename(cls, modulename): + modulename = DottedIdentifier(modulename) + try: + return cls._cls_cache[modulename] + except KeyError: + pass + self = object.__new__(cls) + self.name = modulename + cls._cls_cache[modulename] = self + return self + + @classmethod + def _from_module(cls, module): + if not isinstance(module, types.ModuleType): + raise TypeError + self = cls._from_modulename(module.__name__) + assert self.module is module + return self + + @classmethod + def _from_filename(cls, filename): + filename = Filename(filename) + raise NotImplementedError( + "TODO: look at sys.path to guess module name") + + @cached_attribute + def parent(self): + if not self.name.parent: + return None + return ModuleHandle(self.name.parent) + + @cached_attribute + def ancestors(self): + return tuple(ModuleHandle(m) for m in self.name.prefixes) + + @cached_attribute + def module(self): + """ + Return the module instance. + + :rtype: + ``types.ModuleType`` + :raise ErrorDuringImportError: + The module should exist but an error occurred while attempting to + import it. + :raise ImportError: + The module doesn't exist. + """ + # First check if prefix component is importable. + if self.parent: + self.parent.module + # Import. + return import_module(self.name) + + @cached_attribute + def exists(self): + """ + Return whether the module exists, according to pkgutil. + Note that this doesn't work for things that are only known by using + sys.meta_path. + """ + name = str(self.name) + if name in sys.modules: + return True + if self.parent and not self.parent.exists: + return False + import pkgutil + try: + loader = pkgutil.find_loader(name) + except Exception: + # Catch all exceptions, not just ImportError. If the __init__.py + # for the parent package of the module raises an exception, it'll + # propagate to here. + loader = None + return loader is not None + + @cached_attribute + def filename(self): + """ + Return the filename, if appropriate. + + The module itself will not be imported, but if the module is not a + top-level module/package, accessing this attribute may cause the + parent package to be imported. + + :rtype: + `Filename` + """ + # Use the loader mechanism to find the filename. We do so instead of + # using self.module.__file__, because the latter forces importing a + # module, which may be undesirable. + import pkgutil + try: + loader = pkgutil.get_loader(str(self.name)) + except ImportError: + return None + if not loader: + return None + # Get the filename using loader.get_filename(). Note that this does + # more than just loader.filename: for example, it adds /__init__.py + # for packages. + filename = loader.get_filename() + if not filename: + return None + return Filename(pyc_to_py(filename)) + + @cached_attribute + def text(self): + return FileText(self.filename) + + def __text__(self): + return self.text + + @cached_attribute + def block(self): + from pyflyby._parse import PythonBlock + return PythonBlock(self.text) + + @staticmethod + @memoize + def list(): + """ + Enumerate all top-level packages/modules. + + :rtype: + ``tuple`` of `ModuleHandle` s + """ + import pkgutil + # Get the list of top-level packages/modules using pkgutil. + # We exclude "." from sys.path while doing so. Python includes "." in + # sys.path by default, but this is undesirable for autoimporting. If + # we autoimported random python scripts in the current directory, we + # could accidentally execute code with side effects. If the current + # working directory is /tmp, trying to enumerate modules there also + # causes problems, because there are typically directories there not + # readable by the current user. + with ExcludeImplicitCwdFromPathCtx(): + modlist = pkgutil.iter_modules(None) + module_names = [t[1] for t in modlist] + # pkgutil includes all *.py even if the name isn't a legal python + # module name, e.g. if a directory in $PYTHONPATH has files named + # "try.py" or "123.py", pkgutil will return entries named "try" or + # "123". Filter those out. + module_names = [m for m in module_names if is_identifier(m)] + # Canonicalize. + return tuple(ModuleHandle(m) for m in sorted(set(module_names))) + + @cached_attribute + def submodules(self): + """ + Enumerate the importable submodules of this module. + + >>> ModuleHandle("email").submodules # doctest:+ELLIPSIS + (..., 'email.encoders', ..., 'email.mime', ...) + + :rtype: + ``tuple`` of `ModuleHandle` s + """ + import pkgutil + module = self.module + try: + path = module.__path__ + except AttributeError: + return () + # Enumerate the modules at a given path. Prefer to use ``pkgutil`` if + # we can. However, if it fails due to OSError, use our own version + # which is robust to that. + try: + submodule_names = [t[1] for t in pkgutil.iter_modules(path)] + except OSError: + submodule_names = [t[0] for p in path for t in _my_iter_modules(p)] + return tuple(ModuleHandle("%s.%s" % (self.name,m)) + for m in sorted(set(submodule_names))) + + @cached_attribute + def exports(self): + """ + Get symbols exported by this module. + + Note that this requires involves actually importing this module, which + may have side effects. (TODO: rewrite to avoid this?) + + :rtype: + `ImportSet` or ``None`` + :return: + Exports, or ``None`` if nothing exported. + """ + from pyflyby._importclns import ImportStatement, ImportSet + module = self.module + try: + members = module.__all__ + except AttributeError: + members = dir(module) + # Filter by non-private. + members = [n for n in members if not n.startswith("_")] + # Filter by definition in the module. + def from_this_module(name): + # TODO: could do this more robustly by parsing the AST and + # looking for STOREs (definitions/assignments/etc). + x = getattr(module, name) + m = getattr(x, "__module__", None) + if not m: + return False + return DottedIdentifier(m).startswith(self.name) + members = [n for n in members if from_this_module(n)] + else: + if not all(type(s) == str for s in members): + raise Exception( + "Module %r contains non-string entries in __all__" + % (str(self.name),)) + # Filter out artificially added "deep" members. + members = [n for n in members if "." not in n] + if not members: + return None + return ImportSet( + [ ImportStatement.from_parts(str(self.name), members) ]) + + def __str__(self): + return str(self.name) + + def __repr__(self): + return "%s(%r)" % (type(self).__name__, str(self.name)) + + def __hash__(self): + return hash(self.name) + + def __cmp__(self, o): + if self is o: + return 0 + if not isinstance(o, ModuleHandle): + return NotImplemented + return cmp(self.name, o.name) + + def __eq__(self, o): + if self is o: + return True + if not isinstance(o, ModuleHandle): + return NotImplemented + return self.name == o.name + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, o): + if not isinstance(o, ModuleHandle): + return NotImplemented + return self.name < o.name + + def __getitem__(self, x): + if isinstance(x, slice): + return type(self)(self.name[x]) + raise TypeError + + @classmethod + def containing(cls, identifier): + """ + Try to find the module that defines a name such as ``a.b.c`` by trying + to import ``a``, ``a.b``, and ``a.b.c``. + + :return: + The name of the 'deepest' module (most commonly it would be ``a.b`` + in this example). + :rtype: + `Module` + """ + # In the code below we catch "Exception" rather than just ImportError + # or AttributeError since importing and __getattr__ing can raise other + # exceptions. + identifier = DottedIdentifier(identifier) + try: + module = ModuleHandle(identifier[:1]) + result = module.module + except Exception as e: + raise ImportError(e) + for part, prefix in zip(identifier, prefixes(identifier))[1:]: + try: + result = getattr(result, str(part)) + except Exception: + try: + module = cls(prefix) + result = module.module + except Exception as e: + raise ImportError(e) + else: + if isinstance(result, types.ModuleType): + module = cls(result) + logger.debug("Imported %r to get %r", module, identifier) + return module diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_parse.py b/.venv/lib/python3.8/site-packages/pyflyby/_parse.py new file mode 100644 index 0000000..2d48370 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_parse.py @@ -0,0 +1,1482 @@ +# pyflyby/_parse.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +import ast +from collections import namedtuple +from doctest import DocTestParser +from functools import total_ordering +from itertools import groupby +import re +import sys +from textwrap import dedent +import types + +import six +from six import PY2, PY3, text_type as unicode +from six.moves import range + +from pyflyby._file import FilePos, FileText, Filename +from pyflyby._flags import CompilerFlags +from pyflyby._log import logger +from pyflyby._util import cached_attribute, cmp + +if PY3: + from ast import Bytes +else: + Bytes = ast.Str + + +if sys.version_info >= (3, 8): + from ast import TypeIgnore +else: + # TypeIgnore does not exist on Python 3.7 and before. + # thus we define a dummy TypeIgnore just to simplify remaining code. + + class TypeIgnore: + pass + + +def _is_comment_or_blank(line): + """ + Returns whether a line of python code contains only a comment is blank. + + >>> _is_comment_or_blank("foo\\n") + False + + >>> _is_comment_or_blank(" # blah\\n") + True + """ + return re.sub("#.*", "", line).rstrip() == "" + + +def _ast_str_literal_value(node): + if isinstance(node, (ast.Str, Bytes)): + return node.s + if isinstance(node, ast.Expr) and isinstance(node.value, (ast.Str, Bytes)): + return node.value.s + else: + return None + + +def _flatten_ast_nodes(arg): + if arg is None: + pass + elif isinstance(arg, ast.AST): + yield arg + elif isinstance(arg, str): + #FunctionDef type_comments + yield arg + elif isinstance(arg, (tuple, list, types.GeneratorType)): + for x in arg: + for y in _flatten_ast_nodes(x): + yield y + else: + raise TypeError( + "_flatten_ast_nodes: unexpected %s" % (type(arg).__name__,)) + + +def _iter_child_nodes_in_order(node): + """ + Yield all direct child nodes of ``node``, that is, all fields that are nodes + and all items of fields that are lists of nodes. + + ``_iter_child_nodes_in_order`` yields nodes in the same order that they + appear in the source. + + ``ast.iter_child_nodes`` does the same thing, but not in source order. + e.g. for ``Dict`` s, it yields all key nodes before all value nodes. + """ + return _flatten_ast_nodes(_iter_child_nodes_in_order_internal_1(node)) + + +def _iter_child_nodes_in_order_internal_1(node): + if isinstance(node, str): + # this happen for type comments which are not ast nodes but str + # they do not have children. We yield nothing. + yield [] + return + if not isinstance(node, ast.AST): + raise TypeError + if isinstance(node, ast.Dict): + assert node._fields == ("keys", "values") + yield list(zip(node.keys, node.values)) + elif isinstance(node, ast.FunctionDef): + if six.PY2: + assert node._fields == ('name', 'args', 'body', 'decorator_list'), node._fields + yield node.decorator_list, node.args, node.body + elif sys.version_info >= (3, 8): + assert node._fields == ( + "name", + "args", + "body", + "decorator_list", + "returns", + "type_comment", + ), node._fields + res = ( + node.type_comment, + node.decorator_list, + node.args, + node.returns, + node.body, + ) + yield res + else: + assert node._fields == ('name', 'args', 'body', 'decorator_list', + 'returns'), node._fields + yield node.decorator_list, node.args, node.returns, node.body + # node.name is a string, not an AST node + elif isinstance(node, ast.arguments): + if six.PY2: + assert node._fields == ('args', 'vararg', 'kwarg', 'defaults'), node._fields + elif sys.version_info >= (3, 8): + assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs', + 'kw_defaults', 'kwarg', 'defaults'), node._fields + else: + assert node._fields == ('args', 'vararg', 'kwonlyargs', + 'kw_defaults', 'kwarg', 'defaults'), node._fields + defaults = node.defaults or () + num_no_default = len(node.args)-len(defaults) + yield node.args[:num_no_default] + yield list(zip(node.args[num_no_default:], defaults)) + # node.varags and node.kwarg are strings, not AST nodes. + elif isinstance(node, ast.IfExp): + assert node._fields == ('test', 'body', 'orelse') + yield node.body, node.test, node.orelse + elif isinstance(node, ast.Call): + # call arguments order are lost by ast, re-order them + yield node.func + args = sorted([(k.value.lineno, k.value.col_offset, k) for k in node.keywords]+ + [(k.lineno,k.col_offset, k) for k in node.args]) + yield [a[2] for a in args] + elif isinstance(node, ast.ClassDef): + if six.PY2: + assert node._fields == ('name', 'bases', 'body', 'decorator_list') + else: + assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list') + yield node.decorator_list, node.bases, node.body + # node.name is a string, not an AST node + elif sys.version_info >= (3, 7) and isinstance(node, ast.FormattedValue): + assert node._fields == ('value', 'conversion', 'format_spec') + yield node.value, + else: + # Default behavior. + yield ast.iter_child_nodes(node) + + +def _walk_ast_nodes_in_order(node): + """ + Recursively yield all child nodes of ``node``, in the same order that the + node appears in the source. + + ``ast.walk`` does the same thing, but yields nodes in an arbitrary order. + """ + # The implementation is basically the same as ``ast.walk``, but: + # 1. Use a stack instead of a deque. (I.e., depth-first search instead + # of breadth-first search.) + # 2. Use _iter_child_nodes_in_order instead of ``ast.iter_child_nodes``. + todo = [node] + while todo: + node = todo.pop() + yield node + todo.extend(reversed(list(_iter_child_nodes_in_order(node)))) + + +def _flags_to_try(source, flags, auto_flags, mode): + """ + Flags to try for ``auto_flags``. + + If ``auto_flags`` is False, then only yield ``flags``. + If ``auto_flags`` is True, then yield ``flags`` and ``flags ^ print_function``. + """ + flags = CompilerFlags(flags) + if sys.version_info >= (3, 8): + if re.search(r"# *type:", source): + flags = flags | CompilerFlags('type_comments') + yield flags + return + if not auto_flags: + yield flags + return + if PY3: + yield flags + return + if mode == "eval": + if re.search(r"\bprint\b", source): + flags = flags | CompilerFlags("print_function") + yield flags + return + yield flags + if re.search(r"\bprint\b", source): + yield flags ^ CompilerFlags("print_function") + + +def _parse_ast_nodes(text, flags, auto_flags, mode): + """ + Parse a block of lines into an AST. + + Also annotate ``input_flags``, ``source_flags``, and ``flags`` on the + resulting ast node. + + :type text: + ``FileText`` + :type flags: + ``CompilerFlags`` + :type auto_flags: + ``bool`` + :param auto_flags: + Whether to guess different flags if ``text`` can't be parsed with + ``flags``. + :param mode: + Compilation mode: "exec", "single", or "eval". + :rtype: + ``ast.Module`` + """ + text = FileText(text) + filename = str(text.filename) if text.filename else "" + source = text.joined + source = dedent(source) + if PY2 and isinstance(source, unicode): + source = source.encode('utf-8') + if not source.endswith("\n"): + # Ensure that the last line ends with a newline (``ast`` barfs + # otherwise). + source += "\n" + exp = None + for flags in _flags_to_try(source, flags, auto_flags, mode): + cflags = ast.PyCF_ONLY_AST | int(flags) + try: + result = compile( + source, filename, mode, flags=cflags, dont_inherit=1) + except SyntaxError as e: + exp = e + pass + else: + # Attach flags to the result. + result.input_flags = flags + result.source_flags = CompilerFlags.from_ast(result) + result.flags = result.input_flags | result.source_flags + result.text = text + return result + raise exp # SyntaxError + + +def _test_parse_string_literal(text, flags): + r""" + Attempt to parse ``text``. If it parses cleanly to a single string + literal, return its value. Otherwise return ``None``. + + >>> _test_parse_string_literal(r'"foo\n" r"\nbar"', None) + 'foo\n\\nbar' + + """ + text = FileText(text) + if PY2: + try: + text.joined.encode('ascii') + except UnicodeError: + text = FileText(u'# encoding: utf-8\n' + unicode(text), filename=text.filename) + + try: + module_node = _parse_ast_nodes(text, flags, False, "eval") + except SyntaxError: + return None + body = module_node.body + if not isinstance(body, (ast.Str, Bytes)): + return None + return body.s + + +AstNodeContext = namedtuple("AstNodeContext", "parent field index") + + +def _annotate_ast_nodes(ast_node): + """ + Annotate AST with: + - startpos and endpos + - [disabled for now: context as `AstNodeContext` ] + + :type ast_node: + ``ast.AST`` + :param ast_node: + AST node returned by `_parse_ast_nodes` + :return: + ``None`` + """ + text = ast_node.text + flags = ast_node.flags + startpos = text.startpos + _annotate_ast_startpos(ast_node, None, startpos, text, flags) + # Not used for now: + # ast_node.context = AstNodeContext(None, None, None) + # _annotate_ast_context(ast_node) + + +def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags): + r""" + Annotate ``ast_node``. Set ``ast_node.startpos`` to the starting position + of the node within ``text``. + + For "typical" nodes, i.e. those other than multiline strings, this is + simply FilePos(ast_node.lineno, ast_node.col_offset+1), but taking + ``text.startpos`` into account. + + For multiline string nodes, this function works by trying to parse all + possible subranges of lines until finding the range that is syntactically + valid and matches ``value``. The candidate range is + text[min_start_lineno:lineno+text.startpos.lineno+1]. + + This function is unfortunately necessary because of a flaw in the output + produced by the Python built-in parser. For some crazy reason, the + ``ast_node.lineno`` attribute represents something different for multiline + string literals versus all other statements. For multiline string literal + nodes and statements that are just a string expression (or more generally, + nodes where the first descendant leaf node is a multiline string literal), + the compiler attaches the ending line number as the value of the ``lineno`` + attribute. For all other than AST nodes, the compiler attaches the + starting line number as the value of the ``lineno`` attribute. This means + e.g. the statement "'''foo\nbar'''" has a lineno value of 2, but the + statement "x='''foo\nbar'''" has a lineno value of 1. + + :type ast_node: + ``ast.AST`` + :type minpos: + ``FilePos`` + :param minpos: + Earliest position to check, in the number space of ``text``. + :type text: + ``FileText`` + :param text: + Source text that was used to parse the AST, whose ``startpos`` should be + used in interpreting ``ast_node.lineno`` (which always starts at 1 for + the subset that was parsed). + :type flags: + ``CompilerFlags`` + :param flags: + Compiler flags to use when re-compiling code. + :return: + ``True`` if this node is a multiline string literal or the first child is + such a node (recursively); ``False`` otherwise. + :raise ValueError: + Could not find the starting line number. + """ + assert isinstance(ast_node, (ast.AST, str, TypeIgnore)), ast_node + + # joined strings and children do not carry a column offset on pre-3.8 + # this prevent reformatting. + # set the column offset to the parent value before 3.8 + if (3, 7) < sys.version_info < (3, 8): + if ( + isinstance(ast_node, (getattr(ast, "JoinedStr", None), ast.FormattedValue)) + or isinstance( + parent_ast_node, (getattr(ast, "JoinedStr", None), ast.FormattedValue) + ) + ) and ast_node.col_offset == -1: + ast_node.col_offset = parent_ast_node.col_offset + + # First, traverse child nodes. If the first child node (recursively) is a + # multiline string, then we need to transfer its information to this node. + # Walk all nodes/fields of the AST. We implement this as a custom + # depth-first search instead of using ast.walk() or ast.NodeVisitor + # so that we can easily keep track of the preceding node's lineno. + child_minpos = minpos + is_first_child = True + leftstr_node = None + for child_node in _iter_child_nodes_in_order(ast_node): + leftstr = _annotate_ast_startpos(child_node, ast_node, + child_minpos, text, flags) + if is_first_child and leftstr: + leftstr_node = child_node + if hasattr(child_node, 'lineno') and not isinstance(child_node, TypeIgnore): + if child_node.startpos < child_minpos: + raise AssertionError( + "Got out-of-order AST node(s):\n" + " parent minpos=%s\n" % minpos + + " node: %s\n" % ast.dump(ast_node) + + " fields: %s\n" % (" ".join(ast_node._fields)) + + " children:\n" + + ''.join( + " %s %9s: %s\n" % ( + ("==>" if cn is child_node else " "), + getattr(cn, 'startpos', ""), + ast.dump(cn)) + for cn in _iter_child_nodes_in_order(ast_node)) + + "\n" + "This indicates a bug in pyflyby._\n" + "\n" + "pyflyby developer: Check if there's a bug or missing ast node handler in " + "pyflyby._parse._iter_child_nodes_in_order() - " + "probably the handler for ast.%s." % type(ast_node).__name__) + child_minpos = child_node.startpos + is_first_child = False + + # If the node has no lineno at all, then skip it. This should only happen + # for nodes we don't care about, e.g. ``ast.Module`` or ``ast.alias``. + if not hasattr(ast_node, 'lineno') or isinstance(ast_node, TypeIgnore): + return False + # If col_offset is set then the lineno should be correct also. + if ast_node.col_offset >= 0: + # In Python 3.8+, FunctionDef.lineno is the line with the def. To + # account for decorators, we need the lineno of the first decorator + if (sys.version_info >= (3, 8) + and isinstance(ast_node, (ast.FunctionDef, ast.ClassDef)) + and ast_node.decorator_list): + delta = (ast_node.decorator_list[0].lineno-1, + # The col_offset doesn't include the @ + ast_node.decorator_list[0].col_offset - 1) + else: + delta = (ast_node.lineno-1, ast_node.col_offset) + + # Not a multiline string literal. (I.e., it could be a non-string or + # a single-line string.) + # Easy. + startpos = text.startpos + delta + + # Special case for 'with' statements. Consider the code: + # with X: pass + # ^0 ^5 + # In python2.6, col_offset is 0. + # In python2.7, col_offset is 5. + # This is because python2.7 allows for multiple clauses: + # with X, Y: pass + # Since 'Y's col_offset isn't the beginning of the line, the authors + # of Python presumably changed 'X's col_offset to also not be the + # beginning of the line. If they had made the With ast node support + # multiple clauses, they wouldn't have needed to do that, but then + # that would introduce an API change in the AST. So it's + # understandable that they did that. + # Since we use startpos for breaking lines, we need to set startpos to + # the beginning of the line. + # In Python 3, the col_offset for the with is 0 again. + if (isinstance(ast_node, ast.With) and + not isinstance(parent_ast_node, ast.With) and + sys.version_info[:2] == (2,7)): + assert ast_node.col_offset >= 5 + if startpos.lineno == text.startpos.lineno: + linestart = text.startpos.colno + else: + linestart = 1 + line = text[(startpos.lineno,linestart):startpos] + m = re.search(r"\bwith\s+$", str(line)) + assert m + lk = len(m.group()) # length of 'with ' including spaces + startpos = FilePos(startpos.lineno, startpos.colno - lk) + assert str(text[startpos:(startpos+(0,4))]) == "with" + ast_node.startpos = startpos + if sys.version_info <= (3, 8): + ast_node.startpos = max(startpos, minpos) + return False + + assert ast_node.col_offset == -1 + if leftstr_node: + # This is an ast node where the leftmost deepest leaf is a + # multiline string. The bug that multiline strings have broken + # lineno/col_offset infects ancestors up the tree. + # + # If the leftmost leaf is a multi-line string, then ``lineno`` + # contains the ending line number, and col_offset is -1: + # >>> ast.parse("""'''foo\nbar'''+blah""").body[0].lineno + # 2 + # But if the leftmost leaf is not a multi-line string, then + # ``lineno`` contains the starting line number: + # >>> ast.parse("""'''foobar'''+blah""").body[0].lineno + # 1 + # >>> ast.parse("""blah+'''foo\nbar'''+blah""").body[0].lineno + # 1 + # + # To fix that, we copy start_lineno and start_colno from the Str + # node once we've corrected the values. + assert not isinstance(ast_node, (ast.Str, Bytes)) + assert leftstr_node.lineno == ast_node.lineno + assert leftstr_node.col_offset == -1 + ast_node.startpos = leftstr_node.startpos + return True + + # It should now be the case that we are looking at a multi-line string + # literal. + if sys.version_info >= (3, 7) and isinstance(ast_node, ast.FormattedValue): + ast_node.startpos = ast_node.value.startpos + ast_node.endpos = ast_node.value.startpos + + return True + if not isinstance(ast_node, (ast.Str, Bytes)): + raise ValueError( + "got a non-string col_offset=-1: %s" % (ast.dump(ast_node))) + # The ``lineno`` attribute gives the ending line number of the multiline + # string ... unless it's multiple multiline strings that are concatenated + # by adjacency, in which case it's merely the end of the first one of + # them. At least we know that the start lineno is definitely not later + # than the ``lineno`` attribute. + first_end_lineno = text.startpos.lineno + ast_node.lineno - 1 + # Compute possible start positions. + # The starting line number of this string could be anywhere between the + # end of the previous expression and ``first_end_lineno``. + startpos_candidates = [] + assert minpos.lineno <= first_end_lineno + for start_lineno in range(minpos.lineno, first_end_lineno + 1): + start_line = text[start_lineno] + start_line_colno = (text.startpos.colno + if start_lineno==text.startpos.lineno else 1) + startpos_candidates.extend([ + (_m.group()[-1], FilePos(start_lineno, _m.start()+start_line_colno)) + for _m in re.finditer("[bBrRuU]*[\"\']", start_line)]) + target_str = ast_node.s + + if isinstance(target_str, bytes) and sys.version_info[:2] == (3, 7): + target_str = target_str.decode() + + # Loop over possible end_linenos. The first one we've identified is the + # by far most likely one, but in theory it could be anywhere later in the + # file. This could be because of a dastardly concatenated string like + # this: + # """ # L1 + # two # L2 + # """ """ # L3 + # four # L4 + # five # L5 + # six # L6 + # """ # L7 + # There are two substrings on L1:L3 and L3:L7. The parser gives us a + # single concatenated string, but sets lineno to 3 instead of 7. We don't + # have much to go on to figure out that the real end_lineno is 7. If we + # don't find the string ending on L3, then search forward looking for the + # real end of the string. Yuck! + # + # This is now complicated by fstrings that do interpolate variable on 3.7 fixed on 3.8+) + # where we'll try to guess based on prefix + f_string_candidate_prefixes = [] + for end_lineno in range(first_end_lineno, text.endpos.lineno+1): + # Compute possible end positions. We're given the line we're ending + # on, but not the column position. Note that the ending line could + # contain more than just the string we're looking for -- including + # possibly other strings or comments. + end_line = text[end_lineno] + end_line_startcol = ( + text.startpos.colno if end_lineno==text.startpos.lineno else 1) + endpos_candidates = [ + (_m.group(), FilePos(end_lineno,_m.start()+end_line_startcol+1)) + for _m in re.finditer("[\"\']", end_line)] + if not endpos_candidates: + # We found no endpos_candidates. This should not happen for + # first_end_lineno because there should be _some_ string that ends + # there. + if end_lineno == first_end_lineno: + raise AssertionError( + "No quote char found on line with supposed string") + continue + # Filter and sort the possible startpos candidates given this endpos + # candidate. It's possible for the starting quotechar and ending + # quotechar to be different in case of adjacent string concatenation, + # e.g. "foo"'''bar'''. That said, it's an unlikely case, so + # deprioritize checking them. + likely_candidates = [] + unlikely_candidates = [] + for end_quotechar, endpos in reversed(endpos_candidates): + for start_quotechar, startpos in startpos_candidates: + if not startpos < endpos: + continue + if start_quotechar == end_quotechar: + candidate_list = likely_candidates + else: + candidate_list = unlikely_candidates + candidate_list.append((startpos,endpos)) + # Loop over sorted candidates. + matched_prefix = set() + for (startpos, endpos) in likely_candidates + unlikely_candidates: + # Try to parse the given range and see if it matches the target + # string literal. + subtext = text[startpos:endpos] + candidate_str = _test_parse_string_literal(subtext, flags) + if candidate_str is None: + continue + if isinstance(candidate_str, bytes) and sys.version_info[:2] == (3, 7): + candidate_str = candidate_str.decode() + + maybe_fstring = False + try: + if (3, 7) <= sys.version_info <= (3, 8): + potential_start = text.lines[startpos.lineno - 1] + maybe_fstring = ("f'" in potential_start) or ( + 'f"' in potential_start + ) + except IndexError: + pass + + if target_str == candidate_str and target_str: + # Success! + ast_node.startpos = startpos + ast_node.endpos = endpos + # This node is a multiline string; and, it's a leaf, so by + # definition it is the leftmost node. + return True # all done + elif candidate_str and target_str.startswith(candidate_str): + matched_prefix.add(startpos) + elif maybe_fstring: + candidate_prefix = candidate_str.split("{")[0] + if candidate_prefix and target_str.startswith(candidate_prefix): + f_string_candidate_prefixes.append((startpos, endpos)) + # We didn't find a string given the current end_lineno candidate. + # Only continue checking the startpos candidates that so far produced + # prefixes of the string we're looking for. + if not matched_prefix: + break + startpos_candidates = [ + (sq, sp) + for (sq, sp) in startpos_candidates + if sp in matched_prefix + ] + if (3, 7) <= sys.version_info <= (3, 8): + if len(f_string_candidate_prefixes) == 1: + # we did not find the string but there is one fstring candidate starting it + + ast_node.startpos, ast_node.endpos = f_string_candidate_prefixes[0] + return True + elif isinstance(parent_ast_node, ast.JoinedStr): + self_pos = parent_ast_node.values.index(ast_node) + ast_node.startpos = parent_ast_node.values[self_pos - 1].startpos + ast_node.endpos = parent_ast_node.values[self_pos - 1].endpos + return True + raise ValueError("Couldn't find exact position of %s" % (ast.dump(ast_node))) + + +def _annotate_ast_context(ast_node): + """ + Recursively annotate ``context`` on ast nodes, setting ``context`` to + a `AstNodeContext` named tuple with values + ``(parent, field, index)``. + Each ast_node satisfies ``parent.[] is ast_node``. + + For non-list fields, the index part is ``None``. + """ + assert isinstance(ast_node, ast.AST) + for field_name, field_value in ast.iter_fields(ast_node): + if isinstance(field_value, ast.AST): + child_node = field_value + child_node.context = AstNodeContext(ast_node, field_name, None) + _annotate_ast_context(child_node) + elif isinstance(field_value, list): + for i, item in enumerate(field_value): + if isinstance(item, ast.AST): + child_node = item + child_node.context = AstNodeContext(ast_node, field_name, i) + _annotate_ast_context(child_node) + + +def _split_code_lines(ast_nodes, text): + """ + Split the given ``ast_nodes`` and corresponding ``text`` by code/noncode + statement. + + Yield tuples of (nodes, subtext). ``nodes`` is a list of ``ast.AST`` nodes, + length 0 or 1; ``subtext`` is a `FileText` sliced from ``text``. + + FileText(...))} for code lines and ``(None, FileText(...))`` for non-code + lines (comments and blanks). + + :type ast_nodes: + sequence of ``ast.AST`` nodes + :type text: + `FileText` + """ + if not ast_nodes: + yield ([], text) + return + assert text.startpos <= ast_nodes[0].startpos + assert ast_nodes[-1].startpos < text.endpos + if text.startpos != ast_nodes[0].startpos: + # Starting noncode lines. + yield ([], text[text.startpos:ast_nodes[0].startpos]) + end_sentinel = _DummyAst_Node() + end_sentinel.startpos = text.endpos + for node, next_node in zip(ast_nodes, ast_nodes[1:] + [end_sentinel]): + startpos = node.startpos + next_startpos = next_node.startpos + assert startpos < next_startpos + # We have the start position of this node. Figure out the end + # position, excluding noncode lines (standalone comments and blank + # lines). + if hasattr(node, 'endpos'): + # We have an endpos for the node because this was a multi-line + # string. Start with the node endpos. + endpos = node.endpos + assert startpos < endpos <= next_startpos + # enpos points to the character *after* the ending quote, so we + # know that this is never at the beginning of the line. + assert endpos.colno != 1 + # Advance past whitespace an inline comment, if any. Do NOT + # advance past other code that could be on the same line, nor past + # blank lines and comments on subsequent lines. + line = text[endpos : min(text.endpos, FilePos(endpos.lineno+1,1))] + if _is_comment_or_blank(line): + endpos = FilePos(endpos.lineno+1, 1) + else: + endpos = next_startpos + assert endpos <= text.endpos + # We don't have an endpos yet; what we do have is the next node's + # startpos (or the position at the end of the text). Start there + # and work backward. + if endpos.colno != 1: + if endpos == text.endpos: + # There could be a comment on the last line and no + # trailing newline. + # TODO: do this in a more principled way. + if _is_comment_or_blank(text[endpos.lineno]): + assert startpos.lineno < endpos.lineno + if not text[endpos.lineno-1].endswith("\\"): + endpos = FilePos(endpos.lineno,1) + else: + # We're not at end of file, yet the next node starts in + # the middle of the line. This should only happen with if + # we're not looking at a comment. [The first character in + # the line could still be "#" if we're inside a multiline + # string that's the last child of the parent node. + # Therefore we don't assert 'not + # _is_comment_or_blank(...)'.] + pass + if endpos.colno == 1: + while (endpos.lineno-1 > startpos.lineno and + _is_comment_or_blank(text[endpos.lineno-1]) and + (not text[endpos.lineno-2].endswith("\\") or + _is_comment_or_blank(text[endpos.lineno-2]))): + endpos = FilePos(endpos.lineno-1, 1) + assert startpos < endpos <= next_startpos + yield ([node], text[startpos:endpos]) + if endpos != next_startpos: + yield ([], text[endpos:next_startpos]) + + +def _ast_node_is_in_docstring_position(ast_node): + """ + Given a ``Str`` AST node, return whether its position within the AST makes + it eligible as a docstring. + + The main way a ``Str`` can be a docstring is if it is a standalone string + at the beginning of a ``Module``, ``FunctionDef``, or ``ClassDef``. + + We also support variable docstrings per Epydoc: + + - If a variable assignment statement is immediately followed by a bare + string literal, then that assignment is treated as a docstring for + that variable. + + :type ast_node: + ``ast.Str`` + :param ast_node: + AST node that has been annotated by ``_annotate_ast_nodes``. + :rtype: + ``bool`` + :return: + Whether this string ast node is in docstring position. + """ + if not isinstance(ast_node, (ast.Str, Bytes)): + raise TypeError + expr_node = ast_node.context.parent + if not isinstance(expr_node, ast.Expr): + return False + assert ast_node.context.field == 'value' + assert ast_node.context.index is None + expr_ctx = expr_node.context + if expr_ctx.field != 'body': + return False + parent_node = expr_ctx.parent + if not isinstance(parent_node, (ast.FunctionDef, ast.ClassDef, ast.Module)): + return False + if expr_ctx.index == 0: + return True + prev_sibling_node = parent_node.body[expr_ctx.index-1] + if isinstance(prev_sibling_node, ast.Assign): + return True + return False + + +def infer_compile_mode(arg): + """ + Infer the mode needed to compile ``arg``. + + :type arg: + ``ast.AST`` + :rtype: + ``str`` + """ + # Infer mode from ast object. + if isinstance(arg, ast.Module): + mode = "exec" + elif isinstance(arg, ast.Expression): + mode = "eval" + elif isinstance(arg, ast.Interactive): + mode = "single" + else: + raise TypeError( + "Expected Module/Expression/Interactive ast node; got %s" + % (type(arg).__name__)) + return mode + + +class _DummyAst_Node(object): + pass + + +class PythonStatement(object): + r""" + Representation of a top-level Python statement or consecutive + comments/blank lines. + + >>> PythonStatement('print("x",\n file=None)\n', flags='print_function') #doctest: +SKIP + PythonStatement('print("x",\n file=None)\n', flags=0x10000) + + Implemented as a wrapper around a `PythonBlock` containing at most one + top-level AST node. + """ + + def __new__(cls, arg, filename=None, startpos=None, flags=None): + if isinstance(arg, cls): + if filename is startpos is flags is None: + return arg + arg = arg.block + # Fall through + if isinstance(arg, (PythonBlock, FileText, str, six.text_type)): + block = PythonBlock(arg, filename=filename, + startpos=startpos, flags=flags) + statements = block.statements + if len(statements) != 1: + raise ValueError( + "Code contains %d statements instead of exactly 1: %r" + % (len(statements), block)) + statement, = statements + assert isinstance(statement, cls) + return statement + raise TypeError("PythonStatement: unexpected %s" % (type(arg).__name__,)) + + @classmethod + def _construct_from_block(cls, block): + # Only to be used by PythonBlock. + assert isinstance(block, PythonBlock) + self = object.__new__(cls) + self.block = block + return self + + @property + def text(self): + """ + :rtype: + `FileText` + """ + return self.block.text + + @property + def filename(self): + """ + :rtype: + `Filename` + """ + return self.text.filename + + @property + def startpos(self): + """ + :rtype: + `FilePos` + """ + return self.text.startpos + + @property + def flags(self): + """ + :rtype: + `CompilerFlags` + """ + return self.block.flags + + @property + def ast_node(self): + """ + A single AST node representing this statement, or ``None`` if this + object only represents comments/blanks. + + :rtype: + ``ast.AST`` or ``NoneType`` + """ + ast_nodes = self.block.ast_node.body + if len(ast_nodes) == 0: + return None + if len(ast_nodes) == 1: + return ast_nodes[0] + raise AssertionError("More than one AST node in block") + + @property + def is_comment_or_blank(self): + return self.ast_node is None + + @property + def is_comment_or_blank_or_string_literal(self): + return (self.is_comment_or_blank + or _ast_str_literal_value(self.ast_node) is not None) + + @property + def is_import(self): + return isinstance(self.ast_node, (ast.Import, ast.ImportFrom)) + + @property + def is_single_assign(self): + n = self.ast_node + return isinstance(n, ast.Assign) and len(n.targets) == 1 + + def get_assignment_literal_value(self): + """ + If the statement is an assignment, return the name and literal value. + + >>> PythonStatement('foo = {1: {2: 3}}').get_assignment_literal_value() + ('foo', {1: {2: 3}}) + + :return: + (target, literal_value) + """ + if not self.is_single_assign: + raise ValueError( + "Statement is not an assignment to a single name: %s" % self) + n = self.ast_node + target_name = n.targets[0].id + literal_value = ast.literal_eval(n.value) + return (target_name, literal_value) + + def __repr__(self): + r = repr(self.block) + assert r.startswith("PythonBlock(") + r = "PythonStatement(" + r[12:] + return r + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, PythonStatement): + return NotImplemented + return self.block == other.block + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, PythonStatement): + return NotImplemented + return self.block < other.block + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, PythonStatement): + return NotImplemented + return cmp(self.block, other.block) + + def __hash__(self): + return hash(self.block) + + +@total_ordering +class PythonBlock(object): + r""" + Representation of a sequence of consecutive top-level + `PythonStatement` (s). + + >>> source_code = '# 1\nprint(2)\n# 3\n# 4\nprint(5)\nx=[6,\n 7]\n# 8\n' + >>> codeblock = PythonBlock(source_code) + >>> for stmt in PythonBlock(codeblock).statements: + ... print(stmt) + PythonStatement('# 1\n') + PythonStatement('print(2)\n', startpos=(2,1)) + PythonStatement('# 3\n# 4\n', startpos=(3,1)) + PythonStatement('print(5)\n', startpos=(5,1)) + PythonStatement('x=[6,\n 7]\n', startpos=(6,1)) + PythonStatement('# 8\n', startpos=(8,1)) + + A ``PythonBlock`` has a ``flags`` attribute that gives the compiler_flags + associated with the __future__ features using which the code should be + parsed. + + """ + + def __new__(cls, arg, filename=None, startpos=None, flags=None, + auto_flags=None): + if isinstance(arg, PythonStatement): + arg = arg.block + # Fall through + if isinstance(arg, cls): + if filename is startpos is flags is None: + return arg + flags = CompilerFlags(flags, arg.flags) + arg = arg.text + # Fall through + if isinstance(arg, (FileText, Filename, str, six.text_type)): + return cls.from_text( + arg, filename=filename, startpos=startpos, + flags=flags, auto_flags=auto_flags) + raise TypeError("%s: unexpected %s" + % (cls.__name__, type(arg).__name__,)) + + @classmethod + def from_filename(cls, filename): + return cls.from_text(Filename(filename)) + + @classmethod + def from_text(cls, text, filename=None, startpos=None, flags=None, + auto_flags=False): + """ + :type text: + `FileText` or convertible + :type filename: + ``Filename`` + :param filename: + Filename, if not already given by ``text``. + :type startpos: + ``FilePos`` + :param startpos: + Starting position, if not already given by ``text``. + :type flags: + ``CompilerFlags`` + :param flags: + Input compiler flags. + :param auto_flags: + Whether to try other flags if ``flags`` fails. + :rtype: + `PythonBlock` + """ + text = FileText(text, filename=filename, startpos=startpos) + self = object.__new__(cls) + self.text = text + self._input_flags = CompilerFlags(flags) + self._auto_flags = auto_flags + return self + + @classmethod + def __construct_from_annotated_ast(cls, annotated_ast_nodes, text, flags): + # Constructor for internal use by _split_by_statement() or + # concatenate(). + ast_node = ast.Module(annotated_ast_nodes) + ast_node.text = text + ast_node.flags = flags + if not hasattr(ast_node, "source_flags"): + ast_node.source_flags = CompilerFlags.from_ast(annotated_ast_nodes) + self = object.__new__(cls) + self._ast_node_or_parse_exception = ast_node + self.ast_node = ast_node + self.annotated_ast_node = ast_node + self.text = text + self.flags = self._input_flags = flags + self._auto_flags = False + return self + + @classmethod + def concatenate(cls, blocks, assume_contiguous=False): + """ + Concatenate a bunch of blocks into one block. + + :type blocks: + sequence of `PythonBlock` s and/or `PythonStatement` s + :param assume_contiguous: + Whether to assume, without checking, that the input blocks were + originally all contiguous. This must be set to True to indicate the + caller understands the assumption; False is not implemented. + """ + if not assume_contiguous: + raise NotImplementedError + blocks = [PythonBlock(b) for b in blocks] + if len(blocks) == 1: + return blocks[0] + assert blocks + text = FileText.concatenate([b.text for b in blocks]) + # The contiguous assumption is important here because ``ast_node`` + # contains line information that would otherwise be wrong. + ast_nodes = [n for b in blocks for n in b.annotated_ast_node.body] + flags = blocks[0].flags + return cls.__construct_from_annotated_ast(ast_nodes, text, flags) + + @property + def filename(self): + return self.text.filename + + @property + def startpos(self): + return self.text.startpos + + @property + def endpos(self): + return self.text.endpos + + @cached_attribute + def _ast_node_or_parse_exception(self): + """ + Attempt to parse this block of code into an abstract syntax tree. + Cached (including exception case). + + :return: + Either ast_node or exception. + """ + # This attribute may also be set by __construct_from_annotated_ast(), + # in which case this code does not run. + try: + return _parse_ast_nodes( + self.text, self._input_flags, self._auto_flags, "exec") + except Exception as e: + # Add the filename to the exception message to be nicer. + if self.text.filename: + try: + e = type(e)("While parsing %s: %s" % (self.text.filename, e)) + except TypeError: + # Exception takes more than one argument + pass + # Cache the exception to avoid re-attempting while debugging. + return e + + @cached_attribute + def parsable(self): + """ + Whether the contents of this ``PythonBlock`` are parsable as Python + code, using the given flags. + + :rtype: + ``bool`` + """ + return isinstance(self._ast_node_or_parse_exception, ast.AST) + + @cached_attribute + def parsable_as_expression(self): + """ + Whether the contents of this ``PythonBlock`` are parsable as a single + Python expression, using the given flags. + + :rtype: + ``bool`` + """ + return self.parsable and self.expression_ast_node is not None + + @cached_attribute + def ast_node(self): + """ + Parse this block of code into an abstract syntax tree. + + The returned object type is the kind of AST as returned by the + ``compile`` built-in (rather than as returned by the older, deprecated + ``compiler`` module). The code is parsed using mode="exec". + + The result is a ``ast.Module`` node, even if this block represents only + a subset of the entire file. + + :rtype: + ``ast.Module`` + """ + r = self._ast_node_or_parse_exception + if isinstance(r, ast.AST): + return r + else: + raise r + + @cached_attribute + def annotated_ast_node(self): + """ + Return ``self.ast_node``, annotated in place with positions. + + All nodes are annotated with ``startpos``. + All top-level nodes are annotated with ``endpos``. + + :rtype: + ``ast.Module`` + """ + result = self.ast_node + _annotate_ast_nodes(result) + return result + + @cached_attribute + def expression_ast_node(self): + """ + Return an ``ast.Expression`` if ``self.ast_node`` can be converted into + one. I.e., return parse(self.text, mode="eval"), if possible. + + Otherwise, return ``None``. + + :rtype: + ``ast.Expression`` + """ + node = self.ast_node + if len(node.body) == 1 and isinstance(node.body[0], ast.Expr): + return ast.Expression(node.body[0].value) + else: + return None + + def parse(self, mode=None): + """ + Parse the source text into an AST. + + :param mode: + Compilation mode: "exec", "single", or "eval". "exec", "single", + and "eval" work as the built-in ``compile`` function do. If ``None``, + then default to "eval" if the input is a string with a single + expression, else "exec". + :rtype: + ``ast.AST`` + """ + if mode == "exec": + return self.ast_node + elif mode == "eval": + if self.expression_ast_node: + return self.expression_ast_node + else: + raise SyntaxError + elif mode == None: + if self.expression_ast_node: + return self.expression_ast_node + else: + return self.ast_node + elif mode == "exec": + raise NotImplementedError + else: + raise ValueError("parse(): invalid mode=%r" % (mode,)) + + def compile(self, mode=None): + """ + Parse into AST and compile AST into code. + + :rtype: + ``CodeType`` + """ + ast_node = self.parse(mode=mode) + mode = infer_compile_mode(ast_node) + filename = str(self.filename or "") + return compile(ast_node, filename, mode) + + @cached_attribute + def statements(self): + r""" + Partition of this ``PythonBlock`` into individual ``PythonStatement`` s. + Each one contains at most 1 top-level ast node. A ``PythonStatement`` + can contain no ast node to represent comments. + + >>> code = "# multiline\n# comment\n'''multiline\nstring'''\nblah\n" + >>> print(PythonBlock(code).statements) # doctest:+NORMALIZE_WHITESPACE + (PythonStatement('# multiline\n# comment\n'), + PythonStatement("'''multiline\nstring'''\n", startpos=(3,1)), + PythonStatement('blah\n', startpos=(5,1))) + + :rtype: + ``tuple`` of `PythonStatement` s + """ + node = self.annotated_ast_node + nodes_subtexts = list(_split_code_lines(node.body, self.text)) + if nodes_subtexts == [(self.ast_node.body, self.text)]: + # This block is either all comments/blanks or a single statement + # with no surrounding whitespace/comment lines. Return self. + return (PythonStatement._construct_from_block(self),) + cls = type(self) + statement_blocks = [ + cls.__construct_from_annotated_ast(subnodes, subtext, self.flags) + for subnodes, subtext in nodes_subtexts] + # Convert to statements. + statements = [] + for b in statement_blocks: + statement = PythonStatement._construct_from_block(b) + statements.append(statement) + # Optimization: set the new sub-block's ``statements`` attribute + # since we already know it contains exactly one statement, itself. + assert 'statements' not in b.__dict__ + b.statements = (statement,) + return tuple(statements) + + @cached_attribute + def source_flags(self): + """ + If the AST contains __future__ imports, then the compiler_flags + associated with them. Otherwise, 0. + + The difference between ``source_flags`` and ``flags`` is that ``flags`` + may be set by the caller (e.g. based on an earlier __future__ import) + and include automatically guessed flags, whereas ``source_flags`` is + only nonzero if this code itself contains __future__ imports. + + :rtype: + `CompilerFlags` + """ + return self.ast_node.source_flags + + @cached_attribute + def flags(self): + """ + The compiler flags for this code block, including both the input flags + (possibly automatically guessed), and the flags from "__future__" + imports in the source code text. + + :rtype: + `CompilerFlags` + """ + return self.ast_node.flags + + def groupby(self, predicate): + """ + Partition this block of code into smaller blocks of code which + consecutively have the same ``predicate``. + + :param predicate: + Function that takes a `PythonStatement` and returns a value. + :return: + Generator that yields (group, `PythonBlock` s). + """ + cls = type(self) + for pred, stmts in groupby(self.statements, predicate): + blocks = [s.block for s in stmts] + yield pred, cls.concatenate(blocks, assume_contiguous=True) + + def string_literals(self): + r""" + Yield all string literals anywhere in this block. + + The string literals have ``startpos`` attributes attached. + + >>> block = PythonBlock("'a' + ('b' + \n'c')") + >>> [(f.s, f.startpos) for f in block.string_literals()] + [('a', FilePos(1,1)), ('b', FilePos(1,8)), ('c', FilePos(2,1))] + + :return: + Iterable of ``ast.Str`` or ``ast.Bytes`` nodes + """ + for node in _walk_ast_nodes_in_order(self.annotated_ast_node): + if isinstance(node, (ast.Str, Bytes)): + assert hasattr(node, 'startpos') + yield node + + def _get_docstring_nodes(self): + """ + Yield docstring AST nodes. + + We consider the following to be docstrings:: + + - First literal string of function definitions, class definitions, + and modules (the python standard) + - Literal strings after assignments, per Epydoc + + :rtype: + Generator of ``ast.Str`` nodes + """ + # This is similar to ``ast.get_docstring``, but: + # - This function is recursive + # - This function yields the node object, rather than the string + # - This function yields multiple docstrings (even per ast node) + # - This function doesn't raise TypeError on other AST types + # - This function doesn't cleandoc + # A previous implementation did + # [n for n in self.string_literals() + # if _ast_node_is_in_docstring_position(n)] + # However, the method we now use is more straightforward, and doesn't + # require first annotating each node with context information. + docstring_containers = (ast.FunctionDef, ast.ClassDef, ast.Module) + for node in _walk_ast_nodes_in_order(self.annotated_ast_node): + if not isinstance(node, docstring_containers): + continue + if not node.body: + continue + # If the first body item is a literal string, then yield the node. + if (isinstance(node.body[0], ast.Expr) and + isinstance(node.body[0].value, ast.Str)): + yield node.body[0].value + for i in range(1, len(node.body)-1): + # If a body item is an assignment and the next one is a + # literal string, then yield the node for the literal string. + n1, n2 = node.body[i], node.body[i+1] + if (isinstance(n1, ast.Assign) and + isinstance(n2, ast.Expr) and + isinstance(n2.value, ast.Str)): + yield n2.value + + def get_doctests(self): + r""" + Return doctests in this code. + + >>> PythonBlock("x\n'''\n >>> foo(bar\n ... + baz)\n'''\n").get_doctests() + [PythonBlock('foo(bar\n + baz)\n', startpos=(3,2))] + + :rtype: + ``list`` of `PythonStatement` s + """ + parser = IgnoreOptionsDocTestParser() + doctest_blocks = [] + filename = self.filename + flags = self.flags + for ast_node in self._get_docstring_nodes(): + try: + examples = parser.get_examples(ast_node.s) + except Exception: + blob = ast_node.s + if len(blob) > 60: + blob = blob[:60] + '...' + # TODO: let caller decide how to handle + logger.warning("Can't parse docstring; ignoring: %r", blob) + continue + for example in examples: + lineno = ast_node.startpos.lineno + example.lineno + colno = ast_node.startpos.colno + example.indent # dubious + text = FileText(example.source, filename=filename, + startpos=(lineno,colno)) + try: + block = PythonBlock(text, flags=flags) + block.ast_node # make sure we can parse + except Exception: + blob = text.joined + if len(blob) > 60: + blob = blob[:60] + '...' + logger.warning("Can't parse doctest; ignoring: %r", blob) + continue + doctest_blocks.append(block) + return doctest_blocks + + def __repr__(self): + r = "%s(%r" % (type(self).__name__, self.text.joined) + if self.filename: + r += ", filename=%r" % (str(self.filename),) + if self.startpos != FilePos(): + r += ", startpos=%s" % (self.startpos,) + if self.flags != self.source_flags: + r += ", flags=%s" % (self.flags,) + r += ")" + return r + + def __str__(self): + return str(self.text) + + def __text__(self): + return self.text + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, PythonBlock): + return NotImplemented + return self.text == other.text and self.flags == other.flags + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, PythonBlock): + return NotImplemented + return (self.text, self.flags) < (other.text, other.flags) + + def __cmp__(self, other): + if self is other: + return 0 + if not isinstance(other, PythonBlock): + return NotImplemented + return cmp(self.text, other.text) or cmp(self.flags, other.flags) + + def __hash__(self): + h = hash((self.text, self.flags)) + self.__hash__ = lambda: h + return h + +class IgnoreOptionsDocTestParser(DocTestParser): + def _find_options(self, source, name, lineno): + # Ignore doctest options. We don't use them, and we don't want to + # error on unknown options, which is what the default DocTestParser + # does. + return {} diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_py.py b/.venv/lib/python3.8/site-packages/pyflyby/_py.py new file mode 100644 index 0000000..52106e7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_py.py @@ -0,0 +1,2046 @@ +# pyflyby/_py.py +# Copyright (C) 2014, 2015, 2018, 2019 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +r""" +The `py` program (part of the pyflyby project) is a command-line multitool for +running python code, with heuristic intention guessing, automatic importing, +and debugging support. + +Invocation summary +================== + +.. code:: + + py [--file] filename.py arg1 arg2 Execute a file + py [--eval] 'function(arg1, arg2)' Evaluate an expression/statement + py [--apply] function arg1 arg2 Call function(arg1, arg2) + py [--module] modname arg1 arg2 Run a module + + py --map function arg1 arg2 Call function(arg1); function(arg2) + + py -i 'function(arg1, arg2)' Run file/code/etc, then run IPython + py --debug 'function(arg1, arg2)' Debug file/code/etc + py --debug PID Attach debugger to PID + + py function? Get help for a function or module + py function?? Get source of a function or module + + py Start IPython with autoimporter + py nb Start IPython Notebook with autoimporter + + +Features +======== + + * Heuristic action mode guessing: If none of --file, --eval, --apply, + --module, or --map is specified, then guess what to do, choosing one of + these actions: + + * Execute (run) a file + * Evaluate concatenated arguments + * Run a module + * Call (apply) a function + * Evaluate first argument + + * Automatic importing: All action modes (except run_module) automatically + import as needed. + + * Heuristic argument evaluation: By default, `py --eval`, `py --apply`, and + `py --map` guess whether the arguments should be interpreted as + expressions or literal strings. A "--" by itself will designate subsequent + args as strings. A "-" by itself will be replaced by the contents of + stdin as a string. + + * Merged eval/exec: Code is eval()ed or exec()ed as appropriate. + + * Result printing: By default, results are pretty-printed if not None. + + * Heuristic flags: "print" can be used as a function or a statement. + + * Matplotlib/pylab integration: show() is called if appropriate to block on + plots. + + * Enter debugger upon unhandled exception. (This functionality is enabled + by default when stdout is a tty. Use --postmortem=no to never use the + postmortem debugger. Use --postmortem=yes enable even if stdout is not a + tty. If the postmortem debugger is enabled but /dev/tty is not available, + then if an exception occurs, py will email instructions for attaching a + debugger.) + + * Control-\\ (SIGQUIT) enters debugger while running (and allows continuing). + + * New builtin functions such as "debugger()". + +Warning +======= +`py` is intended as an interactive tool. When writing shell aliases for +interactive use, the `--safe` option can be useful. When writing scripts, +it's better to avoid all heuristic guessing; use regular `python -c ...`, or +better yet, a full-fledged python program (and run tidy-imports). + + +Options +======= + +.. code:: + + Global options valid before code argument: + + --args=string Interpret all arguments as literal strings. + (The "--" argument also specifies remaining arguments to be + literal strings.) + --args=eval Evaluate all arguments as expressions. + --args=auto (Default) Heuristically guess whether to evaluate arguments + as literal strings or expressions. + --output=silent Don't print the result of evaluation. + --output=str Print str(result). + --output=repr Print repr(result). + --output=pprint Print pprint.pformat(result). + --output=repr-if-not-none + Print repr(result), but only if result is not None. + --output=pprint-if-not-none + Print pprint.pformat(result), but only if result is not None. + --output=interactive + (Default) Print result.__interactive_display__() if defined, + else pprint if result is not None. + --output=exit Raise SystemExit(result). + --safe Equivalent to --args=strings and PYFLYBY_PATH=EMPTY. + --quiet, --q Log only error messages to stderr; omit info and warnings. + --interactive, --i + Run an IPython shell after completion + --debug, --d Run the target code file etc under the debugger. If a PID is + given, then instead attach a debugger to the target PID. + --verbose Turn on verbose messages from pyflyby. + + Pseudo-actions valid before, after, or without code argument: + + --version Print pyflyby version or version of a module. + --help, --h, --? Print this help or help for a function or module. + --source, --?? Print source code for a function or module. + + +Examples +======== + + Start IPython with pyflyby autoimporter enabled:: + + $ py + + Start IPython/Jupyter Notebook with pyflyby autoimporter enabled:: + + $ py nb + + Find the ASCII value of the letter "j" (apply builtin function):: + + $ py ord j + [PYFLYBY] ord('j') + 106 + + Decode a base64-encoded string (apply autoimported function):: + + $ py b64decode aGVsbG8= + [PYFLYBY] from base64 import b64decode + [PYFLYBY] b64decode('aGVsbG8=', altchars=None) + b'hello' + + Find the day of the week of some date (apply function in module):: + + $ py calendar.weekday 2014 7 18 + [PYFLYBY] import calendar + [PYFLYBY] calendar.weekday(2014, 7, 18) + 4 + + Using named arguments:: + + $ py calendar.weekday --day=16 --month=7 --year=2014 + [PYFLYBY] import calendar + [PYFLYBY] calendar.weekday(2014, 7, 16) + 2 + + Using short named arguments:: + + $ py calendar.weekday -m 7 -d 15 -y 2014 + [PYFLYBY] import calendar + [PYFLYBY] calendar.weekday(2014, 7, 15) + 1 + + Invert a matrix (evaluate expression, with autoimporting):: + + $ py 'matrix("1 3 3; 1 4 3; 1 3 4").I' + [PYFLYBY] from numpy import matrix + [PYFLYBY] matrix("1 3 3; 1 4 3; 1 3 4").I + matrix([[ 7., -3., -3.], + [-1., 1., 0.], + [-1., 0., 1.]]) + + Plot cosine (evaluate expression, with autoimporting):: + + $ py 'plot(cos(arange(30)))' + [PYFLYBY] from numpy import arange + [PYFLYBY] from numpy import cos + [PYFLYBY] from matplotlib.pyplot import plot + [PYFLYBY] plot(cos(arange(30))) + + + Command-line calculator (multiple arguments):: + + $ py 3 / 4 + 0.75 + + Command-line calculator (single arguments):: + + $ py '(5+7j) \** 12' + (65602966976-150532462080j) + + Rationalize a decimal (apply bound method):: + + $ py 2.5.as_integer_ratio + [PYFLYBY] 2.5.as_integer_ratio() + (5, 2) + + Rationalize a decimal (apply unbound method):: + + $ py float.as_integer_ratio 2.5 + [PYFLYBY] float.as_integer_ratio(2.5) + (5, 2) + + Rationalize decimals (map/apply):: + + $ py --map float.as_integer_ratio 2.5 3.5 + [PYFLYBY] float.as_integer_ratio(2.5) + (5, 2) + [PYFLYBY] float.as_integer_ratio(3.5) + (7, 2) + + Square numbers (map lambda):: + + $ py --map 'lambda x: x \**2' 3 4 5 + [PYFLYBY] (lambda x: x \**2)(3) + 9 + [PYFLYBY] (lambda x: x \**2)(4) + 16 + [PYFLYBY] (lambda x: x \**2)(5) + 25 + + Find length of string (using "-" for stdin):: + + $ echo hello | py len - + [PYFLYBY] len('hello\\n') + 6 + + Run stdin as code:: + + $ echo 'print(sys.argv[1:])' | py - hello world + [PYFLYBY] import sys + ['hello', 'world'] + + Run libc functions:: + + $ py --quiet --output=none 'CDLL("libc.so.6").printf' %03d 7 + 007 + + Download web page:: + + $ py --print 'requests.get(sys.argv[1]).text' http://example.com + + Get function help:: + + $ py b64decode? + [PYFLYBY] from base64 import b64decode + Python signature:: + + >> b64decode(s, altchars=None, validate=False) + + Command-line signature:: + + $ py b64decode s [altchars [validate]] + $ py b64decode --s=... [--altchars=...] [--validate=...] + ... + + Get module help:: + + $ py pandas? + [PYFLYBY] import pandas + Version: + 0.13.1 + Filename: + /usr/local/lib/python2.7/site-packages/pandas/__init__.pyc + Docstring: + pandas - a powerful data analysis and manipulation library for Python + ... + +""" + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from functools import total_ordering + +from pyflyby._util import cmp + +usage = """ +py --- command-line python multitool with automatic importing + +$ py [--file] filename.py arg1 arg2 Execute file +$ py [--apply] function arg1 arg2 Call function +$ py [--eval] 'function(arg1, arg2)' Evaluate code +$ py [--module] modname arg1 arg2 Run a module + +$ py --debug file/code... args... Debug code +$ py --debug PID Attach debugger to PID + +$ py IPython shell +""".strip() + +# TODO: add --tidy-imports, etc + +# TODO: new --action="concat_eval eval apply" etc. specifying multiple +# actions means try each of them in that order. then --safe can exclude +# concat-eval, and users can customize which action modes are included. +# --apply would be equivalent to --action=apply. + +# TODO: plug-in system. 'py foo' should attempt something that the +# user/vendor can add to the system. leading candidate: use entry_point +# system (http://stackoverflow.com/a/774859). other candidates: maybe use +# python namespace like pyflyby.vendor.foo or pyflyby.commands.foo or +# pyflyby.magics.foo, or maybe a config file. +# TODO: note additional features in documentation feature list + +# TODO: somehow do the right thing with glob.glob vs glob, pprint.pprint vs +# pprint, etc. Ideas: +# - make --apply special case detect modules and take module.module +# - enhance auto_import() to keep track of the context while finding missing imports +# - enhance auto_import() to scan for calls after importing + +# TODO: pipe help/source output (all output?) through $PYFLYBY_PAGER (default "less -FRX"). + +# TODO: unparse ast node for info logging +# https://hg.python.org/cpython/log/tip/Tools/parser/unparse.py + +# TODO: run_module should detect if the module doesn't check __name__ and +# therefore is unlikely to be meaningful to use with run_module. + +# TODO: make sure run_modules etc work correctly with modules under namespace +# packages. + +# TODO: detect deeper ImportError, e.g. suppose user accesses module1; module1 +# imports badmodule, which can't be imported successfully and raises +# ImportError; we should get that ImportError instead of trying other things +# or turning it into a string. probably do this by changing the place where +# we import modules to first get the loader, then if import fails, raise a +# subclass of ImportError. + +# TODO: provide a way to omit newline in output. maybe --output=write. + +# TODO: make sure 'py -c' matches behavior of 'python -c' w.r.t. sys.modules["__main__"] +# $ py -c 'x=3;import sys; print sys.modules["__main__"].__dict__["x"]' +# mimic runpy.{_TempModule,_run_code} + +# TODO: refactor this module - maybe to _heuristic.py + +# TODO: add --profile, --runsnake + +import ast +from contextlib import contextmanager +import inspect +import os +import re +import six +from six.moves import builtins +import sys +import types +from types import FunctionType, MethodType + +from pyflyby._autoimp import auto_import, find_missing_imports +from pyflyby._cmdline import print_version_and_exit, syntax +from pyflyby._dbg import (add_debug_functions_to_builtins, + attach_debugger, debug_statement, + debugger, enable_faulthandler, + enable_signal_handler_debugger, + enable_sigterm_handler, + remote_print_stack) +from pyflyby._file import Filename, UnsafeFilenameError, which +from pyflyby._flags import CompilerFlags +from pyflyby._idents import is_identifier +from pyflyby._interactive import (get_ipython_terminal_app_with_autoimporter, + run_ipython_line_magic, + start_ipython_with_autoimporter) +from pyflyby._log import logger +from pyflyby._modules import ModuleHandle +from pyflyby._parse import PythonBlock +from pyflyby._util import indent, prefixes + + +# Default compiler flags (feature flags) used for all user code. We include +# "print_function" here, but we also use auto_flags=True, which means +# print_function may be flipped off if the code contains print statements. +FLAGS = CompilerFlags(["absolute_import", "with_statement", "division", + "print_function"]) + + +def _get_argspec(arg, _recurse=False): + from inspect import getargspec, ArgSpec + if isinstance(arg, FunctionType): + return getargspec(arg) + elif isinstance(arg, MethodType): + argspec = getargspec(arg) + if arg.__self__ is not None: + # For bound methods, ignore the "self" argument. + return ArgSpec(argspec.args[1:], *argspec[1:]) + return argspec + elif isinstance(arg, type): + if arg.__new__ is not object.__new__: + argspec = _get_argspec(arg.__new__) + return ArgSpec(argspec.args[1:], *argspec[1:]) + else: + argspec = _get_argspec(arg.__init__) + return ArgSpec(argspec.args[1:], *argspec[1:]) + # Old style class. Should only run in Python 2. types.ClassType doesn't + # exist in Python 3. + elif isinstance(arg, getattr(types, 'ClassType', type)): + argspec = _get_argspec(arg.__init__) + return ArgSpec(argspec.args[1:], *argspec[1:]) + elif _recurse and hasattr(arg, '__call__'): + return _get_argspec(arg.__call__, _recurse=False) + elif callable(arg): + # Unknown, probably a built-in method. + return ArgSpec((), "args", "kwargs", None) + raise TypeError( + "_get_argspec: unexpected %s" % (type(arg).__name__,)) + + +def _requires_parens_as_function(function_name): + """ + Returns whether the given string of a callable would require parentheses + around it to call it. + + >>> _requires_parens_as_function("foo.bar[4]") + False + + >>> _requires_parens_as_function("foo+bar") + True + + >>> _requires_parens_as_function("(foo+bar)()") + False + + >>> _requires_parens_as_function("(foo+bar)") + False + + >>> _requires_parens_as_function("(foo)+(bar)") + True + + :type function_name: + ``str`` + :rtype: + ``bool`` + """ + # TODO: this might be obsolete if we use unparse instead of keeping original + # user formatting (or alternatively, unparse should use something like this). + + function_name = PythonBlock(function_name, flags=FLAGS) + node = function_name.expression_ast_node + if not node: + # Couldn't parse? Just assume we do need parens for now. Or should + # we raise an exception here? + return True + body = node.body + # Is it something that doesn't need parentheses? + if isinstance(body, (ast.Name, ast.Attribute, ast.Call, ast.Subscript)): + return False + # Does it already have parentheses? + n = str(function_name) + if n.startswith("(") and n.endswith(")"): + # It has parentheses, superficially. Make sure it's not something + # like "(foo)+(bar)". + flags = int(FLAGS) | ast.PyCF_ONLY_AST + try: + tnode = compile(n[1:-1], "", "eval", flags) + except SyntaxError: + return True + if ast.dump(tnode) == ast.dump(node): + return False + else: + return True + return True + + +def _format_call_spec(function_name, argspec): + callspec = inspect.formatargspec(*argspec) + if _requires_parens_as_function(function_name): + return "(%s)%s" % (function_name, callspec) + else: + return "%s%s" % (function_name, callspec) + + +# TODO: move to util module +try: + from shlex import quote as shquote # python3.3+ +except ImportError: + # Backport of shlex.quote from python3.3 + _find_unsafe = re.compile(r'[^\w@%+=:,./-]').search + def shquote(s): + """Return a shell-escaped version of the string *s*.""" + if not s: + return "''" + if _find_unsafe(s) is None: + return s + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + return "'" + s.replace("'", "'\"'\"'") + "'" + + +def _build_function_usage_string(function_name, argspec, prefix): + usage = [] + # TODO: colorize + usage.append("Python signature:") + usage.append(" >"+">> " + _format_call_spec(function_name, argspec)) + usage.append("") + usage.append("Command-line signature:") + if not argspec.args and argspec.varargs and argspec.keywords: + # We have no information about the arguments. It's probably a + # built-in where getargspec failed. + usage.append(" $ %s%s ...\n" % (prefix, function_name)) + return "\n".join(usage) + defaults = argspec.defaults or () + first_with_default = len(argspec.args) - len(defaults) + # Show first alternative of command-line syntax. + syntax1 = " $ %s%s" % (prefix, shquote(function_name),) + for i, arg in enumerate(argspec.args): + if i >= first_with_default: + syntax1 += " [%s" % (arg,) + else: + syntax1 += " %s" % (arg,) + if argspec.varargs: + syntax1 += " %s..." % argspec.varargs + syntax1 += "]" * len(defaults) + if argspec.keywords: + syntax1 += " [--...]" + usage.append(syntax1) + # usage.append("or:") + syntax2 = " $ %s%s" % (prefix, shquote(function_name),) + for i, arg in enumerate(argspec.args): + if i >= first_with_default: + syntax2 += " [--%s=...]" % (arg,) + else: + syntax2 += " --%s=..." % (arg,) + if argspec.varargs: + syntax2 += " %s..." % argspec.varargs + if argspec.keywords: + syntax2 += " [--...]" + usage.append(syntax2) + usage.append("") + return "\n".join(usage) + + +class ParseError(Exception): + pass + + +class _ParseInterruptedWantHelp(Exception): + pass + + +class _ParseInterruptedWantSource(Exception): + pass + + +class UserExpr(object): + """ + An expression from user input, and its evaluated value. + + The expression can come from a string literal or other raw value, or a + string that is evaluated as an expression, or heuristically chosen. + + >>> ns = _Namespace() + + Heuristic auto-evaluation:: + + >>> UserExpr('5+2', ns, "auto").value + 7 + + >>> UserExpr('5j+2', ns, "auto").value + (2+5j) + + >>> UserExpr('base64.b64decode("SGFsbG93ZWVu")', ns, "auto").value + [PYFLYBY] import base64 + b'Halloween' + + Returning an unparsable argument as a string:: + + >>> UserExpr('Victory Loop', ns, "auto").value + 'Victory Loop' + + Returning an undefined (and not auto-importable) argument as a string:: + + >>> UserExpr('Willowbrook29817621+5', ns, "auto").value + 'Willowbrook29817621+5' + + Explicit literal string:: + + >>> UserExpr("2+3", ns, "raw_value").value + '2+3' + + >>> UserExpr("'2+3'", ns, "raw_value").value + "'2+3'" + + Other raw values:: + + >>> UserExpr(sys.exit, ns, "raw_value").value + + """ + + def __init__(self, arg, namespace, arg_mode, source=None): + """ + Construct a new UserExpr. + + :type arg: + ``str`` if ``arg_mode`` is "eval" or "auto"; anything if ``arg_mode`` + is "raw_value" + :param arg: + Input user argument. + :type namespace: + `_Namespace` + :type arg_mode: + ``str`` + :param arg_mode: + If ``"raw_value"``, then return ``arg`` unchanged. If ``"eval"``, then + always evaluate ``arg``. If ``"auto"``, then heuristically evaluate + if appropriate. + """ + if arg_mode == "string": + arg_mode = "raw_value" + self._namespace = namespace + self._original_arg_mode = arg_mode + self._original_arg = arg + if arg_mode == "raw_value": + # self.inferred_arg_mode = "raw_value" + # self.original_source = None + if source is None: + source = PythonBlock(repr(self._original_arg)) + else: + source = PythonBlock(source) + self.source = source + self.value = self._original_arg + elif arg_mode == "eval": + if source is not None: + raise ValueError( + "UserExpr(): source argument not allowed for eval") + # self.inferred_arg_mode = "eval" + self._original_arg_as_source = PythonBlock(arg, flags=FLAGS) + # self.original_source = self._original_arg_as_source + elif arg_mode == "auto": + if source is not None: + raise ValueError( + "UserExpr(): source argument not allowed for auto") + if not isinstance(arg, six.string_types): + raise ValueError( + "UserExpr(): arg must be a string if arg_mode='auto'") + self._original_arg_as_source = PythonBlock(arg, flags=FLAGS) + else: + raise ValueError("UserExpr(): bad arg_mode=%r" % (arg_mode,)) + + def _infer_and_evaluate(self): + if self._original_arg_mode == "raw_value": + pass + elif self._original_arg_mode == "eval": + block = self._original_arg_as_source + if not (str(block).strip()): + raise ValueError("empty input") + self.value = self._namespace.auto_eval(block) + self.source = self._original_arg_as_source #.pretty_print() # TODO + elif self._original_arg_mode == "auto": + block = self._original_arg_as_source + ERROR = object() + if not (str(block).strip()): + value = ERROR + elif not block.parsable_as_expression: + value = ERROR + else: + try: + value = self._namespace.auto_eval(block) + except UnimportableNameError: + value = ERROR + if value is ERROR: + # self.inferred_arg_mode = "raw_value" + self.value = self._original_arg + # self.original_source = None + self.source = PythonBlock(repr(self.value)) + else: + # self.inferred_arg_mode = "eval" + self.value = value + # self.original_source = block + self.source = block #.pretty_print() # TODO + else: + raise AssertionError("internal error") + self._infer_and_evaluate = lambda: None + + def __getattr__(self, k): + self._infer_and_evaluate() + return object.__getattribute__(self, k) + + def __str__(self): + return str(self._original_arg) + + +def _parse_auto_apply_args(argspec, commandline_args, namespace, arg_mode="auto"): + """ + Parse command-line arguments heuristically. Arguments that can be + evaluated are evaluated; otherwise they are treated as strings. + + :returns: + ``args``, ``kwargs`` + """ + # This is implemented manually instead of using optparse or argparse. We + # do so because neither supports dynamic keyword arguments well. Optparse + # doesn't support parsing known arguments only, and argparse doesn't + # support turning off interspersed positional arguments. + def make_expr(arg, arg_mode=arg_mode): + return UserExpr(arg, namespace, arg_mode) + + # Create a map from argname to default value. + defaults = argspec.defaults or () + argname2default = {} + for argname, default in zip(argspec.args[len(argspec.args)-len(defaults):], + defaults): + argname2default[argname] = make_expr(default, "raw_value") + # Create a map from prefix to arguments with that prefix. E.g. {"foo": + # ["foobar", "foobaz"]} + prefix2argname = {} + for argname in argspec.args: + for prefix in prefixes(argname): + prefix2argname.setdefault(prefix, []).append(argname) + # Enumerate over input arguments. + got_pos_args = [] + got_keyword_args = {} + args = list(commandline_args) + while args: + arg = args.pop(0) + if arg in ["--?", "-?", "?"]: + raise _ParseInterruptedWantHelp + elif arg in ["--??", "-??", "??"]: + raise _ParseInterruptedWantSource + elif arg.startswith("-"): + if arg == "-": + # Read from stdin and stuff into next argument as a string. + data = sys.stdin.read() + got_pos_args.append(make_expr(data, "string")) + continue + elif arg == "--": + # Treat remaining arguments as strings. + got_pos_args.extend([make_expr(x, "string") for x in args]) + del args[:] + continue + elif arg.startswith("--"): + argname = arg[2:] + else: + argname = arg[1:] + argname, equalsign, value = argname.partition("=") + argname = argname.replace("-", "_") + if not is_identifier(argname): + raise ParseError("Invalid option name %s" % (argname,)) + matched_argnames = prefix2argname.get(argname, []) + if len(matched_argnames) == 1: + argname, = matched_argnames + elif len(matched_argnames) == 0: + if equalsign == "": + if argname in ["help", "h"]: + raise _ParseInterruptedWantHelp + if argname in ["source"]: + raise _ParseInterruptedWantSource + if not argspec.keywords: + raise ParseError("Unknown option name %s" % (argname,)) + elif len(matched_argnames) > 1: + raise ParseError( + "Ambiguous %s: could mean one of: %s" + % (argname, + ", ".join("--%s"%s for s in matched_argnames))) + else: + raise AssertionError + if not value: + try: + value = args.pop(0) + except IndexError: + raise ParseError("Missing argument to %s" % (arg,)) + if value.startswith("--"): + raise ParseError( + "Missing argument to %s. " + "If you really want to use %r as the argument to %s, " + "then use %s=%s." + % (arg, value, arg, arg, value)) + got_keyword_args[argname] = make_expr(value) + else: + got_pos_args.append(make_expr(arg)) + + parsed_args = [] + parsed_kwargs = {} + for i, argname in enumerate(argspec.args): + if i < len(got_pos_args): + if argname in got_keyword_args: + raise ParseError( + "%s specified both as positional argument (%s) " + "and keyword argument (%s)" + % (argname, got_pos_args[i], got_keyword_args[argname])) + expr = got_pos_args[i] + else: + try: + expr = got_keyword_args.pop(argname) + except KeyError: + try: + expr = argname2default[argname] + except KeyError: + raise ParseError( + "missing required argument %s" % (argname,)) + try: + value = expr.value + except Exception as e: + raise ParseError( + "Error parsing value for --%s=%s: %s: %s" + % (argname, expr, type(e).__name__, e)) + parsed_args.append(value) + + if len(got_pos_args) > len(argspec.args): + if argspec.varargs: + for expr in got_pos_args[len(argspec.args):]: + try: + value = expr.value + except Exception as e: + raise ParseError( + "Error parsing value for *%s: %s: %s: %s" + % (argspec.varargs, expr, type(e).__name__, e)) + parsed_args.append(value) + else: + max_nargs = len(argspec.args) + if argspec.defaults: + expected = "%d-%d" % (max_nargs-len(argspec.defaults),max_nargs) + else: + expected = "%d" % (max_nargs,) + raise ParseError( + "Too many positional arguments. " + "Expected %s positional argument(s): %s. Got %d args: %s" + % (expected, ", ".join(argspec.args), + len(got_pos_args), " ".join(map(str, got_pos_args)))) + + for argname, expr in sorted(got_keyword_args.items()): + try: + parsed_kwargs[argname] = expr.value + except Exception as e: + raise ParseError( + "Error parsing value for --%s=%s: %s: %s" + % (argname, expr, type(e).__name__, e)) + + return parsed_args, parsed_kwargs + + +def _format_call(function_name, argspec, args, kwargs): + # TODO: print original unparsed arg strings + defaults = argspec.defaults or () + first_with_default = len(argspec.args) - len(defaults) + argparts = [] + for i in range(max(len(args), len(argspec.args))): + if i >= first_with_default and len(args) <= len(argspec.args): + argparts.append("%s=%r" % (argspec.args[i], args[i])) + else: + argparts.append(repr(args[i])) + for k, v in sorted(kwargs.items()): + argparts.append("%s=%r" % (k, v)) + if _requires_parens_as_function(function_name): + function_name = "(%s)" % (function_name,) + r = "%s(%s)" % (function_name, ", ".join(argparts)) + return r + + +class UnimportableNameError(NameError): + pass + + +class NotAFunctionError(Exception): + pass + + +def _get_help(expr, verbosity=1): + """ + Construct a help string. + + :type expr: + `UserExpr` + :param expr: + Object to generate help for. + :rtype: + ``str`` + """ + # TODO: colorize headers + result = "" + obj = expr.value + name = str(expr.source) + if callable(obj): + argspec = _get_argspec(obj) + prefix = os.path.basename(sys.orig_argv[0]) + " " + result += _build_function_usage_string(name, argspec, prefix) + if verbosity == 0: + include_filename = False + include_doc = False + include_source = False + elif verbosity == 1: + include_filename = True + include_doc = True + include_source = False + elif verbosity == 2: + include_filename = True + include_doc = False + include_source = True + else: + raise ValueError("invalid verbosity=%r" % (verbosity,)) + try: + version = obj.__version__ + except Exception: + pass + else: + result += "\nVersion:\n %s\n" % (version,) + if include_filename: + try: + filename = inspect.getfile(obj) + except Exception: + pass + else: + result += "\nFilename:\n %s\n" % (filename,) + if include_source: + try: + source = inspect.getsource(obj) + except Exception: + source = "" + if source: + # TODO: colorize source + result += "\nSource:\n%s\n" % (indent(source, " ")) + else: + source = "(Not available)" + include_doc = True + if include_doc: + doc = (inspect.getdoc(obj) or "").strip() or "(No docstring)" + result += "\nDocstring:\n%s" % (indent(doc, " ")) + return result + + +_enable_postmortem_debugger = None + +def _handle_user_exception(exc_info=None): + """ + Handle an exception in user code. + """ + # TODO: Make tracebacks show user code being executed. IPython does that + # by stuffing linecache.cache, and also advising linecache.checkcache. We + # can either advise it ourselves also, or re-use IPython.core.compilerop. + # Probably better to advise ourselves. Add "" to + # linecache. Perhaps do it in a context manager that removes from + # linecache when done. Advise checkcache to protect any "". + # Do it for all code compiled from here, including args, debug_statement, + # etc. + if exc_info is None: + exc_info = sys.exc_info() + if exc_info[2].tb_next: + exc_info = (exc_info[0], exc_info[1], + exc_info[2].tb_next) # skip this traceback + # If ``_enable_postmortem_debugger`` is enabled, then debug the exception. + # By default, this is enabled run running in a tty. + # We check isatty(1) here because we want 'py ... | cat' to never go into + # the debugger. Note that debugger() also checks whether /dev/tty is + # usable (and if not, waits for attach). + if _enable_postmortem_debugger: + # *** Run debugger. *** + debugger(exc_info) + # TODO: consider using print_verbose_tb(*exc_info) + print("Traceback (most recent call last):", file=sys.stderr) + import traceback + traceback.print_tb(exc_info[2]) + print("%s: %s" % (exc_info[0].__name__, exc_info[1]), file=sys.stderr) + raise SystemExit(1) + + +def auto_apply(function, commandline_args, namespace, arg_mode=None, + debug=False): + """ + Call ``function`` on command-line arguments. Arguments can be positional + or keyword arguments like "--foo=bar". Arguments are by default + heuristically evaluated. + + :type function: + ``UserExpr`` + :param function: + Function to apply. + :type commandline_args: + ``list`` of ``str`` + :param commandline_args: + Arguments to ``function`` as strings. + :param arg_mode: + How to interpret ``commandline_args``. If ``"string"``, then treat them + as literal strings. If ``"eval"``, then evaluate all arguments as + expressions. If ``"auto"`` (the default), then heuristically decide + whether to treat as expressions or strings. + """ + if not isinstance(function, UserExpr): + raise TypeError + if not callable(function.value): + raise NotAFunctionError("Not a function", function.value) + arg_mode = _interpret_arg_mode(arg_mode, default="auto") + # Parse command-line arguments. + argspec = _get_argspec(function.value) + try: + args, kwargs = _parse_auto_apply_args(argspec, commandline_args, + namespace, arg_mode=arg_mode) + except _ParseInterruptedWantHelp: + usage = _get_help(function, verbosity=1) + print(usage) + raise SystemExit(0) + except _ParseInterruptedWantSource: + usage = _get_help(function, verbosity=2) + print(usage) + raise SystemExit(0) + except ParseError as e: + # Failed parsing command-line arguments. Print usage. + logger.error(e) + usage = _get_help(function, verbosity=(1 if logger.info_enabled else 0)) + sys.stderr.write("\n" + usage) + raise SystemExit(1) + # Log what we're doing. + logger.info("%s", _format_call(function.source, argspec, args, kwargs)) + + # *** Call the function. *** + f = function.value + try: + if debug: + result = debug_statement("f(*args, **kwargs)") + else: + result = f(*args, **kwargs) + return result + except SystemExit: + raise + # TODO: handle "quit" by user here specially instead of returning None. + # May need to reimplement pdb.runeval() so we can catch BdbQuit. + except: + # Handle exception in user code. + _handle_user_exception() + + +@total_ordering +class LoggedList(object): + """ + List that logs which members have not yet been accessed (nor removed). + """ + + _ACCESSED = object() + + def __init__(self, items): + self._items = list(items) + self._unaccessed = list(self._items) + + def append(self, x): + self._unaccessed.append(self._ACCESSED) + self._items.append(x) + + def count(self): + return self._items.count() + + def extend(self, new_items): + new_items = list(new_items) + self._unaccessed.extend([self._ACCESSED] * len(new_items)) + self._items.extend(new_items) + + def index(self, x, *start_stop): + index = self._items.index(x, *start_stop) # may raise ValueError + self._unaccessed[index] = self._ACCESSED + return index + + def insert(self, index, x): + self._unaccessed.insert(index, self._ACCESSED) + self._items.insert(index, x) + + def pop(self, index): + self._unaccessed.pop(index) + return self._items.pop(index) + + def remove(self, x): + index = self._items.index(x) + self.pop(index) + + def reverse(self): + self._items.reverse() + self._unaccessed.reverse() + + def sort(self): + indexes = range(len(self._items)) + indexes.sort(key=self._items.__getitem__) # argsort + self._items = [self._items[i] for i in indexes] + self._unaccessed = [self._unaccessed[i] for i in indexes] + + def __add__(self, other): + return self._items + other + + def __contains__(self, x): + try: + self.index(x) + return True + except ValueError: + return False + + def __delitem__(self, x): + del self._items[x] + del self._unaccessed[x] + + def __eq__(self, other): + if not isinstance(other, LoggedList): + return NotImplemented + return self._items == other._items + + def __ne__(self, other): + return not (self == other) + + # The rest are defined by total_ordering + def __lt__(self, other): + if not isinstance(other, LoggedList): + return NotImplemented + return self._items < other._items + + def __cmp__(self, x): + return cmp(self._items, x) + + def __getitem__(self, idx): + result = self._items[idx] + if isinstance(idx, slice): + self._unaccessed[idx] = [self._ACCESSED]*len(result) + else: + self._unaccessed[idx] = self._ACCESSED + return result + + def __hash__(self): + raise TypeError("unhashable type: 'LoggedList'") + + def __iadd__(self, x): + self.extend(x) + + def __imul__(self, n): + self._items *= n + self._unaccessed *= n + + def __iter__(self): + # Todo: detect mutation while iterating. + for i, x in enumerate(self._items): + self._unaccessed[i] = self._ACCESSED + yield x + + def __len__(self): + return len(self._items) + + + def __mul__(self, n): + return self._items * n + + def __reduce__(self): + return + + def __repr__(self): + self._unaccessed[:] = [self._ACCESSED]*len(self._unaccessed) + return repr(self._items) + + def __reversed__(self): + # Todo: detect mutation while iterating. + for i in reversed(range(len(self._items))): + self._unaccessed[i] = self._ACCESSED + yield self._items[i] + + def __rmul__(self, n): + return self._items * n + + def __setitem__(self, idx, value): + self._items[idx] = value + if isinstance(idx, slice): + self._unaccessed[idx] = [self._ACCESSED]*len(value) + else: + self._unaccessed[idx] = value + + def __str__(self): + self._unaccessed[:] = [self._ACCESSED]*len(self._unaccessed) + return str(self._items) + + @property + def unaccessed(self): + return [x for x in self._unaccessed if x is not self._ACCESSED] + + +@contextmanager +def SysArgvCtx(*args): + """ + Context manager that: + * Temporarily sets sys.argv = args. + * At end of context, complains if any args were never accessed. + """ + # There should always be at least one arg, since the first one is + # the program name. + if not args: + raise ValueError("Missing args") + nargs = len(args) - 1 + # Create a list proxy that will log accesses. + argv = LoggedList(args) + # Don't consider first argument to be interesting. + argv[0] + orig_argv = list(sys.argv) + try: + sys.argv = argv + # Run context code. + yield + # Complain if there are unaccessed arguments. + unaccessed = argv.unaccessed + if not unaccessed: + pass + else: + if nargs == 1: + msg = ("You specified a command-line argument, but your code didn't use it: %s" + % (unaccessed[0],)) + elif len(unaccessed) == nargs: + msg = ("You specified %d command-line arguments, but your code didn't use them: %s" + % (len(unaccessed), " ".join(unaccessed))) + else: + msg = ("You specified %d command-line arguments, but your code didn't use %d of them: %s" + % (nargs, len(unaccessed), " ".join(unaccessed))) + msg2 = "\nIf this is intentional, access 'sys.argv[:]' somewhere in your code." + logger.error(msg + msg2) + raise SystemExit(1) + finally: + sys.argv = orig_argv + + +def _as_filename_if_seems_like_filename(arg): + """ + If ``arg`` seems like a filename, then return it as one. + + >>> bool(_as_filename_if_seems_like_filename("foo.py")) + True + + >>> bool(_as_filename_if_seems_like_filename("%foo.py")) + False + + >>> bool(_as_filename_if_seems_like_filename("foo(bar)")) + False + + >>> bool(_as_filename_if_seems_like_filename("/foo/bar/baz.quux-660470")) + True + + >>> bool(_as_filename_if_seems_like_filename("../foo/bar-24084866")) + True + + :type arg: + ``str`` + :rtype: + ``Filename`` + """ + try: + filename = Filename(arg) + except UnsafeFilenameError: + # If the filename isn't a "safe" filename, then don't treat it as one, + # and don't even check whether it exists. This means that for an + # argument like "foo(bar)" or "lambda x:x*y" we won't even check + # existence. This is both a performance optimization and a safety + # valve to avoid unsafe filenames being created to intercept expressions. + return None + # If the argument "looks" like a filename and is unlikely to be a python + # expression, then assume it is a filename. We assume so regardless of + # whether the file actually exists; if it turns out to not exist, we'll + # complain later. + if arg.startswith("/") or arg.startswith("./") or arg.startswith("../"): + return filename + if filename.ext == ".py": + # TODO: .pyc, .pyo + return which(arg) or filename + # Even if it doesn't obviously look like a filename, but it does exist as + # a filename, then use it as one. + if filename.exists: + return filename + # If it's a plain name and we can find an executable on $PATH, then use + # that. + if re.match("^[a-zA-Z0-9_-]+$", arg): + filename = which(arg) + if not filename: + return None + if not _has_python_shebang(filename): + logger.debug("Found %s but it doesn't seem like a python script", + filename) + return None + return filename + return None + + +def _has_python_shebang(filename): + """ + Return whether the first line contains #!...python... + + Used for confirming that an executable script found via which() is + actually supposed to be a python script. + + Note that this test is only needed for scripts found via which(), since + otherwise the shebang is not necessary. + """ + filename = Filename(filename) + with open(str(filename), 'rb') as f: + line = f.readline(1024) + return line.startswith(b"#!") and b"python" in line + + + +def _interpret_arg_mode(arg, default="auto"): + """ + >>> _interpret_arg_mode("Str") + 'string' + """ + if arg is None: + arg = default + if arg == "auto" or arg == "eval" or arg == "string": + return arg # optimization for interned strings + rarg = str(arg).strip().lower() + if rarg in ["eval", "evaluate", "exprs", "expr", "expressions", "expression", "e"]: + return "eval" + elif rarg in ["strings", "string", "str", "strs", "literal", "literals", "s"]: + return "string" + elif rarg in ["auto", "automatic", "a"]: + return "auto" + elif rarg == "error": + # Intentionally not documented to user + return "error" + else: + raise ValueError( + "Invalid arg_mode=%r; expected one of eval/string/auto" + % (arg,)) + + +def _interpret_output_mode(arg, default="interactive"): + """ + >>> _interpret_output_mode('Repr_If_Not_None') + 'repr-if-not-none' + """ + if arg is None: + arg = default + rarg = str(arg).strip().lower().replace("-", "").replace("_", "") + if rarg in ["none", "no", "n", "silent"]: + return "silent" + elif rarg in ["interactive", "i"]: + return "interactive" + elif rarg in ["print", "p", "string", "str"]: + return "str" + elif rarg in ["repr", "r"]: + return "repr" + elif rarg in ["pprint", "pp"]: + return "pprint" + elif rarg in ["reprifnotnone", "reprunlessnone", "rn"]: + return "repr-if-not-none" + elif rarg in ["pprintifnotnone", "pprintunlessnone", "ppn"]: + return "pprint-if-not-none" + elif rarg in ["systemexit", "exit", "raise"]: + return "exit" + else: + raise ValueError( + "Invalid output=%r; expected one of " + "silent/interactive/str/repr/pprint/repr-if-not-none/pprint-if-not-none/exit" + % (arg,)) + + +def print_result(result, output_mode): + output_mode = _interpret_output_mode(output_mode) + if output_mode == "silent": + return + if output_mode == "interactive": + # TODO: support IPython output stuff (text/plain) + try: + idisp = result.__interactive_display__ + except Exception: + output_mode = "pprint-if-not-none" + else: + result = idisp() + output_mode = "print-if-not-none" + # Fall through. + if output_mode == "str": + print(str(result)) + elif output_mode == "repr": + print(repr(result)) + elif output_mode == "pprint": + import pprint + pprint.pprint(result) # or equivalently, print pprint.pformat(result) + elif output_mode == "repr-if-not-none": + if result is not None: + print(repr(result)) + elif output_mode == "print-if-not-none": + if result is not None: + print(result) + elif output_mode == "pprint-if-not-none": + if result is not None: + import pprint + pprint.pprint(result) + elif output_mode == "exit": + # TODO: only raise at end after pre_exit + raise SystemExit(result) + else: + raise AssertionError("unexpected output_mode=%r" % (output_mode,)) + + +class _Namespace(object): + + def __init__(self): + self.globals = {"__name__": "__main__", + "__builtin__": builtins, + "__builtins__": builtins} + self.autoimported = {} + + def auto_import(self, arg): + return auto_import(arg, [self.globals], autoimported=self.autoimported) + + def auto_eval(self, block, mode=None, info=False, auto_import=True, + debug=False): + """ + Evaluate ``block`` with auto-importing. + """ + # Equivalent to:: + # auto_eval(arg, mode=mode, flags=FLAGS, globals=self.globals) + # but better logging and error raising. + block = PythonBlock(block, flags=FLAGS, auto_flags=True) + if auto_import and not self.auto_import(block): + missing = find_missing_imports(block, [self.globals]) + mstr = ", ".join(repr(str(x)) for x in missing) + if len(missing) == 1: + msg = "name %s is not defined and not importable" % mstr + elif len(missing) > 1: + msg = "names %s are not defined and not importable" % mstr + else: + raise AssertionError + raise UnimportableNameError(msg) + if info: + logger.info(block) + try: + # TODO: enter text into linecache + if debug: + return debug_statement(block, self.globals) + else: + code = block.compile(mode=mode) + return eval(code, self.globals, self.globals) + except SystemExit: + raise + except: + _handle_user_exception() + + +class _PyMain(object): + + def __init__(self, args): + self.main_args = args + self.namespace = _Namespace() + self.result = None + self.ipython_app = None + + def exec_stdin(self, cmd_args): + arg_mode = _interpret_arg_mode(self.arg_mode, default="string") + output_mode = _interpret_output_mode(self.output_mode, default="silent") + cmd_args = [UserExpr(a, self.namespace, arg_mode).value + for a in cmd_args] + with SysArgvCtx(*cmd_args): + result = self.namespace.auto_eval(Filename.STDIN, debug=self.debug) + print_result(result, output_mode) + self.result = result + + def eval(self, cmd, cmd_args): + arg_mode = _interpret_arg_mode(self.arg_mode, default="string") + output_mode = _interpret_output_mode(self.output_mode) + cmd_args = [UserExpr(a, self.namespace, arg_mode).value + for a in cmd_args] + with SysArgvCtx("-c", *cmd_args): + cmd = PythonBlock(cmd) + result = self.namespace.auto_eval(cmd, info=True, debug=self.debug) + # TODO: make auto_eval() plow ahead even if there are unimportable + # names, after warning + print_result(result, output_mode) + self.result = result + + def execfile(self, filename_arg, cmd_args): + # TODO: pass filename to import db target_filename; unit test. + # TODO: set __file__ + # TODO: support compiled (.pyc/.pyo) files + arg_mode = _interpret_arg_mode(self.arg_mode, default="string") + output_mode = _interpret_output_mode(self.output_mode) + cmd_args = [UserExpr(a, self.namespace, arg_mode).value + for a in cmd_args] + additional_msg = "" + if isinstance(filename_arg, Filename): + filename = filename_arg + elif filename_arg == "-": + filename = Filename.STDIN + elif "/" in filename_arg: + filename = Filename(filename_arg) + else: + filename = which(filename_arg) + if not filename: + filename = Filename(filename_arg) + additional_msg = (" (and didn't find %s on $PATH)" + % (filename_arg,)) + elif not _has_python_shebang(filename): + additional_msg = (" (found %s but it doesn't look " + "like python source" + % (filename,)) + filename = Filename(filename_arg) + if not filename.exists: + raise Exception("No such file: %s%s" % (filename, additional_msg)) + with SysArgvCtx(str(filename), *cmd_args): + sys.path.insert(0, str(filename.dir)) + result = self.namespace.auto_eval(filename, debug=self.debug) + print_result(result, output_mode) + self.result = result + + def apply(self, function, cmd_args): + arg_mode = _interpret_arg_mode(self.arg_mode, default="auto") + output_mode = _interpret_output_mode(self.output_mode) + # Todo: what should we set argv to? + result = auto_apply(function, cmd_args, self.namespace, arg_mode, + debug=self.debug) + print_result(result, output_mode) + self.result = result + + def _seems_like_runnable_module(self, arg): + if not is_identifier(arg, dotted=True): + # It's not a single (dotted) identifier. + return False + if not find_missing_imports(arg, [{}]): + # It's off of a builtin, e.g. "str.upper" + return False + m = ModuleHandle(arg) + if m.parent: + # Auto-import the parent, which is necessary in order to get the + # filename of the module. ``ModuleHandle.filename`` does this + # automatically, but we do it explicitly here so that we log + # the import of the parent module. + if not self.namespace.auto_import(str(m.parent.name)): + return False + if not m.filename: + logger.debug("Module %s doesn't have a source filename", m) + return False + # TODO: check that the source accesses __main__ (ast traversal?) + return True + + def heuristic_cmd(self, cmd, cmd_args, function_name=None): + output_mode = _interpret_output_mode(self.output_mode) + # If the "command" is just a module name, then call run_module. Make + # sure we check that it's not a builtin. + if self._seems_like_runnable_module(str(cmd)): + self.heuristic_run_module(str(cmd), cmd_args) + return + # FIXME TODO heed arg_mode for non-apply case. This is tricky to + # implement; will require assigning some proxy class to sys.argv + # that's more sophisticated than just logging. + with SysArgvCtx("-c", *cmd_args): + # Log the expression before we evaluate it, unless we're likely to + # log another substantially similar line. (We can only guess + # heuristically whether it's interesting enough to log it. And we + # can't know whether the result will be callable until we evaluate + # it.) + info = not re.match("^[a-zA-Z0-9_.]+$", function_name) + result = self.namespace.auto_eval(cmd, info=info, debug=self.debug) + if callable(result): + function = UserExpr( + result, self.namespace, "raw_value", function_name) + result = auto_apply(function, cmd_args, self.namespace, + self.arg_mode, debug=self.debug) + print_result(result, output_mode) + self.result = result + sys.argv[:] # mark as accessed + else: + if not info: + # We guessed wrong earlier and didn't log yet; log now. + logger.info(cmd) + print_result(result, output_mode) + self.result = result + unaccessed = sys.argv.unaccessed + if unaccessed: + logger.error( + "%s is not callable. Unexpected argument(s): %s", + result, " ".join(unaccessed)) + sys.argv[:] # don't complain again + + def run_module(self, module, args): + arg_mode = _interpret_arg_mode(self.arg_mode, default="string") + if arg_mode != "string": + raise NotImplementedError( + "run_module only supports string arguments") + module = ModuleHandle(module) + logger.info("python -m %s", ' '.join([str(module.name)] + args)) + # Imitate 'python -m'. + # TODO: include only the traceback below runpy.run_module + # os.execvp(sys.executable, [sys.argv[0], "-m", modname] + args) + sys.argv = [str(module.filename)] + args + import runpy + if self.debug: + # TODO: break closer to user code + debugger() + try: + runpy.run_module(str(module.name), run_name="__main__") + except SystemExit: + raise + except: + _handle_user_exception() + + def heuristic_run_module(self, module, args): + module = ModuleHandle(module) + # If the user ran 'py numpy --version', then print the numpy + # version, i.e. same as 'py --version numpy'. This has the + # downside of shadowing a possible "--version" feature + # implemented by the module itself. However, this is probably + # not a big deal, because (1) a full-featured program that + # supports --version would normally have a driver script and + # not rely on 'python -m foo'; (2) it would probably do + # something similar anyway; (3) the user can do 'py -m foo + # --version' if necessary. + if len(args)==1 and args[0] in ["--version", "-version"]: + self.print_version(module) + return + if len(args)==1 and args[0] in ["--help", "-help", "--h", "-h", + "--?", "-?", "?"]: + expr = UserExpr(module.module, None, "raw_value", + source=str(module.name)) + usage = _get_help(expr, 1) + print(usage) + return + if len(args)==1 and args[0] in ["--source", "-source", + "--??", "-??", "??"]: + expr = UserExpr(module.module, None, "raw_value", + source=str(module.name)) + usage = _get_help(expr, 2) + print(usage) + return + # TODO: check if module checks __main__ + self.run_module(module, args) + + def print_version(self, arg): + if not arg: + print_version_and_exit() + return + if isinstance(arg, (ModuleHandle, types.ModuleType)): + module = ModuleHandle(arg).module + else: + module = self.namespace.auto_eval(arg, mode="eval") + if not isinstance(module, types.ModuleType): + raise TypeError("print_version(): got a %s instead of a module" + % (type(module).__name__,)) + try: + version = module.__version__ + except AttributeError: + raise AttributeError( + "Module %s does not have a __version__ attribute" + % (module.__name__,)) + print(version) + + def print_help(self, objname, verbosity=1): + objname = objname and objname.strip() + if not objname: + print(__doc__) + return + expr = UserExpr(objname, self.namespace, "eval") + usage = _get_help(expr, verbosity) + print(usage) + + def create_ipython_app(self): + """ + Create an IPython application and initialize it, but don't start it. + """ + assert self.ipython_app is None + self.ipython_app = get_ipython_terminal_app_with_autoimporter() + + def start_ipython(self, args=[]): + user_ns = self.namespace.globals + start_ipython_with_autoimporter(args, _user_ns=user_ns, + app=self.ipython_app) + # Don't need to do another interactive session after this one + # (i.e. make 'py --interactive' the same as 'py'). + self.interactive = False + + def _parse_global_opts(self): + args = list(self.main_args) + self.debug = False + self.interactive = False + self.verbosity = 1 + self.arg_mode = None + self.output_mode = None + postmortem = 'auto' + while args: + arg = args[0] + if arg in ["debug", "pdb", "ipdb", "dbg"]: + argname = "debug" + elif not arg.startswith("-"): + break + elif arg.startswith("--"): + argname = arg[2:] + else: + argname = arg[1:] + argname, equalsign, value = argname.partition("=") + def popvalue(): + if equalsign: + return value + else: + try: + return args.pop(0) + except IndexError: + raise ValueError("expected argument to %s" % arg) + def novalue(): + if equalsign: + raise ValueError("unexpected argument %s" % arg) + if argname in ["interactive", "i"]: + novalue() + self.interactive = True + del args[0] + # Create and initialize the IPython app now (but don't start + # it yet). We'll use it later. The reason to initialize it + # now is that the code that we're running might check if it's + # running in interactive mode based on whether an IPython app + # has been initialized. Some user code even initializes + # things differently at module import time based on this. + self.create_ipython_app() + elif argname in ["debug", "pdb", "ipdb", "dbg", "d"]: + novalue() + self.debug = True + del args[0] + if argname == "verbose": + novalue() + logger.set_level("DEBUG") + del args[0] + continue + if argname in ["quiet", "q"]: + novalue() + logger.set_level("ERROR") + del args[0] + continue + if argname in ["safe"]: + del args[0] + novalue() + self.arg_mode = _interpret_arg_mode("string") + # TODO: make this less hacky, something like + # self.import_db = "" + # TODO: also turn off which() behavior + os.environ["PYFLYBY_PATH"] = "EMPTY" + os.environ["PYFLYBY_KNOWN_IMPORTS_PATH"] = "" + os.environ["PYFLYBY_MANDATORY_IMPORTS_PATH"] = "" + continue + if argname in ["arguments", "argument", "args", "arg", + "arg_mode", "arg-mode", "argmode"]: + # Interpret --args=eval|string|auto. + # Note that if the user didn't specify --args, then we + # intentionally leave ``opts.arg_mode`` set to ``None`` for now, + # because the default varies per action. + del args[0] + self.arg_mode = _interpret_arg_mode(popvalue()) + continue + if argname in ["output", "output_mode", "output-mode", + "out", "outmode", "out_mode", "out-mode", "o"]: + del args[0] + self.output_mode = _interpret_output_mode(popvalue()) + continue + if argname in ["print", "pprint", "silent", "repr"]: + del args[0] + novalue() + self.output_mode = _interpret_output_mode(argname) + continue + if argname in ["postmortem"]: + del args[0] + v = (value or "").lower().strip() + if v in ["yes", "y", "always", "true", "t", "1", "enable", ""]: + postmortem = True + elif v in ["no", "n", "never", "false", "f", "0", "disable"]: + postmortem = False + elif v in ["auto", "automatic", "default", "if-tty"]: + postmortem = "auto" + else: + raise ValueError( + "unexpected %s=%s. " + "Try --postmortem=yes or --postmortem=no." + % (argname, value)) + continue + if argname in ["no-postmortem", "np"]: + del args[0] + novalue() + postmortem = False + continue + break + self.args = args + if postmortem == "auto": + postmortem = os.isatty(1) + global _enable_postmortem_debugger + _enable_postmortem_debugger = postmortem + + def _enable_debug_tools(self): + # Enable a bunch of debugging tools. + enable_faulthandler() + enable_signal_handler_debugger() + enable_sigterm_handler() + add_debug_functions_to_builtins() + + def run(self): + # Parse global options. + sys.orig_argv = list(sys.argv) + self._parse_global_opts() + self._enable_debug_tools() + self._run_action() + self._pre_exit() + + def _run_action(self): + args = self.args + if not args or args[0] == "-": + if os.isatty(0): + # The user specified no arguments (or only a "-") and stdin is a + # tty. Run ipython with autoimporter enabled, i.e. equivalent to + # autoipython. Note that we directly start IPython in the same + # process, instead of using subprocess.call(['autoipython']), + # because the latter messes with Control-C handling. + # TODO: add 'py shell' and make this an alias. + # TODO: if IPython isn't installed, then do our own + # interactive REPL with code.InteractiveConsole, readline, and + # autoimporter. + self.start_ipython() + return + else: + # Emulate python args. + cmd_args = args or [""] + # Execute code from stdin, with auto importing. + self.exec_stdin(cmd_args) + return + + # Consider --action=arg, --action arg, -action=arg, -action arg, + # %action arg. + arg0 = args.pop(0) + if not arg0.strip(): + raise ValueError("got empty string as first argument") + if arg0.startswith("--"): + action, equalsign, cmdarg = arg0[2:].partition("=") + elif arg0.startswith("-"): + action, equalsign, cmdarg = arg0[1:].partition("=") + elif arg0.startswith("%"): + action, equalsign, cmdarg = arg0[1:], None, None + elif len(arg0) > 1 or arg0 == "?": + action, equalsign, cmdarg = arg0, None, None + else: + action, equalsign, cmdarg = None, None, None + def popcmdarg(): + if equalsign: + return cmdarg + else: + try: + return args.pop(0) + except IndexError: + raise ValueError("expected argument to %s" % arg0) + def nocmdarg(): + if equalsign: + raise ValueError("unexpected argument %s" % arg0) + + if action in ["eval", "c", "e"]: + # Evaluate a command from the command-line, with auto importing. + # Supports expressions as well as statements. + # Note: Regular python supports "python -cfoo" as equivalent to + # "python -c foo". For now, we intentionally don't support that. + cmd = popcmdarg() + self.eval(cmd, args) + elif action in ["file", "execfile", "execf", "runfile", "run", "f", + "python"]: + # Execute a file named on the command-line, with auto importing. + cmd = popcmdarg() + self.execfile(cmd, args) + elif action in ["apply", "call"]: + # Call a function named on the command-line, with auto importing and + # auto evaluation of arguments. + function_name = popcmdarg() + function = UserExpr(function_name, self.namespace, "eval") + self.apply(function, args) + elif action in ["map"]: + # Call function on each argument. + # TODO: instead of making this a standalone mode, change this to a + # flag that can eval/apply/exec/etc. Set "_" to each argument. + # E.g. py --map 'print _' obj1 obj2 + # py --map _.foo obj1 obj2 + # py --map '_**2' 3 4 5 + # when using heuristic mode, "lock in" the action mode on the + # first argument. + function_name = popcmdarg() + function = UserExpr(function_name, self.namespace, "eval") + if args and args[0] == '--': + for arg in args[1:]: + self.apply(function, ['--', arg]) + else: + for arg in args: + self.apply(function, [arg]) + elif action in ["xargs"]: + # TODO: read lines from stdin and map. default arg_mode=string + raise NotImplementedError("TODO: xargs") + elif action in ["module", "m", "runmodule", "run_module", "run-module"]: + # Exactly like `python -m'. Intentionally does NOT do auto + # importing within the module, because modules should not be + # sloppy; they should instead be tidied to have the correct + # imports. + modname = popcmdarg() + self.run_module(modname, args) + elif arg0.startswith("-m"): + # Support "py -mbase64" in addition to "py -m base64". + modname = arg0[2:] + self.run_module(modname, args) + elif action in ["attach"]: + pid = int(popcmdarg()) + nocmdarg() + attach_debugger(pid) + elif action in ["stack", "stack_trace", "stacktrace", "backtrace", "bt"]: + pid = int(popcmdarg()) + nocmdarg() + print("Stack trace for process %s:" % (pid,)) + remote_print_stack(pid) + elif action in ["ipython", "ip"]: + # Start IPython. + self.start_ipython(args) + elif action in ["notebook", "nb"]: + # Start IPython notebook. + nocmdarg() + self.start_ipython(["notebook"] + args) + elif action in ["kernel"]: + # Start IPython kernel. + nocmdarg() + self.start_ipython(["kernel"] + args) + elif action in ["qtconsole", "qt"]: + # Start IPython qtconsole. + nocmdarg() + self.start_ipython(["qtconsole"] + args) + elif action in ["console"]: + # Start IPython console (with new kernel). + nocmdarg() + self.start_ipython(["console"] + args) + elif action in ["existing"]: + # Start IPython console (with existing kernel). + if equalsign: + args.insert(0, cmdarg) + self.start_ipython(["console", "--existing"] + args) + elif action in ["nbconvert"]: + # Start IPython nbconvert. (autoimporter is irrelevant.) + if equalsign: + args.insert(0, cmdarg) + start_ipython_with_autoimporter(["nbconvert"] + args) + elif action in ["timeit"]: + # TODO: make --timeit and --time flags which work with any mode + # and heuristic, instead of only eval. + # TODO: fallback if IPython isn't available. above todo probably + # requires not using IPython anyway. + nocmdarg() + run_ipython_line_magic("%timeit " + ' '.join(args)) + elif action in ["time"]: + # TODO: make --timeit and --time flags which work with any mode + # and heuristic, instead of only eval. + # TODO: fallback if IPython isn't available. above todo probably + # requires not using IPython anyway. + nocmdarg() + run_ipython_line_magic("%time " + ' '.join(args)) + elif action in ["version"]: + if equalsign: + args.insert(0, cmdarg) + self.print_version(args[0] if args else None) + elif action in ["help", "h", "?"]: + if equalsign: + args.insert(0, cmdarg) + self.print_help(args[0] if args else None, verbosity=1) + elif action in ["pinfo"]: + self.print_help(popcmdarg(), verbosity=1) + elif action in ["source", "pinfo2", "??"]: + self.print_help(popcmdarg(), verbosity=2) + + elif arg0.startswith("-"): + # Unknown argument. + msg = "Unknown option %s" % (arg0,) + if arg0.startswith("-c"): + msg += "; do you mean -c %s?" % (arg0[2:]) + syntax(msg, usage=usage) + + elif arg0.startswith("??"): + # TODO: check number of args + self.print_help(arg0[2:], verbosity=2) + + elif arg0.endswith("??"): + # TODO: check number of args + self.print_help(arg0[:-2], verbosity=2) + + elif arg0.startswith("?"): + # TODO: check number of args + self.print_help(arg0[1:], verbosity=1) + + elif arg0.endswith("?"): + # TODO: check number of args + self.print_help(arg0[:-1], verbosity=1) + + elif arg0.startswith("%"): + run_ipython_line_magic(' '.join([arg0]+args)) + + # Heuristically choose the behavior automatically based on what the + # argument looks like. + else: + filename = _as_filename_if_seems_like_filename(arg0) + if filename: + # Implied --execfile. + self.execfile(filename, args) + return + if not args and arg0.isdigit(): + if self.debug: + attach_debugger(int(arg0, 10)) + return + else: + logger.error( + "Use py -d %s if you want to attach a debugger", arg0) + raise SystemExit(1) + # Implied --eval. + # When given multiple arguments, first see if the args can be + # concatenated and parsed as a single python program/expression. + # But don't try this if any arguments look like options, empty + # string or whitespace, etc. + # TODO: refactor + if (args and + self.arg_mode == None and + not any(re.match(r"\s*$|-[a-zA-Z-]", a) for a in args)): + cmd = PythonBlock(" ".join([arg0]+args), + flags=FLAGS, auto_flags=True) + if cmd.parsable and self.namespace.auto_import(cmd): + with SysArgvCtx("-c"): + output_mode = _interpret_output_mode(self.output_mode) + result = self.namespace.auto_eval( + cmd, info=True, auto_import=False) + print_result(result, output_mode) + self.result = result + return + # else fall through + # Heuristic based on first arg: try run_module, apply, or exec/eval. + cmd = PythonBlock(arg0, flags=FLAGS, auto_flags=True) + if not cmd.parsable: + logger.error( + "Could not interpret as filename or expression: %s", + arg0) + syntax(usage=usage) + self.heuristic_cmd(cmd, args, function_name=arg0) + + def _pre_exit(self): + self._pre_exit_matplotlib_show() + self._pre_exit_interactive_shell() + + def _pre_exit_matplotlib_show(self): + """ + If matplotlib.pyplot (pylab) is loaded, then call the show() function. + This will cause the program to block until all figures are closed. + Without this, a command like 'py plot(...)' would exit immediately. + """ + if self.interactive: + return + try: + pyplot = sys.modules["matplotlib.pyplot"] + except KeyError: + return + pyplot.show() + + def _pre_exit_interactive_shell(self): + if self.interactive: + assert self.ipython_app is not None + self.namespace.globals["_"] = self.result + self.start_ipython() + + +def py_main(args=None): + if args is None: + args = sys.argv[1:] + _PyMain(args).run() diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_util.py b/.venv/lib/python3.8/site-packages/pyflyby/_util.py new file mode 100644 index 0000000..9ed7812 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_util.py @@ -0,0 +1,506 @@ +# pyflyby/_util.py. +# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from contextlib import contextmanager +import inspect +import os +import six +from six import PY3, reraise +import sys +import types + +# Python 2/3 compatibility +DictProxyType = type(object.__dict__) + +class Object(object): + pass + + +def memoize(function): + cache = {} + def wrapped_fn(*args, **kwargs): + cache_key = (args, tuple(sorted(kwargs.items()))) + try: + return cache[cache_key] + except KeyError: + result = function(*args, **kwargs) + cache[cache_key] = result + return result + wrapped_fn.cache = cache + return wrapped_fn + + +class WrappedAttributeError(Exception): + pass + + +class cached_attribute(object): + """Computes attribute value and caches it in instance. + + Example:: + + class MyClass(object): + @cached_attribute + def myMethod(self): + # ... + + Use "del inst.myMethod" to clear cache.""" + # http://code.activestate.com/recipes/276643/ + + def __init__(self, method, name=None): + self.method = method + self.name = name or method.__name__ + + def __get__(self, inst, cls): + if inst is None: + return self + try: + result = self.method(inst) + except AttributeError as e: + reraise(WrappedAttributeError, WrappedAttributeError(str(e)), sys.exc_info()[2]) + setattr(inst, self.name, result) + return result + + +def stable_unique(items): + """ + Return a copy of ``items`` without duplicates. The order of other items is + unchanged. + + >>> stable_unique([1,4,6,4,6,5,7]) + [1, 4, 6, 5, 7] + """ + result = [] + seen = set() + for item in items: + if item in seen: + continue + seen.add(item) + result.append(item) + return result + + +def longest_common_prefix(items1, items2): + """ + Return the longest common prefix. + + >>> longest_common_prefix("abcde", "abcxy") + 'abc' + + :rtype: + ``type(items1)`` + """ + n = 0 + for x1, x2 in zip(items1, items2): + if x1 != x2: + break + n += 1 + return items1[:n] + + +def prefixes(parts): + """ + >>> list(prefixes("abcd")) + ['a', 'ab', 'abc', 'abcd'] + + """ + for i in range(1, len(parts)+1): + yield parts[:i] + + +def indent(lines, prefix): + r""" + >>> indent('hello\nworld\n', '@@') + '@@hello\n@@world\n' + """ + return "".join("%s%s\n"%(prefix,line) for line in lines.splitlines(False)) + + +def partition(iterable, predicate): + """ + >>> partition('12321233221', lambda c: int(c) % 2 == 0) + (['2', '2', '2', '2', '2'], ['1', '3', '1', '3', '3', '1']) + + """ + falses = [] + trues = [] + for item in iterable: + if predicate(item): + trues.append(item) + else: + falses.append(item) + return trues, falses + + +Inf = float('Inf') + + +@contextmanager +def NullCtx(): + """ + Context manager that does nothing. + """ + yield + + +@contextmanager +def ImportPathCtx(path_additions): + """ + Context manager that temporarily prepends ``sys.path`` with ``path_additions``. + """ + if not isinstance(path_additions, (tuple, list)): + path_additions = [path_additions] + old_path = sys.path[:] + sys.path[0:0] = path_additions + try: + yield + finally: + sys.path[:] = old_path + + +@contextmanager +def CwdCtx(path): + """ + Context manager that temporarily enters a new working directory. + """ + old_cwd = os.getcwd() + os.chdir(str(path)) + try: + yield + finally: + os.chdir(old_cwd) + + +@contextmanager +def EnvVarCtx(**kwargs): + """ + Context manager that temporarily modifies os.environ. + """ + unset = object() + old = {} + try: + for k, v in kwargs.items(): + old[k] = os.environ.get(k, unset) + os.environ[k] = v + yield + finally: + for k, v in old.items(): + if v is unset: + del os.environ[k] + else: + os.environ[k] = v + + +@contextmanager +def ExcludeImplicitCwdFromPathCtx(): + """ + Context manager that temporarily removes "." from ``sys.path``. + """ + old_path = sys.path + try: + sys.path = [p for p in sys.path if p not in (".", "")] + yield + finally: + sys.path[:] = old_path + + +class FunctionWithGlobals(object): + """ + A callable that at runtime adds extra variables to the target function's + global namespace. + + This is written as a class with a __call__ method. We do so rather than + using a metafunction, so that we can also implement __getattr__ to look + through to the target. + """ + + def __init__(self, function, **variables): + self.__function = function + self.__variables = variables + try: + self.__original__ = variables["__original__"] + except KeyError: + pass + + def __call__(self, *args, **kwargs): + function = self.__function + variables = self.__variables + undecorated = function + while True: + try: + undecorated = undecorated.undecorated + except AttributeError: + break + globals = undecorated.__globals__ + UNSET = object() + old = {} + for k in variables: + old[k] = globals.get(k, UNSET) + try: + for k, v in six.iteritems(variables): + globals[k] = v + return function(*args, **kwargs) + finally: + for k, v in six.iteritems(old): + if v is UNSET: + del globals[k] + else: + globals[k] = v + + def __getattr__(self, k): + return getattr(self.__original__, k) + + + def __get__(self, inst, cls=None): + if PY3: + if inst is None: + return self + return types.MethodType(self, inst) + else: + return types.MethodType(self, inst, cls) + + + +class _WritableDictProxy(object): + """ + Writable equivalent of cls.__dict__. + """ + + # We need to implement __getitem__ differently from __setitem__. The + # reason is because of an asymmetry in the mechanics of classes: + # - getattr(cls, k) does NOT in general do what we want because it + # returns unbound methods. It's actually equivalent to + # cls.__dict__[k].__get__(cls). + # - setattr(cls, k, v) does do what we want. + # - cls.__dict__[k] does do what we want. + # - cls.__dict__[k] = v does not work, because dictproxy is read-only. + + def __init__(self, cls): + self._cls = cls + + def __getitem__(self, k): + return self._cls.__dict__[k] + + def get(self, k, default=None): + return self._cls.__dict__.get(k, default) + + def __setitem__(self, k, v): + setattr(self._cls, k, v) + + def __delitem__(self, k): + delattr(self._cls, k) + + +_UNSET = object() + +class Aspect(object): + """ + Monkey-patch a target method (joinpoint) with "around" advice. + + The advice can call "__original__(...)". At run time, a global named + "__original__" will magically be available to the wrapped function. + This refers to the original function. + + Suppose someone else wrote Foo.bar():: + + >>> class Foo(object): + ... def __init__(self, x): + ... self.x = x + ... def bar(self, y): + ... return "bar(self.x=%s,y=%s)" % (self.x,y) + + >>> foo = Foo(42) + + To monkey patch ``foo.bar``, decorate the wrapper with ``"@advise(foo.bar)"``:: + + >>> @advise(foo.bar) + ... def addthousand(y): + ... return "advised foo.bar(y=%s): %s" % (y, __original__(y+1000)) + + >>> foo.bar(100) + 'advised foo.bar(y=100): bar(self.x=42,y=1100)' + + You can uninstall the advice and get the original behavior back:: + + >>> addthousand.unadvise() + + >>> foo.bar(100) + 'bar(self.x=42,y=100)' + + :see: + http://en.wikipedia.org/wiki/Aspect-oriented_programming + """ + + _wrapper = None + + def __init__(self, joinpoint): + spec = joinpoint + while hasattr(joinpoint, "__joinpoint__"): + joinpoint = joinpoint.__joinpoint__ + self._joinpoint = joinpoint + if (isinstance(joinpoint, (types.FunctionType, six.class_types, type)) + and not (PY3 and joinpoint.__name__ != joinpoint.__qualname__)): + self._qname = "%s.%s" % ( + joinpoint.__module__, + joinpoint.__name__) + self._container = sys.modules[joinpoint.__module__].__dict__ + self._name = joinpoint.__name__ + self._original = spec + assert spec == self._container[self._name], joinpoint + elif isinstance(joinpoint, types.MethodType) or (PY3 and isinstance(joinpoint, + types.FunctionType) and joinpoint.__name__ != + joinpoint.__qualname__) or isinstance(joinpoint, property): + if isinstance(joinpoint, property): + joinpoint = joinpoint.fget + self._wrapper = property + if PY3: + self._qname = '%s.%s' % (joinpoint.__module__, + joinpoint.__qualname__) + self._name = joinpoint.__name__ + else: + self._qname = "%s.%s.%s" % ( + joinpoint.__self__.__class__.__module__, + joinpoint.__self__.__class__.__name__, + joinpoint.__func__.__name__) + self._name = joinpoint.__func__.__name__ + if getattr(joinpoint, '__self__', None) is None: + # Unbound method in Python 2 only. In Python 3, there are no unbound methods + # (they are just functions). + if PY3: + container_obj = getattr(inspect.getmodule(joinpoint), + joinpoint.__qualname__.split('.', 1)[0].rsplit('.', 1)[0]) + else: + container_obj = joinpoint.im_class + self._container = _WritableDictProxy(container_obj) + # __func__ gives the function for the Python 2 unbound method. + # In Python 3, spec is already a function. + self._original = getattr(spec, '__func__', spec) + else: + # Instance method. + container_obj = joinpoint.__self__ + self._container = container_obj.__dict__ + self._original = spec + assert spec == getattr(container_obj, self._name), (container_obj, self._qname) + assert self._original == self._container.get(self._name, self._original) + elif isinstance(joinpoint, tuple) and len(joinpoint) == 2: + container, name = joinpoint + if isinstance(container, dict): + self._original = container[name] + self._container = container + self._qname = name + elif name in container.__dict__.get('_trait_values', ()): + # traitlet stuff from IPython + self._container = container._trait_values + self._original = self._container[name] + self._qname = name + elif isinstance(container.__dict__, DictProxyType): + original = getattr(container, name) + if hasattr(original, "__func__"): + # TODO: generalize this to work for all cases, not just classmethod + original = original.__func__ + self._wrapper = classmethod + self._original = original + self._container = _WritableDictProxy(container) + self._qname = "%s.%s.%s" % ( + container.__module__, container.__name__, name) + else: + # Keep track of the original. We use getattr on the + # container, instead of getitem on container.__dict__, so that + # it works even if it's a class dict proxy that inherits the + # value from a super class. + self._original = getattr(container, name) + self._container = container.__dict__ + self._qname = "%s.%s.%s" % ( + container.__class__.__module__, + container.__class__.__name__, + name) + self._name = name + # TODO: unbound method + else: + raise TypeError("JoinPoint: unexpected type %s" + % (type(joinpoint).__name__,)) + self._wrapped = None + + def advise(self, hook, once=False): + from pyflyby._log import logger + self._previous = self._container.get(self._name, _UNSET) + if once and getattr(self._previous, "__aspect__", None) : + # TODO: check that it's the same hook - at least check the name. + logger.debug("already advised %s", self._qname) + return None + logger.debug("advising %s", self._qname) + assert self._previous is _UNSET or self._previous == self._original + assert self._wrapped is None + # Create the wrapped function. + wrapped = FunctionWithGlobals(hook, __original__=self._original) + wrapped.__joinpoint__ = self._joinpoint + wrapped.__original__ = self._original + wrapped.__name__ = "%s__advised__%s" % (self._name, hook.__name__) + wrapped.__doc__ = "%s.\n\nAdvice %s:\n%s" % ( + self._original.__doc__, hook.__name__, hook.__doc__) + wrapped.__aspect__ = self + if self._wrapper is not None: + wrapped = self._wrapper(wrapped) + self._wrapped = wrapped + # Install the wrapped function! + self._container[self._name] = wrapped + return self + + def unadvise(self): + if self._wrapped is None: + return + cur = self._container.get(self._name, _UNSET) + if cur is self._wrapped: + from pyflyby._log import logger + logger.debug("unadvising %s", self._qname) + if self._previous is _UNSET: + del self._container[self._name] + else: + self._container[self._name] = self._previous + elif cur == self._previous: + pass + else: + from pyflyby._log import logger + logger.debug("%s seems modified; not unadvising it", self._name) + self._wrapped = None + + +def advise(joinpoint): + """ + Advise ``joinpoint``. + + See `Aspect`. + """ + aspect = Aspect(joinpoint) + return aspect.advise + + +@contextmanager +def AdviceCtx(joinpoint, hook): + aspect = Aspect(joinpoint) + advice = aspect.advise(hook) + try: + yield + finally: + advice.unadvise() + +# For Python 2/3 compatibility. cmp isn't included with six. +def cmp(a, b): + return (a > b) - (a < b) + + +# Create a context manager with an arbitrary number of contexts. This is +# the same as Py2 contextlib.nested, but that one is removed in Py3. +if six.PY2: + from contextlib import nested +else: + from contextlib import ExitStack + @contextmanager + def nested(*mgrs): + with ExitStack() as stack: + ctxes = [stack.enter_context(mgr) for mgr in mgrs] + yield ctxes diff --git a/.venv/lib/python3.8/site-packages/pyflyby/_version.py b/.venv/lib/python3.8/site-packages/pyflyby/_version.py new file mode 100644 index 0000000..9cfff71 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/_version.py @@ -0,0 +1,9 @@ +# pyflyby/_version.py. + +# License for THIS FILE ONLY: CC0 Public Domain Dedication +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import (absolute_import, division, print_function, + with_statement) + +__version__ = '1.7.4' diff --git a/.venv/lib/python3.8/site-packages/pyflyby/autoimport.py b/.venv/lib/python3.8/site-packages/pyflyby/autoimport.py new file mode 100644 index 0000000..138a116 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/autoimport.py @@ -0,0 +1,21 @@ +# pyflyby/autoimport.py. +# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +# Deprecated stub for backwards compatibility. +# +# Change your old code from: +# import pyflyby.autoimport +# pyflyby.autoimport.install_auto_importer() +# to: +# import pyflyby +# pyflyby.enable_auto_importer() + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from pyflyby._interactive import enable_auto_importer + +install_auto_importer = enable_auto_importer + +__all__ = [install_auto_importer] diff --git a/.venv/lib/python3.8/site-packages/pyflyby/importdb.py b/.venv/lib/python3.8/site-packages/pyflyby/importdb.py new file mode 100644 index 0000000..fa37476 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pyflyby/importdb.py @@ -0,0 +1,20 @@ +# pyflyby/importdb.py. +# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. +# License: MIT http://opensource.org/licenses/MIT + +# Deprecated stub for backwards compatibility. + +from __future__ import (absolute_import, division, print_function, + with_statement) + +from pyflyby._importdb import ImportDB + + +def global_known_imports(): + # Deprecated stub for backwards compatibility. + return ImportDB.get_default(".").known_imports + + +def global_mandatory_imports(): + # Deprecated stub for backwards compatibility. + return ImportDB.get_default(".").mandatory_imports diff --git a/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/INSTALLER b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/METADATA b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/METADATA new file mode 100644 index 0000000..5675b46 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/METADATA @@ -0,0 +1,228 @@ +Metadata-Version: 2.1 +Name: pygame +Version: 2.1.2 +Summary: Python Game Development +Home-page: https://www.pygame.org +Author: A community project. +Author-email: pygame@pygame.org +License: LGPL +Project-URL: Documentation, https://pygame.org/docs +Project-URL: Bug Tracker, https://github.com/pygame/pygame/issues +Project-URL: Source, https://github.com/pygame/pygame +Project-URL: Twitter, https://twitter.com/pygame_org +Platform: UNKNOWN +Classifier: Development Status :: 6 - Mature +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Programming Language :: Assembly +Classifier: Programming Language :: C +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Objective C +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Games/Entertainment +Classifier: Topic :: Multimedia :: Sound/Audio +Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI +Classifier: Topic :: Multimedia :: Sound/Audio :: Players +Classifier: Topic :: Multimedia :: Graphics +Classifier: Topic :: Multimedia :: Graphics :: Capture :: Digital Camera +Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture +Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion +Classifier: Topic :: Multimedia :: Graphics :: Viewers +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Operating System :: Unix +Classifier: Operating System :: MacOS +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst + +.. image:: https://raw.githubusercontent.com/pygame/pygame/main/docs/pygame_logo.svg + :alt: pygame + :target: https://www.pygame.org/ + + +|AppVeyorBuild| |PyPiVersion| |PyPiLicense| +|Python3| |GithubCommits| |LGTMAlerts| |LGTMGradePython| |LGTMGradeC| + +pygame_ is a free and open-source cross-platform library +for the development of multimedia applications like video games using Python. +It uses the `Simple DirectMedia Layer library`_ and several other +popular libraries to abstract the most common functions, making writing +these programs a more intuitive task. + +`We need your help`_ to make pygame the best it can be! +New contributors are welcome. + + +Installation +------------ + +:: + + pip install pygame + + +Help +---- + +If you are just getting started with pygame, you should be able to +get started fairly quickly. Pygame comes with many tutorials and +introductions. There is also full reference documentation for the +entire library. Browse the documentation on the `docs page`_. + +The online documentation stays up to date with the development version +of pygame on github. This may be a bit newer than the version of pygame +you are using. To upgrade to the latest full release, run +``pip install pygame --upgrade`` in your terminal. + +Best of all, the examples directory has many playable small programs +which can get you started playing with the code right away. + + +Building From Source +-------------------- + +If you want to use features that are currently in development, +or you want to contribute to pygame, you will need to build pygame +locally from its source code, rather than pip installing it. + +Installing from source is fairly automated. The most work will +involve compiling and installing all the pygame dependencies. Once +that is done, run the ``setup.py`` script which will attempt to +auto-configure, build, and install pygame. + +Much more information about installing and compiling is available +on the `Compilation wiki page`_. + + +Credits +------- + +Thanks to everyone who has helped contribute to this library. +Special thanks are also in order. + +* Marcus Von Appen: many changes, and fixes, 1.7.1+ freebsd maintainer +* Lenard Lindstrom: the 1.8+ windows maintainer, many changes, and fixes +* Brian Fisher for svn auto builder, bug tracker and many contributions +* Rene Dudfield: many changes, and fixes, 1.7+ release manager/maintainer +* Phil Hassey for his work on the pygame.org website +* DR0ID for his work on the sprite module +* Richard Goedeken for his smoothscale function +* Ulf Ekström for his pixel perfect collision detection code +* Pete Shinners: original author +* David Clark for filling the right-hand-man position +* Ed Boraas and Francis Irving: Debian packages +* Maxim Sobolev: FreeBSD packaging +* Bob Ippolito: MacOS and OS X porting (much work!) +* Jan Ekhol, Ray Kelm, and Peter Nicolai: putting up with early design ideas +* Nat Pryce for starting our unit tests +* Dan Richter for documentation work +* TheCorruptor for his incredible logos and graphics +* Nicholas Dudfield: many test improvements +* Alex Folkner for pygame-ctypes + +Thanks to those sending in patches and fixes: Niki Spahiev, Gordon +Tyler, Nathaniel Pryce, Dave Wallace, John Popplewell, Michael Urman, +Andrew Straw, Michael Hudson, Ole Martin Bjoerndalen, Herve Cauwelier, +James Mazer, Lalo Martins, Timothy Stranex, Chad Lester, Matthias +Spiller, Bo Jangeborg, Dmitry Borisov, Campbell Barton, Diego Essaya, +Eyal Lotem, Regis Desgroppes, Emmanuel Hainry, Randy Kaelber +Matthew L Daniel, Nirav Patel, Forrest Voight, Charlie Nolan, +Frankie Robertson, John Krukoff, Lorenz Quack, Nick Irvine, +Michael George, Saul Spatz, Thomas Ibbotson, Tom Rothamel, Evan Kroske, +Cambell Barton. + +And our bug hunters above and beyond: Angus, Guillaume Proux, Frank +Raiser, Austin Henry, Kaweh Kazemi, Arturo Aldama, Mike Mulcheck, +Michael Benfield, David Lau + +There's many more folks out there who've submitted helpful ideas, kept +this project going, and basically made our life easier. Thanks! + +Many thank you's for people making documentation comments, and adding to the +pygame.org wiki. + +Also many thanks for people creating games and putting them on the +pygame.org website for others to learn from and enjoy. + +Lots of thanks to James Paige for hosting the pygame bugzilla. + +Also a big thanks to Roger Dingledine and the crew at SEUL.ORG for our +excellent hosting. + +Dependencies +------------ + +Pygame is obviously strongly dependent on SDL and Python. It also +links to and embeds several other smaller libraries. The font +module relies on SDL_ttf, which is dependent on freetype. The mixer +(and mixer.music) modules depend on SDL_mixer. The image module +depends on SDL_image, which also can use libjpeg and libpng. The +transform module has an embedded version of SDL_rotozoom for its +own rotozoom function. The surfarray module requires the Python +NumPy package for its multidimensional numeric arrays. +Dependency versions: + +* CPython >= 3.6 or PyPy3 +* SDL >= 2.0.0 +* SDL_mixer >= 2.0.0 +* SDL_image >= 2.0.0 +* SDL_ttf >= 2.0.11 +* SDL_gfx (optional, vendored in) +* NumPy >= 1.6.2 (optional) + + +License +------- + +This library is distributed under `GNU LGPL version 2.1`_, which can +be found in the file ``docs/LGPL.txt``. We reserve the right to place +future versions of this library under a different license. + +This basically means you can use pygame in any project you want, +but if you make any changes or additions to pygame itself, those +must be released with a compatible license (preferably submitted +back to the pygame project). Closed source and commercial games are fine. + +The programs in the ``examples`` subdirectory are in the public domain. + +See docs/licenses for licenses of dependencies. + + +.. |AppVeyorBuild| image:: https://ci.appveyor.com/api/projects/status/x4074ybuobsh4myx?svg=true + :target: https://ci.appveyor.com/project/pygame/pygame + +.. |PyPiVersion| image:: https://img.shields.io/pypi/v/pygame.svg?v=1 + :target: https://pypi.python.org/pypi/pygame + +.. |PyPiLicense| image:: https://img.shields.io/pypi/l/pygame.svg?v=1 + :target: https://pypi.python.org/pypi/pygame + +.. |Python3| image:: https://img.shields.io/badge/python-3-blue.svg?v=1 + +.. |GithubCommits| image:: https://img.shields.io/github/commits-since/pygame/pygame/2.1.2.svg + :target: https://github.com/pygame/pygame/compare/2.1.2...main + +.. |LGTMAlerts| image:: https://img.shields.io/lgtm/alerts/g/pygame/pygame.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/pygame/pygame/alerts/ + +.. |LGTMGradePython| image:: https://img.shields.io/lgtm/grade/python/g/pygame/pygame.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/pygame/pygame/context:python + +.. |LGTMGradeC| image:: https://img.shields.io/lgtm/grade/cpp/g/pygame/pygame.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/pygame/pygame/context:cpp + +.. _pygame: https://www.pygame.org +.. _Simple DirectMedia Layer library: https://www.libsdl.org +.. _We need your help: https://www.pygame.org/contribute.html +.. _Compilation wiki page: https://www.pygame.org/wiki/Compilation +.. _docs page: https://www.pygame.org/docs/ +.. _GNU LGPL version 2.1: https://www.gnu.org/copyleft/lesser.html + + diff --git a/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/RECORD b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/RECORD new file mode 100644 index 0000000..affef47 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/RECORD @@ -0,0 +1,767 @@ +../../../include/site/python3.8/pygame/_camera.h,sha256=T0VYAfQxm0c4zww_BZaJGz4exa4z0FdEf3RSN_W2E-E,839 +../../../include/site/python3.8/pygame/_pygame.h,sha256=CikuDYKerHeppUL-DykqiRwxdLBATvWbUWYL0hfV30o,9562 +../../../include/site/python3.8/pygame/_surface.h,sha256=Bbi9rW0SqwGs6THID0l6eB5d-5h-kxW7517TdqoxEZM,957 +../../../include/site/python3.8/pygame/camera.h,sha256=M4aRsLhiFgaEeGEWGltpkFtMAfgMS51tJ43yu46wcw0,6548 +../../../include/site/python3.8/pygame/font.h,sha256=VHcKhYtIHduegTXEf1hbmbxCwN5IrqsJch2HaxEtB6I,348 +../../../include/site/python3.8/pygame/freetype.h,sha256=LbGY6saj9oakyoeGSlWlirSHySOpeoTKKOF-DO4zLRs,3245 +../../../include/site/python3.8/pygame/include/_pygame.h,sha256=_12ZQTJT7buO_nJkvP1wd7_WXunpJ8_mJimyvWoLHNQ,15800 +../../../include/site/python3.8/pygame/include/bitmask.h,sha256=tGzYwZ407sMIHDQG7xeXBQCRmhZZ4wp-yTerhUmIlCU,4952 +../../../include/site/python3.8/pygame/include/pgcompat.h,sha256=Yi3MQsneRDFCdjDxhRn2XmergYYaT-oTFRfsVJG-IX4,2019 +../../../include/site/python3.8/pygame/include/pgimport.h,sha256=p3W2we9tyxK3VhBxn3_BjUVUdBeo_EReehUMPgtYdj4,2927 +../../../include/site/python3.8/pygame/include/pgplatform.h,sha256=VkD2qNRPrDibskbApL_W2xa_YJFXDiE4TIfu9_MgN1Y,2478 +../../../include/site/python3.8/pygame/include/pygame.h,sha256=OsEc_zNPFlXo4owOhGRqH4cbuYMtNJ_7K9d_Te--OEU,1245 +../../../include/site/python3.8/pygame/include/pygame_bufferproxy.h,sha256=Poh7HsIjugo3NFeKjItZe40_BavF5V7re8bVFjkpmfU,1834 +../../../include/site/python3.8/pygame/include/pygame_font.h,sha256=JKPbDFQdh_BAuz5F9S37iBSZbfjnL2scUlkS1geLd4s,1501 +../../../include/site/python3.8/pygame/include/pygame_freetype.h,sha256=VNyvy7xukNOXymg3IMZne9-iVu3sI5LvKLagfkWpKAk,1346 +../../../include/site/python3.8/pygame/include/pygame_mask.h,sha256=ONXIz3M3MPF4BlPSS2xRquysEYjZZf7AP2JRro8j4I0,1303 +../../../include/site/python3.8/pygame/include/pygame_mixer.h,sha256=HthA7STa9TLomwQQswroyAmAd72pywDu_UCLfIV71is,2021 +../../../include/site/python3.8/pygame/include/sse2neon.h,sha256=DcazZmLfny6MVJFIUlWbsdI39ZWQWGwmCJDuFEVZsw0,237885 +../../../include/site/python3.8/pygame/mask.h,sha256=Y7OqzNUqQQHchUsSlvd-ja5d9IgAfSV2uFlaa8_5Lys,153 +../../../include/site/python3.8/pygame/mixer.h,sha256=HJMd0Ho0DrdGBdPMDo_egSqkVxZnUZPMEQyPJMIw6_M,348 +../../../include/site/python3.8/pygame/palette.h,sha256=dzARYIsQdHAaV8ypCrQbYRWFisXYXABD4ToMlzYKojg,7057 +../../../include/site/python3.8/pygame/pgarrinter.h,sha256=alsw7p6X7ukOB1o3curyrjWOcGHgVCQgCvS1D9FtiRc,1060 +../../../include/site/python3.8/pygame/pgbufferproxy.h,sha256=tqMDkdkH40QoYJ3NtTjiknAnSMh0i1sfNMaow3npvKI,179 +../../../include/site/python3.8/pygame/pgcompat.h,sha256=4bEjh9FGMhxpEEq6KMO-nKXEzyEH6z6QjqA3lVXoMx0,1597 +../../../include/site/python3.8/pygame/pgopengl.h,sha256=bbIysbLph5paPfeE2nnrQBIrq8iZz4T4pGfz2mYPuRw,606 +../../../include/site/python3.8/pygame/pgplatform.h,sha256=K25oz-likyqlYgKmlqtVVW-wOb8Pqsd8yu4xXFDBHUA,956 +../../../include/site/python3.8/pygame/pygame.h,sha256=AQcZWIoAWGmN9fYBynCXxc81hd9lcwxBwWeq8WZjTKE,1083 +../../../include/site/python3.8/pygame/scrap.h,sha256=dfD-C8y2VbdoW-iJhWDMATBakwA3p0KFffXQXDtSJ28,4704 +../../../include/site/python3.8/pygame/surface.h,sha256=3uku5zs5pTotf5dydrtMu1Vot1F_W9LXMp_gsLcxb4Q,14452 +pygame-2.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pygame-2.1.2.dist-info/METADATA,sha256=k79RWBp5mg3gPKwQR2XnkNx4H1loAbcqiRgvEZBsMlE,9144 +pygame-2.1.2.dist-info/RECORD,, +pygame-2.1.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pygame-2.1.2.dist-info/WHEEL,sha256=paN2rHE-sLfyg0Z4YvQnentMRWXxZnkclRDH8E5J6qk,148 +pygame-2.1.2.dist-info/entry_points.txt,sha256=ecT3iHcE_RL35uzQ2sXUdmOZbqq1XtPQNmbotP1FOQY,64 +pygame-2.1.2.dist-info/top_level.txt,sha256=ABXdFGIAE2g9m2VOzQPaLa917r6XEu6d96RqIzvAWCs,7 +pygame.libs/libFLAC-1889cd2f.so.8.3.0,sha256=U4cDAVAro7qZ5GmVrURDJsk-0_r5AOi8JU35BbVDI8I,2525808 +pygame.libs/libSDL2-2-302c83d0.0.so.0.16.0,sha256=_UmeyBLtjymdAYRC25QYlCs0Xve1gD4cw74waBLdqOc,10061136 +pygame.libs/libSDL2_image-2-ca388ea9.0.so.0.2.3,sha256=WiGzO0HukZkkcLbDwtd2Cp_a37mtZZk-gBwyrZrriWA,822216 +pygame.libs/libSDL2_mixer-2-d009651f.0.so.0.2.2,sha256=R6sy0O9NkrmUbA8h2-S-v0LNIpWu1AVlGGo-c9yyw00,807136 +pygame.libs/libSDL2_ttf-2-cc75fba4.0.so.0.14.1,sha256=KmFpCKR-F2GfNHglG4GMfTjwSqidqngHRwTkRVky8EE,169872 +pygame.libs/libasound-a3e880f2.so.2.0.0,sha256=chGtrtshJY34R_fbwei0IbrwasaOV_3h104O4PyR2sQ,5269584 +pygame.libs/libattr-4f2a9577.so.1.1.0,sha256=B2FUS3_CrqK0NtDxWShfhzS8lNm5yz723oSxjM_fpAo,21456 +pygame.libs/libbz2-a273e504.so.1.0.6,sha256=rG90qT6zByoD_7GEoC9BGhcYmz84nAlVGMxs0ts9bqw,70992 +pygame.libs/libcap-79733b59.so.2.22,sha256=LC4uoTM49e-E1u-EfFDS-ulD3y68YtNODRRuw-X_1Fo,25408 +pygame.libs/libdbus-1-5d678da9.so.3.14.14,sha256=KoZQ9rHGfApWN1Oxs6iUHsEP_tkF_YlbfA8O-MHjEqA,371408 +pygame.libs/libdw-0-780627ce.176.so,sha256=heZyod6e7OTxRhTPwCh5tWoGlkUHhFDZb52QzBqq16o,383176 +pygame.libs/libelf-0-3d9de92a.176.so,sha256=vNXIM4StWXL1VShLrddETo8E5AInRNKngdY8BETcdzE,105720 +pygame.libs/libfluidsynth-1a88b3a9.so.3.0.3,sha256=BZLaCrDekxSftbXMBA252QIP6vFTiOLUBp_LJI6nh1k,631752 +pygame.libs/libfreetype-9936d4a5.so.6.18.0,sha256=L-xQTWAU5uBCJJlZQ16UWpk38yTyn0uWYSCnRKolj7k,4273160 +pygame.libs/libgcrypt-18957bae.so.11.8.2,sha256=uAmSGkITbw9lRSw0_5Idl_7Jjl20yYUKXhuCUC00_7o,549248 +pygame.libs/libgomp-a34b3233.so.1.0.0,sha256=1GXGWfRf6XU3wOEAn66x6m5ApWQt3_3un8G3PAcyQh4,168192 +pygame.libs/libgpg-error-3f09c3c7.so.0.10.0,sha256=Cn1ZI6m8RYQiEB4aDXWjr-CqS4mzmg3jnd3MaYW8yP8,21112 +pygame.libs/libjpeg-988b02a7.so.62.3.0,sha256=kEfOBPQTdqABymCosIWOs_Bq15g2WVb8c7ytOx0SUxE,744616 +pygame.libs/liblz4-af1653fb.so.1.8.3,sha256=8PieEuq3o9hwVWfd-vwFBL0Cv1L8iAFUDbaSNJKjH_I,68576 +pygame.libs/liblzma-07b5b5c8.so.5.2.2,sha256=jbT30ebhJOCsHMXsU4AMu2mAe0U17dckTo2R6ESAy4E,163472 +pygame.libs/libmikmod-6af46a87.so.3.0.0,sha256=HW7lZIFLqhPeYWcSAS8pdAojKpqW5rc6ocP6KHYFUcE,284920 +pygame.libs/libmodplug-c335b2c6.so.1.0.0,sha256=L3IXAEH2dC7QOTfEEBVMZdsTEFmoK-h09G4ss84_4pY,2046496 +pygame.libs/libmpg123-e4fca779.so.0.46.3,sha256=4eVG4_yzK6BMNryohWRGJwXPCDZteib7ofSvXhF574k,1421296 +pygame.libs/libogg-04548ab3.so.0.8.5,sha256=uwSEsgm9mD1AgyXmZqoS2fJiyzjh4YWCK2_y0qaramY,92200 +pygame.libs/libopus-06b786df.so.0.8.0,sha256=_wlih0l3cwL2shTbSYe-SQbCvBHHGBw2nWF0ReTUfM4,2247392 +pygame.libs/libpcre-9513aab5.so.1.2.0,sha256=r9Y0L2DyrIDPw-5TsPJRcdYcb55MKF6Wf3Ar9eI7gbI,406816 +pygame.libs/libpng16-67f87fff.so.16.37.0,sha256=a2AkMhWNQwkeeKm2oZ-qUnAA-p_dTpRmOM6Kxp247pI,1097008 +pygame.libs/libportmidi-fbdafcc9.so,sha256=Fzhe3RMRciis4h5bJ6UBgePl9b6mQ-FMvsssY_a92-k,64040 +pygame.libs/libpulse-76af7338.so.0.23.0,sha256=KuuxWJPcroKowUIerIDwyj5WjV9eljuE9wADYu2cqhE,1538376 +pygame.libs/libpulse-simple-9065ac8d.so.0.1.1,sha256=YKGqdvgFWEvEHru5m8hCMw0rNmt3BW3lSfms81huByc,112336 +pygame.libs/libpulsecommon-14-e93e2477.0.so,sha256=B24cJYdK9PjUAh5KQOkddvrS04d8f7gvjXH5HV8l5N4,2249296 +pygame.libs/libselinux-0922c95c.so.1,sha256=QYUoY49r_73g2YCrevw22vsNRPMouBuNZzcna-pTATM,178336 +pygame.libs/libsndfile-72e2e06b.so.1.0.31,sha256=Sr3hy4JwfVaMs2UbZlkIW97rdkXD8LY6bnFHRP-U51A,585480 +pygame.libs/libsystemd-c4cac0a4.so.0.6.0,sha256=oAbO3RGaZlTuinPXrx2LN1zrjN_4p3IjFx8Bh1cu0_Y,266784 +pygame.libs/libtiff-8d84a4e4.so.5.7.0,sha256=O6h1-Vi3WfQT7iqUgTI8B00Tkwy7TPozyZ3ttu0hsko,2020952 +pygame.libs/libvorbis-3a6647c4.so.0.4.9,sha256=SwJZzgjoIouvK_its6Yv2cUqaTcNixyBt-eUU-G931w,252592 +pygame.libs/libvorbisenc-f97181cf.so.2.0.12,sha256=SNZhomIFX9ZsMP11502P5hX6oXxoKVyMv0pCy940HiA,823712 +pygame.libs/libvorbisfile-b464aa2e.so.3.3.8,sha256=bUy08CDgA3PfH8npaXY43Amwru5-ykoZA_ll-MOU_jU,63064 +pygame.libs/libwebp-c8fa709c.so.7.1.2,sha256=ZTjXo-jLNFUgbNso63a7iE8Y7KJbSUJOlGXR1_3iiaA,4573872 +pygame/__init__.py,sha256=1fYUjwATVywvryFZrU4tcWOIMWPlEf7at3ax87U1AUQ,10287 +pygame/__init__.pyi,sha256=OnbBT0SMG4QZJDkzBHqgdDEIaIGvMi3GHX3AQj6eqK0,2390 +pygame/__pycache__/__init__.cpython-38.pyc,, +pygame/__pycache__/_camera_opencv.cpython-38.pyc,, +pygame/__pycache__/_camera_vidcapture.cpython-38.pyc,, +pygame/__pycache__/camera.cpython-38.pyc,, +pygame/__pycache__/colordict.cpython-38.pyc,, +pygame/__pycache__/cursors.cpython-38.pyc,, +pygame/__pycache__/draw_py.cpython-38.pyc,, +pygame/__pycache__/fastevent.cpython-38.pyc,, +pygame/__pycache__/freetype.cpython-38.pyc,, +pygame/__pycache__/ftfont.cpython-38.pyc,, +pygame/__pycache__/locals.cpython-38.pyc,, +pygame/__pycache__/macosx.cpython-38.pyc,, +pygame/__pycache__/midi.cpython-38.pyc,, +pygame/__pycache__/pkgdata.cpython-38.pyc,, +pygame/__pycache__/sndarray.cpython-38.pyc,, +pygame/__pycache__/sprite.cpython-38.pyc,, +pygame/__pycache__/surfarray.cpython-38.pyc,, +pygame/__pycache__/sysfont.cpython-38.pyc,, +pygame/__pycache__/version.cpython-38.pyc,, +pygame/__pyinstaller/__init__.py,sha256=-c4Zo8nQGKAm8wc_LDscxMtK7zr_YhZwRnC9CMruUBE,72 +pygame/__pyinstaller/__pycache__/__init__.cpython-38.pyc,, +pygame/__pyinstaller/__pycache__/hook-pygame.cpython-38.pyc,, +pygame/__pyinstaller/hook-pygame.py,sha256=2aLZ6jkeJe_bNqFAU98vgNc2xeJl4X_anExEn7rp_xo,1367 +pygame/_camera.cpython-38-x86_64-linux-gnu.so,sha256=nu9C3JuzMTMs3TXmw9NN2ynzPK4Y-aP7bOd_70VYueE,137496 +pygame/_camera_opencv.py,sha256=oUb_Rf3PKDJpcryavpOAB1__6ZGE5XrqkVqo4cc_vOU,4386 +pygame/_camera_vidcapture.py,sha256=MJih74Ya6djH-mZZj4OYfRKJOH-SD0VUHNaUcxwX7N8,3402 +pygame/_common.pyi,sha256=EQ6zBPsEITU8rgzSW9PFJAzlaOQusBBYySDXFSAzVLs,956 +pygame/_freetype.cpython-38-x86_64-linux-gnu.so,sha256=mqehqOU69gyPetWmV32Osi3q_FijuT8xmgYyy318B18,135272 +pygame/_sdl2/__init__.py,sha256=gmSh3cXyxHqEXwbck_mNOmoW--itmGMwonEHQnY49Zo,248 +pygame/_sdl2/__init__.pyi,sha256=YGq944qutqf59Mv4nSzlRI2CfpbfBTiPPkd7bEw-PZ8,33 +pygame/_sdl2/__pycache__/__init__.cpython-38.pyc,, +pygame/_sdl2/audio.cpython-38-x86_64-linux-gnu.so,sha256=lmIDiVAcTGQQJaUKLrUnv-3UhyOYEf7MD1B818kOYak,261928 +pygame/_sdl2/controller.cpython-38-x86_64-linux-gnu.so,sha256=qmzP_98H_15Q4Q97YfWvAMS-grSJ4GSn_QEK85H3Hy8,147072 +pygame/_sdl2/controller.pyi,sha256=-GgY9eKV3vuuJlTd2Vor9Wt8v0hN1UPmBjTr6W8aplU,1039 +pygame/_sdl2/mixer.cpython-38-x86_64-linux-gnu.so,sha256=YzwRErai0WTi4lFDHqVZd2v1zLrnJ9Peq5FkEvPs5gE,233024 +pygame/_sdl2/sdl2.cpython-38-x86_64-linux-gnu.so,sha256=VSJ_6pQgANcsaSto41cOtlhF26qknjzC5K9kb8xlEig,72040 +pygame/_sdl2/touch.cpython-38-x86_64-linux-gnu.so,sha256=GxeO37xtwRYYi5YvMkNFiG6xQrl1Zhe-5K6CzjDeKu0,27016 +pygame/_sdl2/touch.pyi,sha256=-vYGJsSC18E2gitH10HIv_V_DUc6DIeCb55zuQp3jPc,231 +pygame/_sdl2/video.cpython-38-x86_64-linux-gnu.so,sha256=MNr2if9N6tyk3ZGVuZphw-jEpnw7P0L74EIwibHl0pg,345696 +pygame/_sdl2/video.pyi,sha256=Rs6qO6xdcRHGY85eKzaQbT6GlTTRE53zIj1fFtfsNvY,4443 +pygame/_sprite.cpython-38-x86_64-linux-gnu.so,sha256=wkrTeFStzw8ToJtBUsFbX912VvNx0QeUSO7ma1bx08c,462664 +pygame/base.cpython-38-x86_64-linux-gnu.so,sha256=GP9rLanZOLUQbZX2F2jKuXMvzlim0uHtc1GpOs8-X6k,51200 +pygame/bufferproxy.cpython-38-x86_64-linux-gnu.so,sha256=UsTNp9QV5RZJbXTAF4L_O1tnGQ1J9fl6AZyyB3td7Mk,35720 +pygame/bufferproxy.pyi,sha256=aES-Sa035yvUZRj4dc7Y0Y5XFpyazzlBO7xkBfQKs-w,271 +pygame/camera.py,sha256=t3-tRPygmpezwOCTwp8wcqqlqD99tHI_3ZPXOjOjuFI,4496 +pygame/camera.pyi,sha256=1-83dS-kpbPnt_7uSLQlaXblf31kWDE9IPbRZ0XEPXA,885 +pygame/color.cpython-38-x86_64-linux-gnu.so,sha256=9eyYH7CcFh339Ym6k9OUoQBDgkL89IeH54hlNlnhC8I,57088 +pygame/color.pyi,sha256=7tbA3I5P0AAXgccUhXbBeYA6GrZI17jLjq40SPP2P58,1476 +pygame/colordict.py,sha256=xS-mwTxatoVa3Howpp53otRNZ3p-b1MzZChlvy0cuic,25773 +pygame/constants.cpython-38-x86_64-linux-gnu.so,sha256=ydSXkV60wRuUH6x4uah7T7djM7aGf-ihYFgJzjpDUzg,70528 +pygame/constants.pyi,sha256=JXis8T-vJqjvJVmI1skNg0Ofmq1EUfpE4DXwwoEMfm4,9852 +pygame/cursors.py,sha256=Tg7Qo2LkmH6miYCNmlq1kHrF99r3CTgOBlVsRD56-O4,18203 +pygame/cursors.pyi,sha256=LrV2FL07nz-URidMq_ME1QN-LO01LcNYO3tT2cYZ21o,1829 +pygame/display.cpython-38-x86_64-linux-gnu.so,sha256=sUINBbHiIMw0fpf38rX0KCHwywR3FhNsELYjWIO2brM,77328 +pygame/display.pyi,sha256=McfbuPD2mfnGyDqRd5WFYETxiAWSZvfeZ2C8qd3Cy24,2025 +pygame/docs/__main__.py,sha256=hDDCEJ6Jhts8k7dKgdAPTTx91X4COGtKCPiac1H-5q0,1020 +pygame/docs/__pycache__/__main__.cpython-38.pyc,, +pygame/docs/generated/_images/AdvancedInputOutput1.gif,sha256=uSCxW5dFtO7PYQyoJglWJTe_aRYFfOav5DjnPkKBzKg,5649 +pygame/docs/generated/_images/AdvancedInputOutput11.gif,sha256=uSCxW5dFtO7PYQyoJglWJTe_aRYFfOav5DjnPkKBzKg,5649 +pygame/docs/generated/_images/AdvancedInputOutput2.gif,sha256=2UMDweIgRefzHFTJcJgbDsWgy-SlyOYaw2AKD6NKwmM,72233 +pygame/docs/generated/_images/AdvancedInputOutput21.gif,sha256=2UMDweIgRefzHFTJcJgbDsWgy-SlyOYaw2AKD6NKwmM,72233 +pygame/docs/generated/_images/AdvancedInputOutput3.gif,sha256=ez4Y7Yy0LsNH6UPYybfGckLmzutcLh7VIisajgFa4O8,6294 +pygame/docs/generated/_images/AdvancedInputOutput31.gif,sha256=ez4Y7Yy0LsNH6UPYybfGckLmzutcLh7VIisajgFa4O8,6294 +pygame/docs/generated/_images/AdvancedInputOutput4.gif,sha256=2-rK9cGngrgpJLW404oI3lG4NQnoT-UNwzA3Up48YIc,29185 +pygame/docs/generated/_images/AdvancedInputOutput41.gif,sha256=2-rK9cGngrgpJLW404oI3lG4NQnoT-UNwzA3Up48YIc,29185 +pygame/docs/generated/_images/AdvancedInputOutput5.gif,sha256=C6N4d_JD4QNMjLwPdINCOFWufyD4vM7b5LpR2GaHMcY,37349 +pygame/docs/generated/_images/AdvancedInputOutput51.gif,sha256=C6N4d_JD4QNMjLwPdINCOFWufyD4vM7b5LpR2GaHMcY,37349 +pygame/docs/generated/_images/AdvancedOutputAlpha1.gif,sha256=MH3CmK658WpN_u56GL9CV4YMFky9GLaKh0CAcixkMrA,14915 +pygame/docs/generated/_images/AdvancedOutputAlpha11.gif,sha256=MH3CmK658WpN_u56GL9CV4YMFky9GLaKh0CAcixkMrA,14915 +pygame/docs/generated/_images/AdvancedOutputAlpha2.gif,sha256=9E2YAIULWHZsAELfqLf5gdqL7-uE-Ebg4uqKgbSyVYk,71819 +pygame/docs/generated/_images/AdvancedOutputAlpha21.gif,sha256=9E2YAIULWHZsAELfqLf5gdqL7-uE-Ebg4uqKgbSyVYk,71819 +pygame/docs/generated/_images/AdvancedOutputAlpha3.gif,sha256=7gHdbhSKaUpyXgOC7GSusTdN7oJQvjGFY_o8DQNZ2Fc,30380 +pygame/docs/generated/_images/AdvancedOutputAlpha31.gif,sha256=7gHdbhSKaUpyXgOC7GSusTdN7oJQvjGFY_o8DQNZ2Fc,30380 +pygame/docs/generated/_images/AdvancedOutputProcess1.gif,sha256=QihTI3ThxtEnPjQ0qZaYF4rKj5GxSwDfFgfUmFjvcOw,15951 +pygame/docs/generated/_images/AdvancedOutputProcess11.gif,sha256=QihTI3ThxtEnPjQ0qZaYF4rKj5GxSwDfFgfUmFjvcOw,15951 +pygame/docs/generated/_images/AdvancedOutputProcess2.gif,sha256=qP09Je0xWwST1CoAs9BCy7_vq6OB69opna8kejTln50,1868 +pygame/docs/generated/_images/AdvancedOutputProcess21.gif,sha256=qP09Je0xWwST1CoAs9BCy7_vq6OB69opna8kejTln50,1868 +pygame/docs/generated/_images/AdvancedOutputProcess3.gif,sha256=4WhoBbEheizUdZfs_pFEBGGAm9aoetw3tJhgcNgVIyY,1912 +pygame/docs/generated/_images/AdvancedOutputProcess31.gif,sha256=4WhoBbEheizUdZfs_pFEBGGAm9aoetw3tJhgcNgVIyY,1912 +pygame/docs/generated/_images/AdvancedOutputProcess4.gif,sha256=m-gUJNOn6AuXT7FpKF6HRR8A6ytWorY9Y07N2uZaSIQ,14500 +pygame/docs/generated/_images/AdvancedOutputProcess41.gif,sha256=m-gUJNOn6AuXT7FpKF6HRR8A6ytWorY9Y07N2uZaSIQ,14500 +pygame/docs/generated/_images/AdvancedOutputProcess5.gif,sha256=GCi9KGUIhQTFg-HLEnp2GEgrnQl8_2KftS3N_UkuEH8,16896 +pygame/docs/generated/_images/AdvancedOutputProcess51.gif,sha256=GCi9KGUIhQTFg-HLEnp2GEgrnQl8_2KftS3N_UkuEH8,16896 +pygame/docs/generated/_images/AdvancedOutputProcess6.gif,sha256=nzV_M0JoA4aDyGwpe8lWoUFd5c1PK-WxUI-lF8WzfQQ,34058 +pygame/docs/generated/_images/AdvancedOutputProcess61.gif,sha256=nzV_M0JoA4aDyGwpe8lWoUFd5c1PK-WxUI-lF8WzfQQ,34058 +pygame/docs/generated/_images/Bagic-INPUT-resultscreen.png,sha256=RDZbxtVyFMJXdZA8wouSyJJjXf2MQ2WYTBzVotsDH88,5973 +pygame/docs/generated/_images/Bagic-INPUT-resultscreen1.png,sha256=RDZbxtVyFMJXdZA8wouSyJJjXf2MQ2WYTBzVotsDH88,5973 +pygame/docs/generated/_images/Bagic-INPUT-sourcecode.png,sha256=3F2c3AnravGgsRMxaNXlaekyqrhMl2cwYySJxhj7L0A,77061 +pygame/docs/generated/_images/Bagic-INPUT-sourcecode1.png,sha256=3F2c3AnravGgsRMxaNXlaekyqrhMl2cwYySJxhj7L0A,77061 +pygame/docs/generated/_images/Bagic-PROCESS-resultscreen.png,sha256=hQ1m6S1xhXpcaf0g50VRoOagmsiZZpUeOBx7QgG7Lqs,5348 +pygame/docs/generated/_images/Bagic-PROCESS-resultscreen1.png,sha256=hQ1m6S1xhXpcaf0g50VRoOagmsiZZpUeOBx7QgG7Lqs,5348 +pygame/docs/generated/_images/Bagic-PROCESS-sourcecode.png,sha256=vj0D6wrXFNjIHKmRFZrltZH4nH51zG6YSy94ID2fWos,66070 +pygame/docs/generated/_images/Bagic-PROCESS-sourcecode1.png,sha256=vj0D6wrXFNjIHKmRFZrltZH4nH51zG6YSy94ID2fWos,66070 +pygame/docs/generated/_images/Bagic-ouput-result-screen.png,sha256=Ig1vKczM-l0ebtdjYEHdwcJuqt8IoyUiK-RANEC065k,4819 +pygame/docs/generated/_images/Bagic-ouput-result-screen1.png,sha256=Ig1vKczM-l0ebtdjYEHdwcJuqt8IoyUiK-RANEC065k,4819 +pygame/docs/generated/_images/Basic-ouput-sourcecode.png,sha256=B6OVjvOtA2ZwiEwJyYkK1-tsGyN13y8O9kls1OYvzTo,57466 +pygame/docs/generated/_images/Basic-ouput-sourcecode1.png,sha256=B6OVjvOtA2ZwiEwJyYkK1-tsGyN13y8O9kls1OYvzTo,57466 +pygame/docs/generated/_images/camera_average.jpg,sha256=dkXZ7NdHmM69rbcYCpu0vKtqmHF3qh9nPUgZX3wlWDc,20881 +pygame/docs/generated/_images/camera_background.jpg,sha256=exoGN5fT9IKQyMJK_3VrEjfKTvr5yMeoSLCQplD0hes,7493 +pygame/docs/generated/_images/camera_green.jpg,sha256=NpIuT5qRzN5I7TFLva8m_kCAo1cwOuR5R5-Du9kaEo0,10219 +pygame/docs/generated/_images/camera_hsv.jpg,sha256=tfL0KJyxSk5A_KjVZR7MdV-qegBhej5HlXXw2CnoZR8,36673 +pygame/docs/generated/_images/camera_mask.jpg,sha256=0u0yMCldZMvSW1vyO2KK32D-fVYuYpXlNzmWwbdZ__s,18779 +pygame/docs/generated/_images/camera_rgb.jpg,sha256=GN_1jI8mnDJm1bRbjNBmpJETDSAKcVAS9BxmycYMMv0,32488 +pygame/docs/generated/_images/camera_thresh.jpg,sha256=WBYm8M-TxnuKCYEBvmu68iO_r9EYucnENZ5Ew8i6tqk,4346 +pygame/docs/generated/_images/camera_thresholded.jpg,sha256=OMh-3zXV2a-aahnMQlE7ihvxwXy1UADb15c3LzIWgh0,23678 +pygame/docs/generated/_images/camera_yuv.jpg,sha256=Gp0omp1py-_j6Qpv95VIo6EmDO2u5VY83fPWA2Rd6Bk,20105 +pygame/docs/generated/_images/chimpshot.gif,sha256=Yc_ufSFTkZ5NA1IogV2juH5Cr4_ykoI7QcXQvfGYBfc,46010 +pygame/docs/generated/_images/draw_module_example.png,sha256=fshvuNXWzZt3XL5FEmYilZ8F5xl-3CSy5lxQ-eqEhl8,7727 +pygame/docs/generated/_images/intro_ball.gif,sha256=vEs0-OG_j55JZJML48IXhHsN4ZMIWrqlS7dfxd9-hxc,5015 +pygame/docs/generated/_images/intro_blade.jpg,sha256=Aj59Tt9z1mdJeDK89HbWaQ7DVTDKbzoUDT-vNcJnYQo,2631 +pygame/docs/generated/_images/intro_freedom.jpg,sha256=RL-jChKVMdqoS7BN5NGV7hjlxSgU4qaKrj4ZXH2zsI8,7050 +pygame/docs/generated/_images/introduction-Battleship.png,sha256=6iHEhqo_HnXRfmQv9yriYMc3dX8Q2TwGbSGDQREFkN4,165586 +pygame/docs/generated/_images/introduction-Battleship1.png,sha256=6iHEhqo_HnXRfmQv9yriYMc3dX8Q2TwGbSGDQREFkN4,165586 +pygame/docs/generated/_images/introduction-PuyoPuyo.png,sha256=OEMjFSzQc8vJLQryUdkp7lJ3DhIw_yEo5-5nm2vBfrs,31388 +pygame/docs/generated/_images/introduction-PuyoPuyo1.png,sha256=OEMjFSzQc8vJLQryUdkp7lJ3DhIw_yEo5-5nm2vBfrs,31388 +pygame/docs/generated/_images/introduction-TPS.png,sha256=M4ioZMyjR2n7pQIp8UhGRV4m2V_rcXJCuo5lU3V7yGw,136031 +pygame/docs/generated/_images/introduction-TPS1.png,sha256=M4ioZMyjR2n7pQIp8UhGRV4m2V_rcXJCuo5lU3V7yGw,136031 +pygame/docs/generated/_images/joystick_calls.png,sha256=AWSPPJNhXafqoaGxi3evBCQFEcbvbS-EiqcQJqF__k4,23524 +pygame/docs/generated/_images/surfarray_allblack.png,sha256=XEUO2hKFfTfZMyaqbPvM0u3zmETfl___AANBkHn6y-w,125 +pygame/docs/generated/_images/surfarray_flipped.png,sha256=UZ1FpljGrdAnB1UCUTuAfeJJxyqI-_0duSCKqy9UN3w,50835 +pygame/docs/generated/_images/surfarray_redimg.png,sha256=6tlO_tZokQTsfgvD3yNW21nHA5uA9hDWoaL7POrr_qE,23443 +pygame/docs/generated/_images/surfarray_rgbarray.png,sha256=8US5r3GcG_jZBncK-47HG2JLB2ZwQjaSBWhb3ynNs9w,50897 +pygame/docs/generated/_images/surfarray_scaledown.png,sha256=Z68XSoPUvV5bYIhJdkmhZYr42JpQKnC8g9hmN18iaWI,15109 +pygame/docs/generated/_images/surfarray_scaleup.png,sha256=sdxQlmVoRhlF_ocD2ecre0q51Q2LLoCxNsJaI8O1NYI,67759 +pygame/docs/generated/_images/surfarray_soften.png,sha256=XNzAZzfLUqn-QIQ25TxB2ujoDZkPEIRI6QSDnjJMIk0,47540 +pygame/docs/generated/_images/surfarray_striped.png,sha256=iH7gLZhBu5aATV-vfrsQmW30KdsMQoYB_LD-efRzl_Y,392 +pygame/docs/generated/_images/surfarray_xfade.png,sha256=uD8g8Ueqc3IMZaOqiD4n4sZmg5I4j798oIzl1pWDM70,41834 +pygame/docs/generated/_images/tom_basic.png,sha256=RzKBFmep1ksfD5QrJVW7JzdHvDN1_4ayUfGyVN-8Wms,5139 +pygame/docs/generated/_images/tom_event-flowchart.png,sha256=sG8YOH8YX2yTtx4-agBUWIcT8-jj1m6uKCF98QxmbKQ,5528 +pygame/docs/generated/_images/tom_formulae.png,sha256=6k8VDsueGVOh01ZrAVLE5miliOWKhQJqIrlPW_JEmXk,6763 +pygame/docs/generated/_images/tom_radians.png,sha256=BkBTx4OoiSXO5d6sMG01MyYBtO7EmDvgJixbTRKMm9Q,17409 +pygame/docs/generated/_sources/c_api.rst.txt,sha256=42nhfRAilcsq1ezpGMlfdHLVLOy6GszORZzNplGrPSM,492 +pygame/docs/generated/_sources/filepaths.rst.txt,sha256=sou-1N5amW1JXHqwKUU0BqYQmm2c5qDjjFKVIKIppfo,899 +pygame/docs/generated/_sources/index.rst.txt,sha256=e8kO6-BbS8hlDqzthROoGHaGMIvDkycoN9Gc1OF3lvM,5190 +pygame/docs/generated/_sources/ref/bufferproxy.rst.txt,sha256=V5gSzq__85alqL5Is05MTmpDv5NUHbLuuW4PYH98MzI,4708 +pygame/docs/generated/_sources/ref/camera.rst.txt,sha256=jGe1yh6URcyLvMq4n52_IetG4hRWQxJPAHJXtQAMR3M,9563 +pygame/docs/generated/_sources/ref/cdrom.rst.txt,sha256=0FlYODuxoQOsTYpZ9EiDYmUD9PIQrAeAlsXE8I67RQk,9068 +pygame/docs/generated/_sources/ref/color.rst.txt,sha256=5fld0cemR82HhYYdSWOckR_-x58XYc_hYS35bHMS6-Q,10222 +pygame/docs/generated/_sources/ref/color_list.rst.txt,sha256=XLIKLmTx_liiWk0gdKlrElBlCNZD0_sIvAg_El6tgfE,96353 +pygame/docs/generated/_sources/ref/cursors.rst.txt,sha256=I34-Lsy054BfoHels8rEsvDPeQ4LrKRPG6cMt_v2kcA,9414 +pygame/docs/generated/_sources/ref/display.rst.txt,sha256=s_rmPPKV3ZRZKqcMDWAqgNeeBkdc9agVnCP9S8C-ZSc,28538 +pygame/docs/generated/_sources/ref/draw.rst.txt,sha256=CEFZSKX_KEAtsom-I1nafw0EEK-PlxzratVOhoIs0BA,24806 +pygame/docs/generated/_sources/ref/event.rst.txt,sha256=_9GUThqJVfqIwDnY8WoOR3Eu490DWjitTFXZMXd75NM,17626 +pygame/docs/generated/_sources/ref/examples.rst.txt,sha256=044bT9v4pQYRZqmaAwrdAI_GV3HLiwRlWHKYturVbN4,14095 +pygame/docs/generated/_sources/ref/fastevent.rst.txt,sha256=6gyem0cRZ-syKMDcQ6qexC83dJMro1O9-XHY_k2_HNg,3545 +pygame/docs/generated/_sources/ref/font.rst.txt,sha256=NuF6G-sjVET0h2oWnFTT2Mc2D1tGVCtQG6LNv1Z4RvE,14703 +pygame/docs/generated/_sources/ref/freetype.rst.txt,sha256=YPU6Ag9224LZMTpGOyeEjHcTeZb2eJJZvebNbRiOFLg,31070 +pygame/docs/generated/_sources/ref/gfxdraw.rst.txt,sha256=bQYID2bOb6xETZoDKM0mRtaCU_QD48MjQmCV8lvEErE,21842 +pygame/docs/generated/_sources/ref/image.rst.txt,sha256=Emy-SkCQClMxR3vHKyvipTgWH-cKLHMDiaCj6392--c,9544 +pygame/docs/generated/_sources/ref/joystick.rst.txt,sha256=L94TFACz01-OaO-qQrAGEAKa41g4Ql6jxzxhPRIaOuA,15523 +pygame/docs/generated/_sources/ref/key.rst.txt,sha256=DHgxo8qWMOhcQ7cPNSFlxWJmBN1pToqC2AqCz1gcX6M,14822 +pygame/docs/generated/_sources/ref/locals.rst.txt,sha256=VZi8cE2ZlPei4WdzSsok0fU_BOgPhlOmjfTCTSvl0N8,1022 +pygame/docs/generated/_sources/ref/mask.rst.txt,sha256=RAVpZZNK2i-ltHlCbNZgFa80x2RAXc_4lYkPgAVoyjs,24220 +pygame/docs/generated/_sources/ref/math.rst.txt,sha256=OVUUPjA_yd-24tDz1B_Wopn7v9XeF4IQOYHgzmToQnI,27250 +pygame/docs/generated/_sources/ref/midi.rst.txt,sha256=jIJ5qOZNvlMtBH0TA2icWTSB4_7CcUmU6_y9uVhGiak,14358 +pygame/docs/generated/_sources/ref/mixer.rst.txt,sha256=xIef_nLJUcN4jggYtRTd91iZo_oVYVDDHo-vVg5HNis,22346 +pygame/docs/generated/_sources/ref/mouse.rst.txt,sha256=VmAjwqKkTwm3RM96WW9O6xnVUJ1ZnVuS59OOQhe8Hv8,8129 +pygame/docs/generated/_sources/ref/music.rst.txt,sha256=TRWBNIajSaZWCdj82yMna5vQCFafiaju0KvKClN8Q7s,9531 +pygame/docs/generated/_sources/ref/overlay.rst.txt,sha256=loD8HVw0KQnsaPPisw_Xe8yms59CEAbdsboXwhS7zqk,2659 +pygame/docs/generated/_sources/ref/pixelarray.rst.txt,sha256=I5BlUFayOaB4wS1j8uSYAHJKRL_ErHi_CgEACnZOx_Q,10207 +pygame/docs/generated/_sources/ref/pixelcopy.rst.txt,sha256=SMb-VGMZSxBBWgAjsFg2ucaQp80KglnIo56AkS7O-vE,4531 +pygame/docs/generated/_sources/ref/pygame.rst.txt,sha256=BOv-OScRXp9bMGBCn2KnAeTFXbNE9QUfj_kZ0nd233I,14445 +pygame/docs/generated/_sources/ref/rect.rst.txt,sha256=ddBCeV756daL6s5nVyX8HOfbGTv0OlSNB9E7sBQ6iTs,13019 +pygame/docs/generated/_sources/ref/scrap.rst.txt,sha256=At1I3Htg5K0y61krT4hHiW2bOYimeOcqJs5BsLO28eI,7990 +pygame/docs/generated/_sources/ref/sdl2_controller.rst.txt,sha256=1fJFma_4t39mGIbYIyenuM8Q2XdWCAA-rPBJNsU62iU,9324 +pygame/docs/generated/_sources/ref/sdl2_video.rst.txt,sha256=3YrzdapWvu7Jy4rwvwPy0rPTdXQP4Lz44MgAtvV7hgw,9115 +pygame/docs/generated/_sources/ref/sndarray.rst.txt,sha256=9N1u6w7AxTr943nJiSUoDyNi-8qE_tWJeVU0rVEQYi0,3260 +pygame/docs/generated/_sources/ref/sprite.rst.txt,sha256=DfJlI4xKt3iuWQ_XTc727G4IHRQb6ohKxSMRo9SqVYQ,29584 +pygame/docs/generated/_sources/ref/surface.rst.txt,sha256=Z8_rxC2kannzcGdCZlvENgj_-bSTUSFdGsx2ExnuaB0,34198 +pygame/docs/generated/_sources/ref/surfarray.rst.txt,sha256=rBeiZncxG9HIetGkZS0IcwxIlHqQgCxpiZxO6bSvY8s,12251 +pygame/docs/generated/_sources/ref/tests.rst.txt,sha256=yBiMVm8xKcDfnFq1rrxQRDx-N0Zf12XmnjsapTBrsO0,4571 +pygame/docs/generated/_sources/ref/time.rst.txt,sha256=YL2ZqmQh9pIOULLhQDwAOOImjiFgU2RPXAiSX4PSLho,5624 +pygame/docs/generated/_sources/ref/touch.rst.txt,sha256=v7V0P85KlxiQIBLjaVyhTtH1fmDmqif-kDLhV0O6x4s,1957 +pygame/docs/generated/_sources/ref/transform.rst.txt,sha256=7xIt7WJtx4CgbK0vDXIXDty97Qq8uX92NeFY5MOOpIg,10639 +pygame/docs/generated/_static/basic.css,sha256=PT8oPl85ajFNgmu_xzmQdnrMFcU4BnTis_6AHlM9Q74,14667 +pygame/docs/generated/_static/doctools.js,sha256=WgAZNIhr3ty6v4n2qB6CU8DBu0esGOnHuFnw_LHZJAY,9630 +pygame/docs/generated/_static/documentation_options.js,sha256=2EL4UL29uO9t-W_q-eoHlRXqACotAFSF4aUsuhODexY,355 +pygame/docs/generated/_static/file.png,sha256=XEvJoWrr84xLlQ9ZuOUByjZJUyjLnrYiIYvOkGSjXj4,286 +pygame/docs/generated/_static/jquery-3.5.1.js,sha256=QWo7LDvxbWT2tbbQ97B53yJnYU3WhH_C8ycbRAkjPDc,287630 +pygame/docs/generated/_static/jquery.js,sha256=9_aliU8dGd2tb6OSsuzixeV4y_faTqgFtohetphbbj0,89476 +pygame/docs/generated/_static/language_data.js,sha256=AErWrX53LW88YcZaf7JV4rNti_WDBEwIq5fn5ea4RC0,10854 +pygame/docs/generated/_static/minus.png,sha256=R-f8UNs2mfHKQc6aL_ogLADF0dUYDFX2K6hZsb1swAg,90 +pygame/docs/generated/_static/plus.png,sha256=VBFRmblqEwy6AhR8R8DetD3Mm58ItRYruoZCs0mArGM,90 +pygame/docs/generated/_static/pygame.css,sha256=l0Oz6jiSpdEuw-wLk2nk1QscHB95uXWRuKMWzRpVhnQ,11454 +pygame/docs/generated/_static/pygame.ico,sha256=YeIWletq938Rg_G11m_1iGL_zw9T_U1QXZhjnbHjJSU,1078 +pygame/docs/generated/_static/pygame_tiny.png,sha256=BXPk3OkSWdSkqjMkCQ1Dt5WjxZfb4zj4c2ir9U9_Y7Q,15310 +pygame/docs/generated/_static/pygments.css,sha256=Ha6G_c7xz4xpohyOBtK_2UWhoxTi7HwFhZE-KjauAiw,4846 +pygame/docs/generated/_static/reset.css,sha256=wqvSs8L_cB2K6bR903cOJkEtfJi0LpNQFl3nC_kZQr4,1083 +pygame/docs/generated/_static/searchtools.js,sha256=Owa-DGWcGVZoE91fdbc1m9h_5qVc4e_gXsMS6EoXi8w,16793 +pygame/docs/generated/_static/tooltip.css,sha256=UkuHG9X2M7DaTSMJX3-mW-4LV8wBtFxfG5k_pr7xrc4,798 +pygame/docs/generated/_static/underscore-1.13.1.js,sha256=zBD3mc0Pa2X5XEASRFSX5bo8ufUZZKlGiUCye96YtIc,68420 +pygame/docs/generated/_static/underscore.js,sha256=IY-xwfxy6a9rhm9DC-Kmf6N2OStNsvTb8ydyZxtq5Vw,19530 +pygame/docs/generated/c_api.html,sha256=NiaNeQjKhz2rb9sEMuW8eRBr1Fgqln1yr9KDrT_BdbQ,7410 +pygame/docs/generated/c_api/base.html,sha256=jKt-groZfg283h1QfkU-RZc08UB7X3PEOoPX4hPhlXM,32166 +pygame/docs/generated/c_api/bufferproxy.html,sha256=Hnkl5JhqOj2d1nbkwbkjVgD9pguTw6IePMaiJzvRXKE,11763 +pygame/docs/generated/c_api/cdrom.html,sha256=xH2qo7yJzFsotaNuW8E69nPPwMKTfdG4xqRUl6l0lm4,10647 +pygame/docs/generated/c_api/color.html,sha256=T9MZMgi0maZcgcil7MOYmQI_azuCVQ0elxUmifbroaE,10189 +pygame/docs/generated/c_api/display.html,sha256=nVh-AB3Lvue4Hz14jFOPJasXTiK3S5W0U8hkjI-kAYM,10612 +pygame/docs/generated/c_api/event.html,sha256=aCm33QOklyoD1uwwLAZATYayA3wFRWnTvL-F_KiF4Cs,11910 +pygame/docs/generated/c_api/freetype.html,sha256=jQWycGCKeja8u9lcfG7USJKf1AbL7VQaNZgc6hUWDPA,11164 +pygame/docs/generated/c_api/mixer.html,sha256=8T1tBCyVvJr6SZ7brpcoDrvBAmAtqA-6HoHSR9u4op8,15135 +pygame/docs/generated/c_api/rect.html,sha256=R6P29KHtVrGxInlHkJBapx2lBDsnxdCAnwXUWkR5qBs,12772 +pygame/docs/generated/c_api/rwobject.html,sha256=6o2Vu_defbN_Gt1_9yPxW6vSYX0n6EoWiZShz4ll4NQ,15296 +pygame/docs/generated/c_api/slots.html,sha256=icIiIzgxb2EKI7PBPGYgtSWulv0raRWnLnefRHM_VtM,6630 +pygame/docs/generated/c_api/surface.html,sha256=QBpU6KF88FQhdnqafM96j8dp87ngMc62zft31O7ZHV4,13514 +pygame/docs/generated/c_api/surflock.html,sha256=-h8R9UWH23CbmhocwP_HxjJQyUwtPJrqf2kmdFUM5xc,16549 +pygame/docs/generated/c_api/version.html,sha256=tqC14O8xkRz5TA06ZZlhuarbZqLifSNEXIaj8PZ3JkY,8020 +pygame/docs/generated/filepaths.html,sha256=AzsV7NjZp9ZfaRKSE4INhmgzKs6552h7gMIs9sAW-18,6109 +pygame/docs/generated/genindex.html,sha256=bXRL0a8tMhh6tHrUST40rCU-yoFWuYlv8ftS40YBTNs,118056 +pygame/docs/generated/index.html,sha256=noIE5aSENMvJgotzNhzG8zhyVXdXXwdiNNPR41g1ZZA,14561 +pygame/docs/generated/py-modindex.html,sha256=mz7npnGEyk1hK4pQ-WuQ1T4SBbIswWKbfeDlWCKhnYs,11245 +pygame/docs/generated/ref/bufferproxy.html,sha256=t9zmJnE2Xt_HKRA41jzfkY57gebjCIir-MKorTiLeUg,17452 +pygame/docs/generated/ref/camera.html,sha256=CocBhB4XuXpBdCAVsHcePt1ZCPPx18EoSfpKVRjjAK8,26748 +pygame/docs/generated/ref/cdrom.html,sha256=selCcZUcmEVEsNNKq4uHsbZzneQ5LNUL7SsnYB067k0,32782 +pygame/docs/generated/ref/color.html,sha256=TBsnNlo6zgrLlmYa0kvzNCYlDqpaLBR1DF1IS8jNqRs,34494 +pygame/docs/generated/ref/color_list.html,sha256=xiqf0q3xFOSWsqVDEMdGq58O6TJ80lWzNlmjfvIkXnU,174246 +pygame/docs/generated/ref/cursors.html,sha256=5ZdSgfl9c_s8kyTIt3Pb4CLg5Gn4SxN8V2nCocv0tek,35325 +pygame/docs/generated/ref/display.html,sha256=eem75qz25cp-v9_ANPaLDA7v8AM6ksFJZqmnlMxCUmw,75302 +pygame/docs/generated/ref/draw.html,sha256=TujkN709bjkQdRuL14rsXGJWORAc1d5cJZgkFQoNP3s,84154 +pygame/docs/generated/ref/event.html,sha256=SoQxcuWbO0XW8J6GowJHTndio0SkZccte8wUpL5umNA,57127 +pygame/docs/generated/ref/examples.html,sha256=0faINa4vAFCBoQdirlivl3HUCCOQ5HmP9ZplVOBxEXc,47825 +pygame/docs/generated/ref/fastevent.html,sha256=Bn7D9OsL4c_jtAkET9PcN4OGI64ikbbuieZNRUp8oBw,14792 +pygame/docs/generated/ref/font.html,sha256=NZHlnPGaRSj_s3FRoJ5VEnsXzZRI2ptWsrdDgKNljMM,40765 +pygame/docs/generated/ref/freetype.html,sha256=1lxqnOEJn3iaR4MmOvLL1TphxjzzHl4gqShDkeJNCC8,98830 +pygame/docs/generated/ref/gfxdraw.html,sha256=fKqtUUO4Ky91XrRlDZRTPktYIQPF1GoZpHxSW6R-FJY,78728 +pygame/docs/generated/ref/image.html,sha256=lZAF7l7xYFyLNP-8UmZaXcNSuTiQ6iKp6HZdfTkbQkg,29060 +pygame/docs/generated/ref/joystick.html,sha256=Cnl61DeZMblBTmoDY-wtbuaYj3Kma2pwmXVm1i0PB2Y,77305 +pygame/docs/generated/ref/key.html,sha256=G-j0h7bcxlgDZQ3VBgB-CnMB_A-Up3NWdk3DnvdCoZo,39532 +pygame/docs/generated/ref/locals.html,sha256=yMM4zvTbz_sSAzBHy1SYxyuJ2vPy0l9zUTEhSsIBuSM,8436 +pygame/docs/generated/ref/mask.html,sha256=T8xZM_uaQbDXkkLVzGdt6Ej50ht6IUs889FdNERD77c,79340 +pygame/docs/generated/ref/math.html,sha256=-ugNRSZPt35x-lEPzB46jrMMK5zUz8Zk_z9uISoccJU,92846 +pygame/docs/generated/ref/midi.html,sha256=u-p4QWJq0k8lAnfkvARsqlEKWBqiwh0KoPVHoOGc3Qw,51730 +pygame/docs/generated/ref/mixer.html,sha256=1jixQ7Ddf_jcXOMZc1xYimRzXQvNzZWGpor1wHvb7Qo,63008 +pygame/docs/generated/ref/mouse.html,sha256=KORwcEQhfds0cTbnHT5zFqKYl0v7airzKpg-EGoyI3A,27064 +pygame/docs/generated/ref/music.html,sha256=oZkwS370BTQmfGsDLOhpWw1K6wHd_v1bRpnjiVwB9Mc,33113 +pygame/docs/generated/ref/overlay.html,sha256=khT7lFEoIMWDMjhFuncssF1dMh_wqIcwn9lhPxyAqno,10760 +pygame/docs/generated/ref/pixelarray.html,sha256=ZCCfXhGnO5FSR0_eO9Q3Iu-DvAB7DhAi_XRbPyOPg_4,30971 +pygame/docs/generated/ref/pixelcopy.html,sha256=-9U_Bng2vaszv9A0EiC2BBpAuA5pNTpvAzPSuaCgK68,14836 +pygame/docs/generated/ref/pygame.html,sha256=_-hr-dTvNzrXEszK3GvVRTcI0dM7-dBACL97wO07YLM,47342 +pygame/docs/generated/ref/rect.html,sha256=sEP06toOTs7-rBz5kTsFL8SKPnt6HaPVzpiDJlguH7k,39078 +pygame/docs/generated/ref/scrap.html,sha256=d0aN55iYwQc6UTUpTEAW99bNAQTXl_Llf_2vvSpb9BU,30090 +pygame/docs/generated/ref/sdl2_controller.html,sha256=nqmtjDjlv6ly4lB5p-cUv78g82WfK4zbFfZamFfYKPw,36120 +pygame/docs/generated/ref/sdl2_video.html,sha256=nWW7y9d_aY2iWoimwcMETe_fVgd129vL_-Wv_L7aDDg,60725 +pygame/docs/generated/ref/sndarray.html,sha256=KdLFEbxZPSecSwJ8Iwr2xuVBhWwSnLCZm7XyzRY5d80,14425 +pygame/docs/generated/ref/sprite.html,sha256=iUDv5dsGF7JytRynR2NVc2Xf7YpPtzQSvRlFd0gNls4,91342 +pygame/docs/generated/ref/surface.html,sha256=iZ6JDOVlVRN03WTEo7JZixEPB-bUWhuU8e7257v5PYo,84986 +pygame/docs/generated/ref/surfarray.html,sha256=VqXMCLLd7fHWPhpGBvYf239HcBSVjxoqbKBcUdv9huc,37729 +pygame/docs/generated/ref/tests.html,sha256=LYuia3GRyCcjEWDeNWaXqG1ZoU7oN6G53OQuXsHay8c,18238 +pygame/docs/generated/ref/time.html,sha256=L_AJZhjE6UgZKZmIikLbfzlGY4krHIObyg9oGd4vZM4,19654 +pygame/docs/generated/ref/touch.html,sha256=kfJx9qXwR-JZH9OIJO1rYC3YnzAtnMqLKMy7WRy0KWU,12737 +pygame/docs/generated/ref/transform.html,sha256=uWDngpdZIwIgRxcDnI8QDJ1R0U2IK9GAiB_8W_RPJSM,36455 +pygame/docs/generated/search.html,sha256=V9GzDP20Pkizo5AU1v0wv6D1DU6nuoVP3gC6Kv74t3o,3143 +pygame/docs/generated/searchindex.js,sha256=Y3nIF--cV8Cl7rl-E4rI78JQ5IAxSmKySGznQqYtGXk,199568 +pygame/docs/generated/tut/CameraIntro.html,sha256=EKaGvFRtC3REGMcUKWeOzqyTq47yi5Jh2HA1n-RrefE,38106 +pygame/docs/generated/tut/ChimpLineByLine.html,sha256=RFafm4EViwfSh_jwykoaNuxR7eF-MOX1CuEGCP7GXDU,58114 +pygame/docs/generated/tut/DisplayModes.html,sha256=LqQKyOiNKOqdllkw6Tg8NH3o7JUEaZ-JJKzq6WTxQRU,21978 +pygame/docs/generated/tut/ImportInit.html,sha256=82I0ERS8xCdHFIpRJrH1RmnzuZVSvZ3aMjiNaizPniI,9533 +pygame/docs/generated/tut/MakeGames.html,sha256=LWAkFqVhBtl12MebA-MbpeJ2K7hCYt2OdOZORCNlvPA,14686 +pygame/docs/generated/tut/MoveIt.html,sha256=W9fuz5CWZi78KJPKoIyoHUqubUn-JfTPIGMYY1nAmYA,47005 +pygame/docs/generated/tut/PygameIntro.html,sha256=pTJd3-xYXS8xeDPu4EiyEwK_HfynEmA1LGlXJzuG8JM,29359 +pygame/docs/generated/tut/SpriteIntro.html,sha256=lW7X_7YwmM6yJCJme_9wm6b5mWH6wlYmSekzHs8PLeI,43891 +pygame/docs/generated/tut/SurfarrayIntro.html,sha256=yDCYrJ3zuiICrWfqzRrpMM_0glO864XzWaaD-eZ8pbk,50565 +pygame/docs/generated/tut/chimp.py.html,sha256=e6mzcguKm60dxzBUlwWMjuGW1XxztL7dUp9jUi5cnLI,35882 +pygame/docs/generated/tut/newbieguide.html,sha256=7OPO2DD7FfVG_w6EK1A3yrbfUAmAqhhSppiOFwkNmBk,37246 +pygame/docs/generated/tut/tom_games2.html,sha256=T_1gNV46hO9PhojS8a0ZtFAeegcN8fMvsgxcm59npbY,17519 +pygame/docs/generated/tut/tom_games3.html,sha256=xUswNEFKOGfNE1IIZXFfhqJF1pHx1gXeztGkoO-RNzY,15147 +pygame/docs/generated/tut/tom_games4.html,sha256=BIskEqj27qoP6wloTGJmMBwy3slfMMzJG1NNYHzDlhs,18993 +pygame/docs/generated/tut/tom_games5.html,sha256=l21xKIyP5J23PrmXeUtQztGjMBrWOxA7GaGvUzp7LhE,21268 +pygame/docs/generated/tut/tom_games6.html,sha256=rhXpDtZH3pxj1kP2MxzYIsWsGIsKWmuKliBiFWrUzxE,52826 +pygame/draw.cpython-38-x86_64-linux-gnu.so,sha256=uK0g8TLut-76CooQ8guTJCeorzzN4PzoX2rOiZ9-NK4,68416 +pygame/draw.pyi,sha256=fY1s2gCQgqXZ_o2uANCdyI41lf3M599qblgFn1r2-rA,1753 +pygame/draw_py.py,sha256=jFIaxRR9jqvfHqy5xuaugK398H2KKF9IPubE3TTLkAo,18687 +pygame/event.cpython-38-x86_64-linux-gnu.so,sha256=u5JdXTQ7HYN7b2E-wWdrajwtAD4eXxHh1gBxgf8Gysk,76128 +pygame/event.pyi,sha256=mtDr6zuXbg5Scdm5P5ICFniTNJi6-KWjiHoqoCpJ4Yg,1193 +pygame/examples/README.rst,sha256=EByCXjSksQjKrdwut3-4LbS-u69zb1vJOXpExxRScek,4360 +pygame/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pygame/examples/__pycache__/__init__.cpython-38.pyc,, +pygame/examples/__pycache__/aacircle.cpython-38.pyc,, +pygame/examples/__pycache__/aliens.cpython-38.pyc,, +pygame/examples/__pycache__/arraydemo.cpython-38.pyc,, +pygame/examples/__pycache__/audiocapture.cpython-38.pyc,, +pygame/examples/__pycache__/blend_fill.cpython-38.pyc,, +pygame/examples/__pycache__/blit_blends.cpython-38.pyc,, +pygame/examples/__pycache__/camera.cpython-38.pyc,, +pygame/examples/__pycache__/chimp.cpython-38.pyc,, +pygame/examples/__pycache__/cursors.cpython-38.pyc,, +pygame/examples/__pycache__/dropevent.cpython-38.pyc,, +pygame/examples/__pycache__/eventlist.cpython-38.pyc,, +pygame/examples/__pycache__/font_viewer.cpython-38.pyc,, +pygame/examples/__pycache__/fonty.cpython-38.pyc,, +pygame/examples/__pycache__/freetype_misc.cpython-38.pyc,, +pygame/examples/__pycache__/glcube.cpython-38.pyc,, +pygame/examples/__pycache__/headless_no_windows_needed.cpython-38.pyc,, +pygame/examples/__pycache__/joystick.cpython-38.pyc,, +pygame/examples/__pycache__/liquid.cpython-38.pyc,, +pygame/examples/__pycache__/mask.cpython-38.pyc,, +pygame/examples/__pycache__/midi.cpython-38.pyc,, +pygame/examples/__pycache__/moveit.cpython-38.pyc,, +pygame/examples/__pycache__/music_drop_fade.cpython-38.pyc,, +pygame/examples/__pycache__/pixelarray.cpython-38.pyc,, +pygame/examples/__pycache__/playmus.cpython-38.pyc,, +pygame/examples/__pycache__/prevent_display_stretching.cpython-38.pyc,, +pygame/examples/__pycache__/resizing_new.cpython-38.pyc,, +pygame/examples/__pycache__/scaletest.cpython-38.pyc,, +pygame/examples/__pycache__/scrap_clipboard.cpython-38.pyc,, +pygame/examples/__pycache__/scroll.cpython-38.pyc,, +pygame/examples/__pycache__/setmodescale.cpython-38.pyc,, +pygame/examples/__pycache__/sound.cpython-38.pyc,, +pygame/examples/__pycache__/sound_array_demos.cpython-38.pyc,, +pygame/examples/__pycache__/sprite_texture.cpython-38.pyc,, +pygame/examples/__pycache__/stars.cpython-38.pyc,, +pygame/examples/__pycache__/testsprite.cpython-38.pyc,, +pygame/examples/__pycache__/textinput.cpython-38.pyc,, +pygame/examples/__pycache__/vgrade.cpython-38.pyc,, +pygame/examples/__pycache__/video.cpython-38.pyc,, +pygame/examples/aacircle.py,sha256=pX1LpRdF_w_tvkCX9ngs7T1lyM5JpG8mwcB7P2k4_10,1034 +pygame/examples/aliens.py,sha256=BiT_DsYpTnWCZu4Ozl2YcopkIqcYAsv5GLaFrpvJUcI,12055 +pygame/examples/arraydemo.py,sha256=IdAvdvTIVSx9jEZjcgc1CrZy2dghKsxCQlxtnXSE_Tg,3633 +pygame/examples/audiocapture.py,sha256=4RQdjYlRMM_iAAwuccV0kT_SuNhQw2KibDeLOhgy6ow,1563 +pygame/examples/blend_fill.py,sha256=LZ1u3axCmw7uunMFJkoyF2Wir74uIIOxDU1i2HFO-eg,3425 +pygame/examples/blit_blends.py,sha256=P_BgoD_GnDtGkltRs5p5nIFOljhYWjl1Yocw8zEzqWQ,6345 +pygame/examples/camera.py,sha256=YmSUf-E3UD34uianEMIfIhyKblTGakn7pt3QvPUOBKc,3028 +pygame/examples/chimp.py,sha256=htSz24LiUgGgrkGdnd0bj0fKEj6BYyFf8WA1Y8EyQT8,5910 +pygame/examples/cursors.py,sha256=b-IapWngIiv_nCm89Ctb4cXRMt_wFGrK4EnEqF-CV1g,2873 +pygame/examples/data/BGR.png,sha256=DvOrlW5BJdat94nNV8XEETBLRrSWRV7byQsMPsA69uw,244 +pygame/examples/data/alien1.gif,sha256=8Wveo1zpLVaFCtYITm_SoYqjy8L-TDuaZOcNa8Osqsw,3826 +pygame/examples/data/alien1.jpg,sha256=HOjXjmW4Ofsu_en9WNrkuIp_DCwupXcFB0Yt_cqV9rA,3103 +pygame/examples/data/alien1.png,sha256=femzLssV7oGvT3S2tyviyq7qO32QfhBDtMOR3ENBCLs,3522 +pygame/examples/data/alien2.gif,sha256=0MPpVYzvjAECy0pd7YRFKCEzzIYDKEJt70rbjlLbTZM,3834 +pygame/examples/data/alien2.png,sha256=FKGYDI2FBBR1Z56BLn357PNfh3-M38gAJpSQL8BpKYY,3526 +pygame/examples/data/alien3.gif,sha256=bFCRGZOQPaadCKIc-tlqoUjHdsi5IzR0E-2SjpPEvmA,3829 +pygame/examples/data/alien3.png,sha256=a51Tb9E4IvoICGzQChHq51RKVQJLf1GOCEeqA5yYfnk,3518 +pygame/examples/data/arraydemo.bmp,sha256=xM4-n_hRCQFZlfwwdTK6eaBweycUc863TgSFbWp3dbA,76854 +pygame/examples/data/asprite.bmp,sha256=97XMpKq9lLpMuv8UveCf8UJEAxheBhPUjHfMRQBkUx4,578 +pygame/examples/data/background.gif,sha256=-3kZwt99MFUBbBo-kHvPZXVlFrSB34XVNQWWxfHb970,9133 +pygame/examples/data/black.ppm,sha256=Yu8BwDOeFwOnVYjdWTMo7Tl1xcx2a7J38zZP-JllcMQ,6203 +pygame/examples/data/blue.gif,sha256=hqbgDzCeUz0NHjAQHYURIxSOpRbpHf6QeFch8ux_dAE,84 +pygame/examples/data/blue.mpg,sha256=XDj1CRPt1MWxspCfA3oqb822nlZgQ7CyyEuVJwlgmpg,6144 +pygame/examples/data/bomb.gif,sha256=TZ60QP1S2QBN6QPNSqBwS5VyebZA93iu8ZMUXzEg2QA,1170 +pygame/examples/data/boom.wav,sha256=kfoWs0VVDGHv0JSa46nXZBGyw70-jpfPq_B31qNA_F8,12562 +pygame/examples/data/brick.png,sha256=K_mshK0aL81nzOjAorTXyPps6n9mvofLeOWFXFpVjYA,170 +pygame/examples/data/car_door.wav,sha256=TwYWVqme5NqVVID1N4es92RSKEdTYkxbNx6dNamK-_4,3910 +pygame/examples/data/chimp.png,sha256=gFY5lDOflZ5fCMXpL9_HmipP4-3ALn_r6cCB9yTZKBk,826 +pygame/examples/data/city.png,sha256=c0Nu2o7x7QmvGMDmDCaPnhvJ8tPNuguKKpI_Z-NfQ40,143 +pygame/examples/data/crimson.pnm,sha256=o9ziiY4ox_cCmEo07w08SQckCQTRttxtLgKBE0VmZY8,3124 +pygame/examples/data/danger.gif,sha256=m0CBKalFbkqlohgOmrwkwVOfqBhRWonb7xm1pzbDy2Q,2761 +pygame/examples/data/explosion1.gif,sha256=WYcdwbZqmYdaaaPYFiR5vka0Anp4F4nnNlpSSx_1xug,6513 +pygame/examples/data/fist.png,sha256=X0VOsy6fP0UGqBjy7baoBX8XAXyp_1_s2tOItbtA7EI,86196 +pygame/examples/data/green.pcx,sha256=si9WT7dyn3nsXoh34UBW0yOCKWbC-Rz0fKkc_7TDRbY,320 +pygame/examples/data/grey.pgm,sha256=uWTtnBH-Fv605OtEJzS9fG5ns9XaeUHq2YeAC_cdkKU,4155 +pygame/examples/data/house_lo.mp3,sha256=R0nZUXymMp_XLPU8S1yvsiVeWT6MKLt5Rjp-WSnVrLQ,116320 +pygame/examples/data/house_lo.ogg,sha256=64FiQ1Zjq-cOj6Bmya_v3ZjEWmBaGZlTl19udKaz6sU,31334 +pygame/examples/data/house_lo.wav,sha256=B1BwfFaPIsSxaash-igVI_YE9SQd1BCXRTnSAKsNunY,78464 +pygame/examples/data/laplacian.png,sha256=uWI8dPstqMEPVuFPGtm-guu48T2-L3kn99rWA3ZhZ-Q,253 +pygame/examples/data/liquid.bmp,sha256=qtzPXhq0dr2ORNCCZ6gY2loT2Tsu0Dx5YvXB548I1Xg,11734 +pygame/examples/data/midikeys.png,sha256=9HCCmMHvlubR6G9a0jMv1C-AKeBzYfb5jjNhol2Mdqw,19666 +pygame/examples/data/player1.gif,sha256=3ZTVWGxnedKqtf3R-X1omPC0Y8jUSPGgHBAzeGhnV4c,3470 +pygame/examples/data/punch.wav,sha256=A0F1xT8aIZ6aNI_5McMqLygb1EfmdIzPi4kWkU4EwQc,4176 +pygame/examples/data/purple.xpm,sha256=3r6_3v6tob2qy-1hrQ3ujYHpuFb9UQ7LuNsHWq9mj5A,1249 +pygame/examples/data/red.jpg,sha256=mgaTBGP_k55FcqJIL7eV4jYll80zaZHPHfFtXAOLnF8,1251 +pygame/examples/data/sans.ttf,sha256=nrZ6FRet4dwlvA7xOReYCP2QwyGebk0iVJaSFbtpOhM,133088 +pygame/examples/data/scarlet.webp,sha256=iLN1RrY8LCSUnDrwYvWC99v_pLGy0iN8winH7VAyVL0,82 +pygame/examples/data/secosmic_lo.wav,sha256=-EIFkzj7k5qEqG04n7mnUGUp1SsyCJ4n08TzPT600DY,18700 +pygame/examples/data/shot.gif,sha256=bF2eY629zQzvDu83AKpveSFhJq5G4QpOE98A0tvbPFI,129 +pygame/examples/data/static.png,sha256=Xe4wN80awt7nTNiLemoSNTEKlAbGFW7djNETP8IleNs,1202 +pygame/examples/data/teal.svg,sha256=nkksR3fo0NPwC9sVXQPrPR_QrvqRiUB1vC4I-K83dho,313 +pygame/examples/data/turquoise.tif,sha256=4OkIy6CDPMv77tRR_wA9ZHA6qZzG3pjZ-1m1mNB7bcI,1186 +pygame/examples/data/whiff.wav,sha256=FMWM3XnYtce6mHFXQCYPgzT-xu-Q4DJybZfpPjG8cpE,5850 +pygame/examples/data/yellow.tga,sha256=EhxUG3SMO6bbHxr4yFggnKrsC1mYZVq-L6znAsR3z8I,3116 +pygame/examples/dropevent.py,sha256=BvidStsTzZoC4CURTc0muK6ErDd4Q3FJt6MDsNgMGcA,2240 +pygame/examples/eventlist.py,sha256=1PhPxMies_2rZtxVSg4nnVw7S3bDEYXw7p4yJS-n0ic,5938 +pygame/examples/font_viewer.py,sha256=53EE0szoIMxZFk7I44hD7kZg--zlC7kQg-AcqnZu0TU,9841 +pygame/examples/fonty.py,sha256=Fm3POU3D82ypRTYEl7NIdeFcpcCPL6h3TC2tfkp_DDw,2080 +pygame/examples/freetype_misc.py,sha256=Zq2hF3j0MaleVkELLz1vn0wgc5k5yuwoHPt8Mjz0XVQ,3656 +pygame/examples/glcube.py,sha256=UONh_9RvLhCG8qLQUys4sE2xXY8ukf2zs0KXr_IRE5k,16860 +pygame/examples/headless_no_windows_needed.py,sha256=DYSKwYk4EWjNtujuv2SAyQ_-8eKhVqiHGKdI67fBjd0,1301 +pygame/examples/joystick.py,sha256=Yb9KMRvaFEK_0f1peZVo089qxZgM1WKBIasm-RH9EiU,5333 +pygame/examples/liquid.py,sha256=Y9pOpubVrb4pwd8F8qoC2aiueyWi4h_Xmq58g8jn-is,2541 +pygame/examples/mask.py,sha256=RsJFbWGJWNyLkKDxz-Oa8uqFS4eNZZUBdIUCDS4TxRM,5811 +pygame/examples/midi.py,sha256=aIF0N5c12-SMqAhEfncWeC2mlpokgIUF6jyRNdl4PpU,29414 +pygame/examples/moveit.py,sha256=swUcvxdNi0zx50i-sT2IW9UtBegWEepZFTGL2KhPTgQ,1824 +pygame/examples/music_drop_fade.py,sha256=Kn8r4u1zGAovWhUHIotjia1Ex-XhpiDvWU-TpND0vR0,8999 +pygame/examples/pixelarray.py,sha256=G2QLM6ooZKN0y3ULndWcZap5R8FBXQl3FwUcDeNAq9M,3450 +pygame/examples/playmus.py,sha256=jTFOwnRpA701s6HbFxzD9bKY039foXJcr2LRBjYrusg,5223 +pygame/examples/prevent_display_stretching.py,sha256=QK0eUSE7Am0gJ5H7C9XPeBIoURcEUzyX8o2XK3oKv3A,2492 +pygame/examples/resizing_new.py,sha256=8p6Sy8s74A49OXeboxmWnKGQJVO99ATE4nm7A_ADTxY,1046 +pygame/examples/scaletest.py,sha256=tP6-18mhlRa4ffsECE3LoID0w0RRwCgC4Eqkp4NTsD4,4866 +pygame/examples/scrap_clipboard.py,sha256=QERCic7zIHipQjp7eUxmzNU7kQ_N-sYdejsoA87tX2I,3056 +pygame/examples/scroll.py,sha256=KZZVc-TZhYRIZCBChDq6OmA5kM_CBrUlwe9g7J6AqI4,6578 +pygame/examples/setmodescale.py,sha256=6GCUOLrGp7KITI1qoGj1hJqNxkouUqmdfYgXlD7IYns,1801 +pygame/examples/sound.py,sha256=CwQ3hSKjD_sHmXEBLfimdbt18cQcXwk5QQ7jfnOuSS0,1172 +pygame/examples/sound_array_demos.py,sha256=WscuFblSjp8K9G1rtBbnDr7B0B12haYo4Ma7j41FmzQ,5797 +pygame/examples/sprite_texture.py,sha256=zq5ry_333hIOYevVmoOrkTJauWIoPvIqeF7CrIPerXM,2516 +pygame/examples/stars.py,sha256=nLDKyCQ4xor98M_z62uMWEojAkC9eWlFbDk_r7eCQTE,2762 +pygame/examples/testsprite.py,sha256=ibKDhB0LEXGmcVNOa7fcuCW7VGMFUoZPYZEpG_9iOKw,7001 +pygame/examples/textinput.py,sha256=r6rsAZDb-4L_BIFwYPvhrw0CJnC0yD94thfLNXUfz2w,5394 +pygame/examples/vgrade.py,sha256=_t_10Y7vQoFQjyeUlxQJgcbLkT1r3jCKLeOrWek7ofY,3277 +pygame/examples/video.py,sha256=RMnMLIY_AHaItROsYK2XKKqofIdebwYwU4PkeGJ4cWo,4452 +pygame/fastevent.py,sha256=NOVGX3eAvQGCSHDOZZJ_VuWSyekqdPO1Wrr2MvCEQeU,1694 +pygame/fastevent.pyi,sha256=qZgTFBKUESMcDySpT0uIGjH1fkov9CO2VvazYoWQFxI,249 +pygame/font.cpython-38-x86_64-linux-gnu.so,sha256=MuRSOZaPy-rJ8aubwjCLRZr6pC4q553QXxzYy3RgmRY,51360 +pygame/font.pyi,sha256=622c9NX1dIFIFNQX28xp5MnPBEn602QE_PK20UfW3cI,1528 +pygame/freesansbold.ttf,sha256=v5JRJp8R5LNVgqmTdglt7uPQxJc6RZy9l7C-vAH0QK0,98600 +pygame/freetype.py,sha256=fuFG_GnP5IpwyDbu0tpgcFLqLBL24MM1oh76AzqXJVQ,2216 +pygame/freetype.pyi,sha256=TfhgKrq9pnw9O3oMxH0UxbZC1tG4zClHztk0NHkXvEI,3269 +pygame/ftfont.py,sha256=i_YMSrD-fqL7vDhaNJTF-S9fZ2-cEyHWe22W7eNb4-Y,6145 +pygame/gfxdraw.cpython-38-x86_64-linux-gnu.so,sha256=fJZ8paoJGm_2cORVT0YO2nNEAqlMYzRpBK_IdQC2W2M,88264 +pygame/gfxdraw.pyi,sha256=L3J_XWYdwJdfXojW3fZRNNnNULH6jDOGGz8XXw15s9M,2531 +pygame/image.cpython-38-x86_64-linux-gnu.so,sha256=tCGCZqNCG4IOgNaoFVQ6ZSbwsbZOL14Y33gyUv4v5K8,42760 +pygame/image.pyi,sha256=nXtOIFYkslgBLnka3vZyRx-z59aM5FYr5dWGrHTUWrM,1317 +pygame/imageext.cpython-38-x86_64-linux-gnu.so,sha256=k4fjRL7SzEi51wx98Ejzpldxd45y0CUIvHIBHYl2AAM,51344 +pygame/joystick.cpython-38-x86_64-linux-gnu.so,sha256=hg6ZoKVAoWzyXFhcsLJCo4hBQBfg4Cf8NCJQ-QKb_hE,40440 +pygame/joystick.pyi,sha256=OSudzqdY_e9yfsddxK5F6A7n0mMoYXO3a6WuYxtzapc,1032 +pygame/key.cpython-38-x86_64-linux-gnu.so,sha256=1VrfluQ8u8Je3abpuOHOZytcFs4nBswQgqcYm17R7Js,48088 +pygame/key.pyi,sha256=axWlbWi-noOlfXKx8gO3w9ZTlY6BghTSjTxfdTA_4yk,502 +pygame/locals.py,sha256=20tMfZH9ByKbxjHd3WIrF98FH2ZH6WMv8DetCxXZNR0,11638 +pygame/macosx.py,sha256=sZuU-urA2snMkjlody2cl2dkEGudSCoJfen2YenBRPs,399 +pygame/mask.cpython-38-x86_64-linux-gnu.so,sha256=cyoVelOiUVaLV_O70xin5TU0e4lHuMj5LjGeBlBayis,88016 +pygame/mask.pyi,sha256=MFj5YpcgBE5PyPbsOaxBG1vhhKaCY1iUlLDp_0l6l80,2328 +pygame/math.cpython-38-x86_64-linux-gnu.so,sha256=bz1nqeyi3HYZ92HidKwoiBEArhEy3eOqMOdZOqyWZg8,108352 +pygame/math.pyi,sha256=2k_qesYS32Iu3QEuV216XUMc4nIheXk5cNJQo-4WL8w,10394 +pygame/midi.py,sha256=iZIlXZrPMPXUrwVU1dpbq8gH0-9BMH1DBt8WPQmytRM,24398 +pygame/midi.pyi,sha256=SR2ye2EixdqdKGdSuoWLoSZbvuxsOLrkvpzS1nfvLDQ,1780 +pygame/mixer.cpython-38-x86_64-linux-gnu.so,sha256=fxOiwQcaoGYzBy4mB7VJdIicNfnpQAyCIrtPatHkhcE,72296 +pygame/mixer.pyi,sha256=4686BQHhEqmPwEiybnMd0Aa003SSuZrG3jpP-skbea8,2522 +pygame/mixer_music.cpython-38-x86_64-linux-gnu.so,sha256=GsD7uB9nq-cBADFJyOwKFz8zRrC4uGtI_7V8_kEH6Uk,44600 +pygame/mouse.cpython-38-x86_64-linux-gnu.so,sha256=XI9KQzqEMrqUatR-911NSCr-PgAnqH_JpuNp14vHIRY,36016 +pygame/mouse.pyi,sha256=0qUx45nLXk7k_kTLwHYWht7JrN7KF4ZXoO3YsMEmQTQ,1050 +pygame/music.pyi,sha256=WFKWboTgVmNsI-4j2dj9Z3OsyHrVoKMtu5IMfonQcvg,694 +pygame/newbuffer.cpython-38-x86_64-linux-gnu.so,sha256=v20gsidSs5vqPCts3ZnCEilW58MwLk-v5_o1PirutbA,39664 +pygame/pixelarray.cpython-38-x86_64-linux-gnu.so,sha256=8f-34-EFyC7vtMmUVyFRbBTniZCjSXyIAGj0yeR-3kA,65488 +pygame/pixelarray.pyi,sha256=YrDhSQfAVlBq3a9gVRLsPnKhKym640mH1Amtc2phHts,949 +pygame/pixelcopy.cpython-38-x86_64-linux-gnu.so,sha256=t1lVX2uui8qro0L0__s8DrG5Q7bZEbBCfhNZNaMuDlY,43464 +pygame/pixelcopy.pyi,sha256=ihu_zbReupXhpCRVcrkz13FEzLOwkPfwVs7ko6zBxr8,545 +pygame/pkgdata.py,sha256=0soLxo1AmhV06EZ8k72T0tirZQIsX_54hLGD7OAPr5k,2445 +pygame/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pygame/pygame.ico,sha256=PBF9cw0Ca9Rw2pNmDD3iXvcYYQeI9ZzZ9vxtRLQRoJc,145516 +pygame/pygame_icon.bmp,sha256=Twnby8nv4HMhGka49n-47CPseDvwrSLZ0l1o9U2Bb5s,630 +pygame/pygame_icon.icns,sha256=4jwAo9VtMhTK9hq9ibt6MZ4_sd2VsZueWZ3YAMuTPgw,264148 +pygame/pygame_icon_mac.bmp,sha256=QrAs3kEF6v7wVMlIJgktI90aUdTg9RdTmp16d2HZhNg,262282 +pygame/pypm.cpython-38-x86_64-linux-gnu.so,sha256=T2T9p5QYmPecquvTnVm9z_4Y5vQz3q_zi56JCmYKrSw,158840 +pygame/rect.cpython-38-x86_64-linux-gnu.so,sha256=hV0ZF6-0a19suKbz9eeGSU4lKh0ESXEnTPU_AvhX0UA,68944 +pygame/rect.pyi,sha256=B6MtasUsjTQCmijkaZa1CBFdZBgCFE8H6bCBBegco2g,7989 +pygame/rwobject.cpython-38-x86_64-linux-gnu.so,sha256=SD2p_sZtTxNcJjBmSLbYFWlnlVEkYooAK9kchvXbvBk,34480 +pygame/scrap.cpython-38-x86_64-linux-gnu.so,sha256=1mk2jUbTDlBLgEemxH31yj-6mUay2hBUcKe8xpWrjzU,30424 +pygame/scrap.pyi,sha256=iBei8wQYnp2UcZ01YyOblEKzkhpWyKr1--NkrTb5vZk,312 +pygame/sndarray.py,sha256=hJMTnQBEbOBYzeBRdCiGHCr3S20NqpHVevX2WFxw51I,4083 +pygame/sndarray.pyi,sha256=-kUmCShhKSazBRhHberq4kDhQau0I-02LZHvfIVYi7A,337 +pygame/sprite.py,sha256=SrMpwUJ9wpXYupD1vrWx7qrGXNw49gZw2L7JSkJ2dEo,61450 +pygame/sprite.pyi,sha256=a_7QqTKtfrcN5iIKzIK2bbvG1vohzhGVp00xVw5JHJI,5259 +pygame/surface.cpython-38-x86_64-linux-gnu.so,sha256=qRTqYgTEhIRTEegn4w-HK-4x6tlCaENv7jKVnP9U37o,268840 +pygame/surface.pyi,sha256=2Yaz8WLA1HSO3Exu1jfn3cy0gehzsUzhfbgZjxoN1P0,4540 +pygame/surfarray.py,sha256=4Beyg1cQUaFmvf7VgddTsUOr-nT89YvZ6Dwv_oer9yU,14424 +pygame/surfarray.pyi,sha256=9mkysuFQ3set-H6Fwc2lurpEfUcjpStn7DbaiuJfZ-4,1111 +pygame/surflock.cpython-38-x86_64-linux-gnu.so,sha256=6paK_WnwGlMe0Tn06ZktMzSZ5pJE1ls8c812Vprag-E,26920 +pygame/sysfont.py,sha256=VCQ1YR-wUC0RylnRIw7vPojp_QI7X7btBV2bRUfUcn4,16053 +pygame/tests/__init__.py,sha256=wfUhz-LZF-OXZNT81UfGdofNYPCUMJoF3nwgXpzg4sE,1251 +pygame/tests/__main__.py,sha256=B9PuRd67whv9v1qnCzeIlFzc4R-_9kGj6xwKAS3F8_w,3826 +pygame/tests/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/__pycache__/__main__.cpython-38.pyc,, +pygame/tests/__pycache__/base_test.cpython-38.pyc,, +pygame/tests/__pycache__/blit_test.cpython-38.pyc,, +pygame/tests/__pycache__/bufferproxy_test.cpython-38.pyc,, +pygame/tests/__pycache__/camera_test.cpython-38.pyc,, +pygame/tests/__pycache__/color_test.cpython-38.pyc,, +pygame/tests/__pycache__/constants_test.cpython-38.pyc,, +pygame/tests/__pycache__/controller_test.cpython-38.pyc,, +pygame/tests/__pycache__/cursors_test.cpython-38.pyc,, +pygame/tests/__pycache__/display_test.cpython-38.pyc,, +pygame/tests/__pycache__/docs_test.cpython-38.pyc,, +pygame/tests/__pycache__/draw_test.cpython-38.pyc,, +pygame/tests/__pycache__/event_test.cpython-38.pyc,, +pygame/tests/__pycache__/font_test.cpython-38.pyc,, +pygame/tests/__pycache__/freetype_tags.cpython-38.pyc,, +pygame/tests/__pycache__/freetype_test.cpython-38.pyc,, +pygame/tests/__pycache__/ftfont_tags.cpython-38.pyc,, +pygame/tests/__pycache__/ftfont_test.cpython-38.pyc,, +pygame/tests/__pycache__/gfxdraw_test.cpython-38.pyc,, +pygame/tests/__pycache__/image__save_gl_surface_test.cpython-38.pyc,, +pygame/tests/__pycache__/image_tags.cpython-38.pyc,, +pygame/tests/__pycache__/image_test.cpython-38.pyc,, +pygame/tests/__pycache__/imageext_tags.cpython-38.pyc,, +pygame/tests/__pycache__/imageext_test.cpython-38.pyc,, +pygame/tests/__pycache__/joystick_test.cpython-38.pyc,, +pygame/tests/__pycache__/key_test.cpython-38.pyc,, +pygame/tests/__pycache__/mask_test.cpython-38.pyc,, +pygame/tests/__pycache__/math_test.cpython-38.pyc,, +pygame/tests/__pycache__/midi_test.cpython-38.pyc,, +pygame/tests/__pycache__/mixer_music_tags.cpython-38.pyc,, +pygame/tests/__pycache__/mixer_music_test.cpython-38.pyc,, +pygame/tests/__pycache__/mixer_tags.cpython-38.pyc,, +pygame/tests/__pycache__/mixer_test.cpython-38.pyc,, +pygame/tests/__pycache__/mouse_test.cpython-38.pyc,, +pygame/tests/__pycache__/pixelarray_test.cpython-38.pyc,, +pygame/tests/__pycache__/pixelcopy_test.cpython-38.pyc,, +pygame/tests/__pycache__/rect_test.cpython-38.pyc,, +pygame/tests/__pycache__/rwobject_test.cpython-38.pyc,, +pygame/tests/__pycache__/scrap_tags.cpython-38.pyc,, +pygame/tests/__pycache__/scrap_test.cpython-38.pyc,, +pygame/tests/__pycache__/sndarray_tags.cpython-38.pyc,, +pygame/tests/__pycache__/sndarray_test.cpython-38.pyc,, +pygame/tests/__pycache__/sprite_test.cpython-38.pyc,, +pygame/tests/__pycache__/surface_test.cpython-38.pyc,, +pygame/tests/__pycache__/surfarray_tags.cpython-38.pyc,, +pygame/tests/__pycache__/surfarray_test.cpython-38.pyc,, +pygame/tests/__pycache__/surflock_test.cpython-38.pyc,, +pygame/tests/__pycache__/sysfont_test.cpython-38.pyc,, +pygame/tests/__pycache__/test_test_.cpython-38.pyc,, +pygame/tests/__pycache__/threads_test.cpython-38.pyc,, +pygame/tests/__pycache__/time_test.cpython-38.pyc,, +pygame/tests/__pycache__/touch_test.cpython-38.pyc,, +pygame/tests/__pycache__/transform_test.cpython-38.pyc,, +pygame/tests/__pycache__/version_test.cpython-38.pyc,, +pygame/tests/__pycache__/video_test.cpython-38.pyc,, +pygame/tests/base_test.py,sha256=gIAQG4pAY5RbpVq6tpeQMg1Jc3ewL133SIYtgh5hhkY,22634 +pygame/tests/blit_test.py,sha256=YmrU8GAJEuMFCPpDZ7jRjP7ArUCaz1_TiPmjjoA40OE,4740 +pygame/tests/bufferproxy_test.py,sha256=EroM5MVP_mKxC64fFJjPc5WPrmQYx8CA5v48DbD4zPY,16511 +pygame/tests/camera_test.py,sha256=KdGG1pLTe8Eh2SHiEsH7DuLqwn-OsaLCHm4MGCKPAvg,70 +pygame/tests/color_test.py,sha256=avlBC0X2KM2uFiyv_ZBM-3Hh4b5AL0xbBh6Bz9n_s2Y,47441 +pygame/tests/constants_test.py,sha256=t3xGL86Lt5YvYxx7ooH_taZfQBvC5UL4bBp_KdXuu4Q,9516 +pygame/tests/controller_test.py,sha256=a6NSWn1k1Rpt4HTYeOAbF-cDx0KfnKsrG6DwA0vk_0k,10834 +pygame/tests/cursors_test.py,sha256=NyQ2B9I5VJXwPS-VZfd2HNPJKHUhIZDxGw6GRtuHWWA,7700 +pygame/tests/display_test.py,sha256=_9t187FcrghjskhLLWWWi3rzcMm-DxMWxSxthAvLEOQ,29009 +pygame/tests/docs_test.py,sha256=r2qa_ox8eg2_Y5Pb9-XzZExAhqJrdVMO30guO1o7PPM,1091 +pygame/tests/draw_test.py,sha256=sS3dxb_Z0n-EAeFEZlRUO2Mhl3ppQmByzBvnjk46SYc,237007 +pygame/tests/event_test.py,sha256=5KRBJQXlrkEO7NlIMVlMIeYRpIGYRRyi_rDmyETWpAM,28625 +pygame/tests/fixtures/fonts/A_PyGameMono-8.png,sha256=QmhReADwKrzW5RWnG1KHEtZIqpVtwWzhXmydX1su10c,92 +pygame/tests/fixtures/fonts/PyGameMono-18-100dpi.bdf,sha256=nm3okxnfAFtADlp7s2AY43zS49NYg9jq7GVzG2lPhOQ,1947 +pygame/tests/fixtures/fonts/PyGameMono-18-75dpi.bdf,sha256=4kB0uYeEpa3W-ZAomFMpc0hD-h6FnOh2m5IPi6xzfds,1648 +pygame/tests/fixtures/fonts/PyGameMono-8.bdf,sha256=aK0KV-_osDPTPiA1BUCgZHOmufy6J9Vh5pf1IAi0_yg,1365 +pygame/tests/fixtures/fonts/PyGameMono.otf,sha256=_Af4LyMEgKKGa8jDlfik89axhLc3HoS8aG5JHWN5sZw,3128 +pygame/tests/fixtures/fonts/test_fixed.otf,sha256=FWHmFsQUobgtbm370Y5XJv1lAokTreGR5fo4tuw3Who,58464 +pygame/tests/fixtures/fonts/test_sans.ttf,sha256=nrZ6FRet4dwlvA7xOReYCP2QwyGebk0iVJaSFbtpOhM,133088 +pygame/tests/fixtures/fonts/u13079_PyGameMono-8.png,sha256=x_D28PW8aKed8ZHBK6AISEZ9vlEV76Whi770ItTuFVU,89 +pygame/tests/fixtures/xbm_cursors/white_sizing.xbm,sha256=VLAS1A417T-Vg6GMsmicUCYpOhvGsrgJJYUvdFYYteY,366 +pygame/tests/fixtures/xbm_cursors/white_sizing_mask.xbm,sha256=CKQeiOtlFoJdAts83UmTEeVk-3pxgJ9Wu2QJaCjzAQM,391 +pygame/tests/font_test.py,sha256=pD2uulSNvBMejK_gpqbdVz-zDXWQWJr8BUAw4W_l0sQ,22605 +pygame/tests/freetype_tags.py,sha256=NdjMDSYHfrhopKR0JuTeUfFX-AbcCu4fsXnS1a46iVM,182 +pygame/tests/freetype_test.py,sha256=z3un9cZ53PedVy5NUIWoduudXlNUxuslHL8lxxvTuK4,64428 +pygame/tests/ftfont_tags.py,sha256=IvteBUDEp4rv9q6FwlTpQ9X2px-XUromSMQ921VrhCU,180 +pygame/tests/ftfont_test.py,sha256=-VB-0kdWCDTWVvIf4bRTjrvjvTBZDbfgYZyl20pd2SI,523 +pygame/tests/gfxdraw_test.py,sha256=HI3NMPdA2qhKmva0CzL0ORveInHEvgemjZ7evvRLGFY,32375 +pygame/tests/image__save_gl_surface_test.py,sha256=5H8TeGZNRZzu5kJInWPe8AuuKqHv-utunadoBmn--CI,1198 +pygame/tests/image_tags.py,sha256=_WJGXgTOaUn4IG7fIk1sDKfDDZP3W8N6PkrrOpPT-U8,132 +pygame/tests/image_test.py,sha256=cpWnFpqn5-IitDfNaJunHWgd64Mxcxnt-51LTnXkoJY,37989 +pygame/tests/imageext_tags.py,sha256=-vnXr7O5F1NVrEDrOHBEYdaD-JiuBT9NI-lxGps-K1U,135 +pygame/tests/imageext_test.py,sha256=6HYyo1dBkOhfJgo8I_YkKxmeHYHHoYO8WF-2aVUL3uw,2889 +pygame/tests/joystick_test.py,sha256=yH8HxUvnZwq3lEnxBDJ7ObDbdDlB8ph5DqO-mqj19Sg,6168 +pygame/tests/key_test.py,sha256=Jrza338ku2Wr3nyYm_IM-fF9JGeMSsNXsXB0844CbVI,4171 +pygame/tests/mask_test.py,sha256=VqBVCT-h3HSo-szDIUrtLhswIYyqToGoi5mNB-bij-g,246451 +pygame/tests/math_test.py,sha256=lEEQkuM15kgYD3RspkGFAZFj1FSrIjskuUp2JAlV_hQ,87928 +pygame/tests/midi_test.py,sha256=4AX1VUn0JsamgQrH2c11H44qucayulbcwNUuOxNZkOU,16914 +pygame/tests/mixer_music_tags.py,sha256=o0gsQDjuICFYw8j3WOlIluwk9fdA42ledU1U6DIJzNU,138 +pygame/tests/mixer_music_test.py,sha256=rc7L55A8851lZOaAv6Dht_XNHyq1F3hVdAoJoLcJKeY,13312 +pygame/tests/mixer_tags.py,sha256=qKcn8AD46H3V87xONG0iXlGH_FveeGBgf2gE1MMh2s0,132 +pygame/tests/mixer_test.py,sha256=9pKcARpHFF3PrW8aaaTGBHk1sKk-XqX7xxAm-SRnlg8,43923 +pygame/tests/mouse_test.py,sha256=AcMoPwO4O56_UQcioyaKS43kX3p4_e2addYXskqsvzY,13149 +pygame/tests/pixelarray_test.py,sha256=UEgev8LnH4x-JMFwtC3v5-6hYfmevUb4QfDKPUfz7AE,62428 +pygame/tests/pixelcopy_test.py,sha256=9HwB0wcSBEzjlBh8-xLSemlomn6CrhXOPTOZyk0egSo,25561 +pygame/tests/rect_test.py,sha256=oHS1V9MNV2IiNXS8evZ0ul68Qqo8SB8ju20RSANqjQM,75529 +pygame/tests/run_tests__tests/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/__pycache__/run_tests__test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/all_ok/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__pycache__/fake_3_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__pycache__/fake_4_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__pycache__/fake_5_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__pycache__/fake_6_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__pycache__/no_assertions__ret_code_of_1__test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/__pycache__/zero_tests_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/all_ok/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/all_ok/fake_3_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/all_ok/fake_4_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/all_ok/fake_5_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/all_ok/fake_6_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/all_ok/no_assertions__ret_code_of_1__test.py,sha256=PNrfACCpcPnO964Oxv2-9l4ciuJ-Iqw3x8HDs-kebVg,797 +pygame/tests/run_tests__tests/all_ok/zero_tests_test.py,sha256=XzLaMjkygsvNkFEqnRU9y2Ijm6bfds9n5Z6mg_LOMJQ,545 +pygame/tests/run_tests__tests/everything/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/everything/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/everything/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/everything/__pycache__/incomplete_todo_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/everything/__pycache__/magic_tag_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/everything/__pycache__/sleep_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/everything/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/everything/incomplete_todo_test.py,sha256=71myeZtFerYY2rB-j60l5Ltz3FiRCuOR4evFXtJHC34,909 +pygame/tests/run_tests__tests/everything/magic_tag_test.py,sha256=SjIKB_7aLfGdih8cotQ34m1KbSEII_1wGQUBwrWeIyY,859 +pygame/tests/run_tests__tests/everything/sleep_test.py,sha256=AyGwZk5fQAkfeCr9VewdsuD_z5BzlVfkmbZD-XetB50,715 +pygame/tests/run_tests__tests/exclude/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/exclude/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/exclude/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/exclude/__pycache__/invisible_tag_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/exclude/__pycache__/magic_tag_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/exclude/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/exclude/invisible_tag_test.py,sha256=AdHFvOK-kCRi2iUs68So6Ngef6C_LEdx3QpMLjhKtmM,925 +pygame/tests/run_tests__tests/exclude/magic_tag_test.py,sha256=SjIKB_7aLfGdih8cotQ34m1KbSEII_1wGQUBwrWeIyY,859 +pygame/tests/run_tests__tests/failures1/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/failures1/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/failures1/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/failures1/__pycache__/fake_3_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/failures1/__pycache__/fake_4_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/failures1/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/failures1/fake_3_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/failures1/fake_4_test.py,sha256=xWpIVUpzevSs4bVeze48Q9jkZzss4szdw6eMOrJnZV8,949 +pygame/tests/run_tests__tests/incomplete/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/incomplete/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/incomplete/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/incomplete/__pycache__/fake_3_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/incomplete/fake_2_test.py,sha256=RVUuQZxqYScIUAflNIsXd7UE6Rxm6HHFZSi8cpz5m-k,889 +pygame/tests/run_tests__tests/incomplete/fake_3_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/incomplete_todo/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/incomplete_todo/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_3_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/incomplete_todo/fake_2_test.py,sha256=71myeZtFerYY2rB-j60l5Ltz3FiRCuOR4evFXtJHC34,909 +pygame/tests/run_tests__tests/incomplete_todo/fake_3_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/infinite_loop/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/infinite_loop/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_1_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/infinite_loop/fake_1_test.py,sha256=rNt-VaNziz7OmfbDXcbXbDIbwC_6ScFJ-MtenMjR68Y,906 +pygame/tests/run_tests__tests/infinite_loop/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/print_stderr/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/print_stderr/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_3_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_4_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stderr/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/print_stderr/fake_3_test.py,sha256=6AGEff135DU_spRhZ09oDGXE4lZC3dlHU_phnfOyWYY,954 +pygame/tests/run_tests__tests/print_stderr/fake_4_test.py,sha256=xWpIVUpzevSs4bVeze48Q9jkZzss4szdw6eMOrJnZV8,949 +pygame/tests/run_tests__tests/print_stdout/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/print_stdout/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_3_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_4_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/print_stdout/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/print_stdout/fake_3_test.py,sha256=cruYqrh3O3MQ8fczEFloLpsrQrYmMOd6jgxMU6e5H8w,1012 +pygame/tests/run_tests__tests/print_stdout/fake_4_test.py,sha256=xWpIVUpzevSs4bVeze48Q9jkZzss4szdw6eMOrJnZV8,949 +pygame/tests/run_tests__tests/run_tests__test.py,sha256=oM-sBRUtv5QkCJ49H-LtuSLIWsX_u0_f8LeVDczZqN8,4342 +pygame/tests/run_tests__tests/timeout/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8 +pygame/tests/run_tests__tests/timeout/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/run_tests__tests/timeout/__pycache__/fake_2_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/timeout/__pycache__/sleep_test.cpython-38.pyc,, +pygame/tests/run_tests__tests/timeout/fake_2_test.py,sha256=zFUNsDLmH9Pvpo-YEpvfW2raQyA6EL_BW3CuP10YIKU,899 +pygame/tests/run_tests__tests/timeout/sleep_test.py,sha256=5EDW4U6kYN4QIid0IgHBypJ3T3a78pILILF41DPpujk,716 +pygame/tests/rwobject_test.py,sha256=LAJun6obwHADEiONe6F68WNM9qAuL7i4hqbPErjmox4,4323 +pygame/tests/scrap_tags.py,sha256=zHyLWy2JRyfw0DamlH9dz-MZq2R2uOryjH9JRu-RCkw,671 +pygame/tests/scrap_test.py,sha256=qt47IQLTs3jqf8FEP1mBC8gX6SzbeLsjJnIdszMhbYU,9160 +pygame/tests/sndarray_tags.py,sha256=ThDQxqGFaAembuWgdYGsFSWEppVezgXJ2htYRvvDaXE,190 +pygame/tests/sndarray_test.py,sha256=TGqnR0acDPEIEsKZv9nSDRxCNFDpx3RZix-GSth13J4,6297 +pygame/tests/sprite_test.py,sha256=cpH70OV5xVzaqexIl03RFJGLSdzzZiSTEM-EJoPOWM4,46638 +pygame/tests/surface_test.py,sha256=mhZm0WwNu4jxXuMD-ientJiX50ysPjd_fwkCYKvxj8g,163734 +pygame/tests/surfarray_tags.py,sha256=AwlglKM7DrjHvvcSMm-yXb-PSxsVhJkS6VE7Z8wOhes,260 +pygame/tests/surfarray_test.py,sha256=p7cV1-ND6aO47T9917QckFQ7yuXBd1-GdPHYsDDPg0U,25768 +pygame/tests/surflock_test.py,sha256=dMZkzND7-R_z-GaxN4ZIcpNtW3PsK1i7kdnizpU21UY,4728 +pygame/tests/sysfont_test.py,sha256=uf7ISqCvoiegyUvYTqOT1-kYQi_POeejL1TqXXeYF9Q,1463 +pygame/tests/test_test_.py,sha256=2LXEtnUdSV5f_vU-SvIYka21ANoN8o3BlyfKDNohPYs,21 +pygame/tests/test_utils/__init__.py,sha256=NOlEmfNGF1o7X0vZ_oUvVvV7twigUP-m4gWYOvEytyA,5371 +pygame/tests/test_utils/__pycache__/__init__.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/arrinter.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/async_sub.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/buftools.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/endian.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/png.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/run_tests.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/test_machinery.cpython-38.pyc,, +pygame/tests/test_utils/__pycache__/test_runner.cpython-38.pyc,, +pygame/tests/test_utils/arrinter.py,sha256=pIG0XmkGBzkXGeZxuNuBrn0AzgH7zG3FLbkK8ITLKIc,14843 +pygame/tests/test_utils/async_sub.py,sha256=3uoy70d7BBH27Jq3QZr3WrYoMoXSHh4hnQiKfBQiFL0,9128 +pygame/tests/test_utils/buftools.py,sha256=4jm6Cm4M0KNgdunooqvXfLQrdWbnPdglJUs_IA97-4A,23706 +pygame/tests/test_utils/endian.py,sha256=Rc7rl38YamHgi8EzB92Muu8C4XH6yltH9f5On7qfMpY,495 +pygame/tests/test_utils/png.py,sha256=zyc67s1HBUgeiuf3UUKhXmZWrEFXIAKuWs2FJjrjEgg,152382 +pygame/tests/test_utils/run_tests.py,sha256=vzxjrZZ3E_jCBjhrUDeLrR3inXYRjmYIf7gVwS0oGqA,12049 +pygame/tests/test_utils/test_machinery.py,sha256=WcbORRvQPnKXvNWq10tqD92xjVoSIUhdieEh2ozTQ1c,2483 +pygame/tests/test_utils/test_runner.py,sha256=-hrK4ZI2EXx7dMKjW3ZNDXOXiLcWPoicS-Hd0NBqY1U,9434 +pygame/tests/threads_test.py,sha256=nAu5n4xHfIG2DRAFKV8ZttQLu1akW8kqo1avoUXqlUI,7842 +pygame/tests/time_test.py,sha256=iyBWHYLbFvO3Lo12xVRspcs_9zhzNDmFG7dO50Q79nE,15336 +pygame/tests/touch_test.py,sha256=7XiB-3ywji23u8tqjDMrZ4QFNGdfmCjVN3buiDTkXic,3223 +pygame/tests/transform_test.py,sha256=-07Y0v6kEEwbtwQzmiV8vaUJPfoaszWkB7TcR1AmnfI,49044 +pygame/tests/version_test.py,sha256=dvNIneFf1c4PAKa4xo1YLAlYRDFYL8_ZeUwI1FS55Is,1536 +pygame/tests/video_test.py,sha256=TvH2fuzpNkSkhuOsB7k_jqfDlA-DGqRc9fM4k_IEcBs,696 +pygame/threads/__init__.py,sha256=KW19d_4mfBXMZuf1s2o9J6O2rdvEP1QjlgU5QkRUfW8,8074 +pygame/threads/__pycache__/__init__.cpython-38.pyc,, +pygame/time.cpython-38-x86_64-linux-gnu.so,sha256=aOH_fxcn6ytRJFg3a9XhcO3RTyZ5puXBESbegea-foc,34400 +pygame/time.pyi,sha256=BnGVQeO1cedoAucxR-HIkCwf9abC6HPlNOaVi2K8YVc,484 +pygame/transform.cpython-38-x86_64-linux-gnu.so,sha256=GZFExHEDR1GQj8w7vsSqzKRQaa-2UUVSUU1mSkV0fCM,84040 +pygame/transform.pyi,sha256=_ALjGSYNIIaR4BUKfrHhBkQ-jZPEVwPAVjTF_v0IJ7Q,1551 +pygame/version.py,sha256=fPfxlpQwZlAERiQQbsYEP5ZMxLb4HNA6lkxP5y7uOHM,2454 +pygame/version.pyi,sha256=kHMtNQTsvLjrGnbCum1RPdNJgJ66DaN7e8n-phr7LQE,403 diff --git a/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/REQUESTED b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/WHEEL b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/WHEEL new file mode 100644 index 0000000..32bdea0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: false +Tag: cp38-cp38-manylinux_2_17_x86_64 +Tag: cp38-cp38-manylinux2014_x86_64 + diff --git a/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/entry_points.txt b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/entry_points.txt new file mode 100644 index 0000000..e5a477e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[pyinstaller40] +hook-dirs = pygame.__pyinstaller:get_hook_dirs + diff --git a/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/top_level.txt b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/top_level.txt new file mode 100644 index 0000000..0cb7ff1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame-2.1.2.dist-info/top_level.txt @@ -0,0 +1 @@ +pygame diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libFLAC-1889cd2f.so.8.3.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libFLAC-1889cd2f.so.8.3.0 new file mode 100755 index 0000000..491ea27 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libFLAC-1889cd2f.so.8.3.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2-2-302c83d0.0.so.0.16.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2-2-302c83d0.0.so.0.16.0 new file mode 100755 index 0000000..7801975 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2-2-302c83d0.0.so.0.16.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_image-2-ca388ea9.0.so.0.2.3 b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_image-2-ca388ea9.0.so.0.2.3 new file mode 100755 index 0000000..191f25a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_image-2-ca388ea9.0.so.0.2.3 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_mixer-2-d009651f.0.so.0.2.2 b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_mixer-2-d009651f.0.so.0.2.2 new file mode 100755 index 0000000..d05a8f2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_mixer-2-d009651f.0.so.0.2.2 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_ttf-2-cc75fba4.0.so.0.14.1 b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_ttf-2-cc75fba4.0.so.0.14.1 new file mode 100755 index 0000000..8dbcb13 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libSDL2_ttf-2-cc75fba4.0.so.0.14.1 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libasound-a3e880f2.so.2.0.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libasound-a3e880f2.so.2.0.0 new file mode 100755 index 0000000..a96f038 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libasound-a3e880f2.so.2.0.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libattr-4f2a9577.so.1.1.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libattr-4f2a9577.so.1.1.0 new file mode 100755 index 0000000..277382c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libattr-4f2a9577.so.1.1.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libbz2-a273e504.so.1.0.6 b/.venv/lib/python3.8/site-packages/pygame.libs/libbz2-a273e504.so.1.0.6 new file mode 100755 index 0000000..46173ef Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libbz2-a273e504.so.1.0.6 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libcap-79733b59.so.2.22 b/.venv/lib/python3.8/site-packages/pygame.libs/libcap-79733b59.so.2.22 new file mode 100755 index 0000000..2ff5b15 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libcap-79733b59.so.2.22 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libdbus-1-5d678da9.so.3.14.14 b/.venv/lib/python3.8/site-packages/pygame.libs/libdbus-1-5d678da9.so.3.14.14 new file mode 100755 index 0000000..3605db9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libdbus-1-5d678da9.so.3.14.14 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libdw-0-780627ce.176.so b/.venv/lib/python3.8/site-packages/pygame.libs/libdw-0-780627ce.176.so new file mode 100755 index 0000000..b56276a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libdw-0-780627ce.176.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libelf-0-3d9de92a.176.so b/.venv/lib/python3.8/site-packages/pygame.libs/libelf-0-3d9de92a.176.so new file mode 100755 index 0000000..797e11f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libelf-0-3d9de92a.176.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libfluidsynth-1a88b3a9.so.3.0.3 b/.venv/lib/python3.8/site-packages/pygame.libs/libfluidsynth-1a88b3a9.so.3.0.3 new file mode 100755 index 0000000..52fc217 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libfluidsynth-1a88b3a9.so.3.0.3 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libfreetype-9936d4a5.so.6.18.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libfreetype-9936d4a5.so.6.18.0 new file mode 100755 index 0000000..e515274 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libfreetype-9936d4a5.so.6.18.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libgcrypt-18957bae.so.11.8.2 b/.venv/lib/python3.8/site-packages/pygame.libs/libgcrypt-18957bae.so.11.8.2 new file mode 100755 index 0000000..c19bf8e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libgcrypt-18957bae.so.11.8.2 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libgomp-a34b3233.so.1.0.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libgomp-a34b3233.so.1.0.0 new file mode 100755 index 0000000..fc93efc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libgomp-a34b3233.so.1.0.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libgpg-error-3f09c3c7.so.0.10.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libgpg-error-3f09c3c7.so.0.10.0 new file mode 100755 index 0000000..eb86e67 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libgpg-error-3f09c3c7.so.0.10.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libjpeg-988b02a7.so.62.3.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libjpeg-988b02a7.so.62.3.0 new file mode 100755 index 0000000..e23276b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libjpeg-988b02a7.so.62.3.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/liblz4-af1653fb.so.1.8.3 b/.venv/lib/python3.8/site-packages/pygame.libs/liblz4-af1653fb.so.1.8.3 new file mode 100755 index 0000000..1fd26d0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/liblz4-af1653fb.so.1.8.3 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/liblzma-07b5b5c8.so.5.2.2 b/.venv/lib/python3.8/site-packages/pygame.libs/liblzma-07b5b5c8.so.5.2.2 new file mode 100755 index 0000000..c0713dc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/liblzma-07b5b5c8.so.5.2.2 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libmikmod-6af46a87.so.3.0.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libmikmod-6af46a87.so.3.0.0 new file mode 100755 index 0000000..5446bdb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libmikmod-6af46a87.so.3.0.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libmodplug-c335b2c6.so.1.0.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libmodplug-c335b2c6.so.1.0.0 new file mode 100755 index 0000000..f9f467c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libmodplug-c335b2c6.so.1.0.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libmpg123-e4fca779.so.0.46.3 b/.venv/lib/python3.8/site-packages/pygame.libs/libmpg123-e4fca779.so.0.46.3 new file mode 100755 index 0000000..543c644 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libmpg123-e4fca779.so.0.46.3 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libogg-04548ab3.so.0.8.5 b/.venv/lib/python3.8/site-packages/pygame.libs/libogg-04548ab3.so.0.8.5 new file mode 100755 index 0000000..fd8de5c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libogg-04548ab3.so.0.8.5 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libopus-06b786df.so.0.8.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libopus-06b786df.so.0.8.0 new file mode 100755 index 0000000..9c07d50 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libopus-06b786df.so.0.8.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libpcre-9513aab5.so.1.2.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libpcre-9513aab5.so.1.2.0 new file mode 100755 index 0000000..9c8c5f5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libpcre-9513aab5.so.1.2.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libpng16-67f87fff.so.16.37.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libpng16-67f87fff.so.16.37.0 new file mode 100755 index 0000000..07d866d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libpng16-67f87fff.so.16.37.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libportmidi-fbdafcc9.so b/.venv/lib/python3.8/site-packages/pygame.libs/libportmidi-fbdafcc9.so new file mode 100755 index 0000000..e3be13b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libportmidi-fbdafcc9.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libpulse-76af7338.so.0.23.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libpulse-76af7338.so.0.23.0 new file mode 100755 index 0000000..fd3da3f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libpulse-76af7338.so.0.23.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libpulse-simple-9065ac8d.so.0.1.1 b/.venv/lib/python3.8/site-packages/pygame.libs/libpulse-simple-9065ac8d.so.0.1.1 new file mode 100755 index 0000000..7d79257 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libpulse-simple-9065ac8d.so.0.1.1 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libpulsecommon-14-e93e2477.0.so b/.venv/lib/python3.8/site-packages/pygame.libs/libpulsecommon-14-e93e2477.0.so new file mode 100755 index 0000000..dfe8b70 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libpulsecommon-14-e93e2477.0.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libselinux-0922c95c.so.1 b/.venv/lib/python3.8/site-packages/pygame.libs/libselinux-0922c95c.so.1 new file mode 100755 index 0000000..171b836 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libselinux-0922c95c.so.1 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libsndfile-72e2e06b.so.1.0.31 b/.venv/lib/python3.8/site-packages/pygame.libs/libsndfile-72e2e06b.so.1.0.31 new file mode 100755 index 0000000..651b6f1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libsndfile-72e2e06b.so.1.0.31 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libsystemd-c4cac0a4.so.0.6.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libsystemd-c4cac0a4.so.0.6.0 new file mode 100755 index 0000000..2b731fe Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libsystemd-c4cac0a4.so.0.6.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libtiff-8d84a4e4.so.5.7.0 b/.venv/lib/python3.8/site-packages/pygame.libs/libtiff-8d84a4e4.so.5.7.0 new file mode 100755 index 0000000..aead33a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libtiff-8d84a4e4.so.5.7.0 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libvorbis-3a6647c4.so.0.4.9 b/.venv/lib/python3.8/site-packages/pygame.libs/libvorbis-3a6647c4.so.0.4.9 new file mode 100755 index 0000000..cc72639 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libvorbis-3a6647c4.so.0.4.9 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libvorbisenc-f97181cf.so.2.0.12 b/.venv/lib/python3.8/site-packages/pygame.libs/libvorbisenc-f97181cf.so.2.0.12 new file mode 100755 index 0000000..147eee1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libvorbisenc-f97181cf.so.2.0.12 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libvorbisfile-b464aa2e.so.3.3.8 b/.venv/lib/python3.8/site-packages/pygame.libs/libvorbisfile-b464aa2e.so.3.3.8 new file mode 100755 index 0000000..3246ce6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libvorbisfile-b464aa2e.so.3.3.8 differ diff --git a/.venv/lib/python3.8/site-packages/pygame.libs/libwebp-c8fa709c.so.7.1.2 b/.venv/lib/python3.8/site-packages/pygame.libs/libwebp-c8fa709c.so.7.1.2 new file mode 100755 index 0000000..be914b2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame.libs/libwebp-c8fa709c.so.7.1.2 differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__init__.py b/.venv/lib/python3.8/site-packages/pygame/__init__.py new file mode 100644 index 0000000..d488d67 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/__init__.py @@ -0,0 +1,371 @@ +# coding: ascii +# pygame - Python Game Library +# Copyright (C) 2000-2001 Pete Shinners +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Pete Shinners +# pete@shinners.org +"""Pygame is a set of Python modules designed for writing games. +It is written on top of the excellent SDL library. This allows you +to create fully featured games and multimedia programs in the python +language. The package is highly portable, with games running on +Windows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux.""" + +import sys +import os + +# Choose Windows display driver +if os.name == "nt": + # pypy does not find the dlls, so we add package folder to PATH. + pygame_dir = os.path.split(__file__)[0] + os.environ["PATH"] = os.environ["PATH"] + ";" + pygame_dir + +# when running under X11, always set the SDL window WM_CLASS to make the +# window managers correctly match the pygame window. +elif "DISPLAY" in os.environ and "SDL_VIDEO_X11_WMCLASS" not in os.environ: + os.environ["SDL_VIDEO_X11_WMCLASS"] = os.path.basename(sys.argv[0]) + + +class MissingModule: + _NOT_IMPLEMENTED_ = True + + def __init__(self, name, urgent=0): + self.name = name + exc_type, exc_msg = sys.exc_info()[:2] + self.info = str(exc_msg) + self.reason = f"{exc_type.__name__}: {self.info}" + self.urgent = urgent + if urgent: + self.warn() + + def __getattr__(self, var): + if not self.urgent: + self.warn() + self.urgent = 1 + missing_msg = f"{self.name} module not available ({self.reason})" + raise NotImplementedError(missing_msg) + + def __nonzero__(self): + return False + + __bool__ = __nonzero__ + + def warn(self): + msg_type = "import" if self.urgent else "use" + message = f"{msg_type} {self.name}: {self.info}\n({self.reason})" + try: + import warnings + + level = 4 if self.urgent else 3 + warnings.warn(message, RuntimeWarning, level) + except ImportError: + print(message) + + +# we need to import like this, each at a time. the cleanest way to import +# our modules is with the import command (not the __import__ function) +# isort: skip_file + +# first, the "required" modules +from pygame.base import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +from pygame.constants import * # now has __all__ pylint: disable=wildcard-import; lgtm[py/polluting-import] +from pygame.version import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +from pygame.rect import Rect +from pygame.rwobject import encode_string, encode_file_path +import pygame.surflock +import pygame.color + +Color = pygame.color.Color +import pygame.bufferproxy + +BufferProxy = pygame.bufferproxy.BufferProxy +import pygame.math + +Vector2 = pygame.math.Vector2 +Vector3 = pygame.math.Vector3 + +__version__ = ver + +# next, the "standard" modules +# we still allow them to be missing for stripped down pygame distributions +if get_sdl_version() < (2, 0, 0): + # cdrom only available for SDL 1.2.X + try: + import pygame.cdrom + except (ImportError, IOError): + cdrom = MissingModule("cdrom", urgent=1) + +try: + import pygame.display +except (ImportError, IOError): + display = MissingModule("display", urgent=1) + +try: + import pygame.draw +except (ImportError, IOError): + draw = MissingModule("draw", urgent=1) + +try: + import pygame.event +except (ImportError, IOError): + event = MissingModule("event", urgent=1) + +try: + import pygame.image +except (ImportError, IOError): + image = MissingModule("image", urgent=1) + +try: + import pygame.joystick +except (ImportError, IOError): + joystick = MissingModule("joystick", urgent=1) + +try: + import pygame.key +except (ImportError, IOError): + key = MissingModule("key", urgent=1) + +try: + import pygame.mouse +except (ImportError, IOError): + mouse = MissingModule("mouse", urgent=1) + +try: + import pygame.cursors + from pygame.cursors import Cursor +except (ImportError, IOError): + cursors = MissingModule("cursors", urgent=1) + Cursor = lambda: Missing_Function + +try: + import pygame.sprite +except (ImportError, IOError): + sprite = MissingModule("sprite", urgent=1) + +try: + import pygame.threads +except (ImportError, IOError): + threads = MissingModule("threads", urgent=1) + +try: + import pygame.pixelcopy +except (ImportError, IOError): + pixelcopy = MissingModule("pixelcopy", urgent=1) + + +def warn_unwanted_files(): + """warn about unneeded old files""" + + # a temporary hack to warn about camera.so and camera.pyd. + install_path = os.path.split(pygame.base.__file__)[0] + extension_ext = os.path.splitext(pygame.base.__file__)[1] + + # here are the .so/.pyd files we need to ask to remove. + ext_to_remove = ["camera"] + + # here are the .py/.pyo/.pyc files we need to ask to remove. + py_to_remove = ["color"] + + # Don't warn on Symbian. The color.py is used as a wrapper. + if os.name == "e32": + py_to_remove = [] + + # See if any of the files are there. + extension_files = [f"{x}{extension_ext}" for x in ext_to_remove] + + py_files = [ + f"{x}{py_ext}" for py_ext in [".py", ".pyc", ".pyo"] for x in py_to_remove + ] + + files = py_files + extension_files + + unwanted_files = [] + for f in files: + unwanted_files.append(os.path.join(install_path, f)) + + ask_remove = [] + for f in unwanted_files: + if os.path.exists(f): + ask_remove.append(f) + + if ask_remove: + message = "Detected old file(s). Please remove the old files:\n" + message += " ".join(ask_remove) + message += "\nLeaving them there might break pygame. Cheers!\n\n" + + try: + import warnings + + level = 4 + warnings.warn(message, RuntimeWarning, level) + except ImportError: + print(message) + + +# disable, because we hopefully don't need it. +# warn_unwanted_files() + + +try: + from pygame.surface import Surface, SurfaceType +except (ImportError, IOError): + Surface = lambda: Missing_Function + +try: + import pygame.mask + from pygame.mask import Mask +except (ImportError, IOError): + mask = MissingModule("mask", urgent=0) + Mask = lambda: Missing_Function + +try: + from pygame.pixelarray import PixelArray +except (ImportError, IOError): + PixelArray = lambda: Missing_Function + +try: + from pygame.overlay import Overlay +except (ImportError, IOError): + Overlay = lambda: Missing_Function + +try: + import pygame.time +except (ImportError, IOError): + time = MissingModule("time", urgent=1) + +try: + import pygame.transform +except (ImportError, IOError): + transform = MissingModule("transform", urgent=1) + +# lastly, the "optional" pygame modules +if "PYGAME_FREETYPE" in os.environ: + try: + import pygame.ftfont as font + + sys.modules["pygame.font"] = font + except (ImportError, IOError): + pass +try: + import pygame.font + import pygame.sysfont + + pygame.font.SysFont = pygame.sysfont.SysFont + pygame.font.get_fonts = pygame.sysfont.get_fonts + pygame.font.match_font = pygame.sysfont.match_font +except (ImportError, IOError): + font = MissingModule("font", urgent=0) + +# try and load pygame.mixer_music before mixer, for py2app... +try: + import pygame.mixer_music + + # del pygame.mixer_music + # print ("NOTE2: failed importing pygame.mixer_music in lib/__init__.py") +except (ImportError, IOError): + pass + +try: + import pygame.mixer +except (ImportError, IOError): + mixer = MissingModule("mixer", urgent=0) + +# always fails, but MissingModule needs an exception to process +try: + import pygame.movie +except (ImportError, IOError): + movie = MissingModule("movie", urgent=0) + +try: + import pygame.scrap +except (ImportError, IOError): + scrap = MissingModule("scrap", urgent=0) + +try: + import pygame.surfarray +except (ImportError, IOError): + surfarray = MissingModule("surfarray", urgent=0) + +try: + import pygame.sndarray +except (ImportError, IOError): + sndarray = MissingModule("sndarray", urgent=0) + +try: + import pygame.fastevent +except (ImportError, IOError): + fastevent = MissingModule("fastevent", urgent=0) + +# there's also a couple "internal" modules not needed +# by users, but putting them here helps "dependency finder" +# programs get everything they need (like py2exe) +try: + import pygame.imageext + + del pygame.imageext +except (ImportError, IOError): + pass + + +def packager_imports(): + """some additional imports that py2app/py2exe will want to see""" + import atexit + import numpy + import OpenGL.GL + import pygame.macosx + import pygame.colordict + + +# make Rects pickleable + +import copyreg + + +def __rect_constructor(x, y, w, h): + return Rect(x, y, w, h) + + +def __rect_reduce(r): + assert isinstance(r, Rect) + return __rect_constructor, (r.x, r.y, r.w, r.h) + + +copyreg.pickle(Rect, __rect_reduce, __rect_constructor) + + +# make Colors pickleable +def __color_constructor(r, g, b, a): + return Color(r, g, b, a) + + +def __color_reduce(c): + assert isinstance(c, Color) + return __color_constructor, (c.r, c.g, c.b, c.a) + + +copyreg.pickle(Color, __color_reduce, __color_constructor) + +# Thanks for supporting pygame. Without support now, there won't be pygame later. +if "PYGAME_HIDE_SUPPORT_PROMPT" not in os.environ: + print( + "pygame {} (SDL {}.{}.{}, Python {}.{}.{})".format( # pylint: disable=consider-using-f-string + ver, *get_sdl_version() + sys.version_info[0:3] + ) + ) + print("Hello from the pygame community. https://www.pygame.org/contribute.html") + +# cleanup namespace +del pygame, os, sys, MissingModule, copyreg diff --git a/.venv/lib/python3.8/site-packages/pygame/__init__.pyi b/.venv/lib/python3.8/site-packages/pygame/__init__.pyi new file mode 100644 index 0000000..9c1bc7f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/__init__.pyi @@ -0,0 +1,74 @@ +from typing import Any, Tuple, Callable, Optional, overload, Type + +# Re-export modules as members; see PEP 484 for stub export rules. + +# Most useful stuff +from pygame.constants import * +from pygame import surface as surface +from pygame import rect as rect +from pygame import color as color +from pygame import event as event +from pygame import draw as draw +from pygame import display as display +from pygame import font as font +from pygame import image as image +from pygame import key as key +from pygame import mixer as mixer +from pygame import mouse as mouse +from pygame import time as time +from pygame import version as version + +# Advanced stuff +from pygame import cursors as cursors +from pygame import joystick as joystick +from pygame import mask as mask +from pygame import sprite as sprite +from pygame import transform as transform +from pygame import bufferproxy as bufferproxy +from pygame import pixelarray as pixelarray +from pygame import pixelcopy as pixelcopy +from pygame import sndarray as sndarray +from pygame import surfarray as surfarray +from pygame import math as math +from pygame import fastevent as fastevent + +# Other +from pygame import scrap as scrap + +from ._common import _AnyPath + +# These classes are auto imported with pygame, so I put their declaration here +class Rect(rect.Rect): ... +class Surface(surface.Surface): ... +class Color(color.Color): ... +class PixelArray(pixelarray.PixelArray): ... +class Vector2(math.Vector2): ... +class Vector3(math.Vector3): ... +class Cursor(cursors.Cursor): ... + +def init() -> Tuple[int, int]: ... +def quit() -> None: ... +def get_init() -> bool: ... + +class error(RuntimeError): ... + +def get_error() -> str: ... +def set_error(error_msg: str) -> None: ... +def get_sdl_version() -> Tuple[int, int, int]: ... +def get_sdl_byteorder() -> int: ... +def encode_string( + obj: Optional[_AnyPath], + encoding: Optional[str] = "unicode_escape", + errors: Optional[str] = "backslashreplace", + etype: Optional[Type[Exception]] = UnicodeEncodeError, +) -> bytes: ... +@overload +def encode_file_path( + obj: Optional[_AnyPath], etype: Optional[Type[Exception]] = UnicodeEncodeError +) -> bytes: ... +@overload +def encode_file_path( + obj: Any, etype: Optional[Type[Exception]] = UnicodeEncodeError +) -> bytes: ... +def register_quit(callable: Callable[[], Any]) -> None: ... +def __getattr__(name: str) -> Any: ... # don't error on missing stubs diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..abdbd72 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/_camera_opencv.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/_camera_opencv.cpython-38.pyc new file mode 100644 index 0000000..3da0383 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/_camera_opencv.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/_camera_vidcapture.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/_camera_vidcapture.cpython-38.pyc new file mode 100644 index 0000000..36522b7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/_camera_vidcapture.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/camera.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/camera.cpython-38.pyc new file mode 100644 index 0000000..ff2078e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/camera.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/colordict.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/colordict.cpython-38.pyc new file mode 100644 index 0000000..0878999 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/colordict.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/cursors.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/cursors.cpython-38.pyc new file mode 100644 index 0000000..4cc9436 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/cursors.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/draw_py.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/draw_py.cpython-38.pyc new file mode 100644 index 0000000..5e2b676 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/draw_py.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/fastevent.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/fastevent.cpython-38.pyc new file mode 100644 index 0000000..4a9ff72 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/fastevent.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/freetype.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/freetype.cpython-38.pyc new file mode 100644 index 0000000..1827470 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/freetype.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/ftfont.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/ftfont.cpython-38.pyc new file mode 100644 index 0000000..6132705 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/ftfont.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/locals.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/locals.cpython-38.pyc new file mode 100644 index 0000000..5492185 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/locals.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/macosx.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/macosx.cpython-38.pyc new file mode 100644 index 0000000..08d3a64 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/macosx.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/midi.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/midi.cpython-38.pyc new file mode 100644 index 0000000..a4aa67a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/midi.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/pkgdata.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/pkgdata.cpython-38.pyc new file mode 100644 index 0000000..c3d0a9a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/pkgdata.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/sndarray.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/sndarray.cpython-38.pyc new file mode 100644 index 0000000..45243e7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/sndarray.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/sprite.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/sprite.cpython-38.pyc new file mode 100644 index 0000000..0a55035 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/sprite.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/surfarray.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/surfarray.cpython-38.pyc new file mode 100644 index 0000000..b37c884 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/surfarray.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/sysfont.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/sysfont.cpython-38.pyc new file mode 100644 index 0000000..1a01c9a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/sysfont.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pycache__/version.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pycache__/version.cpython-38.pyc new file mode 100644 index 0000000..d9ff6b2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pycache__/version.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__init__.py b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__init__.py new file mode 100644 index 0000000..1c52aad --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__init__.py @@ -0,0 +1,5 @@ +import os + + +def get_hook_dirs(): + return [os.path.dirname(__file__)] diff --git a/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..a14da42 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__pycache__/hook-pygame.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__pycache__/hook-pygame.cpython-38.pyc new file mode 100644 index 0000000..6d8e922 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/__pycache__/hook-pygame.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/hook-pygame.py b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/hook-pygame.py new file mode 100644 index 0000000..64ba71b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/__pyinstaller/hook-pygame.py @@ -0,0 +1,44 @@ +""" +binaries hook for pygame seems to be required for pygame 2.0 Windows. +Otherwise some essential DLLs will not be transfered to the exe. + +And also put hooks for datas, resources that pygame uses, to work +correctly with pyinstaller +""" + +import os +import platform + +from pygame import __file__ as pygame_main_file + +# Get pygame's folder +pygame_folder = os.path.dirname(os.path.abspath(pygame_main_file)) + +# datas is the variable that pyinstaller looks for while processing hooks +datas = [] + +# A helper to append the relative path of a resource to hook variable - datas +def _append_to_datas(file_path): + res_path = os.path.join(pygame_folder, file_path) + if os.path.exists(res_path): + datas.append((res_path, "pygame")) + + +# First append the font file, then based on the OS, append pygame icon file +_append_to_datas("freesansbold.ttf") +if platform.system() == "Darwin": + _append_to_datas("pygame_icon_mac.bmp") +else: + _append_to_datas("pygame_icon.bmp") + +if platform.system() == "Windows": + from PyInstaller.utils.hooks import collect_dynamic_libs + + pre_binaries = collect_dynamic_libs("pygame") + binaries = [] + + for b in pre_binaries: + binary, location = b + # settles all the DLLs into the top level folder, which prevents duplication + # with the DLLs already being put there. + binaries.append((binary, ".")) diff --git a/.venv/lib/python3.8/site-packages/pygame/_camera.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_camera.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..d2b4a88 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_camera.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_camera_opencv.py b/.venv/lib/python3.8/site-packages/pygame/_camera_opencv.py new file mode 100644 index 0000000..a16a086 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_camera_opencv.py @@ -0,0 +1,174 @@ +import numpy +import cv2 +import time + +import pygame + + +def list_cameras(): + return [0] + + +def list_cameras_darwin(): + import subprocess + from xml.etree import ElementTree + + # pylint: disable=consider-using-with + flout, _ = subprocess.Popen( + "system_profiler -xml SPCameraDataType", + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ).communicate() + + last_text = None + cameras = [] + + for node in ElementTree.fromstring(flout).iterfind("./array/dict/array/dict/*"): + if last_text == "_name": + cameras.append(node.text) + last_text = node.text + + return cameras + + +class Camera(object): + def __init__(self, device=0, size=(640, 480), mode="RGB"): + self._device_index = device + self._size = size + + if mode == "RGB": + self._fmt = cv2.COLOR_BGR2RGB + elif mode == "YUV": + self._fmt = cv2.COLOR_BGR2YUV + elif mode == "HSV": + self._fmt = cv2.COLOR_BGR2HSV + else: + raise ValueError("Not a supported mode") + + self._open = False + + # all of this could have been done in the constructor, but creating + # the VideoCapture is very time consuming, so it makes more sense in the + # actual start() method + def start(self): + if self._open: + return + + self._cam = cv2.VideoCapture(self._device_index) + + if not self._cam.isOpened(): + raise ValueError("Could not open camera.") + + self._cam.set(cv2.CAP_PROP_FRAME_WIDTH, self._size[0]) + self._cam.set(cv2.CAP_PROP_FRAME_HEIGHT, self._size[1]) + + w = self._cam.get(cv2.CAP_PROP_FRAME_WIDTH) + h = self._cam.get(cv2.CAP_PROP_FRAME_HEIGHT) + self._size = (int(w), int(h)) + + self._flipx = False + self._flipy = False + self._brightness = 1 + + self._frametime = 1 / self._cam.get(cv2.CAP_PROP_FPS) + self._last_frame_time = 0 + + self._open = True + + def stop(self): + if self._open: + self._cam.release() + self._cam = None + self._open = False + + def _check_open(self): + if not self._open: + raise pygame.error("Camera must be started") + + def get_size(self): + self._check_open() + + return self._size + + def set_controls(self, hflip=None, vflip=None, brightness=None): + self._check_open() + + if hflip is not None: + self._flipx = bool(hflip) + if vflip is not None: + self._flipy = bool(vflip) + if brightness is not None: + self._cam.set(cv2.CAP_PROP_BRIGHTNESS, brightness) + + return self.get_controls() + + def get_controls(self): + self._check_open() + + return (self._flipx, self._flipy, self._cam.get(cv2.CAP_PROP_BRIGHTNESS)) + + def query_image(self): + self._check_open() + + current_time = time.time() + if current_time - self._last_frame_time > self._frametime: + return True + return False + + def get_image(self, dest_surf=None): + self._check_open() + + self._last_frame_time = time.time() + + _, image = self._cam.read() + + image = cv2.cvtColor(image, self._fmt) + + flip_code = None + if self._flipx: + if self._flipy: + flip_code = -1 + else: + flip_code = 1 + elif self._flipy: + flip_code = 0 + + if flip_code is not None: + image = cv2.flip(image, flip_code) + + image = numpy.fliplr(image) + image = numpy.rot90(image) + + surf = pygame.surfarray.make_surface(image) + + if dest_surf: + dest_surf.blit(surf, (0, 0)) + return dest_surf + + return surf + + def get_raw(self): + self._check_open() + + self._last_frame_time = time.time() + + _, image = self._cam.read() + + return image.tobytes() + + +class CameraMac(Camera): + def __init__(self, device=0, size=(640, 480), mode="RGB"): + + if isinstance(device, int): + _dev = device + elif isinstance(device, str): + _dev = list_cameras_darwin().index(device) + else: + raise TypeError( + "OpenCV-Mac backend can take device indices or names, ints or strings, not ", + str(type(device)), + ) + + super().__init__(_dev, size, mode) diff --git a/.venv/lib/python3.8/site-packages/pygame/_camera_vidcapture.py b/.venv/lib/python3.8/site-packages/pygame/_camera_vidcapture.py new file mode 100644 index 0000000..56df43d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_camera_vidcapture.py @@ -0,0 +1,117 @@ +"""pygame.camera.Camera implementation using the videocapture module for windows. + +http://videocapture.sourceforge.net/ + +Binary windows wheels: + https://www.lfd.uci.edu/~gohlke/pythonlibs/#videocapture +""" +import pygame + + +def list_cameras(): + """Always only lists one camera. + + Functionality not supported in videocapture module. + """ + return [0] + + # this just cycles through all the cameras trying to open them + # cameras = [] + # for x in range(256): + # try: + # c = Camera(x) + # except: + # break + # cameras.append(x) + # return cameras + + +def init(): + global vidcap + try: + import vidcap as vc + except ImportError: + from VideoCapture import vidcap as vc + vidcap = vc + + +def quit(): + global vidcap + vidcap = None + + +class Camera: + # pylint: disable=unused-argument + def __init__(self, device=0, size=(640, 480), mode="RGB", show_video_window=0): + """device: VideoCapture enumerates the available video capture devices + on your system. If you have more than one device, specify + the desired one here. The device number starts from 0. + + show_video_window: 0 ... do not display a video window (the default) + 1 ... display a video window + + Mainly used for debugging, since the video window + can not be closed or moved around. + """ + self.dev = vidcap.new_Dev(device, show_video_window) + width, height = size + self.dev.setresolution(width, height) + + def display_capture_filter_properties(self): + """Displays a dialog containing the property page of the capture filter. + + For VfW drivers you may find the option to select the resolution most + likely here. + """ + self.dev.displaycapturefilterproperties() + + def display_capture_pin_properties(self): + """Displays a dialog containing the property page of the capture pin. + + For WDM drivers you may find the option to select the resolution most + likely here. + """ + self.dev.displaycapturepinproperties() + + def set_resolution(self, width, height): + """Sets the capture resolution. (without dialog)""" + self.dev.setresolution(width, height) + + def get_buffer(self): + """Returns a string containing the raw pixel data.""" + return self.dev.getbuffer() + + def start(self): + """Not implemented.""" + + def set_controls(self, **kwargs): + """Not implemented.""" + + def stop(self): + """Not implemented.""" + + def get_image(self, dest_surf=None): + """ """ + return self.get_surface(dest_surf) + + def get_surface(self, dest_surf=None): + """Returns a pygame Surface.""" + abuffer, width, height = self.get_buffer() + if not abuffer: + return None + surf = pygame.image.frombuffer(abuffer, (width, height), "BGR") + surf = pygame.transform.flip(surf, 0, 1) + # if there is a destination surface given, we blit onto that. + if dest_surf: + dest_surf.blit(surf, (0, 0)) + else: + dest_surf = surf + return dest_surf + + +if __name__ == "__main__": + import pygame.examples.camera + + pygame.camera.Camera = Camera + pygame.camera.list_cameras = list_cameras + pygame.examples.camera.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/_common.pyi b/.venv/lib/python3.8/site-packages/pygame/_common.pyi new file mode 100644 index 0000000..f4084ba --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_common.pyi @@ -0,0 +1,34 @@ +from os import PathLike +from typing import IO, List, Sequence, Tuple, Union + +from typing_extensions import Protocol + +from pygame.color import Color +from pygame.math import Vector2 +from pygame.rect import Rect + +# For functions that take a file name +_AnyPath = Union[str, bytes, PathLike[str], PathLike[bytes]] + +# Most pygame functions that take a file argument should be able to handle +# a _FileArg type +_FileArg = Union[_AnyPath, IO[bytes], IO[str]] + +_Coordinate = Union[Tuple[float, float], Sequence[float], Vector2] + +# This typehint is used when a function would return an RGBA tuble +_RgbaOutput = Tuple[int, int, int, int] +_ColorValue = Union[Color, int, str, Tuple[int, int, int], List[int], _RgbaOutput] + +_CanBeRect = Union[ + Rect, + Tuple[int, int, int, int], + List[int], + Tuple[_Coordinate, _Coordinate], + List[_Coordinate], +] + +class _HasRectAttribute(Protocol): + rect: _CanBeRect + +_RectValue = Union[_CanBeRect, _HasRectAttribute] diff --git a/.venv/lib/python3.8/site-packages/pygame/_freetype.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_freetype.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..374c2b7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_freetype.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/__init__.py b/.venv/lib/python3.8/site-packages/pygame/_sdl2/__init__.py new file mode 100644 index 0000000..b08b716 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_sdl2/__init__.py @@ -0,0 +1,3 @@ +from .sdl2 import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +from .audio import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +from .video import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/__init__.pyi b/.venv/lib/python3.8/site-packages/pygame/_sdl2/__init__.pyi new file mode 100644 index 0000000..0106556 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_sdl2/__init__.pyi @@ -0,0 +1 @@ +from pygame._sdl2.video import * diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/_sdl2/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..2eefdc6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sdl2/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/audio.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_sdl2/audio.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..cddb000 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sdl2/audio.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/controller.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_sdl2/controller.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..b4064f3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sdl2/controller.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/controller.pyi b/.venv/lib/python3.8/site-packages/pygame/_sdl2/controller.pyi new file mode 100644 index 0000000..5437b6b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_sdl2/controller.pyi @@ -0,0 +1,30 @@ +from typing import Dict, Mapping, Optional + +from pygame.joystick import Joystick + +def init() -> None: ... +def get_init() -> bool: ... +def quit() -> None: ... +def set_eventstate(state: bool) -> None: ... +def get_eventstate() -> bool: ... +def get_count() -> int: ... +def is_controller(index: int) -> bool: ... +def name_forindex(index: int) -> Optional[str]: ... + +class Controller: + def __init__(self, index: int) -> None: ... + def init(self) -> None: ... + def get_init(self) -> bool: ... + def quit(self) -> None: ... + @staticmethod + def from_joystick(joy: Joystick) -> Controller: ... + def attached(self) -> bool: ... + def as_joystick(self) -> Joystick: ... + def get_axis(self, axis: int) -> int: ... + def get_button(self, button: int) -> bool: ... + def get_mapping(self) -> Dict[str, str]: ... + def set_mapping(self, mapping: Mapping[str, str]) -> int: ... + def rumble( + self, low_frequency: float, high_frequency: float, duration: int + ) -> bool: ... + def stop_rumble(self) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/mixer.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_sdl2/mixer.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..a5d9a9f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sdl2/mixer.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/sdl2.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_sdl2/sdl2.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..53497fa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sdl2/sdl2.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/touch.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_sdl2/touch.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..d9de7ef Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sdl2/touch.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/touch.pyi b/.venv/lib/python3.8/site-packages/pygame/_sdl2/touch.pyi new file mode 100644 index 0000000..93e2745 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_sdl2/touch.pyi @@ -0,0 +1,6 @@ +from typing import Dict, Union + +def get_num_devices() -> int: ... +def get_device(index: int) -> int: ... +def get_num_fingers(device_id: int) -> int: ... +def get_finger(touchid: int, index: int) -> Dict[str, Union[int, float]]: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/video.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_sdl2/video.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..8146044 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sdl2/video.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/_sdl2/video.pyi b/.venv/lib/python3.8/site-packages/pygame/_sdl2/video.pyi new file mode 100644 index 0000000..706dd42 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/_sdl2/video.pyi @@ -0,0 +1,154 @@ +from typing import Any, Generator, Iterable, Optional, Tuple, Union + +from pygame.color import Color +from pygame.rect import Rect +from pygame.surface import Surface + +from .._common import _CanBeRect + +WINDOWPOS_UNDEFINED: int +WINDOWPOS_CENTERED: int + +MESSAGEBOX_ERROR: int +MESSAGEBOX_WARNING: int +MESSAGEBOX_INFORMATION: int + +class RendererDriverInfo: + name: str + flags: int + num_texture_formats: int + max_texture_width: int + max_texture_height: int + +def get_drivers() -> Generator[RendererDriverInfo, None, None]: ... +def get_grabbed_window() -> Optional[Window]: ... +def messagebox( + title: str, + message: str, + window: Optional[Window] = None, + info: bool = False, + warn: bool = False, + error: bool = False, + buttons: Tuple[str, ...] = ("OK",), + return_button: int = 0, + escape_button: int = 0, +) -> int: ... + +class Window: + def __init__( + self, + title: str = "pygame", + size: Iterable[int] = (640, 480), + position: Optional[Iterable[int]] = None, + fullscreen: bool = False, + fullscreen_desktop: bool = False, + **kwargs: bool + ) -> None: ... + @staticmethod + def from_display_module() -> Window: ... + grab: bool + relative_mouse: bool + def set_windowed(self) -> None: ... + def set_fullscreen(self, desktop: bool = False) -> None: ... + title: str + def destroy(self) -> None: ... + def hide(self) -> None: ... + def show(self) -> None: ... + def focus(self, input_only: bool = False) -> None: ... + def restore(self) -> None: ... + def maximize(self) -> None: ... + def minimize(self) -> None: ... + resizable: bool + borderless: bool + def set_icon(self, surface: Surface) -> None: ... + id: int + size: Iterable[int] + position: Union[int, Iterable[int]] + opacity: float + brightness: float + display_index: int + def set_modal_for(self, Window) -> None: ... + +class Texture: + def __init__( + self, + renderer: Renderer, + size: Iterable[int], + static: bool = False, + streaming: bool = False, + target: bool = False, + ) -> None: ... + @staticmethod + def from_surface(renderer: Renderer, surface: Surface) -> Texture: ... + renderer: Renderer + width: int + height: int + alpha: int + blend_mode: int + color: Color + def get_rect(self, **kwargs: Any) -> Rect: ... + def draw( + self, + srcrect: Optional[_CanBeRect] = None, + dstrect: Optional[Union[_CanBeRect, Iterable[int]]] = None, + angle: int = 0, + origin: Optional[Iterable[int]] = None, + flipX: bool = False, + flipY: bool = False, + ) -> None: ... + def update(self, surface: Surface, area: Optional[_CanBeRect] = None) -> None: ... + +class Image: + def __init__( + self, + textureOrImage: Union[Texture, Image], + srcrect: Optional[_CanBeRect] = None, + ) -> None: ... + def get_rect(self, **kwargs: Any) -> Rect: ... + def draw( + self, srcrect: Optional[_CanBeRect] = None, dstrect: Optional[_CanBeRect] = None + ) -> None: ... + angle: float + origin: Optional[Iterable[float]] + flipX: bool + flipY: bool + color: Color + alpha: float + blend_mode: int + texture: Texture + srcrect: Rect + +class Renderer: + def __init__( + self, + window: Window, + index: int = -1, + accelerated: int = -1, + vsync: bool = False, + target_texture: bool = False, + ) -> None: ... + @staticmethod + def from_window(window: Window) -> Renderer: ... + draw_blend_mode: int + draw_color: Color + def clear(self) -> None: ... + def present(self) -> None: ... + def get_viewport(self) -> Rect: ... + def set_viewport(self, area: Optional[_CanBeRect]) -> None: ... + logical_size: Iterable[int] + scale: Iterable[float] + target: Union[Texture, None] + def blit( + self, + source: Union[Texture, Image], + dest: Optional[_CanBeRect] = None, + area: Optional[_CanBeRect] = None, + special_flags: int = 0, + ) -> Rect: ... + def draw_line(self, p1: Iterable[int], p2: Iterable[int]) -> None: ... + def draw_point(self, point: Iterable[int]) -> None: ... + def draw_rect(self, rect: _CanBeRect) -> None: ... + def fill_rect(self, rect: _CanBeRect) -> None: ... + def to_surface( + self, surface: Optional[Surface] = None, area: Optional[_CanBeRect] = None + ) -> Surface: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/_sprite.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/_sprite.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..a79dc73 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/_sprite.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/base.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/base.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..dfd756e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/base.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/bufferproxy.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/bufferproxy.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..8caee5a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/bufferproxy.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/bufferproxy.pyi b/.venv/lib/python3.8/site-packages/pygame/bufferproxy.pyi new file mode 100644 index 0000000..44e7b47 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/bufferproxy.pyi @@ -0,0 +1,10 @@ +from typing import Any, overload + +class BufferProxy(object): + parent: Any + length: int + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, parent: Any) -> None: ... + def write(self, buffer: bytes, offset: int = 0) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/camera.py b/.venv/lib/python3.8/site-packages/pygame/camera.py new file mode 100644 index 0000000..f4174a5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/camera.py @@ -0,0 +1,194 @@ +import os +import sys +import platform +import warnings +from abc import ABC, abstractmethod + +_is_init = 0 + + +def _setup_opencv_mac(): + global list_cameras, Camera, colorspace + + from pygame import _camera_opencv + + try: + from pygame import _camera + except ImportError: + _camera = None + + list_cameras = _camera_opencv.list_cameras_darwin + Camera = _camera_opencv.CameraMac + if _camera: + colorspace = _camera.colorspace + + +def _setup_opencv(): + global list_cameras, Camera, colorspace + + from pygame import _camera_opencv + + try: + from pygame import _camera + except ImportError: + _camera = None + + list_cameras = _camera_opencv.list_cameras + Camera = _camera_opencv.Camera + if _camera: + colorspace = _camera.colorspace + + +def _setup__camera(): + global list_cameras, Camera, colorspace + + from pygame import _camera + + list_cameras = _camera.list_cameras + Camera = _camera.Camera + colorspace = _camera.colorspace + + +def _setup_vidcapture(): + global list_cameras, Camera, colorspace + + from pygame import _camera_vidcapture + + try: + from pygame import _camera + except ImportError: + _camera = None + + warnings.warn( + "The VideoCapture backend is not recommended and may be removed." + "For Python3 and Windows 8+, there is now a native Windows backend built into pygame.", + DeprecationWarning, + stacklevel=2, + ) + + _camera_vidcapture.init() + list_cameras = _camera_vidcapture.list_cameras + Camera = _camera_vidcapture.Camera + if _camera: + colorspace = _camera.colorspace + + +def get_backends(): + possible_backends = [] + + if sys.platform == "win32" and int(platform.win32_ver()[0]) > 8: + possible_backends.append("_camera (MSMF)") + + if "linux" in sys.platform: + possible_backends.append("_camera (V4L2)") + + if "darwin" in sys.platform: + possible_backends.append("OpenCV-Mac") + + possible_backends.append("OpenCV") + + if sys.platform == "win32": + possible_backends.append("VidCapture") + + # see if we have any user specified defaults in environments. + camera_env = os.environ.get("PYGAME_CAMERA", "") + if camera_env == "opencv": # prioritize opencv + if "OpenCV" in possible_backends: + possible_backends.remove("OpenCV") + possible_backends = ["OpenCV"] + possible_backends + if camera_env == "vidcapture": # prioritize vidcapture + if "VideoCapture" in possible_backends: + possible_backends.remove("VideoCapture") + possible_backends = ["VideoCapture"] + possible_backends + + return possible_backends + + +backend_table = { + "opencv-mac": _setup_opencv_mac, + "opencv": _setup_opencv, + "_camera (msmf)": _setup__camera, + "_camera (v4l2)": _setup__camera, + "videocapture": _setup_vidcapture, +} + + +def init(backend=None): + global _is_init + # select the camera module to import here. + + backends = get_backends() + + if not backends: + _is_init = 1 + return + + backends = [b.lower() for b in backends] + + if not backend: + backend = backends[0] + else: + backend = backend.lower() + + if backend not in backend_table: + raise ValueError("unrecognized backend name") + + if backend not in backends: + warnings.warn( + "We don't think this is a supported backend on this system, but we'll try it...", + Warning, + stacklevel=2, + ) + + backend_table[backend]() + + _is_init = 1 + + +def quit(): + global _is_init + _is_init = 0 + + +class AbstractCamera(ABC): + @abstractmethod + def __init__(self, device=0, size=(320, 200), mode="RGB"): + """ """ + + @abstractmethod + def set_resolution(self, width, height): + """Sets the capture resolution. (without dialog)""" + + @abstractmethod + def start(self): + """ """ + + @abstractmethod + def stop(self): + """ """ + + @abstractmethod + def get_buffer(self): + """ """ + + @abstractmethod + def set_controls(self, **kwargs): + """ """ + + @abstractmethod + def get_image(self, dest_surf=None): + """ """ + + @abstractmethod + def get_surface(self, dest_surf=None): + """ """ + + +if __name__ == "__main__": + + # try and use this camera stuff with the pygame camera example. + import pygame.examples.camera + + # pygame.camera.Camera = Camera + # pygame.camera.list_cameras = list_cameras + pygame.examples.camera.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/camera.pyi b/.venv/lib/python3.8/site-packages/pygame/camera.pyi new file mode 100644 index 0000000..ba9a051 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/camera.pyi @@ -0,0 +1,29 @@ +from typing import List, Optional, Tuple, Union + +from pygame.surface import Surface + +def get_backends() -> List[str]: ... +def init(backend: Optional[str]) -> None: ... +def quit() -> None: ... +def list_cameras() -> List[str]: ... + +class Camera: + def __init__( + self, + device: str, + size: Union[Tuple[int, int], List[int]] = (640, 480), + format: str = "RGB", + ) -> None: ... + def start(self) -> None: ... + def stop(self) -> None: ... + def get_controls(self) -> Tuple[bool, bool, int]: ... + def set_controls( + self, + hflip: bool = ..., + vflip: bool = ..., + brightness: int = ..., + ) -> Tuple[bool, bool, int]: ... + def get_size(self) -> Tuple[int, int]: ... + def query_image(self) -> bool: ... + def get_image(self, surface: Optional[Surface] = None) -> Surface: ... + def get_raw(self) -> bytes: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/color.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/color.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..c1253fd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/color.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/color.pyi b/.venv/lib/python3.8/site-packages/pygame/color.pyi new file mode 100644 index 0000000..8af8f3f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/color.pyi @@ -0,0 +1,40 @@ +from typing import Tuple, overload + +from ._common import _ColorValue + +class Color: + r: int + g: int + b: int + a: int + cmy: Tuple[float, float, float] + hsva: Tuple[float, float, float, float] + hsla: Tuple[float, float, float, float] + i1i2i3: Tuple[float, float, float] + __hash__: None # type: ignore + @overload + def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ... + @overload + def __init__(self, rgbvalue: _ColorValue) -> None: ... + @overload + def __getitem__(self, i: int) -> int: ... + @overload + def __getitem__(self, s: slice) -> Tuple[int]: ... + def __setitem__(self, key: int, value: int) -> None: ... + def __add__(self, other: Color) -> Color: ... + def __sub__(self, other: Color) -> Color: ... + def __mul__(self, other: Color) -> Color: ... + def __floordiv__(self, other: Color) -> Color: ... + def __mod__(self, other: Color) -> Color: ... + def __int__(self) -> int: ... + def __float__(self) -> float: ... + def __len__(self) -> int: ... + def normalize(self) -> Tuple[float, float, float, float]: ... + def correct_gamma(self, gamma: float) -> Color: ... + def set_length(self, length: int) -> None: ... + def lerp(self, color: _ColorValue, amount: float) -> Color: ... + def premul_alpha(self) -> Color: ... + @overload + def update(self, r: int, g: int, b: int, a: int = 255) -> None: ... + @overload + def update(self, rgbvalue: _ColorValue) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/colordict.py b/.venv/lib/python3.8/site-packages/pygame/colordict.py new file mode 100644 index 0000000..0bd7256 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/colordict.py @@ -0,0 +1,692 @@ +# pygame - Python Game Library +# Copyright (C) 2000-2003 Pete Shinners +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Pete Shinners +# pete@shinners.org + +""" A dictionary of RGBA tuples indexed by color names. + +See https://www.pygame.org/docs/ref/color_list.html for sample swatches. +""" + +THECOLORS = { + "aliceblue": (240, 248, 255, 255), + "antiquewhite": (250, 235, 215, 255), + "antiquewhite1": (255, 239, 219, 255), + "antiquewhite2": (238, 223, 204, 255), + "antiquewhite3": (205, 192, 176, 255), + "antiquewhite4": (139, 131, 120, 255), + "aqua": (0, 255, 255, 255), + "aquamarine": (127, 255, 212, 255), + "aquamarine1": (127, 255, 212, 255), + "aquamarine2": (118, 238, 198, 255), + "aquamarine3": (102, 205, 170, 255), + "aquamarine4": (69, 139, 116, 255), + "azure": (240, 255, 255, 255), + "azure1": (240, 255, 255, 255), + "azure3": (193, 205, 205, 255), + "azure2": (224, 238, 238, 255), + "azure4": (131, 139, 139, 255), + "beige": (245, 245, 220, 255), + "bisque": (255, 228, 196, 255), + "bisque1": (255, 228, 196, 255), + "bisque2": (238, 213, 183, 255), + "bisque3": (205, 183, 158, 255), + "bisque4": (139, 125, 107, 255), + "black": (0, 0, 0, 255), + "blanchedalmond": (255, 235, 205, 255), + "blue": (0, 0, 255, 255), + "blue1": (0, 0, 255, 255), + "blue2": (0, 0, 238, 255), + "blue3": (0, 0, 205, 255), + "blue4": (0, 0, 139, 255), + "blueviolet": (138, 43, 226, 255), + "brown": (165, 42, 42, 255), + "brown1": (255, 64, 64, 255), + "brown2": (238, 59, 59, 255), + "brown3": (205, 51, 51, 255), + "brown4": (139, 35, 35, 255), + "burlywood": (222, 184, 135, 255), + "burlywood1": (255, 211, 155, 255), + "burlywood2": (238, 197, 145, 255), + "burlywood3": (205, 170, 125, 255), + "burlywood4": (139, 115, 85, 255), + "cadetblue": (95, 158, 160, 255), + "cadetblue1": (152, 245, 255, 255), + "cadetblue2": (142, 229, 238, 255), + "cadetblue3": (122, 197, 205, 255), + "cadetblue4": (83, 134, 139, 255), + "chartreuse": (127, 255, 0, 255), + "chartreuse1": (127, 255, 0, 255), + "chartreuse2": (118, 238, 0, 255), + "chartreuse3": (102, 205, 0, 255), + "chartreuse4": (69, 139, 0, 255), + "chocolate": (210, 105, 30, 255), + "chocolate1": (255, 127, 36, 255), + "chocolate2": (238, 118, 33, 255), + "chocolate3": (205, 102, 29, 255), + "chocolate4": (139, 69, 19, 255), + "coral": (255, 127, 80, 255), + "coral1": (255, 114, 86, 255), + "coral2": (238, 106, 80, 255), + "coral3": (205, 91, 69, 255), + "coral4": (139, 62, 47, 255), + "cornflowerblue": (100, 149, 237, 255), + "cornsilk": (255, 248, 220, 255), + "cornsilk1": (255, 248, 220, 255), + "cornsilk2": (238, 232, 205, 255), + "cornsilk3": (205, 200, 177, 255), + "cornsilk4": (139, 136, 120, 255), + "crimson": (220, 20, 60, 255), + "cyan": (0, 255, 255, 255), + "cyan1": (0, 255, 255, 255), + "cyan2": (0, 238, 238, 255), + "cyan3": (0, 205, 205, 255), + "cyan4": (0, 139, 139, 255), + "darkblue": (0, 0, 139, 255), + "darkcyan": (0, 139, 139, 255), + "darkgoldenrod": (184, 134, 11, 255), + "darkgoldenrod1": (255, 185, 15, 255), + "darkgoldenrod2": (238, 173, 14, 255), + "darkgoldenrod3": (205, 149, 12, 255), + "darkgoldenrod4": (139, 101, 8, 255), + "darkgray": (169, 169, 169, 255), + "darkgreen": (0, 100, 0, 255), + "darkgrey": (169, 169, 169, 255), + "darkkhaki": (189, 183, 107, 255), + "darkmagenta": (139, 0, 139, 255), + "darkolivegreen": (85, 107, 47, 255), + "darkolivegreen1": (202, 255, 112, 255), + "darkolivegreen2": (188, 238, 104, 255), + "darkolivegreen3": (162, 205, 90, 255), + "darkolivegreen4": (110, 139, 61, 255), + "darkorange": (255, 140, 0, 255), + "darkorange1": (255, 127, 0, 255), + "darkorange2": (238, 118, 0, 255), + "darkorange3": (205, 102, 0, 255), + "darkorange4": (139, 69, 0, 255), + "darkorchid": (153, 50, 204, 255), + "darkorchid1": (191, 62, 255, 255), + "darkorchid2": (178, 58, 238, 255), + "darkorchid3": (154, 50, 205, 255), + "darkorchid4": (104, 34, 139, 255), + "darkred": (139, 0, 0, 255), + "darksalmon": (233, 150, 122, 255), + "darkseagreen": (143, 188, 143, 255), + "darkseagreen1": (193, 255, 193, 255), + "darkseagreen2": (180, 238, 180, 255), + "darkseagreen3": (155, 205, 155, 255), + "darkseagreen4": (105, 139, 105, 255), + "darkslateblue": (72, 61, 139, 255), + "darkslategray": (47, 79, 79, 255), + "darkslategray1": (151, 255, 255, 255), + "darkslategray2": (141, 238, 238, 255), + "darkslategray3": (121, 205, 205, 255), + "darkslategray4": (82, 139, 139, 255), + "darkslategrey": (47, 79, 79, 255), + "darkturquoise": (0, 206, 209, 255), + "darkviolet": (148, 0, 211, 255), + "deeppink": (255, 20, 147, 255), + "deeppink1": (255, 20, 147, 255), + "deeppink2": (238, 18, 137, 255), + "deeppink3": (205, 16, 118, 255), + "deeppink4": (139, 10, 80, 255), + "deepskyblue": (0, 191, 255, 255), + "deepskyblue1": (0, 191, 255, 255), + "deepskyblue2": (0, 178, 238, 255), + "deepskyblue3": (0, 154, 205, 255), + "deepskyblue4": (0, 104, 139, 255), + "dimgray": (105, 105, 105, 255), + "dimgrey": (105, 105, 105, 255), + "dodgerblue": (30, 144, 255, 255), + "dodgerblue1": (30, 144, 255, 255), + "dodgerblue2": (28, 134, 238, 255), + "dodgerblue3": (24, 116, 205, 255), + "dodgerblue4": (16, 78, 139, 255), + "firebrick": (178, 34, 34, 255), + "firebrick1": (255, 48, 48, 255), + "firebrick2": (238, 44, 44, 255), + "firebrick3": (205, 38, 38, 255), + "firebrick4": (139, 26, 26, 255), + "floralwhite": (255, 250, 240, 255), + "forestgreen": (34, 139, 34, 255), + "fuchsia": (255, 0, 255, 255), + "gainsboro": (220, 220, 220, 255), + "ghostwhite": (248, 248, 255, 255), + "gold": (255, 215, 0, 255), + "gold1": (255, 215, 0, 255), + "gold2": (238, 201, 0, 255), + "gold3": (205, 173, 0, 255), + "gold4": (139, 117, 0, 255), + "goldenrod": (218, 165, 32, 255), + "goldenrod1": (255, 193, 37, 255), + "goldenrod2": (238, 180, 34, 255), + "goldenrod3": (205, 155, 29, 255), + "goldenrod4": (139, 105, 20, 255), + "gray": (190, 190, 190, 255), + "gray0": (0, 0, 0, 255), + "gray1": (3, 3, 3, 255), + "gray2": (5, 5, 5, 255), + "gray3": (8, 8, 8, 255), + "gray4": (10, 10, 10, 255), + "gray5": (13, 13, 13, 255), + "gray6": (15, 15, 15, 255), + "gray7": (18, 18, 18, 255), + "gray8": (20, 20, 20, 255), + "gray9": (23, 23, 23, 255), + "gray10": (26, 26, 26, 255), + "gray11": (28, 28, 28, 255), + "gray12": (31, 31, 31, 255), + "gray13": (33, 33, 33, 255), + "gray14": (36, 36, 36, 255), + "gray15": (38, 38, 38, 255), + "gray16": (41, 41, 41, 255), + "gray17": (43, 43, 43, 255), + "gray18": (46, 46, 46, 255), + "gray19": (48, 48, 48, 255), + "gray20": (51, 51, 51, 255), + "gray21": (54, 54, 54, 255), + "gray22": (56, 56, 56, 255), + "gray23": (59, 59, 59, 255), + "gray24": (61, 61, 61, 255), + "gray25": (64, 64, 64, 255), + "gray26": (66, 66, 66, 255), + "gray27": (69, 69, 69, 255), + "gray28": (71, 71, 71, 255), + "gray29": (74, 74, 74, 255), + "gray30": (77, 77, 77, 255), + "gray31": (79, 79, 79, 255), + "gray32": (82, 82, 82, 255), + "gray33": (84, 84, 84, 255), + "gray34": (87, 87, 87, 255), + "gray35": (89, 89, 89, 255), + "gray36": (92, 92, 92, 255), + "gray37": (94, 94, 94, 255), + "gray38": (97, 97, 97, 255), + "gray39": (99, 99, 99, 255), + "gray40": (102, 102, 102, 255), + "gray41": (105, 105, 105, 255), + "gray42": (107, 107, 107, 255), + "gray43": (110, 110, 110, 255), + "gray44": (112, 112, 112, 255), + "gray45": (115, 115, 115, 255), + "gray46": (117, 117, 117, 255), + "gray47": (120, 120, 120, 255), + "gray48": (122, 122, 122, 255), + "gray49": (125, 125, 125, 255), + "gray50": (127, 127, 127, 255), + "gray51": (130, 130, 130, 255), + "gray52": (133, 133, 133, 255), + "gray53": (135, 135, 135, 255), + "gray54": (138, 138, 138, 255), + "gray55": (140, 140, 140, 255), + "gray56": (143, 143, 143, 255), + "gray57": (145, 145, 145, 255), + "gray58": (148, 148, 148, 255), + "gray59": (150, 150, 150, 255), + "gray60": (153, 153, 153, 255), + "gray61": (156, 156, 156, 255), + "gray62": (158, 158, 158, 255), + "gray63": (161, 161, 161, 255), + "gray64": (163, 163, 163, 255), + "gray65": (166, 166, 166, 255), + "gray66": (168, 168, 168, 255), + "gray67": (171, 171, 171, 255), + "gray68": (173, 173, 173, 255), + "gray69": (176, 176, 176, 255), + "gray70": (179, 179, 179, 255), + "gray71": (181, 181, 181, 255), + "gray72": (184, 184, 184, 255), + "gray73": (186, 186, 186, 255), + "gray74": (189, 189, 189, 255), + "gray75": (191, 191, 191, 255), + "gray76": (194, 194, 194, 255), + "gray77": (196, 196, 196, 255), + "gray78": (199, 199, 199, 255), + "gray79": (201, 201, 201, 255), + "gray80": (204, 204, 204, 255), + "gray81": (207, 207, 207, 255), + "gray82": (209, 209, 209, 255), + "gray83": (212, 212, 212, 255), + "gray84": (214, 214, 214, 255), + "gray85": (217, 217, 217, 255), + "gray86": (219, 219, 219, 255), + "gray87": (222, 222, 222, 255), + "gray88": (224, 224, 224, 255), + "gray89": (227, 227, 227, 255), + "gray90": (229, 229, 229, 255), + "gray91": (232, 232, 232, 255), + "gray92": (235, 235, 235, 255), + "gray93": (237, 237, 237, 255), + "gray94": (240, 240, 240, 255), + "gray95": (242, 242, 242, 255), + "gray96": (245, 245, 245, 255), + "gray97": (247, 247, 247, 255), + "gray98": (250, 250, 250, 255), + "gray99": (252, 252, 252, 255), + "gray100": (255, 255, 255, 255), + "green": (0, 255, 0, 255), + "green1": (0, 255, 0, 255), + "green2": (0, 238, 0, 255), + "green3": (0, 205, 0, 255), + "green4": (0, 139, 0, 255), + "greenyellow": (173, 255, 47, 255), + "grey": (190, 190, 190, 255), + "grey0": (0, 0, 0, 255), + "grey1": (3, 3, 3, 255), + "grey2": (5, 5, 5, 255), + "grey3": (8, 8, 8, 255), + "grey4": (10, 10, 10, 255), + "grey5": (13, 13, 13, 255), + "grey6": (15, 15, 15, 255), + "grey7": (18, 18, 18, 255), + "grey8": (20, 20, 20, 255), + "grey9": (23, 23, 23, 255), + "grey10": (26, 26, 26, 255), + "grey11": (28, 28, 28, 255), + "grey12": (31, 31, 31, 255), + "grey13": (33, 33, 33, 255), + "grey14": (36, 36, 36, 255), + "grey15": (38, 38, 38, 255), + "grey16": (41, 41, 41, 255), + "grey17": (43, 43, 43, 255), + "grey18": (46, 46, 46, 255), + "grey19": (48, 48, 48, 255), + "grey20": (51, 51, 51, 255), + "grey21": (54, 54, 54, 255), + "grey22": (56, 56, 56, 255), + "grey23": (59, 59, 59, 255), + "grey24": (61, 61, 61, 255), + "grey25": (64, 64, 64, 255), + "grey26": (66, 66, 66, 255), + "grey27": (69, 69, 69, 255), + "grey28": (71, 71, 71, 255), + "grey29": (74, 74, 74, 255), + "grey30": (77, 77, 77, 255), + "grey31": (79, 79, 79, 255), + "grey32": (82, 82, 82, 255), + "grey33": (84, 84, 84, 255), + "grey34": (87, 87, 87, 255), + "grey35": (89, 89, 89, 255), + "grey36": (92, 92, 92, 255), + "grey37": (94, 94, 94, 255), + "grey38": (97, 97, 97, 255), + "grey39": (99, 99, 99, 255), + "grey40": (102, 102, 102, 255), + "grey41": (105, 105, 105, 255), + "grey42": (107, 107, 107, 255), + "grey43": (110, 110, 110, 255), + "grey44": (112, 112, 112, 255), + "grey45": (115, 115, 115, 255), + "grey46": (117, 117, 117, 255), + "grey47": (120, 120, 120, 255), + "grey48": (122, 122, 122, 255), + "grey49": (125, 125, 125, 255), + "grey50": (127, 127, 127, 255), + "grey51": (130, 130, 130, 255), + "grey52": (133, 133, 133, 255), + "grey53": (135, 135, 135, 255), + "grey54": (138, 138, 138, 255), + "grey55": (140, 140, 140, 255), + "grey56": (143, 143, 143, 255), + "grey57": (145, 145, 145, 255), + "grey58": (148, 148, 148, 255), + "grey59": (150, 150, 150, 255), + "grey60": (153, 153, 153, 255), + "grey61": (156, 156, 156, 255), + "grey62": (158, 158, 158, 255), + "grey63": (161, 161, 161, 255), + "grey64": (163, 163, 163, 255), + "grey65": (166, 166, 166, 255), + "grey66": (168, 168, 168, 255), + "grey67": (171, 171, 171, 255), + "grey68": (173, 173, 173, 255), + "grey69": (176, 176, 176, 255), + "grey70": (179, 179, 179, 255), + "grey71": (181, 181, 181, 255), + "grey72": (184, 184, 184, 255), + "grey73": (186, 186, 186, 255), + "grey74": (189, 189, 189, 255), + "grey75": (191, 191, 191, 255), + "grey76": (194, 194, 194, 255), + "grey77": (196, 196, 196, 255), + "grey78": (199, 199, 199, 255), + "grey79": (201, 201, 201, 255), + "grey80": (204, 204, 204, 255), + "grey81": (207, 207, 207, 255), + "grey82": (209, 209, 209, 255), + "grey83": (212, 212, 212, 255), + "grey84": (214, 214, 214, 255), + "grey85": (217, 217, 217, 255), + "grey86": (219, 219, 219, 255), + "grey87": (222, 222, 222, 255), + "grey88": (224, 224, 224, 255), + "grey89": (227, 227, 227, 255), + "grey90": (229, 229, 229, 255), + "grey91": (232, 232, 232, 255), + "grey92": (235, 235, 235, 255), + "grey93": (237, 237, 237, 255), + "grey94": (240, 240, 240, 255), + "grey95": (242, 242, 242, 255), + "grey96": (245, 245, 245, 255), + "grey97": (247, 247, 247, 255), + "grey98": (250, 250, 250, 255), + "grey99": (252, 252, 252, 255), + "grey100": (255, 255, 255, 255), + "honeydew": (240, 255, 240, 255), + "honeydew1": (240, 255, 240, 255), + "honeydew2": (224, 238, 224, 255), + "honeydew3": (193, 205, 193, 255), + "honeydew4": (131, 139, 131, 255), + "hotpink": (255, 105, 180, 255), + "hotpink1": (255, 110, 180, 255), + "hotpink2": (238, 106, 167, 255), + "hotpink3": (205, 96, 144, 255), + "hotpink4": (139, 58, 98, 255), + "indianred": (205, 92, 92, 255), + "indianred1": (255, 106, 106, 255), + "indianred2": (238, 99, 99, 255), + "indianred3": (205, 85, 85, 255), + "indianred4": (139, 58, 58, 255), + "indigo": (75, 0, 130, 255), + "ivory": (255, 255, 240, 255), + "ivory1": (255, 255, 240, 255), + "ivory2": (238, 238, 224, 255), + "ivory3": (205, 205, 193, 255), + "ivory4": (139, 139, 131, 255), + "khaki": (240, 230, 140, 255), + "khaki1": (255, 246, 143, 255), + "khaki2": (238, 230, 133, 255), + "khaki3": (205, 198, 115, 255), + "khaki4": (139, 134, 78, 255), + "lavender": (230, 230, 250, 255), + "lavenderblush": (255, 240, 245, 255), + "lavenderblush1": (255, 240, 245, 255), + "lavenderblush2": (238, 224, 229, 255), + "lavenderblush3": (205, 193, 197, 255), + "lavenderblush4": (139, 131, 134, 255), + "lawngreen": (124, 252, 0, 255), + "lemonchiffon": (255, 250, 205, 255), + "lemonchiffon1": (255, 250, 205, 255), + "lemonchiffon2": (238, 233, 191, 255), + "lemonchiffon3": (205, 201, 165, 255), + "lemonchiffon4": (139, 137, 112, 255), + "lightblue": (173, 216, 230, 255), + "lightblue1": (191, 239, 255, 255), + "lightblue2": (178, 223, 238, 255), + "lightblue3": (154, 192, 205, 255), + "lightblue4": (104, 131, 139, 255), + "lightcoral": (240, 128, 128, 255), + "lightcyan": (224, 255, 255, 255), + "lightcyan1": (224, 255, 255, 255), + "lightcyan2": (209, 238, 238, 255), + "lightcyan3": (180, 205, 205, 255), + "lightcyan4": (122, 139, 139, 255), + "lightgoldenrod": (238, 221, 130, 255), + "lightgoldenrod1": (255, 236, 139, 255), + "lightgoldenrod2": (238, 220, 130, 255), + "lightgoldenrod3": (205, 190, 112, 255), + "lightgoldenrod4": (139, 129, 76, 255), + "lightgoldenrodyellow": (250, 250, 210, 255), + "lightgray": (211, 211, 211, 255), + "lightgreen": (144, 238, 144, 255), + "lightgrey": (211, 211, 211, 255), + "lightpink": (255, 182, 193, 255), + "lightpink1": (255, 174, 185, 255), + "lightpink2": (238, 162, 173, 255), + "lightpink3": (205, 140, 149, 255), + "lightpink4": (139, 95, 101, 255), + "lightsalmon": (255, 160, 122, 255), + "lightsalmon1": (255, 160, 122, 255), + "lightsalmon2": (238, 149, 114, 255), + "lightsalmon3": (205, 129, 98, 255), + "lightsalmon4": (139, 87, 66, 255), + "lightseagreen": (32, 178, 170, 255), + "lightskyblue": (135, 206, 250, 255), + "lightskyblue1": (176, 226, 255, 255), + "lightskyblue2": (164, 211, 238, 255), + "lightskyblue3": (141, 182, 205, 255), + "lightskyblue4": (96, 123, 139, 255), + "lightslateblue": (132, 112, 255, 255), + "lightslategray": (119, 136, 153, 255), + "lightslategrey": (119, 136, 153, 255), + "lightsteelblue": (176, 196, 222, 255), + "lightsteelblue1": (202, 225, 255, 255), + "lightsteelblue2": (188, 210, 238, 255), + "lightsteelblue3": (162, 181, 205, 255), + "lightsteelblue4": (110, 123, 139, 255), + "lightyellow": (255, 255, 224, 255), + "lightyellow1": (255, 255, 224, 255), + "lightyellow2": (238, 238, 209, 255), + "lightyellow3": (205, 205, 180, 255), + "lightyellow4": (139, 139, 122, 255), + "linen": (250, 240, 230, 255), + "lime": (0, 255, 0, 255), + "limegreen": (50, 205, 50, 255), + "magenta": (255, 0, 255, 255), + "magenta1": (255, 0, 255, 255), + "magenta2": (238, 0, 238, 255), + "magenta3": (205, 0, 205, 255), + "magenta4": (139, 0, 139, 255), + "maroon": (176, 48, 96, 255), + "maroon1": (255, 52, 179, 255), + "maroon2": (238, 48, 167, 255), + "maroon3": (205, 41, 144, 255), + "maroon4": (139, 28, 98, 255), + "mediumaquamarine": (102, 205, 170, 255), + "mediumblue": (0, 0, 205, 255), + "mediumorchid": (186, 85, 211, 255), + "mediumorchid1": (224, 102, 255, 255), + "mediumorchid2": (209, 95, 238, 255), + "mediumorchid3": (180, 82, 205, 255), + "mediumorchid4": (122, 55, 139, 255), + "mediumpurple": (147, 112, 219, 255), + "mediumpurple1": (171, 130, 255, 255), + "mediumpurple2": (159, 121, 238, 255), + "mediumpurple3": (137, 104, 205, 255), + "mediumpurple4": (93, 71, 139, 255), + "mediumseagreen": (60, 179, 113, 255), + "mediumslateblue": (123, 104, 238, 255), + "mediumspringgreen": (0, 250, 154, 255), + "mediumturquoise": (72, 209, 204, 255), + "mediumvioletred": (199, 21, 133, 255), + "midnightblue": (25, 25, 112, 255), + "mintcream": (245, 255, 250, 255), + "mistyrose": (255, 228, 225, 255), + "mistyrose1": (255, 228, 225, 255), + "mistyrose2": (238, 213, 210, 255), + "mistyrose3": (205, 183, 181, 255), + "mistyrose4": (139, 125, 123, 255), + "moccasin": (255, 228, 181, 255), + "navajowhite": (255, 222, 173, 255), + "navajowhite1": (255, 222, 173, 255), + "navajowhite2": (238, 207, 161, 255), + "navajowhite3": (205, 179, 139, 255), + "navajowhite4": (139, 121, 94, 255), + "navy": (0, 0, 128, 255), + "navyblue": (0, 0, 128, 255), + "oldlace": (253, 245, 230, 255), + "olive": (128, 128, 0, 255), + "olivedrab": (107, 142, 35, 255), + "olivedrab1": (192, 255, 62, 255), + "olivedrab2": (179, 238, 58, 255), + "olivedrab3": (154, 205, 50, 255), + "olivedrab4": (105, 139, 34, 255), + "orange": (255, 165, 0, 255), + "orange1": (255, 165, 0, 255), + "orange2": (238, 154, 0, 255), + "orange3": (205, 133, 0, 255), + "orange4": (139, 90, 0, 255), + "orangered": (255, 69, 0, 255), + "orangered1": (255, 69, 0, 255), + "orangered2": (238, 64, 0, 255), + "orangered3": (205, 55, 0, 255), + "orangered4": (139, 37, 0, 255), + "orchid": (218, 112, 214, 255), + "orchid1": (255, 131, 250, 255), + "orchid2": (238, 122, 233, 255), + "orchid3": (205, 105, 201, 255), + "orchid4": (139, 71, 137, 255), + "palegreen": (152, 251, 152, 255), + "palegreen1": (154, 255, 154, 255), + "palegreen2": (144, 238, 144, 255), + "palegreen3": (124, 205, 124, 255), + "palegreen4": (84, 139, 84, 255), + "palegoldenrod": (238, 232, 170, 255), + "paleturquoise": (175, 238, 238, 255), + "paleturquoise1": (187, 255, 255, 255), + "paleturquoise2": (174, 238, 238, 255), + "paleturquoise3": (150, 205, 205, 255), + "paleturquoise4": (102, 139, 139, 255), + "palevioletred": (219, 112, 147, 255), + "palevioletred1": (255, 130, 171, 255), + "palevioletred2": (238, 121, 159, 255), + "palevioletred3": (205, 104, 137, 255), + "palevioletred4": (139, 71, 93, 255), + "papayawhip": (255, 239, 213, 255), + "peachpuff": (255, 218, 185, 255), + "peachpuff1": (255, 218, 185, 255), + "peachpuff2": (238, 203, 173, 255), + "peachpuff3": (205, 175, 149, 255), + "peachpuff4": (139, 119, 101, 255), + "peru": (205, 133, 63, 255), + "pink": (255, 192, 203, 255), + "pink1": (255, 181, 197, 255), + "pink2": (238, 169, 184, 255), + "pink3": (205, 145, 158, 255), + "pink4": (139, 99, 108, 255), + "plum": (221, 160, 221, 255), + "plum1": (255, 187, 255, 255), + "plum2": (238, 174, 238, 255), + "plum3": (205, 150, 205, 255), + "plum4": (139, 102, 139, 255), + "powderblue": (176, 224, 230, 255), + "purple": (160, 32, 240, 255), + "purple1": (155, 48, 255, 255), + "purple2": (145, 44, 238, 255), + "purple3": (125, 38, 205, 255), + "purple4": (85, 26, 139, 255), + "red": (255, 0, 0, 255), + "red1": (255, 0, 0, 255), + "red2": (238, 0, 0, 255), + "red3": (205, 0, 0, 255), + "red4": (139, 0, 0, 255), + "rosybrown": (188, 143, 143, 255), + "rosybrown1": (255, 193, 193, 255), + "rosybrown2": (238, 180, 180, 255), + "rosybrown3": (205, 155, 155, 255), + "rosybrown4": (139, 105, 105, 255), + "royalblue": (65, 105, 225, 255), + "royalblue1": (72, 118, 255, 255), + "royalblue2": (67, 110, 238, 255), + "royalblue3": (58, 95, 205, 255), + "royalblue4": (39, 64, 139, 255), + "salmon": (250, 128, 114, 255), + "salmon1": (255, 140, 105, 255), + "salmon2": (238, 130, 98, 255), + "salmon3": (205, 112, 84, 255), + "salmon4": (139, 76, 57, 255), + "saddlebrown": (139, 69, 19, 255), + "sandybrown": (244, 164, 96, 255), + "seagreen": (46, 139, 87, 255), + "seagreen1": (84, 255, 159, 255), + "seagreen2": (78, 238, 148, 255), + "seagreen3": (67, 205, 128, 255), + "seagreen4": (46, 139, 87, 255), + "seashell": (255, 245, 238, 255), + "seashell1": (255, 245, 238, 255), + "seashell2": (238, 229, 222, 255), + "seashell3": (205, 197, 191, 255), + "seashell4": (139, 134, 130, 255), + "sienna": (160, 82, 45, 255), + "sienna1": (255, 130, 71, 255), + "sienna2": (238, 121, 66, 255), + "sienna3": (205, 104, 57, 255), + "sienna4": (139, 71, 38, 255), + "silver": (192, 192, 192, 255), + "skyblue": (135, 206, 235, 255), + "skyblue1": (135, 206, 255, 255), + "skyblue2": (126, 192, 238, 255), + "skyblue3": (108, 166, 205, 255), + "skyblue4": (74, 112, 139, 255), + "slateblue": (106, 90, 205, 255), + "slateblue1": (131, 111, 255, 255), + "slateblue2": (122, 103, 238, 255), + "slateblue3": (105, 89, 205, 255), + "slateblue4": (71, 60, 139, 255), + "slategray": (112, 128, 144, 255), + "slategray1": (198, 226, 255, 255), + "slategray2": (185, 211, 238, 255), + "slategray3": (159, 182, 205, 255), + "slategray4": (108, 123, 139, 255), + "slategrey": (112, 128, 144, 255), + "snow": (255, 250, 250, 255), + "snow1": (255, 250, 250, 255), + "snow2": (238, 233, 233, 255), + "snow3": (205, 201, 201, 255), + "snow4": (139, 137, 137, 255), + "springgreen": (0, 255, 127, 255), + "springgreen1": (0, 255, 127, 255), + "springgreen2": (0, 238, 118, 255), + "springgreen3": (0, 205, 102, 255), + "springgreen4": (0, 139, 69, 255), + "steelblue": (70, 130, 180, 255), + "steelblue1": (99, 184, 255, 255), + "steelblue2": (92, 172, 238, 255), + "steelblue3": (79, 148, 205, 255), + "steelblue4": (54, 100, 139, 255), + "tan": (210, 180, 140, 255), + "tan1": (255, 165, 79, 255), + "tan2": (238, 154, 73, 255), + "tan3": (205, 133, 63, 255), + "tan4": (139, 90, 43, 255), + "teal": (0, 128, 128, 255), + "thistle": (216, 191, 216, 255), + "thistle1": (255, 225, 255, 255), + "thistle2": (238, 210, 238, 255), + "thistle3": (205, 181, 205, 255), + "thistle4": (139, 123, 139, 255), + "tomato": (255, 99, 71, 255), + "tomato1": (255, 99, 71, 255), + "tomato2": (238, 92, 66, 255), + "tomato3": (205, 79, 57, 255), + "tomato4": (139, 54, 38, 255), + "turquoise": (64, 224, 208, 255), + "turquoise1": (0, 245, 255, 255), + "turquoise2": (0, 229, 238, 255), + "turquoise3": (0, 197, 205, 255), + "turquoise4": (0, 134, 139, 255), + "violet": (238, 130, 238, 255), + "violetred": (208, 32, 144, 255), + "violetred1": (255, 62, 150, 255), + "violetred2": (238, 58, 140, 255), + "violetred3": (205, 50, 120, 255), + "violetred4": (139, 34, 82, 255), + "wheat": (245, 222, 179, 255), + "wheat1": (255, 231, 186, 255), + "wheat2": (238, 216, 174, 255), + "wheat3": (205, 186, 150, 255), + "wheat4": (139, 126, 102, 255), + "white": (255, 255, 255, 255), + "whitesmoke": (245, 245, 245, 255), + "yellow": (255, 255, 0, 255), + "yellow1": (255, 255, 0, 255), + "yellow2": (238, 238, 0, 255), + "yellow3": (205, 205, 0, 255), + "yellow4": (139, 139, 0, 255), + "yellowgreen": (154, 205, 50, 255), +} diff --git a/.venv/lib/python3.8/site-packages/pygame/constants.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/constants.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..de3585f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/constants.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/constants.pyi b/.venv/lib/python3.8/site-packages/pygame/constants.pyi new file mode 100644 index 0000000..171a0c7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/constants.pyi @@ -0,0 +1,557 @@ +""" +Script used to generate this file (if we change something in the constants in the future): +import pygame.constants +const = [] +for element in dir(pygame.constants): + constant_type = getattr(pygame.constants, element) + if not element.startswith("_"): + const.append("{}: {}\n".format(element, constant_type.__class__.__name__)) +with open("constants.pyi", "w") as f: + f.write("from typing import List\n\n\n") + for line in const: + f.write(str(line)) + f.write("__all__: List[str]\n") +""" + +from typing import List + +ACTIVEEVENT: int +ANYFORMAT: int +APPACTIVE: int +APPFOCUSMOUSE: int +APPINPUTFOCUS: int +ASYNCBLIT: int +AUDIODEVICEADDED: int +AUDIODEVICEREMOVED: int +AUDIO_ALLOW_ANY_CHANGE: int +AUDIO_ALLOW_CHANNELS_CHANGE: int +AUDIO_ALLOW_FORMAT_CHANGE: int +AUDIO_ALLOW_FREQUENCY_CHANGE: int +AUDIO_S16: int +AUDIO_S16LSB: int +AUDIO_S16MSB: int +AUDIO_S16SYS: int +AUDIO_S8: int +AUDIO_U16: int +AUDIO_U16LSB: int +AUDIO_U16MSB: int +AUDIO_U16SYS: int +AUDIO_U8: int +BIG_ENDIAN: int +BLENDMODE_ADD: int +BLENDMODE_BLEND: int +BLENDMODE_MOD: int +BLENDMODE_NONE: int +BLEND_ADD: int +BLEND_ALPHA_SDL2: int +BLEND_MAX: int +BLEND_MIN: int +BLEND_MULT: int +BLEND_PREMULTIPLIED: int +BLEND_RGBA_ADD: int +BLEND_RGBA_MAX: int +BLEND_RGBA_MIN: int +BLEND_RGBA_MULT: int +BLEND_RGBA_SUB: int +BLEND_RGB_ADD: int +BLEND_RGB_MAX: int +BLEND_RGB_MIN: int +BLEND_RGB_MULT: int +BLEND_RGB_SUB: int +BLEND_SUB: int +BUTTON_LEFT: int +BUTTON_MIDDLE: int +BUTTON_RIGHT: int +BUTTON_WHEELDOWN: int +BUTTON_WHEELUP: int +BUTTON_X1: int +BUTTON_X2: int +CONTROLLERAXISMOTION: int +CONTROLLERBUTTONDOWN: int +CONTROLLERBUTTONUP: int +CONTROLLERDEVICEADDED: int +CONTROLLERDEVICEREMAPPED: int +CONTROLLERDEVICEREMOVED: int +CONTROLLER_AXIS_INVALID: int +CONTROLLER_AXIS_LEFTX: int +CONTROLLER_AXIS_LEFTY: int +CONTROLLER_AXIS_MAX: int +CONTROLLER_AXIS_RIGHTX: int +CONTROLLER_AXIS_RIGHTY: int +CONTROLLER_AXIS_TRIGGERLEFT: int +CONTROLLER_AXIS_TRIGGERRIGHT: int +CONTROLLER_BUTTON_A: int +CONTROLLER_BUTTON_B: int +CONTROLLER_BUTTON_BACK: int +CONTROLLER_BUTTON_DPAD_DOWN: int +CONTROLLER_BUTTON_DPAD_LEFT: int +CONTROLLER_BUTTON_DPAD_RIGHT: int +CONTROLLER_BUTTON_DPAD_UP: int +CONTROLLER_BUTTON_GUIDE: int +CONTROLLER_BUTTON_INVALID: int +CONTROLLER_BUTTON_LEFTSHOULDER: int +CONTROLLER_BUTTON_LEFTSTICK: int +CONTROLLER_BUTTON_MAX: int +CONTROLLER_BUTTON_RIGHTSHOULDER: int +CONTROLLER_BUTTON_RIGHTSTICK: int +CONTROLLER_BUTTON_START: int +CONTROLLER_BUTTON_X: int +CONTROLLER_BUTTON_Y: int +DOUBLEBUF: int +DROPBEGIN: int +DROPCOMPLETE: int +DROPFILE: int +DROPTEXT: int +FINGERDOWN: int +FINGERMOTION: int +FINGERUP: int +FULLSCREEN: int +GL_ACCELERATED_VISUAL: int +GL_ACCUM_ALPHA_SIZE: int +GL_ACCUM_BLUE_SIZE: int +GL_ACCUM_GREEN_SIZE: int +GL_ACCUM_RED_SIZE: int +GL_ALPHA_SIZE: int +GL_BLUE_SIZE: int +GL_BUFFER_SIZE: int +GL_CONTEXT_DEBUG_FLAG: int +GL_CONTEXT_FLAGS: int +GL_CONTEXT_FORWARD_COMPATIBLE_FLAG: int +GL_CONTEXT_MAJOR_VERSION: int +GL_CONTEXT_MINOR_VERSION: int +GL_CONTEXT_PROFILE_COMPATIBILITY: int +GL_CONTEXT_PROFILE_CORE: int +GL_CONTEXT_PROFILE_ES: int +GL_CONTEXT_PROFILE_MASK: int +GL_CONTEXT_RELEASE_BEHAVIOR: int +GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH: int +GL_CONTEXT_RELEASE_BEHAVIOR_NONE: int +GL_CONTEXT_RESET_ISOLATION_FLAG: int +GL_CONTEXT_ROBUST_ACCESS_FLAG: int +GL_DEPTH_SIZE: int +GL_DOUBLEBUFFER: int +GL_FRAMEBUFFER_SRGB_CAPABLE: int +GL_GREEN_SIZE: int +GL_MULTISAMPLEBUFFERS: int +GL_MULTISAMPLESAMPLES: int +GL_RED_SIZE: int +GL_SHARE_WITH_CURRENT_CONTEXT: int +GL_STENCIL_SIZE: int +GL_STEREO: int +GL_SWAP_CONTROL: int +HAT_CENTERED: int +HAT_DOWN: int +HAT_LEFT: int +HAT_LEFTDOWN: int +HAT_LEFTUP: int +HAT_RIGHT: int +HAT_RIGHTDOWN: int +HAT_RIGHTUP: int +HAT_UP: int +HIDDEN: int +HWACCEL: int +HWPALETTE: int +HWSURFACE: int +JOYAXISMOTION: int +JOYBALLMOTION: int +JOYBUTTONDOWN: int +JOYBUTTONUP: int +JOYDEVICEADDED: int +JOYDEVICEREMOVED: int +JOYHATMOTION: int +KEYDOWN: int +KEYUP: int +KMOD_ALT: int +KMOD_CAPS: int +KMOD_CTRL: int +KMOD_GUI: int +KMOD_LALT: int +KMOD_LCTRL: int +KMOD_LGUI: int +KMOD_LMETA: int +KMOD_LSHIFT: int +KMOD_META: int +KMOD_MODE: int +KMOD_NONE: int +KMOD_NUM: int +KMOD_RALT: int +KMOD_RCTRL: int +KMOD_RGUI: int +KMOD_RMETA: int +KMOD_RSHIFT: int +KMOD_SHIFT: int +KSCAN_0: int +KSCAN_1: int +KSCAN_2: int +KSCAN_3: int +KSCAN_4: int +KSCAN_5: int +KSCAN_6: int +KSCAN_7: int +KSCAN_8: int +KSCAN_9: int +KSCAN_A: int +KSCAN_APOSTROPHE: int +KSCAN_B: int +KSCAN_BACKSLASH: int +KSCAN_BACKSPACE: int +KSCAN_BREAK: int +KSCAN_C: int +KSCAN_CAPSLOCK: int +KSCAN_CLEAR: int +KSCAN_COMMA: int +KSCAN_CURRENCYSUBUNIT: int +KSCAN_CURRENCYUNIT: int +KSCAN_D: int +KSCAN_DELETE: int +KSCAN_DOWN: int +KSCAN_E: int +KSCAN_END: int +KSCAN_EQUALS: int +KSCAN_ESCAPE: int +KSCAN_EURO: int +KSCAN_F: int +KSCAN_F1: int +KSCAN_F10: int +KSCAN_F11: int +KSCAN_F12: int +KSCAN_F13: int +KSCAN_F14: int +KSCAN_F15: int +KSCAN_F2: int +KSCAN_F3: int +KSCAN_F4: int +KSCAN_F5: int +KSCAN_F6: int +KSCAN_F7: int +KSCAN_F8: int +KSCAN_F9: int +KSCAN_G: int +KSCAN_GRAVE: int +KSCAN_H: int +KSCAN_HELP: int +KSCAN_HOME: int +KSCAN_I: int +KSCAN_INSERT: int +KSCAN_INTERNATIONAL1: int +KSCAN_INTERNATIONAL2: int +KSCAN_INTERNATIONAL3: int +KSCAN_INTERNATIONAL4: int +KSCAN_INTERNATIONAL5: int +KSCAN_INTERNATIONAL6: int +KSCAN_INTERNATIONAL7: int +KSCAN_INTERNATIONAL8: int +KSCAN_INTERNATIONAL9: int +KSCAN_J: int +KSCAN_K: int +KSCAN_KP0: int +KSCAN_KP1: int +KSCAN_KP2: int +KSCAN_KP3: int +KSCAN_KP4: int +KSCAN_KP5: int +KSCAN_KP6: int +KSCAN_KP7: int +KSCAN_KP8: int +KSCAN_KP9: int +KSCAN_KP_0: int +KSCAN_KP_1: int +KSCAN_KP_2: int +KSCAN_KP_3: int +KSCAN_KP_4: int +KSCAN_KP_5: int +KSCAN_KP_6: int +KSCAN_KP_7: int +KSCAN_KP_8: int +KSCAN_KP_9: int +KSCAN_KP_DIVIDE: int +KSCAN_KP_ENTER: int +KSCAN_KP_EQUALS: int +KSCAN_KP_MINUS: int +KSCAN_KP_MULTIPLY: int +KSCAN_KP_PERIOD: int +KSCAN_KP_PLUS: int +KSCAN_L: int +KSCAN_LALT: int +KSCAN_LANG1: int +KSCAN_LANG2: int +KSCAN_LANG3: int +KSCAN_LANG4: int +KSCAN_LANG5: int +KSCAN_LANG6: int +KSCAN_LANG7: int +KSCAN_LANG8: int +KSCAN_LANG9: int +KSCAN_LCTRL: int +KSCAN_LEFT: int +KSCAN_LEFTBRACKET: int +KSCAN_LGUI: int +KSCAN_LMETA: int +KSCAN_LSHIFT: int +KSCAN_LSUPER: int +KSCAN_M: int +KSCAN_MENU: int +KSCAN_MINUS: int +KSCAN_MODE: int +KSCAN_N: int +KSCAN_NONUSBACKSLASH: int +KSCAN_NONUSHASH: int +KSCAN_NUMLOCK: int +KSCAN_NUMLOCKCLEAR: int +KSCAN_O: int +KSCAN_P: int +KSCAN_PAGEDOWN: int +KSCAN_PAGEUP: int +KSCAN_PAUSE: int +KSCAN_PERIOD: int +KSCAN_POWER: int +KSCAN_PRINT: int +KSCAN_PRINTSCREEN: int +KSCAN_Q: int +KSCAN_R: int +KSCAN_RALT: int +KSCAN_RCTRL: int +KSCAN_RETURN: int +KSCAN_RGUI: int +KSCAN_RIGHT: int +KSCAN_RIGHTBRACKET: int +KSCAN_RMETA: int +KSCAN_RSHIFT: int +KSCAN_RSUPER: int +KSCAN_S: int +KSCAN_SCROLLLOCK: int +KSCAN_SCROLLOCK: int +KSCAN_SEMICOLON: int +KSCAN_SLASH: int +KSCAN_SPACE: int +KSCAN_SYSREQ: int +KSCAN_T: int +KSCAN_TAB: int +KSCAN_U: int +KSCAN_UNKNOWN: int +KSCAN_UP: int +KSCAN_V: int +KSCAN_W: int +KSCAN_X: int +KSCAN_Y: int +KSCAN_Z: int +K_0: int +K_1: int +K_2: int +K_3: int +K_4: int +K_5: int +K_6: int +K_7: int +K_8: int +K_9: int +K_AC_BACK: int +K_AMPERSAND: int +K_ASTERISK: int +K_AT: int +K_BACKQUOTE: int +K_BACKSLASH: int +K_BACKSPACE: int +K_BREAK: int +K_CAPSLOCK: int +K_CARET: int +K_CLEAR: int +K_COLON: int +K_COMMA: int +K_CURRENCYSUBUNIT: int +K_CURRENCYUNIT: int +K_DELETE: int +K_DOLLAR: int +K_DOWN: int +K_END: int +K_EQUALS: int +K_ESCAPE: int +K_EURO: int +K_EXCLAIM: int +K_F1: int +K_F10: int +K_F11: int +K_F12: int +K_F13: int +K_F14: int +K_F15: int +K_F2: int +K_F3: int +K_F4: int +K_F5: int +K_F6: int +K_F7: int +K_F8: int +K_F9: int +K_GREATER: int +K_HASH: int +K_HELP: int +K_HOME: int +K_INSERT: int +K_KP0: int +K_KP1: int +K_KP2: int +K_KP3: int +K_KP4: int +K_KP5: int +K_KP6: int +K_KP7: int +K_KP8: int +K_KP9: int +K_KP_0: int +K_KP_1: int +K_KP_2: int +K_KP_3: int +K_KP_4: int +K_KP_5: int +K_KP_6: int +K_KP_7: int +K_KP_8: int +K_KP_9: int +K_KP_DIVIDE: int +K_KP_ENTER: int +K_KP_EQUALS: int +K_KP_MINUS: int +K_KP_MULTIPLY: int +K_KP_PERIOD: int +K_KP_PLUS: int +K_LALT: int +K_LCTRL: int +K_LEFT: int +K_LEFTBRACKET: int +K_LEFTPAREN: int +K_LESS: int +K_LGUI: int +K_LMETA: int +K_LSHIFT: int +K_LSUPER: int +K_MENU: int +K_MINUS: int +K_MODE: int +K_NUMLOCK: int +K_NUMLOCKCLEAR: int +K_PAGEDOWN: int +K_PAGEUP: int +K_PAUSE: int +K_PERCENT: int +K_PERIOD: int +K_PLUS: int +K_POWER: int +K_PRINT: int +K_PRINTSCREEN: int +K_QUESTION: int +K_QUOTE: int +K_QUOTEDBL: int +K_RALT: int +K_RCTRL: int +K_RETURN: int +K_RGUI: int +K_RIGHT: int +K_RIGHTBRACKET: int +K_RIGHTPAREN: int +K_RMETA: int +K_RSHIFT: int +K_RSUPER: int +K_SCROLLLOCK: int +K_SCROLLOCK: int +K_SEMICOLON: int +K_SLASH: int +K_SPACE: int +K_SYSREQ: int +K_TAB: int +K_UNDERSCORE: int +K_UNKNOWN: int +K_UP: int +K_a: int +K_b: int +K_c: int +K_d: int +K_e: int +K_f: int +K_g: int +K_h: int +K_i: int +K_j: int +K_k: int +K_l: int +K_m: int +K_n: int +K_o: int +K_p: int +K_q: int +K_r: int +K_s: int +K_t: int +K_u: int +K_v: int +K_w: int +K_x: int +K_y: int +K_z: int +LIL_ENDIAN: int +MIDIIN: int +MIDIOUT: int +MOUSEBUTTONDOWN: int +MOUSEBUTTONUP: int +MOUSEMOTION: int +MOUSEWHEEL: int +MULTIGESTURE: int +NOEVENT: int +NOFRAME: int +NUMEVENTS: int +OPENGL: int +OPENGLBLIT: int +PREALLOC: int +QUIT: int +RESIZABLE: int +RLEACCEL: int +RLEACCELOK: int +SCALED: int +SCRAP_BMP: str +SCRAP_CLIPBOARD: int +SCRAP_PBM: str +SCRAP_PPM: str +SCRAP_SELECTION: int +SCRAP_TEXT: str +SHOWN: int +SRCALPHA: int +SRCCOLORKEY: int +SWSURFACE: int +SYSTEM_CURSOR_ARROW: int +SYSTEM_CURSOR_CROSSHAIR: int +SYSTEM_CURSOR_HAND: int +SYSTEM_CURSOR_IBEAM: int +SYSTEM_CURSOR_NO: int +SYSTEM_CURSOR_SIZEALL: int +SYSTEM_CURSOR_SIZENESW: int +SYSTEM_CURSOR_SIZENS: int +SYSTEM_CURSOR_SIZENWSE: int +SYSTEM_CURSOR_SIZEWE: int +SYSTEM_CURSOR_WAIT: int +SYSTEM_CURSOR_WAITARROW: int +SYSWMEVENT: int +TEXTEDITING: int +TEXTINPUT: int +TIMER_RESOLUTION: int +USEREVENT: int +USEREVENT_DROPFILE: int +VIDEOEXPOSE: int +VIDEORESIZE: int +WINDOWCLOSE: int +WINDOWENTER: int +WINDOWEXPOSED: int +WINDOWFOCUSGAINED: int +WINDOWFOCUSLOST: int +WINDOWHIDDEN: int +WINDOWHITTEST: int +WINDOWLEAVE: int +WINDOWMAXIMIZED: int +WINDOWMINIMIZED: int +WINDOWMOVED: int +WINDOWRESIZED: int +WINDOWRESTORED: int +WINDOWSHOWN: int +WINDOWSIZECHANGED: int +WINDOWTAKEFOCUS: int + +__all__: List[str] diff --git a/.venv/lib/python3.8/site-packages/pygame/cursors.py b/.venv/lib/python3.8/site-packages/pygame/cursors.py new file mode 100644 index 0000000..d7f71b5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/cursors.py @@ -0,0 +1,840 @@ +# pygame - Python Game Library +# Copyright (C) 2000-2003 Pete Shinners +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Pete Shinners +# pete@shinners.org + +"""Set of cursor resources available for use. These cursors come +in a sequence of values that are needed as the arguments for +pygame.mouse.set_cursor(). To dereference the sequence in place +and create the cursor in one step, call like this: + pygame.mouse.set_cursor(*pygame.cursors.arrow). + +Here is a list of available cursors: + arrow, diamond, ball, broken_x, tri_left, tri_right + +There is also a sample string cursor named 'thickarrow_strings'. +The compile() function can convert these string cursors into cursor byte data that can be used to +create Cursor objects. + +Alternately, you can also create Cursor objects using surfaces or cursors constants, +such as pygame.SYSTEM_CURSOR_ARROW. +""" + +import pygame + +_cursor_id_table = { + pygame.SYSTEM_CURSOR_ARROW: "SYSTEM_CURSOR_ARROW", + pygame.SYSTEM_CURSOR_IBEAM: "SYSTEM_CURSOR_IBEAM", + pygame.SYSTEM_CURSOR_WAIT: "SYSTEM_CURSOR_WAIT", + pygame.SYSTEM_CURSOR_CROSSHAIR: "SYSTEM_CURSOR_CROSSHAIR", + pygame.SYSTEM_CURSOR_WAITARROW: "SYSTEM_CURSOR_WAITARROW", + pygame.SYSTEM_CURSOR_SIZENWSE: "SYSTEM_CURSOR_SIZENWSE", + pygame.SYSTEM_CURSOR_SIZENESW: "SYSTEM_CURSOR_SIZENESW", + pygame.SYSTEM_CURSOR_SIZEWE: "SYSTEM_CURSOR_SIZEWE", + pygame.SYSTEM_CURSOR_SIZENS: "SYSTEM_CURSOR_SIZENS", + pygame.SYSTEM_CURSOR_SIZEALL: "SYSTEM_CURSOR_SIZEALL", + pygame.SYSTEM_CURSOR_NO: "SYSTEM_CURSOR_NO", + pygame.SYSTEM_CURSOR_HAND: "SYSTEM_CURSOR_HAND", +} + + +class Cursor(object): + def __init__(self, *args): + """Cursor(size, hotspot, xormasks, andmasks) -> Cursor + Cursor(hotspot, Surface) -> Cursor + Cursor(constant) -> Cursor + Cursor(Cursor) -> copies the Cursor object passed as an argument + Cursor() -> Cursor + + pygame object for representing cursors + + You can initialize a cursor from a system cursor or use the + constructor on an existing Cursor object, which will copy it. + Providing a Surface instance will render the cursor displayed + as that Surface when used. + + These Surfaces may use other colors than black and white.""" + if len(args) == 0: + self.type = "system" + self.data = (pygame.SYSTEM_CURSOR_ARROW,) + elif len(args) == 1 and args[0] in _cursor_id_table: + self.type = "system" + self.data = (args[0],) + elif len(args) == 1 and isinstance(args[0], Cursor): + self.type = args[0].type + self.data = args[0].data + elif ( + len(args) == 2 and len(args[0]) == 2 and isinstance(args[1], pygame.Surface) + ): + self.type = "color" + self.data = tuple(args) + elif len(args) == 4 and len(args[0]) == 2 and len(args[1]) == 2: + self.type = "bitmap" + # pylint: disable=consider-using-generator + # See https://github.com/pygame/pygame/pull/2509 for analysis + self.data = tuple([tuple(arg) for arg in args]) + else: + raise TypeError("Arguments must match a cursor specification") + + def __len__(self): + return len(self.data) + + def __getitem__(self, index): + return self.data[index] + + def __eq__(self, other): + return isinstance(other, Cursor) and self.data == other.data + + def __ne__(self, other): + return not self.__eq__(other) + + def __copy__(self): + """Clone the current Cursor object. + You can do the same thing by doing Cursor(Cursor).""" + return self.__class__(self) + + copy = __copy__ + + def __hash__(self): + return hash(tuple([self.type] + list(self.data))) + + def __repr__(self): + if self.type == "system": + id_string = _cursor_id_table.get(self.data[0], "constant lookup error") + return "" + if self.type == "bitmap": + size = "size: " + str(self.data[0]) + hotspot = "hotspot: " + str(self.data[1]) + return "" + if self.type == "color": + hotspot = "hotspot: " + str(self.data[0]) + surf = repr(self.data[1]) + return "" + raise TypeError("Invalid Cursor") + + +# Python side of the set_cursor function: C side in mouse.c +def set_cursor(*args): + """set_cursor(pygame.cursors.Cursor OR args for a pygame.cursors.Cursor) -> None + set the mouse cursor to a new cursor""" + cursor = Cursor(*args) + pygame.mouse._set_cursor(**{cursor.type: cursor.data}) + + +pygame.mouse.set_cursor = set_cursor +del set_cursor # cleanup namespace + +# Python side of the get_cursor function: C side in mouse.c +def get_cursor(): + """get_cursor() -> pygame.cursors.Cursor + get the current mouse cursor""" + return Cursor(*pygame.mouse._get_cursor()) + + +pygame.mouse.get_cursor = get_cursor +del get_cursor # cleanup namespace + +arrow = Cursor( + (16, 16), + (0, 0), + ( + 0x00, + 0x00, + 0x40, + 0x00, + 0x60, + 0x00, + 0x70, + 0x00, + 0x78, + 0x00, + 0x7C, + 0x00, + 0x7E, + 0x00, + 0x7F, + 0x00, + 0x7F, + 0x80, + 0x7C, + 0x00, + 0x6C, + 0x00, + 0x46, + 0x00, + 0x06, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + ), + ( + 0x40, + 0x00, + 0xE0, + 0x00, + 0xF0, + 0x00, + 0xF8, + 0x00, + 0xFC, + 0x00, + 0xFE, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x80, + 0xFF, + 0xC0, + 0xFF, + 0x80, + 0xFE, + 0x00, + 0xEF, + 0x00, + 0x4F, + 0x00, + 0x07, + 0x80, + 0x07, + 0x80, + 0x03, + 0x00, + ), +) + +diamond = Cursor( + (16, 16), + (7, 7), + ( + 0, + 0, + 1, + 0, + 3, + 128, + 7, + 192, + 14, + 224, + 28, + 112, + 56, + 56, + 112, + 28, + 56, + 56, + 28, + 112, + 14, + 224, + 7, + 192, + 3, + 128, + 1, + 0, + 0, + 0, + 0, + 0, + ), + ( + 1, + 0, + 3, + 128, + 7, + 192, + 15, + 224, + 31, + 240, + 62, + 248, + 124, + 124, + 248, + 62, + 124, + 124, + 62, + 248, + 31, + 240, + 15, + 224, + 7, + 192, + 3, + 128, + 1, + 0, + 0, + 0, + ), +) + +ball = Cursor( + (16, 16), + (7, 7), + ( + 0, + 0, + 3, + 192, + 15, + 240, + 24, + 248, + 51, + 252, + 55, + 252, + 127, + 254, + 127, + 254, + 127, + 254, + 127, + 254, + 63, + 252, + 63, + 252, + 31, + 248, + 15, + 240, + 3, + 192, + 0, + 0, + ), + ( + 3, + 192, + 15, + 240, + 31, + 248, + 63, + 252, + 127, + 254, + 127, + 254, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 127, + 254, + 127, + 254, + 63, + 252, + 31, + 248, + 15, + 240, + 3, + 192, + ), +) + +broken_x = Cursor( + (16, 16), + (7, 7), + ( + 0, + 0, + 96, + 6, + 112, + 14, + 56, + 28, + 28, + 56, + 12, + 48, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 12, + 48, + 28, + 56, + 56, + 28, + 112, + 14, + 96, + 6, + 0, + 0, + ), + ( + 224, + 7, + 240, + 15, + 248, + 31, + 124, + 62, + 62, + 124, + 30, + 120, + 14, + 112, + 0, + 0, + 0, + 0, + 14, + 112, + 30, + 120, + 62, + 124, + 124, + 62, + 248, + 31, + 240, + 15, + 224, + 7, + ), +) + +tri_left = Cursor( + (16, 16), + (1, 1), + ( + 0, + 0, + 96, + 0, + 120, + 0, + 62, + 0, + 63, + 128, + 31, + 224, + 31, + 248, + 15, + 254, + 15, + 254, + 7, + 128, + 7, + 128, + 3, + 128, + 3, + 128, + 1, + 128, + 1, + 128, + 0, + 0, + ), + ( + 224, + 0, + 248, + 0, + 254, + 0, + 127, + 128, + 127, + 224, + 63, + 248, + 63, + 254, + 31, + 255, + 31, + 255, + 15, + 254, + 15, + 192, + 7, + 192, + 7, + 192, + 3, + 192, + 3, + 192, + 1, + 128, + ), +) + +tri_right = Cursor( + (16, 16), + (14, 1), + ( + 0, + 0, + 0, + 6, + 0, + 30, + 0, + 124, + 1, + 252, + 7, + 248, + 31, + 248, + 127, + 240, + 127, + 240, + 1, + 224, + 1, + 224, + 1, + 192, + 1, + 192, + 1, + 128, + 1, + 128, + 0, + 0, + ), + ( + 0, + 7, + 0, + 31, + 0, + 127, + 1, + 254, + 7, + 254, + 31, + 252, + 127, + 252, + 255, + 248, + 255, + 248, + 127, + 240, + 3, + 240, + 3, + 224, + 3, + 224, + 3, + 192, + 3, + 192, + 1, + 128, + ), +) + + +# Here is an example string resource cursor. To use this: +# curs, mask = pygame.cursors.compile_cursor(pygame.cursors.thickarrow_strings, 'X', '.') +# pygame.mouse.set_cursor((24, 24), (0, 0), curs, mask) +# Be warned, though, that cursors created from compiled strings do not support colors. + +# sized 24x24 +thickarrow_strings = ( + "XX ", + "XXX ", + "XXXX ", + "XX.XX ", + "XX..XX ", + "XX...XX ", + "XX....XX ", + "XX.....XX ", + "XX......XX ", + "XX.......XX ", + "XX........XX ", + "XX........XXX ", + "XX......XXXXX ", + "XX.XXX..XX ", + "XXXX XX..XX ", + "XX XX..XX ", + " XX..XX ", + " XX..XX ", + " XX..XX ", + " XXXX ", + " XX ", + " ", + " ", + " ", +) + +# sized 24x16 +sizer_x_strings = ( + " X X ", + " XX XX ", + " X.X X.X ", + " X..X X..X ", + " X...XXXXXXXX...X ", + "X................X ", + " X...XXXXXXXX...X ", + " X..X X..X ", + " X.X X.X ", + " XX XX ", + " X X ", + " ", + " ", + " ", + " ", + " ", +) + +# sized 16x24 +sizer_y_strings = ( + " X ", + " X.X ", + " X...X ", + " X.....X ", + " X.......X ", + "XXXXX.XXXXX ", + " X.X ", + " X.X ", + " X.X ", + " X.X ", + " X.X ", + " X.X ", + " X.X ", + "XXXXX.XXXXX ", + " X.......X ", + " X.....X ", + " X...X ", + " X.X ", + " X ", + " ", + " ", + " ", + " ", + " ", +) + +# sized 24x16 +sizer_xy_strings = ( + "XXXXXXXX ", + "X.....X ", + "X....X ", + "X...X ", + "X..X.X ", + "X.X X.X ", + "XX X.X X ", + "X X.X XX ", + " X.XX.X ", + " X...X ", + " X...X ", + " X....X ", + " X.....X ", + " XXXXXXXX ", + " ", + " ", +) + +# sized 8x16 +textmarker_strings = ( + "ooo ooo ", + " o ", + " o ", + " o ", + " o ", + " o ", + " o ", + " o ", + " o ", + " o ", + " o ", + "ooo ooo ", + " ", + " ", + " ", + " ", +) + + +def compile(strings, black="X", white=".", xor="o"): + """pygame.cursors.compile(strings, black, white, xor) -> data, mask + compile cursor strings into cursor data + + This takes a set of strings with equal length and computes + the binary data for that cursor. The string widths must be + divisible by 8. + + The black and white arguments are single letter strings that + tells which characters will represent black pixels, and which + characters represent white pixels. All other characters are + considered clear. + + Some systems allow you to set a special toggle color for the + system color, this is also called the xor color. If the system + does not support xor cursors, that color will simply be black. + + This returns a tuple containing the cursor data and cursor mask + data. Both these arguments are used when setting a cursor with + pygame.mouse.set_cursor(). + """ + # first check for consistent lengths + size = len(strings[0]), len(strings) + if size[0] % 8 or size[1] % 8: + raise ValueError(f"cursor string sizes must be divisible by 8 {size}") + + for s in strings[1:]: + if len(s) != size[0]: + raise ValueError("Cursor strings are inconsistent lengths") + + # create the data arrays. + # this could stand a little optimizing + maskdata = [] + filldata = [] + maskitem = fillitem = 0 + step = 8 + for s in strings: + for c in s: + maskitem = maskitem << 1 + fillitem = fillitem << 1 + step = step - 1 + if c == black: + maskitem = maskitem | 1 + fillitem = fillitem | 1 + elif c == white: + maskitem = maskitem | 1 + elif c == xor: + fillitem = fillitem | 1 + + if not step: + maskdata.append(maskitem) + filldata.append(fillitem) + maskitem = fillitem = 0 + step = 8 + + return tuple(filldata), tuple(maskdata) + + +def load_xbm(curs, mask): + """pygame.cursors.load_xbm(cursorfile, maskfile) -> cursor_args + reads a pair of XBM files into set_cursor arguments + + Arguments can either be filenames or filelike objects + with the readlines method. Not largely tested, but + should work with typical XBM files. + """ + + def bitswap(num): + val = 0 + for x in range(8): + b = num & (1 << x) != 0 + val = val << 1 | b + return val + + if hasattr(curs, "readlines"): + curs = curs.readlines() + else: + with open(curs, encoding="ascii") as cursor_f: + curs = cursor_f.readlines() + + if hasattr(mask, "readlines"): + mask = mask.readlines() + else: + with open(mask, encoding="ascii") as mask_f: + mask = mask_f.readlines() + + # avoid comments + for i, line in enumerate(curs): + if line.startswith("#define"): + curs = curs[i:] + break + + for i, line in enumerate(mask): + if line.startswith("#define"): + mask = mask[i:] + break + + # load width,height + width = int(curs[0].split()[-1]) + height = int(curs[1].split()[-1]) + # load hotspot position + if curs[2].startswith("#define"): + hotx = int(curs[2].split()[-1]) + hoty = int(curs[3].split()[-1]) + else: + hotx = hoty = 0 + + info = width, height, hotx, hoty + + possible_starts = ("static char", "static unsigned char") + for i, line in enumerate(curs): + if line.startswith(possible_starts): + break + data = " ".join(curs[i + 1 :]).replace("};", "").replace(",", " ") + cursdata = [] + for x in data.split(): + cursdata.append(bitswap(int(x, 16))) + cursdata = tuple(cursdata) + for i, line in enumerate(mask): + if line.startswith(possible_starts): + break + data = " ".join(mask[i + 1 :]).replace("};", "").replace(",", " ") + maskdata = [] + for x in data.split(): + maskdata.append(bitswap(int(x, 16))) + + maskdata = tuple(maskdata) + return info[:2], info[2:], cursdata, maskdata diff --git a/.venv/lib/python3.8/site-packages/pygame/cursors.pyi b/.venv/lib/python3.8/site-packages/pygame/cursors.pyi new file mode 100644 index 0000000..834b457 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/cursors.pyi @@ -0,0 +1,86 @@ +from typing import Iterator, List, Tuple, Sequence, Iterable, Union, overload + +from pygame.surface import Surface + +_Small_string = Tuple[ + str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str +] +_Big_string = Tuple[ + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, + str, +] + +arrow: Cursor +diamond: Cursor +broken_x: Cursor +tri_left: Cursor +tri_right: Cursor +thickarrow_strings: _Big_string +sizer_x_strings: _Small_string +sizer_y_strings: _Big_string +sizer_xy_strings: _Small_string + +def compile( + strings: Sequence[str], + black: str = "X", + white: str = ".", + xor: str = "o", +) -> Tuple[Sequence[int], Sequence[int]]: ... +def load_xbm( + cursorfile: str, maskfile: str +) -> Tuple[List[int], List[int], Tuple[int, ...], Tuple[int, ...]]: ... + +class Cursor(Iterable[object]): + @overload + def __init__(self, constant: int = ...) -> None: ... + @overload + def __init__(self, cursor: Cursor) -> None: ... + @overload + def __init__( + self, + size: Union[Tuple[int, int], List[int]], + hotspot: Union[Tuple[int, int], List[int]], + xormasks: Sequence[int], + andmasks: Sequence[int], + ) -> None: ... + @overload + def __init__( + self, + hotspot: Union[Tuple[int, int], List[int]], + surface: Surface, + ) -> None: ... + def __iter__(self) -> Iterator[object]: ... + def __len__(self) -> int: ... + type: str + data: Union[ + Tuple[int], + Tuple[ + Union[Tuple[int, int], List[int]], + Union[Tuple[int, int], List[int]], + Sequence[int], + Sequence[int], + ], + Tuple[int, Surface], + ] diff --git a/.venv/lib/python3.8/site-packages/pygame/display.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/display.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..953aea1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/display.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/display.pyi b/.venv/lib/python3.8/site-packages/pygame/display.pyi new file mode 100644 index 0000000..bfcf865 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/display.pyi @@ -0,0 +1,68 @@ +from typing import Union, Tuple, List, Optional, Dict, Sequence + +from pygame.surface import Surface +from pygame.constants import FULLSCREEN +from ._common import _Coordinate, _RectValue, _ColorValue, _RgbaOutput + +class _VidInfo: + hw: int + wm: int + video_mem: int + bitsize: int + bytesize: int + masks: _RgbaOutput + shifts: _RgbaOutput + losses: _RgbaOutput + blit_hw: int + blit_hw_CC: int + blit_hw_A: int + blit_sw: int + blit_sw_CC: int + blit_sw_A: int + current_h: int + current_w: int + +def init() -> None: ... +def quit() -> None: ... +def get_init() -> bool: ... +def set_mode( + size: _Coordinate = (0, 0), + flags: int = 0, + depth: int = 0, + display: int = 0, + vsync: int = 0, +) -> Surface: ... +def get_surface() -> Surface: ... +def flip() -> None: ... +def update(rectangle: Optional[Union[_RectValue, List[_RectValue]]] = None) -> None: ... +def get_driver() -> str: ... +def Info() -> _VidInfo: ... +def get_wm_info() -> Dict[str, int]: ... +def list_modes( + depth: int = 0, + flags: int = FULLSCREEN, + display: int = 0, +) -> List[Tuple[int, int]]: ... +def mode_ok( + size: Union[Sequence[int], Tuple[int, int]], + flags: int = 0, + depth: int = 0, + display: int = 0, +) -> int: ... +def gl_get_attribute(flag: int) -> int: ... +def gl_set_attribute(flag: int, value: int) -> None: ... +def get_active() -> bool: ... +def iconify() -> bool: ... +def toggle_fullscreen() -> int: ... +def set_gamma(red: float, green: float = ..., blue: float = ...) -> int: ... +def set_gamma_ramp( + red: Sequence[int], green: Sequence[int], blue: Sequence[int] +) -> int: ... +def set_icon(surface: Surface) -> None: ... +def set_caption(title: str, icontitle: Optional[str] = None) -> None: ... +def get_caption() -> Tuple[str, str]: ... +def set_palette(palette: Sequence[_ColorValue]) -> None: ... +def get_num_displays() -> int: ... +def get_window_size() -> Tuple[int, int]: ... +def get_allow_screensaver() -> bool: ... +def set_allow_screensaver(value: bool = True) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/__main__.py b/.venv/lib/python3.8/site-packages/pygame/docs/__main__.py new file mode 100644 index 0000000..28b95bb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/__main__.py @@ -0,0 +1,38 @@ +# python -m pygame.docs + +import os +import webbrowser +from urllib.parse import quote, urlunparse + + +def _iterpath(path): + path, last = os.path.split(path) + if last: + for p in _iterpath(path): + yield p + yield last + + +# for test suite to confirm pygame built with local docs +def has_local_docs(): + pkg_dir = os.path.dirname(os.path.abspath(__file__)) + main_page = os.path.join(pkg_dir, "generated", "index.html") + return os.path.exists(main_page) + + +def open_docs(): + pkg_dir = os.path.dirname(os.path.abspath(__file__)) + main_page = os.path.join(pkg_dir, "generated", "index.html") + if os.path.exists(main_page): + url_path = quote("/".join(_iterpath(main_page))) + drive, rest = os.path.splitdrive(__file__) + if drive: + url_path = "%s/%s" % (drive, url_path) + url = urlunparse(("file", "", url_path, "", "", "")) + else: + url = "https://www.pygame.org/docs/" + webbrowser.open(url) + + +if __name__ == "__main__": + open_docs() diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/docs/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..1d27a02 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput1.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput1.gif new file mode 100644 index 0000000..8c7f7ea Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput1.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput11.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput11.gif new file mode 100644 index 0000000..8c7f7ea Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput11.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput2.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput2.gif new file mode 100644 index 0000000..2094e3a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput2.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput21.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput21.gif new file mode 100644 index 0000000..2094e3a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput21.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput3.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput3.gif new file mode 100644 index 0000000..3a0d748 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput3.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput31.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput31.gif new file mode 100644 index 0000000..3a0d748 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput31.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput4.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput4.gif new file mode 100644 index 0000000..415644e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput4.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput41.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput41.gif new file mode 100644 index 0000000..415644e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput41.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput5.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput5.gif new file mode 100644 index 0000000..5796b4e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput5.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput51.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput51.gif new file mode 100644 index 0000000..5796b4e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedInputOutput51.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha1.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha1.gif new file mode 100644 index 0000000..c0d4124 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha1.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha11.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha11.gif new file mode 100644 index 0000000..c0d4124 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha11.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha2.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha2.gif new file mode 100644 index 0000000..00b443d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha2.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha21.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha21.gif new file mode 100644 index 0000000..00b443d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha21.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha3.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha3.gif new file mode 100644 index 0000000..be1204d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha3.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha31.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha31.gif new file mode 100644 index 0000000..be1204d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputAlpha31.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess1.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess1.gif new file mode 100644 index 0000000..20edf21 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess1.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess11.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess11.gif new file mode 100644 index 0000000..20edf21 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess11.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess2.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess2.gif new file mode 100644 index 0000000..5445c0e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess2.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess21.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess21.gif new file mode 100644 index 0000000..5445c0e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess21.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess3.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess3.gif new file mode 100644 index 0000000..0da81f1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess3.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess31.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess31.gif new file mode 100644 index 0000000..0da81f1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess31.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess4.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess4.gif new file mode 100644 index 0000000..18506fa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess4.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess41.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess41.gif new file mode 100644 index 0000000..18506fa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess41.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess5.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess5.gif new file mode 100644 index 0000000..def0e06 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess5.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess51.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess51.gif new file mode 100644 index 0000000..def0e06 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess51.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess6.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess6.gif new file mode 100644 index 0000000..127cc1e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess6.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess61.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess61.gif new file mode 100644 index 0000000..127cc1e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/AdvancedOutputProcess61.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-resultscreen.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-resultscreen.png new file mode 100644 index 0000000..9280cbe Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-resultscreen.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-resultscreen1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-resultscreen1.png new file mode 100644 index 0000000..9280cbe Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-resultscreen1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-sourcecode.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-sourcecode.png new file mode 100644 index 0000000..d378854 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-sourcecode.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-sourcecode1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-sourcecode1.png new file mode 100644 index 0000000..d378854 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-INPUT-sourcecode1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-resultscreen.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-resultscreen.png new file mode 100644 index 0000000..f4e8322 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-resultscreen.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-resultscreen1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-resultscreen1.png new file mode 100644 index 0000000..f4e8322 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-resultscreen1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-sourcecode.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-sourcecode.png new file mode 100644 index 0000000..b20873a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-sourcecode.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-sourcecode1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-sourcecode1.png new file mode 100644 index 0000000..b20873a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-PROCESS-sourcecode1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-ouput-result-screen.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-ouput-result-screen.png new file mode 100644 index 0000000..d8628e1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-ouput-result-screen.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-ouput-result-screen1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-ouput-result-screen1.png new file mode 100644 index 0000000..d8628e1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Bagic-ouput-result-screen1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Basic-ouput-sourcecode.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Basic-ouput-sourcecode.png new file mode 100644 index 0000000..659c981 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Basic-ouput-sourcecode.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Basic-ouput-sourcecode1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Basic-ouput-sourcecode1.png new file mode 100644 index 0000000..659c981 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/Basic-ouput-sourcecode1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_average.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_average.jpg new file mode 100644 index 0000000..043e485 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_average.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_background.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_background.jpg new file mode 100644 index 0000000..cfcbd02 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_background.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_green.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_green.jpg new file mode 100644 index 0000000..24c4b09 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_green.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_hsv.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_hsv.jpg new file mode 100644 index 0000000..afe9559 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_hsv.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_mask.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_mask.jpg new file mode 100644 index 0000000..cd8ed1d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_mask.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_rgb.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_rgb.jpg new file mode 100644 index 0000000..3401421 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_rgb.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_thresh.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_thresh.jpg new file mode 100644 index 0000000..cb2426f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_thresh.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_thresholded.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_thresholded.jpg new file mode 100644 index 0000000..c4ee891 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_thresholded.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_yuv.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_yuv.jpg new file mode 100644 index 0000000..1cda596 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/camera_yuv.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/chimpshot.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/chimpshot.gif new file mode 100644 index 0000000..c27191d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/chimpshot.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/draw_module_example.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/draw_module_example.png new file mode 100644 index 0000000..c6c832d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/draw_module_example.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_ball.gif b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_ball.gif new file mode 100644 index 0000000..bbc4a95 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_ball.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_blade.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_blade.jpg new file mode 100644 index 0000000..2ed490c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_blade.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_freedom.jpg b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_freedom.jpg new file mode 100644 index 0000000..9ed473d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/intro_freedom.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-Battleship.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-Battleship.png new file mode 100644 index 0000000..35674f6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-Battleship.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-Battleship1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-Battleship1.png new file mode 100644 index 0000000..35674f6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-Battleship1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-PuyoPuyo.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-PuyoPuyo.png new file mode 100644 index 0000000..dc104ea Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-PuyoPuyo.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-PuyoPuyo1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-PuyoPuyo1.png new file mode 100644 index 0000000..dc104ea Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-PuyoPuyo1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-TPS.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-TPS.png new file mode 100644 index 0000000..9c8cb0c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-TPS.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-TPS1.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-TPS1.png new file mode 100644 index 0000000..9c8cb0c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/introduction-TPS1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/joystick_calls.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/joystick_calls.png new file mode 100644 index 0000000..93b80eb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/joystick_calls.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_allblack.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_allblack.png new file mode 100644 index 0000000..80cbc35 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_allblack.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_flipped.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_flipped.png new file mode 100644 index 0000000..742f4f3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_flipped.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_redimg.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_redimg.png new file mode 100644 index 0000000..58e9c3f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_redimg.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_rgbarray.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_rgbarray.png new file mode 100644 index 0000000..8513ef7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_rgbarray.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_scaledown.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_scaledown.png new file mode 100644 index 0000000..0be3541 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_scaledown.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_scaleup.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_scaleup.png new file mode 100644 index 0000000..ff1f15e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_scaleup.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_soften.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_soften.png new file mode 100644 index 0000000..c1d4ce4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_soften.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_striped.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_striped.png new file mode 100644 index 0000000..2ca4a79 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_striped.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_xfade.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_xfade.png new file mode 100644 index 0000000..8b90706 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/surfarray_xfade.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_basic.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_basic.png new file mode 100644 index 0000000..849adb5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_basic.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_event-flowchart.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_event-flowchart.png new file mode 100644 index 0000000..6a33613 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_event-flowchart.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_formulae.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_formulae.png new file mode 100644 index 0000000..f04b482 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_formulae.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_radians.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_radians.png new file mode 100644 index 0000000..4569df1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_images/tom_radians.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/c_api.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/c_api.rst.txt new file mode 100644 index 0000000..66d878f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/c_api.rst.txt @@ -0,0 +1,26 @@ +pygame C API +============ + +.. toctree:: + :maxdepth: 1 + :glob: + + c_api/slots.rst + c_api/base.rst + c_api/bufferproxy.rst + c_api/cdrom.rst + c_api/color.rst + c_api/display.rst + c_api/event.rst + c_api/freetype.rst + c_api/mixer.rst + c_api/rect.rst + c_api/rwobject.rst + c_api/surface.rst + c_api/surflock.rst + c_api/version.rst + + +src_c/include/ contains header files for applications +that use the pygame C API, while src_c/ contains +headers used by pygame internally. diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/filepaths.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/filepaths.rst.txt new file mode 100644 index 0000000..e17f687 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/filepaths.rst.txt @@ -0,0 +1,17 @@ +File Path Function Arguments +============================ + +A pygame function or method which takes a file path argument will accept +either a Unicode or a byte (8-bit or ASCII character) string. +Unicode strings are translated to Python's default filesystem encoding, +as returned by sys.getfilesystemencoding(). A Unicode code point +above U+FFFF (``\uFFFF``) can be coded directly with a 32-bit escape sequences +(``\Uxxxxxxxx``), even for Python interpreters built with an UCS-2 +(16-bit character) Unicode type. Byte strings are passed +to the operating system unchanged. + +Null characters (``\x00``) are not permitted in the path, raising an exception. +An exception is also raised if an Unicode file path cannot be encoded. +How UTF-16 surrogate codes are handled is Python-interpreter-dependent. +Use UTF-32 code points and 32-bit escape sequences instead. +The exception types are function-dependent. diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/index.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/index.rst.txt new file mode 100644 index 0000000..f1a700c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/index.rst.txt @@ -0,0 +1,190 @@ +.. Pygame documentation master file, created by + sphinx-quickstart on Sat Mar 5 11:56:39 2011. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Pygame Front Page +================= + +.. toctree:: + :maxdepth: 2 + :glob: + :hidden: + + ref/* + tut/* + tut/en/**/* + tut/ko/**/* + c_api + filepaths + +Documents +--------- + +`Readme`_ + Basic information about pygame: what it is, who is involved, and where to find it. + +`Install`_ + Steps needed to compile pygame on several platforms. + Also help on finding and installing prebuilt binaries for your system. + +`File Path Function Arguments`_ + How pygame handles file system paths. + +`LGPL License`_ + This is the license pygame is distributed under. + It provides for pygame to be distributed with open source and commercial software. + Generally, if pygame is not changed, it can be used with any type of program. + +Tutorials +--------- + +:doc:`Introduction to Pygame ` + An introduction to the basics of pygame. + This is written for users of Python and appeared in volume two of the Py magazine. + +:doc:`Import and Initialize ` + The beginning steps on importing and initializing pygame. + The pygame package is made of several modules. + Some modules are not included on all platforms. + +:doc:`How do I move an Image? ` + A basic tutorial that covers the concepts behind 2D computer animation. + Information about drawing and clearing objects to make them appear animated. + +:doc:`Chimp Tutorial, Line by Line ` + The pygame examples include a simple program with an interactive fist and a chimpanzee. + This was inspired by the annoying flash banner of the early 2000s. + This tutorial examines every line of code used in the example. + +:doc:`Sprite Module Introduction ` + Pygame includes a higher level sprite module to help organize games. + The sprite module includes several classes that help manage details found in almost all games types. + The Sprite classes are a bit more advanced than the regular pygame modules, + and need more understanding to be properly used. + +:doc:`Surfarray Introduction ` + Pygame used the NumPy python module to allow efficient per pixel effects on images. + Using the surface arrays is an advanced feature that allows custom effects and filters. + This also examines some of the simple effects from the pygame example, arraydemo.py. + +:doc:`Camera Module Introduction ` + Pygame, as of 1.9, has a camera module that allows you to capture images, + watch live streams, and do some basic computer vision. + This tutorial covers those use cases. + +:doc:`Newbie Guide ` + A list of thirteen helpful tips for people to get comfortable using pygame. + +:doc:`Making Games Tutorial ` + A large tutorial that covers the bigger topics needed to create an entire game. + +:doc:`Display Modes ` + Getting a display surface for the screen. + +:doc:`한국어 튜토리얼 (Korean Tutorial) ` + ë¹¨ê°„ë¸”ë¡ ê²€ì€ë¸”ë¡ + +Reference +--------- + +:ref:`genindex` + A list of all functions, classes, and methods in the pygame package. + +:doc:`ref/bufferproxy` + An array protocol view of surface pixels + +:doc:`ref/color` + Color representation. + +:doc:`ref/cursors` + Loading and compiling cursor images. + +:doc:`ref/display` + Configure the display surface. + +:doc:`ref/draw` + Drawing simple shapes like lines and ellipses to surfaces. + +:doc:`ref/event` + Manage the incoming events from various input devices and the windowing platform. + +:doc:`ref/examples` + Various programs demonstrating the use of individual pygame modules. + +:doc:`ref/font` + Loading and rendering TrueType fonts. + +:doc:`ref/freetype` + Enhanced pygame module for loading and rendering font faces. + +:doc:`ref/gfxdraw` + Anti-aliasing draw functions. + +:doc:`ref/image` + Loading, saving, and transferring of surfaces. + +:doc:`ref/joystick` + Manage the joystick devices. + +:doc:`ref/key` + Manage the keyboard device. + +:doc:`ref/locals` + Pygame constants. + +:doc:`ref/mixer` + Load and play sounds + +:doc:`ref/mouse` + Manage the mouse device and display. + +:doc:`ref/music` + Play streaming music tracks. + +:doc:`ref/pygame` + Top level functions to manage pygame. + +:doc:`ref/pixelarray` + Manipulate image pixel data. + +:doc:`ref/rect` + Flexible container for a rectangle. + +:doc:`ref/scrap` + Native clipboard access. + +:doc:`ref/sndarray` + Manipulate sound sample data. + +:doc:`ref/sprite` + Higher level objects to represent game images. + +:doc:`ref/surface` + Objects for images and the screen. + +:doc:`ref/surfarray` + Manipulate image pixel data. + +:doc:`ref/tests` + Test pygame. + +:doc:`ref/time` + Manage timing and framerate. + +:doc:`ref/transform` + Resize and move images. + +:doc:`pygame C API ` + The C api shared amongst pygame extension modules. + +:ref:`search` + Search pygame documents by keyword. + +.. _Readme: ../wiki/about + +.. _Install: ../wiki/GettingStarted#Pygame%20Installation + +.. _File Path Function Arguments: filepaths.html + +.. _LGPL License: LGPL.txt diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/bufferproxy.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/bufferproxy.rst.txt new file mode 100644 index 0000000..bb4e693 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/bufferproxy.rst.txt @@ -0,0 +1,113 @@ +.. include:: common.txt + +.. default-domain:: py + +:class:`pygame.BufferProxy` +=========================== + +.. currentmodule:: pygame + +.. class:: BufferProxy + + | :sl:`pygame object to export a surface buffer through an array protocol` + | :sg:`BufferProxy() -> BufferProxy` + + :class:`BufferProxy` is a pygame support type, designed as the return value + of the :meth:`Surface.get_buffer` and :meth:`Surface.get_view` methods. + For all Python versions a :class:`BufferProxy` object exports a C struct + and Python level array interface on behalf of its parent object's buffer. + A new buffer interface is also exported. + In pygame, :class:`BufferProxy` is key to implementing the + :mod:`pygame.surfarray` module. + + :class:`BufferProxy` instances can be created directly from Python code, + either for a parent that exports an interface, or from a Python ``dict`` + describing an object's buffer layout. The dict entries are based on the + Python level array interface mapping. The following keys are recognized: + + ``"shape"`` : tuple + The length of each array dimension as a tuple of integers. The + length of the tuple is the number of dimensions in the array. + + ``"typestr"`` : string + The array element type as a length 3 string. The first character + gives byteorder, '<' for little-endian, '>' for big-endian, and + '\|' for not applicable. The second character is the element type, + 'i' for signed integer, 'u' for unsigned integer, 'f' for floating + point, and 'V' for an chunk of bytes. The third character gives the + bytesize of the element, from '1' to '9' bytes. So, for example, + " Surface` + | :sg:`parent -> ` + + The :class:`Surface` which returned the :class:`BufferProxy` object or + the object passed to a :class:`BufferProxy` call. + + .. attribute:: length + + | :sl:`The size, in bytes, of the exported buffer.` + | :sg:`length -> int` + + The number of valid bytes of data exported. For discontinuous data, + that is data which is not a single block of memory, the bytes within + the gaps are excluded from the count. This property is equivalent to + the ``Py_buffer`` C struct ``len`` field. + + .. attribute:: raw + + | :sl:`A copy of the exported buffer as a single block of bytes.` + | :sg:`raw -> bytes` + + The buffer data as a ``str``/``bytes`` object. + Any gaps in the exported data are removed. + + .. method:: write + + | :sl:`Write raw bytes to object buffer.` + | :sg:`write(buffer, offset=0)` + + Overwrite bytes in the parent object's data. The data must be C or F + contiguous, otherwise a ValueError is raised. Argument `buffer` is a + ``str``/``bytes`` object. An optional offset gives a + start position, in bytes, within the buffer where overwriting begins. + If the offset is negative or greater that or equal to the buffer proxy's + :attr:`length` value, an ``IndexException`` is raised. + If ``len(buffer) > proxy.length + offset``, a ``ValueError`` is raised. diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/camera.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/camera.rst.txt new file mode 100644 index 0000000..b9377df --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/camera.rst.txt @@ -0,0 +1,247 @@ +.. include:: common.txt + +:mod:`pygame.camera` +==================== + +.. module:: pygame.camera + :synopsis: pygame module for camera use + +| :sl:`pygame module for camera use` + +Pygame currently supports Linux (V4L2) and Windows (MSMF) cameras natively, +with wider platform support available via an integrated OpenCV backend. + +.. versionadded:: 2.0.2 Windows native camera support +.. versionadded:: 2.0.3 New OpenCV backends + +EXPERIMENTAL!: This API may change or disappear in later pygame releases. If +you use this, your code will very likely break with the next pygame release. + +The Bayer to ``RGB`` function is based on: + +:: + + Sonix SN9C101 based webcam basic I/F routines + Copyright (C) 2004 Takafumi Mizuno + 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. + THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + +New in pygame 1.9.0. + +.. function:: init + + | :sl:`Module init` + | :sg:`init(backend = None) -> None` + + This function starts up the camera module, choosing the best webcam backend + it can find for your system. This is not guaranteed to succeed, and may even + attempt to import third party modules, like `OpenCV`. If you want to + override its backend choice, you can call pass the name of the backend you + want into this function. More about backends in + :func:`get_backends()`. + + .. versionchanged:: 2.0.3 Option to explicitly select backend + + .. ## pygame.camera.init ## + +.. function:: get_backends + + | :sl:`Get the backends supported on this system` + | :sg:`get_backends() -> [str]` + + This function returns every backend it thinks has a possibility of working + on your system, in order of priority. + + pygame.camera Backends: + :: + + Backend OS Description + --------------------------------------------------------------------------------- + _camera (MSMF) Windows Builtin, works on Windows 8+ Python3 + _camera (V4L2) Linux Builtin + OpenCV Any Uses `opencv-python` module, can't enumerate cameras + OpenCV-Mac Mac Same as OpenCV, but has camera enumeration + VideoCapture Windows Uses abandoned `VideoCapture` module, can't enumerate + cameras, may be removed in the future + + There are two main differences among backends. + + The _camera backends are built in to pygame itself, and require no third + party imports. All the other backends do. For the OpenCV and VideoCapture + backends, those modules need to be installed on your system. + + The other big difference is "camera enumeration." Some backends don't have + a way to list out camera names, or even the number of cameras on the + system. In these cases, :func:`list_cameras()` will return + something like ``[0]``. If you know you have multiple cameras on the + system, these backend ports will pass through a "camera index number" + through if you use that as the ``device`` parameter. + + .. versionadded:: 2.0.3 + + .. ## pygame.camera.get_backends ## + +.. function:: colorspace + + | :sl:`Surface colorspace conversion` + | :sg:`colorspace(Surface, format, DestSurface = None) -> Surface` + + Allows for conversion from "RGB" to a destination colorspace of "HSV" or + "YUV". The source and destination surfaces must be the same size and pixel + depth. This is useful for computer vision on devices with limited processing + power. Capture as small of an image as possible, ``transform.scale()`` it + even smaller, and then convert the colorspace to ``YUV`` or ``HSV`` before + doing any processing on it. + + .. ## pygame.camera.colorspace ## + +.. function:: list_cameras + + | :sl:`returns a list of available cameras` + | :sg:`list_cameras() -> [cameras]` + + Checks the computer for available cameras and returns a list of strings of + camera names, ready to be fed into :class:`pygame.camera.Camera`. + + If the camera backend doesn't support webcam enumeration, this will return + something like ``[0]``. See :func:`get_backends()` for much more + information. + + .. ## pygame.camera.list_cameras ## + +.. class:: Camera + + | :sl:`load a camera` + | :sg:`Camera(device, (width, height), format) -> Camera` + + Loads a camera. On Linux, the device is typically something like + "/dev/video0". Default width and height are 640 by 480. + Format is the desired colorspace of the output. + This is useful for computer vision purposes. The default is + ``RGB``. The following are supported: + + * ``RGB`` - Red, Green, Blue + + * ``YUV`` - Luma, Blue Chrominance, Red Chrominance + + * ``HSV`` - Hue, Saturation, Value + + .. method:: start + + | :sl:`opens, initializes, and starts capturing` + | :sg:`start() -> None` + + Opens the camera device, attempts to initialize it, and begins recording + images to a buffer. The camera must be started before any of the below + functions can be used. + + .. ## Camera.start ## + + .. method:: stop + + | :sl:`stops, uninitializes, and closes the camera` + | :sg:`stop() -> None` + + Stops recording, uninitializes the camera, and closes it. Once a camera + is stopped, the below functions cannot be used until it is started again. + + .. ## Camera.stop ## + + .. method:: get_controls + + | :sl:`gets current values of user controls` + | :sg:`get_controls() -> (hflip = bool, vflip = bool, brightness)` + + If the camera supports it, get_controls will return the current settings + for horizontal and vertical image flip as bools and brightness as an int. + If unsupported, it will return the default values of (0, 0, 0). Note that + the return values here may be different than those returned by + set_controls, though these are more likely to be correct. + + .. ## Camera.get_controls ## + + .. method:: set_controls + + | :sl:`changes camera settings if supported by the camera` + | :sg:`set_controls(hflip = bool, vflip = bool, brightness) -> (hflip = bool, vflip = bool, brightness)` + + Allows you to change camera settings if the camera supports it. The + return values will be the input values if the camera claims it succeeded + or the values previously in use if not. Each argument is optional, and + the desired one can be chosen by supplying the keyword, like hflip. Note + that the actual settings being used by the camera may not be the same as + those returned by set_controls. On Windows, :code:`hflip` and :code:`vflip` are + implemented by pygame, not by the Camera, so they should always work, but + :code:`brightness` is unsupported. + + .. ## Camera.set_controls ## + + .. method:: get_size + + | :sl:`returns the dimensions of the images being recorded` + | :sg:`get_size() -> (width, height)` + + Returns the current dimensions of the images being captured by the + camera. This will return the actual size, which may be different than the + one specified during initialization if the camera did not support that + size. + + .. ## Camera.get_size ## + + .. method:: query_image + + | :sl:`checks if a frame is ready` + | :sg:`query_image() -> bool` + + If an image is ready to get, it returns true. Otherwise it returns false. + Note that some webcams will always return False and will only queue a + frame when called with a blocking function like :func:`get_image()`. + On Windows (MSMF), and the OpenCV backends, :func:`query_image()` + should be reliable, though. This is useful to separate the framerate of + the game from that of the camera without having to use threading. + + .. ## Camera.query_image ## + + .. method:: get_image + + | :sl:`captures an image as a Surface` + | :sg:`get_image(Surface = None) -> Surface` + + Pulls an image off of the buffer as an ``RGB`` Surface. It can optionally + reuse an existing Surface to save time. The bit-depth of the surface is + 24 bits on Linux, 32 bits on Windows, or the same as the optionally + supplied Surface. + + .. ## Camera.get_image ## + + .. method:: get_raw + + | :sl:`returns an unmodified image as bytes` + | :sg:`get_raw() -> bytes` + + Gets an image from a camera as a string in the native pixelformat of the + camera. Useful for integration with other libraries. This returns a + bytes object + + .. ## Camera.get_raw ## + + .. ## pygame.camera.Camera ## + +.. ## pygame.camera ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/cdrom.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/cdrom.rst.txt new file mode 100644 index 0000000..62688c9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/cdrom.rst.txt @@ -0,0 +1,310 @@ +.. include:: common.txt + +:mod:`pygame.cdrom` +=================== + +.. module:: pygame.cdrom + :synopsis: pygame module for audio cdrom control + +| :sl:`pygame module for audio cdrom control` + +.. warning:: + This module is non functional in pygame 2.0 and above, unless you have manually compiled pygame with SDL1. + This module will not be supported in the future. + One alternative for python cdrom functionality is `pycdio `_. + +The cdrom module manages the ``CD`` and ``DVD`` drives on a computer. It can +also control the playback of audio CDs. This module needs to be initialized +before it can do anything. Each ``CD`` object you create represents a cdrom +drive and must also be initialized individually before it can do most things. + +.. function:: init + + | :sl:`initialize the cdrom module` + | :sg:`init() -> None` + + Initialize the cdrom module. This will scan the system for all ``CD`` + devices. The module must be initialized before any other functions will + work. This automatically happens when you call ``pygame.init()``. + + It is safe to call this function more than once. + + .. ## pygame.cdrom.init ## + +.. function:: quit + + | :sl:`uninitialize the cdrom module` + | :sg:`quit() -> None` + + Uninitialize the cdrom module. After you call this any existing ``CD`` + objects will no longer work. + + It is safe to call this function more than once. + + .. ## pygame.cdrom.quit ## + +.. function:: get_init + + | :sl:`true if the cdrom module is initialized` + | :sg:`get_init() -> bool` + + Test if the cdrom module is initialized or not. This is different than the + ``CD.init()`` since each drive must also be initialized individually. + + .. ## pygame.cdrom.get_init ## + +.. function:: get_count + + | :sl:`number of cd drives on the system` + | :sg:`get_count() -> count` + + Return the number of cd drives on the system. When you create ``CD`` objects + you need to pass an integer id that must be lower than this count. The count + will be 0 if there are no drives on the system. + + .. ## pygame.cdrom.get_count ## + +.. class:: CD + + | :sl:`class to manage a cdrom drive` + | :sg:`CD(id) -> CD` + + You can create a ``CD`` object for each cdrom on the system. Use + ``pygame.cdrom.get_count()`` to determine how many drives actually exist. + The id argument is an integer of the drive, starting at zero. + + The ``CD`` object is not initialized, you can only call ``CD.get_id()`` and + ``CD.get_name()`` on an uninitialized drive. + + It is safe to create multiple ``CD`` objects for the same drive, they will + all cooperate normally. + + .. method:: init + + | :sl:`initialize a cdrom drive for use` + | :sg:`init() -> None` + + Initialize the cdrom drive for use. The drive must be initialized for + most ``CD`` methods to work. Even if the rest of pygame has been + initialized. + + There may be a brief pause while the drive is initialized. Avoid + ``CD.init()`` if the program should not stop for a second or two. + + .. ## CD.init ## + + .. method:: quit + + | :sl:`uninitialize a cdrom drive for use` + | :sg:`quit() -> None` + + Uninitialize a drive for use. Call this when your program will not be + accessing the drive for awhile. + + .. ## CD.quit ## + + .. method:: get_init + + | :sl:`true if this cd device initialized` + | :sg:`get_init() -> bool` + + Test if this ``CDROM`` device is initialized. This is different than the + ``pygame.cdrom.init()`` since each drive must also be initialized + individually. + + .. ## CD.get_init ## + + .. method:: play + + | :sl:`start playing audio` + | :sg:`play(track, start=None, end=None) -> None` + + Playback audio from an audio cdrom in the drive. Besides the track number + argument, you can also pass a starting and ending time for playback. The + start and end time are in seconds, and can limit the section of an audio + track played. + + If you pass a start time but no end, the audio will play to the end of + the track. If you pass a start time and 'None' for the end time, the + audio will play to the end of the entire disc. + + See the ``CD.get_numtracks()`` and ``CD.get_track_audio()`` to find + tracks to playback. + + Note, track 0 is the first track on the ``CD``. Track numbers start at + zero. + + .. ## CD.play ## + + .. method:: stop + + | :sl:`stop audio playback` + | :sg:`stop() -> None` + + Stops playback of audio from the cdrom. This will also lose the current + playback position. This method does nothing if the drive isn't already + playing audio. + + .. ## CD.stop ## + + .. method:: pause + + | :sl:`temporarily stop audio playback` + | :sg:`pause() -> None` + + Temporarily stop audio playback on the ``CD``. The playback can be + resumed at the same point with the ``CD.resume()`` method. If the ``CD`` + is not playing this method does nothing. + + Note, track 0 is the first track on the ``CD``. Track numbers start at + zero. + + .. ## CD.pause ## + + .. method:: resume + + | :sl:`unpause audio playback` + | :sg:`resume() -> None` + + Unpause a paused ``CD``. If the ``CD`` is not paused or already playing, + this method does nothing. + + .. ## CD.resume ## + + .. method:: eject + + | :sl:`eject or open the cdrom drive` + | :sg:`eject() -> None` + + This will open the cdrom drive and eject the cdrom. If the drive is + playing or paused it will be stopped. + + .. ## CD.eject ## + + .. method:: get_id + + | :sl:`the index of the cdrom drive` + | :sg:`get_id() -> id` + + Returns the integer id that was used to create the ``CD`` instance. This + method can work on an uninitialized ``CD``. + + .. ## CD.get_id ## + + .. method:: get_name + + | :sl:`the system name of the cdrom drive` + | :sg:`get_name() -> name` + + Return the string name of the drive. This is the system name used to + represent the drive. It is often the drive letter or device name. This + method can work on an uninitialized ``CD``. + + .. ## CD.get_name ## + + .. method:: get_busy + + | :sl:`true if the drive is playing audio` + | :sg:`get_busy() -> bool` + + Returns True if the drive busy playing back audio. + + .. ## CD.get_busy ## + + .. method:: get_paused + + | :sl:`true if the drive is paused` + | :sg:`get_paused() -> bool` + + Returns True if the drive is currently paused. + + .. ## CD.get_paused ## + + .. method:: get_current + + | :sl:`the current audio playback position` + | :sg:`get_current() -> track, seconds` + + Returns both the current track and time of that track. This method works + when the drive is either playing or paused. + + Note, track 0 is the first track on the ``CD``. Track numbers start at + zero. + + .. ## CD.get_current ## + + .. method:: get_empty + + | :sl:`False if a cdrom is in the drive` + | :sg:`get_empty() -> bool` + + Return False if there is a cdrom currently in the drive. If the drive is + empty this will return True. + + .. ## CD.get_empty ## + + .. method:: get_numtracks + + | :sl:`the number of tracks on the cdrom` + | :sg:`get_numtracks() -> count` + + Return the number of tracks on the cdrom in the drive. This will return + zero of the drive is empty or has no tracks. + + .. ## CD.get_numtracks ## + + .. method:: get_track_audio + + | :sl:`true if the cdrom track has audio data` + | :sg:`get_track_audio(track) -> bool` + + Determine if a track on a cdrom contains audio data. You can also call + ``CD.num_tracks()`` and ``CD.get_all()`` to determine more information + about the cdrom. + + Note, track 0 is the first track on the ``CD``. Track numbers start at + zero. + + .. ## CD.get_track_audio ## + + .. method:: get_all + + | :sl:`get all track information` + | :sg:`get_all() -> [(audio, start, end, length), ...]` + + Return a list with information for every track on the cdrom. The + information consists of a tuple with four values. The audio value is True + if the track contains audio data. The start, end, and length values are + floating point numbers in seconds. Start and end represent absolute times + on the entire disc. + + .. ## CD.get_all ## + + .. method:: get_track_start + + | :sl:`start time of a cdrom track` + | :sg:`get_track_start(track) -> seconds` + + Return the absolute time in seconds where at start of the cdrom track. + + Note, track 0 is the first track on the ``CD``. Track numbers start at + zero. + + .. ## CD.get_track_start ## + + .. method:: get_track_length + + | :sl:`length of a cdrom track` + | :sg:`get_track_length(track) -> seconds` + + Return a floating point value in seconds of the length of the cdrom + track. + + Note, track 0 is the first track on the ``CD``. Track numbers start at + zero. + + .. ## CD.get_track_length ## + + .. ## pygame.cdrom.CD ## + +.. ## pygame.cdrom ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/color.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/color.rst.txt new file mode 100644 index 0000000..1841137 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/color.rst.txt @@ -0,0 +1,266 @@ +.. include:: common.txt + +:mod:`pygame.Color` +=================== + +.. currentmodule:: pygame + +.. class:: Color + + | :sl:`pygame object for color representations` + | :sg:`Color(r, g, b) -> Color` + | :sg:`Color(r, g, b, a=255) -> Color` + | :sg:`Color(color_value) -> Color` + + The ``Color`` class represents ``RGBA`` color values using a value range of + 0 to 255 inclusive. It allows basic arithmetic operations — binary + operations ``+``, ``-``, ``*``, ``//``, ``%``, and unary operation ``~`` — to + create new colors, supports conversions to other color spaces such as ``HSV`` + or ``HSL`` and lets you adjust single color channels. + Alpha defaults to 255 (fully opaque) when not given. + The arithmetic operations and ``correct_gamma()`` method preserve subclasses. + For the binary operators, the class of the returned color is that of the + left hand color object of the operator. + + Color objects support equality comparison with other color objects and 3 or + 4 element tuples of integers. There was a bug in pygame 1.8.1 + where the default alpha was 0, not 255 like previously. + + Color objects export the C level array interface. The interface exports a + read-only one dimensional unsigned byte array of the same assigned length + as the color. The new buffer interface is also exported, with the same + characteristics as the array interface. + + The floor division, ``//``, and modulus, ``%``, operators do not raise + an exception for division by zero. Instead, if a color, or alpha, channel + in the right hand color is 0, then the result is 0. For example: :: + + # These expressions are True + Color(255, 255, 255, 255) // Color(0, 64, 64, 64) == Color(0, 3, 3, 3) + Color(255, 255, 255, 255) % Color(64, 64, 64, 0) == Color(63, 63, 63, 0) + + Use ``int(color)`` to return the immutable integer value of the color, + usable as a ``dict`` key. This integer value differs from the mapped + pixel values of :meth:`pygame.Surface.get_at_mapped`, + :meth:`pygame.Surface.map_rgb` and :meth:`pygame.Surface.unmap_rgb`. + It can be passed as a ``color_value`` argument to :class:`Color` + (useful with sets). + + See :doc:`color_list` for samples of the available named colors. + + :param int r: red value in the range of 0 to 255 inclusive + :param int g: green value in the range of 0 to 255 inclusive + :param int b: blue value in the range of 0 to 255 inclusive + :param int a: (optional) alpha value in the range of 0 to 255 inclusive, + default is 255 + :param color_value: color value (see note below for the supported formats) + + .. note:: + Supported ``color_value`` formats: + | - **Color object:** clones the given :class:`Color` object + | - **Color name: str:** name of the color to use, e.g. ``'red'`` + (all the supported name strings can be found in the + :doc:`color_list`, with sample swatches) + | - **HTML color format str:** ``'#rrggbbaa'`` or ``'#rrggbb'``, + where rr, gg, bb, and aa are 2-digit hex numbers in the range + of 0 to 0xFF inclusive, the aa (alpha) value defaults to 0xFF + if not provided + | - **hex number str:** ``'0xrrggbbaa'`` or ``'0xrrggbb'``, where + rr, gg, bb, and aa are 2-digit hex numbers in the range of 0x00 + to 0xFF inclusive, the aa (alpha) value defaults to 0xFF if not + provided + | - **int:** int value of the color to use, using hex numbers can + make this parameter more readable, e.g. ``0xrrggbbaa``, where rr, + gg, bb, and aa are 2-digit hex numbers in the range of 0x00 to + 0xFF inclusive, note that the aa (alpha) value is not optional for + the int format and must be provided + | - **tuple/list of int color values:** ``(R, G, B, A)`` or + ``(R, G, B)``, where R, G, B, and A are int values in the range of + 0 to 255 inclusive, the A (alpha) value defaults to 255 if not + provided + + :type color_value: Color or str or int or tuple(int, int, int, [int]) or + list(int, int, int, [int]) + + :returns: a newly created :class:`Color` object + :rtype: Color + + .. versionchanged:: 2.0.0 + Support for tuples, lists, and :class:`Color` objects when creating + :class:`Color` objects. + .. versionchanged:: 1.9.2 Color objects export the C level array interface. + .. versionchanged:: 1.9.0 Color objects support 4-element tuples of integers. + .. versionchanged:: 1.8.1 New implementation of the class. + + .. attribute:: r + + | :sl:`Gets or sets the red value of the Color.` + | :sg:`r -> int` + + The red value of the Color. + + .. ## Color.r ## + + .. attribute:: g + + | :sl:`Gets or sets the green value of the Color.` + | :sg:`g -> int` + + The green value of the Color. + + .. ## Color.g ## + + .. attribute:: b + + | :sl:`Gets or sets the blue value of the Color.` + | :sg:`b -> int` + + The blue value of the Color. + + .. ## Color.b ## + + .. attribute:: a + + | :sl:`Gets or sets the alpha value of the Color.` + | :sg:`a -> int` + + The alpha value of the Color. + + .. ## Color.a ## + + .. attribute:: cmy + + | :sl:`Gets or sets the CMY representation of the Color.` + | :sg:`cmy -> tuple` + + The ``CMY`` representation of the Color. The ``CMY`` components are in + the ranges ``C`` = [0, 1], ``M`` = [0, 1], ``Y`` = [0, 1]. Note that this + will not return the absolutely exact ``CMY`` values for the set ``RGB`` + values in all cases. Due to the ``RGB`` mapping from 0-255 and the + ``CMY`` mapping from 0-1 rounding errors may cause the ``CMY`` values to + differ slightly from what you might expect. + + .. ## Color.cmy ## + + .. attribute:: hsva + + | :sl:`Gets or sets the HSVA representation of the Color.` + | :sg:`hsva -> tuple` + + The ``HSVA`` representation of the Color. The ``HSVA`` components are in + the ranges ``H`` = [0, 360], ``S`` = [0, 100], ``V`` = [0, 100], A = [0, + 100]. Note that this will not return the absolutely exact ``HSV`` values + for the set ``RGB`` values in all cases. Due to the ``RGB`` mapping from + 0-255 and the ``HSV`` mapping from 0-100 and 0-360 rounding errors may + cause the ``HSV`` values to differ slightly from what you might expect. + + .. ## Color.hsva ## + + .. attribute:: hsla + + | :sl:`Gets or sets the HSLA representation of the Color.` + | :sg:`hsla -> tuple` + + The ``HSLA`` representation of the Color. The ``HSLA`` components are in + the ranges ``H`` = [0, 360], ``S`` = [0, 100], ``V`` = [0, 100], A = [0, + 100]. Note that this will not return the absolutely exact ``HSL`` values + for the set ``RGB`` values in all cases. Due to the ``RGB`` mapping from + 0-255 and the ``HSL`` mapping from 0-100 and 0-360 rounding errors may + cause the ``HSL`` values to differ slightly from what you might expect. + + .. ## Color.hsla ## + + .. attribute:: i1i2i3 + + | :sl:`Gets or sets the I1I2I3 representation of the Color.` + | :sg:`i1i2i3 -> tuple` + + The ``I1I2I3`` representation of the Color. The ``I1I2I3`` components are + in the ranges ``I1`` = [0, 1], ``I2`` = [-0.5, 0.5], ``I3`` = [-0.5, + 0.5]. Note that this will not return the absolutely exact ``I1I2I3`` + values for the set ``RGB`` values in all cases. Due to the ``RGB`` + mapping from 0-255 and the ``I1I2I3`` mapping from 0-1 rounding errors + may cause the ``I1I2I3`` values to differ slightly from what you might + expect. + + .. ## Color.i1i2i3 ## + + .. method:: normalize + + | :sl:`Returns the normalized RGBA values of the Color.` + | :sg:`normalize() -> tuple` + + Returns the normalized ``RGBA`` values of the Color as floating point + values. + + .. ## Color.normalize ## + + .. method:: correct_gamma + + | :sl:`Applies a certain gamma value to the Color.` + | :sg:`correct_gamma (gamma) -> Color` + + Applies a certain gamma value to the Color and returns a new Color with + the adjusted ``RGBA`` values. + + .. ## Color.correct_gamma ## + + .. method:: set_length + + | :sl:`Set the number of elements in the Color to 1,2,3, or 4.` + | :sg:`set_length(len) -> None` + + The default Color length is 4. Colors can have lengths 1,2,3 or 4. This + is useful if you want to unpack to r,g,b and not r,g,b,a. If you want to + get the length of a Color do ``len(acolor)``. + + .. versionadded:: 1.9.0 + + .. ## Color.set_length ## + + .. method:: lerp + + | :sl:`returns a linear interpolation to the given Color.` + | :sg:`lerp(Color, float) -> Color` + + Returns a Color which is a linear interpolation between self and the + given Color in RGBA space. The second parameter determines how far + between self and other the result is going to be. + It must be a value between 0 and 1 where 0 means self and 1 means + other will be returned. + + .. versionadded:: 2.0.1 + + .. ## Color.lerp ## + + .. method:: premul_alpha + + | :sl:`returns a Color where the r,g,b components have been multiplied by the alpha.` + | :sg:`premul_alpha() -> Color` + + Returns a new Color where each of the red, green and blue colour + channels have been multiplied by the alpha channel of the original + color. The alpha channel remains unchanged. + + This is useful when working with the ``BLEND_PREMULTIPLIED`` blending mode + flag for :meth:`pygame.Surface.blit()`, which assumes that all surfaces using + it are using pre-multiplied alpha colors. + + .. versionadded:: 2.0.0 + + .. ## Color.premul_alpha ## + + .. method:: update + + | :sl:`Sets the elements of the color` + | :sg:`update(r, g, b) -> None` + | :sg:`update(r, g, b, a=255) -> None` + | :sg:`update(color_value) -> None` + + Sets the elements of the color. See parameters for :meth:`pygame.Color` for the + parameters of this function. If the alpha value was not set it will not change. + + .. versionadded:: 2.0.1 + + .. ## Color.update ## + .. ## pygame.Color ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/color_list.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/color_list.rst.txt new file mode 100644 index 0000000..b6cf289 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/color_list.rst.txt @@ -0,0 +1,2014 @@ +.. include:: common.txt + +Named Colors +============ + +.. raw:: html + + + +:doc:`color` lets you specify any of these named colors when creating a new +``pygame.Color`` (taken from the +`colordict module `_). + +.. role:: aliceblue +.. role:: antiquewhite +.. role:: antiquewhite1 +.. role:: antiquewhite2 +.. role:: antiquewhite3 +.. role:: antiquewhite4 +.. role:: aqua +.. role:: aquamarine +.. role:: aquamarine1 +.. role:: aquamarine2 +.. role:: aquamarine3 +.. role:: aquamarine4 +.. role:: azure +.. role:: azure1 +.. role:: azure2 +.. role:: azure3 +.. role:: azure4 +.. role:: beige +.. role:: bisque +.. role:: bisque1 +.. role:: bisque2 +.. role:: bisque3 +.. role:: bisque4 +.. role:: black +.. role:: blanchedalmond +.. role:: blue +.. role:: blue1 +.. role:: blue2 +.. role:: blue3 +.. role:: blue4 +.. role:: blueviolet +.. role:: brown +.. role:: brown1 +.. role:: brown2 +.. role:: brown3 +.. role:: brown4 +.. role:: burlywood +.. role:: burlywood1 +.. role:: burlywood2 +.. role:: burlywood3 +.. role:: burlywood4 +.. role:: cadetblue +.. role:: cadetblue1 +.. role:: cadetblue2 +.. role:: cadetblue3 +.. role:: cadetblue4 +.. role:: chartreuse +.. role:: chartreuse1 +.. role:: chartreuse2 +.. role:: chartreuse3 +.. role:: chartreuse4 +.. role:: chocolate +.. role:: chocolate1 +.. role:: chocolate2 +.. role:: chocolate3 +.. role:: chocolate4 +.. role:: coral +.. role:: coral1 +.. role:: coral2 +.. role:: coral3 +.. role:: coral4 +.. role:: cornflowerblue +.. role:: cornsilk +.. role:: cornsilk1 +.. role:: cornsilk2 +.. role:: cornsilk3 +.. role:: cornsilk4 +.. role:: crimson +.. role:: cyan +.. role:: cyan1 +.. role:: cyan2 +.. role:: cyan3 +.. role:: cyan4 +.. role:: darkblue +.. role:: darkcyan +.. role:: darkgoldenrod +.. role:: darkgoldenrod1 +.. role:: darkgoldenrod2 +.. role:: darkgoldenrod3 +.. role:: darkgoldenrod4 +.. role:: darkgray +.. role:: darkgreen +.. role:: darkgrey +.. role:: darkkhaki +.. role:: darkmagenta +.. role:: darkolivegreen +.. role:: darkolivegreen1 +.. role:: darkolivegreen2 +.. role:: darkolivegreen3 +.. role:: darkolivegreen4 +.. role:: darkorange +.. role:: darkorange1 +.. role:: darkorange2 +.. role:: darkorange3 +.. role:: darkorange4 +.. role:: darkorchid +.. role:: darkorchid1 +.. role:: darkorchid2 +.. role:: darkorchid3 +.. role:: darkorchid4 +.. role:: darkred +.. role:: darksalmon +.. role:: darkseagreen +.. role:: darkseagreen1 +.. role:: darkseagreen2 +.. role:: darkseagreen3 +.. role:: darkseagreen4 +.. role:: darkslateblue +.. role:: darkslategray +.. role:: darkslategray1 +.. role:: darkslategray2 +.. role:: darkslategray3 +.. role:: darkslategray4 +.. role:: darkslategrey +.. role:: darkturquoise +.. role:: darkviolet +.. role:: deeppink +.. role:: deeppink1 +.. role:: deeppink2 +.. role:: deeppink3 +.. role:: deeppink4 +.. role:: deepskyblue +.. role:: deepskyblue1 +.. role:: deepskyblue2 +.. role:: deepskyblue3 +.. role:: deepskyblue4 +.. role:: dimgray +.. role:: dimgrey +.. role:: dodgerblue +.. role:: dodgerblue1 +.. role:: dodgerblue2 +.. role:: dodgerblue3 +.. role:: dodgerblue4 +.. role:: firebrick +.. role:: firebrick1 +.. role:: firebrick2 +.. role:: firebrick3 +.. role:: firebrick4 +.. role:: floralwhite +.. role:: forestgreen +.. role:: fuchsia +.. role:: gainsboro +.. role:: ghostwhite +.. role:: gold +.. role:: gold1 +.. role:: gold2 +.. role:: gold3 +.. role:: gold4 +.. role:: goldenrod +.. role:: goldenrod1 +.. role:: goldenrod2 +.. role:: goldenrod3 +.. role:: goldenrod4 +.. role:: gray +.. role:: gray0 +.. role:: gray1 +.. role:: gray2 +.. role:: gray3 +.. role:: gray4 +.. role:: gray5 +.. role:: gray6 +.. role:: gray7 +.. role:: gray8 +.. role:: gray9 +.. role:: gray10 +.. role:: gray11 +.. role:: gray12 +.. role:: gray13 +.. role:: gray14 +.. role:: gray15 +.. role:: gray16 +.. role:: gray17 +.. role:: gray18 +.. role:: gray19 +.. role:: gray20 +.. role:: gray21 +.. role:: gray22 +.. role:: gray23 +.. role:: gray24 +.. role:: gray25 +.. role:: gray26 +.. role:: gray27 +.. role:: gray28 +.. role:: gray29 +.. role:: gray30 +.. role:: gray31 +.. role:: gray32 +.. role:: gray33 +.. role:: gray34 +.. role:: gray35 +.. role:: gray36 +.. role:: gray37 +.. role:: gray38 +.. role:: gray39 +.. role:: gray40 +.. role:: gray41 +.. role:: gray42 +.. role:: gray43 +.. role:: gray44 +.. role:: gray45 +.. role:: gray46 +.. role:: gray47 +.. role:: gray48 +.. role:: gray49 +.. role:: gray50 +.. role:: gray51 +.. role:: gray52 +.. role:: gray53 +.. role:: gray54 +.. role:: gray55 +.. role:: gray56 +.. role:: gray57 +.. role:: gray58 +.. role:: gray59 +.. role:: gray60 +.. role:: gray61 +.. role:: gray62 +.. role:: gray63 +.. role:: gray64 +.. role:: gray65 +.. role:: gray66 +.. role:: gray67 +.. role:: gray68 +.. role:: gray69 +.. role:: gray70 +.. role:: gray71 +.. role:: gray72 +.. role:: gray73 +.. role:: gray74 +.. role:: gray75 +.. role:: gray76 +.. role:: gray77 +.. role:: gray78 +.. role:: gray79 +.. role:: gray80 +.. role:: gray81 +.. role:: gray82 +.. role:: gray83 +.. role:: gray84 +.. role:: gray85 +.. role:: gray86 +.. role:: gray87 +.. role:: gray88 +.. role:: gray89 +.. role:: gray90 +.. role:: gray91 +.. role:: gray92 +.. role:: gray93 +.. role:: gray94 +.. role:: gray95 +.. role:: gray96 +.. role:: gray97 +.. role:: gray98 +.. role:: gray99 +.. role:: gray100 +.. role:: green +.. role:: green1 +.. role:: green2 +.. role:: green3 +.. role:: green4 +.. role:: greenyellow +.. role:: grey +.. role:: grey0 +.. role:: grey1 +.. role:: grey2 +.. role:: grey3 +.. role:: grey4 +.. role:: grey5 +.. role:: grey6 +.. role:: grey7 +.. role:: grey8 +.. role:: grey9 +.. role:: grey10 +.. role:: grey11 +.. role:: grey12 +.. role:: grey13 +.. role:: grey14 +.. role:: grey15 +.. role:: grey16 +.. role:: grey17 +.. role:: grey18 +.. role:: grey19 +.. role:: grey20 +.. role:: grey21 +.. role:: grey22 +.. role:: grey23 +.. role:: grey24 +.. role:: grey25 +.. role:: grey26 +.. role:: grey27 +.. role:: grey28 +.. role:: grey29 +.. role:: grey30 +.. role:: grey31 +.. role:: grey32 +.. role:: grey33 +.. role:: grey34 +.. role:: grey35 +.. role:: grey36 +.. role:: grey37 +.. role:: grey38 +.. role:: grey39 +.. role:: grey40 +.. role:: grey41 +.. role:: grey42 +.. role:: grey43 +.. role:: grey44 +.. role:: grey45 +.. role:: grey46 +.. role:: grey47 +.. role:: grey48 +.. role:: grey49 +.. role:: grey50 +.. role:: grey51 +.. role:: grey52 +.. role:: grey53 +.. role:: grey54 +.. role:: grey55 +.. role:: grey56 +.. role:: grey57 +.. role:: grey58 +.. role:: grey59 +.. role:: grey60 +.. role:: grey61 +.. role:: grey62 +.. role:: grey63 +.. role:: grey64 +.. role:: grey65 +.. role:: grey66 +.. role:: grey67 +.. role:: grey68 +.. role:: grey69 +.. role:: grey70 +.. role:: grey71 +.. role:: grey72 +.. role:: grey73 +.. role:: grey74 +.. role:: grey75 +.. role:: grey76 +.. role:: grey77 +.. role:: grey78 +.. role:: grey79 +.. role:: grey80 +.. role:: grey81 +.. role:: grey82 +.. role:: grey83 +.. role:: grey84 +.. role:: grey85 +.. role:: grey86 +.. role:: grey87 +.. role:: grey88 +.. role:: grey89 +.. role:: grey90 +.. role:: grey91 +.. role:: grey92 +.. role:: grey93 +.. role:: grey94 +.. role:: grey95 +.. role:: grey96 +.. role:: grey97 +.. role:: grey98 +.. role:: grey99 +.. role:: grey100 +.. role:: honeydew +.. role:: honeydew1 +.. role:: honeydew2 +.. role:: honeydew3 +.. role:: honeydew4 +.. role:: hotpink +.. role:: hotpink1 +.. role:: hotpink2 +.. role:: hotpink3 +.. role:: hotpink4 +.. role:: indianred +.. role:: indianred1 +.. role:: indianred2 +.. role:: indianred3 +.. role:: indianred4 +.. role:: indigo +.. role:: ivory +.. role:: ivory1 +.. role:: ivory2 +.. role:: ivory3 +.. role:: ivory4 +.. role:: khaki +.. role:: khaki1 +.. role:: khaki2 +.. role:: khaki3 +.. role:: khaki4 +.. role:: lavender +.. role:: lavenderblush +.. role:: lavenderblush1 +.. role:: lavenderblush2 +.. role:: lavenderblush3 +.. role:: lavenderblush4 +.. role:: lawngreen +.. role:: lemonchiffon +.. role:: lemonchiffon1 +.. role:: lemonchiffon2 +.. role:: lemonchiffon3 +.. role:: lemonchiffon4 +.. role:: lightblue +.. role:: lightblue1 +.. role:: lightblue2 +.. role:: lightblue3 +.. role:: lightblue4 +.. role:: lightcoral +.. role:: lightcyan +.. role:: lightcyan1 +.. role:: lightcyan2 +.. role:: lightcyan3 +.. role:: lightcyan4 +.. role:: lightgoldenrod +.. role:: lightgoldenrod1 +.. role:: lightgoldenrod2 +.. role:: lightgoldenrod3 +.. role:: lightgoldenrod4 +.. role:: lightgoldenrodyellow +.. role:: lightgray +.. role:: lightgreen +.. role:: lightgrey +.. role:: lightpink +.. role:: lightpink1 +.. role:: lightpink2 +.. role:: lightpink3 +.. role:: lightpink4 +.. role:: lightsalmon +.. role:: lightsalmon1 +.. role:: lightsalmon2 +.. role:: lightsalmon3 +.. role:: lightsalmon4 +.. role:: lightseagreen +.. role:: lightskyblue +.. role:: lightskyblue1 +.. role:: lightskyblue2 +.. role:: lightskyblue3 +.. role:: lightskyblue4 +.. role:: lightslateblue +.. role:: lightslategray +.. role:: lightslategrey +.. role:: lightsteelblue +.. role:: lightsteelblue1 +.. role:: lightsteelblue2 +.. role:: lightsteelblue3 +.. role:: lightsteelblue4 +.. role:: lightyellow +.. role:: lightyellow1 +.. role:: lightyellow2 +.. role:: lightyellow3 +.. role:: lightyellow4 +.. role:: limegreen +.. role:: lime +.. role:: linen +.. role:: magenta +.. role:: magenta1 +.. role:: magenta2 +.. role:: magenta3 +.. role:: magenta4 +.. role:: maroon +.. role:: maroon1 +.. role:: maroon2 +.. role:: maroon3 +.. role:: maroon4 +.. role:: mediumaquamarine +.. role:: mediumblue +.. role:: mediumorchid +.. role:: mediumorchid1 +.. role:: mediumorchid2 +.. role:: mediumorchid3 +.. role:: mediumorchid4 +.. role:: mediumpurple +.. role:: mediumpurple1 +.. role:: mediumpurple2 +.. role:: mediumpurple3 +.. role:: mediumpurple4 +.. role:: mediumseagreen +.. role:: mediumslateblue +.. role:: mediumspringgreen +.. role:: mediumturquoise +.. role:: mediumvioletred +.. role:: midnightblue +.. role:: mintcream +.. role:: mistyrose +.. role:: mistyrose1 +.. role:: mistyrose2 +.. role:: mistyrose3 +.. role:: mistyrose4 +.. role:: moccasin +.. role:: navajowhite +.. role:: navajowhite1 +.. role:: navajowhite2 +.. role:: navajowhite3 +.. role:: navajowhite4 +.. role:: navy +.. role:: navyblue +.. role:: oldlace +.. role:: olive +.. role:: olivedrab +.. role:: olivedrab1 +.. role:: olivedrab2 +.. role:: olivedrab3 +.. role:: olivedrab4 +.. role:: orange +.. role:: orange1 +.. role:: orange2 +.. role:: orange3 +.. role:: orange4 +.. role:: orangered +.. role:: orangered1 +.. role:: orangered2 +.. role:: orangered3 +.. role:: orangered4 +.. role:: orchid +.. role:: orchid1 +.. role:: orchid2 +.. role:: orchid3 +.. role:: orchid4 +.. role:: palegoldenrod +.. role:: palegreen +.. role:: palegreen1 +.. role:: palegreen2 +.. role:: palegreen3 +.. role:: palegreen4 +.. role:: paleturquoise +.. role:: paleturquoise1 +.. role:: paleturquoise2 +.. role:: paleturquoise3 +.. role:: paleturquoise4 +.. role:: palevioletred +.. role:: palevioletred1 +.. role:: palevioletred2 +.. role:: palevioletred3 +.. role:: palevioletred4 +.. role:: papayawhip +.. role:: peachpuff +.. role:: peachpuff1 +.. role:: peachpuff2 +.. role:: peachpuff3 +.. role:: peachpuff4 +.. role:: peru +.. role:: pink +.. role:: pink1 +.. role:: pink2 +.. role:: pink3 +.. role:: pink4 +.. role:: plum +.. role:: plum1 +.. role:: plum2 +.. role:: plum3 +.. role:: plum4 +.. role:: powderblue +.. role:: purple +.. role:: purple1 +.. role:: purple2 +.. role:: purple3 +.. role:: purple4 +.. role:: red +.. role:: red1 +.. role:: red2 +.. role:: red3 +.. role:: red4 +.. role:: rosybrown +.. role:: rosybrown1 +.. role:: rosybrown2 +.. role:: rosybrown3 +.. role:: rosybrown4 +.. role:: royalblue +.. role:: royalblue1 +.. role:: royalblue2 +.. role:: royalblue3 +.. role:: royalblue4 +.. role:: saddlebrown +.. role:: salmon +.. role:: salmon1 +.. role:: salmon2 +.. role:: salmon3 +.. role:: salmon4 +.. role:: sandybrown +.. role:: seagreen +.. role:: seagreen1 +.. role:: seagreen2 +.. role:: seagreen3 +.. role:: seagreen4 +.. role:: seashell +.. role:: seashell1 +.. role:: seashell2 +.. role:: seashell3 +.. role:: seashell4 +.. role:: sienna +.. role:: sienna1 +.. role:: sienna2 +.. role:: sienna3 +.. role:: sienna4 +.. role:: silver +.. role:: skyblue +.. role:: skyblue1 +.. role:: skyblue2 +.. role:: skyblue3 +.. role:: skyblue4 +.. role:: slateblue +.. role:: slateblue1 +.. role:: slateblue2 +.. role:: slateblue3 +.. role:: slateblue4 +.. role:: slategray +.. role:: slategray1 +.. role:: slategray2 +.. role:: slategray3 +.. role:: slategray4 +.. role:: slategrey +.. role:: snow +.. role:: snow1 +.. role:: snow2 +.. role:: snow3 +.. role:: snow4 +.. role:: springgreen +.. role:: springgreen1 +.. role:: springgreen2 +.. role:: springgreen3 +.. role:: springgreen4 +.. role:: steelblue +.. role:: steelblue1 +.. role:: steelblue2 +.. role:: steelblue3 +.. role:: steelblue4 +.. role:: tan +.. role:: tan1 +.. role:: tan2 +.. role:: tan3 +.. role:: tan4 +.. role:: teal +.. role:: thistle +.. role:: thistle1 +.. role:: thistle2 +.. role:: thistle3 +.. role:: thistle4 +.. role:: tomato +.. role:: tomato1 +.. role:: tomato2 +.. role:: tomato3 +.. role:: tomato4 +.. role:: turquoise +.. role:: turquoise1 +.. role:: turquoise2 +.. role:: turquoise3 +.. role:: turquoise4 +.. role:: violet +.. role:: violetred +.. role:: violetred1 +.. role:: violetred2 +.. role:: violetred3 +.. role:: violetred4 +.. role:: wheat +.. role:: wheat1 +.. role:: wheat2 +.. role:: wheat3 +.. role:: wheat4 +.. role:: white +.. role:: whitesmoke +.. role:: yellow +.. role:: yellow1 +.. role:: yellow2 +.. role:: yellow3 +.. role:: yellow4 +.. role:: yellowgreen + +========================== ====================================================================================================== +Name Color +========================== ====================================================================================================== +``aliceblue`` :aliceblue:`████████` +``antiquewhite`` :antiquewhite:`████████` +``antiquewhite1`` :antiquewhite1:`████████` +``antiquewhite2`` :antiquewhite2:`████████` +``antiquewhite3`` :antiquewhite3:`████████` +``antiquewhite4`` :antiquewhite4:`████████` +``aqua`` :aqua:`████████` +``aquamarine`` :aquamarine:`████████` +``aquamarine1`` :aquamarine1:`████████` +``aquamarine2`` :aquamarine2:`████████` +``aquamarine3`` :aquamarine3:`████████` +``aquamarine4`` :aquamarine4:`████████` +``azure`` :azure:`████████` +``azure1`` :azure1:`████████` +``azure2`` :azure2:`████████` +``azure3`` :azure3:`████████` +``azure4`` :azure4:`████████` +``beige`` :beige:`████████` +``bisque`` :bisque:`████████` +``bisque1`` :bisque1:`████████` +``bisque2`` :bisque2:`████████` +``bisque3`` :bisque3:`████████` +``bisque4`` :bisque4:`████████` +``black`` :black:`████████` +``blanchedalmond`` :blanchedalmond:`████████` +``blue`` :blue:`████████` +``blue1`` :blue1:`████████` +``blue2`` :blue2:`████████` +``blue3`` :blue3:`████████` +``blue4`` :blue4:`████████` +``blueviolet`` :blueviolet:`████████` +``brown`` :brown:`████████` +``brown1`` :brown1:`████████` +``brown2`` :brown2:`████████` +``brown3`` :brown3:`████████` +``brown4`` :brown4:`████████` +``burlywood`` :burlywood:`████████` +``burlywood1`` :burlywood1:`████████` +``burlywood2`` :burlywood2:`████████` +``burlywood3`` :burlywood3:`████████` +``burlywood4`` :burlywood4:`████████` +``cadetblue`` :cadetblue:`████████` +``cadetblue1`` :cadetblue1:`████████` +``cadetblue2`` :cadetblue2:`████████` +``cadetblue3`` :cadetblue3:`████████` +``cadetblue4`` :cadetblue4:`████████` +``chartreuse`` :chartreuse:`████████` +``chartreuse1`` :chartreuse1:`████████` +``chartreuse2`` :chartreuse2:`████████` +``chartreuse3`` :chartreuse3:`████████` +``chartreuse4`` :chartreuse4:`████████` +``chocolate`` :chocolate:`████████` +``chocolate1`` :chocolate1:`████████` +``chocolate2`` :chocolate2:`████████` +``chocolate3`` :chocolate3:`████████` +``chocolate4`` :chocolate4:`████████` +``coral`` :coral:`████████` +``coral1`` :coral1:`████████` +``coral2`` :coral2:`████████` +``coral3`` :coral3:`████████` +``coral4`` :coral4:`████████` +``cornflowerblue`` :cornflowerblue:`████████` +``cornsilk`` :cornsilk:`████████` +``cornsilk1`` :cornsilk1:`████████` +``cornsilk2`` :cornsilk2:`████████` +``cornsilk3`` :cornsilk3:`████████` +``cornsilk4`` :cornsilk4:`████████` +``crimson`` :crimson:`████████` +``cyan`` :cyan:`████████` +``cyan1`` :cyan1:`████████` +``cyan2`` :cyan2:`████████` +``cyan3`` :cyan3:`████████` +``cyan4`` :cyan4:`████████` +``darkblue`` :darkblue:`████████` +``darkcyan`` :darkcyan:`████████` +``darkgoldenrod`` :darkgoldenrod:`████████` +``darkgoldenrod1`` :darkgoldenrod1:`████████` +``darkgoldenrod2`` :darkgoldenrod2:`████████` +``darkgoldenrod3`` :darkgoldenrod3:`████████` +``darkgoldenrod4`` :darkgoldenrod4:`████████` +``darkgray`` :darkgray:`████████` +``darkgreen`` :darkgreen:`████████` +``darkgrey`` :darkgrey:`████████` +``darkkhaki`` :darkkhaki:`████████` +``darkmagenta`` :darkmagenta:`████████` +``darkolivegreen`` :darkolivegreen:`████████` +``darkolivegreen1`` :darkolivegreen1:`████████` +``darkolivegreen2`` :darkolivegreen2:`████████` +``darkolivegreen3`` :darkolivegreen3:`████████` +``darkolivegreen4`` :darkolivegreen4:`████████` +``darkorange`` :darkorange:`████████` +``darkorange1`` :darkorange1:`████████` +``darkorange2`` :darkorange2:`████████` +``darkorange3`` :darkorange3:`████████` +``darkorange4`` :darkorange4:`████████` +``darkorchid`` :darkorchid:`████████` +``darkorchid1`` :darkorchid1:`████████` +``darkorchid2`` :darkorchid2:`████████` +``darkorchid3`` :darkorchid3:`████████` +``darkorchid4`` :darkorchid4:`████████` +``darkred`` :darkred:`████████` +``darksalmon`` :darksalmon:`████████` +``darkseagreen`` :darkseagreen:`████████` +``darkseagreen1`` :darkseagreen1:`████████` +``darkseagreen2`` :darkseagreen2:`████████` +``darkseagreen3`` :darkseagreen3:`████████` +``darkseagreen4`` :darkseagreen4:`████████` +``darkslateblue`` :darkslateblue:`████████` +``darkslategray`` :darkslategray:`████████` +``darkslategray1`` :darkslategray1:`████████` +``darkslategray2`` :darkslategray2:`████████` +``darkslategray3`` :darkslategray3:`████████` +``darkslategray4`` :darkslategray4:`████████` +``darkslategrey`` :darkslategrey:`████████` +``darkturquoise`` :darkturquoise:`████████` +``darkviolet`` :darkviolet:`████████` +``deeppink`` :deeppink:`████████` +``deeppink1`` :deeppink1:`████████` +``deeppink2`` :deeppink2:`████████` +``deeppink3`` :deeppink3:`████████` +``deeppink4`` :deeppink4:`████████` +``deepskyblue`` :deepskyblue:`████████` +``deepskyblue1`` :deepskyblue1:`████████` +``deepskyblue2`` :deepskyblue2:`████████` +``deepskyblue3`` :deepskyblue3:`████████` +``deepskyblue4`` :deepskyblue4:`████████` +``dimgray`` :dimgray:`████████` +``dimgrey`` :dimgrey:`████████` +``dodgerblue`` :dodgerblue:`████████` +``dodgerblue1`` :dodgerblue1:`████████` +``dodgerblue2`` :dodgerblue2:`████████` +``dodgerblue3`` :dodgerblue3:`████████` +``dodgerblue4`` :dodgerblue4:`████████` +``firebrick`` :firebrick:`████████` +``firebrick1`` :firebrick1:`████████` +``firebrick2`` :firebrick2:`████████` +``firebrick3`` :firebrick3:`████████` +``firebrick4`` :firebrick4:`████████` +``floralwhite`` :floralwhite:`████████` +``forestgreen`` :forestgreen:`████████` +``fuchsia`` :fuchsia:`████████` +``gainsboro`` :gainsboro:`████████` +``ghostwhite`` :ghostwhite:`████████` +``gold`` :gold:`████████` +``gold1`` :gold1:`████████` +``gold2`` :gold2:`████████` +``gold3`` :gold3:`████████` +``gold4`` :gold4:`████████` +``goldenrod`` :goldenrod:`████████` +``goldenrod1`` :goldenrod1:`████████` +``goldenrod2`` :goldenrod2:`████████` +``goldenrod3`` :goldenrod3:`████████` +``goldenrod4`` :goldenrod4:`████████` +``gray`` :gray:`████████` +``gray0`` :gray0:`████████` +``gray1`` :gray1:`████████` +``gray2`` :gray2:`████████` +``gray3`` :gray3:`████████` +``gray4`` :gray4:`████████` +``gray5`` :gray5:`████████` +``gray6`` :gray6:`████████` +``gray7`` :gray7:`████████` +``gray8`` :gray8:`████████` +``gray9`` :gray9:`████████` +``gray10`` :gray10:`████████` +``gray11`` :gray11:`████████` +``gray12`` :gray12:`████████` +``gray13`` :gray13:`████████` +``gray14`` :gray14:`████████` +``gray15`` :gray15:`████████` +``gray16`` :gray16:`████████` +``gray17`` :gray17:`████████` +``gray18`` :gray18:`████████` +``gray19`` :gray19:`████████` +``gray20`` :gray20:`████████` +``gray21`` :gray21:`████████` +``gray22`` :gray22:`████████` +``gray23`` :gray23:`████████` +``gray24`` :gray24:`████████` +``gray25`` :gray25:`████████` +``gray26`` :gray26:`████████` +``gray27`` :gray27:`████████` +``gray28`` :gray28:`████████` +``gray29`` :gray29:`████████` +``gray30`` :gray30:`████████` +``gray31`` :gray31:`████████` +``gray32`` :gray32:`████████` +``gray33`` :gray33:`████████` +``gray34`` :gray34:`████████` +``gray35`` :gray35:`████████` +``gray36`` :gray36:`████████` +``gray37`` :gray37:`████████` +``gray38`` :gray38:`████████` +``gray39`` :gray39:`████████` +``gray40`` :gray40:`████████` +``gray41`` :gray41:`████████` +``gray42`` :gray42:`████████` +``gray43`` :gray43:`████████` +``gray44`` :gray44:`████████` +``gray45`` :gray45:`████████` +``gray46`` :gray46:`████████` +``gray47`` :gray47:`████████` +``gray48`` :gray48:`████████` +``gray49`` :gray49:`████████` +``gray50`` :gray50:`████████` +``gray51`` :gray51:`████████` +``gray52`` :gray52:`████████` +``gray53`` :gray53:`████████` +``gray54`` :gray54:`████████` +``gray55`` :gray55:`████████` +``gray56`` :gray56:`████████` +``gray57`` :gray57:`████████` +``gray58`` :gray58:`████████` +``gray59`` :gray59:`████████` +``gray60`` :gray60:`████████` +``gray61`` :gray61:`████████` +``gray62`` :gray62:`████████` +``gray63`` :gray63:`████████` +``gray64`` :gray64:`████████` +``gray65`` :gray65:`████████` +``gray66`` :gray66:`████████` +``gray67`` :gray67:`████████` +``gray68`` :gray68:`████████` +``gray69`` :gray69:`████████` +``gray70`` :gray70:`████████` +``gray71`` :gray71:`████████` +``gray72`` :gray72:`████████` +``gray73`` :gray73:`████████` +``gray74`` :gray74:`████████` +``gray75`` :gray75:`████████` +``gray76`` :gray76:`████████` +``gray77`` :gray77:`████████` +``gray78`` :gray78:`████████` +``gray79`` :gray79:`████████` +``gray80`` :gray80:`████████` +``gray81`` :gray81:`████████` +``gray82`` :gray82:`████████` +``gray83`` :gray83:`████████` +``gray84`` :gray84:`████████` +``gray85`` :gray85:`████████` +``gray86`` :gray86:`████████` +``gray87`` :gray87:`████████` +``gray88`` :gray88:`████████` +``gray89`` :gray89:`████████` +``gray90`` :gray90:`████████` +``gray91`` :gray91:`████████` +``gray92`` :gray92:`████████` +``gray93`` :gray93:`████████` +``gray94`` :gray94:`████████` +``gray95`` :gray95:`████████` +``gray96`` :gray96:`████████` +``gray97`` :gray97:`████████` +``gray98`` :gray98:`████████` +``gray99`` :gray99:`████████` +``gray100`` :gray100:`████████` +``green`` :green:`████████` +``green1`` :green1:`████████` +``green2`` :green2:`████████` +``green3`` :green3:`████████` +``green4`` :green4:`████████` +``greenyellow`` :greenyellow:`████████` +``grey`` :grey:`████████` +``grey0`` :grey0:`████████` +``grey1`` :grey1:`████████` +``grey2`` :grey2:`████████` +``grey3`` :grey3:`████████` +``grey4`` :grey4:`████████` +``grey5`` :grey5:`████████` +``grey6`` :grey6:`████████` +``grey7`` :grey7:`████████` +``grey8`` :grey8:`████████` +``grey9`` :grey9:`████████` +``grey10`` :grey10:`████████` +``grey11`` :grey11:`████████` +``grey12`` :grey12:`████████` +``grey13`` :grey13:`████████` +``grey14`` :grey14:`████████` +``grey15`` :grey15:`████████` +``grey16`` :grey16:`████████` +``grey17`` :grey17:`████████` +``grey18`` :grey18:`████████` +``grey19`` :grey19:`████████` +``grey20`` :grey20:`████████` +``grey21`` :grey21:`████████` +``grey22`` :grey22:`████████` +``grey23`` :grey23:`████████` +``grey24`` :grey24:`████████` +``grey25`` :grey25:`████████` +``grey26`` :grey26:`████████` +``grey27`` :grey27:`████████` +``grey28`` :grey28:`████████` +``grey29`` :grey29:`████████` +``grey30`` :grey30:`████████` +``grey31`` :grey31:`████████` +``grey32`` :grey32:`████████` +``grey33`` :grey33:`████████` +``grey34`` :grey34:`████████` +``grey35`` :grey35:`████████` +``grey36`` :grey36:`████████` +``grey37`` :grey37:`████████` +``grey38`` :grey38:`████████` +``grey39`` :grey39:`████████` +``grey40`` :grey40:`████████` +``grey41`` :grey41:`████████` +``grey42`` :grey42:`████████` +``grey43`` :grey43:`████████` +``grey44`` :grey44:`████████` +``grey45`` :grey45:`████████` +``grey46`` :grey46:`████████` +``grey47`` :grey47:`████████` +``grey48`` :grey48:`████████` +``grey49`` :grey49:`████████` +``grey50`` :grey50:`████████` +``grey51`` :grey51:`████████` +``grey52`` :grey52:`████████` +``grey53`` :grey53:`████████` +``grey54`` :grey54:`████████` +``grey55`` :grey55:`████████` +``grey56`` :grey56:`████████` +``grey57`` :grey57:`████████` +``grey58`` :grey58:`████████` +``grey59`` :grey59:`████████` +``grey60`` :grey60:`████████` +``grey61`` :grey61:`████████` +``grey62`` :grey62:`████████` +``grey63`` :grey63:`████████` +``grey64`` :grey64:`████████` +``grey65`` :grey65:`████████` +``grey66`` :grey66:`████████` +``grey67`` :grey67:`████████` +``grey68`` :grey68:`████████` +``grey69`` :grey69:`████████` +``grey70`` :grey70:`████████` +``grey71`` :grey71:`████████` +``grey72`` :grey72:`████████` +``grey73`` :grey73:`████████` +``grey74`` :grey74:`████████` +``grey75`` :grey75:`████████` +``grey76`` :grey76:`████████` +``grey77`` :grey77:`████████` +``grey78`` :grey78:`████████` +``grey79`` :grey79:`████████` +``grey80`` :grey80:`████████` +``grey81`` :grey81:`████████` +``grey82`` :grey82:`████████` +``grey83`` :grey83:`████████` +``grey84`` :grey84:`████████` +``grey85`` :grey85:`████████` +``grey86`` :grey86:`████████` +``grey87`` :grey87:`████████` +``grey88`` :grey88:`████████` +``grey89`` :grey89:`████████` +``grey90`` :grey90:`████████` +``grey91`` :grey91:`████████` +``grey92`` :grey92:`████████` +``grey93`` :grey93:`████████` +``grey94`` :grey94:`████████` +``grey95`` :grey95:`████████` +``grey96`` :grey96:`████████` +``grey97`` :grey97:`████████` +``grey98`` :grey98:`████████` +``grey99`` :grey99:`████████` +``grey100`` :grey100:`████████` +``honeydew`` :honeydew:`████████` +``honeydew1`` :honeydew1:`████████` +``honeydew2`` :honeydew2:`████████` +``honeydew3`` :honeydew3:`████████` +``honeydew4`` :honeydew4:`████████` +``hotpink`` :hotpink:`████████` +``hotpink1`` :hotpink1:`████████` +``hotpink2`` :hotpink2:`████████` +``hotpink3`` :hotpink3:`████████` +``hotpink4`` :hotpink4:`████████` +``indianred`` :indianred:`████████` +``indianred1`` :indianred1:`████████` +``indianred2`` :indianred2:`████████` +``indianred3`` :indianred3:`████████` +``indianred4`` :indianred4:`████████` +``indigo`` :indigo:`████████` +``ivory`` :ivory:`████████` +``ivory1`` :ivory1:`████████` +``ivory2`` :ivory2:`████████` +``ivory3`` :ivory3:`████████` +``ivory4`` :ivory4:`████████` +``khaki`` :khaki:`████████` +``khaki1`` :khaki1:`████████` +``khaki2`` :khaki2:`████████` +``khaki3`` :khaki3:`████████` +``khaki4`` :khaki4:`████████` +``lavender`` :lavender:`████████` +``lavenderblush`` :lavenderblush:`████████` +``lavenderblush1`` :lavenderblush1:`████████` +``lavenderblush2`` :lavenderblush2:`████████` +``lavenderblush3`` :lavenderblush3:`████████` +``lavenderblush4`` :lavenderblush4:`████████` +``lawngreen`` :lawngreen:`████████` +``lemonchiffon`` :lemonchiffon:`████████` +``lemonchiffon1`` :lemonchiffon1:`████████` +``lemonchiffon2`` :lemonchiffon2:`████████` +``lemonchiffon3`` :lemonchiffon3:`████████` +``lemonchiffon4`` :lemonchiffon4:`████████` +``lightblue`` :lightblue:`████████` +``lightblue1`` :lightblue1:`████████` +``lightblue2`` :lightblue2:`████████` +``lightblue3`` :lightblue3:`████████` +``lightblue4`` :lightblue4:`████████` +``lightcoral`` :lightcoral:`████████` +``lightcyan`` :lightcyan:`████████` +``lightcyan1`` :lightcyan1:`████████` +``lightcyan2`` :lightcyan2:`████████` +``lightcyan3`` :lightcyan3:`████████` +``lightcyan4`` :lightcyan4:`████████` +``lightgoldenrod`` :lightgoldenrod:`████████` +``lightgoldenrod1`` :lightgoldenrod1:`████████` +``lightgoldenrod2`` :lightgoldenrod2:`████████` +``lightgoldenrod3`` :lightgoldenrod3:`████████` +``lightgoldenrod4`` :lightgoldenrod4:`████████` +``lightgoldenrodyellow`` :lightgoldenrodyellow:`████████` +``lightgray`` :lightgray:`████████` +``lightgreen`` :lightgreen:`████████` +``lightgrey`` :lightgrey:`████████` +``lightpink`` :lightpink:`████████` +``lightpink1`` :lightpink1:`████████` +``lightpink2`` :lightpink2:`████████` +``lightpink3`` :lightpink3:`████████` +``lightpink4`` :lightpink4:`████████` +``lightsalmon`` :lightsalmon:`████████` +``lightsalmon1`` :lightsalmon1:`████████` +``lightsalmon2`` :lightsalmon2:`████████` +``lightsalmon3`` :lightsalmon3:`████████` +``lightsalmon4`` :lightsalmon4:`████████` +``lightseagreen`` :lightseagreen:`████████` +``lightskyblue`` :lightskyblue:`████████` +``lightskyblue1`` :lightskyblue1:`████████` +``lightskyblue2`` :lightskyblue2:`████████` +``lightskyblue3`` :lightskyblue3:`████████` +``lightskyblue4`` :lightskyblue4:`████████` +``lightslateblue`` :lightslateblue:`████████` +``lightslategray`` :lightslategray:`████████` +``lightslategrey`` :lightslategrey:`████████` +``lightsteelblue`` :lightsteelblue:`████████` +``lightsteelblue1`` :lightsteelblue1:`████████` +``lightsteelblue2`` :lightsteelblue2:`████████` +``lightsteelblue3`` :lightsteelblue3:`████████` +``lightsteelblue4`` :lightsteelblue4:`████████` +``lightyellow`` :lightyellow:`████████` +``lightyellow1`` :lightyellow1:`████████` +``lightyellow2`` :lightyellow2:`████████` +``lightyellow3`` :lightyellow3:`████████` +``lightyellow4`` :lightyellow4:`████████` +``lime`` :lime:`████████` +``limegreen`` :limegreen:`████████` +``linen`` :linen:`████████` +``magenta`` :magenta:`████████` +``magenta1`` :magenta1:`████████` +``magenta2`` :magenta2:`████████` +``magenta3`` :magenta3:`████████` +``magenta4`` :magenta4:`████████` +``maroon`` :maroon:`████████` +``maroon1`` :maroon1:`████████` +``maroon2`` :maroon2:`████████` +``maroon3`` :maroon3:`████████` +``maroon4`` :maroon4:`████████` +``mediumaquamarine`` :mediumaquamarine:`████████` +``mediumblue`` :mediumblue:`████████` +``mediumorchid`` :mediumorchid:`████████` +``mediumorchid1`` :mediumorchid1:`████████` +``mediumorchid2`` :mediumorchid2:`████████` +``mediumorchid3`` :mediumorchid3:`████████` +``mediumorchid4`` :mediumorchid4:`████████` +``mediumpurple`` :mediumpurple:`████████` +``mediumpurple1`` :mediumpurple1:`████████` +``mediumpurple2`` :mediumpurple2:`████████` +``mediumpurple3`` :mediumpurple3:`████████` +``mediumpurple4`` :mediumpurple4:`████████` +``mediumseagreen`` :mediumseagreen:`████████` +``mediumslateblue`` :mediumslateblue:`████████` +``mediumspringgreen`` :mediumspringgreen:`████████` +``mediumturquoise`` :mediumturquoise:`████████` +``mediumvioletred`` :mediumvioletred:`████████` +``midnightblue`` :midnightblue:`████████` +``mintcream`` :mintcream:`████████` +``mistyrose`` :mistyrose:`████████` +``mistyrose1`` :mistyrose1:`████████` +``mistyrose2`` :mistyrose2:`████████` +``mistyrose3`` :mistyrose3:`████████` +``mistyrose4`` :mistyrose4:`████████` +``moccasin`` :moccasin:`████████` +``navajowhite`` :navajowhite:`████████` +``navajowhite1`` :navajowhite1:`████████` +``navajowhite2`` :navajowhite2:`████████` +``navajowhite3`` :navajowhite3:`████████` +``navajowhite4`` :navajowhite4:`████████` +``navy`` :navy:`████████` +``navyblue`` :navyblue:`████████` +``oldlace`` :oldlace:`████████` +``olive`` :olive:`████████` +``olivedrab`` :olivedrab:`████████` +``olivedrab1`` :olivedrab1:`████████` +``olivedrab2`` :olivedrab2:`████████` +``olivedrab3`` :olivedrab3:`████████` +``olivedrab4`` :olivedrab4:`████████` +``orange`` :orange:`████████` +``orange1`` :orange1:`████████` +``orange2`` :orange2:`████████` +``orange3`` :orange3:`████████` +``orange4`` :orange4:`████████` +``orangered`` :orangered:`████████` +``orangered1`` :orangered1:`████████` +``orangered2`` :orangered2:`████████` +``orangered3`` :orangered3:`████████` +``orangered4`` :orangered4:`████████` +``orchid`` :orchid:`████████` +``orchid1`` :orchid1:`████████` +``orchid2`` :orchid2:`████████` +``orchid3`` :orchid3:`████████` +``orchid4`` :orchid4:`████████` +``palegoldenrod`` :palegoldenrod:`████████` +``palegreen`` :palegreen:`████████` +``palegreen1`` :palegreen1:`████████` +``palegreen2`` :palegreen2:`████████` +``palegreen3`` :palegreen3:`████████` +``palegreen4`` :palegreen4:`████████` +``paleturquoise`` :paleturquoise:`████████` +``paleturquoise1`` :paleturquoise1:`████████` +``paleturquoise2`` :paleturquoise2:`████████` +``paleturquoise3`` :paleturquoise3:`████████` +``paleturquoise4`` :paleturquoise4:`████████` +``palevioletred`` :palevioletred:`████████` +``palevioletred1`` :palevioletred1:`████████` +``palevioletred2`` :palevioletred2:`████████` +``palevioletred3`` :palevioletred3:`████████` +``palevioletred4`` :palevioletred4:`████████` +``papayawhip`` :papayawhip:`████████` +``peachpuff`` :peachpuff:`████████` +``peachpuff1`` :peachpuff1:`████████` +``peachpuff2`` :peachpuff2:`████████` +``peachpuff3`` :peachpuff3:`████████` +``peachpuff4`` :peachpuff4:`████████` +``peru`` :peru:`████████` +``pink`` :pink:`████████` +``pink1`` :pink1:`████████` +``pink2`` :pink2:`████████` +``pink3`` :pink3:`████████` +``pink4`` :pink4:`████████` +``plum`` :plum:`████████` +``plum1`` :plum1:`████████` +``plum2`` :plum2:`████████` +``plum3`` :plum3:`████████` +``plum4`` :plum4:`████████` +``powderblue`` :powderblue:`████████` +``purple`` :purple:`████████` +``purple1`` :purple1:`████████` +``purple2`` :purple2:`████████` +``purple3`` :purple3:`████████` +``purple4`` :purple4:`████████` +``red`` :red:`████████` +``red1`` :red1:`████████` +``red2`` :red2:`████████` +``red3`` :red3:`████████` +``red4`` :red4:`████████` +``rosybrown`` :rosybrown:`████████` +``rosybrown1`` :rosybrown1:`████████` +``rosybrown2`` :rosybrown2:`████████` +``rosybrown3`` :rosybrown3:`████████` +``rosybrown4`` :rosybrown4:`████████` +``royalblue`` :royalblue:`████████` +``royalblue1`` :royalblue1:`████████` +``royalblue2`` :royalblue2:`████████` +``royalblue3`` :royalblue3:`████████` +``royalblue4`` :royalblue4:`████████` +``saddlebrown`` :saddlebrown:`████████` +``salmon`` :salmon:`████████` +``salmon1`` :salmon1:`████████` +``salmon2`` :salmon2:`████████` +``salmon3`` :salmon3:`████████` +``salmon4`` :salmon4:`████████` +``sandybrown`` :sandybrown:`████████` +``seagreen`` :seagreen:`████████` +``seagreen1`` :seagreen1:`████████` +``seagreen2`` :seagreen2:`████████` +``seagreen3`` :seagreen3:`████████` +``seagreen4`` :seagreen4:`████████` +``seashell`` :seashell:`████████` +``seashell1`` :seashell1:`████████` +``seashell2`` :seashell2:`████████` +``seashell3`` :seashell3:`████████` +``seashell4`` :seashell4:`████████` +``sienna`` :sienna:`████████` +``sienna1`` :sienna1:`████████` +``sienna2`` :sienna2:`████████` +``sienna3`` :sienna3:`████████` +``sienna4`` :sienna4:`████████` +``silver`` :silver:`████████` +``skyblue`` :skyblue:`████████` +``skyblue1`` :skyblue1:`████████` +``skyblue2`` :skyblue2:`████████` +``skyblue3`` :skyblue3:`████████` +``skyblue4`` :skyblue4:`████████` +``slateblue`` :slateblue:`████████` +``slateblue1`` :slateblue1:`████████` +``slateblue2`` :slateblue2:`████████` +``slateblue3`` :slateblue3:`████████` +``slateblue4`` :slateblue4:`████████` +``slategray`` :slategray:`████████` +``slategray1`` :slategray1:`████████` +``slategray2`` :slategray2:`████████` +``slategray3`` :slategray3:`████████` +``slategray4`` :slategray4:`████████` +``slategrey`` :slategrey:`████████` +``snow`` :snow:`████████` +``snow1`` :snow1:`████████` +``snow2`` :snow2:`████████` +``snow3`` :snow3:`████████` +``snow4`` :snow4:`████████` +``springgreen`` :springgreen:`████████` +``springgreen1`` :springgreen1:`████████` +``springgreen2`` :springgreen2:`████████` +``springgreen3`` :springgreen3:`████████` +``springgreen4`` :springgreen4:`████████` +``steelblue`` :steelblue:`████████` +``steelblue1`` :steelblue1:`████████` +``steelblue2`` :steelblue2:`████████` +``steelblue3`` :steelblue3:`████████` +``steelblue4`` :steelblue4:`████████` +``tan`` :tan:`████████` +``tan1`` :tan1:`████████` +``tan2`` :tan2:`████████` +``tan3`` :tan3:`████████` +``tan4`` :tan4:`████████` +``teal`` :teal:`████████` +``thistle`` :thistle:`████████` +``thistle1`` :thistle1:`████████` +``thistle2`` :thistle2:`████████` +``thistle3`` :thistle3:`████████` +``thistle4`` :thistle4:`████████` +``tomato`` :tomato:`████████` +``tomato1`` :tomato1:`████████` +``tomato2`` :tomato2:`████████` +``tomato3`` :tomato3:`████████` +``tomato4`` :tomato4:`████████` +``turquoise`` :turquoise:`████████` +``turquoise1`` :turquoise1:`████████` +``turquoise2`` :turquoise2:`████████` +``turquoise3`` :turquoise3:`████████` +``turquoise4`` :turquoise4:`████████` +``violet`` :violet:`████████` +``violetred`` :violetred:`████████` +``violetred1`` :violetred1:`████████` +``violetred2`` :violetred2:`████████` +``violetred3`` :violetred3:`████████` +``violetred4`` :violetred4:`████████` +``wheat`` :wheat:`████████` +``wheat1`` :wheat1:`████████` +``wheat2`` :wheat2:`████████` +``wheat3`` :wheat3:`████████` +``wheat4`` :wheat4:`████████` +``white`` :white:`████████` +``whitesmoke`` :whitesmoke:`████████` +``yellow`` :yellow:`████████` +``yellow1`` :yellow1:`████████` +``yellow2`` :yellow2:`████████` +``yellow3`` :yellow3:`████████` +``yellow4`` :yellow4:`████████` +``yellowgreen`` :yellowgreen:`████████` +========================== ====================================================================================================== diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/cursors.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/cursors.rst.txt new file mode 100644 index 0000000..b0dc73e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/cursors.rst.txt @@ -0,0 +1,250 @@ +.. include:: common.txt + +:mod:`pygame.cursors` +===================== + +.. module:: pygame.cursors + :synopsis: pygame module for cursor resources + +| :sl:`pygame module for cursor resources` + +Pygame offers control over the system hardware cursor. Pygame supports +black and white cursors (bitmap cursors), as well as system variant cursors and color cursors. +You control the cursor with functions inside :mod:`pygame.mouse`. + +This cursors module contains functions for loading and decoding various +cursor formats. These allow you to easily store your cursors in external files +or directly as encoded python strings. + +The module includes several standard cursors. The :func:`pygame.mouse.set_cursor()` +function takes several arguments. All those arguments have been stored in a +single tuple you can call like this: + +:: + + >>> pygame.mouse.set_cursor(*pygame.cursors.arrow) + +The following variables can be passed to ``pygame.mouse.set_cursor`` function: + + * ``pygame.cursors.arrow`` + + * ``pygame.cursors.diamond`` + + * ``pygame.cursors.broken_x`` + + * ``pygame.cursors.tri_left`` + + * ``pygame.cursors.tri_right`` + +This module also contains a few cursors as formatted strings. You'll need to +pass these to ``pygame.cursors.compile()`` function before you can use them. +The example call would look like this: + +:: + + >>> cursor = pygame.cursors.compile(pygame.cursors.textmarker_strings) + >>> pygame.mouse.set_cursor((8, 16), (0, 0), *cursor) + +The following strings can be converted into cursor bitmaps with +``pygame.cursors.compile()`` : + + * ``pygame.cursors.thickarrow_strings`` + + * ``pygame.cursors.sizer_x_strings`` + + * ``pygame.cursors.sizer_y_strings`` + + * ``pygame.cursors.sizer_xy_strings`` + + * ``pygame.cursor.textmarker_strings`` + +.. function:: compile + + | :sl:`create binary cursor data from simple strings` + | :sg:`compile(strings, black='X', white='.', xor='o') -> data, mask` + + A sequence of strings can be used to create binary cursor data for the + system cursor. This returns the binary data in the form of two tuples. + Those can be passed as the third and fourth arguments respectively of the + :func:`pygame.mouse.set_cursor()` function. + + If you are creating your own cursor strings, you can use any value represent + the black and white pixels. Some system allow you to set a special toggle + color for the system color, this is also called the xor color. If the system + does not support xor cursors, that color will simply be black. + + The height must be divisible by 8. The width of the strings must all be equal + and be divisible by 8. If these two conditions are not met, ``ValueError`` is + raised. + An example set of cursor strings looks like this + + :: + + thickarrow_strings = ( #sized 24x24 + "XX ", + "XXX ", + "XXXX ", + "XX.XX ", + "XX..XX ", + "XX...XX ", + "XX....XX ", + "XX.....XX ", + "XX......XX ", + "XX.......XX ", + "XX........XX ", + "XX........XXX ", + "XX......XXXXX ", + "XX.XXX..XX ", + "XXXX XX..XX ", + "XX XX..XX ", + " XX..XX ", + " XX..XX ", + " XX..XX ", + " XXXX ", + " XX ", + " ", + " ", + " ") + + .. ## pygame.cursors.compile ## + +.. function:: load_xbm + + | :sl:`load cursor data from an XBM file` + | :sg:`load_xbm(cursorfile) -> cursor_args` + | :sg:`load_xbm(cursorfile, maskfile) -> cursor_args` + + This loads cursors for a simple subset of ``XBM`` files. ``XBM`` files are + traditionally used to store cursors on UNIX systems, they are an ASCII + format used to represent simple images. + + Sometimes the black and white color values will be split into two separate + ``XBM`` files. You can pass a second maskfile argument to load the two + images into a single cursor. + + The cursorfile and maskfile arguments can either be filenames or file-like + object with the readlines method. + + The return value cursor_args can be passed directly to the + ``pygame.mouse.set_cursor()`` function. + + .. ## pygame.cursors.load_xbm ## + + + +.. class:: Cursor + + | :sl:`pygame object representing a cursor` + | :sg:`Cursor(size, hotspot, xormasks, andmasks) -> Cursor` + | :sg:`Cursor(hotspot, surface) -> Cursor` + | :sg:`Cursor(constant) -> Cursor` + | :sg:`Cursor(Cursor) -> Cursor` + | :sg:`Cursor() -> Cursor` + + In pygame 2, there are 3 types of cursors you can create to give your + game that little bit of extra polish. There's **bitmap** type cursors, + which existed in pygame 1.x, and are compiled from a string or load from an xbm file. + Then there are **system** type cursors, where you choose a preset that will + convey the same meaning but look native across different operating systems. + Finally you can create a **color** cursor, which displays a pygame surface as the cursor. + + **Creating a system cursor** + + Choose a constant from this list, pass it into ``pygame.cursors.Cursor(constant)``, + and you're good to go. Be advised that not all systems support every system + cursor, and you may get a substitution instead. For example, on MacOS, + WAIT/WAITARROW should show up as an arrow, and SIZENWSE/SIZENESW/SIZEALL + should show up as a closed hand. And on Wayland, every SIZE cursor should + show up as a hand. + + :: + + Pygame Cursor Constant Description + -------------------------------------------- + pygame.SYSTEM_CURSOR_ARROW arrow + pygame.SYSTEM_CURSOR_IBEAM i-beam + pygame.SYSTEM_CURSOR_WAIT wait + pygame.SYSTEM_CURSOR_CROSSHAIR crosshair + pygame.SYSTEM_CURSOR_WAITARROW small wait cursor + (or wait if not available) + pygame.SYSTEM_CURSOR_SIZENWSE double arrow pointing + northwest and southeast + pygame.SYSTEM_CURSOR_SIZENESW double arrow pointing + northeast and southwest + pygame.SYSTEM_CURSOR_SIZEWE double arrow pointing + west and east + pygame.SYSTEM_CURSOR_SIZENS double arrow pointing + north and south + pygame.SYSTEM_CURSOR_SIZEALL four pointed arrow pointing + north, south, east, and west + pygame.SYSTEM_CURSOR_NO slashed circle or crossbones + pygame.SYSTEM_CURSOR_HAND hand + + **Creating a cursor without passing arguments** + + In addition to the cursor constants available and described above, + you can also call ``pygame.cursors.Cursor()``, and your cursor is ready (doing that is the same as + calling ``pygame.cursors.Cursor(pygame.SYSTEM_CURSOR_ARROW)``. + Doing one of those calls actually creates a system cursor using the default native image. + + **Creating a color cursor** + + To create a color cursor, create a ``Cursor`` from a ``hotspot`` and a ``surface``. + ``hotspot`` is an (x,y) coordinate that determines where in the cursor the exact point is. + The hotspot position must be within the bounds of the ``surface``. + + **Creating a bitmap cursor** + + When the mouse cursor is visible, it will be displayed as a black and white + bitmap using the given bitmask arrays. The ``size`` is a sequence containing + the cursor width and height. ``hotspot`` is a sequence containing the cursor + hotspot position. + + A cursor has a width and height, but a mouse position is represented by a + set of point coordinates. So the value passed into the cursor ``hotspot`` + variable helps pygame to actually determine at what exact point the cursor + is at. + + ``xormasks`` is a sequence of bytes containing the cursor xor data masks. + Lastly ``andmasks``, a sequence of bytes containing the cursor bitmask data. + To create these variables, we can make use of the + :func:`pygame.cursors.compile()` function. + + Width and height must be a multiple of 8, and the mask arrays must be the + correct size for the given width and height. Otherwise an exception is raised. + + .. method:: copy + | :sl:`copy the current cursor` + | :sg:`copy() -> Cursor` + + Returns a new Cursor object with the same data and hotspot as the original. + .. ## pygame.cursors.Cursor.copy ## + + + .. attribute:: type + + | :sl:`Gets the cursor type` + | :sg:`type -> string` + + The type will be ``"system"``, ``"bitmap"``, or ``"color"``. + + .. ## pygame.cursors.Cursor.type ## + + .. attribute:: data + + | :sl:`Gets the cursor data` + | :sg:`data -> tuple` + + Returns the data that was used to create this cursor object, wrapped up in a tuple. + + .. ## pygame.cursors.Cursor.data ## + + .. versionadded:: 2.0.1 + + .. ## pygame.cursors.Cursor ## + +.. ## pygame.cursors ## + +Example code for creating and settings cursors. (Click the mouse to switch cursor) + +.. literalinclude:: code_examples/cursors_module_example.py diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/display.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/display.rst.txt new file mode 100644 index 0000000..95a7ed6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/display.rst.txt @@ -0,0 +1,716 @@ +.. include:: common.txt + +:mod:`pygame.display` +===================== + +.. module:: pygame.display + :synopsis: pygame module to control the display window and screen + +| :sl:`pygame module to control the display window and screen` + +This module offers control over the pygame display. Pygame has a single display +Surface that is either contained in a window or runs full screen. Once you +create the display you treat it as a regular Surface. Changes are not +immediately visible onscreen; you must choose one of the two flipping functions +to update the actual display. + +The origin of the display, where x = 0 and y = 0, is the top left of the +screen. Both axes increase positively towards the bottom right of the screen. + +The pygame display can actually be initialized in one of several modes. By +default, the display is a basic software driven framebuffer. You can request +special modules like automatic scaling or OpenGL support. These are +controlled by flags passed to ``pygame.display.set_mode()``. + +Pygame can only have a single display active at any time. Creating a new one +with ``pygame.display.set_mode()`` will close the previous display. To detect +the number and size of attached screens, you can use +``pygame.display.get_desktop_sizes`` and then select appropriate window size +and display index to pass to ``pygame.display.set_mode()``. + +For backward compatibility ``pygame.display`` allows precise control over +the pixel format or display resolutions. This used to be necessary with old +grahics cards and CRT screens, but is usually not needed any more. Use the +functions ``pygame.display.mode_ok()``, ``pygame.display.list_modes()``, and +``pygame.display.Info()`` to query detailed information about the display. + +Once the display Surface is created, the functions from this module affect the +single existing display. The Surface becomes invalid if the module is +uninitialized. If a new display mode is set, the existing Surface will +automatically switch to operate on the new display. + +When the display mode is set, several events are placed on the pygame event +queue. ``pygame.QUIT`` is sent when the user has requested the program to +shut down. The window will receive ``pygame.ACTIVEEVENT`` events as the display +gains and loses input focus. If the display is set with the +``pygame.RESIZABLE`` flag, ``pygame.VIDEORESIZE`` events will be sent when the +user adjusts the window dimensions. Hardware displays that draw direct to the +screen will get ``pygame.VIDEOEXPOSE`` events when portions of the window must +be redrawn. + +A new windowevent API was introduced in pygame 2.0.1. Check event module docs +for more information on that + +Some display environments have an option for automatically stretching all +windows. When this option is enabled, this automatic stretching distorts the +appearance of the pygame window. In the pygame examples directory, there is +example code (prevent_display_stretching.py) which shows how to disable this +automatic stretching of the pygame display on Microsoft Windows (Vista or newer +required). + +.. function:: init + + | :sl:`Initialize the display module` + | :sg:`init() -> None` + + Initializes the pygame display module. The display module cannot do anything + until it is initialized. This is usually handled for you automatically when + you call the higher level ``pygame.init()``. + + Pygame will select from one of several internal display backends when it is + initialized. The display mode will be chosen depending on the platform and + permissions of current user. Before the display module is initialized the + environment variable ``SDL_VIDEODRIVER`` can be set to control which backend + is used. The systems with multiple choices are listed here. + + :: + + Windows : windib, directx + Unix : x11, dga, fbcon, directfb, ggi, vgl, svgalib, aalib + + On some platforms it is possible to embed the pygame display into an already + existing window. To do this, the environment variable ``SDL_WINDOWID`` must + be set to a string containing the window id or handle. The environment + variable is checked when the pygame display is initialized. Be aware that + there can be many strange side effects when running in an embedded display. + + It is harmless to call this more than once, repeated calls have no effect. + + .. ## pygame.display.init ## + +.. function:: quit + + | :sl:`Uninitialize the display module` + | :sg:`quit() -> None` + + This will shut down the entire display module. This means any active + displays will be closed. This will also be handled automatically when the + program exits. + + It is harmless to call this more than once, repeated calls have no effect. + + .. ## pygame.display.quit ## + +.. function:: get_init + + | :sl:`Returns True if the display module has been initialized` + | :sg:`get_init() -> bool` + + Returns True if the :mod:`pygame.display` module is currently initialized. + + .. ## pygame.display.get_init ## + +.. function:: set_mode + + | :sl:`Initialize a window or screen for display` + | :sg:`set_mode(size=(0, 0), flags=0, depth=0, display=0, vsync=0) -> Surface` + + This function will create a display Surface. The arguments passed in are + requests for a display type. The actual created display will be the best + possible match supported by the system. + + Note that calling this function implicitly initializes ``pygame.display``, if + it was not initialized before. + + The size argument is a pair of numbers representing the width and + height. The flags argument is a collection of additional options. The depth + argument represents the number of bits to use for color. + + The Surface that gets returned can be drawn to like a regular Surface but + changes will eventually be seen on the monitor. + + If no size is passed or is set to ``(0, 0)`` and pygame uses ``SDL`` + version 1.2.10 or above, the created Surface will have the same size as the + current screen resolution. If only the width or height are set to ``0``, the + Surface will have the same width or height as the screen resolution. Using a + ``SDL`` version prior to 1.2.10 will raise an exception. + + It is usually best to not pass the depth argument. It will default to the + best and fastest color depth for the system. If your game requires a + specific color format you can control the depth with this argument. Pygame + will emulate an unavailable color depth which can be slow. + + When requesting fullscreen display modes, sometimes an exact match for the + requested size cannot be made. In these situations pygame will select + the closest compatible match. The returned surface will still always match + the requested size. + + On high resolution displays(4k, 1080p) and tiny graphics games (640x480) + show up very small so that they are unplayable. SCALED scales up the window + for you. The game thinks it's a 640x480 window, but really it can be bigger. + Mouse events are scaled for you, so your game doesn't need to do it. Note + that SCALED is considered an experimental API and may change in future + releases. + + The flags argument controls which type of display you want. There are + several to choose from, and you can even combine multiple types using the + bitwise or operator, (the pipe "|" character). Here are the display + flags you will want to choose from: + + :: + + pygame.FULLSCREEN create a fullscreen display + pygame.DOUBLEBUF (obsolete in pygame 2) recommended for HWSURFACE or OPENGL + pygame.HWSURFACE (obsolete in pygame 2) hardware accelerated, only in FULLSCREEN + pygame.OPENGL create an OpenGL-renderable display + pygame.RESIZABLE display window should be sizeable + pygame.NOFRAME display window will have no border or controls + pygame.SCALED resolution depends on desktop size and scale graphics + pygame.SHOWN window is opened in visible mode (default) + pygame.HIDDEN window is opened in hidden mode + + + .. versionadded:: 2.0.0 ``SCALED``, ``SHOWN`` and ``HIDDEN`` + + By setting the ``vsync`` parameter to ``1``, it is possible to get a display + with vertical sync, but you are not guaranteed to get one. The request only + works at all for calls to ``set_mode()`` with the ``pygame.OPENGL`` or + ``pygame.SCALED`` flags set, and is still not guaranteed even with one of + those set. What you get depends on the hardware and driver configuration + of the system pygame is running on. Here is an example usage of a call + to ``set_mode()`` that may give you a display with vsync: + + :: + + flags = pygame.OPENGL | pygame.FULLSCREEN + window_surface = pygame.display.set_mode((1920, 1080), flags, vsync=1) + + Vsync behaviour is considered experimental, and may change in future releases. + + .. versionadded:: 2.0.0 ``vsync`` + + Basic example: + + :: + + # Open a window on the screen + screen_width=700 + screen_height=400 + screen=pygame.display.set_mode([screen_width, screen_height]) + + The display index ``0`` means the default display is used. If no display + index argument is provided, the default display can be overridden with an + environment variable. + + + .. versionchanged:: 1.9.5 ``display`` argument added + + .. ## pygame.display.set_mode ## + +.. function:: get_surface + + | :sl:`Get a reference to the currently set display surface` + | :sg:`get_surface() -> Surface` + + Return a reference to the currently set display Surface. If no display mode + has been set this will return None. + + .. ## pygame.display.get_surface ## + +.. function:: flip + + | :sl:`Update the full display Surface to the screen` + | :sg:`flip() -> None` + + This will update the contents of the entire display. If your display mode is + using the flags ``pygame.HWSURFACE`` and ``pygame.DOUBLEBUF`` on pygame 1, + this will wait for a vertical retrace and swap the surfaces. + + When using an ``pygame.OPENGL`` display mode this will perform a gl buffer + swap. + + .. ## pygame.display.flip ## + +.. function:: update + + | :sl:`Update portions of the screen for software displays` + | :sg:`update(rectangle=None) -> None` + | :sg:`update(rectangle_list) -> None` + + This function is like an optimized version of ``pygame.display.flip()`` for + software displays. It allows only a portion of the screen to updated, + instead of the entire area. If no argument is passed it updates the entire + Surface area like ``pygame.display.flip()``. + + Note that calling ``display.update(None)`` means no part of the window is + updated. Whereas ``display.update()`` means the whole window is updated. + + You can pass the function a single rectangle, or a sequence of rectangles. + It is more efficient to pass many rectangles at once than to call update + multiple times with single or a partial list of rectangles. If passing a + sequence of rectangles it is safe to include None values in the list, which + will be skipped. + + This call cannot be used on ``pygame.OPENGL`` displays and will generate an + exception. + + .. ## pygame.display.update ## + +.. function:: get_driver + + | :sl:`Get the name of the pygame display backend` + | :sg:`get_driver() -> name` + + Pygame chooses one of many available display backends when it is + initialized. This returns the internal name used for the display backend. + This can be used to provide limited information about what display + capabilities might be accelerated. See the ``SDL_VIDEODRIVER`` flags in + ``pygame.display.set_mode()`` to see some of the common options. + + .. ## pygame.display.get_driver ## + +.. function:: Info + + | :sl:`Create a video display information object` + | :sg:`Info() -> VideoInfo` + + Creates a simple object containing several attributes to describe the + current graphics environment. If this is called before + ``pygame.display.set_mode()`` some platforms can provide information about + the default display mode. This can also be called after setting the display + mode to verify specific display options were satisfied. The VidInfo object + has several attributes: + + :: + + hw: 1 if the display is hardware accelerated + wm: 1 if windowed display modes can be used + video_mem: The megabytes of video memory on the display. This is 0 if + unknown + bitsize: Number of bits used to store each pixel + bytesize: Number of bytes used to store each pixel + masks: Four values used to pack RGBA values into pixels + shifts: Four values used to pack RGBA values into pixels + losses: Four values used to pack RGBA values into pixels + blit_hw: 1 if hardware Surface blitting is accelerated + blit_hw_CC: 1 if hardware Surface colorkey blitting is accelerated + blit_hw_A: 1 if hardware Surface pixel alpha blitting is accelerated + blit_sw: 1 if software Surface blitting is accelerated + blit_sw_CC: 1 if software Surface colorkey blitting is accelerated + blit_sw_A: 1 if software Surface pixel alpha blitting is accelerated + current_h, current_w: Height and width of the current video mode, or + of the desktop mode if called before the display.set_mode + is called. (current_h, current_w are available since + SDL 1.2.10, and pygame 1.8.0). They are -1 on error, or if + an old SDL is being used. + + .. ## pygame.display.Info ## + +.. function:: get_wm_info + + | :sl:`Get information about the current windowing system` + | :sg:`get_wm_info() -> dict` + + Creates a dictionary filled with string keys. The strings and values are + arbitrarily created by the system. Some systems may have no information and + an empty dictionary will be returned. Most platforms will return a "window" + key with the value set to the system id for the current display. + + .. versionadded:: 1.7.1 + + .. ## pygame.display.get_wm_info ## + +.. function:: get_desktop_sizes + + | :sl:`Get sizes of active desktops` + | :sg:`get_desktop_sizes() -> list` + + This function returns the sizes of the currrently configured + virtual desktops as a list of (x, y) tuples of integers. + + The length of the list is not the same as the number of attached monitors, + as a desktop can be mirrored across multiple monitors. The desktop sizes + do not indicate the maximum monitor resolutions supported by the hardware, + but the desktop size configured in the operating system. + + In order to fit windows into the desktop as it is currently configured, and + to respect the resolution configured by the operating system in fullscreen + mode, this function *should* be used to replace many use cases of + ``pygame.display.list_modes()`` whenever applicable. + + .. versionadded:: 2.0.0 + +.. function:: list_modes + + | :sl:`Get list of available fullscreen modes` + | :sg:`list_modes(depth=0, flags=pygame.FULLSCREEN, display=0) -> list` + + This function returns a list of possible sizes for a specified color + depth. The return value will be an empty list if no display modes are + available with the given arguments. A return value of ``-1`` means that + any requested size should work (this is likely the case for windowed + modes). Mode sizes are sorted from biggest to smallest. + + If depth is ``0``, the current/best color depth for the display is used. + The flags defaults to ``pygame.FULLSCREEN``, but you may need to add + additional flags for specific fullscreen modes. + + The display index ``0`` means the default display is used. + + Since pygame 2.0, ``pygame.display.get_desktop_sizes()`` has taken over + some use cases from ``pygame.display.list_modes()``: + + To find a suitable size for non-fullscreen windows, it is preferable to + use ``pygame.display.get_desktop_sizes()`` to get the size of the *current* + desktop, and to then choose a smaller window size. This way, the window is + guaranteed to fit, even when the monitor is configured to a lower resolution + than the maximum supported by the hardware. + + To avoid changing the physical monitor resolution, it is also preferable to + use ``pygame.display.get_desktop_sizes()`` to determine the fullscreen + resolution. Developers are strongly advised to default to the current + physical monitor resolution unless the user explicitly requests a different + one (e.g. in an options menu or configuration file). + + .. versionchanged:: 1.9.5 ``display`` argument added + + .. ## pygame.display.list_modes ## + +.. function:: mode_ok + + | :sl:`Pick the best color depth for a display mode` + | :sg:`mode_ok(size, flags=0, depth=0, display=0) -> depth` + + This function uses the same arguments as ``pygame.display.set_mode()``. It + is used to determine if a requested display mode is available. It will + return ``0`` if the display mode cannot be set. Otherwise it will return a + pixel depth that best matches the display asked for. + + Usually the depth argument is not passed, but some platforms can support + multiple display depths. If passed it will hint to which depth is a better + match. + + The function will return ``0`` if the passed display flags cannot be set. + + The display index ``0`` means the default display is used. + + .. versionchanged:: 1.9.5 ``display`` argument added + + .. ## pygame.display.mode_ok ## + +.. function:: gl_get_attribute + + | :sl:`Get the value for an OpenGL flag for the current display` + | :sg:`gl_get_attribute(flag) -> value` + + After calling ``pygame.display.set_mode()`` with the ``pygame.OPENGL`` flag, + it is a good idea to check the value of any requested OpenGL attributes. See + ``pygame.display.gl_set_attribute()`` for a list of valid flags. + + .. ## pygame.display.gl_get_attribute ## + +.. function:: gl_set_attribute + + | :sl:`Request an OpenGL display attribute for the display mode` + | :sg:`gl_set_attribute(flag, value) -> None` + + When calling ``pygame.display.set_mode()`` with the ``pygame.OPENGL`` flag, + Pygame automatically handles setting the OpenGL attributes like color and + double-buffering. OpenGL offers several other attributes you may want control + over. Pass one of these attributes as the flag, and its appropriate value. + This must be called before ``pygame.display.set_mode()``. + + Many settings are the requested minimum. Creating a window with an OpenGL context + will fail if OpenGL cannot provide the requested attribute, but it may for example + give you a stencil buffer even if you request none, or it may give you a larger + one than requested. + + The ``OPENGL`` flags are: + + :: + + GL_ALPHA_SIZE, GL_DEPTH_SIZE, GL_STENCIL_SIZE, GL_ACCUM_RED_SIZE, + GL_ACCUM_GREEN_SIZE, GL_ACCUM_BLUE_SIZE, GL_ACCUM_ALPHA_SIZE, + GL_MULTISAMPLEBUFFERS, GL_MULTISAMPLESAMPLES, GL_STEREO + + :const:`GL_MULTISAMPLEBUFFERS` + + Whether to enable multisampling anti-aliasing. + Defaults to 0 (disabled). + + Set ``GL_MULTISAMPLESAMPLES`` to a value + above 0 to control the amount of anti-aliasing. + A typical value is 2 or 3. + + :const:`GL_STENCIL_SIZE` + + Minimum bit size of the stencil buffer. Defaults to 0. + + :const:`GL_DEPTH_SIZE` + + Minimum bit size of the depth buffer. Defaults to 16. + + :const:`GL_STEREO` + + 1 enables stereo 3D. Defaults to 0. + + :const:`GL_BUFFER_SIZE` + + Minimum bit size of the frame buffer. Defaults to 0. + + .. versionadded:: 2.0.0 Additional attributes: + + :: + + GL_ACCELERATED_VISUAL, + GL_CONTEXT_MAJOR_VERSION, GL_CONTEXT_MINOR_VERSION, + GL_CONTEXT_FLAGS, GL_CONTEXT_PROFILE_MASK, + GL_SHARE_WITH_CURRENT_CONTEXT, + GL_CONTEXT_RELEASE_BEHAVIOR, + GL_FRAMEBUFFER_SRGB_CAPABLE + + :const:`GL_CONTEXT_PROFILE_MASK` + + Sets the OpenGL profile to one of these values: + + :: + + GL_CONTEXT_PROFILE_CORE disable deprecated features + GL_CONTEXT_PROFILE_COMPATIBILITY allow deprecated features + GL_CONTEXT_PROFILE_ES allow only the ES feature + subset of OpenGL + + :const:`GL_ACCELERATED_VISUAL` + + Set to 1 to require hardware acceleration, or 0 to force software render. + By default, both are allowed. + + .. ## pygame.display.gl_set_attribute ## + +.. function:: get_active + + | :sl:`Returns True when the display is active on the screen` + | :sg:`get_active() -> bool` + + Returns True when the display Surface is considered actively + renderable on the screen and may be visible to the user. This is + the default state immediately after ``pygame.display.set_mode()``. + This method may return True even if the application is fully hidden + behind another application window. + + This will return False if the display Surface has been iconified or + minimized (either via ``pygame.display.iconify()`` or via an OS + specific method such as the minimize-icon available on most + desktops). + + The method can also return False for other reasons without the + application being explicitly iconified or minimized by the user. A + notable example being if the user has multiple virtual desktops and + the display Surface is not on the active virtual desktop. + + .. note:: This function returning True is unrelated to whether the + application has input focus. Please see + ``pygame.key.get_focused()`` and ``pygame.mouse.get_focused()`` + for APIs related to input focus. + + .. ## pygame.display.get_active ## + +.. function:: iconify + + | :sl:`Iconify the display surface` + | :sg:`iconify() -> bool` + + Request the window for the display surface be iconified or hidden. Not all + systems and displays support an iconified display. The function will return + True if successful. + + When the display is iconified ``pygame.display.get_active()`` will return + ``False``. The event queue should receive an ``ACTIVEEVENT`` event when the + window has been iconified. Additionally, the event queue also recieves a + ``WINDOWEVENT_MINIMIZED`` event when the window has been iconified on pygame 2. + + .. ## pygame.display.iconify ## + +.. function:: toggle_fullscreen + + | :sl:`Switch between fullscreen and windowed displays` + | :sg:`toggle_fullscreen() -> int` + + Switches the display window between windowed and fullscreen modes. + Display driver support is not great when using pygame 1, but with + pygame 2 it is the most reliable method to switch to and from fullscreen. + + Supported display drivers in pygame 1: + + * x11 (Linux/Unix) + * wayland (Linux/Unix) + + Supported display drivers in pygame 2: + + * windows (Windows) + * x11 (Linux/Unix) + * wayland (Linux/Unix) + * cocoa (OSX/Mac) + + .. Note:: :func:`toggle_fullscreen` doesn't work on Windows + unless the window size is in :func:`pygame.display.list_modes()` or + the window is created with the flag ``pygame.SCALED``. + See `issue #2380 `_. + + .. ## pygame.display.toggle_fullscreen ## + +.. function:: set_gamma + + | :sl:`Change the hardware gamma ramps` + | :sg:`set_gamma(red, green=None, blue=None) -> bool` + + Set the red, green, and blue gamma values on the display hardware. If the + green and blue arguments are not passed, they will both be the same as red. + Not all systems and hardware support gamma ramps, if the function succeeds + it will return ``True``. + + A gamma value of ``1.0`` creates a linear color table. Lower values will + darken the display and higher values will brighten. + + .. ## pygame.display.set_gamma ## + +.. function:: set_gamma_ramp + + | :sl:`Change the hardware gamma ramps with a custom lookup` + | :sg:`set_gamma_ramp(red, green, blue) -> bool` + + Set the red, green, and blue gamma ramps with an explicit lookup table. Each + argument should be sequence of 256 integers. The integers should range + between ``0`` and ``0xffff``. Not all systems and hardware support gamma + ramps, if the function succeeds it will return ``True``. + + .. ## pygame.display.set_gamma_ramp ## + +.. function:: set_icon + + | :sl:`Change the system image for the display window` + | :sg:`set_icon(Surface) -> None` + + Sets the runtime icon the system will use to represent the display window. + All windows default to a simple pygame logo for the window icon. + + Note that calling this function implicitly initializes ``pygame.display``, if + it was not initialized before. + + You can pass any surface, but most systems want a smaller image around + 32x32. The image can have colorkey transparency which will be passed to the + system. + + Some systems do not allow the window icon to change after it has been shown. + This function can be called before ``pygame.display.set_mode()`` to create + the icon before the display mode is set. + + .. ## pygame.display.set_icon ## + +.. function:: set_caption + + | :sl:`Set the current window caption` + | :sg:`set_caption(title, icontitle=None) -> None` + + If the display has a window title, this function will change the name on the + window. In pygame 1.x, some systems supported an alternate shorter title to + be used for minimized displays, but in pygame 2 ``icontitle`` does nothing. + + .. ## pygame.display.set_caption ## + +.. function:: get_caption + + | :sl:`Get the current window caption` + | :sg:`get_caption() -> (title, icontitle)` + + Returns the title and icontitle for the display window. In pygame 2.x + these will always be the same value. + + .. ## pygame.display.get_caption ## + +.. function:: set_palette + + | :sl:`Set the display color palette for indexed displays` + | :sg:`set_palette(palette=None) -> None` + + This will change the video display color palette for 8-bit displays. This + does not change the palette for the actual display Surface, only the palette + that is used to display the Surface. If no palette argument is passed, the + system default palette will be restored. The palette is a sequence of + ``RGB`` triplets. + + .. ## pygame.display.set_palette ## + +.. function:: get_num_displays + + | :sl:`Return the number of displays` + | :sg:`get_num_displays() -> int` + + Returns the number of available displays. This is always 1 if + :func:`pygame.get_sdl_version()` returns a major version number below 2. + + .. versionadded:: 1.9.5 + + .. ## pygame.display.get_num_displays ## + +.. function:: get_window_size + + | :sl:`Return the size of the window or screen` + | :sg:`get_window_size() -> tuple` + + Returns the size of the window initialized with :func:`pygame.display.set_mode()`. + This may differ from the size of the display surface if ``SCALED`` is used. + + .. versionadded:: 2.0.0 + + .. ## pygame.display.get_window_size ## + +.. function:: get_allow_screensaver + + | :sl:`Return whether the screensaver is allowed to run.` + | :sg:`get_allow_screensaver() -> bool` + + Return whether screensaver is allowed to run whilst the app is running. + Default is ``False``. + By default pygame does not allow the screensaver during game play. + + .. note:: Some platforms do not have a screensaver or support + disabling the screensaver. Please see + :func:`pygame.display.set_allow_screensaver()` for + caveats with screensaver support. + + .. versionadded:: 2.0.0 + + .. ## pygame.display.get_allow_screensaver ## + +.. function:: set_allow_screensaver + + | :sl:`Set whether the screensaver may run` + | :sg:`set_allow_screensaver(bool) -> None` + + Change whether screensavers should be allowed whilst the app is running. + The default value of the argument to the function is True. + By default pygame does not allow the screensaver during game play. + + If the screensaver has been disallowed due to this function, it will automatically + be allowed to run when :func:`pygame.quit()` is called. + + It is possible to influence the default value via the environment variable + ``SDL_HINT_VIDEO_ALLOW_SCREENSAVER``, which can be set to either ``0`` (disable) + or ``1`` (enable). + + .. note:: Disabling screensaver is subject to platform support. + When platform support is absent, this function will + silently appear to work even though the screensaver state + is unchanged. The lack of feedback is due to SDL not + providing any supported method for determining whether + it supports changing the screensaver state. + ``SDL_HINT_VIDEO_ALLOW_SCREENSAVER`` is available in SDL 2.0.2 or later. + SDL1.2 does not implement this. + + .. versionadded:: 2.0.0 + + + .. ## pygame.display.set_allow_screensaver ## + +.. ## pygame.display ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/draw.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/draw.rst.txt new file mode 100644 index 0000000..c82d021 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/draw.rst.txt @@ -0,0 +1,559 @@ +.. include:: common.txt + +:mod:`pygame.draw` +================== + +.. module:: pygame.draw + :synopsis: pygame module for drawing shapes + +| :sl:`pygame module for drawing shapes` + +Draw several simple shapes to a surface. These functions will work for +rendering to any format of surface. Rendering to hardware surfaces will be +slower than regular software surfaces. + +Most of the functions take a width argument to represent the size of stroke +(thickness) around the edge of the shape. If a width of 0 is passed the shape +will be filled (solid). + +All the drawing functions respect the clip area for the surface and will be +constrained to that area. The functions return a rectangle representing the +bounding area of changed pixels. This bounding rectangle is the 'minimum' +bounding box that encloses the affected area. + +All the drawing functions accept a color argument that can be one of the +following formats: + + - a :mod:`pygame.Color` object + - an ``(RGB)`` triplet (tuple/list) + - an ``(RGBA)`` quadruplet (tuple/list) + - an integer value that has been mapped to the surface's pixel format + (see :func:`pygame.Surface.map_rgb` and :func:`pygame.Surface.unmap_rgb`) + +A color's alpha value will be written directly into the surface (if the +surface contains pixel alphas), but the draw function will not draw +transparently. + +These functions temporarily lock the surface they are operating on. Many +sequential drawing calls can be sped up by locking and unlocking the surface +object around the draw calls (see :func:`pygame.Surface.lock` and +:func:`pygame.Surface.unlock`). + +.. note :: + See the :mod:`pygame.gfxdraw` module for alternative draw methods. + + +.. function:: rect + + | :sl:`draw a rectangle` + | :sg:`rect(surface, color, rect) -> Rect` + | :sg:`rect(surface, color, rect, width=0, border_radius=0, border_top_left_radius=-1, border_top_right_radius=-1, border_bottom_left_radius=-1, border_bottom_right_radius=-1) -> Rect` + + Draws a rectangle on the given surface. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param Rect rect: rectangle to draw, position and dimensions + :param int width: (optional) used for line thickness or to indicate that + the rectangle is to be filled (not to be confused with the width value + of the ``rect`` parameter) + + | if ``width == 0``, (default) fill the rectangle + | if ``width > 0``, used for line thickness + | if ``width < 0``, nothing will be drawn + | + + .. note:: + When using ``width`` values ``> 1``, the edge lines will grow + outside the original boundary of the rect. For more details on + how the thickness for edge lines grow, refer to the ``width`` notes + of the :func:`pygame.draw.line` function. + :param int border_radius: (optional) used for drawing rectangle with rounded corners. + The supported range is [0, min(height, width) / 2], with 0 representing a rectangle + without rounded corners. + :param int border_top_left_radius: (optional) used for setting the value of top left + border. If you don't set this value, it will use the border_radius value. + :param int border_top_right_radius: (optional) used for setting the value of top right + border. If you don't set this value, it will use the border_radius value. + :param int border_bottom_left_radius: (optional) used for setting the value of bottom left + border. If you don't set this value, it will use the border_radius value. + :param int border_bottom_right_radius: (optional) used for setting the value of bottom right + border. If you don't set this value, it will use the border_radius value. + + | if ``border_radius < 1`` it will draw rectangle without rounded corners + | if any of border radii has the value ``< 0`` it will use value of the border_radius + | If sum of radii on the same side of the rectangle is greater than the rect size the radii + | will get scaled + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the position of the given ``rect`` + parameter and its width and height will be 0 + :rtype: Rect + + .. note:: + The :func:`pygame.Surface.fill()` method works just as well for drawing + filled rectangles and can be hardware accelerated on some platforms with + both software and hardware display modes. + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + .. versionchanged:: 2.0.0.dev8 Added support for border radius. + + .. ## pygame.draw.rect ## + +.. function:: polygon + + | :sl:`draw a polygon` + | :sg:`polygon(surface, color, points) -> Rect` + | :sg:`polygon(surface, color, points, width=0) -> Rect` + + Draws a polygon on the given surface. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param points: a sequence of 3 or more (x, y) coordinates that make up the + vertices of the polygon, each *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats, + e.g. ``[(x1, y1), (x2, y2), (x3, y3)]`` + :type points: tuple(coordinate) or list(coordinate) + :param int width: (optional) used for line thickness or to indicate that + the polygon is to be filled + + | if width == 0, (default) fill the polygon + | if width > 0, used for line thickness + | if width < 0, nothing will be drawn + | + + .. note:: + When using ``width`` values ``> 1``, the edge lines will grow + outside the original boundary of the polygon. For more details on + how the thickness for edge lines grow, refer to the ``width`` notes + of the :func:`pygame.draw.line` function. + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the position of the first point in the + ``points`` parameter (float values will be truncated) and its width and + height will be 0 + :rtype: Rect + + :raises ValueError: if ``len(points) < 3`` (must have at least 3 points) + :raises TypeError: if ``points`` is not a sequence or ``points`` does not + contain number pairs + + .. note:: + For an aapolygon, use :func:`aalines()` with ``closed=True``. + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + + .. ## pygame.draw.polygon ## + +.. function:: circle + + | :sl:`draw a circle` + | :sg:`circle(surface, color, center, radius) -> Rect` + | :sg:`circle(surface, color, center, radius, width=0, draw_top_right=None, draw_top_left=None, draw_bottom_left=None, draw_bottom_right=None) -> Rect` + + Draws a circle on the given surface. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param center: center point of the circle as a sequence of 2 ints/floats, + e.g. ``(x, y)`` + :type center: tuple(int or float, int or float) or + list(int or float, int or float) or Vector2(int or float, int or float) + :param radius: radius of the circle, measured from the ``center`` parameter, + nothing will be drawn if the ``radius`` is less than 1 + :type radius: int or float + :param int width: (optional) used for line thickness or to indicate that + the circle is to be filled + + | if ``width == 0``, (default) fill the circle + | if ``width > 0``, used for line thickness + | if ``width < 0``, nothing will be drawn + | + + .. note:: + When using ``width`` values ``> 1``, the edge lines will only grow + inward. + :param bool draw_top_right: (optional) if this is set to True then the top right corner + of the circle will be drawn + :param bool draw_top_left: (optional) if this is set to True then the top left corner + of the circle will be drawn + :param bool draw_bottom_left: (optional) if this is set to True then the bottom left corner + of the circle will be drawn + :param bool draw_bottom_right: (optional) if this is set to True then the bottom right corner + of the circle will be drawn + + | if any of the draw_circle_part is True then it will draw all circle parts that have the True + | value, otherwise it will draw the entire circle. + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the ``center`` parameter value (float + values will be truncated) and its width and height will be 0 + :rtype: Rect + + :raises TypeError: if ``center`` is not a sequence of two numbers + :raises TypeError: if ``radius`` is not a number + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + Nothing is drawn when the radius is 0 (a pixel at the ``center`` coordinates + used to be drawn when the radius equaled 0). + Floats, and Vector2 are accepted for the ``center`` param. + The drawing algorithm was improved to look more like a circle. + .. versionchanged:: 2.0.0.dev8 Added support for drawing circle quadrants. + + .. ## pygame.draw.circle ## + +.. function:: ellipse + + | :sl:`draw an ellipse` + | :sg:`ellipse(surface, color, rect) -> Rect` + | :sg:`ellipse(surface, color, rect, width=0) -> Rect` + + Draws an ellipse on the given surface. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param Rect rect: rectangle to indicate the position and dimensions of the + ellipse, the ellipse will be centered inside the rectangle and bounded + by it + :param int width: (optional) used for line thickness or to indicate that + the ellipse is to be filled (not to be confused with the width value + of the ``rect`` parameter) + + | if ``width == 0``, (default) fill the ellipse + | if ``width > 0``, used for line thickness + | if ``width < 0``, nothing will be drawn + | + + .. note:: + When using ``width`` values ``> 1``, the edge lines will only grow + inward from the original boundary of the ``rect`` parameter. + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the position of the given ``rect`` + parameter and its width and height will be 0 + :rtype: Rect + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + + .. ## pygame.draw.ellipse ## + +.. function:: arc + + | :sl:`draw an elliptical arc` + | :sg:`arc(surface, color, rect, start_angle, stop_angle) -> Rect` + | :sg:`arc(surface, color, rect, start_angle, stop_angle, width=1) -> Rect` + + Draws an elliptical arc on the given surface. + + The two angle arguments are given in radians and indicate the start and stop + positions of the arc. The arc is drawn in a counterclockwise direction from + the ``start_angle`` to the ``stop_angle``. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param Rect rect: rectangle to indicate the position and dimensions of the + ellipse which the arc will be based on, the ellipse will be centered + inside the rectangle + :param float start_angle: start angle of the arc in radians + :param float stop_angle: stop angle of the arc in + radians + + | if ``start_angle < stop_angle``, the arc is drawn in a + counterclockwise direction from the ``start_angle`` to the + ``stop_angle`` + | if ``start_angle > stop_angle``, tau (tau == 2 * pi) will be added + to the ``stop_angle``, if the resulting stop angle value is greater + than the ``start_angle`` the above ``start_angle < stop_angle`` case + applies, otherwise nothing will be drawn + | if ``start_angle == stop_angle``, nothing will be drawn + | + + :param int width: (optional) used for line thickness (not to be confused + with the width value of the ``rect`` parameter) + + | if ``width == 0``, nothing will be drawn + | if ``width > 0``, (default is 1) used for line thickness + | if ``width < 0``, same as ``width == 0`` + + .. note:: + When using ``width`` values ``> 1``, the edge lines will only grow + inward from the original boundary of the ``rect`` parameter. + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the position of the given ``rect`` + parameter and its width and height will be 0 + :rtype: Rect + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + + .. ## pygame.draw.arc ## + +.. function:: line + + | :sl:`draw a straight line` + | :sg:`line(surface, color, start_pos, end_pos) -> Rect` + | :sg:`line(surface, color, start_pos, end_pos, width=1) -> Rect` + + Draws a straight line on the given surface. There are no endcaps. For thick + lines the ends are squared off. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param start_pos: start position of the line, (x, y) + :type start_pos: tuple(int or float, int or float) or + list(int or float, int or float) or Vector2(int or float, int or float) + :param end_pos: end position of the line, (x, y) + :type end_pos: tuple(int or float, int or float) or + list(int or float, int or float) or Vector2(int or float, int or float) + :param int width: (optional) used for line thickness + + | if width >= 1, used for line thickness (default is 1) + | if width < 1, nothing will be drawn + | + + .. note:: + When using ``width`` values ``> 1``, lines will grow as follows. + + For odd ``width`` values, the thickness of each line grows with the + original line being in the center. + + For even ``width`` values, the thickness of each line grows with the + original line being offset from the center (as there is no exact + center line drawn). As a result, lines with a slope < 1 + (horizontal-ish) will have 1 more pixel of thickness below the + original line (in the y direction). Lines with a slope >= 1 + (vertical-ish) will have 1 more pixel of thickness to the right of + the original line (in the x direction). + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the ``start_pos`` parameter value (float + values will be truncated) and its width and height will be 0 + :rtype: Rect + + :raises TypeError: if ``start_pos`` or ``end_pos`` is not a sequence of + two numbers + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + + .. ## pygame.draw.line ## + +.. function:: lines + + | :sl:`draw multiple contiguous straight line segments` + | :sg:`lines(surface, color, closed, points) -> Rect` + | :sg:`lines(surface, color, closed, points, width=1) -> Rect` + + Draws a sequence of contiguous straight lines on the given surface. There are + no endcaps or miter joints. For thick lines the ends are squared off. + Drawing thick lines with sharp corners can have undesired looking results. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param bool closed: if ``True`` an additional line segment is drawn between + the first and last points in the ``points`` sequence + :param points: a sequence of 2 or more (x, y) coordinates, where each + *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats and adjacent + coordinates will be connected by a line segment, e.g. for the + points ``[(x1, y1), (x2, y2), (x3, y3)]`` a line segment will be drawn + from ``(x1, y1)`` to ``(x2, y2)`` and from ``(x2, y2)`` to ``(x3, y3)``, + additionally if the ``closed`` parameter is ``True`` another line segment + will be drawn from ``(x3, y3)`` to ``(x1, y1)`` + :type points: tuple(coordinate) or list(coordinate) + :param int width: (optional) used for line thickness + + | if width >= 1, used for line thickness (default is 1) + | if width < 1, nothing will be drawn + | + + .. note:: + When using ``width`` values ``> 1`` refer to the ``width`` notes + of :func:`line` for details on how thick lines grow. + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the position of the first point in the + ``points`` parameter (float values will be truncated) and its width and + height will be 0 + :rtype: Rect + + :raises ValueError: if ``len(points) < 2`` (must have at least 2 points) + :raises TypeError: if ``points`` is not a sequence or ``points`` does not + contain number pairs + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + + .. ## pygame.draw.lines ## + +.. function:: aaline + + | :sl:`draw a straight antialiased line` + | :sg:`aaline(surface, color, start_pos, end_pos) -> Rect` + | :sg:`aaline(surface, color, start_pos, end_pos, blend=1) -> Rect` + + Draws a straight antialiased line on the given surface. + + The line has a thickness of one pixel and the endpoints have a height and + width of one pixel each. + + The way a line and its endpoints are drawn: + If both endpoints are equal, only a single pixel is drawn (after + rounding floats to nearest integer). + + Otherwise if the line is not steep (i.e. if the length along the x-axis + is greater than the height along the y-axis): + + For each endpoint: + + If ``x``, the endpoint's x-coordinate, is a whole number find + which pixels would be covered by it and draw them. + + Otherwise: + + Calculate the position of the nearest point with a whole number + for its x-coordinate, when extending the line past the + endpoint. + + Find which pixels would be covered and how much by that point. + + If the endpoint is the left one, multiply the coverage by (1 - + the decimal part of ``x``). + + Otherwise multiply the coverage by the decimal part of ``x``. + + Then draw those pixels. + + *e.g.:* + | The left endpoint of the line ``((1, 1.3), (5, 3))`` would + cover 70% of the pixel ``(1, 1)`` and 30% of the pixel + ``(1, 2)`` while the right one would cover 100% of the + pixel ``(5, 3)``. + | The left endpoint of the line ``((1.2, 1.4), (4.6, 3.1))`` + would cover 56% *(i.e. 0.8 * 70%)* of the pixel ``(1, 1)`` + and 24% *(i.e. 0.8 * 30%)* of the pixel ``(1, 2)`` while + the right one would cover 42% *(i.e. 0.6 * 70%)* of the + pixel ``(5, 3)`` and 18% *(i.e. 0.6 * 30%)* of the pixel + ``(5, 4)`` while the right + + Then for each point between the endpoints, along the line, whose + x-coordinate is a whole number: + + Find which pixels would be covered and how much by that point and + draw them. + + *e.g.:* + | The points along the line ``((1, 1), (4, 2.5))`` would be + ``(2, 1.5)`` and ``(3, 2)`` and would cover 50% of the pixel + ``(2, 1)``, 50% of the pixel ``(2, 2)`` and 100% of the pixel + ``(3, 2)``. + | The points along the line ``((1.2, 1.4), (4.6, 3.1))`` would + be ``(2, 1.8)`` (covering 20% of the pixel ``(2, 1)`` and 80% + of the pixel ``(2, 2)``), ``(3, 2.3)`` (covering 70% of the + pixel ``(3, 2)`` and 30% of the pixel ``(3, 3)``) and ``(4, + 2.8)`` (covering 20% of the pixel ``(2, 1)`` and 80% of the + pixel ``(2, 2)``) + + Otherwise do the same for steep lines as for non-steep lines except + along the y-axis instead of the x-axis (using ``y`` instead of ``x``, + top instead of left and bottom instead of right). + + .. note:: + Regarding float values for coordinates, a point with coordinate + consisting of two whole numbers is considered being right in the center + of said pixel (and having a height and width of 1 pixel would therefore + completely cover it), while a point with coordinate where one (or both) + of the numbers have non-zero decimal parts would be partially covering + two (or four if both numbers have decimal parts) adjacent pixels, *e.g.* + the point ``(1.4, 2)`` covers 60% of the pixel ``(1, 2)`` and 40% of the + pixel ``(2,2)``. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param start_pos: start position of the line, (x, y) + :type start_pos: tuple(int or float, int or float) or + list(int or float, int or float) or Vector2(int or float, int or float) + :param end_pos: end position of the line, (x, y) + :type end_pos: tuple(int or float, int or float) or + list(int or float, int or float) or Vector2(int or float, int or float) + :param int blend: (optional) if non-zero (default) the line will be blended + with the surface's existing pixel shades, otherwise it will overwrite them + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the ``start_pos`` parameter value (float + values will be truncated) and its width and height will be 0 + :rtype: Rect + + :raises TypeError: if ``start_pos`` or ``end_pos`` is not a sequence of + two numbers + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + + .. ## pygame.draw.aaline ## + +.. function:: aalines + + | :sl:`draw multiple contiguous straight antialiased line segments` + | :sg:`aalines(surface, color, closed, points) -> Rect` + | :sg:`aalines(surface, color, closed, points, blend=1) -> Rect` + + Draws a sequence of contiguous straight antialiased lines on the given + surface. + + :param Surface surface: surface to draw on + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or int or tuple(int, int, int, [int]) + :param bool closed: if ``True`` an additional line segment is drawn between + the first and last points in the ``points`` sequence + :param points: a sequence of 2 or more (x, y) coordinates, where each + *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats and adjacent + coordinates will be connected by a line segment, e.g. for the + points ``[(x1, y1), (x2, y2), (x3, y3)]`` a line segment will be drawn + from ``(x1, y1)`` to ``(x2, y2)`` and from ``(x2, y2)`` to ``(x3, y3)``, + additionally if the ``closed`` parameter is ``True`` another line segment + will be drawn from ``(x3, y3)`` to ``(x1, y1)`` + :type points: tuple(coordinate) or list(coordinate) + :param int blend: (optional) if non-zero (default) each line will be blended + with the surface's existing pixel shades, otherwise the pixels will be + overwritten + + :returns: a rect bounding the changed pixels, if nothing is drawn the + bounding rect's position will be the position of the first point in the + ``points`` parameter (float values will be truncated) and its width and + height will be 0 + :rtype: Rect + + :raises ValueError: if ``len(points) < 2`` (must have at least 2 points) + :raises TypeError: if ``points`` is not a sequence or ``points`` does not + contain number pairs + + .. versionchanged:: 2.0.0 Added support for keyword arguments. + + .. ## pygame.draw.aalines ## + +.. ## pygame.draw ## + +.. figure:: code_examples/draw_module_example.png + :alt: draw module example + + Example code for draw module. + +.. literalinclude:: code_examples/draw_module_example.py + diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/event.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/event.rst.txt new file mode 100644 index 0000000..5b4a6d2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/event.rst.txt @@ -0,0 +1,476 @@ +.. include:: common.txt + +:mod:`pygame.event` +=================== + +.. module:: pygame.event + :synopsis: pygame module for interacting with events and queues + +| :sl:`pygame module for interacting with events and queues` + +Pygame handles all its event messaging through an event queue. The routines in +this module help you manage that event queue. The input queue is heavily +dependent on the :mod:`pygame.display` module. If the display has not been +initialized and a video mode not set, the event queue may not work properly. + +The event queue has an upper limit on the number of events it can hold. When +the queue becomes full new events are quietly dropped. To prevent lost events, +especially input events which signal a quit command, your program must handle +events every frame (with ``pygame.event.get()``, ``pygame.event.pump()``, +``pygame.event.wait()``, ``pygame.event.peek()`` or ``pygame.event.clear()``) +and process them. Not handling events may cause your system to decide your +program has locked up. To speed up queue processing use +:func:`pygame.event.set_blocked()` to limit which events get queued. + +To get the state of various input devices, you can forego the event queue and +access the input devices directly with their appropriate modules: +:mod:`pygame.mouse`, :mod:`pygame.key`, and :mod:`pygame.joystick`. If you use +this method, remember that pygame requires some form of communication with the +system window manager and other parts of the platform. To keep pygame in sync +with the system, you will need to call :func:`pygame.event.pump()` to keep +everything current. Usually, this should be called once per game loop. +Note: Joysticks will not send any events until the device has been initialized. + +The event queue contains :class:`pygame.event.EventType` event objects. +There are a variety of ways to access the queued events, from simply +checking for the existence of events, to grabbing them directly off the stack. +The event queue also offers some simple filtering which can slightly help +performance by blocking certain event types from the queue. Use +:func:`pygame.event.set_allowed()` and :func:`pygame.event.set_blocked()` to +change this filtering. By default, all event types can be placed on the queue. + +All :class:`pygame.event.EventType` instances contain an event type identifier +and attributes specific to that event type. The event type identifier is +accessible as the :attr:`pygame.event.EventType.type` property. Any of the +event specific attributes can be accessed through the +:attr:`pygame.event.EventType.__dict__` attribute or directly as an attribute +of the event object (as member lookups are passed through to the object's +dictionary values). The event object has no method functions. Users can create +their own new events with the :func:`pygame.event.Event()` function. + +The event type identifier is in between the values of ``NOEVENT`` and +``NUMEVENTS``. User defined events should have a value in the inclusive range +of ``USEREVENT`` to ``NUMEVENTS - 1``. User defined events can get a custom +event number with :func:`pygame.event.custom_type()`. +It is recommended all user events follow this system. + +Events support equality and inequality comparisons. Two events are equal if +they are the same type and have identical attribute values. + +While debugging and experimenting, you can print an event object for a quick +display of its type and members. The function :func:`pygame.event.event_name()` +can be used to get a string representing the name of the event type. + +Events that come from the system will have a guaranteed set of member +attributes based on the type. The following is a list event types with their +specific attributes. + +:: + + QUIT none + ACTIVEEVENT gain, state + KEYDOWN key, mod, unicode, scancode + KEYUP key, mod, unicode, scancode + MOUSEMOTION pos, rel, buttons, touch + MOUSEBUTTONUP pos, button, touch + MOUSEBUTTONDOWN pos, button, touch + JOYAXISMOTION joy (deprecated), instance_id, axis, value + JOYBALLMOTION joy (deprecated), instance_id, ball, rel + JOYHATMOTION joy (deprecated), instance_id, hat, value + JOYBUTTONUP joy (deprecated), instance_id, button + JOYBUTTONDOWN joy (deprecated), instance_id, button + VIDEORESIZE size, w, h + VIDEOEXPOSE none + USEREVENT code + +.. versionchanged:: 2.0.0 The ``joy`` attribute was deprecated, ``instance_id`` was added. + +.. versionchanged:: 2.0.1 The ``unicode`` attribute was added to ``KEYUP`` event. + +You can also find a list of constants for keyboard keys +:ref:`here `. + +| + +On MacOSX when a file is opened using a pygame application, a ``USEREVENT`` +with its ``code`` attribute set to ``pygame.USEREVENT_DROPFILE`` is generated. +There is an additional attribute called ``filename`` where the name of the file +being accessed is stored. + +:: + + USEREVENT code=pygame.USEREVENT_DROPFILE, filename + +.. versionadded:: 1.9.2 + +| + +When compiled with SDL2, pygame has these additional events and their +attributes. + +:: + + AUDIODEVICEADDED which, iscapture + AUDIODEVICEREMOVED which, iscapture + FINGERMOTION touch_id, finger_id, x, y, dx, dy + FINGERDOWN touch_id, finger_id, x, y, dx, dy + FINGERUP touch_id, finger_id, x, y, dx, dy + MOUSEWHEEL which, flipped, x, y, touch + MULTIGESTURE touch_id, x, y, pinched, rotated, num_fingers + TEXTEDITING text, start, length + TEXTINPUT text + +.. versionadded:: 1.9.5 + +.. versionchanged:: 2.0.2 Fixed amount horizontal scroll (x, positive to the right and negative to the left). + +.. versionchanged:: 2.0.2 The ``touch`` attribute was added to all the ``MOUSE`` events. + +The ``touch`` attribute of ``MOUSE`` events indicates whether or not the events were generated +by a touch input device, and not a real mouse. You might want to ignore such events, if your application +already handles ``FINGERMOTION``, ``FINGERDOWN`` and ``FINGERUP`` events. + +| + +Many new events were introduced in pygame 2. + +pygame can recognize text or files dropped in its window. If a file +is dropped, ``DROPFILE`` event will be sent, ``file`` will be its path. +The ``DROPTEXT`` event is only supported on X11. + +``MIDIIN`` and ``MIDIOUT`` are events reserved for :mod:`pygame.midi` use. + +pygame 2 also supports controller hot-plugging + +:: + + DROPBEGIN + DROPCOMPLETE + DROPFILE file + DROPTEXT text + MIDIIN + MIDIOUT + CONTROLLERDEVICEADDED device_index + JOYDEVICEADDED device_index + CONTROLLERDEVICEREMOVED instance_id + JOYDEVICEREMOVED instance_id + CONTROLLERDEVICEREMAPPED instance_id + +Also in this version, ``instance_id`` attributes were added to joystick events, +and the ``joy`` attribute was deprecated. + +.. versionadded:: 2.0.0 + +Since pygame 2.0.1, there are a new set of events, called window events. +Here is a list of all window events, along with a short description + +:: + + Event type Short description + + WINDOWSHOWN Window became shown + WINDOWHIDDEN Window became hidden + WINDOWEXPOSED Window got updated by some external event + WINDOWMOVED Window got moved + WINDOWRESIZED Window got resized + WINDOWSIZECHANGED Window changed its size + WINDOWMINIMIZED Window was minimized + WINDOWMAXIMIZED Window was maximized + WINDOWRESTORED Window was restored + WINDOWENTER Mouse entered the window + WINDOWLEAVE Mouse left the window + WINDOWFOCUSGAINED Window gained focus + WINDOWFOCUSLOST Window lost focus + WINDOWCLOSE Window was closed + WINDOWTAKEFOCUS Window was offered focus + WINDOWHITTEST Window has a special hit test + + +If SDL version used is less than 2.0.5, the last two events ``WINDOWTAKEFOCUS`` +and ``WINDOWHITTEST`` will not work. + +Most these window events do not have any attributes, except ``WINDOWMOVED``, +``WINDOWRESIZED`` and ``WINDOWSIZECHANGED``, these have ``x`` and ``y`` attributes + +| + +.. function:: pump + + | :sl:`internally process pygame event handlers` + | :sg:`pump() -> None` + + For each frame of your game, you will need to make some sort of call to the + event queue. This ensures your program can internally interact with the rest + of the operating system. If you are not using other event functions in your + game, you should call ``pygame.event.pump()`` to allow pygame to handle + internal actions. + + This function is not necessary if your program is consistently processing + events on the queue through the other :mod:`pygame.event` functions. + + There are important things that must be dealt with internally in the event + queue. The main window may need to be repainted or respond to the system. If + you fail to make a call to the event queue for too long, the system may + decide your program has locked up. + + .. caution:: + This function should only be called in the thread that initialized :mod:`pygame.display`. + + .. ## pygame.event.pump ## + +.. function:: get + + | :sl:`get events from the queue` + | :sg:`get(eventtype=None) -> Eventlist` + | :sg:`get(eventtype=None, pump=True) -> Eventlist` + | :sg:`get(eventtype=None, pump=True, exclude=None) -> Eventlist` + + This will get all the messages and remove them from the queue. If a type or + sequence of types is given only those messages will be removed from the + queue and returned. + + If a type or sequence of types is passed in the ``exclude`` argument + instead, then all only *other* messages will be removed from the queue. If + an ``exclude`` parameter is passed, the ``eventtype`` parameter *must* be + None. + + If you are only taking specific events from the queue, be aware that the + queue could eventually fill up with the events you are not interested. + + If ``pump`` is ``True`` (the default), then :func:`pygame.event.pump()` will be called. + + .. versionchanged:: 1.9.5 Added ``pump`` argument + .. versionchanged:: 2.0.2 Added ``exclude`` argument + + .. ## pygame.event.get ## + +.. function:: poll + + | :sl:`get a single event from the queue` + | :sg:`poll() -> EventType instance` + + Returns a single event from the queue. If the event queue is empty an event + of type ``pygame.NOEVENT`` will be returned immediately. The returned event + is removed from the queue. + + .. caution:: + This function should only be called in the thread that initialized :mod:`pygame.display`. + + .. ## pygame.event.poll ## + +.. function:: wait + + | :sl:`wait for a single event from the queue` + | :sg:`wait() -> EventType instance` + | :sg:`wait(timeout) -> EventType instance` + + Returns a single event from the queue. If the queue is empty this function + will wait until one is created. From pygame 2.0.0, if a ``timeout`` argument + is given, the function will return an event of type ``pygame.NOEVENT`` + if no events enter the queue in ``timeout`` milliseconds. The event is removed + from the queue once it has been returned. While the program is waiting it will + sleep in an idle state. This is important for programs that want to share the + system with other applications. + + .. versionchanged:: 2.0.0.dev13 Added ``timeout`` argument + + .. caution:: + This function should only be called in the thread that initialized :mod:`pygame.display`. + + .. ## pygame.event.wait ## + +.. function:: peek + + | :sl:`test if event types are waiting on the queue` + | :sg:`peek(eventtype=None) -> bool` + | :sg:`peek(eventtype=None, pump=True) -> bool` + + Returns ``True`` if there are any events of the given type waiting on the + queue. If a sequence of event types is passed, this will return ``True`` if + any of those events are on the queue. + + If ``pump`` is ``True`` (the default), then :func:`pygame.event.pump()` will be called. + + .. versionchanged:: 1.9.5 Added ``pump`` argument + + .. ## pygame.event.peek ## + +.. function:: clear + + | :sl:`remove all events from the queue` + | :sg:`clear(eventtype=None) -> None` + | :sg:`clear(eventtype=None, pump=True) -> None` + + Removes all events from the queue. If ``eventtype`` is given, removes the given event + or sequence of events. This has the same effect as :func:`pygame.event.get()` except ``None`` + is returned. It can be slightly more efficient when clearing a full event queue. + + If ``pump`` is ``True`` (the default), then :func:`pygame.event.pump()` will be called. + + .. versionchanged:: 1.9.5 Added ``pump`` argument + + .. ## pygame.event.clear ## + +.. function:: event_name + + | :sl:`get the string name from an event id` + | :sg:`event_name(type) -> string` + + Returns a string representing the name (in CapWords style) of the given + event type. + + "UserEvent" is returned for all values in the user event id range. + "Unknown" is returned when the event type does not exist. + + .. ## pygame.event.event_name ## + +.. function:: set_blocked + + | :sl:`control which events are allowed on the queue` + | :sg:`set_blocked(type) -> None` + | :sg:`set_blocked(typelist) -> None` + | :sg:`set_blocked(None) -> None` + + The given event types are not allowed to appear on the event queue. By + default all events can be placed on the queue. It is safe to disable an + event type multiple times. + + If ``None`` is passed as the argument, ALL of the event types are blocked + from being placed on the queue. + + .. ## pygame.event.set_blocked ## + +.. function:: set_allowed + + | :sl:`control which events are allowed on the queue` + | :sg:`set_allowed(type) -> None` + | :sg:`set_allowed(typelist) -> None` + | :sg:`set_allowed(None) -> None` + + The given event types are allowed to appear on the event queue. By default, + all event types can be placed on the queue. It is safe to enable an event + type multiple times. + + If ``None`` is passed as the argument, ALL of the event types are allowed + to be placed on the queue. + + .. ## pygame.event.set_allowed ## + +.. function:: get_blocked + + | :sl:`test if a type of event is blocked from the queue` + | :sg:`get_blocked(type) -> bool` + | :sg:`get_blocked(typelist) -> bool` + + Returns ``True`` if the given event type is blocked from the queue. If a + sequence of event types is passed, this will return ``True`` if any of those + event types are blocked. + + .. ## pygame.event.get_blocked ## + +.. function:: set_grab + + | :sl:`control the sharing of input devices with other applications` + | :sg:`set_grab(bool) -> None` + + When your program runs in a windowed environment, it will share the mouse + and keyboard devices with other applications that have focus. If your + program sets the event grab to ``True``, it will lock all input into your + program. + + It is best to not always grab the input, since it prevents the user from + doing other things on their system. + + .. ## pygame.event.set_grab ## + +.. function:: get_grab + + | :sl:`test if the program is sharing input devices` + | :sg:`get_grab() -> bool` + + Returns ``True`` when the input events are grabbed for this application. + + .. ## pygame.event.get_grab ## + +.. function:: post + + | :sl:`place a new event on the queue` + | :sg:`post(Event) -> bool` + + Places the given event at the end of the event queue. + + This is usually used for placing custom events on the event queue. + Any type of event can be posted, and the events posted can have any attributes. + + This returns a boolean on whether the event was posted or not. Blocked events + cannot be posted, and this function returns ``False`` if you try to post them. + + .. versionchanged:: 2.0.1 returns a boolean, previously returned ``None`` + + .. ## pygame.event.post ## + +.. function:: custom_type + + | :sl:`make custom user event type` + | :sg:`custom_type() -> int` + + Reserves a ``pygame.USEREVENT`` for a custom use. + + If too many events are made a :exc:`pygame.error` is raised. + + .. versionadded:: 2.0.0.dev3 + + .. ## pygame.event.custom_type ## + +.. function:: Event + + | :sl:`create a new event object` + | :sg:`Event(type, dict) -> EventType instance` + | :sg:`Event(type, \**attributes) -> EventType instance` + + Creates a new event with the given type and attributes. The attributes can + come from a dictionary argument with string keys or from keyword arguments. + + .. ## pygame.event.Event ## + +.. class:: EventType + + | :sl:`pygame object for representing events` + + A pygame object that represents an event. User event instances are created + with an :func:`pygame.event.Event()` function call. The ``EventType`` type + is not directly callable. ``EventType`` instances support attribute + assignment and deletion. + + .. attribute:: type + + | :sl:`event type identifier.` + | :sg:`type -> int` + + Read-only. The event type identifier. For user created event + objects, this is the ``type`` argument passed to + :func:`pygame.event.Event()`. + + For example, some predefined event identifiers are ``QUIT`` and + ``MOUSEMOTION``. + + .. ## pygame.event.EventType.type ## + + .. attribute:: __dict__ + + | :sl:`event attribute dictionary` + | :sg:`__dict__ -> dict` + + Read-only. The event type specific attributes of an event. The + ``dict`` attribute is a synonym for backward compatibility. + + For example, the attributes of a ``KEYDOWN`` event would be ``unicode``, + ``key``, and ``mod`` + + .. ## pygame.event.EventType.__dict__ ## + + .. versionadded:: 1.9.2 Mutable attributes. + + .. ## pygame.event.EventType ## + +.. ## pygame.event ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/examples.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/examples.rst.txt new file mode 100644 index 0000000..000ea76 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/examples.rst.txt @@ -0,0 +1,451 @@ +.. include:: common.txt + +:mod:`pygame.examples` +====================== + +.. module:: pygame.examples + :synopsis: module of example programs + +| :sl:`module of example programs` + +These examples should help get you started with pygame. Here is a brief rundown +of what you get. The source code for these examples is in the public domain. +Feel free to use for your own projects. + +There are several ways to run the examples. First they can be run as +stand-alone programs. Second they can be imported and their ``main()`` methods +called (see below). Finally, the easiest way is to use the python -m option: + +:: + + python -m pygame.examples. + +eg: + +:: + + python -m pygame.examples.scaletest someimage.png + +Resources such as images and sounds for the examples are found in the +pygame/examples/data subdirectory. + +You can find where the example files are installed by using the following +commands inside the python interpreter. + +:: + + >>> import pygame.examples.scaletest + >>> pygame.examples.scaletest.__file__ + '/usr/lib/python2.6/site-packages/pygame/examples/scaletest.py' + +On each OS and version of Python the location will be slightly different. +For example on Windows it might be in 'C:/Python26/Lib/site-packages/pygame/examples/' +On Mac OS X it might be in '/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/pygame/examples/' + + +You can also run the examples in the python interpreter by calling each modules main() function. + +:: + + >>> import pygame.examples.scaletest + >>> pygame.examples.scaletest.main() + + +We're always on the lookout for more examples and/or example requests. Code +like this is probably the best way to start getting involved with python +gaming. + +examples as a package is new to pygame 1.9.0. But most of the examples came with +pygame much earlier. + +.. function:: aliens.main + + | :sl:`play the full aliens example` + | :sg:`aliens.main() -> None` + + This started off as a port of the ``SDL`` demonstration, Aliens. Now it has + evolved into something sort of resembling fun. This demonstrates a lot of + different uses of sprites and optimized blitting. Also transparency, + colorkeys, fonts, sound, music, joystick, and more. (PS, my high score is + 117! goodluck) + + .. ## pygame.examples.aliens.main ## + +.. function:: stars.main + + | :sl:`run a simple starfield example` + | :sg:`stars.main() -> None` + + A simple starfield example. You can change the center of perspective by + leftclicking the mouse on the screen. + + .. ## pygame.examples.stars.main ## + +.. function:: chimp.main + + | :sl:`hit the moving chimp` + | :sg:`chimp.main() -> None` + + This simple example is derived from the line-by-line tutorial that comes + with pygame. It is based on a 'popular' web banner. Note there are comments + here, but for the full explanation, follow along in the tutorial. + + .. ## pygame.examples.chimp.main ## + +.. function:: moveit.main + + | :sl:`display animated objects on the screen` + | :sg:`moveit.main() -> None` + + This is the full and final example from the Pygame Tutorial, "How Do I Make + It Move". It creates 10 objects and animates them on the screen. + + Note it's a bit scant on error checking, but it's easy to read. :] + Fortunately, this is python, and we needn't wrestle with a pile of error + codes. + + .. ## pygame.examples.moveit.main ## + +.. function:: fonty.main + + | :sl:`run a font rendering example` + | :sg:`fonty.main() -> None` + + Super quick, super simple application demonstrating the different ways to + render fonts with the font module + + .. ## pygame.examples.fonty.main ## + +.. function:: freetype_misc.main + + | :sl:`run a FreeType rendering example` + | :sg:`freetype_misc.main() -> None` + + A showcase of rendering features the :class:`pygame.freetype.Font` + class provides in addition to those available with :class:`pygame.font.Font`. + It is a demonstration of direct to surface rendering, with vertical text + and rotated text, opaque text and semi transparent text, horizontally + stretched text and vertically stretched text. + + .. ## pygame.examples.fonty.main ## + +.. function:: vgrade.main + + | :sl:`display a vertical gradient` + | :sg:`vgrade.main() -> None` + + Demonstrates creating a vertical gradient with pixelcopy and NumPy python. + The app will create a new gradient every half second and report the time + needed to create and display the image. If you're not prepared to start + working with the NumPy arrays, don't worry about the source for this one :] + + .. ## pygame.examples.vgrade.main ## + +.. function:: eventlist.main + + | :sl:`display pygame events` + | :sg:`eventlist.main() -> None` + + Eventlist is a sloppy style of pygame, but is a handy tool for learning + about pygame events and input. At the top of the screen are the state of + several device values, and a scrolling list of events are displayed on the + bottom. + + This is not quality 'ui' code at all, but you can see how to implement very + non-interactive status displays, or even a crude text output control. + + .. ## pygame.examples.eventlist.main ## + +.. function:: arraydemo.main + + | :sl:`show various surfarray effects` + | :sg:`arraydemo.main(arraytype=None) -> None` + + Another example filled with various surfarray effects. It requires the + surfarray and image modules to be installed. This little demo can also make + a good starting point for any of your own tests with surfarray + + The ``arraytype`` parameter is deprecated; passing any value besides 'numpy' + will raise ValueError. + + .. ## pygame.examples.arraydemo.main ## + +.. function:: sound.main + + | :sl:`load and play a sound` + | :sg:`sound.main(file_path=None) -> None` + + Extremely basic testing of the mixer module. Load a sound and play it. All + from the command shell, no graphics. + + If provided, use the audio file 'file_path', otherwise use a default file. + + ``sound.py`` optional command line argument: an audio file + + .. ## pygame.examples.sound.main ## + +.. function:: sound_array_demos.main + + | :sl:`play various sndarray effects` + | :sg:`sound_array_demos.main(arraytype=None) -> None` + + + Uses sndarray and NumPy to create offset faded copies of the + original sound. Currently it just uses hardcoded values for the number of + echoes and the delay. Easy for you to recreate as needed. + + The ``arraytype`` parameter is deprecated; passing any value besides 'numpy' + will raise ValueError. + + .. ## pygame.examples.sound_array_demos.main ## + +.. function:: liquid.main + + | :sl:`display an animated liquid effect` + | :sg:`liquid.main() -> None` + + This example was created in a quick comparison with the BlitzBasic gaming + language. Nonetheless, it demonstrates a quick 8-bit setup (with colormap). + + .. ## pygame.examples.liquid.main ## + +.. function:: glcube.main + + | :sl:`display an animated 3D cube using OpenGL` + | :sg:`glcube.main() -> None` + + Using PyOpenGL and pygame, this creates a spinning 3D multicolored cube. + + .. ## pygame.examples.glcube.main ## + +.. function:: scrap_clipboard.main + + | :sl:`access the clipboard` + | :sg:`scrap_clipboard.main() -> None` + + A simple demonstration example for the clipboard support. + + .. ## pygame.examples.scrap_clipboard.main ## + +.. function:: mask.main + + | :sl:`display multiple images bounce off each other using collision detection` + | :sg:`mask.main(*args) -> None` + + Positional arguments: + + :: + + one or more image file names. + + This ``pygame.masks`` demo will display multiple moving sprites bouncing off + each other. More than one sprite image can be provided. + + If run as a program then ``mask.py`` takes one or more image files as + command line arguments. + + .. ## pygame.examples.mask.main ## + +.. function:: testsprite.main + + | :sl:`show lots of sprites moving around` + | :sg:`testsprite.main(update_rects = True, use_static = False, use_FastRenderGroup = False, screen_dims = [640, 480], use_alpha = False, flags = 0) -> None` + + Optional keyword arguments: + + :: + + update_rects - use the RenderUpdate sprite group class + use_static - include non-moving images + use_FastRenderGroup - Use the FastRenderGroup sprite group + screen_dims - pygame window dimensions + use_alpha - use alpha blending + flags - additional display mode flags + + Like the ``testsprite.c`` that comes with SDL, this pygame version shows + lots of sprites moving around. + + If run as a stand-alone program then no command line arguments are taken. + + .. ## pygame.examples.testsprite.main ## + +.. function:: headless_no_windows_needed.main + + | :sl:`write an image file that is smoothscaled copy of an input file` + | :sg:`headless_no_windows_needed.main(fin, fout, w, h) -> None` + + arguments: + + :: + + fin - name of an input image file + fout - name of the output file to create/overwrite + w, h - size of the rescaled image, as integer width and height + + How to use pygame with no windowing system, like on headless servers. + + Thumbnail generation with scaling is an example of what you can do with + pygame. + + ``NOTE``: the pygame scale function uses MMX/SSE if available, and can be + run in multiple threads. + + If ``headless_no_windows_needed.py`` is run as a program it takes the + following command line arguments: + + :: + + -scale inputimage outputimage new_width new_height + eg. -scale in.png outpng 50 50 + + .. ## pygame.examples.headless_no_windows_needed.main ## + +.. function:: joystick.main + + | :sl:`demonstrate joystick functionality` + | :sg:`joystick.main() -> None` + + A demo showing full joystick support. + + .. versionadded:: 2.0.2 + + .. ## pygame.examples.joystick.main ## + +.. function:: blend_fill.main + + | :sl:`demonstrate the various surface.fill method blend options` + | :sg:`blend_fill.main() -> None` + + A interactive demo that lets one choose which BLEND_xxx option to apply to a + surface. + + .. ## pygame.examples.blend_fill.main ## + +.. function:: blit_blends.main + + | :sl:`uses alternative additive fill to that of surface.fill` + | :sg:`blit_blends.main() -> None` + + Fake additive blending. Using NumPy. it doesn't clamp. Press r,g,b Somewhat + like blend_fill. + + .. ## pygame.examples.blit_blends.main ## + +.. function:: cursors.main + + | :sl:`display two different custom cursors` + | :sg:`cursors.main() -> None` + + Display an arrow or circle with crossbar cursor. + + .. ## pygame.examples.cursors.main ## + +.. function:: pixelarray.main + + | :sl:`display various pixelarray generated effects` + | :sg:`pixelarray.main() -> None` + + Display various pixelarray generated effects. + + .. ## pygame.examples.pixelarray.main ## + +.. function:: scaletest.main + + | :sl:`interactively scale an image using smoothscale` + | :sg:`scaletest.main(imagefile, convert_alpha=False, run_speed_test=True) -> None` + + arguments: + + :: + + imagefile - file name of source image (required) + convert_alpha - use convert_alpha() on the surf (default False) + run_speed_test - (default False) + + A smoothscale example that resized an image on the screen. Vertical and + horizontal arrow keys are used to change the width and height of the + displayed image. If the convert_alpha option is True then the source image + is forced to have source alpha, whether or not the original images does. If + run_speed_test is True then a background timing test is performed instead of + the interactive scaler. + + If ``scaletest.py`` is run as a program then the command line options are: + + :: + + ImageFile [-t] [-convert_alpha] + [-t] = Run Speed Test + [-convert_alpha] = Use convert_alpha() on the surf. + + .. ## pygame.examples.scaletest.main ## + +.. function:: midi.main + + | :sl:`run a midi example` + | :sg:`midi.main(mode='output', device_id=None) -> None` + + Arguments: + + :: + + mode - if 'output' run a midi keyboard output example + 'input' run a midi event logger input example + 'list' list available midi devices + (default 'output') + device_id - midi device number; if None then use the default midi input or + output device for the system + + The output example shows how to translate mouse clicks or computer keyboard + events into midi notes. It implements a rudimentary button widget and state + machine. + + The input example shows how to translate midi input to pygame events. + + With the use of a virtual midi patch cord the output and input examples can + be run as separate processes and connected so the keyboard output is + displayed on a console. + + new to pygame 1.9.0 + + .. ## pygame.examples.midi.main ## + +.. function:: scroll.main + + | :sl:`run a Surface.scroll example that shows a magnified image` + | :sg:`scroll.main(image_file=None) -> None` + + This example shows a scrollable image that has a zoom factor of eight. It + uses the :meth:`Surface.scroll() ` + function to shift the image on the display surface. + A clip rectangle protects a margin area. If called as a function, + the example accepts an optional image file path. If run as a program it + takes an optional file path command line argument. If no file is provided a + default image file is used. + + When running click on a black triangle to move one pixel in the direction + the triangle points. Or use the arrow keys. Close the window or press + ``ESC`` to quit. + + .. ## pygame.examples.scroll.main ## + +.. function:: camera.main + + | :sl:`display video captured live from an attached camera` + | :sg:`camera.main() -> None` + + A simple live video player, it uses the first available camera it finds on + the system. + + .. ## pygame.examples.camera.main ## + +.. function:: playmus.main + + | :sl:`play an audio file` + | :sg:`playmus.main(file_path) -> None` + + A simple music player with window and keyboard playback control. Playback can + be paused and rewound to the beginning. + + .. ## pygame.examples.playmus.main ## + +.. ## pygame.examples ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/fastevent.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/fastevent.rst.txt new file mode 100644 index 0000000..a2efe5f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/fastevent.rst.txt @@ -0,0 +1,109 @@ +.. include:: common.txt + +:mod:`pygame.fastevent` +======================= + +.. module:: pygame.fastevent + :synopsis: pygame module for interacting with events and queues from multiple + threads. + +| :sl:`pygame module for interacting with events and queues` + +IMPORTANT NOTE: THIS MODULE IS DEPRECATED IN PYGAME 2.2 + +In older pygame versions before pygame 2, :mod:`pygame.event` was not well +suited for posting events from different threads. This module served as a +replacement (with less features) for multithreaded use. Now, the usage of this +module is highly discouraged in favour of use of the main :mod:`pygame.event` +module. This module will be removed in a future pygame version. + +Below, the legacy docs of the module is provided + +.. function:: init + + | :sl:`initialize pygame.fastevent` + | :sg:`init() -> None` + + Initialize the pygame.fastevent module. + + .. ## pygame.fastevent.init ## + +.. function:: get_init + + | :sl:`returns True if the fastevent module is currently initialized` + | :sg:`get_init() -> bool` + + Returns True if the pygame.fastevent module is currently initialized. + + .. ## pygame.fastevent.get_init ## + +.. function:: pump + + | :sl:`internally process pygame event handlers` + | :sg:`pump() -> None` + + For each frame of your game, you will need to make some sort of call to the + event queue. This ensures your program can internally interact with the rest + of the operating system. + + This function is not necessary if your program is consistently processing + events on the queue through the other :mod:`pygame.fastevent` functions. + + There are important things that must be dealt with internally in the event + queue. The main window may need to be repainted or respond to the system. If + you fail to make a call to the event queue for too long, the system may + decide your program has locked up. + + .. ## pygame.fastevent.pump ## + +.. function:: wait + + | :sl:`wait for an event` + | :sg:`wait() -> Event` + + Returns the current event on the queue. If there are no messages + waiting on the queue, this will not return until one is available. + Sometimes it is important to use this wait to get events from the queue, + it will allow your application to idle when the user isn't doing anything + with it. + + .. ## pygame.fastevent.wait ## + +.. function:: poll + + | :sl:`get an available event` + | :sg:`poll() -> Event` + + Returns next event on queue. If there is no event waiting on the queue, + this will return an event with type NOEVENT. + + .. ## pygame.fastevent.poll ## + +.. function:: get + + | :sl:`get all events from the queue` + | :sg:`get() -> list of Events` + + This will get all the messages and remove them from the queue. + + .. ## pygame.fastevent.get ## + +.. function:: post + + | :sl:`place an event on the queue` + | :sg:`post(Event) -> None` + + This will post your own event objects onto the event queue. You can post + any event type you want, but some care must be taken. For example, if you + post a MOUSEBUTTONDOWN event to the queue, it is likely any code receiving + the event will expect the standard MOUSEBUTTONDOWN attributes to be + available, like 'pos' and 'button'. + + Because pygame.fastevent.post() may have to wait for the queue to empty, + you can get into a dead lock if you try to append an event on to a full + queue from the thread that processes events. For that reason I do not + recommend using this function in the main thread of an SDL program. + + .. ## pygame.fastevent.post ## + +.. ## pygame.fastevent ## \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/font.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/font.rst.txt new file mode 100644 index 0000000..4d6cd05 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/font.rst.txt @@ -0,0 +1,405 @@ +.. include:: common.txt + +:mod:`pygame.font` +================== + +.. module:: pygame.font + :synopsis: pygame module for loading and rendering fonts + +| :sl:`pygame module for loading and rendering fonts` + +The font module allows for rendering TrueType fonts into a new Surface object. +It accepts any UCS-2 character ('\u0001' to '\uFFFF'). This module is optional +and requires SDL_ttf as a dependency. You should test that :mod:`pygame.font` +is available and initialized before attempting to use the module. + +Most of the work done with fonts are done by using the actual Font objects. The +module by itself only has routines to initialize the module and create Font +objects with ``pygame.font.Font()``. + +You can load fonts from the system by using the ``pygame.font.SysFont()`` +function. There are a few other functions to help lookup the system fonts. + +Pygame comes with a builtin default font. This can always be accessed by +passing None as the font name. + +To use the :mod:`pygame.freetype` based ``pygame.ftfont`` as +:mod:`pygame.font` define the environment variable PYGAME_FREETYPE before the +first import of :mod:`pygame`. Module ``pygame.ftfont`` is a :mod:`pygame.font` +compatible module that passes all but one of the font module unit tests: +it does not have the UCS-2 limitation of the SDL_ttf based font module, so +fails to raise an exception for a code point greater than '\uFFFF'. If +:mod:`pygame.freetype` is unavailable then the SDL_ttf font module will be +loaded instead. + +.. function:: init + + | :sl:`initialize the font module` + | :sg:`init() -> None` + + This method is called automatically by ``pygame.init()``. It initializes the + font module. The module must be initialized before any other functions will + work. + + It is safe to call this function more than once. + + .. ## pygame.font.init ## + +.. function:: quit + + | :sl:`uninitialize the font module` + | :sg:`quit() -> None` + + Manually uninitialize SDL_ttf's font system. This is called automatically by + ``pygame.quit()``. + + It is safe to call this function even if font is currently not initialized. + + .. ## pygame.font.quit ## + +.. function:: get_init + + | :sl:`true if the font module is initialized` + | :sg:`get_init() -> bool` + + Test if the font module is initialized or not. + + .. ## pygame.font.get_init ## + +.. function:: get_default_font + + | :sl:`get the filename of the default font` + | :sg:`get_default_font() -> string` + + Return the filename of the system font. This is not the full path to the + file. This file can usually be found in the same directory as the font + module, but it can also be bundled in separate archives. + + .. ## pygame.font.get_default_font ## + +.. function:: get_fonts + + | :sl:`get all available fonts` + | :sg:`get_fonts() -> list of strings` + + Returns a list of all the fonts available on the system. The names of the + fonts will be set to lowercase with all spaces and punctuation removed. This + works on most systems, but some will return an empty list if they cannot + find fonts. + + .. ## pygame.font.get_fonts ## + +.. function:: match_font + + | :sl:`find a specific font on the system` + | :sg:`match_font(name, bold=False, italic=False) -> path` + + Returns the full path to a font file on the system. If bold or italic are + set to true, this will attempt to find the correct family of font. + + The font name can also be an iterable of font names, a string of + comma-separated font names, or a bytes of comma-separated font names, in + which case the set of names will be searched in order. + If none of the given names are found, None is returned. + + .. versionadded:: 2.0.1 Accept an iterable of font names. + + Example: + + :: + + print pygame.font.match_font('bitstreamverasans') + # output is: /usr/share/fonts/truetype/ttf-bitstream-vera/Vera.ttf + # (but only if you have Vera on your system) + + .. ## pygame.font.match_font ## + +.. function:: SysFont + + | :sl:`create a Font object from the system fonts` + | :sg:`SysFont(name, size, bold=False, italic=False) -> Font` + + Return a new Font object that is loaded from the system fonts. The font will + match the requested bold and italic flags. Pygame uses a small set of common + font aliases. If the specific font you ask for is not available, a reasonable + alternative may be used. If a suitable system font is not found this will + fall back on loading the default pygame font. + + The font name can also be an iterable of font names, a string of + comma-separated font names, or a bytes of comma-separated font names, in + which case the set of names will be searched in order. + + .. versionadded:: 2.0.1 Accept an iterable of font names. + + .. ## pygame.font.SysFont ## + +.. class:: Font + + | :sl:`create a new Font object from a file` + | :sg:`Font(filename, size) -> Font` + | :sg:`Font(pathlib.Path, size) -> Font` + | :sg:`Font(object, size) -> Font` + + Load a new font from a given filename or a python file object. The size is + the height of the font in pixels. If the filename is None the pygame default + font will be loaded. If a font cannot be loaded from the arguments given an + exception will be raised. Once the font is created the size cannot be + changed. + + Font objects are mainly used to render text into new Surface objects. The + render can emulate bold or italic features, but it is better to load from a + font with actual italic or bold glyphs. The rendered text can be regular + strings or unicode. + + .. attribute:: bold + + | :sl:`Gets or sets whether the font should be rendered in (faked) bold.` + | :sg:`bold -> bool` + + Whether the font should be rendered in bold. + + When set to True, this enables the bold rendering of text. This + is a fake stretching of the font that doesn't look good on many + font types. If possible load the font from a real bold font + file. While bold, the font will have a different width than when + normal. This can be mixed with the italic and underline modes. + + .. versionadded:: 2.0.0 + + .. ## Font.bold ## + + .. attribute:: italic + + | :sl:`Gets or sets whether the font should be rendered in (faked) italics.` + | :sg:`italic -> bool` + + Whether the font should be rendered in italic. + + When set to True, this enables fake rendering of italic + text. This is a fake skewing of the font that doesn't look good + on many font types. If possible load the font from a real italic + font file. While italic the font will have a different width + than when normal. This can be mixed with the bold and underline + modes. + + .. versionadded:: 2.0.0 + + .. ## Font.italic ## + + .. attribute:: underline + + | :sl:`Gets or sets whether the font should be rendered with an underline.` + | :sg:`underline -> bool` + + Whether the font should be rendered in underline. + + When set to True, all rendered fonts will include an + underline. The underline is always one pixel thick, regardless + of font size. This can be mixed with the bold and italic modes. + + .. versionadded:: 2.0.0 + + .. ## Font.underline ## + + .. method:: render + + | :sl:`draw text on a new Surface` + | :sg:`render(text, antialias, color, background=None) -> Surface` + + This creates a new Surface with the specified text rendered on it. pygame + provides no way to directly draw text on an existing Surface: instead you + must use ``Font.render()`` to create an image (Surface) of the text, then + blit this image onto another Surface. + + The text can only be a single line: newline characters are not rendered. + Null characters ('\x00') raise a TypeError. Both Unicode and char (byte) + strings are accepted. For Unicode strings only UCS-2 characters + ('\u0001' to '\uFFFF') were previously supported and any greater unicode + codepoint would raise a UnicodeError. Now, characters in the UCS-4 range + are supported. For char strings a ``LATIN1`` encoding is assumed. The + antialias argument is a boolean: if true the characters will have smooth + edges. The color argument is the color of the text + [e.g.: (0,0,255) for blue]. The optional background argument is a color + to use for the text background. If no background is passed the area + outside the text will be transparent. + + The Surface returned will be of the dimensions required to hold the text. + (the same as those returned by Font.size()). If an empty string is passed + for the text, a blank surface will be returned that is zero pixel wide and + the height of the font. + + Depending on the type of background and antialiasing used, this returns + different types of Surfaces. For performance reasons, it is good to know + what type of image will be used. If antialiasing is not used, the return + image will always be an 8-bit image with a two-color palette. If the + background is transparent a colorkey will be set. Antialiased images are + rendered to 24-bit ``RGB`` images. If the background is transparent a + pixel alpha will be included. + + Optimization: if you know that the final destination for the text (on the + screen) will always have a solid background, and the text is antialiased, + you can improve performance by specifying the background color. This will + cause the resulting image to maintain transparency information by + colorkey rather than (much less efficient) alpha values. + + If you render '\\n' an unknown char will be rendered. Usually a + rectangle. Instead you need to handle new lines yourself. + + Font rendering is not thread safe: only a single thread can render text + at any time. + + .. versionchanged:: 2.0.3 Rendering UCS_4 unicode works and does not + raise an exception. Use `if hasattr(pygame.font, 'UCS_4'):` to see if + pygame supports rendering UCS_4 unicode including more languages and + emoji. + + .. ## Font.render ## + + .. method:: size + + | :sl:`determine the amount of space needed to render text` + | :sg:`size(text) -> (width, height)` + + Returns the dimensions needed to render the text. This can be used to + help determine the positioning needed for text before it is rendered. It + can also be used for wordwrapping and other layout effects. + + Be aware that most fonts use kerning which adjusts the widths for + specific letter pairs. For example, the width for "ae" will not always + match the width for "a" + "e". + + .. ## Font.size ## + + .. method:: set_underline + + | :sl:`control if text is rendered with an underline` + | :sg:`set_underline(bool) -> None` + + When enabled, all rendered fonts will include an underline. The underline + is always one pixel thick, regardless of font size. This can be mixed + with the bold and italic modes. + + .. note:: This is the same as the :attr:`underline` attribute. + + .. ## Font.set_underline ## + + .. method:: get_underline + + | :sl:`check if text will be rendered with an underline` + | :sg:`get_underline() -> bool` + + Return True when the font underline is enabled. + + .. note:: This is the same as the :attr:`underline` attribute. + + .. ## Font.get_underline ## + + .. method:: set_bold + + | :sl:`enable fake rendering of bold text` + | :sg:`set_bold(bool) -> None` + + Enables the bold rendering of text. This is a fake stretching of the font + that doesn't look good on many font types. If possible load the font from + a real bold font file. While bold, the font will have a different width + than when normal. This can be mixed with the italic and underline modes. + + .. note:: This is the same as the :attr:`bold` attribute. + + .. ## Font.set_bold ## + + .. method:: get_bold + + | :sl:`check if text will be rendered bold` + | :sg:`get_bold() -> bool` + + Return True when the font bold rendering mode is enabled. + + .. note:: This is the same as the :attr:`bold` attribute. + + .. ## Font.get_bold ## + + .. method:: set_italic + + | :sl:`enable fake rendering of italic text` + | :sg:`set_italic(bool) -> None` + + Enables fake rendering of italic text. This is a fake skewing of the font + that doesn't look good on many font types. If possible load the font from + a real italic font file. While italic the font will have a different + width than when normal. This can be mixed with the bold and underline + modes. + + .. note:: This is the same as the :attr:`italic` attribute. + + .. ## Font.set_italic ## + + .. method:: metrics + + | :sl:`gets the metrics for each character in the passed string` + | :sg:`metrics(text) -> list` + + The list contains tuples for each character, which contain the minimum + ``X`` offset, the maximum ``X`` offset, the minimum ``Y`` offset, the + maximum ``Y`` offset and the advance offset (bearing plus width) of the + character. [(minx, maxx, miny, maxy, advance), (minx, maxx, miny, maxy, + advance), ...]. None is entered in the list for each unrecognized + character. + + .. ## Font.metrics ## + + .. method:: get_italic + + | :sl:`check if the text will be rendered italic` + | :sg:`get_italic() -> bool` + + Return True when the font italic rendering mode is enabled. + + .. note:: This is the same as the :attr:`italic` attribute. + + .. ## Font.get_italic ## + + .. method:: get_linesize + + | :sl:`get the line space of the font text` + | :sg:`get_linesize() -> int` + + Return the height in pixels for a line of text with the font. When + rendering multiple lines of text this is the recommended amount of space + between lines. + + .. ## Font.get_linesize ## + + .. method:: get_height + + | :sl:`get the height of the font` + | :sg:`get_height() -> int` + + Return the height in pixels of the actual rendered text. This is the + average size for each glyph in the font. + + .. ## Font.get_height ## + + .. method:: get_ascent + + | :sl:`get the ascent of the font` + | :sg:`get_ascent() -> int` + + Return the height in pixels for the font ascent. The ascent is the number + of pixels from the font baseline to the top of the font. + + .. ## Font.get_ascent ## + + .. method:: get_descent + + | :sl:`get the descent of the font` + | :sg:`get_descent() -> int` + + Return the height in pixels for the font descent. The descent is the + number of pixels from the font baseline to the bottom of the font. + + .. ## Font.get_descent ## + + .. ## pygame.font.Font ## + +.. ## pygame.font ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/freetype.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/freetype.rst.txt new file mode 100644 index 0000000..6e10461 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/freetype.rst.txt @@ -0,0 +1,766 @@ +.. include:: common.txt + +:mod:`pygame.freetype` +====================== + +.. module:: pygame.freetype + :synopsis: Enhanced pygame module for loading and rendering computer fonts + +| :sl:`Enhanced pygame module for loading and rendering computer fonts` + +The ``pygame.freetype`` module is a replacement for :mod:`pygame.font`. +It has all of the functionality of the original, plus many new features. +Yet is has absolutely no dependencies on the SDL_ttf library. +It is implemented directly on the FreeType 2 library. +The ``pygame.freetype`` module is not itself backward compatible with +:mod:`pygame.font`. +Instead, use the ``pygame.ftfont`` module as a drop-in replacement +for :mod:`pygame.font`. + +All font file formats supported by FreeType can be rendered by +``pygame.freetype``, namely ``TTF``, Type1, ``CFF``, OpenType, +``SFNT``, ``PCF``, ``FNT``, ``BDF``, ``PFR`` and Type42 fonts. +All glyphs having UTF-32 code points are accessible +(see :attr:`Font.ucs4`). + +Most work on fonts is done using :class:`Font` instances. +The module itself only has routines for initialization and creation +of :class:`Font` objects. +You can load fonts from the system using the :func:`SysFont` function. + +Extra support of bitmap fonts is available. Available bitmap sizes can +be listed (see :meth:`Font.get_sizes`). For bitmap only fonts :class:`Font` +can set the size for you (see the :attr:`Font.size` property). + +For now undefined character codes are replaced with the ``.notdef`` +(not defined) character. +How undefined codes are handled may become configurable in a future release. + +Pygame comes with a built-in default font. This can always be accessed by +passing None as the font name to the :class:`Font` constructor. + +Extra rendering features available to :class:`pygame.freetype.Font` +are direct to surface rendering (see :meth:`Font.render_to`), character kerning +(see :attr:`Font.kerning`), vertical layout (see :attr:`Font.vertical`), +rotation of rendered text (see :attr:`Font.rotation`), +and the strong style (see :attr:`Font.strong`). +Some properties are configurable, such as +strong style strength (see :attr:`Font.strength`) and underline positioning +(see :attr:`Font.underline_adjustment`). Text can be positioned by the upper +right corner of the text box or by the text baseline (see :attr:`Font.origin`). +Finally, a font's vertical and horizontal size can be adjusted separately +(see :attr:`Font.size`). +The :any:`pygame.examples.freetype_misc ` +example shows these features in use. + +The pygame package does not import ``freetype`` automatically when +loaded. This module must be imported explicitly to be used. :: + + import pygame + import pygame.freetype + +.. versionadded:: 1.9.2 :mod:`freetype` + + +.. function:: get_error + + | :sl:`Return the latest FreeType error` + | :sg:`get_error() -> str` + | :sg:`get_error() -> None` + + Return a description of the last error which occurred in the FreeType2 + library, or ``None`` if no errors have occurred. + +.. function:: get_version + + | :sl:`Return the FreeType version` + | :sg:`get_version() -> (int, int, int)` + + Returns the version of the FreeType library in use by this module. + + Note that the ``freetype`` module depends on the FreeType 2 library. + It will not compile with the original FreeType 1.0. Hence, the first element + of the tuple will always be "2". + +.. function:: init + + | :sl:`Initialize the underlying FreeType library.` + | :sg:`init(cache_size=64, resolution=72) -> None` + + This function initializes the underlying FreeType library and must be + called before trying to use any of the functionality of the ``freetype`` + module. + + However, :func:`pygame.init()` will automatically call this function + if the ``freetype`` module is already imported. It is safe to call this + function more than once. + + Optionally, you may specify a default *cache_size* for the Glyph cache: the + maximum number of glyphs that will be cached at any given time by the + module. Exceedingly small values will be automatically tuned for + performance. Also a default pixel *resolution*, in dots per inch, can + be given to adjust font scaling. + +.. function:: quit + + | :sl:`Shut down the underlying FreeType library.` + | :sg:`quit() -> None` + + This function closes the ``freetype`` module. After calling this + function, you should not invoke any class, method or function related to the + ``freetype`` module as they are likely to fail or might give unpredictable + results. It is safe to call this function even if the module hasn't been + initialized yet. + +.. function:: get_init + + | :sl:`Returns True if the FreeType module is currently initialized.` + | :sg:`get_init() -> bool` + + Returns ``True`` if the ``pygame.freetype`` module is currently initialized. + + .. versionadded:: 1.9.5 + +.. function:: was_init + + | :sl:`DEPRECATED: Use get_init() instead.` + | :sg:`was_init() -> bool` + + DEPRECATED: Returns ``True`` if the ``pygame.freetype`` module is currently + initialized. Use ``get_init()`` instead. + +.. function:: get_cache_size + + | :sl:`Return the glyph case size` + | :sg:`get_cache_size() -> long` + + See :func:`pygame.freetype.init()`. + +.. function:: get_default_resolution + + | :sl:`Return the default pixel size in dots per inch` + | :sg:`get_default_resolution() -> long` + + Returns the default pixel size, in dots per inch, for the module. + The default is 72 DPI. + +.. function:: set_default_resolution + + | :sl:`Set the default pixel size in dots per inch for the module` + | :sg:`set_default_resolution([resolution])` + + Set the default pixel size, in dots per inch, for the module. If the + optional argument is omitted or zero the resolution is reset to 72 DPI. + +.. function:: SysFont + + | :sl:`create a Font object from the system fonts` + | :sg:`SysFont(name, size, bold=False, italic=False) -> Font` + + Return a new Font object that is loaded from the system fonts. The font will + match the requested *bold* and *italic* flags. Pygame uses a small set of + common font aliases. If the specific font you ask for is not available, a + reasonable alternative may be used. If a suitable system font is not found + this will fall back on loading the default pygame font. + + The font *name* can also be an iterable of font names, a string of + comma-separated font names, or a bytes of comma-separated font names, in + which case the set of names will be searched in order. + + .. versionadded:: 2.0.1 Accept an iterable of font names. + +.. function:: get_default_font + + | :sl:`Get the filename of the default font` + | :sg:`get_default_font() -> string` + + Return the filename of the default pygame font. This is not the full path + to the file. The file is usually in the same directory as the font module, + but can also be bundled in a separate archive. + +.. class:: Font + + | :sl:`Create a new Font instance from a supported font file.` + | :sg:`Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font` + | :sg:`Font(pathlib.Path) -> Font` + + Argument *file* can be either a string representing the font's filename, a + file-like object containing the font, or None; if None, a default, + Pygame, font is used. + + .. _freetype-font-size-argument: + + Optionally, a *size* argument may be specified to set the default size in + points, which determines the size of the rendered characters. + The size can also be passed explicitly to each method call. + Because of the way the caching system works, specifying a default size on + the constructor doesn't imply a performance gain over manually passing + the size on each function call. If the font is bitmap and no *size* + is given, the default size is set to the first available size for the font. + + If the font file has more than one font, the font to load can be chosen with + the *index* argument. An exception is raised for an out-of-range font index + value. + + The optional *resolution* argument sets the pixel size, in dots per inch, + for use in scaling glyphs for this Font instance. If 0 then the default + module value, set by :func:`init`, is used. The Font object's + resolution can only be changed by re-initializing the Font instance. + + The optional *ucs4* argument, an integer, sets the default text translation + mode: 0 (False) recognize UTF-16 surrogate pairs, any other value (True), + to treat Unicode text as UCS-4, with no surrogate pairs. See + :attr:`Font.ucs4`. + + .. attribute:: name + + | :sl:`Proper font name.` + | :sg:`name -> string` + + Read only. Returns the real (long) name of the font, as + recorded in the font file. + + .. attribute:: path + + | :sl:`Font file path` + | :sg:`path -> unicode` + + Read only. Returns the path of the loaded font file + + .. attribute:: size + + | :sl:`The default point size used in rendering` + | :sg:`size -> float` + | :sg:`size -> (float, float)` + + Get or set the default size for text metrics and rendering. It can be + a single point size, given as a Python ``int`` or ``float``, or a + font ppem (width, height) ``tuple``. Size values are non-negative. + A zero size or width represents an undefined size. In this case + the size must be given as a method argument, or an exception is + raised. A zero width but non-zero height is a ValueError. + + For a scalable font, a single number value is equivalent to a tuple + with width equal height. A font can be stretched vertically with + height set greater than width, or horizontally with width set + greater than height. For embedded bitmaps, as listed by :meth:`get_sizes`, + use the nominal width and height to select an available size. + + Font size differs for a non-scalable, bitmap, font. During a + method call it must match one of the available sizes returned by + method :meth:`get_sizes`. If not, an exception is raised. + If the size is a single number, the size is first matched against the + point size value. If no match, then the available size with the + same nominal width and height is chosen. + + .. method:: get_rect + + | :sl:`Return the size and offset of rendered text` + | :sg:`get_rect(text, style=STYLE_DEFAULT, rotation=0, size=0) -> rect` + + Gets the final dimensions and origin, in pixels, of *text* using the + optional *size* in points, *style*, and *rotation*. For other + relevant render properties, and for any optional argument not given, + the default values set for the :class:`Font` instance are used. + + Returns a :class:`Rect ` instance containing the + width and height of the text's bounding box and the position of the + text's origin. + The origin is useful in aligning separately rendered pieces of text. + It gives the baseline position and bearing at the start of the text. + See the :meth:`render_to` method for an example. + + If *text* is a char (byte) string, its encoding is assumed to be + ``LATIN1``. + + Optionally, *text* can be ``None``, which will return the bounding + rectangle for the text passed to a previous :meth:`get_rect`, + :meth:`render`, :meth:`render_to`, :meth:`render_raw`, or + :meth:`render_raw_to` call. See :meth:`render_to` for more + details. + + .. method:: get_metrics + + | :sl:`Return the glyph metrics for the given text` + | :sg:`get_metrics(text, size=0) -> [(...), ...]` + + Returns the glyph metrics for each character in *text*. + + The glyph metrics are returned as a list of tuples. Each tuple gives + metrics of a single character glyph. The glyph metrics are: + + :: + + (min_x, max_x, min_y, max_y, horizontal_advance_x, horizontal_advance_y) + + The bounding box min_x, max_x, min_y, and max_y values are returned as + grid-fitted pixel coordinates of type int. The advance values are + float values. + + The calculations are done using the font's default size in points. + Optionally you may specify another point size with the *size* argument. + + The metrics are adjusted for the current rotation, strong, and oblique + settings. + + If text is a char (byte) string, then its encoding is assumed to be + ``LATIN1``. + + .. attribute:: height + + | :sl:`The unscaled height of the font in font units` + | :sg:`height -> int` + + Read only. Gets the height of the font. This is the average value of all + glyphs in the font. + + .. attribute:: ascender + + | :sl:`The unscaled ascent of the font in font units` + | :sg:`ascender -> int` + + Read only. Return the number of units from the font's baseline to + the top of the bounding box. + + .. attribute:: descender + + | :sl:`The unscaled descent of the font in font units` + | :sg:`descender -> int` + + Read only. Return the height in font units for the font descent. + The descent is the number of units from the font's baseline to the + bottom of the bounding box. + + .. method:: get_sized_ascender + + | :sl:`The scaled ascent of the font in pixels` + | :sg:`get_sized_ascender(=0) -> int` + + Return the number of units from the font's baseline to the top of the + bounding box. It is not adjusted for strong or rotation. + + .. method:: get_sized_descender + + | :sl:`The scaled descent of the font in pixels` + | :sg:`get_sized_descender(=0) -> int` + + Return the number of pixels from the font's baseline to the top of the + bounding box. It is not adjusted for strong or rotation. + + .. method:: get_sized_height + + | :sl:`The scaled height of the font in pixels` + | :sg:`get_sized_height(=0) -> int` + + Returns the height of the font. This is the average value of all + glyphs in the font. It is not adjusted for strong or rotation. + + .. method:: get_sized_glyph_height + + | :sl:`The scaled bounding box height of the font in pixels` + | :sg:`get_sized_glyph_height(=0) -> int` + + Return the glyph bounding box height of the font in pixels. + This is the average value of all glyphs in the font. + It is not adjusted for strong or rotation. + + .. method:: get_sizes + + | :sl:`return the available sizes of embedded bitmaps` + | :sg:`get_sizes() -> [(int, int, int, float, float), ...]` + | :sg:`get_sizes() -> []` + + Returns a list of tuple records, one for each point size + supported. Each tuple containing the point size, the height in pixels, + width in pixels, horizontal ppem (nominal width) in fractional pixels, + and vertical ppem (nominal height) in fractional pixels. + + .. method:: render + + | :sl:`Return rendered text as a surface` + | :sg:`render(text, fgcolor=None, bgcolor=None, style=STYLE_DEFAULT, rotation=0, size=0) -> (Surface, Rect)` + + Returns a new :class:`Surface `, + with the text rendered to it + in the color given by 'fgcolor'. If no foreground color is given, + the default foreground color, :attr:`fgcolor ` is used. + If ``bgcolor`` is given, the surface + will be filled with this color. When no background color is given, + the surface background is transparent, zero alpha. Normally the returned + surface has a 32 bit pixel size. However, if ``bgcolor`` is ``None`` + and anti-aliasing is disabled a monochrome 8 bit colorkey surface, + with colorkey set for the background color, is returned. + + The return value is a tuple: the new surface and the bounding + rectangle giving the size and origin of the rendered text. + + If an empty string is passed for text then the returned Rect is zero + width and the height of the font. + + Optional *fgcolor*, *style*, *rotation*, and *size* arguments override + the default values set for the :class:`Font` instance. + + If *text* is a char (byte) string, then its encoding is assumed to be + ``LATIN1``. + + Optionally, *text* can be ``None``, which will render the text + passed to a previous :meth:`get_rect`, :meth:`render`, :meth:`render_to`, + :meth:`render_raw`, or :meth:`render_raw_to` call. + See :meth:`render_to` for details. + + .. method:: render_to + + | :sl:`Render text onto an existing surface` + | :sg:`render_to(surf, dest, text, fgcolor=None, bgcolor=None, style=STYLE_DEFAULT, rotation=0, size=0) -> Rect` + + Renders the string *text* to the :mod:`pygame.Surface` *surf*, + at position *dest*, a (x, y) surface coordinate pair. + If either x or y is not an integer it is converted to one if possible. + Any sequence where the first two items are x and y positional elements + is accepted, including a :class:`Rect ` instance. + As with :meth:`render`, + optional *fgcolor*, *style*, *rotation*, and *size* argument are + available. + + If a background color *bgcolor* is given, the text bounding box is + first filled with that color. The text is blitted next. + Both the background fill and text rendering involve full alpha blits. + That is, the alpha values of the foreground, background, and destination + target surface all affect the blit. + + The return value is a rectangle giving the size and position of the + rendered text within the surface. + + If an empty string is passed for text then the returned + :class:`Rect ` is zero width and the height of the font. + The rect will test False. + + Optionally, *text* can be set ``None``, which will re-render text + passed to a previous :meth:`render_to`, :meth:`get_rect`, :meth:`render`, + :meth:`render_raw`, or :meth:`render_raw_to` call. Primarily, this + feature is an aid to using :meth:`render_to` in combination with + :meth:`get_rect`. An example: :: + + def word_wrap(surf, text, font, color=(0, 0, 0)): + font.origin = True + words = text.split(' ') + width, height = surf.get_size() + line_spacing = font.get_sized_height() + 2 + x, y = 0, line_spacing + space = font.get_rect(' ') + for word in words: + bounds = font.get_rect(word) + if x + bounds.width + bounds.x >= width: + x, y = 0, y + line_spacing + if x + bounds.width + bounds.x >= width: + raise ValueError("word too wide for the surface") + if y + bounds.height - bounds.y >= height: + raise ValueError("text to long for the surface") + font.render_to(surf, (x, y), None, color) + x += bounds.width + space.width + return x, y + + When :meth:`render_to` is called with the same + font properties ― :attr:`size`, :attr:`style`, :attr:`strength`, + :attr:`wide`, :attr:`antialiased`, :attr:`vertical`, :attr:`rotation`, + :attr:`kerning`, and :attr:`use_bitmap_strikes` ― as :meth:`get_rect`, + :meth:`render_to` will use the layout calculated by :meth:`get_rect`. + Otherwise, :meth:`render_to` will recalculate the layout if called + with a text string or one of the above properties has changed + after the :meth:`get_rect` call. + + If *text* is a char (byte) string, then its encoding is assumed to be + ``LATIN1``. + + .. method:: render_raw + + | :sl:`Return rendered text as a string of bytes` + | :sg:`render_raw(text, style=STYLE_DEFAULT, rotation=0, size=0, invert=False) -> (bytes, (int, int))` + + Like :meth:`render` but with the pixels returned as a byte string + of 8-bit gray-scale values. The foreground color is 255, the + background 0, useful as an alpha mask for a foreground pattern. + + .. method:: render_raw_to + + | :sl:`Render text into an array of ints` + | :sg:`render_raw_to(array, text, dest=None, style=STYLE_DEFAULT, rotation=0, size=0, invert=False) -> Rect` + + Render to an array object exposing an array struct interface. The array + must be two dimensional with integer items. The default *dest* value, + ``None``, is equivalent to position (0, 0). See :meth:`render_to`. + As with the other render methods, *text* can be ``None`` to + render a text string passed previously to another method. + + The return value is a :func:`pygame.Rect` giving the size and position of + the rendered text. + + .. attribute:: style + + | :sl:`The font's style flags` + | :sg:`style -> int` + + Gets or sets the default style of the Font. This default style will be + used for all text rendering and size calculations unless overridden + specifically a render or :meth:`get_rect` call. + The style value may be a bit-wise OR of one or more of the following + constants: + + :: + + STYLE_NORMAL + STYLE_UNDERLINE + STYLE_OBLIQUE + STYLE_STRONG + STYLE_WIDE + STYLE_DEFAULT + + These constants may be found on the FreeType constants module. + Optionally, the default style can be modified or obtained accessing the + individual style attributes (underline, oblique, strong). + + The ``STYLE_OBLIQUE`` and ``STYLE_STRONG`` styles are for + scalable fonts only. An attempt to set either for a bitmap font raises + an AttributeError. An attempt to set either for an inactive font, + as returned by ``Font.__new__()``, raises a RuntimeError. + + Assigning ``STYLE_DEFAULT`` to the :attr:`style` property leaves + the property unchanged, as this property defines the default. + The :attr:`style` property will never return ``STYLE_DEFAULT``. + + .. attribute:: underline + + | :sl:`The state of the font's underline style flag` + | :sg:`underline -> bool` + + Gets or sets whether the font will be underlined when drawing text. This + default style value will be used for all text rendering and size + calculations unless overridden specifically in a render or + :meth:`get_rect` call, via the 'style' parameter. + + .. attribute:: strong + + | :sl:`The state of the font's strong style flag` + | :sg:`strong -> bool` + + Gets or sets whether the font will be bold when drawing text. This + default style value will be used for all text rendering and size + calculations unless overridden specifically in a render or + :meth:`get_rect` call, via the 'style' parameter. + + .. attribute:: oblique + + | :sl:`The state of the font's oblique style flag` + | :sg:`oblique -> bool` + + Gets or sets whether the font will be rendered as oblique. This + default style value will be used for all text rendering and size + calculations unless overridden specifically in a render or + :meth:`get_rect` call, via the *style* parameter. + + The oblique style is only supported for scalable (outline) fonts. + An attempt to set this style on a bitmap font will raise an + AttributeError. If the font object is inactive, as returned by + ``Font.__new__()``, setting this property raises a RuntimeError. + + .. attribute:: wide + + | :sl:`The state of the font's wide style flag` + | :sg:`wide -> bool` + + Gets or sets whether the font will be stretched horizontally + when drawing text. It produces a result similar to + :class:`pygame.font.Font`'s bold. This style not available for + rotated text. + + .. attribute:: strength + + | :sl:`The strength associated with the strong or wide font styles` + | :sg:`strength -> float` + + The amount by which a font glyph's size is enlarged for the + strong or wide transformations, as a fraction of the untransformed + size. For the wide style only the horizontal dimension is + increased. For strong text both the horizontal and vertical + dimensions are enlarged. A wide style of strength 0.08333 ( 1/12 ) is + equivalent to the :class:`pygame.font.Font` bold style. + The default is 0.02778 ( 1/36 ). + + The strength style is only supported for scalable (outline) fonts. + An attempt to set this property on a bitmap font will raise an + AttributeError. If the font object is inactive, as returned by + ``Font.__new__()``, assignment to this property raises a RuntimeError. + + .. attribute:: underline_adjustment + + | :sl:`Adjustment factor for the underline position` + | :sg:`underline_adjustment -> float` + + Gets or sets a factor which, when positive, is multiplied with the + font's underline offset to adjust the underline position. A negative + value turns an underline into a strike-through or overline. It is + multiplied with the ascender. Accepted values range between -2.0 and 2.0 + inclusive. A value of 0.5 closely matches Tango underlining. A value of + 1.0 mimics :class:`pygame.font.Font` underlining. + + .. attribute:: fixed_width + + | :sl:`Gets whether the font is fixed-width` + | :sg:`fixed_width -> bool` + + Read only. Returns ``True`` if the font contains fixed-width + characters (for example Courier, Bitstream Vera Sans Mono, Andale Mono). + + .. attribute:: fixed_sizes + + | :sl:`the number of available bitmap sizes for the font` + | :sg:`fixed_sizes -> int` + + Read only. Returns the number of point sizes for which the font contains + bitmap character images. If zero then the font is not a bitmap font. + A scalable font may contain pre-rendered point sizes as strikes. + + .. attribute:: scalable + + | :sl:`Gets whether the font is scalable` + | :sg:`scalable -> bool` + + Read only. Returns ``True`` if the font contains outline glyphs. + If so, the point size is not limited to available bitmap sizes. + + .. attribute:: use_bitmap_strikes + + | :sl:`allow the use of embedded bitmaps in an outline font file` + | :sg:`use_bitmap_strikes -> bool` + + Some scalable fonts include embedded bitmaps for particular point + sizes. This property controls whether or not those bitmap strikes + are used. Set it ``False`` to disable the loading of any bitmap + strike. Set it ``True``, the default, to permit bitmap strikes + for a non-rotated render with no style other than :attr:`wide` or + :attr:`underline`. This property is ignored for bitmap fonts. + + See also :attr:`fixed_sizes` and :meth:`get_sizes`. + + .. attribute:: antialiased + + | :sl:`Font anti-aliasing mode` + | :sg:`antialiased -> bool` + + Gets or sets the font's anti-aliasing mode. This defaults to + ``True`` on all fonts, which are rendered with full 8 bit blending. + + Set to ``False`` to do monochrome rendering. This should + provide a small speed gain and reduce cache memory size. + + .. attribute:: kerning + + | :sl:`Character kerning mode` + | :sg:`kerning -> bool` + + Gets or sets the font's kerning mode. This defaults to ``False`` + on all fonts, which will be rendered without kerning. + + Set to ``True`` to add kerning between character pairs, if supported + by the font, when positioning glyphs. + + .. attribute:: vertical + + | :sl:`Font vertical mode` + | :sg:`vertical -> bool` + + Gets or sets whether the characters are laid out vertically rather + than horizontally. May be useful when rendering Kanji or some other + vertical script. + + Set to ``True`` to switch to a vertical text layout. The default + is ``False``, place horizontally. + + Note that the :class:`Font` class does not automatically determine + script orientation. Vertical layout must be selected explicitly. + + Also note that several font formats (especially bitmap based ones) don't + contain the necessary metrics to draw glyphs vertically, so drawing in + those cases will give unspecified results. + + .. attribute:: rotation + + | :sl:`text rotation in degrees counterclockwise` + | :sg:`rotation -> int` + + Gets or sets the baseline angle of the rendered text. The angle is + represented as integer degrees. The default angle is 0, with horizontal + text rendered along the X-axis, and vertical text along the Y-axis. + A positive value rotates these axes counterclockwise that many degrees. + A negative angle corresponds to a clockwise rotation. The rotation + value is normalized to a value within the range 0 to 359 inclusive + (eg. 390 -> 390 - 360 -> 30, -45 -> 360 + -45 -> 315, + 720 -> 720 - (2 * 360) -> 0). + + Only scalable (outline) fonts can be rotated. An attempt to change + the rotation of a bitmap font raises an AttributeError. + An attempt to change the rotation of an inactive font instance, as + returned by ``Font.__new__()``, raises a RuntimeError. + + .. attribute:: fgcolor + + | :sl:`default foreground color` + | :sg:`fgcolor -> Color` + + Gets or sets the default glyph rendering color. It is initially opaque + black ― (0, 0, 0, 255). Applies to :meth:`render` and :meth:`render_to`. + + .. attribute:: bgcolor + + | :sl:`default background color` + | :sg:`bgcolor -> Color` + + Gets or sets the default background rendering color. Initially it is + unset and text will render with a transparent background by default. + Applies to :meth:`render` and :meth:`render_to`. + + .. versionadded:: 2.0.0 + + .. attribute:: origin + + | :sl:`Font render to text origin mode` + | :sg:`origin -> bool` + + If set ``True``, :meth:`render_to` and :meth:`render_raw_to` will + take the *dest* position to be that of the text origin, as opposed to + the top-left corner of the bounding box. See :meth:`get_rect` for + details. + + .. attribute:: pad + + | :sl:`padded boundary mode` + | :sg:`pad -> bool` + + If set ``True``, then the text boundary rectangle will be inflated + to match that of :class:`font.Font `. + Otherwise, the boundary rectangle is just large enough for the text. + + .. attribute:: ucs4 + + | :sl:`Enable UCS-4 mode` + | :sg:`ucs4 -> bool` + + Gets or sets the decoding of Unicode text. By default, the + freetype module performs UTF-16 surrogate pair decoding on Unicode text. + This allows 32-bit escape sequences ('\Uxxxxxxxx') between 0x10000 and + 0x10FFFF to represent their corresponding UTF-32 code points on Python + interpreters built with a UCS-2 Unicode type (on Windows, for instance). + It also means character values within the UTF-16 surrogate area (0xD800 + to 0xDFFF) are considered part of a surrogate pair. A malformed surrogate + pair will raise a UnicodeEncodeError. Setting ucs4 ``True`` turns + surrogate pair decoding off, allowing access the full UCS-4 character + range to a Python interpreter built with four-byte Unicode character + support. + + .. attribute:: resolution + + | :sl:`Pixel resolution in dots per inch` + | :sg:`resolution -> int` + + Read only. Gets pixel size used in scaling font glyphs for this + :class:`Font` instance. diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/gfxdraw.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/gfxdraw.rst.txt new file mode 100644 index 0000000..28153a3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/gfxdraw.rst.txt @@ -0,0 +1,628 @@ +.. include:: common.txt + +:mod:`pygame.gfxdraw` +===================== + +.. module:: pygame.gfxdraw + :synopsis: pygame module for drawing shapes + +| :sl:`pygame module for drawing shapes` + +**EXPERIMENTAL!**: This API may change or disappear in later pygame releases. If +you use this, your code may break with the next pygame release. + +The pygame package does not import gfxdraw automatically when loaded, so it +must imported explicitly to be used. + +:: + + import pygame + import pygame.gfxdraw + +For all functions the arguments are strictly positional and integers are +accepted for coordinates and radii. The ``color`` argument can be one of the +following formats: + + - a :mod:`pygame.Color` object + - an ``(RGB)`` triplet (tuple/list) + - an ``(RGBA)`` quadruplet (tuple/list) + +The functions :meth:`rectangle` and :meth:`box` will accept any ``(x, y, w, h)`` +sequence for their ``rect`` argument, though :mod:`pygame.Rect` instances are +preferred. + +To draw a filled antialiased shape, first use the antialiased (aa*) version +of the function, and then use the filled (filled_*) version. +For example: + +:: + + col = (255, 0, 0) + surf.fill((255, 255, 255)) + pygame.gfxdraw.aacircle(surf, x, y, 30, col) + pygame.gfxdraw.filled_circle(surf, x, y, 30, col) + + +.. note:: + For threading, each of the functions releases the GIL during the C part of + the call. + +.. note:: + See the :mod:`pygame.draw` module for alternative draw methods. + The ``pygame.gfxdraw`` module differs from the :mod:`pygame.draw` module in + the API it uses and the different draw functions available. + ``pygame.gfxdraw`` wraps the primitives from the library called SDL_gfx, + rather than using modified versions. + +.. versionadded:: 1.9.0 + + +.. function:: pixel + + | :sl:`draw a pixel` + | :sg:`pixel(surface, x, y, color) -> None` + + Draws a single pixel, at position (x ,y), on the given surface. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the pixel + :param int y: y coordinate of the pixel + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.pixel ## + +.. function:: hline + + | :sl:`draw a horizontal line` + | :sg:`hline(surface, x1, x2, y, color) -> None` + + Draws a straight horizontal line (``(x1, y)`` to ``(x2, y)``) on the given + surface. There are no endcaps. + + :param Surface surface: surface to draw on + :param int x1: x coordinate of one end of the line + :param int x2: x coordinate of the other end of the line + :param int y: y coordinate of the line + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.hline ## + +.. function:: vline + + | :sl:`draw a vertical line` + | :sg:`vline(surface, x, y1, y2, color) -> None` + + Draws a straight vertical line (``(x, y1)`` to ``(x, y2)``) on the given + surface. There are no endcaps. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the line + :param int y1: y coordinate of one end of the line + :param int y2: y coordinate of the other end of the line + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.vline ## + +.. function:: line + + | :sl:`draw a line` + | :sg:`line(surface, x1, y1, x2, y2, color) -> None` + + Draws a straight line (``(x1, y1)`` to ``(x2, y2)``) on the given surface. + There are no endcaps. + + :param Surface surface: surface to draw on + :param int x1: x coordinate of one end of the line + :param int y1: y coordinate of one end of the line + :param int x2: x coordinate of the other end of the line + :param int y2: y coordinate of the other end of the line + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.line ## + +.. function:: rectangle + + | :sl:`draw a rectangle` + | :sg:`rectangle(surface, rect, color) -> None` + + Draws an unfilled rectangle on the given surface. For a filled rectangle use + :meth:`box`. + + :param Surface surface: surface to draw on + :param Rect rect: rectangle to draw, position and dimensions + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. note:: + The ``rect.bottom`` and ``rect.right`` attributes of a :mod:`pygame.Rect` + always lie one pixel outside of its actual border. Therefore, these + values will not be included as part of the drawing. + + .. ## pygame.gfxdraw.rectangle ## + +.. function:: box + + | :sl:`draw a filled rectangle` + | :sg:`box(surface, rect, color) -> None` + + Draws a filled rectangle on the given surface. For an unfilled rectangle use + :meth:`rectangle`. + + :param Surface surface: surface to draw on + :param Rect rect: rectangle to draw, position and dimensions + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. note:: + The ``rect.bottom`` and ``rect.right`` attributes of a :mod:`pygame.Rect` + always lie one pixel outside of its actual border. Therefore, these + values will not be included as part of the drawing. + + .. note:: + The :func:`pygame.Surface.fill` method works just as well for drawing + filled rectangles. In fact :func:`pygame.Surface.fill` can be hardware + accelerated on some platforms with both software and hardware display + modes. + + .. ## pygame.gfxdraw.box ## + +.. function:: circle + + | :sl:`draw a circle` + | :sg:`circle(surface, x, y, r, color) -> None` + + Draws an unfilled circle on the given surface. For a filled circle use + :meth:`filled_circle`. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the circle + :param int y: y coordinate of the center of the circle + :param int r: radius of the circle + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.circle ## + +.. function:: aacircle + + | :sl:`draw an antialiased circle` + | :sg:`aacircle(surface, x, y, r, color) -> None` + + Draws an unfilled antialiased circle on the given surface. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the circle + :param int y: y coordinate of the center of the circle + :param int r: radius of the circle + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.aacircle ## + +.. function:: filled_circle + + | :sl:`draw a filled circle` + | :sg:`filled_circle(surface, x, y, r, color) -> None` + + Draws a filled circle on the given surface. For an unfilled circle use + :meth:`circle`. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the circle + :param int y: y coordinate of the center of the circle + :param int r: radius of the circle + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.filled_circle ## + +.. function:: ellipse + + | :sl:`draw an ellipse` + | :sg:`ellipse(surface, x, y, rx, ry, color) -> None` + + Draws an unfilled ellipse on the given surface. For a filled ellipse use + :meth:`filled_ellipse`. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the ellipse + :param int y: y coordinate of the center of the ellipse + :param int rx: horizontal radius of the ellipse + :param int ry: vertical radius of the ellipse + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.ellipse ## + +.. function:: aaellipse + + | :sl:`draw an antialiased ellipse` + | :sg:`aaellipse(surface, x, y, rx, ry, color) -> None` + + Draws an unfilled antialiased ellipse on the given surface. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the ellipse + :param int y: y coordinate of the center of the ellipse + :param int rx: horizontal radius of the ellipse + :param int ry: vertical radius of the ellipse + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.aaellipse ## + +.. function:: filled_ellipse + + | :sl:`draw a filled ellipse` + | :sg:`filled_ellipse(surface, x, y, rx, ry, color) -> None` + + Draws a filled ellipse on the given surface. For an unfilled ellipse use + :meth:`ellipse`. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the ellipse + :param int y: y coordinate of the center of the ellipse + :param int rx: horizontal radius of the ellipse + :param int ry: vertical radius of the ellipse + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.filled_ellipse ## + +.. function:: arc + + | :sl:`draw an arc` + | :sg:`arc(surface, x, y, r, start_angle, stop_angle, color) -> None` + + Draws an arc on the given surface. For an arc with its endpoints connected + to its center use :meth:`pie`. + + The two angle arguments are given in degrees and indicate the start and stop + positions of the arc. The arc is drawn in a clockwise direction from the + ``start_angle`` to the ``stop_angle``. If ``start_angle == stop_angle``, + nothing will be drawn + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the arc + :param int y: y coordinate of the center of the arc + :param int r: radius of the arc + :param int start_angle: start angle in degrees + :param int stop_angle: stop angle in degrees + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. note:: + This function uses *degrees* while the :func:`pygame.draw.arc` function + uses *radians*. + + .. ## pygame.gfxdraw.arc ## + +.. function:: pie + + | :sl:`draw a pie` + | :sg:`pie(surface, x, y, r, start_angle, stop_angle, color) -> None` + + Draws an unfilled pie on the given surface. A pie is an :meth:`arc` with its + endpoints connected to its center. + + The two angle arguments are given in degrees and indicate the start and stop + positions of the pie. The pie is drawn in a clockwise direction from the + ``start_angle`` to the ``stop_angle``. If ``start_angle == stop_angle``, + a straight line will be drawn from the center position at the given angle, + to a length of the radius. + + :param Surface surface: surface to draw on + :param int x: x coordinate of the center of the pie + :param int y: y coordinate of the center of the pie + :param int r: radius of the pie + :param int start_angle: start angle in degrees + :param int stop_angle: stop angle in degrees + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.pie ## + +.. function:: trigon + + | :sl:`draw a trigon/triangle` + | :sg:`trigon(surface, x1, y1, x2, y2, x3, y3, color) -> None` + + Draws an unfilled trigon (triangle) on the given surface. For a filled + trigon use :meth:`filled_trigon`. + + A trigon can also be drawn using :meth:`polygon` e.g. + ``polygon(surface, ((x1, y1), (x2, y2), (x3, y3)), color)`` + + :param Surface surface: surface to draw on + :param int x1: x coordinate of the first corner of the trigon + :param int y1: y coordinate of the first corner of the trigon + :param int x2: x coordinate of the second corner of the trigon + :param int y2: y coordinate of the second corner of the trigon + :param int x3: x coordinate of the third corner of the trigon + :param int y3: y coordinate of the third corner of the trigon + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.trigon ## + +.. function:: aatrigon + + | :sl:`draw an antialiased trigon/triangle` + | :sg:`aatrigon(surface, x1, y1, x2, y2, x3, y3, color) -> None` + + Draws an unfilled antialiased trigon (triangle) on the given surface. + + An aatrigon can also be drawn using :meth:`aapolygon` e.g. + ``aapolygon(surface, ((x1, y1), (x2, y2), (x3, y3)), color)`` + + :param Surface surface: surface to draw on + :param int x1: x coordinate of the first corner of the trigon + :param int y1: y coordinate of the first corner of the trigon + :param int x2: x coordinate of the second corner of the trigon + :param int y2: y coordinate of the second corner of the trigon + :param int x3: x coordinate of the third corner of the trigon + :param int y3: y coordinate of the third corner of the trigon + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.aatrigon ## + +.. function:: filled_trigon + + | :sl:`draw a filled trigon/triangle` + | :sg:`filled_trigon(surface, x1, y1, x2, y2, x3, y3, color) -> None` + + Draws a filled trigon (triangle) on the given surface. For an unfilled + trigon use :meth:`trigon`. + + A filled_trigon can also be drawn using :meth:`filled_polygon` e.g. + ``filled_polygon(surface, ((x1, y1), (x2, y2), (x3, y3)), color)`` + + :param Surface surface: surface to draw on + :param int x1: x coordinate of the first corner of the trigon + :param int y1: y coordinate of the first corner of the trigon + :param int x2: x coordinate of the second corner of the trigon + :param int y2: y coordinate of the second corner of the trigon + :param int x3: x coordinate of the third corner of the trigon + :param int y3: y coordinate of the third corner of the trigon + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + .. ## pygame.gfxdraw.filled_trigon ## + +.. function:: polygon + + | :sl:`draw a polygon` + | :sg:`polygon(surface, points, color) -> None` + + Draws an unfilled polygon on the given surface. For a filled polygon use + :meth:`filled_polygon`. + + The adjacent coordinates in the ``points`` argument, as well as the first + and last points, will be connected by line segments. + e.g. For the points ``[(x1, y1), (x2, y2), (x3, y3)]`` a line segment will + be drawn from ``(x1, y1)`` to ``(x2, y2)``, from ``(x2, y2)`` to + ``(x3, y3)``, and from ``(x3, y3)`` to ``(x1, y1)``. + + :param Surface surface: surface to draw on + :param points: a sequence of 3 or more (x, y) coordinates, where each + *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats (float values + will be truncated) + :type points: tuple(coordinate) or list(coordinate) + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + :raises ValueError: if ``len(points) < 3`` (must have at least 3 points) + :raises IndexError: if ``len(coordinate) < 2`` (each coordinate must have + at least 2 items) + + .. ## pygame.gfxdraw.polygon ## + +.. function:: aapolygon + + | :sl:`draw an antialiased polygon` + | :sg:`aapolygon(surface, points, color) -> None` + + Draws an unfilled antialiased polygon on the given surface. + + The adjacent coordinates in the ``points`` argument, as well as the first + and last points, will be connected by line segments. + e.g. For the points ``[(x1, y1), (x2, y2), (x3, y3)]`` a line segment will + be drawn from ``(x1, y1)`` to ``(x2, y2)``, from ``(x2, y2)`` to + ``(x3, y3)``, and from ``(x3, y3)`` to ``(x1, y1)``. + + :param Surface surface: surface to draw on + :param points: a sequence of 3 or more (x, y) coordinates, where each + *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats (float values + will be truncated) + :type points: tuple(coordinate) or list(coordinate) + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + :raises ValueError: if ``len(points) < 3`` (must have at least 3 points) + :raises IndexError: if ``len(coordinate) < 2`` (each coordinate must have + at least 2 items) + + .. ## pygame.gfxdraw.aapolygon ## + +.. function:: filled_polygon + + | :sl:`draw a filled polygon` + | :sg:`filled_polygon(surface, points, color) -> None` + + Draws a filled polygon on the given surface. For an unfilled polygon use + :meth:`polygon`. + + The adjacent coordinates in the ``points`` argument, as well as the first + and last points, will be connected by line segments. + e.g. For the points ``[(x1, y1), (x2, y2), (x3, y3)]`` a line segment will + be drawn from ``(x1, y1)`` to ``(x2, y2)``, from ``(x2, y2)`` to + ``(x3, y3)``, and from ``(x3, y3)`` to ``(x1, y1)``. + + :param Surface surface: surface to draw on + :param points: a sequence of 3 or more (x, y) coordinates, where each + *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats (float values + will be truncated)` + :type points: tuple(coordinate) or list(coordinate) + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + :raises ValueError: if ``len(points) < 3`` (must have at least 3 points) + :raises IndexError: if ``len(coordinate) < 2`` (each coordinate must have + at least 2 items) + + .. ## pygame.gfxdraw.filled_polygon ## + +.. function:: textured_polygon + + | :sl:`draw a textured polygon` + | :sg:`textured_polygon(surface, points, texture, tx, ty) -> None` + + Draws a textured polygon on the given surface. For better performance, the + surface and the texture should have the same format. + + A per-pixel alpha texture blit to a per-pixel alpha surface will differ from + a :func:`pygame.Surface.blit` blit. Also, a per-pixel alpha texture cannot be + used with an 8-bit per pixel destination. + + The adjacent coordinates in the ``points`` argument, as well as the first + and last points, will be connected by line segments. + e.g. For the points ``[(x1, y1), (x2, y2), (x3, y3)]`` a line segment will + be drawn from ``(x1, y1)`` to ``(x2, y2)``, from ``(x2, y2)`` to + ``(x3, y3)``, and from ``(x3, y3)`` to ``(x1, y1)``. + + :param Surface surface: surface to draw on + :param points: a sequence of 3 or more (x, y) coordinates, where each + *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats (float values + will be truncated) + :type points: tuple(coordinate) or list(coordinate) + :param Surface texture: texture to draw on the polygon + :param int tx: x offset of the texture + :param int ty: y offset of the texture + + :returns: ``None`` + :rtype: NoneType + + :raises ValueError: if ``len(points) < 3`` (must have at least 3 points) + :raises IndexError: if ``len(coordinate) < 2`` (each coordinate must have + at least 2 items) + + .. ## pygame.gfxdraw.textured_polygon ## + +.. function:: bezier + + | :sl:`draw a Bezier curve` + | :sg:`bezier(surface, points, steps, color) -> None` + + Draws a Bézier curve on the given surface. + + :param Surface surface: surface to draw on + :param points: a sequence of 3 or more (x, y) coordinates used to form a + curve, where each *coordinate* in the sequence must be a + tuple/list/:class:`pygame.math.Vector2` of 2 ints/floats (float values + will be truncated) + :type points: tuple(coordinate) or list(coordinate) + :param int steps: number of steps for the interpolation, the minimum is 2 + :param color: color to draw with, the alpha value is optional if using a + tuple ``(RGB[A])`` + :type color: Color or tuple(int, int, int, [int]) + + :returns: ``None`` + :rtype: NoneType + + :raises ValueError: if ``steps < 2`` + :raises ValueError: if ``len(points) < 3`` (must have at least 3 points) + :raises IndexError: if ``len(coordinate) < 2`` (each coordinate must have + at least 2 items) + + .. ## pygame.gfxdraw.bezier ## + +.. ## pygame.gfxdraw ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/image.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/image.rst.txt new file mode 100644 index 0000000..ee4d238 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/image.rst.txt @@ -0,0 +1,284 @@ +.. include:: common.txt + +:mod:`pygame.image` +=================== + +.. module:: pygame.image + :synopsis: pygame module for image transfer + +| :sl:`pygame module for image transfer` + +The image module contains functions for loading and saving pictures, as well as +transferring Surfaces to formats usable by other packages. + +Note that there is no Image class; an image is loaded as a Surface object. The +Surface class allows manipulation (drawing lines, setting pixels, capturing +regions, etc.). + +The image module is a required dependency of pygame, but it only optionally +supports any extended file formats. By default it can only load uncompressed +``BMP`` images. When built with full image support, the ``pygame.image.load()`` +function can support the following formats. + + * ``BMP`` + + * ``GIF`` (non-animated) + + * ``JPEG`` + + * ``LBM`` (and ``PBM``, ``PGM``, ``PPM``) + + * ``PCX`` + + * ``PNG`` + + * ``PNM`` + + * ``SVG`` (limited support, using Nano SVG) + + * ``TGA`` (uncompressed) + + * ``TIFF`` + + * ``WEBP`` + + * ``XPM`` + + +.. versionadded:: 2.0 Loading SVG, WebP, PNM + +Saving images only supports a limited set of formats. You can save to the +following formats. + + * ``BMP`` + + * ``JPEG`` + + * ``PNG`` + + * ``TGA`` + + +``JPEG`` and ``JPG``, as well as ``TIF`` and ``TIFF`` refer to the same file format + +.. versionadded:: 1.8 Saving PNG and JPEG files. + + +.. function:: load + + | :sl:`load new image from a file (or file-like object)` + | :sg:`load(filename) -> Surface` + | :sg:`load(fileobj, namehint="") -> Surface` + + Load an image from a file source. You can pass either a filename, a Python + file-like object, or a pathlib.Path. + + Pygame will automatically determine the image type (e.g., ``GIF`` or bitmap) + and create a new Surface object from the data. In some cases it will need to + know the file extension (e.g., ``GIF`` images should end in ".gif"). If you + pass a raw file-like object, you may also want to pass the original filename + as the namehint argument. + + The returned Surface will contain the same color format, colorkey and alpha + transparency as the file it came from. You will often want to call + ``Surface.convert()`` with no arguments, to create a copy that will draw + more quickly on the screen. + + For alpha transparency, like in .png images, use the ``convert_alpha()`` + method after loading so that the image has per pixel transparency. + + pygame may not always be built to support all image formats. At minimum it + will support uncompressed ``BMP``. If ``pygame.image.get_extended()`` + returns 'True', you should be able to load most images (including PNG, JPG + and GIF). + + You should use ``os.path.join()`` for compatibility. + + :: + + eg. asurf = pygame.image.load(os.path.join('data', 'bla.png')) + + .. ## pygame.image.load ## + +.. function:: save + + | :sl:`save an image to file (or file-like object)` + | :sg:`save(Surface, filename) -> None` + | :sg:`save(Surface, fileobj, namehint="") -> None` + + This will save your Surface as either a ``BMP``, ``TGA``, ``PNG``, or + ``JPEG`` image. If the filename extension is unrecognized it will default to + ``TGA``. Both ``TGA``, and ``BMP`` file formats create uncompressed files. + You can pass a filename, a pathlib.Path or a Python file-like object. + For file-like object, the image is saved to ``TGA`` format unless + a namehint with a recognizable extension is passed in. + + .. note:: When saving to a file-like object, it seems that for most formats, + the object needs to be flushed after saving to it to make loading + from it possible. + + .. versionchanged:: 1.8 Saving PNG and JPEG files. + .. versionchanged:: 2.0.0 + The ``namehint`` parameter was added to make it possible + to save other formats than ``TGA`` to a file-like object. + Saving to a file-like object with JPEG is possible. + + .. ## pygame.image.save ## + +.. function:: get_sdl_image_version + + | :sl:`get version number of the SDL_Image library being used` + | :sg:`get_sdl_image_version() -> None` + | :sg:`get_sdl_image_version() -> (major, minor, patch)` + + If pygame is built with extended image formats, then this function will + return the SDL_Image library's version number as a tuple of 3 integers + ``(major, minor, patch)``. If not, then it will return ``None``. + + .. versionadded:: 2.0.0 + + .. ## pygame.image.get_sdl_image_version ## + +.. function:: get_extended + + | :sl:`test if extended image formats can be loaded` + | :sg:`get_extended() -> bool` + + If pygame is built with extended image formats this function will return + True. It is still not possible to determine which formats will be available, + but generally you will be able to load them all. + + .. ## pygame.image.get_extended ## + +.. function:: tostring + + | :sl:`transfer image to string buffer` + | :sg:`tostring(Surface, format, flipped=False) -> string` + + Creates a string that can be transferred with the 'fromstring' method in + other Python imaging packages. Some Python image packages prefer their + images in bottom-to-top format (PyOpenGL for example). If you pass True for + the flipped argument, the string buffer will be vertically flipped. + + The format argument is a string of one of the following values. Note that + only 8-bit Surfaces can use the "P" format. The other formats will work for + any Surface. Also note that other Python image packages support more formats + than pygame. + + * ``P``, 8-bit palettized Surfaces + + * ``RGB``, 24-bit image + + * ``RGBX``, 32-bit image with unused space + + * ``RGBA``, 32-bit image with an alpha channel + + * ``ARGB``, 32-bit image with alpha channel first + + * ``RGBA_PREMULT``, 32-bit image with colors scaled by alpha channel + + * ``ARGB_PREMULT``, 32-bit image with colors scaled by alpha channel, alpha channel first + + .. ## pygame.image.tostring ## + +.. function:: fromstring + + | :sl:`create new Surface from a string buffer` + | :sg:`fromstring(string, size, format, flipped=False) -> Surface` + + This function takes arguments similar to ``pygame.image.tostring()``. The + size argument is a pair of numbers representing the width and height. Once + the new Surface is created you can destroy the string buffer. + + The size and format image must compute the exact same size as the passed + string buffer. Otherwise an exception will be raised. + + See the ``pygame.image.frombuffer()`` method for a potentially faster way to + transfer images into pygame. + + .. ## pygame.image.fromstring ## + +.. function:: frombuffer + + | :sl:`create a new Surface that shares data inside a bytes buffer` + | :sg:`frombuffer(bytes, size, format) -> Surface` + + Create a new Surface that shares pixel data directly from a bytes buffer. + This method takes similar arguments to ``pygame.image.fromstring()``, but + is unable to vertically flip the source data. + + This will run much faster than :func:`pygame.image.fromstring`, since no + pixel data must be allocated and copied. + + It accepts the following 'format' arguments: + + * ``P``, 8-bit palettized Surfaces + + * ``RGB``, 24-bit image + + * ``BGR``, 24-bit image, red and blue channels swapped. + + * ``RGBX``, 32-bit image with unused space + + * ``RGBA``, 32-bit image with an alpha channel + + * ``ARGB``, 32-bit image with alpha channel first + + .. ## pygame.image.frombuffer ## + +.. function:: load_basic + + | :sl:`load new BMP image from a file (or file-like object)` + | :sg:`load_basic(file) -> Surface` + + Load an image from a file source. You can pass either a filename or a Python + file-like object, or a pathlib.Path. + + This function only supports loading "basic" image format, ie ``BMP`` + format. + This function is always available, no matter how pygame was built. + + .. ## pygame.image.load_basic ## + +.. function:: load_extended + + | :sl:`load an image from a file (or file-like object)` + | :sg:`load_extended(filename) -> Surface` + | :sg:`load_extended(fileobj, namehint="") -> Surface` + + This function is similar to ``pygame.image.load()``, except that this + function can only be used if pygame was built with extended image format + support. + + From version 2.0.1, this function is always available, but raises an + error if extended image formats are not supported. Previously, this + function may or may not be available, depending on the state of + extended image format support. + + .. versionchanged:: 2.0.1 + + .. ## pygame.image.load_extended ## + +.. function:: save_extended + + | :sl:`save a png/jpg image to file (or file-like object)` + | :sg:`save_extended(Surface, filename) -> None` + | :sg:`save_extended(Surface, fileobj, namehint="") -> None` + + This will save your Surface as either a ``PNG`` or ``JPEG`` image. + + Incase the image is being saved to a file-like object, this function + uses the namehint argument to determine the format of the file being + saved. Saves to ``JPEG`` incase the namehint was not specified while + saving to file-like object. + + .. versionchanged:: 2.0.1 + This function is always available, but raises an + error if extended image formats are not supported. + Previously, this function may or may not be + available, depending on the state of extended image + format support. + + .. ## pygame.image.save_extended ## + +.. ## pygame.image ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/joystick.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/joystick.rst.txt new file mode 100644 index 0000000..9b72b71 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/joystick.rst.txt @@ -0,0 +1,545 @@ +.. include:: common.txt + +:mod:`pygame.joystick` +====================== + +.. module:: pygame.joystick + :synopsis: Pygame module for interacting with joysticks, gamepads, and trackballs. + +| :sl:`Pygame module for interacting with joysticks, gamepads, and trackballs.` + +The joystick module manages the joystick devices on a computer. +Joystick devices include trackballs and video-game-style +gamepads, and the module allows the use of multiple buttons and "hats". +Computers may manage multiple joysticks at a time. + +Each instance of the Joystick class represents one gaming device plugged +into the computer. If a gaming pad has multiple joysticks on it, then the +joystick object can actually represent multiple joysticks on that single +game device. + +For a quick way to initialise the joystick module and get a list of Joystick instances +use the following code:: + + pygame.joystick.init() + joysticks = [pygame.joystick.Joystick(x) for x in range(pygame.joystick.get_count())] + +The following event types will be generated by the joysticks :: + + JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION + +And in pygame 2, which supports hotplugging:: + + JOYDEVICEADDED JOYDEVICEREMOVED + +Note that in pygame 2, joysticks events use a unique "instance ID". The device index +passed in the constructor to a Joystick object is not unique after devices have +been added and removed. You must call :meth:`Joystick.get_instance_id()` to find +the instance ID that was assigned to a Joystick on opening. + +The event queue needs to be pumped frequently for some of the methods to work. +So call one of pygame.event.get, pygame.event.wait, or pygame.event.pump regularly. + + +.. function:: init + + | :sl:`Initialize the joystick module.` + | :sg:`init() -> None` + + This function is called automatically by ``pygame.init()``. + + It initializes the joystick module. The module must be initialized before any + other functions will work. + + It is safe to call this function more than once. + + .. ## pygame.joystick.init ## + +.. function:: quit + + | :sl:`Uninitialize the joystick module.` + | :sg:`quit() -> None` + + Uninitialize the joystick module. After you call this any existing joystick + objects will no longer work. + + It is safe to call this function more than once. + + .. ## pygame.joystick.quit ## + +.. function:: get_init + + | :sl:`Returns True if the joystick module is initialized.` + | :sg:`get_init() -> bool` + + Test if the ``pygame.joystick.init()`` function has been called. + + .. ## pygame.joystick.get_init ## + +.. function:: get_count + + | :sl:`Returns the number of joysticks.` + | :sg:`get_count() -> count` + + Return the number of joystick devices on the system. The count will be ``0`` + if there are no joysticks on the system. + + When you create Joystick objects using ``Joystick(id)``, you pass an integer + that must be lower than this count. + + .. ## pygame.joystick.get_count ## + +.. class:: Joystick + + | :sl:`Create a new Joystick object.` + | :sg:`Joystick(id) -> Joystick` + + Create a new joystick to access a physical device. The id argument must be a + value from ``0`` to ``pygame.joystick.get_count() - 1``. + + Joysticks are initialised on creation and are shut down when deallocated. + Once the device is initialized the pygame event queue will start receiving + events about its input. + + .. versionchanged:: 2.0.0 Joystick objects are now opened immediately on creation. + + .. method:: init + + | :sl:`initialize the Joystick` + | :sg:`init() -> None` + + Initialize the joystick, if it has been closed. It is safe to call this + even if the joystick is already initialized. + + .. deprecated:: 2.0.0 + + In future it will not be possible to reinitialise a closed Joystick + object. Will be removed in Pygame 2.1. + + .. ## Joystick.init ## + + .. method:: quit + + | :sl:`uninitialize the Joystick` + | :sg:`quit() -> None` + + Close a Joystick object. After this the pygame event queue will no longer + receive events from the device. + + It is safe to call this more than once. + + .. ## Joystick.quit ## + + .. method:: get_init + + | :sl:`check if the Joystick is initialized` + | :sg:`get_init() -> bool` + + Return True if the Joystick object is currently initialised. + + .. ## Joystick.get_init ## + + .. method:: get_id + + | :sl:`get the device index (deprecated)` + | :sg:`get_id() -> int` + + Returns the original device index for this device. This is the same + value that was passed to the ``Joystick()`` constructor. This method can + safely be called while the Joystick is not initialized. + + .. deprecated:: 2.0.0 + + The original device index is not useful in pygame 2. Use + :meth:`.get_instance_id` instead. Will be removed in Pygame 2.1. + + .. method:: get_instance_id() -> int + + | :sl:`get the joystick instance id` + | :sg:`get_instance_id() -> int` + + Get the joystick instance ID. This matches the ``instance_id`` field + that is given in joystick events. + + .. versionadded:: 2.0.0dev11 + + .. method:: get_guid() -> str + + | :sl:`get the joystick GUID` + | :sg:`get_guid() -> str` + + Get the GUID string. This identifies the exact hardware of the joystick + device. + + .. versionadded:: 2.0.0dev11 + + .. method:: get_power_level() -> str + + | :sl:`get the approximate power status of the device` + | :sg:`get_power_level() -> str` + + Get a string giving the power status of the device. + + One of: ``empty``, ``low``, ``medium``, ``full``, ``wired``, ``max``, or + ``unknown``. + + .. versionadded:: 2.0.0dev11 + + .. ## Joystick.get_id ## + + .. method:: get_name + + | :sl:`get the Joystick system name` + | :sg:`get_name() -> string` + + Returns the system name for this joystick device. It is unknown what name + the system will give to the Joystick, but it should be a unique name that + identifies the device. This method can safely be called while the + Joystick is not initialized. + + .. ## Joystick.get_name ## + + .. method:: get_numaxes + + | :sl:`get the number of axes on a Joystick` + | :sg:`get_numaxes() -> int` + + Returns the number of input axes are on a Joystick. There will usually be + two for the position. Controls like rudders and throttles are treated as + additional axes. + + The ``pygame.JOYAXISMOTION`` events will be in the range from ``-1.0`` + to ``1.0``. A value of ``0.0`` means the axis is centered. Gamepad devices + will usually be ``-1``, ``0``, or ``1`` with no values in between. Older + analog joystick axes will not always use the full ``-1`` to ``1`` range, + and the centered value will be some area around ``0``. + + Analog joysticks usually have a bit of noise in their axis, which will + generate a lot of rapid small motion events. + + .. ## Joystick.get_numaxes ## + + .. method:: get_axis + + | :sl:`get the current position of an axis` + | :sg:`get_axis(axis_number) -> float` + + Returns the current position of a joystick axis. The value will range + from ``-1`` to ``1`` with a value of ``0`` being centered. You may want + to take into account some tolerance to handle jitter, and joystick drift + may keep the joystick from centering at ``0`` or using the full range of + position values. + + The axis number must be an integer from ``0`` to ``get_numaxes() - 1``. + + When using gamepads both the control sticks and the analog triggers are + usually reported as axes. + + .. ## Joystick.get_axis ## + + .. method:: get_numballs + + | :sl:`get the number of trackballs on a Joystick` + | :sg:`get_numballs() -> int` + + Returns the number of trackball devices on a Joystick. These devices work + similar to a mouse but they have no absolute position; they only have + relative amounts of movement. + + The ``pygame.JOYBALLMOTION`` event will be sent when the trackball is + rolled. It will report the amount of movement on the trackball. + + .. ## Joystick.get_numballs ## + + .. method:: get_ball + + | :sl:`get the relative position of a trackball` + | :sg:`get_ball(ball_number) -> x, y` + + Returns the relative movement of a joystick button. The value is a ``x, y`` + pair holding the relative movement since the last call to get_ball. + + The ball number must be an integer from ``0`` to ``get_numballs() - 1``. + + .. ## Joystick.get_ball ## + + .. method:: get_numbuttons + + | :sl:`get the number of buttons on a Joystick` + | :sg:`get_numbuttons() -> int` + + Returns the number of pushable buttons on the joystick. These buttons + have a boolean (on or off) state. + + Buttons generate a ``pygame.JOYBUTTONDOWN`` and ``pygame.JOYBUTTONUP`` + event when they are pressed and released. + + .. ## Joystick.get_numbuttons ## + + .. method:: get_button + + | :sl:`get the current button state` + | :sg:`get_button(button) -> bool` + + Returns the current state of a joystick button. + + .. ## Joystick.get_button ## + + .. method:: get_numhats + + | :sl:`get the number of hat controls on a Joystick` + | :sg:`get_numhats() -> int` + + Returns the number of joystick hats on a Joystick. Hat devices are like + miniature digital joysticks on a joystick. Each hat has two axes of + input. + + The ``pygame.JOYHATMOTION`` event is generated when the hat changes + position. The ``position`` attribute for the event contains a pair of + values that are either ``-1``, ``0``, or ``1``. A position of ``(0, 0)`` + means the hat is centered. + + .. ## Joystick.get_numhats ## + + .. method:: get_hat + + | :sl:`get the position of a joystick hat` + | :sg:`get_hat(hat_number) -> x, y` + + Returns the current position of a position hat. The position is given as + two values representing the ``x`` and ``y`` position for the hat. ``(0, 0)`` + means centered. A value of ``-1`` means left/down and a value of ``1`` means + right/up: so ``(-1, 0)`` means left; ``(1, 0)`` means right; ``(0, 1)`` means + up; ``(1, 1)`` means upper-right; etc. + + This value is digital, ``i.e.``, each coordinate can be ``-1``, ``0`` or ``1`` + but never in-between. + + The hat number must be between ``0`` and ``get_numhats() - 1``. + + .. ## Joystick.get_hat ## + + .. method:: rumble + + | :sl:`Start a rumbling effect` + | :sg:`rumble(low_frequency, high_frequency, duration) -> bool` + + Start a rumble effect on the joystick, with the specified strength ranging + from 0 to 1. Duration is length of the effect, in ms. Setting the duration + to 0 will play the effect until another one overwrites it or + :meth:`Joystick.stop_rumble` is called. If an effect is already + playing, then it will be overwritten. + + Returns True if the rumble was played successfully or False if the + joystick does not support it or :meth:`pygame.version.SDL` is below 2.0.9. + + .. versionadded:: 2.0.2 + + .. ## Joystick.rumble ## + + .. method:: stop_rumble + + | :sl:`Stop any rumble effect playing` + | :sg:`stop_rumble() -> None` + + Stops any rumble effect playing on the joystick. See + :meth:`Joystick.rumble` for more information. + + .. versionadded:: 2.0.2 + + .. ## Joystick.stop_rumble ## + + .. ## pygame.joystick.Joystick ## + +.. ## pygame.joystick ## + +.. figure:: code_examples/joystick_calls.png + :scale: 100 % + :alt: joystick module example + + Example code for joystick module. + +.. literalinclude:: code_examples/joystick_calls.py + +.. _controller-mappings: + + +**Common Controller Axis Mappings** + +Controller mappings are drawn from the underlying SDL library which pygame uses and they differ +between pygame 1 and pygame 2. Below are a couple of mappings for two popular game pads. + + +**Pygame 2** + +Axis and hat mappings are listed from -1 to +1. + + +**X-Box 360 Controller (name: "Xbox 360 Controller")** + +In pygame 2 the X360 controller mapping has 6 Axes, 11 buttons and 1 hat. + +* **Left Stick**:: + + Left -> Right - Axis 0 + Up -> Down - Axis 1 + +* **Right Stick**:: + + Left -> Right - Axis 3 + Up -> Down - Axis 4 + +* **Left Trigger**:: + + Out -> In - Axis 2 + +* **Right Trigger**:: + + Out -> In - Axis 5 + +* **Buttons**:: + + A Button - Button 0 + B Button - Button 1 + X Button - Button 2 + Y Button - Button 3 + Left Bumper - Button 4 + Right Bumper - Button 5 + Back Button - Button 6 + Start Button - Button 7 + L. Stick In - Button 8 + R. Stick In - Button 9 + Guide Button - Button 10 + +* **Hat/D-pad**:: + + Down -> Up - Y Axis + Left -> Right - X Axis + + +**Playstation 4 Controller (name: "PS4 Controller")** + +In pygame 2 the PS4 controller mapping has 6 Axes and 16 buttons. + +* **Left Stick**:: + + Left -> Right - Axis 0 + Up -> Down - Axis 1 + +* **Right Stick**:: + + Left -> Right - Axis 2 + Up -> Down - Axis 3 + +* **Left Trigger**:: + + Out -> In - Axis 4 + +* **Right Trigger**:: + + Out -> In - Axis 5 + +* **Buttons**:: + + Cross Button - Button 0 + Circle Button - Button 1 + Square Button - Button 2 + Triangle Button - Button 3 + Share Button - Button 4 + PS Button - Button 5 + Options Button - Button 6 + L. Stick In - Button 7 + R. Stick In - Button 8 + Left Bumper - Button 9 + Right Bumper - Button 10 + D-pad Up - Button 11 + D-pad Down - Button 12 + D-pad Left - Button 13 + D-pad Right - Button 14 + Touch Pad Click - Button 15 + +**Pygame 1** + +Axis and hat mappings are listed from -1 to +1. + + +**X-Box 360 Controller (name: "Controller (XBOX 360 For Windows)")** + +In pygame 1 the X360 controller mapping has 5 Axes, 10 buttons and 1 hat. + +* **Left Stick**:: + + Left -> Right - Axis 0 + Up -> Down - Axis 1 + +* **Right Stick**:: + + Left -> Right - Axis 4 + Up -> Down - Axis 3 + +* **Left Trigger & Right Trigger**:: + + RT -> LT - Axis 2 + +* **Buttons**:: + + A Button - Button 0 + B Button - Button 1 + X Button - Button 2 + Y Button - Button 3 + Left Bumper - Button 4 + Right Bumper - Button 5 + Back Button - Button 6 + Start Button - Button 7 + L. Stick In - Button 8 + R. Stick In - Button 9 + +* **Hat/D-pad**:: + + Down -> Up - Y Axis + Left -> Right - X Axis + + +**Playstation 4 Controller (name: "Wireless Controller")** + +In pygame 1 the PS4 controller mapping has 6 Axes and 14 buttons and 1 hat. + +* **Left Stick**:: + + Left -> Right - Axis 0 + Up -> Down - Axis 1 + +* **Right Stick**:: + + Left -> Right - Axis 2 + Up -> Down - Axis 3 + +* **Left Trigger**:: + + Out -> In - Axis 5 + +* **Right Trigger**:: + + Out -> In - Axis 4 + +* **Buttons**:: + + Cross Button - Button 0 + Circle Button - Button 1 + Square Button - Button 2 + Triangle Button - Button 3 + Left Bumper - Button 4 + Right Bumper - Button 5 + L. Trigger(Full)- Button 6 + R. Trigger(Full)- Button 7 + Share Button - Button 8 + Options Button - Button 9 + L. Stick In - Button 10 + R. Stick In - Button 11 + PS Button - Button 12 + Touch Pad Click - Button 13 + +* **Hat/D-pad**:: + + Down -> Up - Y Axis + Left -> Right - X Axis diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/key.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/key.rst.txt new file mode 100644 index 0000000..695ad0e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/key.rst.txt @@ -0,0 +1,430 @@ +.. include:: common.txt + +:mod:`pygame.key` +================= + +.. module:: pygame.key + :synopsis: pygame module to work with the keyboard + +| :sl:`pygame module to work with the keyboard` + +This module contains functions for dealing with the keyboard. + +The :mod:`pygame.event` queue gets ``pygame.KEYDOWN`` and ``pygame.KEYUP`` +events when the keyboard buttons are pressed and released. Both events have +``key`` and ``mod`` attributes. + + * ``key``: an :ref:`integer ID ` representing every key + on the keyboard + * ``mod``: a bitmask of all the :ref:`modifier keys ` + that were in a pressed state when the event occurred + +The ``pygame.KEYDOWN`` event has the additional attributes ``unicode`` and +``scancode``. + + * ``unicode``: a single character string that is the fully translated + character entered, this takes into account the shift and composition keys + * ``scancode``: the platform-specific key code, which could be different from + keyboard to keyboard, but is useful for key selection of weird keys like + the multimedia keys + +.. versionadded:: 2.0.0 + The ``pygame.TEXTINPUT`` event is preferred to the ``unicode`` attribute + of ``pygame.KEYDOWN``. The attribute ``text`` contains the input. + + +.. _key-constants-label: + +The following is a list of all the constants (from :mod:`pygame.locals`) used to +represent keyboard keys. + +Portability note: The integers for key constants differ between pygame 1 and 2. +Always use key constants (``K_a``) rather than integers directly (``97``) so +that your key handling code works well on both pygame 1 and pygame 2. + + +:: + + pygame + Constant ASCII Description + --------------------------------- + K_BACKSPACE \b backspace + K_TAB \t tab + K_CLEAR clear + K_RETURN \r return + K_PAUSE pause + K_ESCAPE ^[ escape + K_SPACE space + K_EXCLAIM ! exclaim + K_QUOTEDBL " quotedbl + K_HASH # hash + K_DOLLAR $ dollar + K_AMPERSAND & ampersand + K_QUOTE quote + K_LEFTPAREN ( left parenthesis + K_RIGHTPAREN ) right parenthesis + K_ASTERISK * asterisk + K_PLUS + plus sign + K_COMMA , comma + K_MINUS - minus sign + K_PERIOD . period + K_SLASH / forward slash + K_0 0 0 + K_1 1 1 + K_2 2 2 + K_3 3 3 + K_4 4 4 + K_5 5 5 + K_6 6 6 + K_7 7 7 + K_8 8 8 + K_9 9 9 + K_COLON : colon + K_SEMICOLON ; semicolon + K_LESS < less-than sign + K_EQUALS = equals sign + K_GREATER > greater-than sign + K_QUESTION ? question mark + K_AT @ at + K_LEFTBRACKET [ left bracket + K_BACKSLASH \ backslash + K_RIGHTBRACKET ] right bracket + K_CARET ^ caret + K_UNDERSCORE _ underscore + K_BACKQUOTE ` grave + K_a a a + K_b b b + K_c c c + K_d d d + K_e e e + K_f f f + K_g g g + K_h h h + K_i i i + K_j j j + K_k k k + K_l l l + K_m m m + K_n n n + K_o o o + K_p p p + K_q q q + K_r r r + K_s s s + K_t t t + K_u u u + K_v v v + K_w w w + K_x x x + K_y y y + K_z z z + K_DELETE delete + K_KP0 keypad 0 + K_KP1 keypad 1 + K_KP2 keypad 2 + K_KP3 keypad 3 + K_KP4 keypad 4 + K_KP5 keypad 5 + K_KP6 keypad 6 + K_KP7 keypad 7 + K_KP8 keypad 8 + K_KP9 keypad 9 + K_KP_PERIOD . keypad period + K_KP_DIVIDE / keypad divide + K_KP_MULTIPLY * keypad multiply + K_KP_MINUS - keypad minus + K_KP_PLUS + keypad plus + K_KP_ENTER \r keypad enter + K_KP_EQUALS = keypad equals + K_UP up arrow + K_DOWN down arrow + K_RIGHT right arrow + K_LEFT left arrow + K_INSERT insert + K_HOME home + K_END end + K_PAGEUP page up + K_PAGEDOWN page down + K_F1 F1 + K_F2 F2 + K_F3 F3 + K_F4 F4 + K_F5 F5 + K_F6 F6 + K_F7 F7 + K_F8 F8 + K_F9 F9 + K_F10 F10 + K_F11 F11 + K_F12 F12 + K_F13 F13 + K_F14 F14 + K_F15 F15 + K_NUMLOCK numlock + K_CAPSLOCK capslock + K_SCROLLOCK scrollock + K_RSHIFT right shift + K_LSHIFT left shift + K_RCTRL right control + K_LCTRL left control + K_RALT right alt + K_LALT left alt + K_RMETA right meta + K_LMETA left meta + K_LSUPER left Windows key + K_RSUPER right Windows key + K_MODE mode shift + K_HELP help + K_PRINT print screen + K_SYSREQ sysrq + K_BREAK break + K_MENU menu + K_POWER power + K_EURO Euro + K_AC_BACK Android back button + + +.. _key-modifiers-label: + +The keyboard also has a list of modifier states (from :mod:`pygame.locals`) that +can be assembled by bitwise-ORing them together. + +:: + + pygame + Constant Description + ------------------------- + KMOD_NONE no modifier keys pressed + KMOD_LSHIFT left shift + KMOD_RSHIFT right shift + KMOD_SHIFT left shift or right shift or both + KMOD_LCTRL left control + KMOD_RCTRL right control + KMOD_CTRL left control or right control or both + KMOD_LALT left alt + KMOD_RALT right alt + KMOD_ALT left alt or right alt or both + KMOD_LMETA left meta + KMOD_RMETA right meta + KMOD_META left meta or right meta or both + KMOD_CAPS caps lock + KMOD_NUM num lock + KMOD_MODE AltGr + + +The modifier information is contained in the ``mod`` attribute of the +``pygame.KEYDOWN`` and ``pygame.KEYUP`` events. The ``mod`` attribute is a +bitmask of all the modifier keys that were in a pressed state when the event +occurred. The modifier information can be decoded using a bitwise AND (except +for ``KMOD_NONE``, which should be compared using equals ``==``). For example: + +:: + + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP: + if event.mod == pygame.KMOD_NONE: + print('No modifier keys were in a pressed state when this ' + 'event occurred.') + else: + if event.mod & pygame.KMOD_LSHIFT: + print('Left shift was in a pressed state when this event ' + 'occurred.') + if event.mod & pygame.KMOD_RSHIFT: + print('Right shift was in a pressed state when this event ' + 'occurred.') + if event.mod & pygame.KMOD_SHIFT: + print('Left shift or right shift or both were in a ' + 'pressed state when this event occurred.') + + + +.. function:: get_focused + + | :sl:`true if the display is receiving keyboard input from the system` + | :sg:`get_focused() -> bool` + + Returns ``True`` when the display window has keyboard focus from the + system. If the display needs to ensure it does not lose keyboard focus, it + can use :func:`pygame.event.set_grab()` to grab all input. + + .. ## pygame.key.get_focused ## + +.. function:: get_pressed + + | :sl:`get the state of all keyboard buttons` + | :sg:`get_pressed() -> bools` + + Returns a sequence of boolean values representing the state of every key on + the keyboard. Use the key constant values to index the array. A ``True`` + value means that the button is pressed. + + .. note:: + Getting the list of pushed buttons with this function is not the proper + way to handle text entry from the user. There is no way to know the order + of keys pressed, and rapidly pushed keys can be completely unnoticed + between two calls to ``pygame.key.get_pressed()``. There is also no way to + translate these pushed keys into a fully translated character value. See + the ``pygame.KEYDOWN`` events on the :mod:`pygame.event` queue for this + functionality. + + .. ## pygame.key.get_pressed ## + +.. function:: get_mods + + | :sl:`determine which modifier keys are being held` + | :sg:`get_mods() -> int` + + Returns a single integer representing a bitmask of all the modifier keys + being held. Using bitwise operators you can test if specific + :ref:`modifier keys ` are pressed. + + .. ## pygame.key.get_mods ## + +.. function:: set_mods + + | :sl:`temporarily set which modifier keys are pressed` + | :sg:`set_mods(int) -> None` + + Create a bitmask of the :ref:`modifier key constants ` + you want to impose on your program. + + .. ## pygame.key.set_mods ## + +.. function:: set_repeat + + | :sl:`control how held keys are repeated` + | :sg:`set_repeat() -> None` + | :sg:`set_repeat(delay) -> None` + | :sg:`set_repeat(delay, interval) -> None` + + When the keyboard repeat is enabled, keys that are held down will generate + multiple ``pygame.KEYDOWN`` events. The ``delay`` parameter is the number of + milliseconds before the first repeated ``pygame.KEYDOWN`` event will be sent. + After that, another ``pygame.KEYDOWN`` event will be sent every ``interval`` + milliseconds. If a ``delay`` value is provided and an ``interval`` value is + not provided or is 0, then the ``interval`` will be set to the same value as + ``delay``. + + To disable key repeat call this function with no arguments or with ``delay`` + set to 0. + + When pygame is initialized the key repeat is disabled. + + :raises ValueError: if ``delay`` or ``interval`` is < 0 + + .. versionchanged:: 2.0.0 A ``ValueError`` is now raised (instead of a + ``pygame.error``) if ``delay`` or ``interval`` is < 0. + + .. ## pygame.key.set_repeat ## + +.. function:: get_repeat + + | :sl:`see how held keys are repeated` + | :sg:`get_repeat() -> (delay, interval)` + + Get the ``delay`` and ``interval`` keyboard repeat values. Refer to + :func:`pygame.key.set_repeat()` for a description of these values. + + .. versionadded:: 1.8 + + .. ## pygame.key.get_repeat ## + +.. function:: name + + | :sl:`get the name of a key identifier` + | :sg:`name(key) -> string` + + Get the descriptive name of the button from a keyboard button id constant. + + .. ## pygame.key.name ## + +.. function:: key_code + + | :sl:`get the key identifier from a key name` + | :sg:`key_code(name=string) -> int` + + Get the key identifier code from the descriptive name of the key. This + returns an integer matching one of the K_* keycodes. For example: + + :: + + >>> pygame.key.key_code("return") == pygame.K_RETURN + True + >>> pygame.key.key_code("0") == pygame.K_0 + True + >>> pygame.key.key_code("space") == pygame.K_SPACE + True + + :raises ValueError: if the key name is not known. + :raises NotImplementedError: if used with SDL 1. + + .. ## pygame.key.key_code ## + + .. versionadded:: 2.0.0 + + .. ## pygame.key.key_code ## + +.. function:: start_text_input + + | :sl:`start handling Unicode text input events` + | :sg:`start_text_input() -> None` + + Start receiving ``pygame.TEXTEDITING`` and ``pygame.TEXTINPUT`` + events. If applicable, show the on-screen keyboard or IME editor. + + For many languages, key presses will automatically generate a + corresponding ``pygame.TEXTINPUT`` event. Special keys like + escape or function keys, and certain key combinations will not + generate ``pygame.TEXTINPUT`` events. + + In other languages, entering a single symbol may require multiple + key presses, or a language-specific user interface. In this case, + ``pygame.TEXTINPUT`` events are preferable to ``pygame.KEYDOWN`` + events for text input. + + A ``pygame.TEXTEDITING`` event is received when an IME composition + is started or changed. It contains the composition ``text``, ``length``, + and editing ``start`` position within the composition (attributes + ``text``, ``length``, and ``start``, respectively). + When the composition is committed (or non-IME input is received), + a ``pygame.TEXTINPUT`` event is generated. + + Text input events handling is on by default. + + .. versionadded:: 2.0.0 + + .. ## pygame.key.start_text_input ## + +.. function:: stop_text_input + + | :sl:`stop handling Unicode text input events` + | :sg:`stop_text_input() -> None` + + Stop receiving ``pygame.TEXTEDITING`` and ``pygame.TEXTINPUT`` + events. If an on-screen keyboard or IME editor was shown with + ``pygame.key.start_text_input()``, hide it again. + + Text input events handling is on by default. + + To avoid triggering the IME editor or the on-screen keyboard + when the user is holding down a key during gameplay, text input + should be disabled once text entry is finished, or when the user + clicks outside of a text box. + + .. versionadded:: 2.0.0 + + .. ## pygame.key.stop_text_input ## + +.. function:: set_text_input_rect + + | :sl:`controls the position of the candidate list` + | :sg:`set_text_input_rect(Rect) -> None` + + This sets the rectangle used for typing with an IME. + It controls where the candidate list will open, if supported. + + .. versionadded:: 2.0.0 + + .. ## pygame.key.set_text_input_rect ## + +.. ## pygame.key ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/locals.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/locals.rst.txt new file mode 100644 index 0000000..091dbaa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/locals.rst.txt @@ -0,0 +1,27 @@ +.. include:: common.txt + +:mod:`pygame.locals` +==================== + +.. module:: pygame.locals + :synopsis: pygame constants + +| :sl:`pygame constants` + +This module contains various constants used by pygame. Its contents are +automatically placed in the pygame module namespace. However, an application +can use ``pygame.locals`` to include only the pygame constants with a ``from +pygame.locals import *``. + +Detailed descriptions of the various constants can be found throughout the +pygame documentation. Here are the locations of some of them. + + - The :mod:`pygame.display` module contains flags like ``FULLSCREEN`` used + by :func:`pygame.display.set_mode`. + - The :mod:`pygame.event` module contains the various event types. + - The :mod:`pygame.key` module lists the keyboard constants and modifiers + (``K_``\* and ``MOD_``\*) relating to the ``key`` and ``mod`` attributes of + the ``KEYDOWN`` and ``KEYUP`` events. + - The :mod:`pygame.time` module defines ``TIMER_RESOLUTION``. + +.. ## pygame.locals ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mask.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mask.rst.txt new file mode 100644 index 0000000..f4365cf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mask.rst.txt @@ -0,0 +1,642 @@ +.. include:: common.txt + +:mod:`pygame.mask` +================== + +.. module:: pygame.mask + :synopsis: pygame module for image masks. + +| :sl:`pygame module for image masks.` + +Useful for fast pixel perfect collision detection. A mask uses 1 bit per-pixel +to store which parts collide. + +.. versionadded:: 1.8 + +.. versionchanged:: 2.0.2 Mask functions now support keyword arguments. + +.. versionchanged:: 2.0.2 Mask functions that take positions or offsets now + support :class:`pygame.math.Vector2` arguments. + + +.. function:: from_surface + + | :sl:`Creates a Mask from the given surface` + | :sg:`from_surface(surface) -> Mask` + | :sg:`from_surface(surface, threshold=127) -> Mask` + + Creates a :class:`Mask` object from the given surface by setting all the + opaque pixels and not setting the transparent pixels. + + If the surface uses a color-key, then it is used to decide which bits in + the resulting mask are set. All the pixels that are **not** equal to the + color-key are **set** and the pixels equal to the color-key are not set. + + If a color-key is not used, then the alpha value of each pixel is used to + decide which bits in the resulting mask are set. All the pixels that have an + alpha value **greater than** the ``threshold`` parameter are **set** and the + pixels with an alpha value less than or equal to the ``threshold`` are + not set. + + :param Surface surface: the surface to create the mask from + :param int threshold: (optional) the alpha threshold (default is 127) to + compare with each surface pixel's alpha value, if the ``surface`` is + color-keyed this parameter is ignored + + :returns: a newly created :class:`Mask` object from the given surface + :rtype: Mask + + .. note:: + This function is used to create the masks for + :func:`pygame.sprite.collide_mask`. + + .. ## pygame.mask.from_surface ## + +.. function:: from_threshold + + | :sl:`Creates a mask by thresholding Surfaces` + | :sg:`from_threshold(surface, color) -> Mask` + | :sg:`from_threshold(surface, color, threshold=(0, 0, 0, 255), othersurface=None, palette_colors=1) -> Mask` + + This is a more featureful method of getting a :class:`Mask` from a surface. + + If the optional ``othersurface`` is not used, all the pixels **within** the + ``threshold`` of the ``color`` parameter are **set** in the resulting mask. + + If the optional ``othersurface`` is used, every pixel in the first surface + that is **within** the ``threshold`` of the corresponding pixel in + ``othersurface`` is **set** in the resulting mask. + + :param Surface surface: the surface to create the mask from + :param color: color used to check if the surface's pixels are within the + given ``threshold`` range, this parameter is ignored if the optional + ``othersurface`` parameter is supplied + :type color: Color or int or tuple(int, int, int, [int]) or list[int, int, int, [int]] + :param threshold: (optional) the threshold range used to check the difference + between two colors (default is ``(0, 0, 0, 255)``) + :type threshold: Color or int or tuple(int, int, int, [int]) or list[int, int, int, [int]] + :param Surface othersurface: (optional) used to check whether the pixels of + the first surface are within the given ``threshold`` range of the pixels + from this surface (default is ``None``) + :param int palette_colors: (optional) indicates whether to use the palette + colors or not, a nonzero value causes the palette colors to be used and a + 0 causes them not to be used (default is 1) + + :returns: a newly created :class:`Mask` object from the given surface + :rtype: Mask + + .. ## pygame.mask.from_threshold ## + +.. class:: Mask + + | :sl:`pygame object for representing 2D bitmasks` + | :sg:`Mask(size=(width, height)) -> Mask` + | :sg:`Mask(size=(width, height), fill=False) -> Mask` + + A ``Mask`` object is used to represent a 2D bitmask. Each bit in + the mask represents a pixel. 1 is used to indicate a set bit and 0 is used + to indicate an unset bit. Set bits in a mask can be used to detect collisions + with other masks and their set bits. + + A filled mask has all of its bits set to 1, conversely an + unfilled/cleared/empty mask has all of its bits set to 0. Masks can be + created unfilled (default) or filled by using the ``fill`` parameter. Masks + can also be cleared or filled using the :func:`pygame.mask.Mask.clear()` and + :func:`pygame.mask.Mask.fill()` methods respectively. + + A mask's coordinates start in the top left corner at ``(0, 0)`` just like + :mod:`pygame.Surface`. Individual bits can be accessed using the + :func:`pygame.mask.Mask.get_at()` and :func:`pygame.mask.Mask.set_at()` + methods. + + .. _mask-offset-label: + + The methods :meth:`overlap`, :meth:`overlap_area`, :meth:`overlap_mask`, + :meth:`draw`, :meth:`erase`, and :meth:`convolve` use an offset parameter + to indicate the offset of another mask's top left corner from the calling + mask's top left corner. The calling mask's top left corner is considered to + be the origin ``(0, 0)``. Offsets are a sequence of two values + ``(x_offset, y_offset)``. Positive and negative offset values are supported. + + :: + + 0 to x (x_offset) + : : + 0 ..... +----:---------+ + to | : | + y .......... +-----------+ + (y_offset) | | othermask | + | +-----------+ + | calling_mask | + +--------------+ + + :param size: the dimensions of the mask (width and height) + :param bool fill: (optional) create an unfilled mask (default: ``False``) or + filled mask (``True``) + + :returns: a newly created :class:`Mask` object + :rtype: Mask + + .. versionchanged:: 2.0.0 + Shallow copy support added. The :class:`Mask` class supports the special + method ``__copy__()`` and shallow copying via ``copy.copy(mask)``. + .. versionchanged:: 2.0.0 Subclassing support added. The :class:`Mask` class + can be used as a base class. + .. versionchanged:: 1.9.5 Added support for keyword arguments. + .. versionchanged:: 1.9.5 Added the optional keyword parameter ``fill``. + .. versionchanged:: 1.9.5 Added support for masks with a width and/or a + height of 0. + + .. method:: copy + + | :sl:`Returns a new copy of the mask` + | :sg:`copy() -> Mask` + + :returns: a new copy of this mask, the new mask will have the same width, + height, and set/unset bits as the original + :rtype: Mask + + .. note:: + If a mask subclass needs to copy any instance specific attributes + then it should override the ``__copy__()`` method. The overridden + ``__copy__()`` method needs to call ``super().__copy__()`` and then + copy the required data as in the following example code. + + :: + + class SubMask(pygame.mask.Mask): + def __copy__(self): + new_mask = super().__copy__() + # Do any SubMask attribute copying here. + return new_mask + + .. versionadded:: 2.0.0 + + .. ## Mask.copy ## + + .. method:: get_size + + | :sl:`Returns the size of the mask` + | :sg:`get_size() -> (width, height)` + + :returns: the size of the mask, (width, height) + :rtype: tuple(int, int) + + .. ## Mask.get_size ## + + .. method:: get_rect + + | :sl:`Returns a Rect based on the size of the mask` + | :sg:`get_rect(\**kwargs) -> Rect` + + Returns a new :func:`pygame.Rect` object based on the size of this mask. + The rect's default position will be ``(0, 0)`` and its default width and + height will be the same as this mask's. The rect's attributes can be + altered via :func:`pygame.Rect` attribute keyword arguments/values passed + into this method. As an example, ``a_mask.get_rect(center=(10, 5))`` would + create a :func:`pygame.Rect` based on the mask's size centered at the + given position. + + :param dict kwargs: :func:`pygame.Rect` attribute keyword arguments/values + that will be applied to the rect + + :returns: a new :func:`pygame.Rect` object based on the size of this mask + with any :func:`pygame.Rect` attribute keyword arguments/values applied + to it + :rtype: Rect + + .. versionadded:: 2.0.0 + + .. ## Mask.get_rect ## + + .. method:: get_at + + | :sl:`Gets the bit at the given position` + | :sg:`get_at(pos) -> int` + + :param pos: the position of the bit to get (x, y) + + :returns: 1 if the bit is set, 0 if the bit is not set + :rtype: int + + :raises IndexError: if the position is outside of the mask's bounds + + .. ## Mask.get_at ## + + .. method:: set_at + + | :sl:`Sets the bit at the given position` + | :sg:`set_at(pos) -> None` + | :sg:`set_at(pos, value=1) -> None` + + :param pos: the position of the bit to set (x, y) + :param int value: any nonzero int will set the bit to 1, 0 will set the + bit to 0 (default is 1) + + :returns: ``None`` + :rtype: NoneType + + :raises IndexError: if the position is outside of the mask's bounds + + .. ## Mask.set_at ## + + .. method:: overlap + + | :sl:`Returns the point of intersection` + | :sg:`overlap(other, offset) -> (x, y)` + | :sg:`overlap(other, offset) -> None` + + Returns the first point of intersection encountered between this mask and + ``other``. A point of intersection is 2 overlapping set bits. + + The current algorithm searches the overlapping area in + ``sizeof(unsigned long int) * CHAR_BIT`` bit wide column blocks (the value + of ``sizeof(unsigned long int) * CHAR_BIT`` is platform dependent, for + clarity it will be referred to as ``W``). Starting at the top left corner + it checks bits 0 to ``W - 1`` of the first row (``(0, 0)`` to + ``(W - 1, 0)``) then continues to the next row (``(0, 1)`` to + ``(W - 1, 1)``). Once this entire column block is checked, it continues to + the next one (``W`` to ``2 * W - 1``). This is repeated until it finds a + point of intersection or the entire overlapping area is checked. + + :param Mask other: the other mask to overlap with this mask + :param offset: the offset of ``other`` from this mask, for more + details refer to the :ref:`Mask offset notes ` + + :returns: point of intersection or ``None`` if no intersection + :rtype: tuple(int, int) or NoneType + + .. ## Mask.overlap ## + + .. method:: overlap_area + + | :sl:`Returns the number of overlapping set bits` + | :sg:`overlap_area(other, offset) -> numbits` + + Returns the number of overlapping set bits between between this mask and + ``other``. + + This can be useful for collision detection. An approximate collision + normal can be found by calculating the gradient of the overlapping area + through the finite difference. + + :: + + dx = mask.overlap_area(other, (x + 1, y)) - mask.overlap_area(other, (x - 1, y)) + dy = mask.overlap_area(other, (x, y + 1)) - mask.overlap_area(other, (x, y - 1)) + + :param Mask other: the other mask to overlap with this mask + :param offset: the offset of ``other`` from this mask, for more + details refer to the :ref:`Mask offset notes ` + + :returns: the number of overlapping set bits + :rtype: int + + .. ## Mask.overlap_area ## + + .. method:: overlap_mask + + | :sl:`Returns a mask of the overlapping set bits` + | :sg:`overlap_mask(other, offset) -> Mask` + + Returns a :class:`Mask`, the same size as this mask, containing the + overlapping set bits between this mask and ``other``. + + :param Mask other: the other mask to overlap with this mask + :param offset: the offset of ``other`` from this mask, for more + details refer to the :ref:`Mask offset notes ` + + :returns: a newly created :class:`Mask` with the overlapping bits set + :rtype: Mask + + .. ## Mask.overlap_mask ## + + .. method:: fill + + | :sl:`Sets all bits to 1` + | :sg:`fill() -> None` + + Sets all bits in the mask to 1. + + :returns: ``None`` + :rtype: NoneType + + .. ## Mask.fill ## + + .. method:: clear + + | :sl:`Sets all bits to 0` + | :sg:`clear() -> None` + + Sets all bits in the mask to 0. + + :returns: ``None`` + :rtype: NoneType + + .. ## Mask.clear ## + + .. method:: invert + + | :sl:`Flips all the bits` + | :sg:`invert() -> None` + + Flips all of the bits in the mask. All the set bits are cleared to 0 and + all the unset bits are set to 1. + + :returns: ``None`` + :rtype: NoneType + + .. ## Mask.invert ## + + .. method:: scale + + | :sl:`Resizes a mask` + | :sg:`scale((width, height)) -> Mask` + + Creates a new :class:`Mask` of the requested size with its bits scaled + from this mask. + + :param size: the width and height (size) of the mask to create + + :returns: a new :class:`Mask` object with its bits scaled from this mask + :rtype: Mask + + :raises ValueError: if ``width < 0`` or ``height < 0`` + + .. ## Mask.scale ## + + .. method:: draw + + | :sl:`Draws a mask onto another` + | :sg:`draw(other, offset) -> None` + + Performs a bitwise OR, drawing ``othermask`` onto this mask. + + :param Mask other: the mask to draw onto this mask + :param offset: the offset of ``other`` from this mask, for more + details refer to the :ref:`Mask offset notes ` + + :returns: ``None`` + :rtype: NoneType + + .. ## Mask.draw ## + + .. method:: erase + + | :sl:`Erases a mask from another` + | :sg:`erase(other, offset) -> None` + + Erases (clears) all bits set in ``other`` from this mask. + + :param Mask other: the mask to erase from this mask + :param offset: the offset of ``other`` from this mask, for more + details refer to the :ref:`Mask offset notes ` + + :returns: ``None`` + :rtype: NoneType + + .. ## Mask.erase ## + + .. method:: count + + | :sl:`Returns the number of set bits` + | :sg:`count() -> bits` + + :returns: the number of set bits in the mask + :rtype: int + + .. ## Mask.count ## + + .. method:: centroid + + | :sl:`Returns the centroid of the set bits` + | :sg:`centroid() -> (x, y)` + + Finds the centroid (the center mass of the set bits) for this mask. + + :returns: a coordinate tuple indicating the centroid of the mask, it will + return ``(0, 0)`` if the mask has no bits set + :rtype: tuple(int, int) + + .. ## Mask.centroid ## + + .. method:: angle + + | :sl:`Returns the orientation of the set bits` + | :sg:`angle() -> theta` + + Finds the approximate orientation (from -90 to 90 degrees) of the set bits + in the mask. This works best if performed on a mask with only one + connected component. + + :returns: the orientation of the set bits in the mask, it will return + ``0.0`` if the mask has no bits set + :rtype: float + + .. note:: + See :meth:`connected_component` for details on how a connected + component is calculated. + + .. ## Mask.angle ## + + .. method:: outline + + | :sl:`Returns a list of points outlining an object` + | :sg:`outline() -> [(x, y), ...]` + | :sg:`outline(every=1) -> [(x, y), ...]` + + Returns a list of points of the outline of the first connected component + encountered in the mask. To find a connected component, the mask is + searched per row (left to right) starting in the top left corner. + + The ``every`` optional parameter skips set bits in the outline. For + example, setting it to 10 would return a list of every 10th set bit in the + outline. + + :param int every: (optional) indicates the number of bits to skip over in + the outline (default is 1) + + :returns: a list of points outlining the first connected component + encountered, an empty list is returned if the mask has no bits set + :rtype: list[tuple(int, int)] + + .. note:: + See :meth:`connected_component` for details on how a connected + component is calculated. + + .. ## Mask.outline ## + + .. method:: convolve + + | :sl:`Returns the convolution of this mask with another mask` + | :sg:`convolve(other) -> Mask` + | :sg:`convolve(other, output=None, offset=(0, 0)) -> Mask` + + Convolve this mask with the given ``other`` Mask. + + :param Mask other: mask to convolve this mask with + :param output: (optional) mask for output (default is ``None``) + :type output: Mask or NoneType + :param offset: the offset of ``other`` from this mask, (default is + ``(0, 0)``) + + :returns: a :class:`Mask` with the ``(i - offset[0], j - offset[1])`` bit + set, if shifting ``other`` (such that its bottom right corner is at + ``(i, j)``) causes it to overlap with this mask + + If an ``output`` Mask is specified, the output is drawn onto it and + it is returned. Otherwise a mask of size ``(MAX(0, width + other mask's + width - 1), MAX(0, height + other mask's height - 1))`` is created and + returned. + :rtype: Mask + + .. ## Mask.convolve ## + + .. method:: connected_component + + | :sl:`Returns a mask containing a connected component` + | :sg:`connected_component() -> Mask` + | :sg:`connected_component(pos) -> Mask` + + A connected component is a group (1 or more) of connected set bits + (orthogonally and diagonally). The SAUF algorithm, which checks 8 point + connectivity, is used to find a connected component in the mask. + + By default this method will return a :class:`Mask` containing the largest + connected component in the mask. Optionally, a bit coordinate can be + specified and the connected component containing it will be returned. If + the bit at the given location is not set, the returned :class:`Mask` will + be empty (no bits set). + + :param pos: (optional) selects the connected component that contains the + bit at this position + + :returns: a :class:`Mask` object (same size as this mask) with the largest + connected component from this mask, if this mask has no bits set then + an empty mask will be returned + + If the ``pos`` parameter is provided then the mask returned will have + the connected component that contains this position. An empty mask will + be returned if the ``pos`` parameter selects an unset bit. + :rtype: Mask + + :raises IndexError: if the optional ``pos`` parameter is outside of the + mask's bounds + + .. ## Mask.connected_component ## + + .. method:: connected_components + + | :sl:`Returns a list of masks of connected components` + | :sg:`connected_components() -> [Mask, ...]` + | :sg:`connected_components(minimum=0) -> [Mask, ...]` + + Provides a list containing a :class:`Mask` object for each connected + component. + + :param int minimum: (optional) indicates the minimum number of bits (to + filter out noise) per connected component (default is 0, which equates + to no minimum and is equivalent to setting it to 1, as a connected + component must have at least 1 bit set) + + :returns: a list containing a :class:`Mask` object for each connected + component, an empty list is returned if the mask has no bits set + :rtype: list[Mask] + + .. note:: + See :meth:`connected_component` for details on how a connected + component is calculated. + + .. ## Mask.connected_components ## + + .. method:: get_bounding_rects + + | :sl:`Returns a list of bounding rects of connected components` + | :sg:`get_bounding_rects() -> [Rect, ...]` + + Provides a list containing a bounding rect for each connected component. + + :returns: a list containing a bounding rect for each connected component, + an empty list is returned if the mask has no bits set + :rtype: list[Rect] + + .. note:: + See :meth:`connected_component` for details on how a connected + component is calculated. + + .. ## Mask.get_bounding_rects ## + + .. method:: to_surface + + | :sl:`Returns a surface with the mask drawn on it` + | :sg:`to_surface() -> Surface` + | :sg:`to_surface(surface=None, setsurface=None, unsetsurface=None, setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 255), dest=(0, 0)) -> Surface` + + Draws this mask on the given surface. Set bits (bits set to 1) and unset + bits (bits set to 0) can be drawn onto a surface. + + :param surface: (optional) Surface to draw mask onto, if no surface is + provided one will be created (default is ``None``, which will cause a + surface with the parameters + ``Surface(size=mask.get_size(), flags=SRCALPHA, depth=32)`` to be + created, drawn on, and returned) + :type surface: Surface or None + :param setsurface: (optional) use this surface's color values to draw + set bits (default is ``None``), if this surface is smaller than the + mask any bits outside its bounds will use the ``setcolor`` value + :type setsurface: Surface or None + :param unsetsurface: (optional) use this surface's color values to draw + unset bits (default is ``None``), if this surface is smaller than the + mask any bits outside its bounds will use the ``unsetcolor`` value + :type unsetsurface: Surface or None + :param setcolor: (optional) color to draw set bits (default is + ``(255, 255, 255, 255)``, white), use ``None`` to skip drawing the set + bits, the ``setsurface`` parameter (if set) will takes precedence over + this parameter + :type setcolor: Color or str or int or tuple(int, int, int, [int]) or + list(int, int, int, [int]) or None + :param unsetcolor: (optional) color to draw unset bits (default is + ``(0, 0, 0, 255)``, black), use ``None`` to skip drawing the unset + bits, the ``unsetsurface`` parameter (if set) will takes precedence + over this parameter + :type unsetcolor: Color or str or int or tuple(int, int, int, [int]) or + list(int, int, int, [int]) or None + :param dest: (optional) surface destination of where to position the + topleft corner of the mask being drawn (default is ``(0, 0)``), if a + Rect is used as the ``dest`` parameter, its ``x`` and ``y`` attributes + will be used as the destination, **NOTE1:** rects with a negative width + or height value will not be normalized before using their ``x`` and + ``y`` values, **NOTE2:** this destination value is only used to + position the mask on the surface, it does not offset the ``setsurface`` + and ``unsetsurface`` from the mask, they are always aligned with the + mask (i.e. position ``(0, 0)`` on the mask always corresponds to + position ``(0, 0)`` on the ``setsurface`` and ``unsetsurface``) + :type dest: Rect or tuple(int, int) or list(int, int) or Vector2(int, int) + + :returns: the ``surface`` parameter (or a newly created surface if no + ``surface`` parameter was provided) with this mask drawn on it + :rtype: Surface + + :raises ValueError: if the ``setsurface`` parameter or ``unsetsurface`` + parameter does not have the same format (bytesize/bitsize/alpha) as + the ``surface`` parameter + + .. note :: + To skip drawing the set bits, both ``setsurface`` and ``setcolor`` must + be ``None``. The ``setsurface`` parameter defaults to ``None``, but + ``setcolor`` defaults to a color value and therefore must be set to + ``None``. + + .. note :: + To skip drawing the unset bits, both ``unsetsurface`` and + ``unsetcolor`` must be ``None``. The ``unsetsurface`` parameter + defaults to ``None``, but ``unsetcolor`` defaults to a color value and + therefore must be set to ``None``. + + .. versionadded:: 2.0.0 + + .. ## Mask.to_surface ## + + .. ## pygame.mask.Mask ## + +.. ## pygame.mask ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/math.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/math.rst.txt new file mode 100644 index 0000000..865803c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/math.rst.txt @@ -0,0 +1,880 @@ +.. include:: common.txt + +:mod:`pygame.math` +================== + +.. module:: pygame.math + :synopsis: pygame module for vector classes + +| :sl:`pygame module for vector classes` + +The pygame math module currently provides Vector classes in two and three +dimensions, ``Vector2`` and ``Vector3`` respectively. + +They support the following numerical operations: ``vec+vec``, ``vec-vec``, +``vec*number``, ``number*vec``, ``vec/number``, ``vec//number``, ``vec+=vec``, +``vec-=vec``, ``vec*=number``, ``vec/=number``, ``vec//=number``. + +All these operations will be performed elementwise. +In addition ``vec*vec`` will perform a scalar-product (a.k.a. dot-product). +If you want to multiply every element from vector v with every element from +vector w you can use the elementwise method: ``v.elementwise() * w`` + +The coordinates of a vector can be retrieved or set using attributes or +subscripts + +:: + + v = pygame.Vector3() + + v.x = 5 + v[1] = 2 * v.x + print(v[1]) # 10 + + v.x == v[0] + v.y == v[1] + v.z == v[2] + +Multiple coordinates can be set using slices or swizzling + +:: + + v = pygame.Vector2() + v.xy = 1, 2 + v[:] = 1, 2 + +.. versionadded:: 1.9.2pre +.. versionchanged:: 1.9.4 Removed experimental notice. +.. versionchanged:: 1.9.4 Allow scalar construction like GLSL Vector2(2) == Vector2(2.0, 2.0) +.. versionchanged:: 1.9.4 :mod:`pygame.math` required import. More convenient ``pygame.Vector2`` and ``pygame.Vector3``. + +.. class:: Vector2 + + | :sl:`a 2-Dimensional Vector` + | :sg:`Vector2() -> Vector2` + | :sg:`Vector2(int) -> Vector2` + | :sg:`Vector2(float) -> Vector2` + | :sg:`Vector2(Vector2) -> Vector2` + | :sg:`Vector2(x, y) -> Vector2` + | :sg:`Vector2((x, y)) -> Vector2` + + Some general information about the ``Vector2`` class. + + .. method:: dot + + | :sl:`calculates the dot- or scalar-product with the other vector` + | :sg:`dot(Vector2) -> float` + + .. ## Vector2.dot ## + + .. method:: cross + + | :sl:`calculates the cross- or vector-product` + | :sg:`cross(Vector2) -> Vector2` + + calculates the third component of the cross-product. + + .. ## Vector2.cross ## + + .. method:: magnitude + + | :sl:`returns the Euclidean magnitude of the vector.` + | :sg:`magnitude() -> float` + + calculates the magnitude of the vector which follows from the + theorem: ``vec.magnitude() == math.sqrt(vec.x**2 + vec.y**2)`` + + .. ## Vector2.magnitude ## + + .. method:: magnitude_squared + + | :sl:`returns the squared magnitude of the vector.` + | :sg:`magnitude_squared() -> float` + + calculates the magnitude of the vector which follows from the + theorem: ``vec.magnitude_squared() == vec.x**2 + vec.y**2``. This + is faster than ``vec.magnitude()`` because it avoids the square root. + + .. ## Vector2.magnitude_squared ## + + .. method:: length + + | :sl:`returns the Euclidean length of the vector.` + | :sg:`length() -> float` + + calculates the Euclidean length of the vector which follows from the + Pythagorean theorem: ``vec.length() == math.sqrt(vec.x**2 + vec.y**2)`` + + .. ## Vector2.length ## + + .. method:: length_squared + + | :sl:`returns the squared Euclidean length of the vector.` + | :sg:`length_squared() -> float` + + calculates the Euclidean length of the vector which follows from the + Pythagorean theorem: ``vec.length_squared() == vec.x**2 + vec.y**2``. + This is faster than ``vec.length()`` because it avoids the square root. + + .. ## Vector2.length_squared ## + + .. method:: normalize + + | :sl:`returns a vector with the same direction but length 1.` + | :sg:`normalize() -> Vector2` + + Returns a new vector that has ``length`` equal to ``1`` and the same + direction as self. + + .. ## Vector2.normalize ## + + .. method:: normalize_ip + + | :sl:`normalizes the vector in place so that its length is 1.` + | :sg:`normalize_ip() -> None` + + Normalizes the vector so that it has ``length`` equal to ``1``. + The direction of the vector is not changed. + + .. ## Vector2.normalize_ip ## + + .. method:: is_normalized + + | :sl:`tests if the vector is normalized i.e. has length == 1.` + | :sg:`is_normalized() -> Bool` + + Returns True if the vector has ``length`` equal to ``1``. Otherwise + it returns ``False``. + + .. ## Vector2.is_normalized ## + + .. method:: scale_to_length + + | :sl:`scales the vector to a given length.` + | :sg:`scale_to_length(float) -> None` + + Scales the vector so that it has the given length. The direction of the + vector is not changed. You can also scale to length ``0``. If the vector + is the zero vector (i.e. has length ``0`` thus no direction) a + ``ValueError`` is raised. + + .. ## Vector2.scale_to_length ## + + .. method:: reflect + + | :sl:`returns a vector reflected of a given normal.` + | :sg:`reflect(Vector2) -> Vector2` + + Returns a new vector that points in the direction as if self would bounce + of a surface characterized by the given surface normal. The length of the + new vector is the same as self's. + + .. ## Vector2.reflect ## + + .. method:: reflect_ip + + | :sl:`reflect the vector of a given normal in place.` + | :sg:`reflect_ip(Vector2) -> None` + + Changes the direction of self as if it would have been reflected of a + surface with the given surface normal. + + .. ## Vector2.reflect_ip ## + + .. method:: distance_to + + | :sl:`calculates the Euclidean distance to a given vector.` + | :sg:`distance_to(Vector2) -> float` + + .. ## Vector2.distance_to ## + + .. method:: distance_squared_to + + | :sl:`calculates the squared Euclidean distance to a given vector.` + | :sg:`distance_squared_to(Vector2) -> float` + + .. ## Vector2.distance_squared_to ## + + .. method:: lerp + + | :sl:`returns a linear interpolation to the given vector.` + | :sg:`lerp(Vector2, float) -> Vector2` + + Returns a Vector which is a linear interpolation between self and the + given Vector. The second parameter determines how far between self and + other the result is going to be. It must be a value between ``0`` and ``1`` + where ``0`` means self and ``1`` means other will be returned. + + .. ## Vector2.lerp ## + + .. method:: slerp + + | :sl:`returns a spherical interpolation to the given vector.` + | :sg:`slerp(Vector2, float) -> Vector2` + + Calculates the spherical interpolation from self to the given Vector. The + second argument - often called t - must be in the range ``[-1, 1]``. It + parametrizes where - in between the two vectors - the result should be. + If a negative value is given the interpolation will not take the + complement of the shortest path. + + .. ## Vector2.slerp ## + + .. method:: elementwise + + | :sl:`The next operation will be performed elementwise.` + | :sg:`elementwise() -> VectorElementwiseProxy` + + Applies the following operation to each element of the vector. + + .. ## Vector2.elementwise ## + + .. method:: rotate + + | :sl:`rotates a vector by a given angle in degrees.` + | :sg:`rotate(angle) -> Vector2` + + Returns a vector which has the same length as self but is rotated + counterclockwise by the given angle in degrees. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector2.rotate ## + + .. method:: rotate_rad + + | :sl:`rotates a vector by a given angle in radians.` + | :sg:`rotate_rad(angle) -> Vector2` + + Returns a vector which has the same length as self but is rotated + counterclockwise by the given angle in radians. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.0.0 + + .. ## Vector2.rotate_rad ## + + .. method:: rotate_ip + + | :sl:`rotates the vector by a given angle in degrees in place.` + | :sg:`rotate_ip(angle) -> None` + + Rotates the vector counterclockwise by the given angle in degrees. The + length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector2.rotate_ip ## + + .. method:: rotate_ip_rad + + | :sl:`rotates the vector by a given angle in radians in place.` + | :sg:`rotate_ip_rad(angle) -> None` + + DEPRECATED: Use rotate_rad_ip() instead. + + .. versionadded:: 2.0.0 + .. deprecated:: 2.1.1 + + .. ## Vector2.rotate_rad_ip ## + + .. method:: rotate_rad_ip + + | :sl:`rotates the vector by a given angle in radians in place.` + | :sg:`rotate_rad_ip(angle) -> None` + + Rotates the vector counterclockwise by the given angle in radians. The + length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.1.1 + + .. ## Vector2.rotate_rad_ip ## + + .. method:: angle_to + + | :sl:`calculates the angle to a given vector in degrees.` + | :sg:`angle_to(Vector2) -> float` + + Returns the angle between self and the given vector. + + .. ## Vector2.angle_to ## + + .. method:: as_polar + + | :sl:`returns a tuple with radial distance and azimuthal angle.` + | :sg:`as_polar() -> (r, phi)` + + Returns a tuple ``(r, phi)`` where r is the radial distance, and phi + is the azimuthal angle. + + .. ## Vector2.as_polar ## + + .. method:: from_polar + + | :sl:`Sets x and y from a polar coordinates tuple.` + | :sg:`from_polar((r, phi)) -> None` + + Sets x and y from a tuple (r, phi) where r is the radial distance, and + phi is the azimuthal angle. + + .. ## Vector2.from_polar ## + + .. method:: project + + | :sl:`projects a vector onto another.` + | :sg:`project(Vector2) -> Vector2` + + Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). + For a more detailed explanation see `Wikipedia `_. + + .. versionadded:: 2.0.2 + + .. ## Vector2.project ## + + + .. method :: copy + + | :sl:`Returns a copy of itself.` + | :sg:`copy() -> Vector2` + + Returns a new Vector2 having the same dimensions. + + .. versionadded:: 2.1.1 + + .. ## Vector2.copy ## + + + .. method:: update + + | :sl:`Sets the coordinates of the vector.` + | :sg:`update() -> None` + | :sg:`update(int) -> None` + | :sg:`update(float) -> None` + | :sg:`update(Vector2) -> None` + | :sg:`update(x, y) -> None` + | :sg:`update((x, y)) -> None` + + Sets coordinates x and y in place. + + .. versionadded:: 1.9.5 + + .. ## Vector2.update ## + + .. ## pygame.math.Vector2 ## + +.. class:: Vector3 + + | :sl:`a 3-Dimensional Vector` + | :sg:`Vector3() -> Vector3` + | :sg:`Vector3(int) -> Vector3` + | :sg:`Vector3(float) -> Vector3` + | :sg:`Vector3(Vector3) -> Vector3` + | :sg:`Vector3(x, y, z) -> Vector3` + | :sg:`Vector3((x, y, z)) -> Vector3` + + Some general information about the Vector3 class. + + .. method:: dot + + | :sl:`calculates the dot- or scalar-product with the other vector` + | :sg:`dot(Vector3) -> float` + + .. ## Vector3.dot ## + + .. method:: cross + + | :sl:`calculates the cross- or vector-product` + | :sg:`cross(Vector3) -> Vector3` + + calculates the cross-product. + + .. ## Vector3.cross ## + + .. method:: magnitude + + | :sl:`returns the Euclidean magnitude of the vector.` + | :sg:`magnitude() -> float` + + calculates the magnitude of the vector which follows from the + theorem: ``vec.magnitude() == math.sqrt(vec.x**2 + vec.y**2 + vec.z**2)`` + + .. ## Vector3.magnitude ## + + .. method:: magnitude_squared + + | :sl:`returns the squared Euclidean magnitude of the vector.` + | :sg:`magnitude_squared() -> float` + + calculates the magnitude of the vector which follows from the + theorem: + ``vec.magnitude_squared() == vec.x**2 + vec.y**2 + vec.z**2``. + This is faster than ``vec.magnitude()`` because it avoids the + square root. + + .. ## Vector3.magnitude_squared ## + + .. method:: length + + | :sl:`returns the Euclidean length of the vector.` + | :sg:`length() -> float` + + calculates the Euclidean length of the vector which follows from the + Pythagorean theorem: + ``vec.length() == math.sqrt(vec.x**2 + vec.y**2 + vec.z**2)`` + + .. ## Vector3.length ## + + .. method:: length_squared + + | :sl:`returns the squared Euclidean length of the vector.` + | :sg:`length_squared() -> float` + + calculates the Euclidean length of the vector which follows from the + Pythagorean theorem: + ``vec.length_squared() == vec.x**2 + vec.y**2 + vec.z**2``. + This is faster than ``vec.length()`` because it avoids the square root. + + .. ## Vector3.length_squared ## + + .. method:: normalize + + | :sl:`returns a vector with the same direction but length 1.` + | :sg:`normalize() -> Vector3` + + Returns a new vector that has ``length`` equal to ``1`` and the same + direction as self. + + .. ## Vector3.normalize ## + + .. method:: normalize_ip + + | :sl:`normalizes the vector in place so that its length is 1.` + | :sg:`normalize_ip() -> None` + + Normalizes the vector so that it has ``length`` equal to ``1``. The + direction of the vector is not changed. + + .. ## Vector3.normalize_ip ## + + .. method:: is_normalized + + | :sl:`tests if the vector is normalized i.e. has length == 1.` + | :sg:`is_normalized() -> Bool` + + Returns True if the vector has ``length`` equal to ``1``. Otherwise it + returns ``False``. + + .. ## Vector3.is_normalized ## + + .. method:: scale_to_length + + | :sl:`scales the vector to a given length.` + | :sg:`scale_to_length(float) -> None` + + Scales the vector so that it has the given length. The direction of the + vector is not changed. You can also scale to length ``0``. If the vector + is the zero vector (i.e. has length ``0`` thus no direction) a + ``ValueError`` is raised. + + .. ## Vector3.scale_to_length ## + + .. method:: reflect + + | :sl:`returns a vector reflected of a given normal.` + | :sg:`reflect(Vector3) -> Vector3` + + Returns a new vector that points in the direction as if self would bounce + of a surface characterized by the given surface normal. The length of the + new vector is the same as self's. + + .. ## Vector3.reflect ## + + .. method:: reflect_ip + + | :sl:`reflect the vector of a given normal in place.` + | :sg:`reflect_ip(Vector3) -> None` + + Changes the direction of self as if it would have been reflected of a + surface with the given surface normal. + + .. ## Vector3.reflect_ip ## + + .. method:: distance_to + + | :sl:`calculates the Euclidean distance to a given vector.` + | :sg:`distance_to(Vector3) -> float` + + .. ## Vector3.distance_to ## + + .. method:: distance_squared_to + + | :sl:`calculates the squared Euclidean distance to a given vector.` + | :sg:`distance_squared_to(Vector3) -> float` + + .. ## Vector3.distance_squared_to ## + + .. method:: lerp + + | :sl:`returns a linear interpolation to the given vector.` + | :sg:`lerp(Vector3, float) -> Vector3` + + Returns a Vector which is a linear interpolation between self and the + given Vector. The second parameter determines how far between self an + other the result is going to be. It must be a value between ``0`` and + ``1``, where ``0`` means self and ``1`` means other will be returned. + + .. ## Vector3.lerp ## + + .. method:: slerp + + | :sl:`returns a spherical interpolation to the given vector.` + | :sg:`slerp(Vector3, float) -> Vector3` + + Calculates the spherical interpolation from self to the given Vector. The + second argument - often called t - must be in the range ``[-1, 1]``. It + parametrizes where - in between the two vectors - the result should be. + If a negative value is given the interpolation will not take the + complement of the shortest path. + + .. ## Vector3.slerp ## + + .. method:: elementwise + + | :sl:`The next operation will be performed elementwise.` + | :sg:`elementwise() -> VectorElementwiseProxy` + + Applies the following operation to each element of the vector. + + .. ## Vector3.elementwise ## + + .. method:: rotate + + | :sl:`rotates a vector by a given angle in degrees.` + | :sg:`rotate(angle, Vector3) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise by the given angle in degrees around the given axis. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate ## + + .. method:: rotate_rad + + | :sl:`rotates a vector by a given angle in radians.` + | :sg:`rotate_rad(angle, Vector3) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise by the given angle in radians around the given axis. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.0.0 + + .. ## Vector3.rotate_rad ## + + .. method:: rotate_ip + + | :sl:`rotates the vector by a given angle in degrees in place.` + | :sg:`rotate_ip(angle, Vector3) -> None` + + Rotates the vector counterclockwise around the given axis by the given + angle in degrees. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate_ip ## + + .. method:: rotate_ip_rad + + | :sl:`rotates the vector by a given angle in radians in place.` + | :sg:`rotate_ip_rad(angle, Vector3) -> None` + + DEPRECATED: Use rotate_rad_ip() instead. + + .. versionadded:: 2.0.0 + .. deprecated:: 2.1.1 + + .. ## Vector3.rotate_ip_rad ## + + .. method:: rotate_rad_ip + + | :sl:`rotates the vector by a given angle in radians in place.` + | :sg:`rotate_rad_ip(angle, Vector3) -> None` + + Rotates the vector counterclockwise around the given axis by the given + angle in radians. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.1.1 + + .. ## Vector3.rotate_rad_ip ## + + .. method:: rotate_x + + | :sl:`rotates a vector around the x-axis by the angle in degrees.` + | :sg:`rotate_x(angle) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise around the x-axis by the given angle in degrees. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate_x ## + + .. method:: rotate_x_rad + + | :sl:`rotates a vector around the x-axis by the angle in radians.` + | :sg:`rotate_x_rad(angle) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise around the x-axis by the given angle in radians. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.0.0 + + .. ## Vector3.rotate_x_rad ## + + .. method:: rotate_x_ip + + | :sl:`rotates the vector around the x-axis by the angle in degrees in place.` + | :sg:`rotate_x_ip(angle) -> None` + + Rotates the vector counterclockwise around the x-axis by the given angle + in degrees. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate_x_ip ## + + .. method:: rotate_x_ip_rad + + | :sl:`rotates the vector around the x-axis by the angle in radians in place.` + | :sg:`rotate_x_ip_rad(angle) -> None` + + DEPRECATED: Use rotate_x_rad_ip() instead. + + .. versionadded:: 2.0.0 + .. deprecated:: 2.1.1 + + .. ## Vector3.rotate_x_ip_rad ## + + .. method:: rotate_x_rad_ip + + | :sl:`rotates the vector around the x-axis by the angle in radians in place.` + | :sg:`rotate_x_rad_ip(angle) -> None` + + Rotates the vector counterclockwise around the x-axis by the given angle + in radians. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.1.1 + + .. ## Vector3.rotate_x_rad_ip ## + + .. method:: rotate_y + + | :sl:`rotates a vector around the y-axis by the angle in degrees.` + | :sg:`rotate_y(angle) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise around the y-axis by the given angle in degrees. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate_y ## + + .. method:: rotate_y_rad + + | :sl:`rotates a vector around the y-axis by the angle in radians.` + | :sg:`rotate_y_rad(angle) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise around the y-axis by the given angle in radians. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.0.0 + + .. ## Vector3.rotate_y_rad ## + + .. method:: rotate_y_ip + + | :sl:`rotates the vector around the y-axis by the angle in degrees in place.` + | :sg:`rotate_y_ip(angle) -> None` + + Rotates the vector counterclockwise around the y-axis by the given angle + in degrees. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate_y_ip ## + + .. method:: rotate_y_ip_rad + + | :sl:`rotates the vector around the y-axis by the angle in radians in place.` + | :sg:`rotate_y_ip_rad(angle) -> None` + + DEPRECATED: Use rotate_y_rad_ip() instead. + + .. versionadded:: 2.0.0 + .. deprecated:: 2.1.1 + + .. ## Vector3.rotate_y_ip_rad ## + + .. method:: rotate_y_rad_ip + + | :sl:`rotates the vector around the y-axis by the angle in radians in place.` + | :sg:`rotate_y_rad_ip(angle) -> None` + + Rotates the vector counterclockwise around the y-axis by the given angle + in radians. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.1.1 + + .. ## Vector3.rotate_y_rad_ip ## + + .. method:: rotate_z + + | :sl:`rotates a vector around the z-axis by the angle in degrees.` + | :sg:`rotate_z(angle) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise around the z-axis by the given angle in degrees. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate_z ## + + .. method:: rotate_z_rad + + | :sl:`rotates a vector around the z-axis by the angle in radians.` + | :sg:`rotate_z_rad(angle) -> Vector3` + + Returns a vector which has the same length as self but is rotated + counterclockwise around the z-axis by the given angle in radians. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.0.0 + + .. ## Vector3.rotate_z_rad ## + + .. method:: rotate_z_ip + + | :sl:`rotates the vector around the z-axis by the angle in degrees in place.` + | :sg:`rotate_z_ip(angle) -> None` + + Rotates the vector counterclockwise around the z-axis by the given angle + in degrees. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. ## Vector3.rotate_z_ip ## + + .. method:: rotate_z_ip_rad + + | :sl:`rotates the vector around the z-axis by the angle in radians in place.` + | :sg:`rotate_z_ip_rad(angle) -> None` + + DEPRECATED: Use rotate_z_rad_ip() instead. + + .. deprecated:: 2.1.1 + + .. ## Vector3.rotate_z_ip_rad ## + + .. method:: rotate_z_rad_ip + + | :sl:`rotates the vector around the z-axis by the angle in radians in place.` + | :sg:`rotate_z_rad_ip(angle) -> None` + + Rotates the vector counterclockwise around the z-axis by the given angle + in radians. The length of the vector is not changed. + (Note that due to pygame's inverted y coordinate system, the rotation + will look clockwise if displayed). + + .. versionadded:: 2.1.1 + + .. ## Vector3.rotate_z_rad_ip ## + + .. method:: angle_to + + | :sl:`calculates the angle to a given vector in degrees.` + | :sg:`angle_to(Vector3) -> float` + + Returns the angle between self and the given vector. + + .. ## Vector3.angle_to ## + + .. method:: as_spherical + + | :sl:`returns a tuple with radial distance, inclination and azimuthal angle.` + | :sg:`as_spherical() -> (r, theta, phi)` + + Returns a tuple ``(r, theta, phi)`` where r is the radial distance, theta is + the inclination angle and phi is the azimuthal angle. + + .. ## Vector3.as_spherical ## + + .. method:: from_spherical + + | :sl:`Sets x, y and z from a spherical coordinates 3-tuple.` + | :sg:`from_spherical((r, theta, phi)) -> None` + + Sets x, y and z from a tuple ``(r, theta, phi)`` where r is the radial + distance, theta is the inclination angle and phi is the azimuthal angle. + + .. ## Vector3.from_spherical ## + + .. method:: project + + | :sl:`projects a vector onto another.` + | :sg:`project(Vector3) -> Vector3` + + Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). + For a more detailed explanation see `Wikipedia `_. + + .. versionadded:: 2.0.2 + + .. ## Vector3.project ## + + .. method :: copy + + | :sl:`Returns a copy of itself.` + | :sg:`copy() -> Vector3` + + Returns a new Vector3 having the same dimensions. + + .. versionadded:: 2.1.1 + + .. ## Vector3.copy ## + + .. method:: update + + | :sl:`Sets the coordinates of the vector.` + | :sg:`update() -> None` + | :sg:`update(int) -> None` + | :sg:`update(float) -> None` + | :sg:`update(Vector3) -> None` + | :sg:`update(x, y, z) -> None` + | :sg:`update((x, y, z)) -> None` + + Sets coordinates x, y, and z in place. + + .. versionadded:: 1.9.5 + + .. ## Vector3.update ## + + .. ## ## + + .. ## pygame.math.Vector3 ## + +.. ## pygame.math ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/midi.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/midi.rst.txt new file mode 100644 index 0000000..edc9f25 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/midi.rst.txt @@ -0,0 +1,484 @@ +.. include:: common.txt + +:mod:`pygame.midi` +================== + +.. module:: pygame.midi + :synopsis: pygame module for interacting with midi input and output. + +| :sl:`pygame module for interacting with midi input and output.` + +.. versionadded:: 1.9.0 + +The midi module can send output to midi devices and get input from midi +devices. It can also list midi devices on the system. + +The midi module supports real and virtual midi devices. + +It uses the portmidi library. Is portable to which ever platforms portmidi +supports (currently Windows, Mac OS X, and Linux). + +This uses pyportmidi for now, but may use its own bindings at some point in the +future. The pyportmidi bindings are included with pygame. + +| + +.. versionadded:: 2.0.0 + +These are pygame events (:mod:`pygame.event`) reserved for midi use. The +``MIDIIN`` event is used by :func:`pygame.midi.midis2events` when converting +midi events to pygame events. + +:: + + MIDIIN + MIDIOUT + +| + +.. function:: init + + | :sl:`initialize the midi module` + | :sg:`init() -> None` + + Initializes the :mod:`pygame.midi` module. Must be called before using the + :mod:`pygame.midi` module. + + It is safe to call this more than once. + + .. ## pygame.midi.init ## + +.. function:: quit + + | :sl:`uninitialize the midi module` + | :sg:`quit() -> None` + + Uninitializes the :mod:`pygame.midi` module. If :func:`pygame.midi.init` was + called to initialize the :mod:`pygame.midi` module, then this function will + be called automatically when your program exits. + + It is safe to call this function more than once. + + .. ## pygame.midi.quit ## + +.. function:: get_init + + | :sl:`returns True if the midi module is currently initialized` + | :sg:`get_init() -> bool` + + Gets the initialization state of the :mod:`pygame.midi` module. + + :returns: ``True`` if the :mod:`pygame.midi` module is currently initialized. + :rtype: bool + + .. versionadded:: 1.9.5 + + .. ## pygame.midi.get_init ## + +.. class:: Input + + | :sl:`Input is used to get midi input from midi devices.` + | :sg:`Input(device_id) -> None` + | :sg:`Input(device_id, buffer_size) -> None` + + :param int device_id: midi device id + :param int buffer_size: (optional) the number of input events to be buffered + + .. method:: close + + | :sl:`closes a midi stream, flushing any pending buffers.` + | :sg:`close() -> None` + + PortMidi attempts to close open streams when the application exits. + + .. note:: This is particularly difficult under Windows. + + .. ## Input.close ## + + .. method:: poll + + | :sl:`returns True if there's data, or False if not.` + | :sg:`poll() -> bool` + + Used to indicate if any data exists. + + :returns: ``True`` if there is data, ``False`` otherwise + :rtype: bool + + :raises MidiException: on error + + .. ## Input.poll ## + + .. method:: read + + | :sl:`reads num_events midi events from the buffer.` + | :sg:`read(num_events) -> midi_event_list` + + Reads from the input buffer and gives back midi events. + + :param int num_events: number of input events to read + + :returns: the format for midi_event_list is + ``[[[status, data1, data2, data3], timestamp], ...]`` + :rtype: list + + .. ## Input.read ## + + .. ## pygame.midi.Input ## + +.. class:: Output + + | :sl:`Output is used to send midi to an output device` + | :sg:`Output(device_id) -> None` + | :sg:`Output(device_id, latency=0) -> None` + | :sg:`Output(device_id, buffer_size=256) -> None` + | :sg:`Output(device_id, latency, buffer_size) -> None` + + The ``buffer_size`` specifies the number of output events to be buffered + waiting for output. In some cases (see below) PortMidi does not buffer + output at all and merely passes data to a lower-level API, in which case + buffersize is ignored. + + ``latency`` is the delay in milliseconds applied to timestamps to determine + when the output should actually occur. If ``latency`` is <<0, 0 is assumed. + + If ``latency`` is zero, timestamps are ignored and all output is delivered + immediately. If ``latency`` is greater than zero, output is delayed until the + message timestamp plus the ``latency``. In some cases, PortMidi can obtain + better timing than your application by passing timestamps along to the + device driver or hardware. Latency may also help you to synchronize midi + data to audio data by matching midi latency to the audio buffer latency. + + .. note:: + Time is measured relative to the time source indicated by time_proc. + Timestamps are absolute, not relative delays or offsets. + + .. method:: abort + + | :sl:`terminates outgoing messages immediately` + | :sg:`abort() -> None` + + The caller should immediately close the output port; this call may result + in transmission of a partial midi message. There is no abort for Midi + input because the user can simply ignore messages in the buffer and close + an input device at any time. + + .. ## Output.abort ## + + .. method:: close + + | :sl:`closes a midi stream, flushing any pending buffers.` + | :sg:`close() -> None` + + PortMidi attempts to close open streams when the application exits. + + .. note:: This is particularly difficult under Windows. + + .. ## Output.close ## + + .. method:: note_off + + | :sl:`turns a midi note off (note must be on)` + | :sg:`note_off(note, velocity=None, channel=0) -> None` + + Turn a note off in the output stream. The note must already be on for + this to work correctly. + + .. ## Output.note_off ## + + .. method:: note_on + + | :sl:`turns a midi note on (note must be off)` + | :sg:`note_on(note, velocity=None, channel=0) -> None` + + Turn a note on in the output stream. The note must already be off for + this to work correctly. + + .. ## Output.note_on ## + + .. method:: set_instrument + + | :sl:`select an instrument, with a value between 0 and 127` + | :sg:`set_instrument(instrument_id, channel=0) -> None` + + Select an instrument. + + .. ## Output.set_instrument ## + + .. method:: pitch_bend + + | :sl:`modify the pitch of a channel.` + | :sg:`set_instrument(value=0, channel=0) -> None` + + Adjust the pitch of a channel. The value is a signed integer + from -8192 to +8191. For example, 0 means "no change", +4096 is + typically a semitone higher, and -8192 is 1 whole tone lower (though + the musical range corresponding to the pitch bend range can also be + changed in some synthesizers). + + If no value is given, the pitch bend is returned to "no change". + + .. versionadded:: 1.9.4 + + .. method:: write + + | :sl:`writes a list of midi data to the Output` + | :sg:`write(data) -> None` + + Writes series of MIDI information in the form of a list. + + :param list data: data to write, the expected format is + ``[[[status, data1=0, data2=0, ...], timestamp], ...]`` + with the ``data#`` fields being optional + + :raises IndexError: if more than 1024 elements in the data list + + Example: + :: + + # Program change at time 20000 and 500ms later send note 65 with + # velocity 100. + write([[[0xc0, 0, 0], 20000], [[0x90, 60, 100], 20500]]) + + .. note:: + - Timestamps will be ignored if latency = 0 + - To get a note to play immediately, send MIDI info with timestamp + read from function Time + - Optional data fields: ``write([[[0xc0, 0, 0], 20000]])`` is + equivalent to ``write([[[0xc0], 20000]])`` + + .. ## Output.write ## + + .. method:: write_short + + | :sl:`writes up to 3 bytes of midi data to the Output` + | :sg:`write_short(status) -> None` + | :sg:`write_short(status, data1=0, data2=0) -> None` + + Output MIDI information of 3 bytes or less. The ``data`` fields are + optional and assumed to be 0 if omitted. + + Examples of status byte values: + :: + + 0xc0 # program change + 0x90 # note on + # etc. + + Example: + :: + + # note 65 on with velocity 100 + write_short(0x90, 65, 100) + + .. ## Output.write_short ## + + .. method:: write_sys_ex + + | :sl:`writes a timestamped system-exclusive midi message.` + | :sg:`write_sys_ex(when, msg) -> None` + + Writes a timestamped system-exclusive midi message. + + :param msg: midi message + :type msg: list[int] or str + :param when: timestamp in milliseconds + + Example: + :: + + midi_output.write_sys_ex(0, '\xF0\x7D\x10\x11\x12\x13\xF7') + + # is equivalent to + + midi_output.write_sys_ex(pygame.midi.time(), + [0xF0, 0x7D, 0x10, 0x11, 0x12, 0x13, 0xF7]) + + .. ## Output.write_sys_ex ## + + .. ## pygame.midi.Output ## + +.. function:: get_count + + | :sl:`gets the number of devices.` + | :sg:`get_count() -> num_devices` + + Device ids range from 0 to ``get_count() - 1`` + + .. ## pygame.midi.get_count ## + +.. function:: get_default_input_id + + | :sl:`gets default input device number` + | :sg:`get_default_input_id() -> default_id` + + The following describes the usage details for this function and the + :func:`get_default_output_id` function. + + Return the default device ID or ``-1`` if there are no devices. The result + can be passed to the :class:`Input`/:class:`Output` class. + + On a PC the user can specify a default device by setting an environment + variable. To use device #1, for example: + :: + + set PM_RECOMMENDED_INPUT_DEVICE=1 + or + set PM_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ID by using the + supplied application "testin" or "testout". + + In general, the registry is a better place for this kind of info. With + USB devices that can come and go, using integers is not very reliable + for device identification. Under Windows, if ``PM_RECOMMENDED_INPUT_DEVICE`` + (or ``PM_RECOMMENDED_OUTPUT_DEVICE``) is NOT found in the environment, + then the default device is obtained by looking for a string in the registry + under: + :: + + HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device + or + HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device + + + The number of the first device with a substring that matches the + string exactly is returned. For example, if the string in the registry is + "USB" and device 1 is named "In USB MidiSport 1x1", then that will be + the default input because it contains the string "USB". + + In addition to the name, :func:`get_device_info()` returns "interf", which is + the interface name. The "interface" is the underlying software system or + API used by PortMidi to access devices. Supported interfaces: + :: + + MMSystem # the only Win32 interface currently supported + ALSA # the only Linux interface currently supported + CoreMIDI # the only Mac OS X interface currently supported + # DirectX - not implemented + # OSS - not implemented + + To specify both the interface and the device name in the registry, separate + the two with a comma and a space. The string before the comma must be a + substring of the "interf" string and the string after the space must be a + substring of the "name" name string in order to match the device. e.g.: + :: + + MMSystem, In USB MidiSport 1x1 + + .. note:: + In the current release, the default is simply the first device (the + input or output device with the lowest PmDeviceID). + + .. ## pygame.midi.get_default_input_id ## + +.. function:: get_default_output_id + + | :sl:`gets default output device number` + | :sg:`get_default_output_id() -> default_id` + + See :func:`get_default_input_id` for usage details. + + .. ## pygame.midi.get_default_output_id ## + +.. function:: get_device_info + + | :sl:`returns information about a midi device` + | :sg:`get_device_info(an_id) -> (interf, name, input, output, opened)` + | :sg:`get_device_info(an_id) -> None` + + Gets the device info for a given id. + + :param int an_id: id of the midi device being queried + + :returns: if the id is out of range ``None`` is returned, otherwise + a tuple of (interf, name, input, output, opened) is returned. + + - interf: string describing the device interface (e.g. 'ALSA') + - name: string name of the device (e.g. 'Midi Through Port-0') + - input: 1 if the device is an input device, otherwise 0 + - output: 1 if the device is an output device, otherwise 0 + - opened: 1 if the device is opened, otherwise 0 + :rtype: tuple or None + + .. ## pygame.midi.get_device_info ## + +.. function:: midis2events + + | :sl:`converts midi events to pygame events` + | :sg:`midis2events(midi_events, device_id) -> [Event, ...]` + + Takes a sequence of midi events and returns list of pygame events. + + The ``midi_events`` data is expected to be a sequence of + ``((status, data1, data2, data3), timestamp)`` midi events (all values + required). + + :returns: a list of pygame events of event type ``MIDIIN`` + :rtype: list + + .. ## pygame.midi.midis2events ## + +.. function:: time + + | :sl:`returns the current time in ms of the PortMidi timer` + | :sg:`time() -> time` + + The time is reset to 0 when the :mod:`pygame.midi` module is initialized. + + .. ## pygame.midi.time ## + + +.. function:: frequency_to_midi + + | :sl:`Converts a frequency into a MIDI note. Rounds to the closest midi note.` + | :sg:`frequency_to_midi(midi_note) -> midi_note` + + example: + :: + + frequency_to_midi(27.5) == 21 + + .. versionadded:: 1.9.5 + + .. ## pygame.midi.frequency_to_midi ## + + +.. function:: midi_to_frequency + + | :sl:`Converts a midi note to a frequency.` + | :sg:`midi_to_frequency(midi_note) -> frequency` + + example: + :: + + midi_to_frequency(21) == 27.5 + + .. versionadded:: 1.9.5 + + .. ## pygame.midi.midi_to_frequency ## + + +.. function:: midi_to_ansi_note + + | :sl:`Returns the Ansi Note name for a midi number.` + | :sg:`midi_to_ansi_note(midi_note) -> ansi_note` + + example: + :: + + midi_to_ansi_note(21) == 'A0' + + .. versionadded:: 1.9.5 + + .. ## pygame.midi.midi_to_ansi_note ## + +.. exception:: MidiException + + | :sl:`exception that pygame.midi functions and classes can raise` + | :sg:`MidiException(errno) -> None` + + .. ## pygame.midi.MidiException ## + + +.. ## pygame.midi ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mixer.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mixer.rst.txt new file mode 100644 index 0000000..3a0fecc --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mixer.rst.txt @@ -0,0 +1,604 @@ +.. include:: common.txt + +:mod:`pygame.mixer` +=================== + +.. module:: pygame.mixer + :synopsis: pygame module for loading and playing sounds + +| :sl:`pygame module for loading and playing sounds` + +This module contains classes for loading Sound objects and controlling +playback. The mixer module is optional and depends on SDL_mixer. Your program +should test that :mod:`pygame.mixer` is available and initialized before using +it. + +The mixer module has a limited number of channels for playback of sounds. +Usually programs tell pygame to start playing audio and it selects an available +channel automatically. The default is 8 simultaneous channels, but complex +programs can get more precise control over the number of channels and their +use. + +All sound playback is mixed in background threads. When you begin to play a +Sound object, it will return immediately while the sound continues to play. A +single Sound object can also be actively played back multiple times. + +The mixer also has a special streaming channel. This is for music playback and +is accessed through the :mod:`pygame.mixer.music` module. Consider using this +module for playing long running music. Unlike mixer module, the music module +streams the music from the files without loading music at once into memory. + +The mixer module must be initialized like other pygame modules, but it has some +extra conditions. The ``pygame.mixer.init()`` function takes several optional +arguments to control the playback rate and sample size. Pygame will default to +reasonable values, but pygame cannot perform Sound resampling, so the mixer +should be initialized to match the values of your audio resources. + +``NOTE``: For less laggy sound use a smaller buffer size. The default +is set to reduce the chance of scratchy sounds on some computers. You can +change the default buffer by calling :func:`pygame.mixer.pre_init` before +:func:`pygame.mixer.init` or :func:`pygame.init` is called. For example: +``pygame.mixer.pre_init(44100,-16,2, 1024)`` + + +.. function:: init + + | :sl:`initialize the mixer module` + | :sg:`init(frequency=44100, size=-16, channels=2, buffer=512, devicename=None, allowedchanges=AUDIO_ALLOW_FREQUENCY_CHANGE | AUDIO_ALLOW_CHANNELS_CHANGE) -> None` + + Initialize the mixer module for Sound loading and playback. The default + arguments can be overridden to provide specific audio mixing. Keyword + arguments are accepted. For backwards compatibility, argument values of + 0 are replaced with the startup defaults, except for ``allowedchanges``, + where -1 is used. (startup defaults may be changed by a :func:`pre_init` call). + + The size argument represents how many bits are used for each audio sample. + If the value is negative then signed sample values will be used. Positive + values mean unsigned audio samples will be used. An invalid value raises an + exception. + + The channels argument is used to specify whether to use mono or stereo. 1 + for mono and 2 for stereo. + + The buffer argument controls the number of internal samples used in the + sound mixer. The default value should work for most cases. It can be lowered + to reduce latency, but sound dropout may occur. It can be raised to larger + values to ensure playback never skips, but it will impose latency on sound + playback. The buffer size must be a power of two (if not it is rounded up to + the next nearest power of 2). + + Some platforms require the :mod:`pygame.mixer` module to be initialized + after the display modules have initialized. The top level ``pygame.init()`` + takes care of this automatically, but cannot pass any arguments to the mixer + init. To solve this, mixer has a function ``pygame.mixer.pre_init()`` to set + the proper defaults before the toplevel init is used. + + When using allowedchanges=0 it will convert the samples at runtime to match + what the hardware supports. For example a sound card may not + support 16bit sound samples, so instead it will use 8bit samples internally. + If AUDIO_ALLOW_FORMAT_CHANGE is supplied, then the requested format will + change to the closest that SDL2 supports. + + Apart from 0, allowedchanged accepts the following constants ORed together: + + - AUDIO_ALLOW_FREQUENCY_CHANGE + - AUDIO_ALLOW_FORMAT_CHANGE + - AUDIO_ALLOW_CHANNELS_CHANGE + - AUDIO_ALLOW_ANY_CHANGE + + It is safe to call this more than once, but after the mixer is initialized + you cannot change the playback arguments without first calling + ``pygame.mixer.quit()``. + + .. versionchanged:: 1.8 The default ``buffersize`` changed from 1024 to 3072. + .. versionchanged:: 1.9.1 The default ``buffersize`` changed from 3072 to 4096. + .. versionchanged:: 2.0.0 The default ``buffersize`` changed from 4096 to 512. + .. versionchanged:: 2.0.0 The default ``frequency`` changed from 22050 to 44100. + .. versionchanged:: 2.0.0 ``size`` can be 32 (32-bit floats). + .. versionchanged:: 2.0.0 ``channels`` can also be 4 or 6. + .. versionadded:: 2.0.0 ``allowedchanges``, ``devicename`` arguments added + + .. ## pygame.mixer.init ## + +.. function:: pre_init + + | :sl:`preset the mixer init arguments` + | :sg:`pre_init(frequency=44100, size=-16, channels=2, buffer=512, devicename=None, allowedchanges=AUDIO_ALLOW_FREQUENCY_CHANGE | AUDIO_ALLOW_CHANNELS_CHANGE) -> None` + + Call pre_init to change the defaults used when the real + ``pygame.mixer.init()`` is called. Keyword arguments are accepted. The best + way to set custom mixer playback values is to call + ``pygame.mixer.pre_init()`` before calling the top level ``pygame.init()``. + For backwards compatibility, argument values of 0 are replaced with the + startup defaults, except for ``allowedchanges``, where -1 is used. + + .. versionchanged:: 1.8 The default ``buffersize`` changed from 1024 to 3072. + .. versionchanged:: 1.9.1 The default ``buffersize`` changed from 3072 to 4096. + .. versionchanged:: 2.0.0 The default ``buffersize`` changed from 4096 to 512. + .. versionchanged:: 2.0.0 The default ``frequency`` changed from 22050 to 44100. + .. versionadded:: 2.0.0 ``allowedchanges``, ``devicename`` arguments added + + .. ## pygame.mixer.pre_init ## + +.. function:: quit + + | :sl:`uninitialize the mixer` + | :sg:`quit() -> None` + + This will uninitialize :mod:`pygame.mixer`. All playback will stop and any + loaded Sound objects may not be compatible with the mixer if it is + reinitialized later. + + .. ## pygame.mixer.quit ## + +.. function:: get_init + + | :sl:`test if the mixer is initialized` + | :sg:`get_init() -> (frequency, format, channels)` + + If the mixer is initialized, this returns the playback arguments it is + using. If the mixer has not been initialized this returns ``None``. + + .. ## pygame.mixer.get_init ## + +.. function:: stop + + | :sl:`stop playback of all sound channels` + | :sg:`stop() -> None` + + This will stop all playback of all active mixer channels. + + .. ## pygame.mixer.stop ## + +.. function:: pause + + | :sl:`temporarily stop playback of all sound channels` + | :sg:`pause() -> None` + + This will temporarily stop all playback on the active mixer channels. The + playback can later be resumed with ``pygame.mixer.unpause()`` + + .. ## pygame.mixer.pause ## + +.. function:: unpause + + | :sl:`resume paused playback of sound channels` + | :sg:`unpause() -> None` + + This will resume all active sound channels after they have been paused. + + .. ## pygame.mixer.unpause ## + +.. function:: fadeout + + | :sl:`fade out the volume on all sounds before stopping` + | :sg:`fadeout(time) -> None` + + This will fade out the volume on all active channels over the time argument + in milliseconds. After the sound is muted the playback will stop. + + .. ## pygame.mixer.fadeout ## + +.. function:: set_num_channels + + | :sl:`set the total number of playback channels` + | :sg:`set_num_channels(count) -> None` + + Sets the number of available channels for the mixer. The default value is 8. + The value can be increased or decreased. If the value is decreased, sounds + playing on the truncated channels are stopped. + + .. ## pygame.mixer.set_num_channels ## + +.. function:: get_num_channels + + | :sl:`get the total number of playback channels` + | :sg:`get_num_channels() -> count` + + Returns the number of currently active playback channels. + + .. ## pygame.mixer.get_num_channels ## + +.. function:: set_reserved + + | :sl:`reserve channels from being automatically used` + | :sg:`set_reserved(count) -> count` + + The mixer can reserve any number of channels that will not be automatically + selected for playback by Sounds. If sounds are currently playing on the + reserved channels they will not be stopped. + + This allows the application to reserve a specific number of channels for + important sounds that must not be dropped or have a guaranteed channel to + play on. + + Will return number of channels actually reserved, this may be less than requested + depending on the number of channels previously allocated. + + .. ## pygame.mixer.set_reserved ## + +.. function:: find_channel + + | :sl:`find an unused channel` + | :sg:`find_channel(force=False) -> Channel` + + This will find and return an inactive Channel object. If there are no + inactive Channels this function will return ``None``. If there are no + inactive channels and the force argument is ``True``, this will find the + Channel with the longest running Sound and return it. + + .. ## pygame.mixer.find_channel ## + +.. function:: get_busy + + | :sl:`test if any sound is being mixed` + | :sg:`get_busy() -> bool` + + Returns ``True`` if the mixer is busy mixing any channels. If the mixer is + idle then this return ``False``. + + .. ## pygame.mixer.get_busy ## + +.. function:: get_sdl_mixer_version + + | :sl:`get the mixer's SDL version` + | :sg:`get_sdl_mixer_version() -> (major, minor, patch)` + | :sg:`get_sdl_mixer_version(linked=True) -> (major, minor, patch)` + + :param bool linked: if ``True`` (default) the linked version number is + returned, otherwise the compiled version number is returned + + :returns: the mixer's SDL library version number (linked or compiled + depending on the ``linked`` parameter) as a tuple of 3 integers + ``(major, minor, patch)`` + :rtype: tuple + + .. note:: + The linked and compile version numbers should be the same. + + .. versionadded:: 2.0.0 + + .. ## pygame.mixer.get_sdl_mixer_version ## + +.. class:: Sound + + | :sl:`Create a new Sound object from a file or buffer object` + | :sg:`Sound(filename) -> Sound` + | :sg:`Sound(file=filename) -> Sound` + | :sg:`Sound(file=pathlib_path) -> Sound` + | :sg:`Sound(buffer) -> Sound` + | :sg:`Sound(buffer=buffer) -> Sound` + | :sg:`Sound(object) -> Sound` + | :sg:`Sound(file=object) -> Sound` + | :sg:`Sound(array=object) -> Sound` + + Load a new sound buffer from a filename, a python file object or a readable + buffer object. Limited resampling will be performed to help the sample match + the initialize arguments for the mixer. A Unicode string can only be a file + pathname. A bytes object can be either a pathname or a buffer object. + Use the 'file' or 'buffer' keywords to avoid ambiguity; otherwise Sound may + guess wrong. If the array keyword is used, the object is expected to export + a new buffer interface (The object is checked for a buffer interface first.) + + The Sound object represents actual sound sample data. Methods that change + the state of the Sound object will the all instances of the Sound playback. + A Sound object also exports a new buffer interface. + + The Sound can be loaded from an ``OGG`` audio file or from an uncompressed + ``WAV``. + + Note: The buffer will be copied internally, no data will be shared between + it and the Sound object. + + For now buffer and array support is consistent with ``sndarray.make_sound`` + for Numeric arrays, in that sample sign and byte order are ignored. This + will change, either by correctly handling sign and byte order, or by raising + an exception when different. Also, source samples are truncated to fit the + audio sample size. This will not change. + + .. versionadded:: 1.8 ``pygame.mixer.Sound(buffer)`` + .. versionadded:: 1.9.2 + :class:`pygame.mixer.Sound` keyword arguments and array interface support + .. versionadded:: 2.0.1 pathlib.Path support on Python 3. + + .. method:: play + + | :sl:`begin sound playback` + | :sg:`play(loops=0, maxtime=0, fade_ms=0) -> Channel` + + Begin playback of the Sound (i.e., on the computer's speakers) on an + available Channel. This will forcibly select a Channel, so playback may + cut off a currently playing sound if necessary. + + The loops argument controls how many times the sample will be repeated + after being played the first time. A value of 5 means that the sound will + be played once, then repeated five times, and so is played a total of six + times. The default value (zero) means the Sound is not repeated, and so + is only played once. If loops is set to -1 the Sound will loop + indefinitely (though you can still call ``stop()`` to stop it). + + The maxtime argument can be used to stop playback after a given number of + milliseconds. + + The fade_ms argument will make the sound start playing at 0 volume and + fade up to full volume over the time given. The sample may end before the + fade-in is complete. + + This returns the Channel object for the channel that was selected. + + .. ## Sound.play ## + + .. method:: stop + + | :sl:`stop sound playback` + | :sg:`stop() -> None` + + This will stop the playback of this Sound on any active Channels. + + .. ## Sound.stop ## + + .. method:: fadeout + + | :sl:`stop sound playback after fading out` + | :sg:`fadeout(time) -> None` + + This will stop playback of the sound after fading it out over the time + argument in milliseconds. The Sound will fade and stop on all actively + playing channels. + + .. ## Sound.fadeout ## + + .. method:: set_volume + + | :sl:`set the playback volume for this Sound` + | :sg:`set_volume(value) -> None` + + This will set the playback volume (loudness) for this Sound. This will + immediately affect the Sound if it is playing. It will also affect any + future playback of this Sound. + + :param float value: volume in the range of 0.0 to 1.0 (inclusive) + + | If value < 0.0, the volume will not be changed + | If value > 1.0, the volume will be set to 1.0 + + .. ## Sound.set_volume ## + + .. method:: get_volume + + | :sl:`get the playback volume` + | :sg:`get_volume() -> value` + + Return a value from 0.0 to 1.0 representing the volume for this Sound. + + .. ## Sound.get_volume ## + + .. method:: get_num_channels + + | :sl:`count how many times this Sound is playing` + | :sg:`get_num_channels() -> count` + + Return the number of active channels this sound is playing on. + + .. ## Sound.get_num_channels ## + + .. method:: get_length + + | :sl:`get the length of the Sound` + | :sg:`get_length() -> seconds` + + Return the length of this Sound in seconds. + + .. ## Sound.get_length ## + + .. method:: get_raw + + | :sl:`return a bytestring copy of the Sound samples.` + | :sg:`get_raw() -> bytes` + + Return a copy of the Sound object buffer as a bytes. + + .. versionadded:: 1.9.2 + + .. ## Sound.get_raw ## + + .. ## pygame.mixer.Sound ## + +.. class:: Channel + + | :sl:`Create a Channel object for controlling playback` + | :sg:`Channel(id) -> Channel` + + Return a Channel object for one of the current channels. The id must be a + value from 0 to the value of ``pygame.mixer.get_num_channels()``. + + The Channel object can be used to get fine control over the playback of + Sounds. A channel can only playback a single Sound at time. Using channels + is entirely optional since pygame can manage them by default. + + .. method:: play + + | :sl:`play a Sound on a specific Channel` + | :sg:`play(Sound, loops=0, maxtime=0, fade_ms=0) -> None` + + This will begin playback of a Sound on a specific Channel. If the Channel + is currently playing any other Sound it will be stopped. + + The loops argument has the same meaning as in ``Sound.play()``: it is the + number of times to repeat the sound after the first time. If it is 3, the + sound will be played 4 times (the first time, then three more). If loops + is -1 then the playback will repeat indefinitely. + + As in ``Sound.play()``, the maxtime argument can be used to stop playback + of the Sound after a given number of milliseconds. + + As in ``Sound.play()``, the fade_ms argument can be used fade in the + sound. + + .. ## Channel.play ## + + .. method:: stop + + | :sl:`stop playback on a Channel` + | :sg:`stop() -> None` + + Stop sound playback on a channel. After playback is stopped the channel + becomes available for new Sounds to play on it. + + .. ## Channel.stop ## + + .. method:: pause + + | :sl:`temporarily stop playback of a channel` + | :sg:`pause() -> None` + + Temporarily stop the playback of sound on a channel. It can be resumed at + a later time with ``Channel.unpause()`` + + .. ## Channel.pause ## + + .. method:: unpause + + | :sl:`resume pause playback of a channel` + | :sg:`unpause() -> None` + + Resume the playback on a paused channel. + + .. ## Channel.unpause ## + + .. method:: fadeout + + | :sl:`stop playback after fading channel out` + | :sg:`fadeout(time) -> None` + + Stop playback of a channel after fading out the sound over the given time + argument in milliseconds. + + .. ## Channel.fadeout ## + + .. method:: set_volume + + | :sl:`set the volume of a playing channel` + | :sg:`set_volume(value) -> None` + | :sg:`set_volume(left, right) -> None` + + Set the volume (loudness) of a playing sound. When a channel starts to + play its volume value is reset. This only affects the current sound. The + value argument is between 0.0 and 1.0. + + If one argument is passed, it will be the volume of both speakers. If two + arguments are passed and the mixer is in stereo mode, the first argument + will be the volume of the left speaker and the second will be the volume + of the right speaker. (If the second argument is ``None``, the first + argument will be the volume of both speakers.) + + If the channel is playing a Sound on which ``set_volume()`` has also been + called, both calls are taken into account. For example: + + :: + + sound = pygame.mixer.Sound("s.wav") + channel = s.play() # Sound plays at full volume by default + sound.set_volume(0.9) # Now plays at 90% of full volume. + sound.set_volume(0.6) # Now plays at 60% (previous value replaced). + channel.set_volume(0.5) # Now plays at 30% (0.6 * 0.5). + + .. ## Channel.set_volume ## + + .. method:: get_volume + + | :sl:`get the volume of the playing channel` + | :sg:`get_volume() -> value` + + Return the volume of the channel for the current playing sound. This does + not take into account stereo separation used by + :meth:`Channel.set_volume`. The Sound object also has its own volume + which is mixed with the channel. + + .. ## Channel.get_volume ## + + .. method:: get_busy + + | :sl:`check if the channel is active` + | :sg:`get_busy() -> bool` + + Returns ``True`` if the channel is actively mixing sound. If the channel + is idle this returns ``False``. + + .. ## Channel.get_busy ## + + .. method:: get_sound + + | :sl:`get the currently playing Sound` + | :sg:`get_sound() -> Sound` + + Return the actual Sound object currently playing on this channel. If the + channel is idle ``None`` is returned. + + .. ## Channel.get_sound ## + + .. method:: queue + + | :sl:`queue a Sound object to follow the current` + | :sg:`queue(Sound) -> None` + + When a Sound is queued on a Channel, it will begin playing immediately + after the current Sound is finished. Each channel can only have a single + Sound queued at a time. The queued Sound will only play if the current + playback finished automatically. It is cleared on any other call to + ``Channel.stop()`` or ``Channel.play()``. + + If there is no sound actively playing on the Channel then the Sound will + begin playing immediately. + + .. ## Channel.queue ## + + .. method:: get_queue + + | :sl:`return any Sound that is queued` + | :sg:`get_queue() -> Sound` + + If a Sound is already queued on this channel it will be returned. Once + the queued sound begins playback it will no longer be on the queue. + + .. ## Channel.get_queue ## + + .. method:: set_endevent + + | :sl:`have the channel send an event when playback stops` + | :sg:`set_endevent() -> None` + | :sg:`set_endevent(type) -> None` + + When an endevent is set for a channel, it will send an event to the + pygame queue every time a sound finishes playing on that channel (not + just the first time). Use ``pygame.event.get()`` to retrieve the endevent + once it's sent. + + Note that if you called ``Sound.play(n)`` or ``Channel.play(sound,n)``, + the end event is sent only once: after the sound has been played "n+1" + times (see the documentation of Sound.play). + + If ``Channel.stop()`` or ``Channel.play()`` is called while the sound was + still playing, the event will be posted immediately. + + The type argument will be the event id sent to the queue. This can be any + valid event type, but a good choice would be a value between + ``pygame.locals.USEREVENT`` and ``pygame.locals.NUMEVENTS``. If no type + argument is given then the Channel will stop sending endevents. + + .. ## Channel.set_endevent ## + + .. method:: get_endevent + + | :sl:`get the event a channel sends when playback stops` + | :sg:`get_endevent() -> type` + + Returns the event type to be sent every time the Channel finishes + playback of a Sound. If there is no endevent the function returns + ``pygame.NOEVENT``. + + .. ## Channel.get_endevent ## + + .. ## pygame.mixer.Channel ## + +.. ## pygame.mixer ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mouse.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mouse.rst.txt new file mode 100644 index 0000000..84aaded --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/mouse.rst.txt @@ -0,0 +1,219 @@ +.. include:: common.txt + +:mod:`pygame.mouse` +=================== + +.. module:: pygame.mouse + :synopsis: pygame module to work with the mouse + +| :sl:`pygame module to work with the mouse` + +The mouse functions can be used to get the current state of the mouse device. +These functions can also alter the system cursor for the mouse. + +When the display mode is set, the event queue will start receiving mouse +events. The mouse buttons generate ``pygame.MOUSEBUTTONDOWN`` and +``pygame.MOUSEBUTTONUP`` events when they are pressed and released. These +events contain a button attribute representing which button was pressed. The +mouse wheel will generate ``pygame.MOUSEBUTTONDOWN`` and +``pygame.MOUSEBUTTONUP`` events when rolled. The button will be set to 4 +when the wheel is rolled up, and to button 5 when the wheel is rolled down. +Whenever the mouse is moved it generates a ``pygame.MOUSEMOTION`` event. The +mouse movement is broken into small and accurate motion events. As the mouse +is moving many motion events will be placed on the queue. Mouse motion events +that are not properly cleaned from the event queue are the primary reason the +event queue fills up. + +If the mouse cursor is hidden, and input is grabbed to the current display the +mouse will enter a virtual input mode, where the relative movements of the +mouse will never be stopped by the borders of the screen. See the functions +``pygame.mouse.set_visible()`` and ``pygame.event.set_grab()`` to get this +configured. + + +**Mouse Wheel Behavior in pygame 2** + +There is proper functionality for mouse wheel behaviour with pygame 2 supporting +``pygame.MOUSEWHEEL`` events. The new events support horizontal and vertical +scroll movements, with signed integer values representing the amount scrolled +(``x`` and ``y``), as well as ``flipped`` direction (the set positive and +negative values for each axis is flipped). Read more about SDL2 +input-related changes here ``_ + +In pygame 2, the mouse wheel functionality can be used by listening for the +``pygame.MOUSEWHEEL`` type of an event (Bear in mind they still emit +``pygame.MOUSEBUTTONDOWN`` events like in pygame 1.x, as well). +When this event is triggered, a developer can access the appropriate ``Event`` object +with ``pygame.event.get()``. The object can be used to access data about the mouse +scroll, such as ``which`` (it will tell you what exact mouse device trigger the event). + +.. code-block:: python + :caption: Code example of mouse scroll (tested on 2.0.0.dev7) + :name: test.py + + # Taken from husano896's PR thread (slightly modified) + import pygame + from pygame.locals import * + pygame.init() + screen = pygame.display.set_mode((640, 480)) + clock = pygame.time.Clock() + + def main(): + while True: + for event in pygame.event.get(): + if event.type == QUIT: + pygame.quit() + return + elif event.type == MOUSEWHEEL: + print(event) + print(event.x, event.y) + print(event.flipped) + print(event.which) + # can access properties with + # proper notation(ex: event.y) + clock.tick(60) + + # Execute game: + main() + +.. function:: get_pressed + + | :sl:`get the state of the mouse buttons` + | :sg:`get_pressed(num_buttons=3) -> (button1, button2, button3)` + | :sg:`get_pressed(num_buttons=5) -> (button1, button2, button3, button4, button5)` + + Returns a sequence of booleans representing the state of all the mouse + buttons. A true value means the mouse is currently being pressed at the time + of the call. + + Note, to get all of the mouse events it is better to use either + ``pygame.event.wait()`` or ``pygame.event.get()`` and check all of those + events to see if they are ``MOUSEBUTTONDOWN``, ``MOUSEBUTTONUP``, or + ``MOUSEMOTION``. + + Note, that on ``X11`` some X servers use middle button emulation. When you + click both buttons ``1`` and ``3`` at the same time a ``2`` button event + can be emitted. + + Note, remember to call ``pygame.event.get()`` before this function. + Otherwise it will not work as expected. + + To support five button mice, an optional parameter ``num_buttons`` has been + added in pygame 2. When this is set to ``5``, ``button4`` and ``button5`` + are added to the returned tuple. Only ``3`` and ``5`` are valid values + for this parameter. + + .. versionchanged:: 2.0.0 ``num_buttons`` argument added + + .. ## pygame.mouse.get_pressed ## + +.. function:: get_pos + + | :sl:`get the mouse cursor position` + | :sg:`get_pos() -> (x, y)` + + Returns the ``x`` and ``y`` position of the mouse cursor. The position is + relative to the top-left corner of the display. The cursor position can be + located outside of the display window, but is always constrained to the + screen. + + .. ## pygame.mouse.get_pos ## + +.. function:: get_rel + + | :sl:`get the amount of mouse movement` + | :sg:`get_rel() -> (x, y)` + + Returns the amount of movement in ``x`` and ``y`` since the previous call to + this function. The relative movement of the mouse cursor is constrained to + the edges of the screen, but see the virtual input mouse mode for a way + around this. Virtual input mode is described at the top of the page. + + .. ## pygame.mouse.get_rel ## + +.. function:: set_pos + + | :sl:`set the mouse cursor position` + | :sg:`set_pos([x, y]) -> None` + + Set the current mouse position to arguments given. If the mouse cursor is + visible it will jump to the new coordinates. Moving the mouse will generate + a new ``pygame.MOUSEMOTION`` event. + + .. ## pygame.mouse.set_pos ## + +.. function:: set_visible + + | :sl:`hide or show the mouse cursor` + | :sg:`set_visible(bool) -> bool` + + If the bool argument is true, the mouse cursor will be visible. This will + return the previous visible state of the cursor. + + .. ## pygame.mouse.set_visible ## + +.. function:: get_visible + + | :sl:`get the current visibility state of the mouse cursor` + | :sg:`get_visible() -> bool` + + Get the current visibility state of the mouse cursor. ``True`` if the mouse is + visible, ``False`` otherwise. + + .. versionadded:: 2.0.0 + + .. ## pygame.mouse.get_visible ## + +.. function:: get_focused + + | :sl:`check if the display is receiving mouse input` + | :sg:`get_focused() -> bool` + + Returns true when pygame is receiving mouse input events (or, in windowing + terminology, is "active" or has the "focus"). + + This method is most useful when working in a window. By contrast, in + full-screen mode, this method always returns true. + + Note: under ``MS`` Windows, the window that has the mouse focus also has the + keyboard focus. But under X-Windows, one window can receive mouse events and + another receive keyboard events. ``pygame.mouse.get_focused()`` indicates + whether the pygame window receives mouse events. + + .. ## pygame.mouse.get_focused ## + +.. function:: set_cursor + + | :sl:`set the mouse cursor to a new cursor` + | :sg:`set_cursor(pygame.cursors.Cursor) -> None` + | :sg:`set_cursor(size, hotspot, xormasks, andmasks) -> None` + | :sg:`set_cursor(hotspot, surface) -> None` + | :sg:`set_cursor(constant) -> None` + + Set the mouse cursor to something new. This function accepts either an explicit + ``Cursor`` object or arguments to create a ``Cursor`` object. + + See :class:`pygame.cursors.Cursor` for help creating cursors and for examples. + + .. versionchanged:: 2.0.1 + + .. ## pygame.mouse.set_cursor ## + + +.. function:: get_cursor + + | :sl:`get the current mouse cursor` + | :sg:`get_cursor() -> pygame.cursors.Cursor` + + Get the information about the mouse system cursor. The return value contains + the same data as the arguments passed into :func:`pygame.mouse.set_cursor()`. + + .. note:: Code that unpacked a get_cursor() call into + ``size, hotspot, xormasks, andmasks`` will still work, + assuming the call returns an old school type cursor. + + .. versionchanged:: 2.0.1 + + .. ## pygame.mouse.get_cursor ## + +.. ## pygame.mouse ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/music.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/music.rst.txt new file mode 100644 index 0000000..49276b8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/music.rst.txt @@ -0,0 +1,274 @@ +.. include:: common.txt + +:mod:`pygame.mixer.music` +========================= + +.. module:: pygame.mixer.music + :synopsis: pygame module for controlling streamed audio + +| :sl:`pygame module for controlling streamed audio` + +The music module is closely tied to :mod:`pygame.mixer`. Use the music module +to control the playback of music in the sound mixer. + +The difference between the music playback and regular Sound playback is that +the music is streamed, and never actually loaded all at once. The mixer system +only supports a single music stream at once. + +On older pygame versions, ``MP3`` support was limited under Mac and Linux. This +changed in pygame ``v2.0.2`` which got improved MP3 support. Consider using +``OGG`` file format for music as that can give slightly better compression than +MP3 in most cases. + +.. function:: load + + | :sl:`Load a music file for playback` + | :sg:`load(filename) -> None` + | :sg:`load(fileobj, namehint="") -> None` + + This will load a music filename/file object and prepare it for playback. If + a music stream is already playing it will be stopped. This does not start + the music playing. + + If you are loading from a file object, the namehint parameter can be used to specify + the type of music data in the object. For example: :code:`load(fileobj, "ogg")`. + + .. versionchanged:: 2.0.2 Added optional ``namehint`` argument + + .. ## pygame.mixer.music.load ## + +.. function:: unload + + | :sl:`Unload the currently loaded music to free up resources` + | :sg:`unload() -> None` + + This closes resources like files for any music that may be loaded. + + .. versionadded:: 2.0.0 + + .. ## pygame.mixer.music.load ## + + +.. function:: play + + | :sl:`Start the playback of the music stream` + | :sg:`play(loops=0, start=0.0, fade_ms=0) -> None` + + This will play the loaded music stream. If the music is already playing it + will be restarted. + + ``loops`` is an optional integer argument, which is ``0`` by default, which + indicates how many times to repeat the music. The music repeats indefinitely if + this argument is set to ``-1``. + + ``start`` is an optional float argument, which is ``0.0`` by default, which + denotes the position in time from which the music starts playing. The starting + position depends on the format of the music played. ``MP3`` and ``OGG`` use + the position as time in seconds. For ``MP3`` files the start time position + selected may not be accurate as things like variable bit rate encoding and ID3 + tags can throw off the timing calculations. For ``MOD`` music it is the pattern + order number. Passing a start position will raise a NotImplementedError if + the start position cannot be set. + + ``fade_ms`` is an optional integer argument, which is ``0`` by default, + which denotes the period of time (in milliseconds) over which the music + will fade up from volume level ``0.0`` to full volume (or the volume level + previously set by :func:`set_volume`). The sample may end before the fade-in + is complete. If the music is already streaming ``fade_ms`` is ignored. + + .. versionchanged:: 2.0.0 Added optional ``fade_ms`` argument + + .. ## pygame.mixer.music.play ## + +.. function:: rewind + + | :sl:`restart music` + | :sg:`rewind() -> None` + + Resets playback of the current music to the beginning. If :func:`pause` has + previoulsy been used to pause the music, the music will remain paused. + + .. note:: :func:`rewind` supports a limited number of file types and notably + ``WAV`` files are NOT supported. For unsupported file types use :func:`play` + which will restart the music that's already playing (note that this + will start the music playing again even if previously paused). + + .. ## pygame.mixer.music.rewind ## + +.. function:: stop + + | :sl:`stop the music playback` + | :sg:`stop() -> None` + + Stops the music playback if it is currently playing. + endevent will be triggered, if set. + It won't unload the music. + + .. ## pygame.mixer.music.stop ## + +.. function:: pause + + | :sl:`temporarily stop music playback` + | :sg:`pause() -> None` + + Temporarily stop playback of the music stream. It can be resumed with the + :func:`unpause` function. + + .. ## pygame.mixer.music.pause ## + +.. function:: unpause + + | :sl:`resume paused music` + | :sg:`unpause() -> None` + + This will resume the playback of a music stream after it has been paused. + + .. ## pygame.mixer.music.unpause ## + +.. function:: fadeout + + | :sl:`stop music playback after fading out` + | :sg:`fadeout(time) -> None` + + Fade out and stop the currently playing music. + + The ``time`` argument denotes the integer milliseconds for which the + fading effect is generated. + + Note, that this function blocks until the music has faded out. Calls + to :func:`fadeout` and :func:`set_volume` will have no effect during + this time. If an event was set using :func:`set_endevent` it will be + called after the music has faded. + + .. ## pygame.mixer.music.fadeout ## + +.. function:: set_volume + + | :sl:`set the music volume` + | :sg:`set_volume(volume) -> None` + + Set the volume of the music playback. + + The ``volume`` argument is a float between ``0.0`` and ``1.0`` that sets + the volume level. When new music is loaded the volume is reset to full + volume. If ``volume`` is a negative value it will be ignored and the + volume will remain set at the current level. If the ``volume`` argument + is greater than ``1.0``, the volume will be set to ``1.0``. + + .. ## pygame.mixer.music.set_volume ## + +.. function:: get_volume + + | :sl:`get the music volume` + | :sg:`get_volume() -> value` + + Returns the current volume for the mixer. The value will be between ``0.0`` + and ``1.0``. + + .. ## pygame.mixer.music.get_volume ## + +.. function:: get_busy + + | :sl:`check if the music stream is playing` + | :sg:`get_busy() -> bool` + + Returns True when the music stream is actively playing. When the music is + idle this returns False. In pygame 2.0.1 and above this function returns + False when the music is paused. In pygame 1 it returns True when the music + is paused. + + .. versionchanged:: 2.0.1 Returns False when music paused. + + .. ## pygame.mixer.music.get_busy ## + +.. function:: set_pos + + | :sl:`set position to play from` + | :sg:`set_pos(pos) -> None` + + This sets the position in the music file where playback will start. + The meaning of "pos", a float (or a number that can be converted to a float), + depends on the music format. + + For ``MOD`` files, pos is the integer pattern number in the module. + For ``OGG`` it is the absolute position, in seconds, from + the beginning of the sound. For ``MP3`` files, it is the relative position, + in seconds, from the current position. For absolute positioning in an ``MP3`` + file, first call :func:`rewind`. + + Other file formats are unsupported. Newer versions of SDL_mixer have + better positioning support than earlier ones. An SDLError is raised if a + particular format does not support positioning. + + Function :func:`set_pos` calls underlining SDL_mixer function + ``Mix_SetMusicPosition``. + + .. versionadded:: 1.9.2 + + .. ## pygame.mixer.music.set_pos ## + +.. function:: get_pos + + | :sl:`get the music play time` + | :sg:`get_pos() -> time` + + This gets the number of milliseconds that the music has been playing for. + The returned time only represents how long the music has been playing; it + does not take into account any starting position offsets. + + .. ## pygame.mixer.music.get_pos ## + +.. function:: queue + + | :sl:`queue a sound file to follow the current` + | :sg:`queue(filename) -> None` + | :sg:`queue(fileobj, namehint="", loops=0) -> None` + + This will load a sound file and queue it. A queued sound file will begin as + soon as the current sound naturally ends. Only one sound can be queued at a + time. Queuing a new sound while another sound is queued will result in the + new sound becoming the queued sound. Also, if the current sound is ever + stopped or changed, the queued sound will be lost. + + If you are loading from a file object, the namehint parameter can be used to specify + the type of music data in the object. For example: :code:`queue(fileobj, "ogg")`. + + The following example will play music by Bach six times, then play music by + Mozart once: + + :: + + pygame.mixer.music.load('bach.ogg') + pygame.mixer.music.play(5) # Plays six times, not five! + pygame.mixer.music.queue('mozart.ogg') + + .. versionchanged:: 2.0.2 Added optional ``namehint`` argument + + .. ## pygame.mixer.music.queue ## + +.. function:: set_endevent + + | :sl:`have the music send an event when playback stops` + | :sg:`set_endevent() -> None` + | :sg:`set_endevent(type) -> None` + + This causes pygame to signal (by means of the event queue) when the music is + done playing. The argument determines the type of event that will be queued. + + The event will be queued every time the music finishes, not just the first + time. To stop the event from being queued, call this method with no + argument. + + .. ## pygame.mixer.music.set_endevent ## + +.. function:: get_endevent + + | :sl:`get the event a channel sends when playback stops` + | :sg:`get_endevent() -> type` + + Returns the event type to be sent every time the music finishes playback. If + there is no endevent the function returns ``pygame.NOEVENT``. + + .. ## pygame.mixer.music.get_endevent ## + +.. ## pygame.mixer.music ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/overlay.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/overlay.rst.txt new file mode 100644 index 0000000..04ff9ae --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/overlay.rst.txt @@ -0,0 +1,79 @@ +.. include:: common.txt + +:mod:`pygame.Overlay` +===================== + +.. currentmodule:: pygame + +.. warning:: + This module is non functional in pygame 2.0 and above, unless you have manually compiled pygame with SDL1. + This module will not be supported in the future. + +.. class:: Overlay + + | :sl:`pygame object for video overlay graphics` + | :sg:`Overlay(format, (width, height)) -> Overlay` + + The Overlay objects provide support for accessing hardware video overlays. + Video overlays do not use standard ``RGB`` pixel formats, and can use + multiple resolutions of data to create a single image. + + The Overlay objects represent lower level access to the display hardware. To + use the object you must understand the technical details of video overlays. + + The Overlay format determines the type of pixel data used. Not all hardware + will support all types of overlay formats. Here is a list of available + format types: + + :: + + YV12_OVERLAY, IYUV_OVERLAY, YUY2_OVERLAY, UYVY_OVERLAY, YVYU_OVERLAY + + The width and height arguments control the size for the overlay image data. + The overlay image can be displayed at any size, not just the resolution of + the overlay. + + The overlay objects are always visible, and always show above the regular + display contents. + + .. method:: display + + | :sl:`set the overlay pixel data` + | :sg:`display((y, u, v)) -> None` + | :sg:`display() -> None` + + Display the YUV data in SDL's overlay planes. The y, u, and v arguments + are strings of binary data. The data must be in the correct format used + to create the Overlay. + + If no argument is passed in, the Overlay will simply be redrawn with the + current data. This can be useful when the Overlay is not really hardware + accelerated. + + The strings are not validated, and improperly sized strings could crash + the program. + + .. ## Overlay.display ## + + .. method:: set_location + + | :sl:`control where the overlay is displayed` + | :sg:`set_location(rect) -> None` + + Set the location for the overlay. The overlay will always be shown + relative to the main display Surface. This does not actually redraw the + overlay, it will be updated on the next call to ``Overlay.display()``. + + .. ## Overlay.set_location ## + + .. method:: get_hardware + + | :sl:`test if the Overlay is hardware accelerated` + | :sg:`get_hardware(rect) -> int` + + Returns a True value when the Overlay is hardware accelerated. If the + platform does not support acceleration, software rendering is used. + + .. ## Overlay.get_hardware ## + + .. ## pygame.Overlay ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pixelarray.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pixelarray.rst.txt new file mode 100644 index 0000000..da1d93a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pixelarray.rst.txt @@ -0,0 +1,295 @@ +.. include:: common.txt + +:class:`pygame.PixelArray` +========================== + +.. currentmodule:: pygame + +.. class:: PixelArray + + | :sl:`pygame object for direct pixel access of surfaces` + | :sg:`PixelArray(Surface) -> PixelArray` + + The PixelArray wraps a Surface and provides direct access to the + surface's pixels. A pixel array can be one or two dimensional. + A two dimensional array, like its surface, is indexed [column, row]. + Pixel arrays support slicing, both for returning a subarray or + for assignment. A pixel array sliced on a single column or row + returns a one dimensional pixel array. Arithmetic and other operations + are not supported. A pixel array can be safely assigned to itself. + Finally, pixel arrays export an array struct interface, allowing + them to interact with :mod:`pygame.pixelcopy` methods and NumPy + arrays. + + A PixelArray pixel item can be assigned a raw integer values, a + :class:`pygame.Color` instance, or a (r, g, b[, a]) tuple. + + :: + + pxarray[x, y] = 0xFF00FF + pxarray[x, y] = pygame.Color(255, 0, 255) + pxarray[x, y] = (255, 0, 255) + + However, only a pixel's integer value is returned. So, to compare a pixel + to a particular color the color needs to be first mapped using + the :meth:`Surface.map_rgb()` method of the Surface object for which the + PixelArray was created. + + :: + + pxarray = pygame.PixelArray(surface) + # Check, if the first pixel at the topleft corner is blue + if pxarray[0, 0] == surface.map_rgb((0, 0, 255)): + ... + + When assigning to a range of of pixels, a non tuple sequence of colors or + a PixelArray can be used as the value. For a sequence, the length must + match the PixelArray width. + + :: + + pxarray[a:b] = 0xFF00FF # set all pixels to 0xFF00FF + pxarray[a:b] = (0xFF00FF, 0xAACCEE, ... ) # first pixel = 0xFF00FF, + # second pixel = 0xAACCEE, ... + pxarray[a:b] = [(255, 0, 255), (170, 204, 238), ...] # same as above + pxarray[a:b] = [(255, 0, 255), 0xAACCEE, ...] # same as above + pxarray[a:b] = otherarray[x:y] # slice sizes must match + + For PixelArray assignment, if the right hand side array has a row length + of 1, then the column is broadcast over the target array's rows. An + array of height 1 is broadcast over the target's columns, and is equivalent + to assigning a 1D PixelArray. + + Subscript slices can also be used to assign to a rectangular subview of + the target PixelArray. + + :: + + # Create some new PixelArray objects providing a different view + # of the original array/surface. + newarray = pxarray[2:4, 3:5] + otherarray = pxarray[::2, ::2] + + Subscript slices can also be used to do fast rectangular pixel manipulations + instead of iterating over the x or y axis. The + + :: + + pxarray[::2, :] = (0, 0, 0) # Make even columns black. + pxarray[::2] = (0, 0, 0) # Same as [::2, :] + + During its lifetime, the PixelArray locks the surface, thus you explicitly + have to close() it once its not used any more and the surface should perform + operations in the same scope. It is best to use it as a context manager + using the with PixelArray(surf) as pixel_array: style. So it works on pypy too. + + A simple ``:`` slice index for the column can be omitted. + + :: + + pxarray[::2, ...] = (0, 0, 0) # Same as pxarray[::2, :] + pxarray[...] = (255, 0, 0) # Same as pxarray[:] + + A note about PixelArray to PixelArray assignment, for arrays with an + item size of 3 (created from 24 bit surfaces) pixel values are translated + from the source to the destinations format. The red, green, and blue + color elements of each pixel are shifted to match the format of the + target surface. For all other pixel sizes no such remapping occurs. + This should change in later pygame releases, where format conversions + are performed for all pixel sizes. To avoid code breakage when full mapped + copying is implemented it is suggested PixelArray to PixelArray copies be + only between surfaces of identical format. + + .. versionadded:: 1.9.4 + + - close() method was added. For explicitly cleaning up. + - being able to use PixelArray as a context manager for cleanup. + - both of these are useful for when working without reference counting (pypy). + + .. versionadded:: 1.9.2 + + - array struct interface + - transpose method + - broadcasting for a length 1 dimension + + .. versionchanged:: 1.9.2 + + - A 2D PixelArray can have a length 1 dimension. + Only an integer index on a 2D PixelArray returns a 1D array. + - For assignment, a tuple can only be a color. Any other sequence type + is a sequence of colors. + + + .. versionadded: 1.8.0 + Subscript support + + .. versionadded: 1.8.1 + Methods :meth:`make_surface`, :meth:`replace`, :meth:`extract`, and + :meth:`compare` + + .. versionadded: 1.9.2 + Properties :attr:`itemsize`, :attr:`ndim`, :attr:`shape`, + and :attr:`strides` + + .. versionadded: 1.9.2 + Array struct interface + + .. versionadded: 1.9.4 + Methods :meth:`close` + + .. attribute:: surface + + | :sl:`Gets the Surface the PixelArray uses.` + | :sg:`surface -> Surface` + + The Surface the PixelArray was created for. + + .. ## PixelArray.surface ## + + .. attribute:: itemsize + + | :sl:`Returns the byte size of a pixel array item` + | :sg:`itemsize -> int` + + This is the same as :meth:`Surface.get_bytesize` for the + pixel array's surface. + + .. versionadded:: 1.9.2 + + .. attribute:: ndim + + | :sl:`Returns the number of dimensions.` + | :sg:`ndim -> int` + + A pixel array can be 1 or 2 dimensional. + + .. versionadded:: 1.9.2 + + .. attribute:: shape + + | :sl:`Returns the array size.` + | :sg:`shape -> tuple of int's` + + A tuple or length :attr:`ndim` giving the length of each + dimension. Analogous to :meth:`Surface.get_size`. + + .. versionadded:: 1.9.2 + + .. attribute:: strides + + | :sl:`Returns byte offsets for each array dimension.` + | :sg:`strides -> tuple of int's` + + A tuple or length :attr:`ndim` byte counts. When a stride is + multiplied by the corresponding index it gives the offset + of that index from the start of the array. A stride is negative + for an array that has is inverted (has a negative step). + + .. versionadded:: 1.9.2 + + .. method:: make_surface + + | :sl:`Creates a new Surface from the current PixelArray.` + | :sg:`make_surface() -> Surface` + + Creates a new Surface from the current PixelArray. Depending on the + current PixelArray the size, pixel order etc. will be different from the + original Surface. + + :: + + # Create a new surface flipped around the vertical axis. + sf = pxarray[:,::-1].make_surface () + + .. versionadded:: 1.8.1 + + .. ## PixelArray.make_surface ## + + .. method:: replace + + | :sl:`Replaces the passed color in the PixelArray with another one.` + | :sg:`replace(color, repcolor, distance=0, weights=(0.299, 0.587, 0.114)) -> None` + + Replaces the pixels with the passed color in the PixelArray by changing + them them to the passed replacement color. + + It uses a simple weighted Euclidean distance formula to calculate the + distance between the colors. The distance space ranges from 0.0 to 1.0 + and is used as threshold for the color detection. This causes the + replacement to take pixels with a similar, but not exactly identical + color, into account as well. + + This is an in place operation that directly affects the pixels of the + PixelArray. + + .. versionadded:: 1.8.1 + + .. ## PixelArray.replace ## + + .. method:: extract + + | :sl:`Extracts the passed color from the PixelArray.` + | :sg:`extract(color, distance=0, weights=(0.299, 0.587, 0.114)) -> PixelArray` + + Extracts the passed color by changing all matching pixels to white, while + non-matching pixels are changed to black. This returns a new PixelArray + with the black/white color mask. + + It uses a simple weighted Euclidean distance formula to calculate the + distance between the colors. The distance space ranges from 0.0 to 1.0 + and is used as threshold for the color detection. This causes the + extraction to take pixels with a similar, but not exactly identical + color, into account as well. + + .. versionadded:: 1.8.1 + + .. ## PixelArray.extract ## + + .. method:: compare + + | :sl:`Compares the PixelArray with another one.` + | :sg:`compare(array, distance=0, weights=(0.299, 0.587, 0.114)) -> PixelArray` + + Compares the contents of the PixelArray with those from the passed in + PixelArray. It returns a new PixelArray with a black/white color mask + that indicates the differences (black) of both arrays. Both PixelArray + objects must have identical bit depths and dimensions. + + It uses a simple weighted Euclidean distance formula to calculate the + distance between the colors. The distance space ranges from 0.0 to 1.0 + and is used as a threshold for the color detection. This causes the + comparison to mark pixels with a similar, but not exactly identical + color, as white. + + .. versionadded:: 1.8.1 + + .. ## PixelArray.compare ## + + .. method:: transpose + + | :sl:`Exchanges the x and y axis.` + | :sg:`transpose() -> PixelArray` + + This method returns a new view of the pixel array with the rows and + columns swapped. So for a (w, h) sized array a (h, w) slice is returned. + If an array is one dimensional, then a length 1 x dimension is added, + resulting in a 2D pixel array. + + .. versionadded:: 1.9.2 + + .. ## PixelArray.transpose ## + + .. method:: close + + | :sl:`Closes the PixelArray, and releases Surface lock.` + | :sg:`transpose() -> PixelArray` + + This method is for explicitly closing the PixelArray, and releasing + a lock on the Suface. + + .. versionadded:: 1.9.4 + + .. ## PixelArray.close ## + + + .. ## pygame.PixelArray ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pixelcopy.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pixelcopy.rst.txt new file mode 100644 index 0000000..dd8ecf7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pixelcopy.rst.txt @@ -0,0 +1,104 @@ +.. include:: common.txt + +:mod:`pygame.pixelcopy` +======================= + +.. module:: pygame.pixelcopy + :synopsis: pygame module for general pixel array copying + +| :sl:`pygame module for general pixel array copying` + +The ``pygame.pixelcopy`` module contains functions for copying between +surfaces and objects exporting an array structure interface. It is a backend +for :mod:`pygame.surfarray`, adding NumPy support. But pixelcopy is more +general, and intended for direct use. + +The array struct interface exposes an array's data in a standard way. +It was introduced in NumPy. In Python 2.7 and above it is replaced by the +new buffer protocol, though the buffer protocol is still a work in progress. +The array struct interface, on the other hand, is stable and works with earlier +Python versions. So for now the array struct interface is the predominate way +pygame handles array introspection. + +For 2d arrays of integer pixel values, the values are mapped to the +pixel format of the related surface. To get the actual color of a pixel +value use :meth:`pygame.Surface.unmap_rgb`. 2d arrays can only be used +directly between surfaces having the same pixel layout. + +New in pygame 1.9.2. + +.. function:: surface_to_array + + | :sl:`copy surface pixels to an array object` + | :sg:`surface_to_array(array, surface, kind='P', opaque=255, clear=0) -> None` + + The surface_to_array function copies pixels from a Surface object + to a 2D or 3D array. Depending on argument ``kind`` and the target array + dimension, a copy may be raw pixel value, RGB, a color component slice, + or colorkey alpha transparency value. Recognized ``kind`` values are the + single character codes 'P', 'R', 'G', 'B', 'A', and 'C'. Kind codes are case + insensitive, so 'p' is equivalent to 'P'. The first two dimensions + of the target must be the surface size (w, h). + + The default 'P' kind code does a direct raw integer pixel (mapped) value + copy to a 2D array and a 'RGB' pixel component (unmapped) copy to a 3D array + having shape (w, h, 3). For an 8 bit colormap surface this means the + table index is copied to a 2D array, not the table value itself. A 2D + array's item size must be at least as large as the surface's pixel + byte size. The item size of a 3D array must be at least one byte. + + For the 'R', 'G', 'B', and 'A' copy kinds a single color component + of the unmapped surface pixels are copied to the target 2D array. + For kind 'A' and surfaces with source alpha (the surface was created with + the SRCALPHA flag), has a colorkey + (set with :meth:`Surface.set_colorkey() `), + or has a blanket alpha + (set with :meth:`Surface.set_alpha() `) + then the alpha values are those expected for a SDL surface. + If a surface has no explicit alpha value, then the target array + is filled with the value of the optional ``opaque`` surface_to_array + argument (default 255: not transparent). + + Copy kind 'C' is a special case for alpha copy of a source surface + with colorkey. Unlike the 'A' color component copy, the ``clear`` + argument value is used for colorkey matches, ``opaque`` otherwise. + By default, a match has alpha 0 (totally transparent), while everything + else is alpha 255 (totally opaque). It is a more general implementation + of :meth:`pygame.surfarray.array_colorkey`. + + Specific to surface_to_array, a ValueError is raised for target arrays + with incorrect shape or item size. A TypeError is raised for an incorrect + kind code. Surface specific problems, such as locking, raise a pygame.error. + + .. ## pygame.pixelcopy.surface_to_array ## + +.. function:: array_to_surface + + | :sl:`copy an array object to a surface` + | :sg:`array_to_surface(, ) -> None` + + See :func:`pygame.surfarray.blit_array`. + + .. ## pygame.pixelcopy.array_to_surface ## + +.. function:: map_array + + | :sl:`copy an array to another array, using surface format` + | :sg:`map_array(, , ) -> None` + + Map an array of color element values - (w, h, ..., 3) - to an array of + pixels - (w, h) according to the format of . + + .. ## pygame.pixelcopy.map_array ## + +.. function:: make_surface + + | :sl:`Copy an array to a new surface` + | :sg:`pygame.pixelcopy.make_surface(array) -> Surface` + + Create a new Surface that best resembles the data and format of the array. + The array can be 2D or 3D with any sized integer values. + + .. ## pygame.pixelcopy.make_surface ## + +.. ## pygame.pixelcopy ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pygame.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pygame.rst.txt new file mode 100644 index 0000000..33d6802 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/pygame.rst.txt @@ -0,0 +1,488 @@ +.. include:: common.txt + +:mod:`pygame` +============= + +.. module:: pygame + :synopsis: the top level pygame package + +| :sl:`the top level pygame package` + +The pygame package represents the top-level package for others to use. Pygame +itself is broken into many submodules, but this does not affect programs that +use pygame. + +As a convenience, most of the top-level variables in pygame have been placed +inside a module named :mod:`pygame.locals`. This is meant to be used with +``from pygame.locals import *``, in addition to ``import pygame``. + +When you ``import pygame`` all available pygame submodules are automatically +imported. Be aware that some of the pygame modules are considered *optional*, +and may not be available. In that case, pygame will provide a placeholder +object instead of the module, which can be used to test for availability. + +.. function:: init + + | :sl:`initialize all imported pygame modules` + | :sg:`init() -> (numpass, numfail)` + + Initialize all imported pygame modules. No exceptions will be raised if a + module fails, but the total number if successful and failed inits will be + returned as a tuple. You can always initialize individual modules manually, + but :func:`pygame.init` is a convenient way to get everything started. The + ``init()`` functions for individual modules will raise exceptions when they + fail. + + You may want to initialize the different modules separately to speed up your + program or to not use modules your game does not require. + + It is safe to call this ``init()`` more than once as repeated calls will have + no effect. This is true even if you have ``pygame.quit()`` all the modules. + + .. ## pygame.init ## + +.. function:: quit + + | :sl:`uninitialize all pygame modules` + | :sg:`quit() -> None` + + Uninitialize all pygame modules that have previously been initialized. When + the Python interpreter shuts down, this method is called regardless, so your + program should not need it, except when it wants to terminate its pygame + resources and continue. It is safe to call this function more than once as + repeated calls have no effect. + + .. note:: + Calling :func:`pygame.quit` will not exit your program. Consider letting + your program end in the same way a normal Python program will end. + + .. ## pygame.quit ## + +.. function:: get_init + + | :sl:`returns True if pygame is currently initialized` + | :sg:`get_init() -> bool` + + Returns ``True`` if pygame is currently initialized. + + .. versionadded:: 1.9.5 + + .. ## pygame.get_init ## + +.. exception:: error + + | :sl:`standard pygame exception` + | :sg:`raise pygame.error(message)` + + This exception is raised whenever a pygame or SDL operation fails. You + can catch any anticipated problems and deal with the error. The exception is + always raised with a descriptive message about the problem. + + Derived from the ``RuntimeError`` exception, which can also be used to catch + these raised errors. + + .. ## pygame.error ## + +.. function:: get_error + + | :sl:`get the current error message` + | :sg:`get_error() -> errorstr` + + SDL maintains an internal error message. This message will usually be + given to you when :func:`pygame.error` is raised, so this function will + rarely be needed. + + .. ## pygame.get_error ## + +.. function:: set_error + + | :sl:`set the current error message` + | :sg:`set_error(error_msg) -> None` + + SDL maintains an internal error message. This message will usually be + given to you when :func:`pygame.error` is raised, so this function will + rarely be needed. + + .. ## pygame.set_error ## + +.. function:: get_sdl_version + + | :sl:`get the version number of SDL` + | :sg:`get_sdl_version() -> major, minor, patch` + + Returns the three version numbers of the SDL library. This version is built + at compile time. It can be used to detect which features may or may not be + available through pygame. + + .. versionadded:: 1.7.0 + + .. ## pygame.get_sdl_version ## + +.. function:: get_sdl_byteorder + + | :sl:`get the byte order of SDL` + | :sg:`get_sdl_byteorder() -> int` + + Returns the byte order of the SDL library. It returns ``1234`` for little + endian byte order and ``4321`` for big endian byte order. + + .. versionadded:: 1.8 + + .. ## pygame.get_sdl_byteorder ## + +.. function:: register_quit + + | :sl:`register a function to be called when pygame quits` + | :sg:`register_quit(callable) -> None` + + When :func:`pygame.quit` is called, all registered quit functions are + called. Pygame modules do this automatically when they are initializing, so + this function will rarely be needed. + + .. ## pygame.register_quit ## + +.. function:: encode_string + + | :sl:`Encode a Unicode or bytes object` + | :sg:`encode_string([obj [, encoding [, errors [, etype]]]]) -> bytes or None` + + obj: If Unicode, encode; if bytes, return unaltered; if anything else, + return ``None``; if not given, raise ``SyntaxError``. + + encoding (string): If present, encoding to use. The default is + ``'unicode_escape'``. + + errors (string): If given, how to handle unencodable characters. The default + is ``'backslashreplace'``. + + etype (exception type): If given, the exception type to raise for an + encoding error. The default is ``UnicodeEncodeError``, as returned by + ``PyUnicode_AsEncodedString()``. For the default encoding and errors values + there should be no encoding errors. + + This function is used in encoding file paths. Keyword arguments are + supported. + + .. versionadded:: 1.9.2 (primarily for use in unit tests) + + .. ## pygame.encode_string ## + +.. function:: encode_file_path + + | :sl:`Encode a Unicode or bytes object as a file system path` + | :sg:`encode_file_path([obj [, etype]]) -> bytes or None` + + obj: If Unicode, encode; if bytes, return unaltered; if anything else, + return ``None``; if not given, raise ``SyntaxError``. + + etype (exception type): If given, the exception type to raise for an + encoding error. The default is ``UnicodeEncodeError``, as returned by + ``PyUnicode_AsEncodedString()``. + + This function is used to encode file paths in pygame. Encoding is to the + codec as returned by ``sys.getfilesystemencoding()``. Keyword arguments are + supported. + + .. versionadded:: 1.9.2 (primarily for use in unit tests) + + .. ## pygame.encode_file_path ## + + +:mod:`pygame.version` +===================== + +.. module:: pygame.version + :synopsis: small module containing version information + +| :sl:`small module containing version information` + +This module is automatically imported into the pygame package and can be used to +check which version of pygame has been imported. + +.. data:: ver + + | :sl:`version number as a string` + | :sg:`ver = '1.2'` + + This is the version represented as a string. It can contain a micro release + number as well, e.g. ``'1.5.2'`` + + .. ## pygame.version.ver ## + +.. data:: vernum + + | :sl:`tupled integers of the version` + | :sg:`vernum = (1, 5, 3)` + + This version information can easily be compared with other version + numbers of the same format. An example of checking pygame version numbers + would look like this: + + :: + + if pygame.version.vernum < (1, 5): + print('Warning, older version of pygame (%s)' % pygame.version.ver) + disable_advanced_features = True + + .. versionadded:: 1.9.6 Attributes ``major``, ``minor``, and ``patch``. + + :: + + vernum.major == vernum[0] + vernum.minor == vernum[1] + vernum.patch == vernum[2] + + .. versionchanged:: 1.9.6 + ``str(pygame.version.vernum)`` returns a string like ``"2.0.0"`` instead + of ``"(2, 0, 0)"``. + + .. versionchanged:: 1.9.6 + ``repr(pygame.version.vernum)`` returns a string like + ``"PygameVersion(major=2, minor=0, patch=0)"`` instead of ``"(2, 0, 0)"``. + + .. ## pygame.version.vernum ## + +.. data:: rev + + | :sl:`repository revision of the build` + | :sg:`rev = 'a6f89747b551+'` + + The Mercurial node identifier of the repository checkout from which this + package was built. If the identifier ends with a plus sign '+' then the + package contains uncommitted changes. Please include this revision number + in bug reports, especially for non-release pygame builds. + + Important note: pygame development has moved to github, this variable is + obsolete now. As soon as development shifted to github, this variable started + returning an empty string ``""``. + It has always been returning an empty string since ``v1.9.5``. + + .. versionchanged:: 1.9.5 + Always returns an empty string ``""``. + + .. ## pygame.version.rev ## + +.. data:: SDL + + | :sl:`tupled integers of the SDL library version` + | :sg:`SDL = '(2, 0, 12)'` + + This is the SDL library version represented as an extended tuple. It also has + attributes 'major', 'minor' & 'patch' that can be accessed like this: + + :: + + >>> pygame.version.SDL.major + 2 + + printing the whole thing returns a string like this: + + :: + + >>> pygame.version.SDL + SDLVersion(major=2, minor=0, patch=12) + + .. versionadded:: 2.0.0 + + .. ## pygame.version.SDL ## + +.. ## pygame.version ## + +.. ## pygame ## + +.. _environment-variables: + +**Setting Environment Variables** + +Some aspects of pygame's behaviour can be controlled by setting environment variables, they cover a wide +range of the library's functionality. Some of the variables are from pygame itself, while others come from +the underlying C SDL library that pygame uses. + +In python, environment variables are usually set in code like this:: + + import os + os.environ['NAME_OF_ENVIRONMENT_VARIABLE'] = 'value_to_set' + +Or to preserve users ability to override the variable:: + + import os + os.environ['ENV_VAR'] = os.environ.get('ENV_VAR', 'value') + +If the variable is more useful for users of an app to set than the developer then they can set it like this: + +**Windows**:: + + set NAME_OF_ENVIRONMENT_VARIABLE=value_to_set + python my_application.py + +**Linux/Mac**:: + + ENV_VAR=value python my_application.py + +For some variables they need to be set before initialising pygame, some must be set before even importing pygame, +and others can simply be set right before the area of code they control is run. + +Below is a list of environment variables, their settable values, and a brief description of what they do. + +| + +**Pygame Environment Variables** + +These variables are defined by pygame itself. + +| + +:: + + PYGAME_DISPLAY - Experimental (subject to change) + Set index of the display to use, "0" is the default. + +This sets the display where pygame will open its window +or screen. The value set here will be used if set before +calling :func:`pygame.display.set_mode()`, and as long as no +'display' parameter is passed into :func:`pygame.display.set_mode()`. + +| + +:: + + PYGAME_FORCE_SCALE - + Set to "photo" or "default". + +This forces set_mode() to use the SCALED display mode and, +if "photo" is set, makes the scaling use the slowest, but +highest quality anisotropic scaling algorithm, if it is +available. Must be set before calling :func:`pygame.display.set_mode()`. + +| + +:: + + PYGAME_BLEND_ALPHA_SDL2 - New in pygame 2.0.0 + Set to "1" to enable the SDL2 blitter. + +This makes pygame use the SDL2 blitter for all alpha +blending. The SDL2 blitter is sometimes faster than +the default blitter but uses a different formula so +the final colours may differ. Must be set before +:func:`pygame.init()` is called. + +| + +:: + + PYGAME_HIDE_SUPPORT_PROMPT - + Set to "1" to hide the prompt. + +This stops the welcome message popping up in the +console that tells you which version of python, +pygame & SDL you are using. Must be set before +importing pygame. + +| + +:: + + PYGAME_FREETYPE - + Set to "1" to enable. + +This switches the pygame.font module to a pure +freetype implementation that bypasses SDL_ttf. +See the font module for why you might want to +do this. Must be set before importing pygame. + +| + +:: + + PYGAME_CAMERA - + Set to "opencv" or "vidcapture" + +Forces the library backend used in the camera +module, overriding the platform defaults. Must +be set before calling :func:`pygame.camera.init()`. + +In pygame 2.0.3, backends can be set programmatically instead, and the old +OpenCV backend has been replaced with one on top of "opencv-python," rather +than the old "highgui" OpenCV port. Also, there is a new native Windows +backend available. + +| +| + +**SDL Environment Variables** + +These variables are defined by SDL. + +For documentation on the environment variables available in +pygame 1 try `here +`__. +For Pygame 2, some selected environment variables are listed below. + +| + +:: + + SDL_VIDEO_CENTERED - + Set to "1" to enable centering the window. + +This will make the pygame window open in the centre of the display. +Must be set before calling :func:`pygame.display.set_mode()`. + +| + +:: + + SDL_VIDEO_WINDOW_POS - + Set to "x,y" to position the top left corner of the window. + +This allows control over the placement of the pygame window within +the display. Must be set before calling :func:`pygame.display.set_mode()`. + +| + +:: + + SDL_VIDEODRIVER - + Set to "drivername" to change the video driver used. + +On some platforms there are multiple video drivers available and +this allows users to pick between them. More information is available +`here `__. Must be set before +calling :func:`pygame.init()` or :func:`pygame.display.init()`. + +| + +:: + + SDL_AUDIODRIVER - + Set to "drivername" to change the audio driver used. + +On some platforms there are multiple audio drivers available and +this allows users to pick between them. More information is available +`here `__. Must be set before +calling :func:`pygame.init()` or :func:`pygame.mixer.init()`. + +| + +:: + + SDL_VIDEO_ALLOW_SCREENSAVER + Set to "1" to allow screensavers while pygame apps are running. + +By default pygame apps disable screensavers while +they are running. Setting this environment variable allows users or +developers to change that and make screensavers run again. + +| + +:: + + SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR + Set to "0" to re-enable the compositor. + +By default SDL tries to disable the X11 compositor for all pygame +apps. This is usually a good thing as it's faster, however if you +have an app which *doesn't* update every frame and are using linux +you may want to disable this bypass. The bypass has reported problems +on KDE linux. This variable is only used on x11/linux platforms. diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/rect.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/rect.rst.txt new file mode 100644 index 0000000..c1c30a5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/rect.rst.txt @@ -0,0 +1,391 @@ +.. include:: common.txt + +:mod:`pygame.Rect` +================== + +.. currentmodule:: pygame + +.. class:: Rect + + | :sl:`pygame object for storing rectangular coordinates` + | :sg:`Rect(left, top, width, height) -> Rect` + | :sg:`Rect((left, top), (width, height)) -> Rect` + | :sg:`Rect(object) -> Rect` + + Pygame uses Rect objects to store and manipulate rectangular areas. A Rect + can be created from a combination of left, top, width, and height values. + Rects can also be created from python objects that are already a Rect or + have an attribute named "rect". + + Any pygame function that requires a Rect argument also accepts any of these + values to construct a Rect. This makes it easier to create Rects on the fly + as arguments to functions. + + The Rect functions that change the position or size of a Rect return a new + copy of the Rect with the affected changes. The original Rect is not + modified. Some methods have an alternate "in-place" version that returns + None but affects the original Rect. These "in-place" methods are denoted + with the "ip" suffix. + + The Rect object has several virtual attributes which can be used to move and + align the Rect: + + :: + + x,y + top, left, bottom, right + topleft, bottomleft, topright, bottomright + midtop, midleft, midbottom, midright + center, centerx, centery + size, width, height + w,h + + All of these attributes can be assigned to: + + :: + + rect1.right = 10 + rect2.center = (20,30) + + Assigning to size, width or height changes the dimensions of the rectangle; + all other assignments move the rectangle without resizing it. Notice that + some attributes are integers and others are pairs of integers. + + If a Rect has a nonzero width or height, it will return ``True`` for a + nonzero test. Some methods return a Rect with 0 size to represent an invalid + rectangle. A Rect with a 0 size will not collide when using collision + detection methods (e.g. :meth:`collidepoint`, :meth:`colliderect`, etc.). + + The coordinates for Rect objects are all integers. The size values can be + programmed to have negative values, but these are considered illegal Rects + for most operations. + + There are several collision tests between other rectangles. Most python + containers can be searched for collisions against a single Rect. + + The area covered by a Rect does not include the right- and bottom-most edge + of pixels. If one Rect's bottom border is another Rect's top border (i.e., + rect1.bottom=rect2.top), the two meet exactly on the screen but do not + overlap, and ``rect1.colliderect(rect2)`` returns false. + + .. versionadded:: 1.9.2 + The Rect class can be subclassed. Methods such as ``copy()`` and ``move()`` + will recognize this and return instances of the subclass. + However, the subclass's ``__init__()`` method is not called, + and ``__new__()`` is assumed to take no arguments. So these methods should be + overridden if any extra attributes need to be copied. + + .. method:: copy + + | :sl:`copy the rectangle` + | :sg:`copy() -> Rect` + + Returns a new rectangle having the same position and size as the original. + + New in pygame 1.9 + + .. ## Rect.copy ## + + .. method:: move + + | :sl:`moves the rectangle` + | :sg:`move(x, y) -> Rect` + + Returns a new rectangle that is moved by the given offset. The x and y + arguments can be any integer value, positive or negative. + + .. ## Rect.move ## + + .. method:: move_ip + + | :sl:`moves the rectangle, in place` + | :sg:`move_ip(x, y) -> None` + + Same as the ``Rect.move()`` method, but operates in place. + + .. ## Rect.move_ip ## + + .. method:: inflate + + | :sl:`grow or shrink the rectangle size` + | :sg:`inflate(x, y) -> Rect` + + Returns a new rectangle with the size changed by the given offset. The + rectangle remains centered around its current center. Negative values + will shrink the rectangle. Note, uses integers, if the offset given is + too small(< 2 > -2), center will be off. + + .. ## Rect.inflate ## + + .. method:: inflate_ip + + | :sl:`grow or shrink the rectangle size, in place` + | :sg:`inflate_ip(x, y) -> None` + + Same as the ``Rect.inflate()`` method, but operates in place. + + .. ## Rect.inflate_ip ## + + .. method:: update + + | :sl:`sets the position and size of the rectangle` + | :sg:`update(left, top, width, height) -> None` + | :sg:`update((left, top), (width, height)) -> None` + | :sg:`update(object) -> None` + + Sets the position and size of the rectangle, in place. See + parameters for :meth:`pygame.Rect` for the parameters of this function. + + .. versionadded:: 2.0.1 + + .. ## Rect.update ## + + .. method:: clamp + + | :sl:`moves the rectangle inside another` + | :sg:`clamp(Rect) -> Rect` + + Returns a new rectangle that is moved to be completely inside the + argument Rect. If the rectangle is too large to fit inside, it is + centered inside the argument Rect, but its size is not changed. + + .. ## Rect.clamp ## + + .. method:: clamp_ip + + | :sl:`moves the rectangle inside another, in place` + | :sg:`clamp_ip(Rect) -> None` + + Same as the ``Rect.clamp()`` method, but operates in place. + + .. ## Rect.clamp_ip ## + + .. method:: clip + + | :sl:`crops a rectangle inside another` + | :sg:`clip(Rect) -> Rect` + + Returns a new rectangle that is cropped to be completely inside the + argument Rect. If the two rectangles do not overlap to begin with, a Rect + with 0 size is returned. + + .. ## Rect.clip ## + + .. method:: clipline + + | :sl:`crops a line inside a rectangle` + | :sg:`clipline(x1, y1, x2, y2) -> ((cx1, cy1), (cx2, cy2))` + | :sg:`clipline(x1, y1, x2, y2) -> ()` + | :sg:`clipline((x1, y1), (x2, y2)) -> ((cx1, cy1), (cx2, cy2))` + | :sg:`clipline((x1, y1), (x2, y2)) -> ()` + | :sg:`clipline((x1, y1, x2, y2)) -> ((cx1, cy1), (cx2, cy2))` + | :sg:`clipline((x1, y1, x2, y2)) -> ()` + | :sg:`clipline(((x1, y1), (x2, y2))) -> ((cx1, cy1), (cx2, cy2))` + | :sg:`clipline(((x1, y1), (x2, y2))) -> ()` + + Returns the coordinates of a line that is cropped to be completely inside + the rectangle. If the line does not overlap the rectangle, then an empty + tuple is returned. + + The line to crop can be any of the following formats (floats can be used + in place of ints, but they will be truncated): + + - four ints + - 2 lists/tuples/Vector2s of 2 ints + - a list/tuple of four ints + - a list/tuple of 2 lists/tuples/Vector2s of 2 ints + + :returns: a tuple with the coordinates of the given line cropped to be + completely inside the rectangle is returned, if the given line does + not overlap the rectangle, an empty tuple is returned + :rtype: tuple(tuple(int, int), tuple(int, int)) or () + + :raises TypeError: if the line coordinates are not given as one of the + above described line formats + + .. note :: + This method can be used for collision detection between a rect and a + line. See example code below. + + .. note :: + The ``rect.bottom`` and ``rect.right`` attributes of a + :mod:`pygame.Rect` always lie one pixel outside of its actual border. + + :: + + # Example using clipline(). + clipped_line = rect.clipline(line) + + if clipped_line: + # If clipped_line is not an empty tuple then the line + # collides/overlaps with the rect. The returned value contains + # the endpoints of the clipped line. + start, end = clipped_line + x1, y1 = start + x2, y2 = end + else: + print("No clipping. The line is fully outside the rect.") + + .. versionadded:: 2.0.0 + + .. ## Rect.clipline ## + + .. method:: union + + | :sl:`joins two rectangles into one` + | :sg:`union(Rect) -> Rect` + + Returns a new rectangle that completely covers the area of the two + provided rectangles. There may be area inside the new Rect that is not + covered by the originals. + + .. ## Rect.union ## + + .. method:: union_ip + + | :sl:`joins two rectangles into one, in place` + | :sg:`union_ip(Rect) -> None` + + Same as the ``Rect.union()`` method, but operates in place. + + .. ## Rect.union_ip ## + + .. method:: unionall + + | :sl:`the union of many rectangles` + | :sg:`unionall(Rect_sequence) -> Rect` + + Returns the union of one rectangle with a sequence of many rectangles. + + .. ## Rect.unionall ## + + .. method:: unionall_ip + + | :sl:`the union of many rectangles, in place` + | :sg:`unionall_ip(Rect_sequence) -> None` + + The same as the ``Rect.unionall()`` method, but operates in place. + + .. ## Rect.unionall_ip ## + + .. method:: fit + + | :sl:`resize and move a rectangle with aspect ratio` + | :sg:`fit(Rect) -> Rect` + + Returns a new rectangle that is moved and resized to fit another. The + aspect ratio of the original Rect is preserved, so the new rectangle may + be smaller than the target in either width or height. + + .. ## Rect.fit ## + + .. method:: normalize + + | :sl:`correct negative sizes` + | :sg:`normalize() -> None` + + This will flip the width or height of a rectangle if it has a negative + size. The rectangle will remain in the same place, with only the sides + swapped. + + .. ## Rect.normalize ## + + .. method:: contains + + | :sl:`test if one rectangle is inside another` + | :sg:`contains(Rect) -> bool` + + Returns true when the argument is completely inside the Rect. + + .. ## Rect.contains ## + + .. method:: collidepoint + + | :sl:`test if a point is inside a rectangle` + | :sg:`collidepoint(x, y) -> bool` + | :sg:`collidepoint((x,y)) -> bool` + + Returns true if the given point is inside the rectangle. A point along + the right or bottom edge is not considered to be inside the rectangle. + + .. note :: + For collision detection between a rect and a line the :meth:`clipline` + method can be used. + + .. ## Rect.collidepoint ## + + .. method:: colliderect + + | :sl:`test if two rectangles overlap` + | :sg:`colliderect(Rect) -> bool` + + Returns true if any portion of either rectangle overlap (except the + top+bottom or left+right edges). + + .. note :: + For collision detection between a rect and a line the :meth:`clipline` + method can be used. + + .. ## Rect.colliderect ## + + .. method:: collidelist + + | :sl:`test if one rectangle in a list intersects` + | :sg:`collidelist(list) -> index` + + Test whether the rectangle collides with any in a sequence of rectangles. + The index of the first collision found is returned. If no collisions are + found an index of -1 is returned. + + .. ## Rect.collidelist ## + + .. method:: collidelistall + + | :sl:`test if all rectangles in a list intersect` + | :sg:`collidelistall(list) -> indices` + + Returns a list of all the indices that contain rectangles that collide + with the Rect. If no intersecting rectangles are found, an empty list is + returned. + + .. ## Rect.collidelistall ## + + .. method:: collidedict + + | :sl:`test if one rectangle in a dictionary intersects` + | :sg:`collidedict(dict) -> (key, value)` + | :sg:`collidedict(dict) -> None` + | :sg:`collidedict(dict, use_values=0) -> (key, value)` + | :sg:`collidedict(dict, use_values=0) -> None` + + Returns the first key and value pair that intersects with the calling + Rect object. If no collisions are found, ``None`` is returned. If + ``use_values`` is 0 (default) then the dict's keys will be used in the + collision detection, otherwise the dict's values will be used. + + .. note :: + Rect objects cannot be used as keys in a dictionary (they are not + hashable), so they must be converted to a tuple. + e.g. ``rect.collidedict({tuple(key_rect) : value})`` + + .. ## Rect.collidedict ## + + .. method:: collidedictall + + | :sl:`test if all rectangles in a dictionary intersect` + | :sg:`collidedictall(dict) -> [(key, value), ...]` + | :sg:`collidedictall(dict, use_values=0) -> [(key, value), ...]` + + Returns a list of all the key and value pairs that intersect with the + calling Rect object. If no collisions are found an empty list is returned. + If ``use_values`` is 0 (default) then the dict's keys will be used in the + collision detection, otherwise the dict's values will be used. + + .. note :: + Rect objects cannot be used as keys in a dictionary (they are not + hashable), so they must be converted to a tuple. + e.g. ``rect.collidedictall({tuple(key_rect) : value})`` + + .. ## Rect.collidedictall ## + + .. ## pygame.Rect ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/scrap.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/scrap.rst.txt new file mode 100644 index 0000000..2b52337 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/scrap.rst.txt @@ -0,0 +1,240 @@ +.. include:: common.txt + +:mod:`pygame.scrap` +=================== + +.. module:: pygame.scrap + :synopsis: pygame module for clipboard support. + +| :sl:`pygame module for clipboard support.` + +**EXPERIMENTAL!**: This API may change or disappear in later pygame releases. If +you use this, your code may break with the next pygame release. + +The scrap module is for transferring data to/from the clipboard. This allows +for cutting and pasting data between pygame and other applications. Some basic +data (MIME) types are defined and registered: + +:: + + pygame string + constant value description + -------------------------------------------------- + SCRAP_TEXT "text/plain" plain text + SCRAP_BMP "image/bmp" BMP encoded image data + SCRAP_PBM "image/pbm" PBM encoded image data + SCRAP_PPM "image/ppm" PPM encoded image data + +``pygame.SCRAP_PPM``, ``pygame.SCRAP_PBM`` and ``pygame.SCRAP_BMP`` are +suitable for surface buffers to be shared with other applications. +``pygame.SCRAP_TEXT`` is an alias for the plain text clipboard type. + +Depending on the platform, additional types are automatically registered when +data is placed into the clipboard to guarantee a consistent sharing behaviour +with other applications. The following listed types can be used as strings to +be passed to the respective :mod:`pygame.scrap` module functions. + +For **Windows** platforms, these additional types are supported automatically +and resolve to their internal definitions: + +:: + + "text/plain;charset=utf-8" UTF-8 encoded text + "audio/wav" WAV encoded audio + "image/tiff" TIFF encoded image data + +For **X11** platforms, these additional types are supported automatically and +resolve to their internal definitions: + +:: + + "text/plain;charset=utf-8" UTF-8 encoded text + "UTF8_STRING" UTF-8 encoded text + "COMPOUND_TEXT" COMPOUND text + +User defined types can be used, but the data might not be accessible by other +applications unless they know what data type to look for. +Example: Data placed into the clipboard by +``pygame.scrap.put("my_data_type", byte_data)`` can only be accessed by +applications which query the clipboard for the ``"my_data_type"`` data type. + +For an example of how the scrap module works refer to the examples page +(:func:`pygame.examples.scrap_clipboard.main`) or the code directly in GitHub +(`pygame/examples/scrap_clipboard.py `_). + +.. versionadded:: 1.8 + +.. note:: + The scrap module is currently only supported for Windows, X11 and Mac OS X. + On Mac OS X only text works at the moment - other types may be supported in + future releases. + +.. function:: init + + | :sl:`Initializes the scrap module.` + | :sg:`init() -> None` + + Initialize the scrap module. + + :raises pygame.error: if unable to initialize scrap module + + .. note:: The scrap module requires :func:`pygame.display.set_mode()` be + called before being initialized. + + .. ## pygame.scrap.init ## + +.. function:: get_init + + | :sl:`Returns True if the scrap module is currently initialized.` + | :sg:`get_init() -> bool` + + Gets the scrap module's initialization state. + + :returns: ``True`` if the :mod:`pygame.scrap` module is currently + initialized, ``False`` otherwise + :rtype: bool + + .. versionadded:: 1.9.5 + + .. ## pygame.scrap.get_init ## + +.. function:: get + + | :sl:`Gets the data for the specified type from the clipboard.` + | :sg:`get(type) -> bytes | None` + + Retrieves the data for the specified type from the clipboard. The data is + returned as a byte string and might need further processing (such as + decoding to Unicode). + + :param string type: data type to retrieve from the clipboard + + :returns: data (bytes object) for the given type identifier or ``None`` if + no data for the given type is available + :rtype: bytes | None + + :: + + text = pygame.scrap.get(pygame.SCRAP_TEXT) + if text: + print("There is text in the clipboard.") + else: + print("There does not seem to be text in the clipboard.") + + .. ## pygame.scrap.get ## + +.. function:: get_types + + | :sl:`Gets a list of the available clipboard types.` + | :sg:`get_types() -> list` + + Gets a list of data type string identifiers for the data currently + available on the clipboard. Each identifier can be used in the + :func:`pygame.scrap.get()` method to get the clipboard content of the + specific type. + + :returns: list of strings of the available clipboard data types, if there + is no data in the clipboard an empty list is returned + :rtype: list + + :: + + for t in pygame.scrap.get_types(): + if "text" in t: + # There is some content with the word "text" in its type string. + print(pygame.scrap.get(t)) + + .. ## pygame.scrap.get_types ## + +.. function:: put + + | :sl:`Places data into the clipboard.` + | :sg:`put(type, data) -> None` + + Places data for a given clipboard type into the clipboard. The data must + be a string buffer. The type is a string identifying the type of data to be + placed into the clipboard. This can be one of the predefined + ``pygame.SCRAP_PBM``, ``pygame.SCRAP_PPM``, ``pygame.SCRAP_BMP`` or + ``pygame.SCRAP_TEXT`` values or a user defined string identifier. + + :param string type: type identifier of the data to be placed into the + clipboard + :param data: data to be place into the clipboard, a bytes object + :type data: bytes + + :raises pygame.error: if unable to put the data into the clipboard + + :: + + with open("example.bmp", "rb") as fp: + pygame.scrap.put(pygame.SCRAP_BMP, fp.read()) + # The image data is now on the clipboard for other applications to access + # it. + pygame.scrap.put(pygame.SCRAP_TEXT, b"A text to copy") + pygame.scrap.put("Plain text", b"Data for user defined type 'Plain text'") + + .. ## pygame.scrap.put ## + +.. function:: contains + + | :sl:`Checks whether data for a given type is available in the clipboard.` + | :sg:`contains(type) -> bool` + + Checks whether data for the given type is currently available in the + clipboard. + + :param string type: data type to check availability of + + :returns: ``True`` if data for the passed type is available in the + clipboard, ``False`` otherwise + :rtype: bool + + :: + + if pygame.scrap.contains(pygame.SCRAP_TEXT): + print("There is text in the clipboard.") + if pygame.scrap.contains("own_data_type"): + print("There is stuff in the clipboard.") + + .. ## pygame.scrap.contains ## + +.. function:: lost + + | :sl:`Indicates if the clipboard ownership has been lost by the pygame application.` + | :sg:`lost() -> bool` + + Indicates if the clipboard ownership has been lost by the pygame + application. + + :returns: ``True``, if the clipboard ownership has been lost by the pygame + application, ``False`` if the pygame application still owns the clipboard + :rtype: bool + + :: + + if pygame.scrap.lost(): + print("The clipboard is in use by another application.") + + .. ## pygame.scrap.lost ## + +.. function:: set_mode + + | :sl:`Sets the clipboard access mode.` + | :sg:`set_mode(mode) -> None` + + Sets the access mode for the clipboard. This is only of interest for X11 + environments where clipboard modes ``pygame.SCRAP_SELECTION`` (for mouse + selections) and ``pygame.SCRAP_CLIPBOARD`` (for the clipboard) are + available. Setting the mode to ``pygame.SCRAP_SELECTION`` in other + environments will not change the mode from ``pygame.SCRAP_CLIPBOARD``. + + :param mode: access mode, supported values are ``pygame.SCRAP_CLIPBOARD`` + and ``pygame.SCRAP_SELECTION`` (``pygame.SCRAP_SELECTION`` only has an + effect when used on X11 platforms) + + :raises ValueError: if the ``mode`` parameter is not + ``pygame.SCRAP_CLIPBOARD`` or ``pygame.SCRAP_SELECTION`` + + .. ## pygame.scrap.set_mode ## + +.. ## pygame.scrap ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sdl2_controller.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sdl2_controller.rst.txt new file mode 100644 index 0000000..dcfee7d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sdl2_controller.rst.txt @@ -0,0 +1,287 @@ +.. include:: common.txt + +:mod:`pygame._sdl2.controller` +============================== + +.. module:: pygame._sdl2.controller + :synopsis: pygame module to work with controllers + +| :sl:`Pygame module to work with controllers.` + +This module offers control over common controller types like the dualshock 4 or +the xbox 360 controllers: They have two analog sticks, two triggers, two shoulder buttons, +a dpad, 4 buttons on the side, 2 (or 3) buttons in the middle. + +Pygame uses xbox controllers naming conventions (like a, b, x, y for buttons) but +they always refer to the same buttons. For example ``CONTROLLER_BUTTON_X`` is +always the leftmost button of the 4 buttons on the right. + +Controllers can generate the following events:: + + CONTROLLERAXISMOTION, CONTROLLERBUTTONDOWN, CONTROLLERBUTTONUP, + CONTROLLERDEVICEREMAPPED, CONTROLLERDEVICEADDED, CONTROLLERDEVICEREMOVED + +Additionally if pygame is built with SDL 2.0.14 or higher the following events can also be generated +(to get the version of sdl pygame is built with use :meth:`pygame.version.SDL`):: + + CONTROLLERTOUCHPADDOWN, CONTROLLERTOUCHPADMOTION, CONTROLLERTOUCHPADUP + +These events can be enabled/disabled by :meth:`pygame._sdl2.controller.set_eventstate` +Note that controllers can generate joystick events as well. This function only toggles +events related to controllers. + +.. note:: + See the :mod:`pygame.joystick` for a more versatile but more advanced api. + +.. versionadded:: 2 This module requires SDL2. + +.. function:: init + + | :sl:`initialize the controller module` + | :sg:`init() -> None` + + Initialize the controller module. + + .. ## pygame._sdl2.controller.init ## + +.. function:: quit + + | :sl:`Uninitialize the controller module.` + | :sg:`quit() -> None` + + Uninitialize the controller module. + + .. ## pygame._sdl2.controller.quit ## + +.. function:: get_init + + | :sl:`Returns True if the controller module is initialized.` + | :sg:`get_init() -> bool` + + Test if ``pygame._sdl2.controller.init()`` was called. + + .. ## pygame._sdl2.controller.get_init ## + +.. function:: set_eventstate + + | :sl:`Sets the current state of events related to controllers` + | :sg:`set_eventstate(state) -> None` + + Enable or disable events connected to controllers. + + .. note:: + Controllers can still generate joystick events, which will not be toggled by this function. + + .. versionchanged:: 2.0.2: Changed return type from int to None + + .. ## pygame._sdl2.controller.set_eventstate ## + +.. function:: get_eventstate + + | :sl:`Gets the current state of events related to controllers` + | :sg:`get_eventstate() -> bool` + + Returns the current state of events related to controllers, True meaning + events will be posted. + + .. versionadded:: 2.0.2 + + .. ## pygame._sdl2.controller.get_eventstate ## + +.. function:: get_count + + | :sl:`Get the number of joysticks connected` + | :sg:`get_count() -> int` + + Get the number of joysticks connected. + + .. ## pygame._sdl2.controller.get_count ## + +.. function:: is_controller + + | :sl:`Check if the given joystick is supported by the game controller interface` + | :sg:`is_controller(index) -> bool` + + Returns True if the index given can be used to create a controller object. + + .. ## pygame._sdl2.controller.is_controller ## + +.. function:: name_forindex + + | :sl:`Get the name of the contoller` + | :sg:`name_forindex(index) -> name or None` + + Returns the name of controller, or None if there's no name or the + index is invalid. + + .. ## pygame._sdl2.controller.name_forindex ## + +.. class:: Controller + + | :sl:`Create a new Controller object.` + | :sg:`Controller(index) -> Controller` + + Create a new Controller object. Index should be integer between + 0 and ``pygame._sdl2.contoller.get_count()``. Controllers also + can be created from a ``pygame.joystick.Joystick`` using + ``pygame._sdl2.controller.from_joystick``. Controllers are + initialized on creation. + + .. method:: quit + + | :sl:`uninitialize the Controller` + | :sg:`quit() -> None` + + Close a Controller object. After this the pygame event queue will no longer + receive events from the device. + + It is safe to call this more than once. + + .. ## Controller.quit ## + + .. method:: get_init + + | :sl:`check if the Controller is initialized` + | :sg:`get_init() -> bool` + + Returns True if the Controller object is currently initialised. + + .. ## Controller.get_init ## + + .. staticmethod:: from_joystick + + | :sl:`Create a Controller from a pygame.joystick.Joystick object` + | :sg:`from_joystick(joystick) -> Controller` + + Create a Controller object from a ``pygame.joystick.Joystick`` object + + .. ## Controller.from_joystick ## + + .. method:: attached + + | :sl:`Check if the Controller has been opened and is currently connected.` + | :sg:`attached() -> bool` + + Returns True if the Controller object is opened and connected. + + .. ## Controller.attached ## + + .. method:: as_joystick + + | :sl:`Returns a pygame.joystick.Joystick() object` + | :sg:`as_joystick() -> Joystick object` + + Returns a pygame.joystick.Joystick() object created from this controller's index + + .. ## Controller.as_joystick ## + + .. method:: get_axis + + | :sl:`Get the current state of a joystick axis` + | :sg:`get_axis(axis) -> int` + + Get the current state of a trigger or joystick axis. + The axis argument must be one of the following constants:: + + CONTROLLER_AXIS_LEFTX, CONTROLLER_AXIS_LEFTY, + CONTROLLER_AXIS_RIGHTX, CONTROLLER_AXIS_RIGHTY, + CONTROLLER_AXIS_TRIGGERLEFT, CONTROLLER_AXIS_TRIGGERRIGHT + + Joysticks can return a value between -32768 and 32767. Triggers however + can only return a value between 0 and 32768. + + .. ## Controller.get_axis ## + + .. method:: get_button + + | :sl:`Get the current state of a button` + | :sg:`get_button(button) -> bool` + + Get the current state of a button, True meaning it is pressed down. + The button argument must be one of the following constants:: + + CONTROLLER_BUTTON_A, CONTROLLER_BUTTON_B, + CONTROLLER_BUTTON_X, CONTROLLER_BUTTON_Y + CONTROLLER_BUTTON_DPAD_UP, CONTROLLER_BUTTON_DPAD_DOWN, + CONTROLLER_BUTTON_DPAD_LEFT, CONTROLLER_BUTTON_DPAD_RIGHT, + CONTROLLER_BUTTON_LEFTSHOULDER, CONTROLLER_BUTTON_RIGHTSHOULDER, + CONTROLLER_BUTTON_LEFTSTICK, CONTROLLER_BUTTON_RIGHTSTICK, + CONTROLLER_BUTTON_BACK, CONTROLLER_BUTTON_GUIDE, + CONTROLLER_BUTTON_START + + + .. ## Controller.get_button ## + + .. method:: get_mapping + + | :sl:`Get the mapping assigned to the controller` + | :sg:`get_mapping() -> mapping` + + Returns a dict containing the mapping of the Controller. For more + information see :meth:`Controller.set_mapping()` + + .. versionchanged:: 2.0.2: Return type changed from ``str`` to ``dict`` + + .. ## Contorller.get_mapping ## + + .. method:: set_mapping + + | :sl:`Assign a mapping to the controller` + | :sg:`set_mapping(mapping) -> int` + + Rebind buttons, axes, triggers and dpads. The mapping should be a + dict containing all buttons, hats and axes. The easiest way to get this + is to use the dict returned by :meth:`Controller.get_mapping`. To edit + this mapping assign a value to the original button. The value of the + dictionary must be a button, hat or axis represented in the following way: + + * For a button use: bX where X is the index of the button. + * For a hat use: hX.Y where X is the index and the Y is the direction (up: 1, right: 2, down: 3, left: 4). + * For an axis use: aX where x is the index of the axis. + + An example of mapping:: + + mapping = controller.get_mapping() # Get current mapping + mapping["a"] = "b3" # Remap button a to y + mapping["y"] = "b0" # Remap button y to a + controller.set_mapping(mapping) # Set the mapping + + + The function will return 1 if a new mapping is added or 0 if an existing one is updated. + + .. versionchanged:: 2.0.2: Renamed from ``add_mapping`` to ``set_mapping`` + .. versionchanged:: 2.0.2: Argument type changed from ``str`` to ``dict`` + + .. ## Contorller.set_mapping ## + + .. method:: rumble + + | :sl:`Start a rumbling effect` + | :sg:`rumble(low_frequency, high_frequency, duration) -> bool` + + Start a rumble effect on the controller, with the specified strength ranging + from 0 to 1. Duration is length of the effect, in ms. Setting the duration + to 0 will play the effect until another one overwrites it or + :meth:`Controller.stop_rumble` is called. If an effect is already + playing, then it will be overwritten. + + Returns True if the rumble was played successfully or False if the + controller does not support it or :meth:`pygame.version.SDL` is below 2.0.9. + + .. versionadded:: 2.0.2 + + .. ## Contorller.rumble ## + + .. method:: stop_rumble + + | :sl:`Stop any rumble effect playing` + | :sg:`stop_rumble() -> None` + + Stops any rumble effect playing on the controller. See + :meth:`Controller.rumble` for more information. + + .. versionadded:: 2.0.2 + + .. ## Contorller.stop_rumble ## + +.. ## pygame._sdl2.controller ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sdl2_video.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sdl2_video.rst.txt new file mode 100644 index 0000000..c822be2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sdl2_video.rst.txt @@ -0,0 +1,334 @@ +.. include:: common.txt + +:mod:`pygame.sdl2_video` +======================== + +.. module:: pygame._sdl2.video + :synopsis: Experimental pygame module for porting new SDL video systems + +.. warning:: + This module isn't ready for prime time yet, it's still in development. + These docs are primarily meant to help the pygame developers and super-early adopters + who are in communication with the developers. This API will change. + +| :sl:`Experimental pygame module for porting new SDL video systems` + +.. class:: Window + + | :sl:`pygame object that represents a window` + | :sg:`Window(title="pygame", size=(640, 480), position=None, fullscreen=False, fullscreen_desktop=False, keywords) -> Window` + + .. classmethod:: from_display_module + + | :sl:`Creates window using window created by pygame.display.set_mode().` + | :sg:`from_display_module() -> Window` + + .. attribute:: grab + + | :sl:`Gets or sets whether the mouse is confined to the window.` + | :sg:`grab -> bool` + + .. attribute:: relative_mouse + + | :sl:`Gets or sets the window's relative mouse motion state.` + | :sg:`relative_mouse -> bool` + + .. method:: set_windowed + + | :sl:`Enable windowed mode (exit fullscreen).` + | :sg:`set_windowed() -> None` + + .. method:: set_fullscreen + + | :sl:`Enter fullscreen.` + | :sg:`set_fullscreen(desktop=False) -> None` + + .. attribute:: title + + | :sl:`Gets or sets whether the window title.` + | :sg:`title -> string` + + .. method:: destroy + + | :sl:`Destroys the window.` + | :sg:`destroy() -> None` + + .. method:: hide + + | :sl:`Hide the window.` + | :sg:`hide() -> None` + + .. method:: show + + | :sl:`Show the window.` + | :sg:`show() -> None` + + .. method:: focus + + | :sl:`Raise the window above other windows and set the input focus. The "input_only" argument is only supported on X11.` + | :sg:`focus(input_only=False) -> None` + + .. method:: restore + + | :sl:`Restore the size and position of a minimized or maximized window.` + | :sg:`restore() -> None` + + .. method:: maximize + + | :sl:`Maximize the window.` + | :sg:`maximize() -> None` + + .. method:: minimize + + | :sl:`Minimize the window.` + | :sg:`maximize() -> None` + + .. attribute:: resizable + + | :sl:`Gets and sets whether the window is resizable.` + | :sg:`resizable -> bool` + + .. attribute:: borderless + + | :sl:`Add or remove the border from the window.` + | :sg:`borderless -> bool` + + .. method:: set_icon + + | :sl:`Set the icon for the window.` + | :sg:`set_icon(surface) -> None` + + .. attribute:: id + + | :sl:`Get the unique window ID. *Read-only*` + | :sg:`id -> int` + + .. attribute:: size + + | :sl:`Gets and sets the window size.` + | :sg:`size -> (int, int)` + + .. attribute:: position + + | :sl:`Gets and sets the window position.` + | :sg:`position -> (int, int) or WINDOWPOS_CENTERED or WINDOWPOS_UNDEFINED` + + .. attribute:: opacity + + | :sl:`Gets and sets the window opacity. Between 0.0 (fully transparent) and 1.0 (fully opaque).` + | :sg:`opacity -> float` + + .. attribute:: brightness + + | :sl:`Gets and sets the brightness (gamma multiplier) for the display that owns the window.` + | :sg:`brightness -> float` + + .. attribute:: display_index + + | :sl:`Get the index of the display that owns the window. *Read-only*` + | :sg:`display_index -> int` + + .. method:: set_modal_for + + | :sl:`Set the window as a modal for a parent window. This function is only supported on X11.` + | :sg:`set_modal_for(Window) -> None` + +.. class:: Texture + + | :sl:`pygame object that representing a Texture.` + | :sg:`Texture(renderer, size, depth=0, static=False, streaming=False, target=False) -> Texture` + + .. staticmethod:: from_surface + + | :sl:`Create a texture from an existing surface.` + | :sg:`from_surface(renderer, surface) -> Texture` + + .. attribute:: renderer + + | :sl:`Gets the renderer associated with the Texture. *Read-only*` + | :sg:`renderer -> Renderer` + + .. attribute:: width + + | :sl:`Gets the width of the Texture. *Read-only*` + | :sg:`width -> int` + + .. attribute:: height + + | :sl:`Gets the height of the Texture. *Read-only*` + | :sg:`height -> int` + + .. attribute:: alpha + + | :sl:`Gets and sets an additional alpha value multiplied into render copy operations.` + | :sg:`alpha -> int` + + .. attribute:: blend_mode + + | :sl:`Gets and sets the blend mode for the Texture.` + | :sg:`blend_mode -> int` + + .. attribute:: color + + | :sl:`Gets and sets an additional color value multiplied into render copy operations.` + | :sg:`color -> color` + + .. method:: get_rect + + | :sl:`Get the rectangular area of the texture.` + | :sg:`get_rect(**kwargs) -> Rect` + + .. method:: draw + + | :sl:`Copy a portion of the texture to the rendering target.` + | :sg:`draw(srcrect=None, dstrect=None, angle=0, origin=None, flipX=False, flipY=False) -> None` + + .. method:: update + + | :sl:`Update the texture with a Surface. WARNING: Slow operation, use sparingly.` + | :sg:`update(surface, area=None) -> None` + +.. class:: Image + + | :sl:`Easy way to use a portion of a Texture without worrying about srcrect all the time.` + | :sg:`Image(textureOrImage, srcrect=None) -> Image` + + .. method:: get_rect + + | :sl:`Get the rectangular area of the Image.` + | :sg:`get_rect() -> Rect` + + .. method:: draw + + | :sl:`Copy a portion of the Image to the rendering target.` + | :sg:`draw(srcrect=None, dstrect=None) -> None` + + .. attribute:: angle + + | :sl:`Gets and sets the angle the Image draws itself with.` + | :sg:`angle -> float` + + .. attribute:: origin + + | :sl:`Gets and sets the origin. Origin=None means the Image will be rotated around its center.` + | :sg:`origin -> (float, float) or None.` + + .. attribute:: flipX + + | :sl:`Gets and sets whether the Image is flipped on the x axis.` + | :sg:`flipX -> bool` + + .. attribute:: flipY + + | :sl:`Gets and sets whether the Image is flipped on the y axis.` + | :sg:`flipY -> bool` + + .. attribute:: color + + | :sl:`Gets and sets the Image color modifier.` + | :sg:`color -> Color` + + .. attribute:: alpha + + | :sl:`Gets and sets the Image alpha modifier.` + | :sg:`alpha -> float` + + .. attribute:: blend_mode + + | :sl:`Gets and sets the blend mode for the Image.` + | :sg:`blend_mode -> int` + + .. attribute:: texture + + | :sl:`Gets and sets the Texture the Image is based on.` + | :sg:`texture -> Texture` + + .. attribute:: srcrect + + | :sl:`Gets and sets the Rect the Image is based on.` + | :sg:`srcrect -> Rect` + +.. class:: Renderer + + | :sl:`Create a 2D rendering context for a window.` + | :sg:`Renderer(window, index=-1, accelerated=-1, vsync=False, target_texture=False) -> Renderer` + + .. classmethod:: from_window + + | :sl:`Easy way to create a Renderer.` + | :sg:`from_window(window) -> Renderer` + + .. attribute:: draw_blend_mode + + | :sl:`Gets and sets the blend mode used by the drawing functions.` + | :sg:`draw_blend_mode -> int` + + .. attribute:: draw_color + + | :sl:`Gets and sets the color used by the drawing functions.` + | :sg:`draw_color -> Color` + + .. method:: clear + + | :sl:`Clear the current rendering target with the drawing color.` + | :sg:`clear() -> None` + + .. method:: present + + | :sl:`Updates the screen with any new rendering since previous call.` + | :sg:`present() -> None` + + .. method:: get_viewport + + | :sl:`Returns the drawing area on the target.` + | :sg:`get_viewport() -> Rect` + + .. method:: set_viewport + + | :sl:`Set the drawing area on the target. If area is None, the entire target will be used.` + | :sg:`set_viewport(area) -> None` + + .. attribute:: logical_size + + | :sl:`Gets and sets the logical size.` + | :sg:`logical_size -> (int width, int height)` + + .. attribute:: scale + + | :sl:`Gets and sets the scale.` + | :sg:`scale -> (float x_scale, float y_scale)` + + .. attribute:: target + + | :sl:`Gets and sets the render target. None represents the default target (the renderer).` + | :sg:`target -> Texture or None` + + .. method:: blit + + | :sl:`For compatibility purposes. Textures created by different Renderers cannot be shared!` + | :sg:`blit(soure, dest, area=None, special_flags=0)-> Rect` + + .. method:: draw_line + + | :sl:`Draws a line.` + | :sg:`draw_line(p1, p2) -> None` + + .. method:: draw_point + + | :sl:`Draws a point.` + | :sg:`draw_point(point) -> None` + + .. method:: draw_rect + + | :sl:`Draws a rectangle.` + | :sg:`draw_rect(rect)-> None` + + .. method:: fill_rect + + | :sl:`Fills a rectangle.` + | :sg:`fill_rect(rect)-> None` + + .. method:: to_surface + + | :sl:`Read pixels from current render target and create a pygame.Surface. WARNING: Slow operation, use sparingly.` + | :sg:`to_surface(surface=None, area=None)-> Surface` \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sndarray.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sndarray.rst.txt new file mode 100644 index 0000000..2a17764 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sndarray.rst.txt @@ -0,0 +1,95 @@ +.. include:: common.txt + +:mod:`pygame.sndarray` +====================== + +.. module:: pygame.sndarray + :synopsis: pygame module for accessing sound sample data + +| :sl:`pygame module for accessing sound sample data` + +Functions to convert between NumPy arrays and Sound objects. This +module will only be functional when pygame can use the external NumPy +package. If NumPy can't be imported, ``surfarray`` becomes a ``MissingModule`` +object. + +Sound data is made of thousands of samples per second, and each sample is the +amplitude of the wave at a particular moment in time. For example, in 22-kHz +format, element number 5 of the array is the amplitude of the wave after +5/22000 seconds. + +The arrays are indexed by the ``X`` axis first, followed by the ``Y`` axis. +Each sample is an 8-bit or 16-bit integer, depending on the data format. A +stereo sound file has two values per sample, while a mono sound file only has +one. + +.. function:: array + + | :sl:`copy Sound samples into an array` + | :sg:`array(Sound) -> array` + + Creates a new array for the sound data and copies the samples. The array + will always be in the format returned from ``pygame.mixer.get_init()``. + + .. ## pygame.sndarray.array ## + +.. function:: samples + + | :sl:`reference Sound samples into an array` + | :sg:`samples(Sound) -> array` + + Creates a new array that directly references the samples in a Sound object. + Modifying the array will change the Sound. The array will always be in the + format returned from ``pygame.mixer.get_init()``. + + .. ## pygame.sndarray.samples ## + +.. function:: make_sound + + | :sl:`convert an array into a Sound object` + | :sg:`make_sound(array) -> Sound` + + Create a new playable Sound object from an array. The mixer module must be + initialized and the array format must be similar to the mixer audio format. + + .. ## pygame.sndarray.make_sound ## + +.. function:: use_arraytype + + | :sl:`Sets the array system to be used for sound arrays` + | :sg:`use_arraytype (arraytype) -> None` + + DEPRECATED: Uses the requested array type for the module functions. The + only supported arraytype is ``'numpy'``. Other values will raise ValueError. + Using this function will raise a ``DeprecationWarning``. + .. ## pygame.sndarray.use_arraytype ## + +.. function:: get_arraytype + + | :sl:`Gets the currently active array type.` + | :sg:`get_arraytype () -> str` + + DEPRECATED: Returns the currently active array type. This will be a value of the + ``get_arraytypes()`` tuple and indicates which type of array module is used + for the array creation. Using this function will raise a ``DeprecationWarning``. + + .. versionadded:: 1.8 + + .. ## pygame.sndarray.get_arraytype ## + +.. function:: get_arraytypes + + | :sl:`Gets the array system types currently supported.` + | :sg:`get_arraytypes () -> tuple` + + DEPRECATED: Checks, which array systems are available and returns them as a tuple of + strings. The values of the tuple can be used directly in the + :func:`pygame.sndarray.use_arraytype` () method. If no supported array + system could be found, None will be returned. Using this function will raise a + ``DeprecationWarning``. + + .. versionadded:: 1.8 + + .. ## pygame.sndarray.get_arraytypes ## + +.. ## pygame.sndarray ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sprite.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sprite.rst.txt new file mode 100644 index 0000000..e5b8cf8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/sprite.rst.txt @@ -0,0 +1,879 @@ +.. include:: common.txt + +:mod:`pygame.sprite` +==================== + +.. module:: pygame.sprite + :synopsis: pygame module with basic game object classes + +| :sl:`pygame module with basic game object classes` + +This module contains several simple classes to be used within games. There is +the main Sprite class and several Group classes that contain Sprites. The use +of these classes is entirely optional when using pygame. The classes are fairly +lightweight and only provide a starting place for the code that is common to +most games. + +The Sprite class is intended to be used as a base class for the different types +of objects in the game. There is also a base Group class that simply stores +sprites. A game could create new types of Group classes that operate on +specially customized Sprite instances they contain. + +The basic Sprite class can draw the Sprites it contains to a Surface. The +``Group.draw()`` method requires that each Sprite have a ``Surface.image`` +attribute and a ``Surface.rect``. The ``Group.clear()`` method requires these +same attributes, and can be used to erase all the Sprites with background. +There are also more advanced Groups: ``pygame.sprite.RenderUpdates()`` and +``pygame.sprite.OrderedUpdates()``. + +Lastly, this module contains several collision functions. These help find +sprites inside multiple groups that have intersecting bounding rectangles. To +find the collisions, the Sprites are required to have a ``Surface.rect`` +attribute assigned. + +The groups are designed for high efficiency in removing and adding Sprites to +them. They also allow cheap testing to see if a Sprite already exists in a +Group. A given Sprite can exist in any number of groups. A game could use some +groups to control object rendering, and a completely separate set of groups to +control interaction or player movement. Instead of adding type attributes or +bools to a derived Sprite class, consider keeping the Sprites inside organized +Groups. This will allow for easier lookup later in the game. + +Sprites and Groups manage their relationships with the ``add()`` and +``remove()`` methods. These methods can accept a single or multiple targets for +membership. The default initializers for these classes also takes a single or +list of targets for initial membership. It is safe to repeatedly add and remove +the same Sprite from a Group. + +While it is possible to design sprite and group classes that don't derive from +the Sprite and AbstractGroup classes below, it is strongly recommended that you +extend those when you add a Sprite or Group class. + +Sprites are not thread safe. So lock them yourself if using threads. + +.. class:: Sprite + + | :sl:`Simple base class for visible game objects.` + | :sg:`Sprite(*groups) -> Sprite` + + The base class for visible game objects. Derived classes will want to + override the ``Sprite.update()`` and assign a ``Sprite.image`` and + ``Sprite.rect`` attributes. The initializer can accept any number of Group + instances to be added to. + + When subclassing the Sprite, be sure to call the base initializer before + adding the Sprite to Groups. For example: + + .. code-block:: python + + class Block(pygame.sprite.Sprite): + + # Constructor. Pass in the color of the block, + # and its x and y position + def __init__(self, color, width, height): + # Call the parent class (Sprite) constructor + pygame.sprite.Sprite.__init__(self) + + # Create an image of the block, and fill it with a color. + # This could also be an image loaded from the disk. + self.image = pygame.Surface([width, height]) + self.image.fill(color) + + # Fetch the rectangle object that has the dimensions of the image + # Update the position of this object by setting the values of rect.x and rect.y + self.rect = self.image.get_rect() + + .. method:: update + + | :sl:`method to control sprite behavior` + | :sg:`update(*args, **kwargs) -> None` + + The default implementation of this method does nothing; it's just a + convenient "hook" that you can override. This method is called by + ``Group.update()`` with whatever arguments you give it. + + There is no need to use this method if not using the convenience method + by the same name in the Group class. + + .. ## Sprite.update ## + + .. method:: add + + | :sl:`add the sprite to groups` + | :sg:`add(*groups) -> None` + + Any number of Group instances can be passed as arguments. The Sprite will + be added to the Groups it is not already a member of. + + .. ## Sprite.add ## + + .. method:: remove + + | :sl:`remove the sprite from groups` + | :sg:`remove(*groups) -> None` + + Any number of Group instances can be passed as arguments. The Sprite will + be removed from the Groups it is currently a member of. + + .. ## Sprite.remove ## + + .. method:: kill + + | :sl:`remove the Sprite from all Groups` + | :sg:`kill() -> None` + + The Sprite is removed from all the Groups that contain it. This won't + change anything about the state of the Sprite. It is possible to continue + to use the Sprite after this method has been called, including adding it + to Groups. + + .. ## Sprite.kill ## + + .. method:: alive + + | :sl:`does the sprite belong to any groups` + | :sg:`alive() -> bool` + + Returns True when the Sprite belongs to one or more Groups. + + .. ## Sprite.alive ## + + .. method:: groups + + | :sl:`list of Groups that contain this Sprite` + | :sg:`groups() -> group_list` + + Return a list of all the Groups that contain this Sprite. + + .. ## Sprite.groups ## + + .. ## pygame.sprite.Sprite ## + +.. class:: DirtySprite + + | :sl:`A subclass of Sprite with more attributes and features.` + | :sg:`DirtySprite(*groups) -> DirtySprite` + + Extra DirtySprite attributes with their default values: + + dirty = 1 + + :: + + if set to 1, it is repainted and then set to 0 again + if set to 2 then it is always dirty ( repainted each frame, + flag is not reset) + 0 means that it is not dirty and therefore not repainted again + + blendmode = 0 + + :: + + its the special_flags argument of blit, blendmodes + + source_rect = None + + :: + + source rect to use, remember that it is relative to + topleft (0,0) of self.image + + visible = 1 + + :: + + normally 1, if set to 0 it will not be repainted + (you must set it dirty too to be erased from screen) + + layer = 0 + + :: + + (READONLY value, it is read when adding it to the + LayeredDirty, for details see doc of LayeredDirty) + + .. ## ## + + .. ## pygame.sprite.DirtySprite ## + +.. class:: Group + + | :sl:`A container class to hold and manage multiple Sprite objects.` + | :sg:`Group(*sprites) -> Group` + + A simple container for Sprite objects. This class can be inherited to create + containers with more specific behaviors. The constructor takes any number of + Sprite arguments to add to the Group. The group supports the following + standard Python operations: + + :: + + in test if a Sprite is contained + len the number of Sprites contained + bool test if any Sprites are contained + iter iterate through all the Sprites + + The Sprites in the Group are ordered only on python 3.6 and higher. + Below python 3.6 drawing and iterating over the Sprites is in no particular order. + + .. method:: sprites + + | :sl:`list of the Sprites this Group contains` + | :sg:`sprites() -> sprite_list` + + Return a list of all the Sprites this group contains. You can also get an + iterator from the group, but you cannot iterate over a Group while + modifying it. + + .. ## Group.sprites ## + + .. method:: copy + + | :sl:`duplicate the Group` + | :sg:`copy() -> Group` + + Creates a new Group with all the same Sprites as the original. If you + have subclassed Group, the new object will have the same (sub-)class as + the original. This only works if the derived class's constructor takes + the same arguments as the Group class's. + + .. ## Group.copy ## + + .. method:: add + + | :sl:`add Sprites to this Group` + | :sg:`add(*sprites) -> None` + + Add any number of Sprites to this Group. This will only add Sprites that + are not already members of the Group. + + Each sprite argument can also be a iterator containing Sprites. + + .. ## Group.add ## + + .. method:: remove + + | :sl:`remove Sprites from the Group` + | :sg:`remove(*sprites) -> None` + + Remove any number of Sprites from the Group. This will only remove + Sprites that are already members of the Group. + + Each sprite argument can also be a iterator containing Sprites. + + .. ## Group.remove ## + + .. method:: has + + | :sl:`test if a Group contains Sprites` + | :sg:`has(*sprites) -> bool` + + Return True if the Group contains all of the given sprites. This is + similar to using the "in" operator on the Group ("if sprite in group: + ..."), which tests if a single Sprite belongs to a Group. + + Each sprite argument can also be a iterator containing Sprites. + + .. ## Group.has ## + + .. method:: update + + | :sl:`call the update method on contained Sprites` + | :sg:`update(*args, **kwargs) -> None` + + Calls the ``update()`` method on all Sprites in the Group. The base + Sprite class has an update method that takes any number of arguments and + does nothing. The arguments passed to ``Group.update()`` will be passed + to each Sprite. + + There is no way to get the return value from the ``Sprite.update()`` + methods. + + .. ## Group.update ## + + .. method:: draw + + | :sl:`blit the Sprite images` + | :sg:`draw(Surface) -> List[Rect]` + + Draws the contained Sprites to the Surface argument. This uses the + ``Sprite.image`` attribute for the source surface, and ``Sprite.rect`` + for the position. + + The Group does not keep sprites in any order, so the draw order is + arbitrary. + + .. ## Group.draw ## + + .. method:: clear + + | :sl:`draw a background over the Sprites` + | :sg:`clear(Surface_dest, background) -> None` + + Erases the Sprites used in the last ``Group.draw()`` call. The + destination Surface is cleared by filling the drawn Sprite positions with + the background. + + The background is usually a Surface image the same dimensions as the + destination Surface. However, it can also be a callback function that + takes two arguments; the destination Surface and an area to clear. The + background callback function will be called several times each clear. + + Here is an example callback that will clear the Sprites with solid red: + + :: + + def clear_callback(surf, rect): + color = 255, 0, 0 + surf.fill(color, rect) + + .. ## Group.clear ## + + .. method:: empty + + | :sl:`remove all Sprites` + | :sg:`empty() -> None` + + Removes all Sprites from this Group. + + .. ## Group.empty ## + + .. ## pygame.sprite.Group ## + +.. class:: RenderPlain + + | :sl:`Same as pygame.sprite.Group` + + This class is an alias to ``pygame.sprite.Group()``. It has no additional functionality. + + .. ## pygame.sprite.RenderClear ## + +.. class:: RenderClear + + | :sl:`Same as pygame.sprite.Group` + + This class is an alias to ``pygame.sprite.Group()``. It has no additional functionality. + + .. ## pygame.sprite.RenderClear ## + +.. class:: RenderUpdates + + | :sl:`Group sub-class that tracks dirty updates.` + | :sg:`RenderUpdates(*sprites) -> RenderUpdates` + + This class is derived from ``pygame.sprite.Group()``. It has an extended + ``draw()`` method that tracks the changed areas of the screen. + + .. method:: draw + + | :sl:`blit the Sprite images and track changed areas` + | :sg:`draw(surface) -> Rect_list` + + Draws all the Sprites to the surface, the same as ``Group.draw()``. This + method also returns a list of Rectangular areas on the screen that have + been changed. The returned changes include areas of the screen that have + been affected by previous ``Group.clear()`` calls. + + The returned Rect list should be passed to ``pygame.display.update()``. + This will help performance on software driven display modes. This type of + updating is usually only helpful on destinations with non-animating + backgrounds. + + .. ## RenderUpdates.draw ## + + .. ## pygame.sprite.RenderUpdates ## + +.. function:: OrderedUpdates + + | :sl:`RenderUpdates sub-class that draws Sprites in order of addition.` + | :sg:`OrderedUpdates(*spites) -> OrderedUpdates` + + This class derives from ``pygame.sprite.RenderUpdates()``. It maintains the + order in which the Sprites were added to the Group for rendering. This makes + adding and removing Sprites from the Group a little slower than regular + Groups. + + .. ## pygame.sprite.OrderedUpdates ## + +.. class:: LayeredUpdates + + | :sl:`LayeredUpdates is a sprite group that handles layers and draws like OrderedUpdates.` + | :sg:`LayeredUpdates(*spites, **kwargs) -> LayeredUpdates` + + This group is fully compatible with :class:`pygame.sprite.Sprite`. + + You can set the default layer through kwargs using 'default_layer' and an + integer for the layer. The default layer is 0. + + If the sprite you add has an attribute _layer then that layer will be used. + If the \**kwarg contains 'layer' then the sprites passed will be added to + that layer (overriding the ``sprite.layer`` attribute). If neither sprite + has attribute layer nor \**kwarg then the default layer is used to add the + sprites. + + .. versionadded:: 1.8 + + .. method:: add + + | :sl:`add a sprite or sequence of sprites to a group` + | :sg:`add(*sprites, **kwargs) -> None` + + If the ``sprite(s)`` have an attribute layer then that is used for the + layer. If \**kwargs contains 'layer' then the ``sprite(s)`` will be added + to that argument (overriding the sprite layer attribute). If neither is + passed then the ``sprite(s)`` will be added to the default layer. + + .. ## LayeredUpdates.add ## + + .. method:: sprites + + | :sl:`returns a ordered list of sprites (first back, last top).` + | :sg:`sprites() -> sprites` + + .. ## LayeredUpdates.sprites ## + + .. method:: draw + + | :sl:`draw all sprites in the right order onto the passed surface.` + | :sg:`draw(surface) -> Rect_list` + + .. ## LayeredUpdates.draw ## + + .. method:: get_sprites_at + + | :sl:`returns a list with all sprites at that position.` + | :sg:`get_sprites_at(pos) -> colliding_sprites` + + Bottom sprites first, top last. + + .. ## LayeredUpdates.get_sprites_at ## + + .. method:: get_sprite + + | :sl:`returns the sprite at the index idx from the groups sprites` + | :sg:`get_sprite(idx) -> sprite` + + Raises IndexOutOfBounds if the idx is not within range. + + .. ## LayeredUpdates.get_sprite ## + + .. method:: remove_sprites_of_layer + + | :sl:`removes all sprites from a layer and returns them as a list.` + | :sg:`remove_sprites_of_layer(layer_nr) -> sprites` + + .. ## LayeredUpdates.remove_sprites_of_layer ## + + .. method:: layers + + | :sl:`returns a list of layers defined (unique), sorted from bottom up.` + | :sg:`layers() -> layers` + + .. ## LayeredUpdates.layers ## + + .. method:: change_layer + + | :sl:`changes the layer of the sprite` + | :sg:`change_layer(sprite, new_layer) -> None` + + sprite must have been added to the renderer. It is not checked. + + .. ## LayeredUpdates.change_layer ## + + .. method:: get_layer_of_sprite + + | :sl:`returns the layer that sprite is currently in.` + | :sg:`get_layer_of_sprite(sprite) -> layer` + + If the sprite is not found then it will return the default layer. + + .. ## LayeredUpdates.get_layer_of_sprite ## + + .. method:: get_top_layer + + | :sl:`returns the top layer` + | :sg:`get_top_layer() -> layer` + + .. ## LayeredUpdates.get_top_layer ## + + .. method:: get_bottom_layer + + | :sl:`returns the bottom layer` + | :sg:`get_bottom_layer() -> layer` + + .. ## LayeredUpdates.get_bottom_layer ## + + .. method:: move_to_front + + | :sl:`brings the sprite to front layer` + | :sg:`move_to_front(sprite) -> None` + + Brings the sprite to front, changing sprite layer to topmost layer (added + at the end of that layer). + + .. ## LayeredUpdates.move_to_front ## + + .. method:: move_to_back + + | :sl:`moves the sprite to the bottom layer` + | :sg:`move_to_back(sprite) -> None` + + Moves the sprite to the bottom layer, moving it behind all other layers + and adding one additional layer. + + .. ## LayeredUpdates.move_to_back ## + + .. method:: get_top_sprite + + | :sl:`returns the topmost sprite` + | :sg:`get_top_sprite() -> Sprite` + + .. ## LayeredUpdates.get_top_sprite ## + + .. method:: get_sprites_from_layer + + | :sl:`returns all sprites from a layer, ordered by how they where added` + | :sg:`get_sprites_from_layer(layer) -> sprites` + + Returns all sprites from a layer, ordered by how they where added. It + uses linear search and the sprites are not removed from layer. + + .. ## LayeredUpdates.get_sprites_from_layer ## + + .. method:: switch_layer + + | :sl:`switches the sprites from layer1 to layer2` + | :sg:`switch_layer(layer1_nr, layer2_nr) -> None` + + The layers number must exist, it is not checked. + + .. ## LayeredUpdates.switch_layer ## + + .. ## pygame.sprite.LayeredUpdates ## + +.. class:: LayeredDirty + + | :sl:`LayeredDirty group is for DirtySprite objects. Subclasses LayeredUpdates.` + | :sg:`LayeredDirty(*spites, **kwargs) -> LayeredDirty` + + This group requires :class:`pygame.sprite.DirtySprite` or any sprite that + has the following attributes: + + :: + + image, rect, dirty, visible, blendmode (see doc of DirtySprite). + + It uses the dirty flag technique and is therefore faster than the + :class:`pygame.sprite.RenderUpdates` if you have many static sprites. It + also switches automatically between dirty rect update and full screen + drawing, so you do not have to worry what would be faster. + + Same as for the :class:`pygame.sprite.Group`. You can specify some + additional attributes through kwargs: + + :: + + _use_update: True/False default is False + _default_layer: default layer where sprites without a layer are added. + _time_threshold: threshold time for switching between dirty rect mode + and fullscreen mode, defaults to 1000./80 == 1000./fps + + .. versionadded:: 1.8 + + .. method:: draw + + | :sl:`draw all sprites in the right order onto the passed surface.` + | :sg:`draw(surface, bgd=None) -> Rect_list` + + You can pass the background too. If a background is already set, then the + bgd argument has no effect. + + .. ## LayeredDirty.draw ## + + .. method:: clear + + | :sl:`used to set background` + | :sg:`clear(surface, bgd) -> None` + + .. ## LayeredDirty.clear ## + + .. method:: repaint_rect + + | :sl:`repaints the given area` + | :sg:`repaint_rect(screen_rect) -> None` + + screen_rect is in screen coordinates. + + .. ## LayeredDirty.repaint_rect ## + + .. method:: set_clip + + | :sl:`clip the area where to draw. Just pass None (default) to reset the clip` + | :sg:`set_clip(screen_rect=None) -> None` + + .. ## LayeredDirty.set_clip ## + + .. method:: get_clip + + | :sl:`clip the area where to draw. Just pass None (default) to reset the clip` + | :sg:`get_clip() -> Rect` + + .. ## LayeredDirty.get_clip ## + + .. method:: change_layer + + | :sl:`changes the layer of the sprite` + | :sg:`change_layer(sprite, new_layer) -> None` + + sprite must have been added to the renderer. It is not checked. + + .. ## LayeredDirty.change_layer ## + + .. method:: set_timing_treshold + + | :sl:`sets the threshold in milliseconds` + | :sg:`set_timing_treshold(time_ms) -> None` + + DEPRECATED: Use set_timing_threshold() instead. + + .. deprecated:: 2.1.1 + + .. ## LayeredDirty.set_timing_treshold ## + + .. method:: set_timing_threshold + + | :sl:`sets the threshold in milliseconds` + | :sg:`set_timing_threshold(time_ms) -> None` + + Defaults to 1000.0 / 80.0. This means that the screen will be painted + using the flip method rather than the update method if the update + method is taking so long to update the screen that the frame rate falls + below 80 frames per second. + + .. versionadded:: 2.1.1 + + :raises TypeError: if ``time_ms`` is not int or float + + .. ## LayeredDirty.set_timing_threshold ## + + .. ## pygame.sprite.LayeredDirty ## + +.. function:: GroupSingle + + | :sl:`Group container that holds a single sprite.` + | :sg:`GroupSingle(sprite=None) -> GroupSingle` + + The GroupSingle container only holds a single Sprite. When a new Sprite is + added, the old one is removed. + + There is a special property, ``GroupSingle.sprite``, that accesses the + Sprite that this Group contains. It can be None when the Group is empty. The + property can also be assigned to add a Sprite into the GroupSingle + container. + + .. ## pygame.sprite.GroupSingle ## + +.. function:: spritecollide + + | :sl:`Find sprites in a group that intersect another sprite.` + | :sg:`spritecollide(sprite, group, dokill, collided = None) -> Sprite_list` + + Return a list containing all Sprites in a Group that intersect with another + Sprite. Intersection is determined by comparing the ``Sprite.rect`` + attribute of each Sprite. + + The dokill argument is a bool. If set to True, all Sprites that collide will + be removed from the Group. + + The collided argument is a callback function used to calculate if two + sprites are colliding. it should take two sprites as values, and return a + bool value indicating if they are colliding. If collided is not passed, all + sprites must have a "rect" value, which is a rectangle of the sprite area, + which will be used to calculate the collision. + + collided callables: + + :: + + collide_rect, collide_rect_ratio, collide_circle, + collide_circle_ratio, collide_mask + + Example: + + .. code-block:: python + + # See if the Sprite block has collided with anything in the Group block_list + # The True flag will remove the sprite in block_list + blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True) + + # Check the list of colliding sprites, and add one to the score for each one + for block in blocks_hit_list: + score +=1 + + .. ## pygame.sprite.spritecollide ## + +.. function:: collide_rect + + | :sl:`Collision detection between two sprites, using rects.` + | :sg:`collide_rect(left, right) -> bool` + + Tests for collision between two sprites. Uses the pygame rect colliderect + function to calculate the collision. Intended to be passed as a collided + callback function to the \*collide functions. Sprites must have a "rect" + attributes. + + .. versionadded:: 1.8 + + .. ## pygame.sprite.collide_rect ## + +.. function:: collide_rect_ratio + + | :sl:`Collision detection between two sprites, using rects scaled to a ratio.` + | :sg:`collide_rect_ratio(ratio) -> collided_callable` + + A callable class that checks for collisions between two sprites, using a + scaled version of the sprites rects. + + Is created with a ratio, the instance is then intended to be passed as a + collided callback function to the \*collide functions. + + A ratio is a floating point number - 1.0 is the same size, 2.0 is twice as + big, and 0.5 is half the size. + + .. versionadded:: 1.8.1 + + .. ## pygame.sprite.collide_rect_ratio ## + +.. function:: collide_circle + + | :sl:`Collision detection between two sprites, using circles.` + | :sg:`collide_circle(left, right) -> bool` + + Tests for collision between two sprites, by testing to see if two circles + centered on the sprites overlap. If the sprites have a "radius" attribute, + that is used to create the circle, otherwise a circle is created that is big + enough to completely enclose the sprites rect as given by the "rect" + attribute. Intended to be passed as a collided callback function to the + \*collide functions. Sprites must have a "rect" and an optional "radius" + attribute. + + .. versionadded:: 1.8.1 + + .. ## pygame.sprite.collide_circle ## + +.. function:: collide_circle_ratio + + | :sl:`Collision detection between two sprites, using circles scaled to a ratio.` + | :sg:`collide_circle_ratio(ratio) -> collided_callable` + + A callable class that checks for collisions between two sprites, using a + scaled version of the sprites radius. + + Is created with a floating point ratio, the instance is then intended to be + passed as a collided callback function to the \*collide functions. + + A ratio is a floating point number - 1.0 is the same size, 2.0 is twice as + big, and 0.5 is half the size. + + The created callable tests for collision between two sprites, by testing to + see if two circles centered on the sprites overlap, after scaling the + circles radius by the stored ratio. If the sprites have a "radius" + attribute, that is used to create the circle, otherwise a circle is created + that is big enough to completely enclose the sprites rect as given by the + "rect" attribute. Intended to be passed as a collided callback function to + the \*collide functions. Sprites must have a "rect" and an optional "radius" + attribute. + + .. versionadded:: 1.8.1 + + .. ## pygame.sprite.collide_circle_ratio ## + +.. function:: collide_mask + + | :sl:`Collision detection between two sprites, using masks.` + | :sg:`collide_mask(sprite1, sprite2) -> (int, int)` + | :sg:`collide_mask(sprite1, sprite2) -> None` + + Tests for collision between two sprites, by testing if their bitmasks + overlap (uses :func:`pygame.mask.Mask.overlap`). If the sprites have a + ``mask`` attribute, it is used as the mask, otherwise a mask is created from + the sprite's ``image`` (uses :func:`pygame.mask.from_surface`). Sprites must + have a ``rect`` attribute; the ``mask`` attribute is optional. + + The first point of collision between the masks is returned. The collision + point is offset from ``sprite1``'s mask's topleft corner (which is always + (0, 0)). The collision point is a position within the mask and is not + related to the actual screen position of ``sprite1``. + + This function is intended to be passed as a ``collided`` callback function + to the group collide functions (see :meth:`spritecollide`, + :meth:`groupcollide`, :meth:`spritecollideany`). + + .. note:: + To increase performance, create and set a ``mask`` attibute for all + sprites that will use this function to check for collisions. Otherwise, + each time this function is called it will create new masks. + + .. note:: + A new mask needs to be recreated each time a sprite's image is changed + (e.g. if a new image is used or the existing image is rotated). + + :: + + # Example of mask creation for a sprite. + sprite.mask = pygame.mask.from_surface(sprite.image) + + :returns: first point of collision between the masks or ``None`` if no + collision + :rtype: tuple(int, int) or NoneType + + .. versionadded:: 1.8.0 + + .. ## pygame.sprite.collide_mask ## + +.. function:: groupcollide + + | :sl:`Find all sprites that collide between two groups.` + | :sg:`groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict` + + This will find collisions between all the Sprites in two groups. + Collision is determined by comparing the ``Sprite.rect`` attribute of + each Sprite or by using the collided function if it is not None. + + Every Sprite inside group1 is added to the return dictionary. The value for + each item is the list of Sprites in group2 that intersect. + + If either dokill argument is True, the colliding Sprites will be removed + from their respective Group. + + The collided argument is a callback function used to calculate if two sprites are + colliding. It should take two sprites as values and return a bool value + indicating if they are colliding. If collided is not passed, then all + sprites must have a "rect" value, which is a rectangle of the sprite area, + which will be used to calculate the collision. + + .. ## pygame.sprite.groupcollide ## + +.. function:: spritecollideany + + | :sl:`Simple test if a sprite intersects anything in a group.` + | :sg:`spritecollideany(sprite, group, collided = None) -> Sprite` Collision with the returned sprite. + | :sg:`spritecollideany(sprite, group, collided = None) -> None` No collision + + If the sprite collides with any single sprite in the group, a single + sprite from the group is returned. On no collision None is returned. + + If you don't need all the features of the ``pygame.sprite.spritecollide()`` function, this + function will be a bit quicker. + + The collided argument is a callback function used to calculate if two sprites are + colliding. It should take two sprites as values and return a bool value + indicating if they are colliding. If collided is not passed, then all + sprites must have a "rect" value, which is a rectangle of the sprite area, + which will be used to calculate the collision. + + .. ## pygame.sprite.spritecollideany ## + +.. ## ## + +.. ## pygame.sprite ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/surface.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/surface.rst.txt new file mode 100644 index 0000000..4ed77a6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/surface.rst.txt @@ -0,0 +1,902 @@ +.. include:: common.txt + +:mod:`pygame.Surface` +===================== + +.. currentmodule:: pygame + +.. class:: Surface + + | :sl:`pygame object for representing images` + | :sg:`Surface((width, height), flags=0, depth=0, masks=None) -> Surface` + | :sg:`Surface((width, height), flags=0, Surface) -> Surface` + + A pygame Surface is used to represent any image. The Surface has a fixed + resolution and pixel format. Surfaces with 8-bit pixels use a color palette + to map to 24-bit color. + + Call :meth:`pygame.Surface()` to create a new image object. The Surface will + be cleared to all black. The only required arguments are the sizes. With no + additional arguments, the Surface will be created in a format that best + matches the display Surface. + + The pixel format can be controlled by passing the bit depth or an existing + Surface. The flags argument is a bitmask of additional features for the + surface. You can pass any combination of these flags: + + :: + + HWSURFACE (obsolete in pygame 2) creates the image in video memory + SRCALPHA the pixel format will include a per-pixel alpha + + Both flags are only a request, and may not be possible for all displays and + formats. + + Advance users can combine a set of bitmasks with a depth value. The masks + are a set of 4 integers representing which bits in a pixel will represent + each color. Normal Surfaces should not require the masks argument. + + Surfaces can have many extra attributes like alpha planes, colorkeys, source + rectangle clipping. These functions mainly effect how the Surface is blitted + to other Surfaces. The blit routines will attempt to use hardware + acceleration when possible, otherwise they will use highly optimized + software blitting methods. + + There are three types of transparency supported in pygame: colorkeys, + surface alphas, and pixel alphas. Surface alphas can be mixed with + colorkeys, but an image with per pixel alphas cannot use the other modes. + Colorkey transparency makes a single color value transparent. Any pixels + matching the colorkey will not be drawn. The surface alpha value is a single + value that changes the transparency for the entire image. A surface alpha of + 255 is opaque, and a value of 0 is completely transparent. + + Per pixel alphas are different because they store a transparency value for + every pixel. This allows for the most precise transparency effects, but it + also the slowest. Per pixel alphas cannot be mixed with surface alpha and + colorkeys. + + There is support for pixel access for the Surfaces. Pixel access on hardware + surfaces is slow and not recommended. Pixels can be accessed using the + :meth:`get_at()` and :meth:`set_at()` functions. These methods are fine for + simple access, but will be considerably slow when doing of pixel work with + them. If you plan on doing a lot of pixel level work, it is recommended to + use a :class:`pygame.PixelArray`, which gives an array like view of the + surface. For involved mathematical manipulations try the + :mod:`pygame.surfarray` module (It's quite quick, but requires NumPy.) + + Any functions that directly access a surface's pixel data will need that + surface to be lock()'ed. These functions can :meth:`lock()` and + :meth:`unlock()` the surfaces themselves without assistance. But, if a + function will be called many times, there will be a lot of overhead for + multiple locking and unlocking of the surface. It is best to lock the + surface manually before making the function call many times, and then + unlocking when you are finished. All functions that need a locked surface + will say so in their docs. Remember to leave the Surface locked only while + necessary. + + Surface pixels are stored internally as a single number that has all the + colors encoded into it. Use the :meth:`map_rgb()` and + :meth:`unmap_rgb()` to convert between individual red, green, and blue + values into a packed integer for that Surface. + + Surfaces can also reference sections of other Surfaces. These are created + with the :meth:`subsurface()` method. Any change to either Surface will + effect the other. + + Each Surface contains a clipping area. By default the clip area covers the + entire Surface. If it is changed, all drawing operations will only effect + the smaller area. + + .. method:: blit + + | :sl:`draw one image onto another` + | :sg:`blit(source, dest, area=None, special_flags=0) -> Rect` + + Draws a source Surface onto this Surface. The draw can be positioned with + the dest argument. The dest argument can either be a pair of coordinates representing the position of + the upper left corner of the blit or a Rect, where the upper left corner of the rectangle will be used as the + position for the blit. The size of the destination rectangle does not + effect the blit. + + An optional area rectangle can be passed as well. This represents a + smaller portion of the source Surface to draw. + + .. versionadded:: 1.8 + Optional ``special_flags``: ``BLEND_ADD``, ``BLEND_SUB``, + ``BLEND_MULT``, ``BLEND_MIN``, ``BLEND_MAX``. + + .. versionadded:: 1.8.1 + Optional ``special_flags``: ``BLEND_RGBA_ADD``, ``BLEND_RGBA_SUB``, + ``BLEND_RGBA_MULT``, ``BLEND_RGBA_MIN``, ``BLEND_RGBA_MAX`` + ``BLEND_RGB_ADD``, ``BLEND_RGB_SUB``, ``BLEND_RGB_MULT``, + ``BLEND_RGB_MIN``, ``BLEND_RGB_MAX``. + + .. versionadded:: 1.9.2 + Optional ``special_flags``: ``BLEND_PREMULTIPLIED`` + + .. versionadded:: 2.0.0 + Optional ``special_flags``: ``BLEND_ALPHA_SDL2`` - Uses the SDL2 blitter for alpha blending, + this gives different results than the default blitter, which is modelled after SDL1, due to + different approximations used for the alpha blending formula. The SDL2 blitter also supports + RLE on alpha blended surfaces which the pygame one does not. + + The return rectangle is the area of the affected pixels, excluding any + pixels outside the destination Surface, or outside the clipping area. + + Pixel alphas will be ignored when blitting to an 8 bit Surface. + + For a surface with colorkey or blanket alpha, a blit to self may give + slightly different colors than a non self-blit. + + .. ## Surface.blit ## + + .. method:: blits + + | :sl:`draw many images onto another` + | :sg:`blits(blit_sequence=((source, dest), ...), doreturn=1) -> [Rect, ...] or None` + | :sg:`blits(((source, dest, area), ...)) -> [Rect, ...]` + | :sg:`blits(((source, dest, area, special_flags), ...)) -> [Rect, ...]` + + Draws many surfaces onto this Surface. It takes a sequence as input, + with each of the elements corresponding to the ones of :meth:`blit()`. + It needs at minimum a sequence of (source, dest). + + :param blit_sequence: a sequence of surfaces and arguments to blit them, + they correspond to the :meth:`blit()` arguments + :param doreturn: if ``True``, return a list of rects of the areas changed, + otherwise return ``None`` + + :returns: a list of rects of the areas changed if ``doreturn`` is + ``True``, otherwise ``None`` + :rtype: list or None + + New in pygame 1.9.4. + + .. ## Surface.blits ## + + + .. method:: convert + + | :sl:`change the pixel format of an image` + | :sg:`convert(Surface=None) -> Surface` + | :sg:`convert(depth, flags=0) -> Surface` + | :sg:`convert(masks, flags=0) -> Surface` + + Creates a new copy of the Surface with the pixel format changed. The new + pixel format can be determined from another existing Surface. Otherwise + depth, flags, and masks arguments can be used, similar to the + :meth:`pygame.Surface()` call. + + If no arguments are passed the new Surface will have the same pixel + format as the display Surface. This is always the fastest format for + blitting. It is a good idea to convert all Surfaces before they are + blitted many times. + + The converted Surface will have no pixel alphas. They will be stripped if + the original had them. See :meth:`convert_alpha()` for preserving or + creating per-pixel alphas. + + The new copy will have the same class as the copied surface. This lets + as Surface subclass inherit this method without the need to override, + unless subclass specific instance attributes also need copying. + + .. ## Surface.convert ## + + .. method:: convert_alpha + + | :sl:`change the pixel format of an image including per pixel alphas` + | :sg:`convert_alpha(Surface) -> Surface` + | :sg:`convert_alpha() -> Surface` + + Creates a new copy of the surface with the desired pixel format. The new + surface will be in a format suited for quick blitting to the given format + with per pixel alpha. If no surface is given, the new surface will be + optimized for blitting to the current display. + + Unlike the :meth:`convert()` method, the pixel format for the new + image will not be exactly the same as the requested source, but it will + be optimized for fast alpha blitting to the destination. + + As with :meth:`convert()` the returned surface has the same class as + the converted surface. + + .. ## Surface.convert_alpha ## + + .. method:: copy + + | :sl:`create a new copy of a Surface` + | :sg:`copy() -> Surface` + + Makes a duplicate copy of a Surface. The new surface will have the same + pixel formats, color palettes, transparency settings, and class as the + original. If a Surface subclass also needs to copy any instance specific + attributes then it should override ``copy()``. + + .. ## Surface.copy ## + + .. method:: fill + + | :sl:`fill Surface with a solid color` + | :sg:`fill(color, rect=None, special_flags=0) -> Rect` + + Fill the Surface with a solid color. If no rect argument is given the + entire Surface will be filled. The rect argument will limit the fill to a + specific area. The fill will also be contained by the Surface clip area. + + The color argument can be either a ``RGB`` sequence, a ``RGBA`` sequence + or a mapped color index. If using ``RGBA``, the Alpha (A part of + ``RGBA``) is ignored unless the surface uses per pixel alpha (Surface has + the ``SRCALPHA`` flag). + + .. versionadded:: 1.8 + Optional ``special_flags``: ``BLEND_ADD``, ``BLEND_SUB``, + ``BLEND_MULT``, ``BLEND_MIN``, ``BLEND_MAX``. + + .. versionadded:: 1.8.1 + Optional ``special_flags``: ``BLEND_RGBA_ADD``, ``BLEND_RGBA_SUB``, + ``BLEND_RGBA_MULT``, ``BLEND_RGBA_MIN``, ``BLEND_RGBA_MAX`` + ``BLEND_RGB_ADD``, ``BLEND_RGB_SUB``, ``BLEND_RGB_MULT``, + ``BLEND_RGB_MIN``, ``BLEND_RGB_MAX``. + + This will return the affected Surface area. + + .. ## Surface.fill ## + + .. method:: scroll + + | :sl:`Shift the surface image in place` + | :sg:`scroll(dx=0, dy=0) -> None` + + Move the image by dx pixels right and dy pixels down. dx and dy may be + negative for left and up scrolls respectively. Areas of the surface that + are not overwritten retain their original pixel values. Scrolling is + contained by the Surface clip area. It is safe to have dx and dy values + that exceed the surface size. + + .. versionadded:: 1.9 + + .. ## Surface.scroll ## + + .. method:: set_colorkey + + | :sl:`Set the transparent colorkey` + | :sg:`set_colorkey(Color, flags=0) -> None` + | :sg:`set_colorkey(None) -> None` + + Set the current color key for the Surface. When blitting this Surface + onto a destination, any pixels that have the same color as the colorkey + will be transparent. The color can be an ``RGB`` color or a mapped color + integer. If ``None`` is passed, the colorkey will be unset. + + The colorkey will be ignored if the Surface is formatted to use per pixel + alpha values. The colorkey can be mixed with the full Surface alpha + value. + + The optional flags argument can be set to ``pygame.RLEACCEL`` to provide + better performance on non accelerated displays. An ``RLEACCEL`` Surface + will be slower to modify, but quicker to blit as a source. + + .. ## Surface.set_colorkey ## + + .. method:: get_colorkey + + | :sl:`Get the current transparent colorkey` + | :sg:`get_colorkey() -> RGB or None` + + Return the current colorkey value for the Surface. If the colorkey is not + set then ``None`` is returned. + + .. ## Surface.get_colorkey ## + + .. method:: set_alpha + + | :sl:`set the alpha value for the full Surface image` + | :sg:`set_alpha(value, flags=0) -> None` + | :sg:`set_alpha(None) -> None` + + Set the current alpha value for the Surface. When blitting this Surface + onto a destination, the pixels will be drawn slightly transparent. The + alpha value is an integer from 0 to 255, 0 is fully transparent and 255 + is fully opaque. If ``None`` is passed for the alpha value, then alpha + blending will be disabled, including per-pixel alpha. + + This value is different than the per pixel Surface alpha. For a surface + with per pixel alpha, blanket alpha is ignored and ``None`` is returned. + + .. versionchanged:: 2.0 per-surface alpha can be combined with per-pixel + alpha. + + The optional flags argument can be set to ``pygame.RLEACCEL`` to provide + better performance on non accelerated displays. An ``RLEACCEL`` Surface + will be slower to modify, but quicker to blit as a source. + + .. ## Surface.set_alpha ## + + .. method:: get_alpha + + | :sl:`get the current Surface transparency value` + | :sg:`get_alpha() -> int_value` + + Return the current alpha value for the Surface. + + .. ## Surface.get_alpha ## + + .. method:: lock + + | :sl:`lock the Surface memory for pixel access` + | :sg:`lock() -> None` + + Lock the pixel data of a Surface for access. On accelerated Surfaces, the + pixel data may be stored in volatile video memory or nonlinear compressed + forms. When a Surface is locked the pixel memory becomes available to + access by regular software. Code that reads or writes pixel values will + need the Surface to be locked. + + Surfaces should not remain locked for more than necessary. A locked + Surface can often not be displayed or managed by pygame. + + Not all Surfaces require locking. The :meth:`mustlock()` method can + determine if it is actually required. There is no performance penalty for + locking and unlocking a Surface that does not need it. + + All pygame functions will automatically lock and unlock the Surface data + as needed. If a section of code is going to make calls that will + repeatedly lock and unlock the Surface many times, it can be helpful to + wrap the block inside a lock and unlock pair. + + It is safe to nest locking and unlocking calls. The surface will only be + unlocked after the final lock is released. + + .. ## Surface.lock ## + + .. method:: unlock + + | :sl:`unlock the Surface memory from pixel access` + | :sg:`unlock() -> None` + + Unlock the Surface pixel data after it has been locked. The unlocked + Surface can once again be drawn and managed by pygame. See the + :meth:`lock()` documentation for more details. + + All pygame functions will automatically lock and unlock the Surface data + as needed. If a section of code is going to make calls that will + repeatedly lock and unlock the Surface many times, it can be helpful to + wrap the block inside a lock and unlock pair. + + It is safe to nest locking and unlocking calls. The surface will only be + unlocked after the final lock is released. + + .. ## Surface.unlock ## + + .. method:: mustlock + + | :sl:`test if the Surface requires locking` + | :sg:`mustlock() -> bool` + + Returns ``True`` if the Surface is required to be locked to access pixel + data. Usually pure software Surfaces do not require locking. This method + is rarely needed, since it is safe and quickest to just lock all Surfaces + as needed. + + All pygame functions will automatically lock and unlock the Surface data + as needed. If a section of code is going to make calls that will + repeatedly lock and unlock the Surface many times, it can be helpful to + wrap the block inside a lock and unlock pair. + + .. ## Surface.mustlock ## + + .. method:: get_locked + + | :sl:`test if the Surface is current locked` + | :sg:`get_locked() -> bool` + + Returns ``True`` when the Surface is locked. It doesn't matter how many + times the Surface is locked. + + .. ## Surface.get_locked ## + + .. method:: get_locks + + | :sl:`Gets the locks for the Surface` + | :sg:`get_locks() -> tuple` + + Returns the currently existing locks for the Surface. + + .. ## Surface.get_locks ## + + .. method:: get_at + + | :sl:`get the color value at a single pixel` + | :sg:`get_at((x, y)) -> Color` + + Return a copy of the ``RGBA`` Color value at the given pixel. If the + Surface has no per pixel alpha, then the alpha value will always be 255 + (opaque). If the pixel position is outside the area of the Surface an + ``IndexError`` exception will be raised. + + Getting and setting pixels one at a time is generally too slow to be used + in a game or realtime situation. It is better to use methods which + operate on many pixels at a time like with the blit, fill and draw + methods - or by using :mod:`pygame.surfarray`/:mod:`pygame.PixelArray`. + + This function will temporarily lock and unlock the Surface as needed. + + .. versionadded:: 1.9 + Returning a Color instead of tuple. Use ``tuple(surf.get_at((x,y)))`` + if you want a tuple, and not a Color. This should only matter if + you want to use the color as a key in a dict. + + .. ## Surface.get_at ## + + .. method:: set_at + + | :sl:`set the color value for a single pixel` + | :sg:`set_at((x, y), Color) -> None` + + Set the ``RGBA`` or mapped integer color value for a single pixel. If the + Surface does not have per pixel alphas, the alpha value is ignored. + Setting pixels outside the Surface area or outside the Surface clipping + will have no effect. + + Getting and setting pixels one at a time is generally too slow to be used + in a game or realtime situation. + + This function will temporarily lock and unlock the Surface as needed. + + .. ## Surface.set_at ## + + .. method:: get_at_mapped + + | :sl:`get the mapped color value at a single pixel` + | :sg:`get_at_mapped((x, y)) -> Color` + + Return the integer value of the given pixel. If the pixel position is + outside the area of the Surface an ``IndexError`` exception will be + raised. + + This method is intended for pygame unit testing. It unlikely has any use + in an application. + + This function will temporarily lock and unlock the Surface as needed. + + .. versionadded:: 1.9.2 + + .. ## Surface.get_at_mapped ## + + .. method:: get_palette + + | :sl:`get the color index palette for an 8-bit Surface` + | :sg:`get_palette() -> [RGB, RGB, RGB, ...]` + + Return a list of up to 256 color elements that represent the indexed + colors used in an 8-bit Surface. The returned list is a copy of the + palette, and changes will have no effect on the Surface. + + Returning a list of ``Color(with length 3)`` instances instead of tuples. + + .. versionadded:: 1.9 + + .. ## Surface.get_palette ## + + .. method:: get_palette_at + + | :sl:`get the color for a single entry in a palette` + | :sg:`get_palette_at(index) -> RGB` + + Returns the red, green, and blue color values for a single index in a + Surface palette. The index should be a value from 0 to 255. + + .. versionadded:: 1.9 + Returning ``Color(with length 3)`` instance instead of a tuple. + + .. ## Surface.get_palette_at ## + + .. method:: set_palette + + | :sl:`set the color palette for an 8-bit Surface` + | :sg:`set_palette([RGB, RGB, RGB, ...]) -> None` + + Set the full palette for an 8-bit Surface. This will replace the colors in + the existing palette. A partial palette can be passed and only the first + colors in the original palette will be changed. + + This function has no effect on a Surface with more than 8-bits per pixel. + + .. ## Surface.set_palette ## + + .. method:: set_palette_at + + | :sl:`set the color for a single index in an 8-bit Surface palette` + | :sg:`set_palette_at(index, RGB) -> None` + + Set the palette value for a single entry in a Surface palette. The index + should be a value from 0 to 255. + + This function has no effect on a Surface with more than 8-bits per pixel. + + .. ## Surface.set_palette_at ## + + .. method:: map_rgb + + | :sl:`convert a color into a mapped color value` + | :sg:`map_rgb(Color) -> mapped_int` + + Convert an ``RGBA`` color into the mapped integer value for this Surface. + The returned integer will contain no more bits than the bit depth of the + Surface. Mapped color values are not often used inside pygame, but can be + passed to most functions that require a Surface and a color. + + See the Surface object documentation for more information about colors + and pixel formats. + + .. ## Surface.map_rgb ## + + .. method:: unmap_rgb + + | :sl:`convert a mapped integer color value into a Color` + | :sg:`unmap_rgb(mapped_int) -> Color` + + Convert an mapped integer color into the ``RGB`` color components for + this Surface. Mapped color values are not often used inside pygame, but + can be passed to most functions that require a Surface and a color. + + See the Surface object documentation for more information about colors + and pixel formats. + + .. ## Surface.unmap_rgb ## + + .. method:: set_clip + + | :sl:`set the current clipping area of the Surface` + | :sg:`set_clip(rect) -> None` + | :sg:`set_clip(None) -> None` + + Each Surface has an active clipping area. This is a rectangle that + represents the only pixels on the Surface that can be modified. If + ``None`` is passed for the rectangle the full Surface will be available + for changes. + + The clipping area is always restricted to the area of the Surface itself. + If the clip rectangle is too large it will be shrunk to fit inside the + Surface. + + .. ## Surface.set_clip ## + + .. method:: get_clip + + | :sl:`get the current clipping area of the Surface` + | :sg:`get_clip() -> Rect` + + Return a rectangle of the current clipping area. The Surface will always + return a valid rectangle that will never be outside the bounds of the + image. If the Surface has had ``None`` set for the clipping area, the + Surface will return a rectangle with the full area of the Surface. + + .. ## Surface.get_clip ## + + .. method:: subsurface + + | :sl:`create a new surface that references its parent` + | :sg:`subsurface(Rect) -> Surface` + + Returns a new Surface that shares its pixels with its new parent. The new + Surface is considered a child of the original. Modifications to either + Surface pixels will effect each other. Surface information like clipping + area and color keys are unique to each Surface. + + The new Surface will inherit the palette, color key, and alpha settings + from its parent. + + It is possible to have any number of subsurfaces and subsubsurfaces on + the parent. It is also possible to subsurface the display Surface if the + display mode is not hardware accelerated. + + See :meth:`get_offset()` and :meth:`get_parent()` to learn more + about the state of a subsurface. + + A subsurface will have the same class as the parent surface. + + .. ## Surface.subsurface ## + + .. method:: get_parent + + | :sl:`find the parent of a subsurface` + | :sg:`get_parent() -> Surface` + + Returns the parent Surface of a subsurface. If this is not a subsurface + then ``None`` will be returned. + + .. ## Surface.get_parent ## + + .. method:: get_abs_parent + + | :sl:`find the top level parent of a subsurface` + | :sg:`get_abs_parent() -> Surface` + + Returns the parent Surface of a subsurface. If this is not a subsurface + then this surface will be returned. + + .. ## Surface.get_abs_parent ## + + .. method:: get_offset + + | :sl:`find the position of a child subsurface inside a parent` + | :sg:`get_offset() -> (x, y)` + + Get the offset position of a child subsurface inside of a parent. If the + Surface is not a subsurface this will return (0, 0). + + .. ## Surface.get_offset ## + + .. method:: get_abs_offset + + | :sl:`find the absolute position of a child subsurface inside its top level parent` + | :sg:`get_abs_offset() -> (x, y)` + + Get the offset position of a child subsurface inside of its top level + parent Surface. If the Surface is not a subsurface this will return (0, + 0). + + .. ## Surface.get_abs_offset ## + + .. method:: get_size + + | :sl:`get the dimensions of the Surface` + | :sg:`get_size() -> (width, height)` + + Return the width and height of the Surface in pixels. + + .. ## Surface.get_size ## + + .. method:: get_width + + | :sl:`get the width of the Surface` + | :sg:`get_width() -> width` + + Return the width of the Surface in pixels. + + .. ## Surface.get_width ## + + .. method:: get_height + + | :sl:`get the height of the Surface` + | :sg:`get_height() -> height` + + Return the height of the Surface in pixels. + + .. ## Surface.get_height ## + + .. method:: get_rect + + | :sl:`get the rectangular area of the Surface` + | :sg:`get_rect(\**kwargs) -> Rect` + + Returns a new rectangle covering the entire surface. This rectangle will + always start at (0, 0) with a width and height the same size as the image. + + You can pass keyword argument values to this function. These named values + will be applied to the attributes of the Rect before it is returned. An + example would be ``mysurf.get_rect(center=(100, 100))`` to create a + rectangle for the Surface centered at a given position. + + .. ## Surface.get_rect ## + + .. method:: get_bitsize + + | :sl:`get the bit depth of the Surface pixel format` + | :sg:`get_bitsize() -> int` + + Returns the number of bits used to represent each pixel. This value may + not exactly fill the number of bytes used per pixel. For example a 15 bit + Surface still requires a full 2 bytes. + + .. ## Surface.get_bitsize ## + + .. method:: get_bytesize + + | :sl:`get the bytes used per Surface pixel` + | :sg:`get_bytesize() -> int` + + Return the number of bytes used per pixel. + + .. ## Surface.get_bytesize ## + + .. method:: get_flags + + | :sl:`get the additional flags used for the Surface` + | :sg:`get_flags() -> int` + + Returns a set of current Surface features. Each feature is a bit in the + flags bitmask. Typical flags are ``RLEACCEL``, ``SRCALPHA``, and + ``SRCCOLORKEY``. + + Here is a more complete list of flags. A full list can be found in + ``SDL_video.h`` + + :: + + SWSURFACE 0x00000000 # Surface is in system memory + HWSURFACE 0x00000001 # (obsolete in pygame 2) Surface is in video memory + ASYNCBLIT 0x00000004 # (obsolete in pygame 2) Use asynchronous blits if possible + + See :func:`pygame.display.set_mode()` for flags exclusive to the + display surface. + + Used internally (read-only) + + :: + + HWACCEL 0x00000100 # Blit uses hardware acceleration + SRCCOLORKEY 0x00001000 # Blit uses a source color key + RLEACCELOK 0x00002000 # Private flag + RLEACCEL 0x00004000 # Surface is RLE encoded + SRCALPHA 0x00010000 # Blit uses source alpha blending + PREALLOC 0x01000000 # Surface uses preallocated memory + + .. ## Surface.get_flags ## + + .. method:: get_pitch + + | :sl:`get the number of bytes used per Surface row` + | :sg:`get_pitch() -> int` + + Return the number of bytes separating each row in the Surface. Surfaces + in video memory are not always linearly packed. Subsurfaces will also + have a larger pitch than their real width. + + This value is not needed for normal pygame usage. + + .. ## Surface.get_pitch ## + + .. method:: get_masks + + | :sl:`the bitmasks needed to convert between a color and a mapped integer` + | :sg:`get_masks() -> (R, G, B, A)` + + Returns the bitmasks used to isolate each color in a mapped integer. + + This value is not needed for normal pygame usage. + + .. ## Surface.get_masks ## + + .. method:: set_masks + + | :sl:`set the bitmasks needed to convert between a color and a mapped integer` + | :sg:`set_masks((r,g,b,a)) -> None` + + This is not needed for normal pygame usage. + + .. note:: In SDL2, the masks are read-only and accordingly this method will raise + an AttributeError if called. + + .. versionadded:: 1.8.1 + + .. ## Surface.set_masks ## + + .. method:: get_shifts + + | :sl:`the bit shifts needed to convert between a color and a mapped integer` + | :sg:`get_shifts() -> (R, G, B, A)` + + Returns the pixel shifts need to convert between each color and a mapped + integer. + + This value is not needed for normal pygame usage. + + .. ## Surface.get_shifts ## + + .. method:: set_shifts + + | :sl:`sets the bit shifts needed to convert between a color and a mapped integer` + | :sg:`set_shifts((r,g,b,a)) -> None` + + This is not needed for normal pygame usage. + + .. note:: In SDL2, the shifts are read-only and accordingly this method will raise + an AttributeError if called. + + .. versionadded:: 1.8.1 + + .. ## Surface.set_shifts ## + + .. method:: get_losses + + | :sl:`the significant bits used to convert between a color and a mapped integer` + | :sg:`get_losses() -> (R, G, B, A)` + + Return the least significant number of bits stripped from each color in a + mapped integer. + + This value is not needed for normal pygame usage. + + .. ## Surface.get_losses ## + + .. method:: get_bounding_rect + + | :sl:`find the smallest rect containing data` + | :sg:`get_bounding_rect(min_alpha = 1) -> Rect` + + Returns the smallest rectangular region that contains all the pixels in + the surface that have an alpha value greater than or equal to the minimum + alpha value. + + This function will temporarily lock and unlock the Surface as needed. + + .. versionadded:: 1.8 + + .. ## Surface.get_bounding_rect ## + + .. method:: get_view + + | :sl:`return a buffer view of the Surface's pixels.` + | :sg:`get_view(='2') -> BufferProxy` + + Return an object which exports a surface's internal pixel buffer as + a C level array struct, Python level array interface or a C level + buffer interface. The new buffer protocol is supported. + + The kind argument is the length 1 string '0', '1', '2', '3', + 'r', 'g', 'b', or 'a'. The letters are case insensitive; + 'A' will work as well. The argument can be either a Unicode or byte (char) + string. The default is '2'. + + '0' returns a contiguous unstructured bytes view. No surface shape + information is given. A ``ValueError`` is raised if the surface's pixels + are discontinuous. + + '1' returns a (surface-width * surface-height) array of continuous + pixels. A ``ValueError`` is raised if the surface pixels are + discontinuous. + + '2' returns a (surface-width, surface-height) array of raw pixels. + The pixels are surface-bytesize-d unsigned integers. The pixel format is + surface specific. The 3 byte unsigned integers of 24 bit surfaces are + unlikely accepted by anything other than other pygame functions. + + '3' returns a (surface-width, surface-height, 3) array of ``RGB`` color + components. Each of the red, green, and blue components are unsigned + bytes. Only 24-bit and 32-bit surfaces are supported. The color + components must be in either ``RGB`` or ``BGR`` order within the pixel. + + 'r' for red, 'g' for green, 'b' for blue, and 'a' for alpha return a + (surface-width, surface-height) view of a single color component within a + surface: a color plane. Color components are unsigned bytes. Both 24-bit + and 32-bit surfaces support 'r', 'g', and 'b'. Only 32-bit surfaces with + ``SRCALPHA`` support 'a'. + + The surface is locked only when an exposed interface is accessed. + For new buffer interface accesses, the surface is unlocked once the + last buffer view is released. For array interface and old buffer + interface accesses, the surface remains locked until the BufferProxy + object is released. + + .. versionadded:: 1.9.2 + + .. method:: get_buffer + + | :sl:`acquires a buffer object for the pixels of the Surface.` + | :sg:`get_buffer() -> BufferProxy` + + Return a buffer object for the pixels of the Surface. The buffer can be + used for direct pixel access and manipulation. Surface pixel data is + represented as an unstructured block of memory, with a start address + and length in bytes. The data need not be contiguous. Any gaps are + included in the length, but otherwise ignored. + + This method implicitly locks the Surface. The lock will be released when + the returned :mod:`pygame.BufferProxy` object is garbage collected. + + .. versionadded:: 1.8 + + .. ## Surface.get_buffer ## + + .. attribute:: _pixels_address + + | :sl:`pixel buffer address` + | :sg:`_pixels_address -> int` + + The starting address of the surface's raw pixel bytes. + + .. versionadded:: 1.9.2 + + .. ## pygame.Surface ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/surfarray.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/surfarray.rst.txt new file mode 100644 index 0000000..c29723a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/surfarray.rst.txt @@ -0,0 +1,337 @@ +.. include:: common.txt + +:mod:`pygame.surfarray` +======================= + +.. module:: pygame.surfarray + :synopsis: pygame module for accessing surface pixel data using array interfaces + +| :sl:`pygame module for accessing surface pixel data using array interfaces` + +Functions to convert between NumPy arrays and Surface objects. This module +will only be functional when pygame can use the external NumPy package. +If NumPy can't be imported, ``surfarray`` becomes a ``MissingModule`` object. + +Every pixel is stored as a single integer value to represent the red, green, +and blue colors. The 8-bit images use a value that looks into a colormap. Pixels +with higher depth use a bit packing process to place three or four values into +a single number. + +The arrays are indexed by the ``X`` axis first, followed by the ``Y`` axis. +Arrays that treat the pixels as a single integer are referred to as 2D arrays. +This module can also separate the red, green, and blue color values into +separate indices. These types of arrays are referred to as 3D arrays, and the +last index is 0 for red, 1 for green, and 2 for blue. + +The pixels of a 2D array as returned by :func:`array2d` and :func:`pixels2d` +are mapped to the specific surface. Use :meth:`pygame.Surface.unmap_rgb` +to convert to a color, and :meth:`pygame.Surface.map_rgb` to get the surface +specific pixel value of a color. Integer pixel values can only be used directly +between surfaces with matching pixel layouts (see :class:`pygame.Surface`). + +All functions that refer to "array" will copy the surface information to a new +numpy array. All functions that refer to "pixels" will directly reference the +pixels from the surface and any changes performed to the array will make changes +in the surface. As this last functions share memory with the surface, this one +will be locked during the lifetime of the array. + +.. function:: array2d + + | :sl:`Copy pixels into a 2d array` + | :sg:`array2d(Surface) -> array` + + Copy the :meth:`mapped ` (raw) pixels from a Surface + into a 2D array. + The bit depth of the surface will control the size of the integer values, + and will work for any type of pixel format. + + This function will temporarily lock the Surface as pixels are copied + (see the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method). + + .. ## pygame.surfarray.array2d ## + +.. function:: pixels2d + + | :sl:`Reference pixels into a 2d array` + | :sg:`pixels2d(Surface) -> array` + + Create a new 2D array that directly references the pixel values in a + Surface. Any changes to the array will affect the pixels in the Surface. + This is a fast operation since no data is copied. + + Pixels from a 24-bit Surface cannot be referenced, but all other Surface bit + depths can. + + The Surface this references will remain locked for the lifetime of the array, + since the array generated by this function shares memory with the surface. + See the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method. + + .. ## pygame.surfarray.pixels2d ## + +.. function:: array3d + + | :sl:`Copy pixels into a 3d array` + | :sg:`array3d(Surface) -> array` + + Copy the pixels from a Surface into a 3D array. The bit depth of the surface + will control the size of the integer values, and will work for any type of + pixel format. + + This function will temporarily lock the Surface as pixels are copied (see + the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method). + + .. ## pygame.surfarray.array3d ## + +.. function:: pixels3d + + | :sl:`Reference pixels into a 3d array` + | :sg:`pixels3d(Surface) -> array` + + Create a new 3D array that directly references the pixel values in a + Surface. Any changes to the array will affect the pixels in the Surface. + This is a fast operation since no data is copied. + + This will only work on Surfaces that have 24-bit or 32-bit formats. Lower + pixel formats cannot be referenced. + + The Surface this references will remain locked for the lifetime of the array, + since the array generated by this function shares memory with the surface. + See the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method. + + .. ## pygame.surfarray.pixels3d ## + +.. function:: array_alpha + + | :sl:`Copy pixel alphas into a 2d array` + | :sg:`array_alpha(Surface) -> array` + + Copy the pixel alpha values (degree of transparency) from a Surface into a + 2D array. This will work for any type of Surface format. Surfaces without a + pixel alpha will return an array with all opaque values. + + This function will temporarily lock the Surface as pixels are copied (see + the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method). + + .. ## pygame.surfarray.array_alpha ## + +.. function:: pixels_alpha + + | :sl:`Reference pixel alphas into a 2d array` + | :sg:`pixels_alpha(Surface) -> array` + + Create a new 2D array that directly references the alpha values (degree of + transparency) in a Surface. Any changes to the array will affect the pixels + in the Surface. This is a fast operation since no data is copied. + + This can only work on 32-bit Surfaces with a per-pixel alpha value. + + The Surface this references will remain locked for the lifetime of the array, + since the array generated by this function shares memory with the surface. + See the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method. + + .. ## pygame.surfarray.pixels_alpha ## + +.. function:: array_red + + | :sl:`Copy red pixels into a 2d array` + | :sg:`array_red(Surface) -> array` + + Copy the pixel red values from a Surface into a 2D array. This will work + for any type of Surface format. + + This function will temporarily lock the Surface as pixels are copied (see + the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method). + + .. versionadded:: 2.0.2 + + .. ## pygame.surfarray.array_red ## + +.. function:: pixels_red + + | :sl:`Reference pixel red into a 2d array.` + | :sg:`pixels_red (Surface) -> array` + + Create a new 2D array that directly references the red values in a Surface. + Any changes to the array will affect the pixels in the Surface. This is a + fast operation since no data is copied. + + This can only work on 24-bit or 32-bit Surfaces. + + The Surface this references will remain locked for the lifetime of the array, + since the array generated by this function shares memory with the surface. + See the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method. + + .. ## pygame.surfarray.pixels_red ## + +.. function:: array_green + + | :sl:`Copy green pixels into a 2d array` + | :sg:`array_green(Surface) -> array` + + Copy the pixel green values from a Surface into a 2D array. This will work + for any type of Surface format. + + This function will temporarily lock the Surface as pixels are copied (see + the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method). + + .. versionadded:: 2.0.2 + + .. ## pygame.surfarray.array_green ## + +.. function:: pixels_green + + | :sl:`Reference pixel green into a 2d array.` + | :sg:`pixels_green (Surface) -> array` + + Create a new 2D array that directly references the green values in a + Surface. Any changes to the array will affect the pixels in the Surface. + This is a fast operation since no data is copied. + + This can only work on 24-bit or 32-bit Surfaces. + + The Surface this references will remain locked for the lifetime of the array, + since the array generated by this function shares memory with the surface. + See the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method. + + .. ## pygame.surfarray.pixels_green ## + +.. function:: array_blue + + | :sl:`Copy blue pixels into a 2d array` + | :sg:`array_blue(Surface) -> array` + + Copy the pixel blue values from a Surface into a 2D array. This will work + for any type of Surface format. + + This function will temporarily lock the Surface as pixels are copied (see + the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method). + + .. versionadded:: 2.0.2 + + .. ## pygame.surfarray.array_blue ## + +.. function:: pixels_blue + + | :sl:`Reference pixel blue into a 2d array.` + | :sg:`pixels_blue (Surface) -> array` + + Create a new 2D array that directly references the blue values in a Surface. + Any changes to the array will affect the pixels in the Surface. This is a + fast operation since no data is copied. + + This can only work on 24-bit or 32-bit Surfaces. + + The Surface this references will remain locked for the lifetime of the array, + since the array generated by this function shares memory with the surface. + See the :meth:`pygame.Surface.lock` - lock the Surface memory for pixel + access method. + + .. ## pygame.surfarray.pixels_blue ## + +.. function:: array_colorkey + + | :sl:`Copy the colorkey values into a 2d array` + | :sg:`array_colorkey(Surface) -> array` + + Create a new array with the colorkey transparency value from each pixel. If + the pixel matches the colorkey it will be fully transparent; otherwise it + will be fully opaque. + + This will work on any type of Surface format. If the image has no colorkey a + solid opaque array will be returned. + + This function will temporarily lock the Surface as pixels are copied. + + .. ## pygame.surfarray.array_colorkey ## + +.. function:: make_surface + + | :sl:`Copy an array to a new surface` + | :sg:`make_surface(array) -> Surface` + + Create a new Surface that best resembles the data and format on the array. + The array can be 2D or 3D with any sized integer values. Function + make_surface uses the array struct interface to acquire array properties, + so is not limited to just NumPy arrays. See :mod:`pygame.pixelcopy`. + + New in pygame 1.9.2: array struct interface support. + + .. ## pygame.surfarray.make_surface ## + +.. function:: blit_array + + | :sl:`Blit directly from a array values` + | :sg:`blit_array(Surface, array) -> None` + + Directly copy values from an array into a Surface. This is faster than + converting the array into a Surface and blitting. The array must be the same + dimensions as the Surface and will completely replace all pixel values. Only + integer, ASCII character and record arrays are accepted. + + This function will temporarily lock the Surface as the new values are + copied. + + .. ## pygame.surfarray.blit_array ## + +.. function:: map_array + + | :sl:`Map a 3d array into a 2d array` + | :sg:`map_array(Surface, array3d) -> array2d` + + Convert a 3D array into a 2D array. This will use the given Surface format + to control the conversion. Palette surface formats are supported for NumPy + arrays. + + .. ## pygame.surfarray.map_array ## + +.. function:: use_arraytype + + | :sl:`Sets the array system to be used for surface arrays` + | :sg:`use_arraytype (arraytype) -> None` + + DEPRECATED: Uses the requested array type for the module functions. + The only supported arraytype is ``'numpy'``. Other values will raise + ValueError. Using this function will raise a ``DeprecationWarning``. + + .. ## pygame.surfarray.use_arraytype ## + +.. function:: get_arraytype + + | :sl:`Gets the currently active array type.` + | :sg:`get_arraytype () -> str` + + DEPRECATED: Returns the currently active array type. This will be a value of the + ``get_arraytypes()`` tuple and indicates which type of array module is used + for the array creation. Using this function will raise a ``DeprecationWarning``. + + .. versionadded:: 1.8 + + .. ## pygame.surfarray.get_arraytype ## + +.. function:: get_arraytypes + + | :sl:`Gets the array system types currently supported.` + | :sg:`get_arraytypes () -> tuple` + + DEPRECATED: Checks, which array systems are available and returns them as a tuple of + strings. The values of the tuple can be used directly in the + :func:`pygame.surfarray.use_arraytype` () method. If no supported array + system could be found, None will be returned. Using this function will raise a + ``DeprecationWarning``. + + .. versionadded:: 1.8 + + .. ## pygame.surfarray.get_arraytypes ## + +.. ## pygame.surfarray ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/tests.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/tests.rst.txt new file mode 100644 index 0000000..09be45d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/tests.rst.txt @@ -0,0 +1,108 @@ +.. include:: common.txt + +:mod:`pygame.tests` +=================== + +.. module:: pygame.tests + :synopsis: Pygame unit test suite package + +| :sl:`Pygame unit test suite package` + +A quick way to run the test suite package from the command line is to import +the go submodule with the Python -m option: + +:: + + python -m pygame.tests [] + +Command line option --help displays a usage message. Available options +correspond to the :func:`pygame.tests.run` arguments. + +The xxxx_test submodules of the tests package are unit test suites for +individual parts of pygame. Each can also be run as a main program. This is +useful if the test, such as cdrom_test, is interactive. + +For pygame development the test suite can be run from a pygame distribution +root directory. Program ``run_tests.py`` is provided for convenience, though +test/go.py can be run directly. + +Module level tags control which modules are included in a unit test run. Tags +are assigned to a unit test module with a corresponding _tags.py module. +The tags module has the global __tags__, a list of tag names. For example, +``cdrom_test.py`` has a tag file ``cdrom_tags.py`` containing a tags list that +has the 'interactive' string. The 'interactive' tag indicates ``cdrom_test.py`` +expects user input. It is excluded from a ``run_tests.py`` or +``pygame.tests.go`` run. Two other tags that are excluded are 'ignore' and +'subprocess_ignore'. These two tags indicate unit tests that will not run on a +particular platform, or for which no corresponding pygame module is available. +The test runner will list each excluded module along with the tag responsible. + +.. function:: run + + | :sl:`Run the pygame unit test suite` + | :sg:`run(*args, **kwds) -> tuple` + + Positional arguments (optional): + + :: + + The names of tests to include. If omitted then all tests are run. Test names + need not include the trailing '_test'. + + Keyword arguments: + + :: + + incomplete - fail incomplete tests (default False) + nosubprocess - run all test suites in the current process + (default False, use separate subprocesses) + dump - dump failures/errors as dict ready to eval (default False) + file - if provided, the name of a file into which to dump failures/errors + timings - if provided, the number of times to run each individual test to + get an average run time (default is run each test once) + exclude - A list of TAG names to exclude from the run + show_output - show silenced stderr/stdout on errors (default False) + all - dump all results, not just errors (default False) + randomize - randomize order of tests (default False) + seed - if provided, a seed randomizer integer + multi_thread - if provided, the number of THREADS in which to run + subprocessed tests + time_out - if subprocess is True then the time limit in seconds before + killing a test (default 30) + fake - if provided, the name of the fake tests package in the + run_tests__tests subpackage to run instead of the normal + pygame tests + python - the path to a python executable to run subprocessed tests + (default sys.executable) + + Return value: + + :: + + A tuple of total number of tests run, dictionary of error information. + The dictionary is empty if no errors were recorded. + + By default individual test modules are run in separate subprocesses. This + recreates normal pygame usage where ``pygame.init()`` and ``pygame.quit()`` + are called only once per program execution, and avoids unfortunate + interactions between test modules. Also, a time limit is placed on test + execution, so frozen tests are killed when there time allotment expired. Use + the single process option if threading is not working properly or if tests + are taking too long. It is not guaranteed that all tests will pass in single + process mode. + + Tests are run in a randomized order if the randomize argument is True or a + seed argument is provided. If no seed integer is provided then the system + time is used. + + Individual test modules may have a __tags__ attribute, a list of tag strings + used to selectively omit modules from a run. By default only 'interactive' + modules such as cdrom_test are ignored. An interactive module must be run + from the console as a Python program. + + This function can only be called once per Python session. It is not + reentrant. + + .. ## pygame.tests.run ## + +.. ## pygame.tests ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/time.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/time.rst.txt new file mode 100644 index 0000000..4c513c4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/time.rst.txt @@ -0,0 +1,165 @@ +.. include:: common.txt + +:mod:`pygame.time` +================== + +.. module:: pygame.time + :synopsis: pygame module for monitoring time + +| :sl:`pygame module for monitoring time` + +Times in pygame are represented in milliseconds (1/1000 seconds). Most +platforms have a limited time resolution of around 10 milliseconds. This +resolution, in milliseconds, is given in the ``TIMER_RESOLUTION`` constant. + +.. function:: get_ticks + + | :sl:`get the time in milliseconds` + | :sg:`get_ticks() -> milliseconds` + + Return the number of milliseconds since ``pygame.init()`` was called. Before + pygame is initialized this will always be 0. + + .. ## pygame.time.get_ticks ## + +.. function:: wait + + | :sl:`pause the program for an amount of time` + | :sg:`wait(milliseconds) -> time` + + Will pause for a given number of milliseconds. This function sleeps the + process to share the processor with other programs. A program that waits for + even a few milliseconds will consume very little processor time. It is + slightly less accurate than the ``pygame.time.delay()`` function. + + This returns the actual number of milliseconds used. + + .. ## pygame.time.wait ## + +.. function:: delay + + | :sl:`pause the program for an amount of time` + | :sg:`delay(milliseconds) -> time` + + Will pause for a given number of milliseconds. This function will use the + processor (rather than sleeping) in order to make the delay more accurate + than ``pygame.time.wait()``. + + This returns the actual number of milliseconds used. + + .. ## pygame.time.delay ## + +.. function:: set_timer + + | :sl:`repeatedly create an event on the event queue` + | :sg:`set_timer(event, millis) -> None` + | :sg:`set_timer(event, millis, loops=0) -> None` + + Set an event to appear on the event queue every given number of milliseconds. + The first event will not appear until the amount of time has passed. + + The ``event`` attribute can be a ``pygame.event.Event`` object or an integer + type that denotes an event. + + ``loops`` is an integer that denotes the number of events posted. If 0 (default) + then the events will keep getting posted, unless explicitly stopped. + + To disable the timer for such an event, call the function again with the same + event argument with ``millis`` argument set to 0. + + It is also worth mentioning that a particular event type can only be put on a + timer once. In other words, there cannot be two timers for the same event type. + Setting an event timer for a particular event discards the old one for that + event type. + + ``loops`` replaces the ``once`` argument, and this does not break backward + compatability + + .. versionadded:: 2.0.0.dev3 once argument added. + .. versionchanged:: 2.0.1 event argument supports ``pygame.event.Event`` object + .. versionadded:: 2.0.1 added loops argument to replace once argument + + .. ## pygame.time.set_timer ## + +.. class:: Clock + + | :sl:`create an object to help track time` + | :sg:`Clock() -> Clock` + + Creates a new Clock object that can be used to track an amount of time. The + clock also provides several functions to help control a game's framerate. + + .. method:: tick + + | :sl:`update the clock` + | :sg:`tick(framerate=0) -> milliseconds` + + This method should be called once per frame. It will compute how many + milliseconds have passed since the previous call. + + If you pass the optional framerate argument the function will delay to + keep the game running slower than the given ticks per second. This can be + used to help limit the runtime speed of a game. By calling + ``Clock.tick(40)`` once per frame, the program will never run at more + than 40 frames per second. + + Note that this function uses SDL_Delay function which is not accurate on + every platform, but does not use much CPU. Use tick_busy_loop if you want + an accurate timer, and don't mind chewing CPU. + + .. ## Clock.tick ## + + .. method:: tick_busy_loop + + | :sl:`update the clock` + | :sg:`tick_busy_loop(framerate=0) -> milliseconds` + + This method should be called once per frame. It will compute how many + milliseconds have passed since the previous call. + + If you pass the optional framerate argument the function will delay to + keep the game running slower than the given ticks per second. This can be + used to help limit the runtime speed of a game. By calling + ``Clock.tick_busy_loop(40)`` once per frame, the program will never run at + more than 40 frames per second. + + Note that this function uses :func:`pygame.time.delay`, which uses lots + of CPU in a busy loop to make sure that timing is more accurate. + + .. versionadded:: 1.8 + + .. ## Clock.tick_busy_loop ## + + .. method:: get_time + + | :sl:`time used in the previous tick` + | :sg:`get_time() -> milliseconds` + + The number of milliseconds that passed between the previous two calls to + ``Clock.tick()``. + + .. ## Clock.get_time ## + + .. method:: get_rawtime + + | :sl:`actual time used in the previous tick` + | :sg:`get_rawtime() -> milliseconds` + + Similar to ``Clock.get_time()``, but does not include any time used + while ``Clock.tick()`` was delaying to limit the framerate. + + .. ## Clock.get_rawtime ## + + .. method:: get_fps + + | :sl:`compute the clock framerate` + | :sg:`get_fps() -> float` + + Compute your game's framerate (in frames per second). It is computed by + averaging the last ten calls to ``Clock.tick()``. + + .. ## Clock.get_fps ## + + .. ## pygame.time.Clock ## + +.. ## pygame.time ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/touch.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/touch.rst.txt new file mode 100644 index 0000000..320da05 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/touch.rst.txt @@ -0,0 +1,66 @@ +.. include:: common.txt + +:mod:`pygame._sdl2.touch` +========================= + +.. module:: pygame._sdl2.touch + :synopsis: pygame module to work with touch input + +| :sl:`pygame module to work with touch input` + +.. versionadded:: 2 This module requires SDL2. + +.. function:: get_num_devices + + | :sl:`get the number of touch devices` + | :sg:`get_num_devices() -> int` + + Return the number of available touch devices. + + .. ## pygame._sdl2.touch.get_num_devices ## + +.. function:: get_device + + | :sl:`get the a touch device id for a given index` + | :sg:`get_device(index) -> touchid` + + :param int index: This number is at least 0 and less than the + :func:`number of devices `. + + Return an integer id associated with the given ``index``. + + .. ## pygame._sdl2.touch.get_device ## + +.. function:: get_num_fingers + + | :sl:`the number of active fingers for a given touch device` + | :sg:`get_num_fingers(touchid) -> int` + + Return the number of fingers active for the touch device + whose id is `touchid`. + + .. ## pygame._sdl2.touch.get_num_fingers ## + +.. function:: get_finger + + | :sl:`get information about an active finger` + | :sg:`get_finger(touchid, index) -> int` + + :param int touchid: The touch device id. + :param int index: The index of the finger to return + information about, between 0 and the + :func:`number of active fingers `. + + Return a dict for the finger ``index`` active on ``touchid``. + The dict contains these keys: + + :: + + id the id of the finger (an integer). + x the normalized x position of the finger, between 0 and 1. + y the normalized y position of the finger, between 0 and 1. + pressure the amount of pressure applied by the finger, between 0 and 1. + + .. ## pygame._sdl2.touch.get_finger ## + +.. ## pygame._sdl2.touch ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/transform.rst.txt b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/transform.rst.txt new file mode 100644 index 0000000..29a4c43 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_sources/ref/transform.rst.txt @@ -0,0 +1,271 @@ +.. include:: common.txt + +:mod:`pygame.transform` +======================= + +.. module:: pygame.transform + :synopsis: pygame module to transform surfaces + +| :sl:`pygame module to transform surfaces` + +A Surface transform is an operation that moves or resizes the pixels. All these +functions take a Surface to operate on and return a new Surface with the +results. + +Some of the transforms are considered destructive. These means every time they +are performed they lose pixel data. Common examples of this are resizing and +rotating. For this reason, it is better to re-transform the original surface +than to keep transforming an image multiple times. (For example, suppose you +are animating a bouncing spring which expands and contracts. If you applied the +size changes incrementally to the previous images, you would lose detail. +Instead, always begin with the original image and scale to the desired size.) + +.. versionchanged:: 2.0.2 transform functions now support keyword arguments. + +.. function:: flip + + | :sl:`flip vertically and horizontally` + | :sg:`flip(surface, flip_x, flip_y) -> Surface` + + This can flip a Surface either vertically, horizontally, or both. + The arguments ``flip_x`` and ``flip_y`` are booleans that control whether + to flip each axis. Flipping a Surface is non-destructive and returns a new + Surface with the same dimensions. + + .. ## pygame.transform.flip ## + +.. function:: scale + + | :sl:`resize to new resolution` + | :sg:`scale(surface, size, dest_surface=None) -> Surface` + + Resizes the Surface to a new size, given as (width, height). + This is a fast scale operation that does not sample the results. + + An optional destination surface can be used, rather than have it create a + new one. This is quicker if you want to repeatedly scale something. However + the destination must be the same size as the size (width, height) passed in. Also + the destination surface must be the same format. + + .. ## pygame.transform.scale ## + +.. function:: rotate + + | :sl:`rotate an image` + | :sg:`rotate(surface, angle) -> Surface` + + Unfiltered counterclockwise rotation. The angle argument represents degrees + and can be any floating point value. Negative angle amounts will rotate + clockwise. + + Unless rotating by 90 degree increments, the image will be padded larger to + hold the new size. If the image has pixel alphas, the padded area will be + transparent. Otherwise pygame will pick a color that matches the Surface + colorkey or the topleft pixel value. + + .. ## pygame.transform.rotate ## + +.. function:: rotozoom + + | :sl:`filtered scale and rotation` + | :sg:`rotozoom(surface, angle, scale) -> Surface` + + This is a combined scale and rotation transform. The resulting Surface will + be a filtered 32-bit Surface. The scale argument is a floating point value + that will be multiplied by the current resolution. The angle argument is a + floating point value that represents the counterclockwise degrees to rotate. + A negative rotation angle will rotate clockwise. + + .. ## pygame.transform.rotozoom ## + +.. function:: scale2x + + | :sl:`specialized image doubler` + | :sg:`scale2x(surface, dest_surface=None) -> Surface` + + This will return a new image that is double the size of the original. It + uses the AdvanceMAME Scale2X algorithm which does a 'jaggie-less' scale of + bitmap graphics. + + This really only has an effect on simple images with solid colors. On + photographic and antialiased images it will look like a regular unfiltered + scale. + + An optional destination surface can be used, rather than have it create a + new one. This is quicker if you want to repeatedly scale something. However + the destination must be twice the size of the source surface passed in. Also + the destination surface must be the same format. + + .. ## pygame.transform.scale2x ## + +.. function:: smoothscale + + | :sl:`scale a surface to an arbitrary size smoothly` + | :sg:`smoothscale(surface, size, dest_surface=None) -> Surface` + + Uses one of two different algorithms for scaling each dimension of the input + surface as required. For shrinkage, the output pixels are area averages of + the colors they cover. For expansion, a bilinear filter is used. For the + x86-64 and i686 architectures, optimized ``MMX`` routines are included and + will run much faster than other machine types. The size is a 2 number + sequence for (width, height). This function only works for 24-bit or 32-bit + surfaces. An exception will be thrown if the input surface bit depth is less + than 24. + + .. versionadded:: 1.8 + + .. ## pygame.transform.smoothscale ## + +.. function:: get_smoothscale_backend + + | :sl:`return smoothscale filter version in use: 'GENERIC', 'MMX', or 'SSE'` + | :sg:`get_smoothscale_backend() -> string` + + Shows whether or not smoothscale is using ``MMX`` or ``SSE`` acceleration. + If no acceleration is available then "GENERIC" is returned. For a x86 + processor the level of acceleration to use is determined at runtime. + + This function is provided for pygame testing and debugging. + + .. ## pygame.transform.get_smoothscale_backend ## + +.. function:: set_smoothscale_backend + + | :sl:`set smoothscale filter version to one of: 'GENERIC', 'MMX', or 'SSE'` + | :sg:`set_smoothscale_backend(backend) -> None` + + Sets smoothscale acceleration. Takes a string argument. A value of 'GENERIC' + turns off acceleration. 'MMX' uses ``MMX`` instructions only. 'SSE' allows + ``SSE`` extensions as well. A value error is raised if type is not + recognized or not supported by the current processor. + + This function is provided for pygame testing and debugging. If smoothscale + causes an invalid instruction error then it is a pygame/SDL bug that should + be reported. Use this function as a temporary fix only. + + .. ## pygame.transform.set_smoothscale_backend ## + +.. function:: chop + + | :sl:`gets a copy of an image with an interior area removed` + | :sg:`chop(surface, rect) -> Surface` + + Extracts a portion of an image. All vertical and horizontal pixels + surrounding the given rectangle area are removed. The corner areas (diagonal + to the rect) are then brought together. (The original image is not altered + by this operation.) + + ``NOTE``: If you want a "crop" that returns the part of an image within a + rect, you can blit with a rect to a new surface or copy a subsurface. + + .. ## pygame.transform.chop ## + +.. function:: laplacian + + | :sl:`find edges in a surface` + | :sg:`laplacian(surface, dest_surface=None) -> Surface` + + Finds the edges in a surface using the laplacian algorithm. + + .. versionadded:: 1.8 + + .. ## pygame.transform.laplacian ## + +.. function:: average_surfaces + + | :sl:`find the average surface from many surfaces.` + | :sg:`average_surfaces(surfaces, dest_surface=None, palette_colors=1) -> Surface` + + Takes a sequence of surfaces and returns a surface with average colors from + each of the surfaces. + + palette_colors - if true we average the colors in palette, otherwise we + average the pixel values. This is useful if the surface is actually + greyscale colors, and not palette colors. + + Note, this function currently does not handle palette using surfaces + correctly. + + .. versionadded:: 1.8 + .. versionadded:: 1.9 ``palette_colors`` argument + + .. ## pygame.transform.average_surfaces ## + +.. function:: average_color + + | :sl:`finds the average color of a surface` + | :sg:`average_color(surface, rect=None) -> Color` + + Finds the average color of a Surface or a region of a surface specified by a + Rect, and returns it as a Color. + + .. ## pygame.transform.average_color ## + +.. function:: threshold + + | :sl:`finds which, and how many pixels in a surface are within a threshold of a 'search_color' or a 'search_surf'.` + | :sg:`threshold(dest_surface, surface, search_color, threshold=(0,0,0,0), set_color=(0,0,0,0), set_behavior=1, search_surf=None, inverse_set=False) -> num_threshold_pixels` + + This versatile function can be used for find colors in a 'surf' close to a 'search_color' + or close to colors in a separate 'search_surf'. + + It can also be used to transfer pixels into a 'dest_surf' that match or don't match. + + By default it sets pixels in the 'dest_surf' where all of the pixels NOT within the + threshold are changed to set_color. If inverse_set is optionally set to True, + the pixels that ARE within the threshold are changed to set_color. + + If the optional 'search_surf' surface is given, it is used to threshold against + rather than the specified 'set_color'. That is, it will find each pixel in the + 'surf' that is within the 'threshold' of the pixel at the same coordinates + of the 'search_surf'. + + :param dest_surf: Surface we are changing. See 'set_behavior'. + Should be None if counting (set_behavior is 0). + :type dest_surf: pygame.Surface or None + + :param pygame.Surface surf: Surface we are looking at. + + :param pygame.Color search_color: Color we are searching for. + + :param pygame.Color threshold: Within this distance from search_color (or search_surf). + You can use a threshold of (r,g,b,a) where the r,g,b can have different + thresholds. So you could use an r threshold of 40 and a blue threshold of 2 + if you like. + + :param set_color: Color we set in dest_surf. + :type set_color: pygame.Color or None + + :param int set_behavior: + - set_behavior=1 (default). Pixels in dest_surface will be changed to 'set_color'. + - set_behavior=0 we do not change 'dest_surf', just count. Make dest_surf=None. + - set_behavior=2 pixels set in 'dest_surf' will be from 'surf'. + + :param search_surf: + - search_surf=None (default). Search against 'search_color' instead. + - search_surf=Surface. Look at the color in 'search_surf' rather than using 'search_color'. + :type search_surf: pygame.Surface or None + + :param bool inverse_set: + - False, default. Pixels outside of threshold are changed. + - True, Pixels within threshold are changed. + + :rtype: int + :returns: The number of pixels that are within the 'threshold' in 'surf' + compared to either 'search_color' or `search_surf`. + + :Examples: + + See the threshold tests for a full of examples: https://github.com/pygame/pygame/blob/master/test/transform_test.py + + .. literalinclude:: ../../../test/transform_test.py + :pyobject: TransformModuleTest.test_threshold_dest_surf_not_change + + + .. versionadded:: 1.8 + .. versionchanged:: 1.9.4 + Fixed a lot of bugs and added keyword arguments. Test your code. + + .. ## pygame.transform.threshold ## + +.. ## pygame.transform ## diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/basic.css b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/basic.css new file mode 100644 index 0000000..603f6a8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/basic.css @@ -0,0 +1,905 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/doctools.js b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/doctools.js new file mode 100644 index 0000000..8cbf1b1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/doctools.js @@ -0,0 +1,323 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + break; + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + break; + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/documentation_options.js b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/documentation_options.js new file mode 100644 index 0000000..278cfec --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '2.1.2', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/file.png b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/file.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/jquery-3.5.1.js b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/jquery-3.5.1.js new file mode 100644 index 0000000..5093733 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/base.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/base.html new file mode 100644 index 0000000..06483e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/base.html @@ -0,0 +1,365 @@ + + + + + + + + + High level API exported by pygame.base — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/base.c¶

+

This extension module defines general purpose routines for starting and stopping +SDL as well as various conversion routines uses elsewhere in pygame.

+

C header: src_c/include/pygame.h

+
+
+PyObject *pgExc_SDLError¶
+

This is pygame.error, the exception type used to raise SDL errors.

+
+ +
+
+int pg_mod_autoinit(const char *modname)¶
+

Inits a pygame module, which has the name modname +Return 1 on success, 0 on error, with python +error set.

+
+ +
+
+void pg_mod_autoquit(const char *modname)¶
+

Quits a pygame module, which has the name modname

+
+ +
+
+void pg_RegisterQuit(void (*f)(void))¶
+

Register function f as a callback on Pygame termination. +Multiple functions can be registered. +Functions are called in the reverse order they were registered.

+
+ +
+
+int pg_IntFromObj(PyObject *obj, int *val)¶
+

Convert number like object obj to C int and place in argument val. +Return 1 on success, else 0. +No Python exceptions are raised.

+
+ +
+
+int pg_IntFromObjIndex(PyObject *obj, int index, int *val)¶
+

Convert number like object at position i in sequence obj +to C int and place in argument val. +Return 1 on success, 0 on failure. +No Python exceptions are raised.

+
+ +
+
+int pg_TwoIntsFromObj(PyObject *obj, int *val1, int *v2)¶
+

Convert the two number like objects in length 2 sequence obj +to C int and place in arguments val1 and val2 respectively. +Return 1 on success, 0 on failure. +No Python exceptions are raised.

+
+ +
+
+int pg_FloatFromObj(PyObject *obj, float *val)¶
+

Convert number like object obj to C float and place in argument val. +Returns 1 on success, 0 on failure. +No Python exceptions are raised.

+
+ +
+
+int pg_FloatFromObjIndex(PyObject *obj, int index, float *val)¶
+

Convert number like object at position i in sequence obj +to C float and place in argument val. +Return 1 on success, else 0. +No Python exceptions are raised.

+
+ +
+
+int pg_TwoFloatsFromObj(PyObject *obj, float *val1, float *val2)¶
+

Convert the two number like objects in length 2 sequence obj +to C float and place in arguments val1 and val2 respectively. +Return 1 on success, else 0. +No Python exceptions are raised.

+
+ +
+
+int pg_UintFromObj(PyObject *obj, Uint32 *val)¶
+

Convert number like object obj to unsigned 32 bit integer and place +in argument val. +Return 1 on success, else 0. +No Python exceptions are raised.

+
+ +
+
+int pg_UintFromObjIndex(PyObject *obj, int _index, Uint32 *val)¶
+

Convert number like object at position i in sequence obj +to unsigned 32 bit integer and place in argument val. +Return 1 on success, else 0. +No Python exceptions are raised.

+
+ +
+
+int pg_RGBAFromObj(PyObject *obj, Uint8 *RGBA)¶
+

Convert the color represented by object obj into a red, green, blue, alpha +length 4 C array RGBA. +The object must be a length 3 or 4 sequence of numbers having values +between 0 and 255 inclusive. +For a length 3 sequence an alpha value of 255 is assumed. +Return 1 on success, 0 otherwise. +No Python exceptions are raised.

+
+ +
+
+type pg_buffer¶
+
+
+Py_buffer view¶
+

A standard buffer description

+
+ +
+
+PyObject *consumer¶
+

The object holding the buffer

+
+ +
+
+pybuffer_releaseproc release_buffer¶
+

A buffer release callback.

+
+ +
+ +
+
+PyObject *pgExc_BufferError¶
+

Python exception type raised for any pg_buffer related errors.

+
+ +
+
+PyObject *pgBuffer_AsArrayInterface(Py_buffer *view_p)¶
+

Return a Python array interface object representation of buffer view_p. +On failure raise a Python exception and return NULL.

+
+ +
+
+PyObject *pgBuffer_AsArrayStruct(Py_buffer *view_p)¶
+

Return a Python array struct object representation of buffer view_p. +On failure raise a Python exception and return NULL.

+
+ +
+
+int pgObject_GetBuffer(PyObject *obj, pg_buffer *pg_view_p, int flags)¶
+

Request a buffer for object obj. +Argument flags are PyBUF options. +Return the buffer description in pg_view_p. +An object may support the Python buffer interface, the NumPy array interface, +or the NumPy array struct interface. +Return 0 on success, raise a Python exception and return -1 on failure.

+
+ +
+
+void pgBuffer_Release(Pg_buffer *pg_view_p)¶
+

Release the Pygame pg_view_p buffer.

+
+ +
+
+int pgDict_AsBuffer(Pg_buffer *pg_view_p, PyObject *dict, int flags)¶
+

Write the array interface dictionary buffer description dict into a Pygame +buffer description struct pg_view_p. +The flags PyBUF options describe the view type requested. +Return 0 on success, or raise a Python exception and return -1 on failure.

+
+ +
+
+void import_pygame_base()¶
+

Import the pygame.base module C API into an extension module. +On failure raise a Python exception.

+
+ +
+
+SDL_Window *pg_GetDefaultWindow(void)¶
+

Return the Pygame default SDL window created by a +pygame.display.set_mode() call, or NULL.

+

Availability: SDL 2.

+
+ +
+
+void pg_SetDefaultWindow(SDL_Window *win)¶
+

Replace the Pygame default window with win. +The previous window, if any, is destroyed. +Argument win may be NULL. +This function is called by pygame.display.set_mode().

+

Availability: SDL 2.

+
+ +
+
+pgSurfaceObject *pg_GetDefaultWindowSurface(void)¶
+

Return a borrowed reference to the Pygame default window display surface, +or NULL if no default window is open.

+

Availability: SDL 2.

+
+ +
+
+void pg_SetDefaultWindowSurface(pgSurfaceObject *screen)¶
+

Replace the Pygame default display surface with object screen. +The previous surface object, if any, is invalidated. +Argument screen may be NULL. +This functions is called by pygame.display.set_mode().

+

Availability: SDL 2.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/bufferproxy.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/bufferproxy.html new file mode 100644 index 0000000..38b4a68 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/bufferproxy.html @@ -0,0 +1,183 @@ + + + + + + + + + Class BufferProxy API exported by pgyame.bufferproxy — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/bufferproxy.c¶

+

This extension module defines Python type pygame.BufferProxypygame object to export a surface buffer through an array protocol.

+

Header file: src_c/include/pygame_bufferproxy.h

+
+
+PyTypeObject *pgBufproxy_Type¶
+

The pygame buffer proxy object type pygame.BufferProxy.

+
+ +
+
+int pgBufproxy_Check(PyObject *x)¶
+

Return true if Python object x is a pygame.BufferProxy instance, +false otherwise. +This will return false on pygame.BufferProxy subclass instances as well.

+
+ +
+
+PyObject *pgBufproxy_New(PyObject *obj, getbufferproc get_buffer)¶
+

Return a new pygame.BufferProxy instance. +Argument obj is the Python object that has its data exposed. +It may be NULL. +Argument get_buffer is the pg_buffer get callback. +It must not be NULL. +On failure raise a Python error and return NULL.

+
+ +
+
+PyObject *pgBufproxy_GetParent(PyObject *obj)¶
+

Return the Python object wrapped by buffer proxy obj. +Argument obj must not be NULL. +On failure, raise a Python error and return NULL.

+
+ +
+
+int pgBufproxy_Trip(PyObject *obj)¶
+

Cause the buffer proxy object obj to create a pg_buffer view of its parent. +Argument obj must not be NULL. +Return 0 on success, otherwise raise a Python error and return -1.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/cdrom.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/cdrom.html new file mode 100644 index 0000000..8c3cfc2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/cdrom.html @@ -0,0 +1,178 @@ + + + + + + + + + API exported by pygame.cdrom — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/cdrom.c¶

+

The pygame.cdrompygame module for audio cdrom control extension module. Only available for SDL 1.

+

Header file: src_c/include/pygame.h

+
+
+type pgCDObject¶
+

The pygame.cdrom.CD instance C struct.

+
+ +
+
+PyTypeObject pgCD_Type¶
+

The pygame.cdrom.CD Python type.

+
+ +
+
+PyObject *pgCD_New(int id)¶
+

Return a new pygame.cdrom.CD instance for CD drive id. +On error raise a Python exception and return NULL.

+
+ +
+
+int pgCD_Check(PyObject *x)¶
+

Return true if x is a pygame.cdrom.CD instance. +Will return false for a subclass of CD. +This is a macro. No check is made that x is not NULL.

+
+ +
+
+int pgCD_AsID(PyObject *x)¶
+

Return the CD identifier associated with the pygame.cdrom.CD +instance x. +This is a macro. No check is made that x is a pygame.cdrom.CD +instance or is not NULL.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/color.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/color.html new file mode 100644 index 0000000..526a182 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/color.html @@ -0,0 +1,172 @@ + + + + + + + + + Class Color API exported by pygame.color — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/color.c¶

+

This extension module defines the Python type pygame.Colorpygame object for color representations.

+

Header file: src_c/include/pygame.h

+
+
+PyTypeObject *pgColor_Type¶
+

The Pygame color object type pygame.Color.

+
+ +
+
+int pgColor_Check(PyObject *obj)¶
+

Return true if obj is an instance of type pgColor_Type, +but not a pgColor_Type subclass instance. +This macro does not check if obj is not NULL or indeed a Python type.

+
+ +
+
+PyObject *pgColor_New(Uint8 rgba[])¶
+

Return a new pygame.Color instance for the the four element array rgba. +On failure, raise a Python exception and return NULL.

+
+ +
+
+PyObject *pgColor_NewLength(Uint8 rgba[], Uint8 length)¶
+

Return a new pygame.Color instance having length elements, +with element values taken from the first length elements of array rgba. +Argument length must be between 1 and 4 inclusive. +On failure, raise a Python exception and return NULL.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/display.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/display.html new file mode 100644 index 0000000..9c05e93 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/display.html @@ -0,0 +1,177 @@ + + + + + + + + + API exported by pygame.display — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/display.c¶

+

This is the pygame.displaypygame module to control the display window and screen extension module.

+

Header file: src_c/include/pygame.h

+
+
+type pgVidInfoObject¶
+

A pygame object that wraps an SDL_VideoInfo struct. +The object returned by pgyame.display.Info().

+
+ +
+
+PyTypeObject *pgVidInfo_Type¶
+

The pgVidInfoObject object Python type.

+
+ +
+
+SDL_VideoInfo pgVidInfo_AsVidInfo(PyObject *obj)¶
+

Return the SDL_VideoInfo field of obj, a pgVidInfo_Type instance. +This macro does not check that obj is not NULL or an actual pgVidInfoObject object.

+
+ +
+
+PyObject *pgVidInfo_New(SDL_VideoInfo *i)¶
+

Return a new pgVidInfoObject object for the SDL_VideoInfo i. +On failure, raise a Python exception and return NULL.

+
+ +
+
+int pgVidInfo_Check(PyObject *x)¶
+

Return true if x is a pgVidInfo_Type instance

+

Will return false if x is a subclass of pgVidInfo_Type. +This macro does not check that x is not NULL.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/event.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/event.html new file mode 100644 index 0000000..81b8723 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/event.html @@ -0,0 +1,192 @@ + + + + + + + + + API exported by pygame.event — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/event.c¶

+

The extsion module pygame.eventpygame module for interacting with events and queues.

+

Header file: src_c/include/pygame.h

+
+
+type pgEventObject¶
+

The pygame.event.EventType object C struct.

+
+
+int type¶
+

The event type code.

+
+ +
+ +
+
+type pgEvent_Type¶
+

The pygame event object type pygame.event.EventType.

+
+ +
+
+int pgEvent_Check(PyObject *x)¶
+

Return true if x is a pygame event instance

+

Will return false if x is a subclass of event. +This is a macro. No check is made that x is not NULL.

+
+ +
+
+PyObject *pgEvent_New(SDL_Event *event)¶
+

Return a new pygame event instance for the SDL event. +If event is NULL then create an empty event object. +On failure raise a Python exception and return NULL.

+
+ +
+
+PyObject *pgEvent_New2(int type, PyObject *dict)¶
+

Return a new pygame event instance of SDL type and with +attribute dictionary dict. +If dict is NULL an empty attribute dictionary is created. +On failure raise a Python exception and return NULL.

+
+ +
+
+int pgEvent_FillUserEvent(pgEventObject *e, SDL_Event *event)¶
+

Fill SDL event event with information from pygame user event instance e. +Return 0 on success, -1 otherwise.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/freetype.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/freetype.html new file mode 100644 index 0000000..05369a1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/freetype.html @@ -0,0 +1,180 @@ + + + + + + + + + API exported by pygame._freetype — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/_freetype.c¶

+

This extension module defines Python type pygame.freetype.FontCreate a new Font instance from a supported font file..

+

Header file: src_c/include/pygame_freetype.h

+
+
+type pgFontObject¶
+

The pygame.freetype.Font instance C struct.

+
+ +
+
+type pgFont_Type¶
+

The pygame.freetype.Font Python type.

+
+ +
+
+PyObject *pgFont_New(const char *filename, long font_index)¶
+

Open the font file with path filename and return a new +new pygame.freetype.Font instance for that font. +Set font_index to 0 unless the file contains multiple, indexed, fonts. +On error raise a Python exception and return NULL.

+
+ +
+
+int pgFont_Check(PyObject *x)¶
+

Return true if x is a pygame.freetype.Font instance. +Will return false for a subclass of Font. +This is a macro. No check is made that x is not NULL.

+
+ +
+
+int pgFont_IS_ALIVE(PyObject *o)¶
+

Return true if pygame.freetype.Font object o +is an open font file. +This is a macro. No check is made that o is not NULL +or not a Font instance.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/mixer.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/mixer.html new file mode 100644 index 0000000..0882cf8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/mixer.html @@ -0,0 +1,213 @@ + + + + + + + + + API exported by pygame.mixer — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/mixer.c¶

+

Python types and module startup/shutdown functions defined in the +pygame.mixerpygame module for loading and playing sounds extension module.

+

Header file: src_c/include/pygame_mixer.h

+
+
+type pgSoundObject¶
+

The pygame.mixer.Sound instance C structure.

+
+ +
+
+PyTypeObject *pgSound_Type¶
+

The pygame.mixer.Sound Python type.

+
+ +
+
+PyObject *pgSound_New(Mix_Chunk *chunk)¶
+

Return a new pygame.mixer.Sound instance for the SDL mixer chunk chunk. +On failure, raise a Python exception and return NULL.

+
+ +
+
+int pgSound_Check(PyObject *obj)¶
+

Return true if obj is an instance of type pgSound_Type, +but not a pgSound_Type subclass instance. +A macro.

+
+ +
+
+Mix_Chunk *pgSound_AsChunk(PyObject *x)¶
+

Return the SDL Mix_Chunk struct associated with the +pgSound_Type instance x. +A macro that does no NULL or Python type check on x.

+
+ +
+
+type pgChannelObject¶
+

The pygame.mixer.Channel instance C structure.

+
+ +
+
+PyTypeObject *pgChannel_Type¶
+

The pygame.mixer.Channel Python type.

+
+ +
+
+PyObject *pgChannel_New(int channelnum)¶
+

Return a new pygame.mixer.Channel instance for the SDL mixer +channel channelnum. +On failure, raise a Python exception and return NULL.

+
+ +
+
+int pgChannel_Check(PyObject *obj)¶
+

Return true if obj is an instance of type pgChannel_Type, +but not a pgChannel_Type subclass instance. +A macro.

+
+ +
+
+int pgChannel_AsInt(PyObject *x)¶
+

Return the SDL mixer music channel number associated with pgChannel_Type instance x. +A macro that does no NULL or Python type check on x.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/rect.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/rect.html new file mode 100644 index 0000000..a4bcfb1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/rect.html @@ -0,0 +1,202 @@ + + + + + + + + + Class Rect API exported by pygame.rect — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/rect.c¶

+

This extension module defines Python type pygame.Rectpygame object for storing rectangular coordinates.

+

Header file: src_c/include/pygame.h

+
+
+type pgRectObject¶
+
+
+SDL_Rect r¶
+
+ +

The Pygame rectangle type instance.

+
+ +
+
+PyTypeObject *pgRect_Type¶
+

The Pygame rectangle object type pygame.Rect.

+
+ +
+
+SDL_Rect pgRect_AsRect(PyObject *obj)¶
+

A macro to access the SDL_Rect field of a pygame.Rect instance.

+
+ +
+
+PyObject *pgRect_New(SDL_Rect *r)¶
+

Return a new pygame.Rect instance from the SDL_Rect r. +On failure, raise a Python exception and return NULL.

+
+ +
+
+PyObject *pgRect_New4(int x, int y, int w, int h)¶
+

Return a new pygame.Rect instance with position (x, y) and +size (w, h). +On failure raise a Python exception and return NULL.

+
+ +
+
+SDL_Rect *pgRect_FromObject(PyObject *obj, SDL_Rect *temp)¶
+

Translate a Python rectangle representation as a Pygame SDL_Rect. +A rectangle can be a length 4 sequence integers (x, y, w, h), +or a length 2 sequence of position (x, y) and size (w, h), +or a length 1 tuple containing a rectangle representation, +or have a method rect that returns a rectangle. +Pass a pointer to a locally declared SDL_Rect as temp. +Do not rely on this being filled in; use the function's return value instead. +On success, return a pointer to a SDL_Rect representation +of the rectangle, else return NULL. +No Python exceptions are raised.

+
+ +
+
+void pgRect_Normalize(SDL_Rect *rect)¶
+

Normalize the given rect. A rect with a negative size (negative width and/or +height) will be adjusted to have a positive size.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/rwobject.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/rwobject.html new file mode 100644 index 0000000..ba23819 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/rwobject.html @@ -0,0 +1,207 @@ + + + + + + + + + API exported by pygame.rwobject — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/rwobject.c¶

+

This extension module implements functions for wrapping a Python file like +object in a SDL_RWops struct for SDL file access.

+

Header file: src_c/include/pygame.h

+
+
+SDL_RWops *pgRWops_FromObject(PyObject *obj)¶
+

Return a SDL_RWops struct filled to access obj. +If obj is a string then let SDL open the file it names. +Otherwise, if obj is a Python file-like object then use its read, write, +seek, tell, and close methods. If threads are available, +the Python GIL is acquired before calling any of the obj methods. +On error raise a Python exception and return NULL.

+
+ +
+
+SDL_RWops *pgRWops_FromFileObject(PyObject *obj)¶
+

Return a SDL_RWops struct filled to access the Python file-like object obj. +Uses its read, write, seek, tell, and close methods. +If threads are available, the Python GIL is acquired before calling any of the obj methods. +On error raise a Python exception and return NULL.

+
+ +
+
+int pgRWops_IsFileObject(SDL_RWops *rw)¶
+

Return true if rw is a Python file-like object wrapper returned by pgRWops_FromObject() +or pgRWops_FromFileObject().

+
+ +
+
+char *pgRWops_GetFileExtension(SDL_RWops *rw)¶
+

Return a string that contains the file extension of the original file +loaded into the SDL_RWops object, or NULL if the SDL_RWops object comes +from a file object.

+
+ +
+
+int pgRWops_ReleaseObject(SDL_RWops *context)¶
+

Free a SDL_RWops struct. If it is attached to a Python file-like object, decrement its +refcount. Otherwise, close the file handle. +Return 0 on success. On error, raise a Python exception and return a negative value.

+
+ +
+
+PyObject *pg_EncodeFilePath(PyObject *obj, PyObject *eclass)¶
+

Return the file path obj as a byte string properly encoded for the OS. +Null bytes are forbidden in the encoded file path. +On error raise a Python exception and return NULL, +using eclass as the exception type if it is not NULL. +If obj is NULL assume an exception was already raised and pass it on.

+
+ +
+
+PyObject *pg_EncodeString(PyObject *obj, const char *encoding, const char *errors, PyObject *eclass)¶
+

Return string obj as an encoded byte string. +The C string arguments encoding and errors are the same as for +PyUnicode_AsEncodedString(). +On error raise a Python exception and return NULL, +using eclass as the exception type if it is not NULL. +If obj is NULL assume an exception was already raised and pass it on.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/slots.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/slots.html new file mode 100644 index 0000000..d1c3326 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/slots.html @@ -0,0 +1,157 @@ + + + + + + + + + Slots and c_api - Making functions and data available from other modules — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+

One example is pg_RGBAFromObj where the implementation is defined in base.c, and also exported in base.c (and _pygame.h).

+

base.c has this exposing the pg_RGBAFromObj function to the c_api structure:

+
+

c_api[12] = pg_RGBAFromObj;

+
+

Then in src_c/include/_pygame.h there is an

+
+

#define pg_RGBAFromObj.

+
+

Also in _pygame.h, it needs to define the number of slots the base module uses. This is PYGAMEAPI_BASE_NUMSLOTS. So if you were adding another function, you need to increment this PYGAMEAPI_BASE_NUMSLOTS number.

+

Then to use the pg_RGBAFromObj in other files,

+
    +
  1. include the "pygame.h" file,

  2. +
  3. they have to make sure base is imported with:

    +
    +

    import_pygame_base();

    +
    +
  4. +
+

Examples that use pg_RGBAFromObj are: _freetype.c, color.c, gfxdraw.c, and surface.c.

+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/surface.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/surface.html new file mode 100644 index 0000000..ef5bf13 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/surface.html @@ -0,0 +1,194 @@ + + + + + + + + + Class Surface API exported by pygame.surface — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/surface.c¶

+

This extension module defines Python type pygame.Surfacepygame object for representing images.

+

Header file: src_c/include/pygame.h

+
+
+type pgSurfaceObject¶
+

A pygame.Surface instance.

+
+ +
+
+PyTypeObject *pgSurface_Type¶
+

The pygame.Surface Python type.

+
+ +
+
+int pgSurface_Check(PyObject *x)¶
+

Return true if x is a pygame.Surface instance

+

Will return false if x is a subclass of Surface. +This is a macro. No check is made that x is not NULL.

+
+ +
+
+pgSurfaceObject *pgSurface_New(SDL_Surface *s)¶
+

Return a new new pygame surface instance for SDL surface s. +Return NULL on error.

+
+ +
+
+SDL_Surface *pgSurface_AsSurface(PyObject *x)¶
+

Return a pointer the SDL surface represented by the pygame Surface instance +x.

+

This is a macro. Argument x is assumed to be a Surface, or subclass of +Surface, instance.

+
+ +
+
+int pgSurface_Blit(PyObject *dstobj, PyObject *srcobj, SDL_Rect *dstrect, SDL_Rect *srcrect, int the_args)¶
+

Blit the srcrect portion of Surface srcobj onto Surface dstobj at srcobj

+

Argument the_args indicates the type of blit to perform: +Normal blit (0), PYGAME_BLEND_ADD, PYGAME_BLEND_SUB, +PYGAME_BLEND_SUB, PYGAME_BLEND_MULT, PYGAME_BLEND_MIN, +PYGAME_BLEND_MAX, PYGAME_BLEND_RGBA_ADD, PYGAME_BLEND_RGBA_SUB, +PYGAME_BLEND_RGBA_MULT, PYGAME_BLEND_RGBA_MIN, +PYGAME_BLEND_RGBA_MAX, PYGAME_BLEND_ALPHA_SDL2 and PYGAME_BLEND_PREMULTIPLIED. +Argument dstrect is updated to the actual area on dstobj affected +by the blit.

+

The C version of the pygame.Surface.blit() method. +Return 1 on success, 0 on an exception.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/surflock.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/surflock.html new file mode 100644 index 0000000..07fec11 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/surflock.html @@ -0,0 +1,231 @@ + + + + + + + + + API exported by pygame.surflock — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_c/surflock.c¶

+

This extension module implements SDL surface locking for the +pygame.Surfacepygame object for representing images type.

+

Header file: src_c/include/pygame.h

+
+
+type pgLifetimeLockObject¶
+
+
+PyObject *surface¶
+

An SDL locked pygame surface.

+
+ +
+
+PyObject *lockobj¶
+

The Python object which owns the lock on the surface. +This field does not own a reference to the object.

+
+ +

The lifetime lock type instance. +A lifetime lock pairs a locked pygame surface with +the Python object that locked the surface for modification. +The lock is removed automatically when the lifetime lock instance +is garbage collected.

+
+ +
+
+PyTypeObject *pgLifetimeLock_Type¶
+

The pygame internal surflock lifetime lock object type.

+
+ +
+
+int pgLifetimeLock_Check(PyObject *x)¶
+

Return true if Python object x is a pgLifetimeLock_Type instance, +false otherwise. +This will return false on pgLifetimeLock_Type subclass instances as well.

+
+ +
+
+void pgSurface_Prep(pgSurfaceObject *surfobj)¶
+

If surfobj is a subsurface, then lock the parent surface with surfobj +the owner of the lock.

+
+ +
+
+void pgSurface_Unprep(pgSurfaceObject *surfobj)¶
+

If surfobj is a subsurface, then release its lock on the parent surface.

+
+ +
+
+int pgSurface_Lock(pgSurfaceObject *surfobj)¶
+

Lock pygame surface surfobj, with surfobj owning its own lock.

+
+ +
+
+int pgSurface_LockBy(pgSurfaceObject *surfobj, PyObject *lockobj)¶
+

Lock pygame surface surfobj with Python object lockobj the owning +the lock.

+

The surface will keep a weak reference to object lockobj, +and eventually remove the lock on itself if lockobj is garbage collected. +However, it is best if lockobj also keep a reference to the locked surface +and call to pgSurface_UnLockBy() when finished with the surface.

+
+ +
+
+int pgSurface_UnLock(pgSurfaceObject *surfobj)¶
+

Remove the pygame surface surfobj object's lock on itself.

+
+ +
+
+int pgSurface_UnLockBy(pgSurfaceObject *surfobj, PyObject *lockobj)¶
+

Remove the lock on pygame surface surfobj owned by Python object lockobj.

+
+ +
+
+PyObject *pgSurface_LockLifetime(PyObject *surfobj, PyObject *lockobj)¶
+

Lock pygame surface surfobj for Python object lockobj and return a +new pgLifetimeLock_Type instance for the lock.

+

This function is not called anywhere within pygame. +It and pgLifetimeLock_Type are candidates for removal.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/version.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/version.html new file mode 100644 index 0000000..366eb29 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/c_api/version.html @@ -0,0 +1,173 @@ + + + + + + + + + API exported by pygame.version — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

src_py/version.py¶

+

Header file: src_c/include/pygame.h

+

Version information can be retrieved at compile-time using these macros.

+
+

New in pygame 1.9.5.

+
+
+
+PG_MAJOR_VERSION¶
+
+ +
+
+PG_MINOR_VERSION¶
+
+ +
+
+PG_PATCH_VERSION¶
+
+ +
+
+PG_VERSIONNUM(MAJOR, MINOR, PATCH)¶
+

Returns an integer representing the given version.

+
+ +
+
+PG_VERSION_ATLEAST(MAJOR, MINOR, PATCH)¶
+

Returns true if the current version is at least equal +to the specified version.

+
+ +
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/filepaths.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/filepaths.html new file mode 100644 index 0000000..be6419c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/filepaths.html @@ -0,0 +1,145 @@ + + + + + + + + + File Path Function Arguments — pygame v2.1.2 documentation + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+

A pygame function or method which takes a file path argument will accept +either a Unicode or a byte (8-bit or ASCII character) string. +Unicode strings are translated to Python's default filesystem encoding, +as returned by sys.getfilesystemencoding(). A Unicode code point +above U+FFFF (\uFFFF) can be coded directly with a 32-bit escape sequences +(\Uxxxxxxxx), even for Python interpreters built with an UCS-2 +(16-bit character) Unicode type. Byte strings are passed +to the operating system unchanged.

+

Null characters (\x00) are not permitted in the path, raising an exception. +An exception is also raised if an Unicode file path cannot be encoded. +How UTF-16 surrogate codes are handled is Python-interpreter-dependent. +Use UTF-32 code points and 32-bit escape sequences instead. +The exception types are function-dependent.

+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/genindex.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/genindex.html new file mode 100644 index 0000000..45d036c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/genindex.html @@ -0,0 +1,2604 @@ + + + + + + + + Index — pygame v2.1.2 documentation + + + + + + + + + + + + +
+
+
+ + +

Index

+ +
+ _ + | A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | K + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | U + | V + | W + +
+

_

+ + + +
+ +

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

J

+ + + +
+ +

K

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/index.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/index.html new file mode 100644 index 0000000..6cc1417 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/index.html @@ -0,0 +1,258 @@ + + + + + + + + + Pygame Front Page — pygame v2.1.2 documentation + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+

Pygame Front Page¶

+
+
+
+

Documents¶

+
+
Readme

Basic information about pygame: what it is, who is involved, and where to find it.

+
+
Install

Steps needed to compile pygame on several platforms. +Also help on finding and installing prebuilt binaries for your system.

+
+
File Path Function Arguments

How pygame handles file system paths.

+
+
LGPL License

This is the license pygame is distributed under. +It provides for pygame to be distributed with open source and commercial software. +Generally, if pygame is not changed, it can be used with any type of program.

+
+
+
+
+

Tutorials¶

+
+
Introduction to Pygame

An introduction to the basics of pygame. +This is written for users of Python and appeared in volume two of the Py magazine.

+
+
Import and Initialize

The beginning steps on importing and initializing pygame. +The pygame package is made of several modules. +Some modules are not included on all platforms.

+
+
How do I move an Image?

A basic tutorial that covers the concepts behind 2D computer animation. +Information about drawing and clearing objects to make them appear animated.

+
+
Chimp Tutorial, Line by Line

The pygame examples include a simple program with an interactive fist and a chimpanzee. +This was inspired by the annoying flash banner of the early 2000s. +This tutorial examines every line of code used in the example.

+
+
Sprite Module Introduction

Pygame includes a higher level sprite module to help organize games. +The sprite module includes several classes that help manage details found in almost all games types. +The Sprite classes are a bit more advanced than the regular pygame modules, +and need more understanding to be properly used.

+
+
Surfarray Introduction

Pygame used the NumPy python module to allow efficient per pixel effects on images. +Using the surface arrays is an advanced feature that allows custom effects and filters. +This also examines some of the simple effects from the pygame example, arraydemo.py.

+
+
Camera Module Introduction

Pygame, as of 1.9, has a camera module that allows you to capture images, +watch live streams, and do some basic computer vision. +This tutorial covers those use cases.

+
+
Newbie Guide

A list of thirteen helpful tips for people to get comfortable using pygame.

+
+
Making Games Tutorial

A large tutorial that covers the bigger topics needed to create an entire game.

+
+
Display Modes

Getting a display surface for the screen.

+
+
한국어 튜토리얼 (Korean Tutorial)

ë¹¨ê°„ë¸”ë¡ ê²€ì€ë¸”ë¡

+
+
+
+
+

Reference¶

+
+
Index

A list of all functions, classes, and methods in the pygame package.

+
+
pygame.BufferProxy

An array protocol view of surface pixels

+
+
pygame.Color

Color representation.

+
+
pygame.cursors

Loading and compiling cursor images.

+
+
pygame.display

Configure the display surface.

+
+
pygame.draw

Drawing simple shapes like lines and ellipses to surfaces.

+
+
pygame.event

Manage the incoming events from various input devices and the windowing platform.

+
+
pygame.examples

Various programs demonstrating the use of individual pygame modules.

+
+
pygame.font

Loading and rendering TrueType fonts.

+
+
pygame.freetype

Enhanced pygame module for loading and rendering font faces.

+
+
pygame.gfxdraw

Anti-aliasing draw functions.

+
+
pygame.image

Loading, saving, and transferring of surfaces.

+
+
pygame.joystick

Manage the joystick devices.

+
+
pygame.key

Manage the keyboard device.

+
+
pygame.locals

Pygame constants.

+
+
pygame.mixer

Load and play sounds

+
+
pygame.mouse

Manage the mouse device and display.

+
+
pygame.mixer.music

Play streaming music tracks.

+
+
pygame

Top level functions to manage pygame.

+
+
pygame.PixelArray

Manipulate image pixel data.

+
+
pygame.Rect

Flexible container for a rectangle.

+
+
pygame.scrap

Native clipboard access.

+
+
pygame.sndarray

Manipulate sound sample data.

+
+
pygame.sprite

Higher level objects to represent game images.

+
+
pygame.Surface

Objects for images and the screen.

+
+
pygame.surfarray

Manipulate image pixel data.

+
+
pygame.tests

Test pygame.

+
+
pygame.time

Manage timing and framerate.

+
+
pygame.transform

Resize and move images.

+
+
pygame C API

The C api shared amongst pygame extension modules.

+
+
Search Page

Search pygame documents by keyword.

+
+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/py-modindex.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/py-modindex.html new file mode 100644 index 0000000..45a5aa1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/py-modindex.html @@ -0,0 +1,251 @@ + + + + + + + + Python Module Index — pygame v2.1.2 documentation + + + + + + + + + + + + + + + +
+
+
+ + +

Python Module Index

+ +
+ . | + p +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ .
    + pygame._sdl2.controller + pygame module to work with controllers
    + pygame._sdl2.touch + pygame module to work with touch input
    + pygame._sdl2.video + Experimental pygame module for porting new SDL video systems
    + pygame.camera + pygame module for camera use
    + pygame.cdrom + pygame module for audio cdrom control
    + pygame.cursors + pygame module for cursor resources
    + pygame.display + pygame module to control the display window and screen
    + pygame.draw + pygame module for drawing shapes
    + pygame.event + pygame module for interacting with events and queues
    + pygame.examples + module of example programs
    + pygame.fastevent + pygame module for interacting with events and queues from multiple +threads.
    + pygame.font + pygame module for loading and rendering fonts
    + pygame.freetype + Enhanced pygame module for loading and rendering computer fonts
    + pygame.gfxdraw + pygame module for drawing shapes
    + pygame.image + pygame module for image transfer
    + pygame.joystick + Pygame module for interacting with joysticks, gamepads, and trackballs.
    + pygame.key + pygame module to work with the keyboard
    + pygame.locals + pygame constants
    + pygame.mask + pygame module for image masks.
    + pygame.math + pygame module for vector classes
    + pygame.midi + pygame module for interacting with midi input and output.
    + pygame.mixer + pygame module for loading and playing sounds
    + pygame.mixer.music + pygame module for controlling streamed audio
    + pygame.mouse + pygame module to work with the mouse
    + pygame.pixelcopy + pygame module for general pixel array copying
    + pygame.scrap + pygame module for clipboard support.
    + pygame.sndarray + pygame module for accessing sound sample data
    + pygame.sprite + pygame module with basic game object classes
    + pygame.surfarray + pygame module for accessing surface pixel data using array interfaces
    + pygame.tests + Pygame unit test suite package
    + pygame.time + pygame module for monitoring time
    + pygame.transform + pygame module to transform surfaces
    + pygame.version + small module containing version information
 
+ p
+ pygame + the top level pygame package
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/bufferproxy.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/bufferproxy.html new file mode 100644 index 0000000..4c48748 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/bufferproxy.html @@ -0,0 +1,283 @@ + + + + + + + + + pygame.BufferProxy — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.BufferProxy¶
+
+
pygame object to export a surface buffer through an array protocol
+
BufferProxy(<parent>) -> BufferProxy
+
+ +++++ + + + + + + + + + + + + + + + + + + +
+—Return wrapped exporting object.
+—The size, in bytes, of the exported buffer.
+—A copy of the exported buffer as a single block of bytes.
+—Write raw bytes to object buffer.
+

BufferProxy is a pygame support type, designed as the return value +of the Surface.get_buffer() and Surface.get_view() methods. +For all Python versions a BufferProxy object exports a C struct +and Python level array interface on behalf of its parent object's buffer. +A new buffer interface is also exported. +In pygame, BufferProxy is key to implementing the +pygame.surfarraypygame module for accessing surface pixel data using array interfaces module.

+

BufferProxy instances can be created directly from Python code, +either for a parent that exports an interface, or from a Python dict +describing an object's buffer layout. The dict entries are based on the +Python level array interface mapping. The following keys are recognized:

+
+
+
"shape"tuple

The length of each array dimension as a tuple of integers. The +length of the tuple is the number of dimensions in the array.

+
+
"typestr"string

The array element type as a length 3 string. The first character +gives byteorder, '<' for little-endian, '>' for big-endian, and +'|' for not applicable. The second character is the element type, +'i' for signed integer, 'u' for unsigned integer, 'f' for floating +point, and 'V' for an chunk of bytes. The third character gives the +bytesize of the element, from '1' to '9' bytes. So, for example, +"<u4" is an unsigned 4 byte little-endian integer, such as a +32 bit pixel on a PC, while "|V3" would represent a 24 bit pixel, +which has no integer equivalent.

+
+
"data"tuple

The physical buffer start address and a read-only flag as a length +2 tuple. The address is an integer value, while the read-only flag +is a bool—False for writable, True for read-only.

+
+
"strides"tuple(optional)

Array stride information as a tuple of integers. It is required +only of non C-contiguous arrays. The tuple length must match +that of "shape".

+
+
"parent"object(optional)

The exporting object. It can be used to keep the parent object +alive while its buffer is visible.

+
+
"before"callable(optional)

Callback invoked when the BufferProxy instance +exports the buffer. The callback is given one argument, the +"parent" object if given, otherwise None. +The callback is useful for setting a lock on the parent.

+
+
"after"callable(optional)

Callback invoked when an exported buffer is released. +The callback is passed on argument, the "parent" object if given, +otherwise None. The callback is useful for releasing a lock on the +parent.

+
+
+
+

The BufferProxy class supports subclassing, instance variables, and weak +references.

+
+

New in pygame 1.8.0.

+
+
+

Extended in pygame 1.9.2.

+
+
+
+parent¶
+
+
Return wrapped exporting object.
+
parent -> Surface
+
parent -> <parent>
+
+

The Surface which returned the BufferProxy object or +the object passed to a BufferProxy call.

+
+ +
+
+length¶
+
+
The size, in bytes, of the exported buffer.
+
length -> int
+
+

The number of valid bytes of data exported. For discontinuous data, +that is data which is not a single block of memory, the bytes within +the gaps are excluded from the count. This property is equivalent to +the Py_buffer C struct len field.

+
+ +
+
+raw¶
+
+
A copy of the exported buffer as a single block of bytes.
+
raw -> bytes
+
+

The buffer data as a str/bytes object. +Any gaps in the exported data are removed.

+
+ +
+
+write()¶
+
+
Write raw bytes to object buffer.
+
write(buffer, offset=0)
+
+

Overwrite bytes in the parent object's data. The data must be C or F +contiguous, otherwise a ValueError is raised. Argument buffer is a +str/bytes object. An optional offset gives a +start position, in bytes, within the buffer where overwriting begins. +If the offset is negative or greater that or equal to the buffer proxy's +length value, an IndexException is raised. +If len(buffer) > proxy.length + offset, a ValueError is raised.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/camera.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/camera.html new file mode 100644 index 0000000..af13daa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/camera.html @@ -0,0 +1,472 @@ + + + + + + + + + pygame.camera — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.camera
+
+
pygame module for camera use
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + +
+—Module init
+—Get the backends supported on this system
+—Surface colorspace conversion
+—returns a list of available cameras
+—load a camera
+

Pygame currently supports Linux (V4L2) and Windows (MSMF) cameras natively, +with wider platform support available via an integrated OpenCV backend.

+
+

New in pygame 2.0.2: Windows native camera support

+
+
+

New in pygame 2.0.3: New OpenCV backends

+
+

EXPERIMENTAL!: This API may change or disappear in later pygame releases. If +you use this, your code will very likely break with the next pygame release.

+

The Bayer to RGB function is based on:

+
Sonix SN9C101 based webcam basic I/F routines
+Copyright (C) 2004 Takafumi Mizuno <taka-qce@ls-a.jp>
+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.
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+
+
+

New in pygame 1.9.0.

+
+
+pygame.camera.init()¶
+
+
Module init
+
init(backend = None) -> None
+
+

This function starts up the camera module, choosing the best webcam backend +it can find for your system. This is not guaranteed to succeed, and may even +attempt to import third party modules, like OpenCV. If you want to +override its backend choice, you can call pass the name of the backend you +want into this function. More about backends in +get_backends().

+
+

Changed in pygame 2.0.3: Option to explicitly select backend

+
+
+ +
+
+pygame.camera.get_backends()¶
+
+
Get the backends supported on this system
+
get_backends() -> [str]
+
+

This function returns every backend it thinks has a possibility of working +on your system, in order of priority.

+

pygame.camera Backends:

+
Backend           OS        Description
+---------------------------------------------------------------------------------
+_camera (MSMF)    Windows   Builtin, works on Windows 8+ Python3
+_camera (V4L2)    Linux     Builtin
+OpenCV            Any       Uses `opencv-python` module, can't enumerate cameras
+OpenCV-Mac        Mac       Same as OpenCV, but has camera enumeration
+VideoCapture      Windows   Uses abandoned `VideoCapture` module, can't enumerate
+                            cameras, may be removed in the future
+
+
+

There are two main differences among backends.

+

The _camera backends are built in to pygame itself, and require no third +party imports. All the other backends do. For the OpenCV and VideoCapture +backends, those modules need to be installed on your system.

+

The other big difference is "camera enumeration." Some backends don't have +a way to list out camera names, or even the number of cameras on the +system. In these cases, list_cameras() will return +something like [0]. If you know you have multiple cameras on the +system, these backend ports will pass through a "camera index number" +through if you use that as the device parameter.

+
+

New in pygame 2.0.3.

+
+
+ +
+
+pygame.camera.colorspace()¶
+
+
Surface colorspace conversion
+
colorspace(Surface, format, DestSurface = None) -> Surface
+
+

Allows for conversion from "RGB" to a destination colorspace of "HSV" or +"YUV". The source and destination surfaces must be the same size and pixel +depth. This is useful for computer vision on devices with limited processing +power. Capture as small of an image as possible, transform.scale() it +even smaller, and then convert the colorspace to YUV or HSV before +doing any processing on it.

+
+ +
+
+pygame.camera.list_cameras()¶
+
+
returns a list of available cameras
+
list_cameras() -> [cameras]
+
+

Checks the computer for available cameras and returns a list of strings of +camera names, ready to be fed into pygame.camera.Cameraload a camera.

+

If the camera backend doesn't support webcam enumeration, this will return +something like [0]. See get_backends() for much more +information.

+
+ +
+
+pygame.camera.Camera¶
+
+
load a camera
+
Camera(device, (width, height), format) -> Camera
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—opens, initializes, and starts capturing
+—stops, uninitializes, and closes the camera
+—gets current values of user controls
+—changes camera settings if supported by the camera
+—returns the dimensions of the images being recorded
+—checks if a frame is ready
+—captures an image as a Surface
+—returns an unmodified image as bytes
+

Loads a camera. On Linux, the device is typically something like +"/dev/video0". Default width and height are 640 by 480. +Format is the desired colorspace of the output. +This is useful for computer vision purposes. The default is +RGB. The following are supported:

+
+
    +
  • RGB - Red, Green, Blue

  • +
  • YUV - Luma, Blue Chrominance, Red Chrominance

  • +
  • HSV - Hue, Saturation, Value

  • +
+
+
+
+start()¶
+
+
opens, initializes, and starts capturing
+
start() -> None
+
+

Opens the camera device, attempts to initialize it, and begins recording +images to a buffer. The camera must be started before any of the below +functions can be used.

+
+ +
+
+stop()¶
+
+
stops, uninitializes, and closes the camera
+
stop() -> None
+
+

Stops recording, uninitializes the camera, and closes it. Once a camera +is stopped, the below functions cannot be used until it is started again.

+
+ +
+
+get_controls()¶
+
+
gets current values of user controls
+
get_controls() -> (hflip = bool, vflip = bool, brightness)
+
+

If the camera supports it, get_controls will return the current settings +for horizontal and vertical image flip as bools and brightness as an int. +If unsupported, it will return the default values of (0, 0, 0). Note that +the return values here may be different than those returned by +set_controls, though these are more likely to be correct.

+
+ +
+
+set_controls()¶
+
+
changes camera settings if supported by the camera
+
set_controls(hflip = bool, vflip = bool, brightness) -> (hflip = bool, vflip = bool, brightness)
+
+

Allows you to change camera settings if the camera supports it. The +return values will be the input values if the camera claims it succeeded +or the values previously in use if not. Each argument is optional, and +the desired one can be chosen by supplying the keyword, like hflip. Note +that the actual settings being used by the camera may not be the same as +those returned by set_controls. On Windows, hflip and vflip are +implemented by pygame, not by the Camera, so they should always work, but +brightness is unsupported.

+
+ +
+
+get_size()¶
+
+
returns the dimensions of the images being recorded
+
get_size() -> (width, height)
+
+

Returns the current dimensions of the images being captured by the +camera. This will return the actual size, which may be different than the +one specified during initialization if the camera did not support that +size.

+
+ +
+
+query_image()¶
+
+
checks if a frame is ready
+
query_image() -> bool
+
+

If an image is ready to get, it returns true. Otherwise it returns false. +Note that some webcams will always return False and will only queue a +frame when called with a blocking function like get_image(). +On Windows (MSMF), and the OpenCV backends, query_image() +should be reliable, though. This is useful to separate the framerate of +the game from that of the camera without having to use threading.

+
+ +
+
+get_image()¶
+
+
captures an image as a Surface
+
get_image(Surface = None) -> Surface
+
+

Pulls an image off of the buffer as an RGB Surface. It can optionally +reuse an existing Surface to save time. The bit-depth of the surface is +24 bits on Linux, 32 bits on Windows, or the same as the optionally +supplied Surface.

+
+ +
+
+get_raw()¶
+
+
returns an unmodified image as bytes
+
get_raw() -> bytes
+
+

Gets an image from a camera as a string in the native pixelformat of the +camera. Useful for integration with other libraries. This returns a +bytes object

+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/cdrom.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/cdrom.html new file mode 100644 index 0000000..dc3d7c5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/cdrom.html @@ -0,0 +1,592 @@ + + + + + + + + + pygame.cdrom — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.cdrom
+
+
pygame module for audio cdrom control
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + +
+—initialize the cdrom module
+—uninitialize the cdrom module
+—true if the cdrom module is initialized
+—number of cd drives on the system
+—class to manage a cdrom drive
+
+

Warning

+

This module is non functional in pygame 2.0 and above, unless you have manually compiled pygame with SDL1. +This module will not be supported in the future. +One alternative for python cdrom functionality is pycdio.

+
+

The cdrom module manages the CD and DVD drives on a computer. It can +also control the playback of audio CDs. This module needs to be initialized +before it can do anything. Each CD object you create represents a cdrom +drive and must also be initialized individually before it can do most things.

+
+
+pygame.cdrom.init()¶
+
+
initialize the cdrom module
+
init() -> None
+
+

Initialize the cdrom module. This will scan the system for all CD +devices. The module must be initialized before any other functions will +work. This automatically happens when you call pygame.init().

+

It is safe to call this function more than once.

+
+ +
+
+pygame.cdrom.quit()¶
+
+
uninitialize the cdrom module
+
quit() -> None
+
+

Uninitialize the cdrom module. After you call this any existing CD +objects will no longer work.

+

It is safe to call this function more than once.

+
+ +
+
+pygame.cdrom.get_init()¶
+
+
true if the cdrom module is initialized
+
get_init() -> bool
+
+

Test if the cdrom module is initialized or not. This is different than the +CD.init() since each drive must also be initialized individually.

+
+ +
+
+pygame.cdrom.get_count()¶
+
+
number of cd drives on the system
+
get_count() -> count
+
+

Return the number of cd drives on the system. When you create CD objects +you need to pass an integer id that must be lower than this count. The count +will be 0 if there are no drives on the system.

+
+ +
+
+pygame.cdrom.CD¶
+
+
class to manage a cdrom drive
+
CD(id) -> CD
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize a cdrom drive for use
+—uninitialize a cdrom drive for use
+—true if this cd device initialized
+—start playing audio
+—stop audio playback
+—temporarily stop audio playback
+—unpause audio playback
+—eject or open the cdrom drive
+—the index of the cdrom drive
+—the system name of the cdrom drive
+—true if the drive is playing audio
+—true if the drive is paused
+—the current audio playback position
+—False if a cdrom is in the drive
+—the number of tracks on the cdrom
+—true if the cdrom track has audio data
+—get all track information
+—start time of a cdrom track
+—length of a cdrom track
+

You can create a CD object for each cdrom on the system. Use +pygame.cdrom.get_count() to determine how many drives actually exist. +The id argument is an integer of the drive, starting at zero.

+

The CD object is not initialized, you can only call CD.get_id() and +CD.get_name() on an uninitialized drive.

+

It is safe to create multiple CD objects for the same drive, they will +all cooperate normally.

+
+
+init()¶
+
+
initialize a cdrom drive for use
+
init() -> None
+
+

Initialize the cdrom drive for use. The drive must be initialized for +most CD methods to work. Even if the rest of pygame has been +initialized.

+

There may be a brief pause while the drive is initialized. Avoid +CD.init() if the program should not stop for a second or two.

+
+ +
+
+quit()¶
+
+
uninitialize a cdrom drive for use
+
quit() -> None
+
+

Uninitialize a drive for use. Call this when your program will not be +accessing the drive for awhile.

+
+ +
+
+get_init()¶
+
+
true if this cd device initialized
+
get_init() -> bool
+
+

Test if this CDROM device is initialized. This is different than the +pygame.cdrom.init() since each drive must also be initialized +individually.

+
+ +
+
+play()¶
+
+
start playing audio
+
play(track, start=None, end=None) -> None
+
+

Playback audio from an audio cdrom in the drive. Besides the track number +argument, you can also pass a starting and ending time for playback. The +start and end time are in seconds, and can limit the section of an audio +track played.

+

If you pass a start time but no end, the audio will play to the end of +the track. If you pass a start time and 'None' for the end time, the +audio will play to the end of the entire disc.

+

See the CD.get_numtracks() and CD.get_track_audio() to find +tracks to playback.

+

Note, track 0 is the first track on the CD. Track numbers start at +zero.

+
+ +
+
+stop()¶
+
+
stop audio playback
+
stop() -> None
+
+

Stops playback of audio from the cdrom. This will also lose the current +playback position. This method does nothing if the drive isn't already +playing audio.

+
+ +
+
+pause()¶
+
+
temporarily stop audio playback
+
pause() -> None
+
+

Temporarily stop audio playback on the CD. The playback can be +resumed at the same point with the CD.resume() method. If the CD +is not playing this method does nothing.

+

Note, track 0 is the first track on the CD. Track numbers start at +zero.

+
+ +
+
+resume()¶
+
+
unpause audio playback
+
resume() -> None
+
+

Unpause a paused CD. If the CD is not paused or already playing, +this method does nothing.

+
+ +
+
+eject()¶
+
+
eject or open the cdrom drive
+
eject() -> None
+
+

This will open the cdrom drive and eject the cdrom. If the drive is +playing or paused it will be stopped.

+
+ +
+
+get_id()¶
+
+
the index of the cdrom drive
+
get_id() -> id
+
+

Returns the integer id that was used to create the CD instance. This +method can work on an uninitialized CD.

+
+ +
+
+get_name()¶
+
+
the system name of the cdrom drive
+
get_name() -> name
+
+

Return the string name of the drive. This is the system name used to +represent the drive. It is often the drive letter or device name. This +method can work on an uninitialized CD.

+
+ +
+
+get_busy()¶
+
+
true if the drive is playing audio
+
get_busy() -> bool
+
+

Returns True if the drive busy playing back audio.

+
+ +
+
+get_paused()¶
+
+
true if the drive is paused
+
get_paused() -> bool
+
+

Returns True if the drive is currently paused.

+
+ +
+
+get_current()¶
+
+
the current audio playback position
+
get_current() -> track, seconds
+
+

Returns both the current track and time of that track. This method works +when the drive is either playing or paused.

+

Note, track 0 is the first track on the CD. Track numbers start at +zero.

+
+ +
+
+get_empty()¶
+
+
False if a cdrom is in the drive
+
get_empty() -> bool
+
+

Return False if there is a cdrom currently in the drive. If the drive is +empty this will return True.

+
+ +
+
+get_numtracks()¶
+
+
the number of tracks on the cdrom
+
get_numtracks() -> count
+
+

Return the number of tracks on the cdrom in the drive. This will return +zero of the drive is empty or has no tracks.

+
+ +
+
+get_track_audio()¶
+
+
true if the cdrom track has audio data
+
get_track_audio(track) -> bool
+
+

Determine if a track on a cdrom contains audio data. You can also call +CD.num_tracks() and CD.get_all() to determine more information +about the cdrom.

+

Note, track 0 is the first track on the CD. Track numbers start at +zero.

+
+ +
+
+get_all()¶
+
+
get all track information
+
get_all() -> [(audio, start, end, length), ...]
+
+

Return a list with information for every track on the cdrom. The +information consists of a tuple with four values. The audio value is True +if the track contains audio data. The start, end, and length values are +floating point numbers in seconds. Start and end represent absolute times +on the entire disc.

+
+ +
+
+get_track_start()¶
+
+
start time of a cdrom track
+
get_track_start(track) -> seconds
+
+

Return the absolute time in seconds where at start of the cdrom track.

+

Note, track 0 is the first track on the CD. Track numbers start at +zero.

+
+ +
+
+get_track_length()¶
+
+
length of a cdrom track
+
get_track_length(track) -> seconds
+
+

Return a floating point value in seconds of the length of the cdrom +track.

+

Note, track 0 is the first track on the CD. Track numbers start at +zero.

+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/color.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/color.html new file mode 100644 index 0000000..2ac0f8c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/color.html @@ -0,0 +1,508 @@ + + + + + + + + + pygame.Color — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.Color¶
+
+
pygame object for color representations
+
Color(r, g, b) -> Color
+
Color(r, g, b, a=255) -> Color
+
Color(color_value) -> Color
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Gets or sets the red value of the Color.
+—Gets or sets the green value of the Color.
+—Gets or sets the blue value of the Color.
+—Gets or sets the alpha value of the Color.
+—Gets or sets the CMY representation of the Color.
+—Gets or sets the HSVA representation of the Color.
+—Gets or sets the HSLA representation of the Color.
+—Gets or sets the I1I2I3 representation of the Color.
+—Returns the normalized RGBA values of the Color.
+—Applies a certain gamma value to the Color.
+—Set the number of elements in the Color to 1,2,3, or 4.
+—returns a linear interpolation to the given Color.
+—returns a Color where the r,g,b components have been multiplied by the alpha.
+—Sets the elements of the color
+

The Color class represents RGBA color values using a value range of +0 to 255 inclusive. It allows basic arithmetic operations — binary +operations +, -, *, //, %, and unary operation ~ — to +create new colors, supports conversions to other color spaces such as HSV +or HSL and lets you adjust single color channels. +Alpha defaults to 255 (fully opaque) when not given. +The arithmetic operations and correct_gamma() method preserve subclasses. +For the binary operators, the class of the returned color is that of the +left hand color object of the operator.

+

Color objects support equality comparison with other color objects and 3 or +4 element tuples of integers. There was a bug in pygame 1.8.1 +where the default alpha was 0, not 255 like previously.

+

Color objects export the C level array interface. The interface exports a +read-only one dimensional unsigned byte array of the same assigned length +as the color. The new buffer interface is also exported, with the same +characteristics as the array interface.

+

The floor division, //, and modulus, %, operators do not raise +an exception for division by zero. Instead, if a color, or alpha, channel +in the right hand color is 0, then the result is 0. For example:

+
# These expressions are True
+Color(255, 255, 255, 255) // Color(0, 64, 64, 64) == Color(0, 3, 3, 3)
+Color(255, 255, 255, 255) % Color(64, 64, 64, 0) == Color(63, 63, 63, 0)
+
+
+

Use int(color) to return the immutable integer value of the color, +usable as a dict key. This integer value differs from the mapped +pixel values of pygame.Surface.get_at_mapped()get the mapped color value at a single pixel, +pygame.Surface.map_rgb()convert a color into a mapped color value and pygame.Surface.unmap_rgb()convert a mapped integer color value into a Color. +It can be passed as a color_value argument to Color +(useful with sets).

+

See Named Colors for samples of the available named colors.

+
+
Parameters
+
    +
  • r (int) -- red value in the range of 0 to 255 inclusive

  • +
  • g (int) -- green value in the range of 0 to 255 inclusive

  • +
  • b (int) -- blue value in the range of 0 to 255 inclusive

  • +
  • a (int) -- (optional) alpha value in the range of 0 to 255 inclusive, +default is 255

  • +
  • color_value (Color or str or int or tuple(int, int, int, [int]) or +list(int, int, int, [int])) --

    color value (see note below for the supported formats)

    +
    +

    Note

    +
    +
    Supported color_value formats:
    +
    - Color object: clones the given Color object
    +
    - Color name: str: name of the color to use, e.g. 'red' +(all the supported name strings can be found in the + Named Colors, with sample swatches)
    +
    - HTML color format str: '#rrggbbaa' or '#rrggbb', +where rr, gg, bb, and aa are 2-digit hex numbers in the range +of 0 to 0xFF inclusive, the aa (alpha) value defaults to 0xFF +if not provided
    +
    - hex number str: '0xrrggbbaa' or '0xrrggbb', where +rr, gg, bb, and aa are 2-digit hex numbers in the range of 0x00 +to 0xFF inclusive, the aa (alpha) value defaults to 0xFF if not +provided
    +
    - int: int value of the color to use, using hex numbers can +make this parameter more readable, e.g. 0xrrggbbaa, where rr, +gg, bb, and aa are 2-digit hex numbers in the range of 0x00 to +0xFF inclusive, note that the aa (alpha) value is not optional for +the int format and must be provided
    +
    - tuple/list of int color values: (R, G, B, A) or +(R, G, B), where R, G, B, and A are int values in the range of +0 to 255 inclusive, the A (alpha) value defaults to 255 if not +provided
    +
    +
    +
    +
    +

  • +
+
+
Returns
+

a newly created Color object

+
+
Return type
+

Color

+
+
+
+

Changed in pygame 2.0.0: Support for tuples, lists, and Color objects when creating +Color objects.

+
+
+

Changed in pygame 1.9.2: Color objects export the C level array interface.

+
+
+

Changed in pygame 1.9.0: Color objects support 4-element tuples of integers.

+
+
+

Changed in pygame 1.8.1: New implementation of the class.

+
+
+
+r¶
+
+
Gets or sets the red value of the Color.
+
r -> int
+
+

The red value of the Color.

+
+ +
+
+g¶
+
+
Gets or sets the green value of the Color.
+
g -> int
+
+

The green value of the Color.

+
+ +
+
+b¶
+
+
Gets or sets the blue value of the Color.
+
b -> int
+
+

The blue value of the Color.

+
+ +
+
+a¶
+
+
Gets or sets the alpha value of the Color.
+
a -> int
+
+

The alpha value of the Color.

+
+ +
+
+cmy¶
+
+
Gets or sets the CMY representation of the Color.
+
cmy -> tuple
+
+

The CMY representation of the Color. The CMY components are in +the ranges C = [0, 1], M = [0, 1], Y = [0, 1]. Note that this +will not return the absolutely exact CMY values for the set RGB +values in all cases. Due to the RGB mapping from 0-255 and the +CMY mapping from 0-1 rounding errors may cause the CMY values to +differ slightly from what you might expect.

+
+ +
+
+hsva¶
+
+
Gets or sets the HSVA representation of the Color.
+
hsva -> tuple
+
+

The HSVA representation of the Color. The HSVA components are in +the ranges H = [0, 360], S = [0, 100], V = [0, 100], A = [0, +100]. Note that this will not return the absolutely exact HSV values +for the set RGB values in all cases. Due to the RGB mapping from +0-255 and the HSV mapping from 0-100 and 0-360 rounding errors may +cause the HSV values to differ slightly from what you might expect.

+
+ +
+
+hsla¶
+
+
Gets or sets the HSLA representation of the Color.
+
hsla -> tuple
+
+

The HSLA representation of the Color. The HSLA components are in +the ranges H = [0, 360], S = [0, 100], V = [0, 100], A = [0, +100]. Note that this will not return the absolutely exact HSL values +for the set RGB values in all cases. Due to the RGB mapping from +0-255 and the HSL mapping from 0-100 and 0-360 rounding errors may +cause the HSL values to differ slightly from what you might expect.

+
+ +
+
+i1i2i3¶
+
+
Gets or sets the I1I2I3 representation of the Color.
+
i1i2i3 -> tuple
+
+

The I1I2I3 representation of the Color. The I1I2I3 components are +in the ranges I1 = [0, 1], I2 = [-0.5, 0.5], I3 = [-0.5, +0.5]. Note that this will not return the absolutely exact I1I2I3 +values for the set RGB values in all cases. Due to the RGB +mapping from 0-255 and the I1I2I3 mapping from 0-1 rounding errors +may cause the I1I2I3 values to differ slightly from what you might +expect.

+
+ +
+
+normalize()¶
+
+
Returns the normalized RGBA values of the Color.
+
normalize() -> tuple
+
+

Returns the normalized RGBA values of the Color as floating point +values.

+
+ +
+
+correct_gamma()¶
+
+
Applies a certain gamma value to the Color.
+
correct_gamma (gamma) -> Color
+
+

Applies a certain gamma value to the Color and returns a new Color with +the adjusted RGBA values.

+
+ +
+
+set_length()¶
+
+
Set the number of elements in the Color to 1,2,3, or 4.
+
set_length(len) -> None
+
+

The default Color length is 4. Colors can have lengths 1,2,3 or 4. This +is useful if you want to unpack to r,g,b and not r,g,b,a. If you want to +get the length of a Color do len(acolor).

+
+

New in pygame 1.9.0.

+
+
+ +
+
+lerp()¶
+
+
returns a linear interpolation to the given Color.
+
lerp(Color, float) -> Color
+
+

Returns a Color which is a linear interpolation between self and the +given Color in RGBA space. The second parameter determines how far +between self and other the result is going to be. +It must be a value between 0 and 1 where 0 means self and 1 means +other will be returned.

+
+

New in pygame 2.0.1.

+
+
+ +
+
+premul_alpha()¶
+
+
returns a Color where the r,g,b components have been multiplied by the alpha.
+
premul_alpha() -> Color
+
+

Returns a new Color where each of the red, green and blue colour +channels have been multiplied by the alpha channel of the original +color. The alpha channel remains unchanged.

+

This is useful when working with the BLEND_PREMULTIPLIED blending mode +flag for pygame.Surface.blit()draw one image onto another, which assumes that all surfaces using +it are using pre-multiplied alpha colors.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+update()¶
+
+
Sets the elements of the color
+
update(r, g, b) -> None
+
update(r, g, b, a=255) -> None
+
update(color_value) -> None
+
+

Sets the elements of the color. See parameters for pygame.Color()pygame object for color representations for the +parameters of this function. If the alpha value was not set it will not change.

+
+

New in pygame 2.0.1.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/color_list.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/color_list.html new file mode 100644 index 0000000..ca41948 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/color_list.html @@ -0,0 +1,2813 @@ + + + + + + + + + Named Colors — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+

pygame.Colorpygame object for color representations lets you specify any of these named colors when creating a new +pygame.Color (taken from the +colordict module).

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Color

aliceblue

████████

antiquewhite

████████

antiquewhite1

████████

antiquewhite2

████████

antiquewhite3

████████

antiquewhite4

████████

aqua

████████

aquamarine

████████

aquamarine1

████████

aquamarine2

████████

aquamarine3

████████

aquamarine4

████████

azure

████████

azure1

████████

azure2

████████

azure3

████████

azure4

████████

beige

████████

bisque

████████

bisque1

████████

bisque2

████████

bisque3

████████

bisque4

████████

black

████████

blanchedalmond

████████

blue

████████

blue1

████████

blue2

████████

blue3

████████

blue4

████████

blueviolet

████████

brown

████████

brown1

████████

brown2

████████

brown3

████████

brown4

████████

burlywood

████████

burlywood1

████████

burlywood2

████████

burlywood3

████████

burlywood4

████████

cadetblue

████████

cadetblue1

████████

cadetblue2

████████

cadetblue3

████████

cadetblue4

████████

chartreuse

████████

chartreuse1

████████

chartreuse2

████████

chartreuse3

████████

chartreuse4

████████

chocolate

████████

chocolate1

████████

chocolate2

████████

chocolate3

████████

chocolate4

████████

coral

████████

coral1

████████

coral2

████████

coral3

████████

coral4

████████

cornflowerblue

████████

cornsilk

████████

cornsilk1

████████

cornsilk2

████████

cornsilk3

████████

cornsilk4

████████

crimson

████████

cyan

████████

cyan1

████████

cyan2

████████

cyan3

████████

cyan4

████████

darkblue

████████

darkcyan

████████

darkgoldenrod

████████

darkgoldenrod1

████████

darkgoldenrod2

████████

darkgoldenrod3

████████

darkgoldenrod4

████████

darkgray

████████

darkgreen

████████

darkgrey

████████

darkkhaki

████████

darkmagenta

████████

darkolivegreen

████████

darkolivegreen1

████████

darkolivegreen2

████████

darkolivegreen3

████████

darkolivegreen4

████████

darkorange

████████

darkorange1

████████

darkorange2

████████

darkorange3

████████

darkorange4

████████

darkorchid

████████

darkorchid1

████████

darkorchid2

████████

darkorchid3

████████

darkorchid4

████████

darkred

████████

darksalmon

████████

darkseagreen

████████

darkseagreen1

████████

darkseagreen2

████████

darkseagreen3

████████

darkseagreen4

████████

darkslateblue

████████

darkslategray

████████

darkslategray1

████████

darkslategray2

████████

darkslategray3

████████

darkslategray4

████████

darkslategrey

████████

darkturquoise

████████

darkviolet

████████

deeppink

████████

deeppink1

████████

deeppink2

████████

deeppink3

████████

deeppink4

████████

deepskyblue

████████

deepskyblue1

████████

deepskyblue2

████████

deepskyblue3

████████

deepskyblue4

████████

dimgray

████████

dimgrey

████████

dodgerblue

████████

dodgerblue1

████████

dodgerblue2

████████

dodgerblue3

████████

dodgerblue4

████████

firebrick

████████

firebrick1

████████

firebrick2

████████

firebrick3

████████

firebrick4

████████

floralwhite

████████

forestgreen

████████

fuchsia

████████

gainsboro

████████

ghostwhite

████████

gold

████████

gold1

████████

gold2

████████

gold3

████████

gold4

████████

goldenrod

████████

goldenrod1

████████

goldenrod2

████████

goldenrod3

████████

goldenrod4

████████

gray

████████

gray0

████████

gray1

████████

gray2

████████

gray3

████████

gray4

████████

gray5

████████

gray6

████████

gray7

████████

gray8

████████

gray9

████████

gray10

████████

gray11

████████

gray12

████████

gray13

████████

gray14

████████

gray15

████████

gray16

████████

gray17

████████

gray18

████████

gray19

████████

gray20

████████

gray21

████████

gray22

████████

gray23

████████

gray24

████████

gray25

████████

gray26

████████

gray27

████████

gray28

████████

gray29

████████

gray30

████████

gray31

████████

gray32

████████

gray33

████████

gray34

████████

gray35

████████

gray36

████████

gray37

████████

gray38

████████

gray39

████████

gray40

████████

gray41

████████

gray42

████████

gray43

████████

gray44

████████

gray45

████████

gray46

████████

gray47

████████

gray48

████████

gray49

████████

gray50

████████

gray51

████████

gray52

████████

gray53

████████

gray54

████████

gray55

████████

gray56

████████

gray57

████████

gray58

████████

gray59

████████

gray60

████████

gray61

████████

gray62

████████

gray63

████████

gray64

████████

gray65

████████

gray66

████████

gray67

████████

gray68

████████

gray69

████████

gray70

████████

gray71

████████

gray72

████████

gray73

████████

gray74

████████

gray75

████████

gray76

████████

gray77

████████

gray78

████████

gray79

████████

gray80

████████

gray81

████████

gray82

████████

gray83

████████

gray84

████████

gray85

████████

gray86

████████

gray87

████████

gray88

████████

gray89

████████

gray90

████████

gray91

████████

gray92

████████

gray93

████████

gray94

████████

gray95

████████

gray96

████████

gray97

████████

gray98

████████

gray99

████████

gray100

████████

green

████████

green1

████████

green2

████████

green3

████████

green4

████████

greenyellow

████████

grey

████████

grey0

████████

grey1

████████

grey2

████████

grey3

████████

grey4

████████

grey5

████████

grey6

████████

grey7

████████

grey8

████████

grey9

████████

grey10

████████

grey11

████████

grey12

████████

grey13

████████

grey14

████████

grey15

████████

grey16

████████

grey17

████████

grey18

████████

grey19

████████

grey20

████████

grey21

████████

grey22

████████

grey23

████████

grey24

████████

grey25

████████

grey26

████████

grey27

████████

grey28

████████

grey29

████████

grey30

████████

grey31

████████

grey32

████████

grey33

████████

grey34

████████

grey35

████████

grey36

████████

grey37

████████

grey38

████████

grey39

████████

grey40

████████

grey41

████████

grey42

████████

grey43

████████

grey44

████████

grey45

████████

grey46

████████

grey47

████████

grey48

████████

grey49

████████

grey50

████████

grey51

████████

grey52

████████

grey53

████████

grey54

████████

grey55

████████

grey56

████████

grey57

████████

grey58

████████

grey59

████████

grey60

████████

grey61

████████

grey62

████████

grey63

████████

grey64

████████

grey65

████████

grey66

████████

grey67

████████

grey68

████████

grey69

████████

grey70

████████

grey71

████████

grey72

████████

grey73

████████

grey74

████████

grey75

████████

grey76

████████

grey77

████████

grey78

████████

grey79

████████

grey80

████████

grey81

████████

grey82

████████

grey83

████████

grey84

████████

grey85

████████

grey86

████████

grey87

████████

grey88

████████

grey89

████████

grey90

████████

grey91

████████

grey92

████████

grey93

████████

grey94

████████

grey95

████████

grey96

████████

grey97

████████

grey98

████████

grey99

████████

grey100

████████

honeydew

████████

honeydew1

████████

honeydew2

████████

honeydew3

████████

honeydew4

████████

hotpink

████████

hotpink1

████████

hotpink2

████████

hotpink3

████████

hotpink4

████████

indianred

████████

indianred1

████████

indianred2

████████

indianred3

████████

indianred4

████████

indigo

████████

ivory

████████

ivory1

████████

ivory2

████████

ivory3

████████

ivory4

████████

khaki

████████

khaki1

████████

khaki2

████████

khaki3

████████

khaki4

████████

lavender

████████

lavenderblush

████████

lavenderblush1

████████

lavenderblush2

████████

lavenderblush3

████████

lavenderblush4

████████

lawngreen

████████

lemonchiffon

████████

lemonchiffon1

████████

lemonchiffon2

████████

lemonchiffon3

████████

lemonchiffon4

████████

lightblue

████████

lightblue1

████████

lightblue2

████████

lightblue3

████████

lightblue4

████████

lightcoral

████████

lightcyan

████████

lightcyan1

████████

lightcyan2

████████

lightcyan3

████████

lightcyan4

████████

lightgoldenrod

████████

lightgoldenrod1

████████

lightgoldenrod2

████████

lightgoldenrod3

████████

lightgoldenrod4

████████

lightgoldenrodyellow

████████

lightgray

████████

lightgreen

████████

lightgrey

████████

lightpink

████████

lightpink1

████████

lightpink2

████████

lightpink3

████████

lightpink4

████████

lightsalmon

████████

lightsalmon1

████████

lightsalmon2

████████

lightsalmon3

████████

lightsalmon4

████████

lightseagreen

████████

lightskyblue

████████

lightskyblue1

████████

lightskyblue2

████████

lightskyblue3

████████

lightskyblue4

████████

lightslateblue

████████

lightslategray

████████

lightslategrey

████████

lightsteelblue

████████

lightsteelblue1

████████

lightsteelblue2

████████

lightsteelblue3

████████

lightsteelblue4

████████

lightyellow

████████

lightyellow1

████████

lightyellow2

████████

lightyellow3

████████

lightyellow4

████████

lime

████████

limegreen

████████

linen

████████

magenta

████████

magenta1

████████

magenta2

████████

magenta3

████████

magenta4

████████

maroon

████████

maroon1

████████

maroon2

████████

maroon3

████████

maroon4

████████

mediumaquamarine

████████

mediumblue

████████

mediumorchid

████████

mediumorchid1

████████

mediumorchid2

████████

mediumorchid3

████████

mediumorchid4

████████

mediumpurple

████████

mediumpurple1

████████

mediumpurple2

████████

mediumpurple3

████████

mediumpurple4

████████

mediumseagreen

████████

mediumslateblue

████████

mediumspringgreen

████████

mediumturquoise

████████

mediumvioletred

████████

midnightblue

████████

mintcream

████████

mistyrose

████████

mistyrose1

████████

mistyrose2

████████

mistyrose3

████████

mistyrose4

████████

moccasin

████████

navajowhite

████████

navajowhite1

████████

navajowhite2

████████

navajowhite3

████████

navajowhite4

████████

navy

████████

navyblue

████████

oldlace

████████

olive

████████

olivedrab

████████

olivedrab1

████████

olivedrab2

████████

olivedrab3

████████

olivedrab4

████████

orange

████████

orange1

████████

orange2

████████

orange3

████████

orange4

████████

orangered

████████

orangered1

████████

orangered2

████████

orangered3

████████

orangered4

████████

orchid

████████

orchid1

████████

orchid2

████████

orchid3

████████

orchid4

████████

palegoldenrod

████████

palegreen

████████

palegreen1

████████

palegreen2

████████

palegreen3

████████

palegreen4

████████

paleturquoise

████████

paleturquoise1

████████

paleturquoise2

████████

paleturquoise3

████████

paleturquoise4

████████

palevioletred

████████

palevioletred1

████████

palevioletred2

████████

palevioletred3

████████

palevioletred4

████████

papayawhip

████████

peachpuff

████████

peachpuff1

████████

peachpuff2

████████

peachpuff3

████████

peachpuff4

████████

peru

████████

pink

████████

pink1

████████

pink2

████████

pink3

████████

pink4

████████

plum

████████

plum1

████████

plum2

████████

plum3

████████

plum4

████████

powderblue

████████

purple

████████

purple1

████████

purple2

████████

purple3

████████

purple4

████████

red

████████

red1

████████

red2

████████

red3

████████

red4

████████

rosybrown

████████

rosybrown1

████████

rosybrown2

████████

rosybrown3

████████

rosybrown4

████████

royalblue

████████

royalblue1

████████

royalblue2

████████

royalblue3

████████

royalblue4

████████

saddlebrown

████████

salmon

████████

salmon1

████████

salmon2

████████

salmon3

████████

salmon4

████████

sandybrown

████████

seagreen

████████

seagreen1

████████

seagreen2

████████

seagreen3

████████

seagreen4

████████

seashell

████████

seashell1

████████

seashell2

████████

seashell3

████████

seashell4

████████

sienna

████████

sienna1

████████

sienna2

████████

sienna3

████████

sienna4

████████

silver

████████

skyblue

████████

skyblue1

████████

skyblue2

████████

skyblue3

████████

skyblue4

████████

slateblue

████████

slateblue1

████████

slateblue2

████████

slateblue3

████████

slateblue4

████████

slategray

████████

slategray1

████████

slategray2

████████

slategray3

████████

slategray4

████████

slategrey

████████

snow

████████

snow1

████████

snow2

████████

snow3

████████

snow4

████████

springgreen

████████

springgreen1

████████

springgreen2

████████

springgreen3

████████

springgreen4

████████

steelblue

████████

steelblue1

████████

steelblue2

████████

steelblue3

████████

steelblue4

████████

tan

████████

tan1

████████

tan2

████████

tan3

████████

tan4

████████

teal

████████

thistle

████████

thistle1

████████

thistle2

████████

thistle3

████████

thistle4

████████

tomato

████████

tomato1

████████

tomato2

████████

tomato3

████████

tomato4

████████

turquoise

████████

turquoise1

████████

turquoise2

████████

turquoise3

████████

turquoise4

████████

violet

████████

violetred

████████

violetred1

████████

violetred2

████████

violetred3

████████

violetred4

████████

wheat

████████

wheat1

████████

wheat2

████████

wheat3

████████

wheat4

████████

white

████████

whitesmoke

████████

yellow

████████

yellow1

████████

yellow2

████████

yellow3

████████

yellow4

████████

yellowgreen

████████

+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/cursors.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/cursors.html new file mode 100644 index 0000000..1d3279c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/cursors.html @@ -0,0 +1,452 @@ + + + + + + + + + pygame.cursors — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.cursors
+
+
pygame module for cursor resources
+
+ +++++ + + + + + + + + + + + + + + +
+—create binary cursor data from simple strings
+—load cursor data from an XBM file
+—pygame object representing a cursor
+

Pygame offers control over the system hardware cursor. Pygame supports +black and white cursors (bitmap cursors), as well as system variant cursors and color cursors. +You control the cursor with functions inside pygame.mousepygame module to work with the mouse.

+

This cursors module contains functions for loading and decoding various +cursor formats. These allow you to easily store your cursors in external files +or directly as encoded python strings.

+

The module includes several standard cursors. The pygame.mouse.set_cursor()set the mouse cursor to a new cursor +function takes several arguments. All those arguments have been stored in a +single tuple you can call like this:

+
>>> pygame.mouse.set_cursor(*pygame.cursors.arrow)
+
+
+

The following variables can be passed to pygame.mouse.set_cursor function:

+
+
    +
  • pygame.cursors.arrow

  • +
  • pygame.cursors.diamond

  • +
  • pygame.cursors.broken_x

  • +
  • pygame.cursors.tri_left

  • +
  • pygame.cursors.tri_right

  • +
+
+

This module also contains a few cursors as formatted strings. You'll need to +pass these to pygame.cursors.compile() function before you can use them. +The example call would look like this:

+
>>> cursor = pygame.cursors.compile(pygame.cursors.textmarker_strings)
+>>> pygame.mouse.set_cursor((8, 16), (0, 0), *cursor)
+
+
+

The following strings can be converted into cursor bitmaps with +pygame.cursors.compile() :

+
+
    +
  • pygame.cursors.thickarrow_strings

  • +
  • pygame.cursors.sizer_x_strings

  • +
  • pygame.cursors.sizer_y_strings

  • +
  • pygame.cursors.sizer_xy_strings

  • +
  • pygame.cursor.textmarker_strings

  • +
+
+
+
+pygame.cursors.compile()¶
+
+
create binary cursor data from simple strings
+
compile(strings, black='X', white='.', xor='o') -> data, mask
+
+

A sequence of strings can be used to create binary cursor data for the +system cursor. This returns the binary data in the form of two tuples. +Those can be passed as the third and fourth arguments respectively of the +pygame.mouse.set_cursor()set the mouse cursor to a new cursor function.

+

If you are creating your own cursor strings, you can use any value represent +the black and white pixels. Some system allow you to set a special toggle +color for the system color, this is also called the xor color. If the system +does not support xor cursors, that color will simply be black.

+

The height must be divisible by 8. The width of the strings must all be equal +and be divisible by 8. If these two conditions are not met, ValueError is +raised. +An example set of cursor strings looks like this

+
thickarrow_strings = (               #sized 24x24
+  "XX                      ",
+  "XXX                     ",
+  "XXXX                    ",
+  "XX.XX                   ",
+  "XX..XX                  ",
+  "XX...XX                 ",
+  "XX....XX                ",
+  "XX.....XX               ",
+  "XX......XX              ",
+  "XX.......XX             ",
+  "XX........XX            ",
+  "XX........XXX           ",
+  "XX......XXXXX           ",
+  "XX.XXX..XX              ",
+  "XXXX XX..XX             ",
+  "XX   XX..XX             ",
+  "     XX..XX             ",
+  "      XX..XX            ",
+  "      XX..XX            ",
+  "       XXXX             ",
+  "       XX               ",
+  "                        ",
+  "                        ",
+  "                        ")
+
+
+
+ +
+
+pygame.cursors.load_xbm()¶
+
+
load cursor data from an XBM file
+
load_xbm(cursorfile) -> cursor_args
+
load_xbm(cursorfile, maskfile) -> cursor_args
+
+

This loads cursors for a simple subset of XBM files. XBM files are +traditionally used to store cursors on UNIX systems, they are an ASCII +format used to represent simple images.

+

Sometimes the black and white color values will be split into two separate +XBM files. You can pass a second maskfile argument to load the two +images into a single cursor.

+

The cursorfile and maskfile arguments can either be filenames or file-like +object with the readlines method.

+

The return value cursor_args can be passed directly to the +pygame.mouse.set_cursor() function.

+
+ +
+
+pygame.cursors.Cursor¶
+
+
pygame object representing a cursor
+
Cursor(size, hotspot, xormasks, andmasks) -> Cursor
+
Cursor(hotspot, surface) -> Cursor
+
Cursor(constant) -> Cursor
+
Cursor(Cursor) -> Cursor
+
Cursor() -> Cursor
+
+ +++++ + + + + + + + + + + + + + + +
+—
+—Gets the cursor type
+—Gets the cursor data
+

In pygame 2, there are 3 types of cursors you can create to give your +game that little bit of extra polish. There's bitmap type cursors, +which existed in pygame 1.x, and are compiled from a string or load from an xbm file. +Then there are system type cursors, where you choose a preset that will +convey the same meaning but look native across different operating systems. +Finally you can create a color cursor, which displays a pygame surface as the cursor.

+

Creating a system cursor

+

Choose a constant from this list, pass it into pygame.cursors.Cursor(constant), +and you're good to go. Be advised that not all systems support every system +cursor, and you may get a substitution instead. For example, on MacOS, +WAIT/WAITARROW should show up as an arrow, and SIZENWSE/SIZENESW/SIZEALL +should show up as a closed hand. And on Wayland, every SIZE cursor should +show up as a hand.

+
Pygame Cursor Constant           Description
+--------------------------------------------
+pygame.SYSTEM_CURSOR_ARROW       arrow
+pygame.SYSTEM_CURSOR_IBEAM       i-beam
+pygame.SYSTEM_CURSOR_WAIT        wait
+pygame.SYSTEM_CURSOR_CROSSHAIR   crosshair
+pygame.SYSTEM_CURSOR_WAITARROW   small wait cursor
+                                 (or wait if not available)
+pygame.SYSTEM_CURSOR_SIZENWSE    double arrow pointing
+                                 northwest and southeast
+pygame.SYSTEM_CURSOR_SIZENESW    double arrow pointing
+                                 northeast and southwest
+pygame.SYSTEM_CURSOR_SIZEWE      double arrow pointing
+                                 west and east
+pygame.SYSTEM_CURSOR_SIZENS      double arrow pointing
+                                 north and south
+pygame.SYSTEM_CURSOR_SIZEALL     four pointed arrow pointing
+                                 north, south, east, and west
+pygame.SYSTEM_CURSOR_NO          slashed circle or crossbones
+pygame.SYSTEM_CURSOR_HAND        hand
+
+
+

Creating a cursor without passing arguments

+

In addition to the cursor constants available and described above, +you can also call pygame.cursors.Cursor(), and your cursor is ready (doing that is the same as +calling pygame.cursors.Cursor(pygame.SYSTEM_CURSOR_ARROW). +Doing one of those calls actually creates a system cursor using the default native image.

+

Creating a color cursor

+

To create a color cursor, create a Cursor from a hotspot and a surface. +hotspot is an (x,y) coordinate that determines where in the cursor the exact point is. +The hotspot position must be within the bounds of the surface.

+

Creating a bitmap cursor

+

When the mouse cursor is visible, it will be displayed as a black and white +bitmap using the given bitmask arrays. The size is a sequence containing +the cursor width and height. hotspot is a sequence containing the cursor +hotspot position.

+

A cursor has a width and height, but a mouse position is represented by a +set of point coordinates. So the value passed into the cursor hotspot +variable helps pygame to actually determine at what exact point the cursor +is at.

+

xormasks is a sequence of bytes containing the cursor xor data masks. +Lastly andmasks, a sequence of bytes containing the cursor bitmask data. +To create these variables, we can make use of the +pygame.cursors.compile()create binary cursor data from simple strings function.

+

Width and height must be a multiple of 8, and the mask arrays must be the +correct size for the given width and height. Otherwise an exception is raised.

+
+
+copy()¶
+
+| :sl:`copy the current cursor`
+
+| :sg:`copy() -> Cursor`
+

Returns a new Cursor object with the same data and hotspot as the original.

+
+ +
+
+type¶
+
+
Gets the cursor type
+
type -> string
+
+

The type will be "system", "bitmap", or "color".

+
+ +
+
+data¶
+
+
Gets the cursor data
+
data -> tuple
+
+

Returns the data that was used to create this cursor object, wrapped up in a tuple.

+
+ +
+

New in pygame 2.0.1.

+
+
+ +

Example code for creating and settings cursors. (Click the mouse to switch cursor)

+
# pygame setup
+import pygame as pg
+
+pg.init()
+screen = pg.display.set_mode([600, 400])
+pg.display.set_caption("Example code for the cursors module")
+
+# create a system cursor
+system = pg.cursors.Cursor(pg.SYSTEM_CURSOR_NO)
+
+# create bitmap cursors
+bitmap_1 = pg.cursors.Cursor(*pg.cursors.arrow)
+bitmap_2 = pg.cursors.Cursor(
+    (24, 24), (0, 0), *pg.cursors.compile(pg.cursors.thickarrow_strings)
+)
+
+# create a color cursor
+surf = pg.Surface((40, 40)) # you could also load an image 
+surf.fill((120, 50, 50))        # and use that as your surface
+color = pg.cursors.Cursor((20, 20), surf)
+
+cursors = [system, bitmap_1, bitmap_2, color]
+cursor_index = 0
+
+pg.mouse.set_cursor(cursors[cursor_index])
+
+clock = pg.time.Clock()
+going = True
+while going:
+    clock.tick(60)
+    screen.fill((0, 75, 30))
+    pg.display.flip()
+
+    for event in pg.event.get():
+        if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
+            going = False
+
+        # if the mouse is clicked it will switch to a new cursor
+        if event.type == pg.MOUSEBUTTONDOWN:
+            cursor_index += 1
+            cursor_index %= len(cursors)
+            pg.mouse.set_cursor(cursors[cursor_index])
+
+pg.quit()
+
+
+
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/display.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/display.html new file mode 100644 index 0000000..b7a365e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/display.html @@ -0,0 +1,990 @@ + + + + + + + + + pygame.display — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.display
+
+
pygame module to control the display window and screen
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Initialize the display module
+—Uninitialize the display module
+—Returns True if the display module has been initialized
+—Initialize a window or screen for display
+—Get a reference to the currently set display surface
+—Update the full display Surface to the screen
+—Update portions of the screen for software displays
+—Get the name of the pygame display backend
+—Create a video display information object
+—Get information about the current windowing system
+—Get sizes of active desktops
+—Get list of available fullscreen modes
+—Pick the best color depth for a display mode
+—Get the value for an OpenGL flag for the current display
+—Request an OpenGL display attribute for the display mode
+—Returns True when the display is active on the screen
+—Iconify the display surface
+—Switch between fullscreen and windowed displays
+—Change the hardware gamma ramps
+—Change the hardware gamma ramps with a custom lookup
+—Change the system image for the display window
+—Set the current window caption
+—Get the current window caption
+—Set the display color palette for indexed displays
+—Return the number of displays
+—Return the size of the window or screen
+—Return whether the screensaver is allowed to run.
+—Set whether the screensaver may run
+

This module offers control over the pygame display. Pygame has a single display +Surface that is either contained in a window or runs full screen. Once you +create the display you treat it as a regular Surface. Changes are not +immediately visible onscreen; you must choose one of the two flipping functions +to update the actual display.

+

The origin of the display, where x = 0 and y = 0, is the top left of the +screen. Both axes increase positively towards the bottom right of the screen.

+

The pygame display can actually be initialized in one of several modes. By +default, the display is a basic software driven framebuffer. You can request +special modules like automatic scaling or OpenGL support. These are +controlled by flags passed to pygame.display.set_mode().

+

Pygame can only have a single display active at any time. Creating a new one +with pygame.display.set_mode() will close the previous display. To detect +the number and size of attached screens, you can use +pygame.display.get_desktop_sizes and then select appropriate window size +and display index to pass to pygame.display.set_mode().

+

For backward compatibility pygame.display allows precise control over +the pixel format or display resolutions. This used to be necessary with old +grahics cards and CRT screens, but is usually not needed any more. Use the +functions pygame.display.mode_ok(), pygame.display.list_modes(), and +pygame.display.Info() to query detailed information about the display.

+

Once the display Surface is created, the functions from this module affect the +single existing display. The Surface becomes invalid if the module is +uninitialized. If a new display mode is set, the existing Surface will +automatically switch to operate on the new display.

+

When the display mode is set, several events are placed on the pygame event +queue. pygame.QUIT is sent when the user has requested the program to +shut down. The window will receive pygame.ACTIVEEVENT events as the display +gains and loses input focus. If the display is set with the +pygame.RESIZABLE flag, pygame.VIDEORESIZE events will be sent when the +user adjusts the window dimensions. Hardware displays that draw direct to the +screen will get pygame.VIDEOEXPOSE events when portions of the window must +be redrawn.

+

A new windowevent API was introduced in pygame 2.0.1. Check event module docs +for more information on that

+

Some display environments have an option for automatically stretching all +windows. When this option is enabled, this automatic stretching distorts the +appearance of the pygame window. In the pygame examples directory, there is +example code (prevent_display_stretching.py) which shows how to disable this +automatic stretching of the pygame display on Microsoft Windows (Vista or newer +required).

+
+
+pygame.display.init()¶
+
+
Initialize the display module
+
init() -> None
+
+

Initializes the pygame display module. The display module cannot do anything +until it is initialized. This is usually handled for you automatically when +you call the higher level pygame.init().

+

Pygame will select from one of several internal display backends when it is +initialized. The display mode will be chosen depending on the platform and +permissions of current user. Before the display module is initialized the +environment variable SDL_VIDEODRIVER can be set to control which backend +is used. The systems with multiple choices are listed here.

+
Windows : windib, directx
+Unix    : x11, dga, fbcon, directfb, ggi, vgl, svgalib, aalib
+
+
+

On some platforms it is possible to embed the pygame display into an already +existing window. To do this, the environment variable SDL_WINDOWID must +be set to a string containing the window id or handle. The environment +variable is checked when the pygame display is initialized. Be aware that +there can be many strange side effects when running in an embedded display.

+

It is harmless to call this more than once, repeated calls have no effect.

+
+ +
+
+pygame.display.quit()¶
+
+
Uninitialize the display module
+
quit() -> None
+
+

This will shut down the entire display module. This means any active +displays will be closed. This will also be handled automatically when the +program exits.

+

It is harmless to call this more than once, repeated calls have no effect.

+
+ +
+
+pygame.display.get_init()¶
+
+
Returns True if the display module has been initialized
+
get_init() -> bool
+
+

Returns True if the pygame.displaypygame module to control the display window and screen module is currently initialized.

+
+ +
+
+pygame.display.set_mode()¶
+
+
Initialize a window or screen for display
+
set_mode(size=(0, 0), flags=0, depth=0, display=0, vsync=0) -> Surface
+
+

This function will create a display Surface. The arguments passed in are +requests for a display type. The actual created display will be the best +possible match supported by the system.

+

Note that calling this function implicitly initializes pygame.display, if +it was not initialized before.

+

The size argument is a pair of numbers representing the width and +height. The flags argument is a collection of additional options. The depth +argument represents the number of bits to use for color.

+

The Surface that gets returned can be drawn to like a regular Surface but +changes will eventually be seen on the monitor.

+

If no size is passed or is set to (0, 0) and pygame uses SDL +version 1.2.10 or above, the created Surface will have the same size as the +current screen resolution. If only the width or height are set to 0, the +Surface will have the same width or height as the screen resolution. Using a +SDL version prior to 1.2.10 will raise an exception.

+

It is usually best to not pass the depth argument. It will default to the +best and fastest color depth for the system. If your game requires a +specific color format you can control the depth with this argument. Pygame +will emulate an unavailable color depth which can be slow.

+

When requesting fullscreen display modes, sometimes an exact match for the +requested size cannot be made. In these situations pygame will select +the closest compatible match. The returned surface will still always match +the requested size.

+

On high resolution displays(4k, 1080p) and tiny graphics games (640x480) +show up very small so that they are unplayable. SCALED scales up the window +for you. The game thinks it's a 640x480 window, but really it can be bigger. +Mouse events are scaled for you, so your game doesn't need to do it. Note +that SCALED is considered an experimental API and may change in future +releases.

+

The flags argument controls which type of display you want. There are +several to choose from, and you can even combine multiple types using the +bitwise or operator, (the pipe "|" character). Here are the display +flags you will want to choose from:

+
pygame.FULLSCREEN    create a fullscreen display
+pygame.DOUBLEBUF     (obsolete in pygame 2) recommended for HWSURFACE or OPENGL
+pygame.HWSURFACE     (obsolete in pygame 2) hardware accelerated, only in FULLSCREEN
+pygame.OPENGL        create an OpenGL-renderable display
+pygame.RESIZABLE     display window should be sizeable
+pygame.NOFRAME       display window will have no border or controls
+pygame.SCALED        resolution depends on desktop size and scale graphics
+pygame.SHOWN         window is opened in visible mode (default)
+pygame.HIDDEN        window is opened in hidden mode
+
+
+
+

New in pygame 2.0.0: SCALED, SHOWN and HIDDEN

+
+

By setting the vsync parameter to 1, it is possible to get a display +with vertical sync, but you are not guaranteed to get one. The request only +works at all for calls to set_mode() with the pygame.OPENGL or +pygame.SCALED flags set, and is still not guaranteed even with one of +those set. What you get depends on the hardware and driver configuration +of the system pygame is running on. Here is an example usage of a call +to set_mode() that may give you a display with vsync:

+
flags = pygame.OPENGL | pygame.FULLSCREEN
+window_surface = pygame.display.set_mode((1920, 1080), flags, vsync=1)
+
+
+

Vsync behaviour is considered experimental, and may change in future releases.

+
+

New in pygame 2.0.0: vsync

+
+

Basic example:

+
# Open a window on the screen
+screen_width=700
+screen_height=400
+screen=pygame.display.set_mode([screen_width, screen_height])
+
+
+

The display index 0 means the default display is used. If no display +index argument is provided, the default display can be overridden with an +environment variable.

+
+

Changed in pygame 1.9.5: display argument added

+
+
+ +
+
+pygame.display.get_surface()¶
+
+
Get a reference to the currently set display surface
+
get_surface() -> Surface
+
+

Return a reference to the currently set display Surface. If no display mode +has been set this will return None.

+
+ +
+
+pygame.display.flip()¶
+
+
Update the full display Surface to the screen
+
flip() -> None
+
+

This will update the contents of the entire display. If your display mode is +using the flags pygame.HWSURFACE and pygame.DOUBLEBUF on pygame 1, +this will wait for a vertical retrace and swap the surfaces.

+

When using an pygame.OPENGL display mode this will perform a gl buffer +swap.

+
+ +
+
+pygame.display.update()¶
+
+
Update portions of the screen for software displays
+
update(rectangle=None) -> None
+
update(rectangle_list) -> None
+
+

This function is like an optimized version of pygame.display.flip() for +software displays. It allows only a portion of the screen to updated, +instead of the entire area. If no argument is passed it updates the entire +Surface area like pygame.display.flip().

+

Note that calling display.update(None) means no part of the window is +updated. Whereas display.update() means the whole window is updated.

+

You can pass the function a single rectangle, or a sequence of rectangles. +It is more efficient to pass many rectangles at once than to call update +multiple times with single or a partial list of rectangles. If passing a +sequence of rectangles it is safe to include None values in the list, which +will be skipped.

+

This call cannot be used on pygame.OPENGL displays and will generate an +exception.

+
+ +
+
+pygame.display.get_driver()¶
+
+
Get the name of the pygame display backend
+
get_driver() -> name
+
+

Pygame chooses one of many available display backends when it is +initialized. This returns the internal name used for the display backend. +This can be used to provide limited information about what display +capabilities might be accelerated. See the SDL_VIDEODRIVER flags in +pygame.display.set_mode() to see some of the common options.

+
+ +
+
+pygame.display.Info()¶
+
+
Create a video display information object
+
Info() -> VideoInfo
+
+

Creates a simple object containing several attributes to describe the +current graphics environment. If this is called before +pygame.display.set_mode() some platforms can provide information about +the default display mode. This can also be called after setting the display +mode to verify specific display options were satisfied. The VidInfo object +has several attributes:

+
hw:         1 if the display is hardware accelerated
+wm:         1 if windowed display modes can be used
+video_mem:  The megabytes of video memory on the display. This is 0 if
+            unknown
+bitsize:    Number of bits used to store each pixel
+bytesize:   Number of bytes used to store each pixel
+masks:      Four values used to pack RGBA values into pixels
+shifts:     Four values used to pack RGBA values into pixels
+losses:     Four values used to pack RGBA values into pixels
+blit_hw:    1 if hardware Surface blitting is accelerated
+blit_hw_CC: 1 if hardware Surface colorkey blitting is accelerated
+blit_hw_A:  1 if hardware Surface pixel alpha blitting is accelerated
+blit_sw:    1 if software Surface blitting is accelerated
+blit_sw_CC: 1 if software Surface colorkey blitting is accelerated
+blit_sw_A:  1 if software Surface pixel alpha blitting is accelerated
+current_h, current_w:  Height and width of the current video mode, or
+            of the desktop mode if called before the display.set_mode
+            is called. (current_h, current_w are available since
+            SDL 1.2.10, and pygame 1.8.0). They are -1 on error, or if
+            an old SDL is being used.
+
+
+
+ +
+
+pygame.display.get_wm_info()¶
+
+
Get information about the current windowing system
+
get_wm_info() -> dict
+
+

Creates a dictionary filled with string keys. The strings and values are +arbitrarily created by the system. Some systems may have no information and +an empty dictionary will be returned. Most platforms will return a "window" +key with the value set to the system id for the current display.

+
+

New in pygame 1.7.1.

+
+
+ +
+
+pygame.display.get_desktop_sizes()¶
+
+
Get sizes of active desktops
+
get_desktop_sizes() -> list
+
+

This function returns the sizes of the currrently configured +virtual desktops as a list of (x, y) tuples of integers.

+

The length of the list is not the same as the number of attached monitors, +as a desktop can be mirrored across multiple monitors. The desktop sizes +do not indicate the maximum monitor resolutions supported by the hardware, +but the desktop size configured in the operating system.

+

In order to fit windows into the desktop as it is currently configured, and +to respect the resolution configured by the operating system in fullscreen +mode, this function should be used to replace many use cases of +pygame.display.list_modes() whenever applicable.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.display.list_modes()¶
+
+
Get list of available fullscreen modes
+
list_modes(depth=0, flags=pygame.FULLSCREEN, display=0) -> list
+
+

This function returns a list of possible sizes for a specified color +depth. The return value will be an empty list if no display modes are +available with the given arguments. A return value of -1 means that +any requested size should work (this is likely the case for windowed +modes). Mode sizes are sorted from biggest to smallest.

+

If depth is 0, the current/best color depth for the display is used. +The flags defaults to pygame.FULLSCREEN, but you may need to add +additional flags for specific fullscreen modes.

+

The display index 0 means the default display is used.

+

Since pygame 2.0, pygame.display.get_desktop_sizes() has taken over +some use cases from pygame.display.list_modes():

+

To find a suitable size for non-fullscreen windows, it is preferable to +use pygame.display.get_desktop_sizes() to get the size of the current +desktop, and to then choose a smaller window size. This way, the window is +guaranteed to fit, even when the monitor is configured to a lower resolution +than the maximum supported by the hardware.

+

To avoid changing the physical monitor resolution, it is also preferable to +use pygame.display.get_desktop_sizes() to determine the fullscreen +resolution. Developers are strongly advised to default to the current +physical monitor resolution unless the user explicitly requests a different +one (e.g. in an options menu or configuration file).

+
+

Changed in pygame 1.9.5: display argument added

+
+
+ +
+
+pygame.display.mode_ok()¶
+
+
Pick the best color depth for a display mode
+
mode_ok(size, flags=0, depth=0, display=0) -> depth
+
+

This function uses the same arguments as pygame.display.set_mode(). It +is used to determine if a requested display mode is available. It will +return 0 if the display mode cannot be set. Otherwise it will return a +pixel depth that best matches the display asked for.

+

Usually the depth argument is not passed, but some platforms can support +multiple display depths. If passed it will hint to which depth is a better +match.

+

The function will return 0 if the passed display flags cannot be set.

+

The display index 0 means the default display is used.

+
+

Changed in pygame 1.9.5: display argument added

+
+
+ +
+
+pygame.display.gl_get_attribute()¶
+
+
Get the value for an OpenGL flag for the current display
+
gl_get_attribute(flag) -> value
+
+

After calling pygame.display.set_mode() with the pygame.OPENGL flag, +it is a good idea to check the value of any requested OpenGL attributes. See +pygame.display.gl_set_attribute() for a list of valid flags.

+
+ +
+
+pygame.display.gl_set_attribute()¶
+
+
Request an OpenGL display attribute for the display mode
+
gl_set_attribute(flag, value) -> None
+
+

When calling pygame.display.set_mode() with the pygame.OPENGL flag, +Pygame automatically handles setting the OpenGL attributes like color and +double-buffering. OpenGL offers several other attributes you may want control +over. Pass one of these attributes as the flag, and its appropriate value. +This must be called before pygame.display.set_mode().

+

Many settings are the requested minimum. Creating a window with an OpenGL context +will fail if OpenGL cannot provide the requested attribute, but it may for example +give you a stencil buffer even if you request none, or it may give you a larger +one than requested.

+

The OPENGL flags are:

+
GL_ALPHA_SIZE, GL_DEPTH_SIZE, GL_STENCIL_SIZE, GL_ACCUM_RED_SIZE,
+GL_ACCUM_GREEN_SIZE,  GL_ACCUM_BLUE_SIZE, GL_ACCUM_ALPHA_SIZE,
+GL_MULTISAMPLEBUFFERS, GL_MULTISAMPLESAMPLES, GL_STEREO
+
+
+

GL_MULTISAMPLEBUFFERS

+
+

Whether to enable multisampling anti-aliasing. +Defaults to 0 (disabled).

+

Set GL_MULTISAMPLESAMPLES to a value +above 0 to control the amount of anti-aliasing. +A typical value is 2 or 3.

+
+

GL_STENCIL_SIZE

+
+

Minimum bit size of the stencil buffer. Defaults to 0.

+
+

GL_DEPTH_SIZE

+
+

Minimum bit size of the depth buffer. Defaults to 16.

+
+

GL_STEREO

+
+

1 enables stereo 3D. Defaults to 0.

+
+

GL_BUFFER_SIZE

+
+

Minimum bit size of the frame buffer. Defaults to 0.

+
+
+

New in pygame 2.0.0: Additional attributes:

+
+
GL_ACCELERATED_VISUAL,
+GL_CONTEXT_MAJOR_VERSION, GL_CONTEXT_MINOR_VERSION,
+GL_CONTEXT_FLAGS, GL_CONTEXT_PROFILE_MASK,
+GL_SHARE_WITH_CURRENT_CONTEXT,
+GL_CONTEXT_RELEASE_BEHAVIOR,
+GL_FRAMEBUFFER_SRGB_CAPABLE
+
+
+

GL_CONTEXT_PROFILE_MASK

+
+

Sets the OpenGL profile to one of these values:

+
GL_CONTEXT_PROFILE_CORE             disable deprecated features
+GL_CONTEXT_PROFILE_COMPATIBILITY    allow deprecated features
+GL_CONTEXT_PROFILE_ES               allow only the ES feature
+                                    subset of OpenGL
+
+
+
+

GL_ACCELERATED_VISUAL

+
+

Set to 1 to require hardware acceleration, or 0 to force software render. +By default, both are allowed.

+
+
+ +
+
+pygame.display.get_active()¶
+
+
Returns True when the display is active on the screen
+
get_active() -> bool
+
+

Returns True when the display Surface is considered actively +renderable on the screen and may be visible to the user. This is +the default state immediately after pygame.display.set_mode(). +This method may return True even if the application is fully hidden +behind another application window.

+

This will return False if the display Surface has been iconified or +minimized (either via pygame.display.iconify() or via an OS +specific method such as the minimize-icon available on most +desktops).

+

The method can also return False for other reasons without the +application being explicitly iconified or minimized by the user. A +notable example being if the user has multiple virtual desktops and +the display Surface is not on the active virtual desktop.

+
+

Note

+

This function returning True is unrelated to whether the +application has input focus. Please see +pygame.key.get_focused() and pygame.mouse.get_focused() +for APIs related to input focus.

+
+
+ +
+
+pygame.display.iconify()¶
+
+
Iconify the display surface
+
iconify() -> bool
+
+

Request the window for the display surface be iconified or hidden. Not all +systems and displays support an iconified display. The function will return +True if successful.

+

When the display is iconified pygame.display.get_active() will return +False. The event queue should receive an ACTIVEEVENT event when the +window has been iconified. Additionally, the event queue also recieves a +WINDOWEVENT_MINIMIZED event when the window has been iconified on pygame 2.

+
+ +
+
+pygame.display.toggle_fullscreen()¶
+
+
Switch between fullscreen and windowed displays
+
toggle_fullscreen() -> int
+
+

Switches the display window between windowed and fullscreen modes. +Display driver support is not great when using pygame 1, but with +pygame 2 it is the most reliable method to switch to and from fullscreen.

+

Supported display drivers in pygame 1:

+
+
    +
  • x11 (Linux/Unix)

  • +
  • wayland (Linux/Unix)

  • +
+
+

Supported display drivers in pygame 2:

+
+
    +
  • windows (Windows)

  • +
  • x11 (Linux/Unix)

  • +
  • wayland (Linux/Unix)

  • +
  • cocoa (OSX/Mac)

  • +
+
+
+

Note

+

toggle_fullscreen() doesn't work on Windows +unless the window size is in pygame.display.list_modes()Get list of available fullscreen modes or +the window is created with the flag pygame.SCALED. +See issue #2380.

+
+
+ +
+
+pygame.display.set_gamma()¶
+
+
Change the hardware gamma ramps
+
set_gamma(red, green=None, blue=None) -> bool
+
+

Set the red, green, and blue gamma values on the display hardware. If the +green and blue arguments are not passed, they will both be the same as red. +Not all systems and hardware support gamma ramps, if the function succeeds +it will return True.

+

A gamma value of 1.0 creates a linear color table. Lower values will +darken the display and higher values will brighten.

+
+ +
+
+pygame.display.set_gamma_ramp()¶
+
+
Change the hardware gamma ramps with a custom lookup
+
set_gamma_ramp(red, green, blue) -> bool
+
+

Set the red, green, and blue gamma ramps with an explicit lookup table. Each +argument should be sequence of 256 integers. The integers should range +between 0 and 0xffff. Not all systems and hardware support gamma +ramps, if the function succeeds it will return True.

+
+ +
+
+pygame.display.set_icon()¶
+
+
Change the system image for the display window
+
set_icon(Surface) -> None
+
+

Sets the runtime icon the system will use to represent the display window. +All windows default to a simple pygame logo for the window icon.

+

Note that calling this function implicitly initializes pygame.display, if +it was not initialized before.

+

You can pass any surface, but most systems want a smaller image around +32x32. The image can have colorkey transparency which will be passed to the +system.

+

Some systems do not allow the window icon to change after it has been shown. +This function can be called before pygame.display.set_mode() to create +the icon before the display mode is set.

+
+ +
+
+pygame.display.set_caption()¶
+
+
Set the current window caption
+
set_caption(title, icontitle=None) -> None
+
+

If the display has a window title, this function will change the name on the +window. In pygame 1.x, some systems supported an alternate shorter title to +be used for minimized displays, but in pygame 2 icontitle does nothing.

+
+ +
+
+pygame.display.get_caption()¶
+
+
Get the current window caption
+
get_caption() -> (title, icontitle)
+
+

Returns the title and icontitle for the display window. In pygame 2.x +these will always be the same value.

+
+ +
+
+pygame.display.set_palette()¶
+
+
Set the display color palette for indexed displays
+
set_palette(palette=None) -> None
+
+

This will change the video display color palette for 8-bit displays. This +does not change the palette for the actual display Surface, only the palette +that is used to display the Surface. If no palette argument is passed, the +system default palette will be restored. The palette is a sequence of +RGB triplets.

+
+ +
+
+pygame.display.get_num_displays()¶
+
+
Return the number of displays
+
get_num_displays() -> int
+
+

Returns the number of available displays. This is always 1 if +pygame.get_sdl_version()get the version number of SDL returns a major version number below 2.

+
+

New in pygame 1.9.5.

+
+
+ +
+
+pygame.display.get_window_size()¶
+
+
Return the size of the window or screen
+
get_window_size() -> tuple
+
+

Returns the size of the window initialized with pygame.display.set_mode()Initialize a window or screen for display. +This may differ from the size of the display surface if SCALED is used.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.display.get_allow_screensaver()¶
+
+
Return whether the screensaver is allowed to run.
+
get_allow_screensaver() -> bool
+
+

Return whether screensaver is allowed to run whilst the app is running. +Default is False. +By default pygame does not allow the screensaver during game play.

+
+

Note

+

Some platforms do not have a screensaver or support +disabling the screensaver. Please see +pygame.display.set_allow_screensaver()Set whether the screensaver may run for +caveats with screensaver support.

+
+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.display.set_allow_screensaver()¶
+
+
Set whether the screensaver may run
+
set_allow_screensaver(bool) -> None
+
+

Change whether screensavers should be allowed whilst the app is running. +The default value of the argument to the function is True. +By default pygame does not allow the screensaver during game play.

+

If the screensaver has been disallowed due to this function, it will automatically +be allowed to run when pygame.quit()uninitialize all pygame modules is called.

+

It is possible to influence the default value via the environment variable +SDL_HINT_VIDEO_ALLOW_SCREENSAVER, which can be set to either 0 (disable) +or 1 (enable).

+
+

Note

+

Disabling screensaver is subject to platform support. +When platform support is absent, this function will +silently appear to work even though the screensaver state +is unchanged. The lack of feedback is due to SDL not +providing any supported method for determining whether +it supports changing the screensaver state. +SDL_HINT_VIDEO_ALLOW_SCREENSAVER is available in SDL 2.0.2 or later. +SDL1.2 does not implement this.

+
+
+

New in pygame 2.0.0.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/draw.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/draw.html new file mode 100644 index 0000000..cab3941 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/draw.html @@ -0,0 +1,971 @@ + + + + + + + + + pygame.draw — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.draw
+
+
pygame module for drawing shapes
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—draw a rectangle
+—draw a polygon
+—draw a circle
+—draw an ellipse
+—draw an elliptical arc
+—draw a straight line
+—draw multiple contiguous straight line segments
+—draw a straight antialiased line
+—draw multiple contiguous straight antialiased line segments
+

Draw several simple shapes to a surface. These functions will work for +rendering to any format of surface. Rendering to hardware surfaces will be +slower than regular software surfaces.

+

Most of the functions take a width argument to represent the size of stroke +(thickness) around the edge of the shape. If a width of 0 is passed the shape +will be filled (solid).

+

All the drawing functions respect the clip area for the surface and will be +constrained to that area. The functions return a rectangle representing the +bounding area of changed pixels. This bounding rectangle is the 'minimum' +bounding box that encloses the affected area.

+

All the drawing functions accept a color argument that can be one of the +following formats:

+
+
+
+

A color's alpha value will be written directly into the surface (if the +surface contains pixel alphas), but the draw function will not draw +transparently.

+

These functions temporarily lock the surface they are operating on. Many +sequential drawing calls can be sped up by locking and unlocking the surface +object around the draw calls (see pygame.Surface.lock()lock the Surface memory for pixel access and +pygame.Surface.unlock()unlock the Surface memory from pixel access).

+
+

Note

+

See the pygame.gfxdrawpygame module for drawing shapes module for alternative draw methods.

+
+
+
+pygame.draw.rect()¶
+
+
draw a rectangle
+
rect(surface, color, rect) -> Rect
+
rect(surface, color, rect, width=0, border_radius=0, border_top_left_radius=-1, border_top_right_radius=-1, border_bottom_left_radius=-1, border_bottom_right_radius=-1) -> Rect
+
+

Draws a rectangle on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • rect (Rect) -- rectangle to draw, position and dimensions

  • +
  • width (int) --

    (optional) used for line thickness or to indicate that +the rectangle is to be filled (not to be confused with the width value +of the rect parameter)

    +
    +
    +
    if width == 0, (default) fill the rectangle
    +
    if width > 0, used for line thickness
    +
    if width < 0, nothing will be drawn
    +

    +
    +
    +

    Note

    +

    When using width values > 1, the edge lines will grow +outside the original boundary of the rect. For more details on +how the thickness for edge lines grow, refer to the width notes +of the pygame.draw.line()draw a straight line function.

    +
    +
    +

  • +
  • border_radius (int) -- (optional) used for drawing rectangle with rounded corners. +The supported range is [0, min(height, width) / 2], with 0 representing a rectangle +without rounded corners.

  • +
  • border_top_left_radius (int) -- (optional) used for setting the value of top left +border. If you don't set this value, it will use the border_radius value.

  • +
  • border_top_right_radius (int) -- (optional) used for setting the value of top right +border. If you don't set this value, it will use the border_radius value.

  • +
  • border_bottom_left_radius (int) -- (optional) used for setting the value of bottom left +border. If you don't set this value, it will use the border_radius value.

  • +
  • border_bottom_right_radius (int) --

    (optional) used for setting the value of bottom right +border. If you don't set this value, it will use the border_radius value.

    +
    +
    +
    if border_radius < 1 it will draw rectangle without rounded corners
    +
    if any of border radii has the value < 0 it will use value of the border_radius
    +
    If sum of radii on the same side of the rectangle is greater than the rect size the radii
    +
    will get scaled
    +
    +
    +

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the position of the given rect +parameter and its width and height will be 0

+
+
Return type
+

Rect

+
+
+
+

Note

+

The pygame.Surface.fill()fill Surface with a solid color method works just as well for drawing +filled rectangles and can be hardware accelerated on some platforms with +both software and hardware display modes.

+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+

Changed in pygame 2.0.0.dev8: Added support for border radius.

+
+
+ +
+
+pygame.draw.polygon()¶
+
+
draw a polygon
+
polygon(surface, color, points) -> Rect
+
polygon(surface, color, points, width=0) -> Rect
+
+

Draws a polygon on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 3 or more (x, y) coordinates that make up the +vertices of the polygon, each coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats, +e.g. [(x1, y1), (x2, y2), (x3, y3)]

  • +
  • width (int) --

    (optional) used for line thickness or to indicate that +the polygon is to be filled

    +
    +
    +
    if width == 0, (default) fill the polygon
    +
    if width > 0, used for line thickness
    +
    if width < 0, nothing will be drawn
    +

    +
    +
    +

    Note

    +

    When using width values > 1, the edge lines will grow +outside the original boundary of the polygon. For more details on +how the thickness for edge lines grow, refer to the width notes +of the pygame.draw.line()draw a straight line function.

    +
    +
    +

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the position of the first point in the +points parameter (float values will be truncated) and its width and +height will be 0

+
+
Return type
+

Rect

+
+
Raises
+
    +
  • ValueError -- if len(points) < 3 (must have at least 3 points)

  • +
  • TypeError -- if points is not a sequence or points does not +contain number pairs

  • +
+
+
+
+

Note

+

For an aapolygon, use aalines() with closed=True.

+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+ +
+
+pygame.draw.circle()¶
+
+
draw a circle
+
circle(surface, color, center, radius) -> Rect
+
circle(surface, color, center, radius, width=0, draw_top_right=None, draw_top_left=None, draw_bottom_left=None, draw_bottom_right=None) -> Rect
+
+

Draws a circle on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • center (tuple(int or float, int or float) or +list(int or float, int or float) or Vector2(int or float, int or float)) -- center point of the circle as a sequence of 2 ints/floats, +e.g. (x, y)

  • +
  • radius (int or float) -- radius of the circle, measured from the center parameter, +nothing will be drawn if the radius is less than 1

  • +
  • width (int) --

    (optional) used for line thickness or to indicate that +the circle is to be filled

    +
    +
    +
    if width == 0, (default) fill the circle
    +
    if width > 0, used for line thickness
    +
    if width < 0, nothing will be drawn
    +

    +
    +
    +

    Note

    +

    When using width values > 1, the edge lines will only grow +inward.

    +
    +
    +

  • +
  • draw_top_right (bool) -- (optional) if this is set to True then the top right corner +of the circle will be drawn

  • +
  • draw_top_left (bool) -- (optional) if this is set to True then the top left corner +of the circle will be drawn

  • +
  • draw_bottom_left (bool) -- (optional) if this is set to True then the bottom left corner +of the circle will be drawn

  • +
  • draw_bottom_right (bool) --

    (optional) if this is set to True then the bottom right corner +of the circle will be drawn

    +
    +
    +
    if any of the draw_circle_part is True then it will draw all circle parts that have the True
    +
    value, otherwise it will draw the entire circle.
    +
    +
    +

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the center parameter value (float +values will be truncated) and its width and height will be 0

+
+
Return type
+

Rect

+
+
Raises
+
    +
  • TypeError -- if center is not a sequence of two numbers

  • +
  • TypeError -- if radius is not a number

  • +
+
+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments. +Nothing is drawn when the radius is 0 (a pixel at the center coordinates +used to be drawn when the radius equaled 0). +Floats, and Vector2 are accepted for the center param. +The drawing algorithm was improved to look more like a circle.

+
+
+

Changed in pygame 2.0.0.dev8: Added support for drawing circle quadrants.

+
+
+ +
+
+pygame.draw.ellipse()¶
+
+
draw an ellipse
+
ellipse(surface, color, rect) -> Rect
+
ellipse(surface, color, rect, width=0) -> Rect
+
+

Draws an ellipse on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • rect (Rect) -- rectangle to indicate the position and dimensions of the +ellipse, the ellipse will be centered inside the rectangle and bounded +by it

  • +
  • width (int) --

    (optional) used for line thickness or to indicate that +the ellipse is to be filled (not to be confused with the width value +of the rect parameter)

    +
    +
    +
    if width == 0, (default) fill the ellipse
    +
    if width > 0, used for line thickness
    +
    if width < 0, nothing will be drawn
    +

    +
    +
    +

    Note

    +

    When using width values > 1, the edge lines will only grow +inward from the original boundary of the rect parameter.

    +
    +
    +

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the position of the given rect +parameter and its width and height will be 0

+
+
Return type
+

Rect

+
+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+ +
+
+pygame.draw.arc()¶
+
+
draw an elliptical arc
+
arc(surface, color, rect, start_angle, stop_angle) -> Rect
+
arc(surface, color, rect, start_angle, stop_angle, width=1) -> Rect
+
+

Draws an elliptical arc on the given surface.

+

The two angle arguments are given in radians and indicate the start and stop +positions of the arc. The arc is drawn in a counterclockwise direction from +the start_angle to the stop_angle.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • rect (Rect) -- rectangle to indicate the position and dimensions of the +ellipse which the arc will be based on, the ellipse will be centered +inside the rectangle

  • +
  • start_angle (float) -- start angle of the arc in radians

  • +
  • stop_angle (float) --

    stop angle of the arc in +radians

    +
    +
    +
    if start_angle < stop_angle, the arc is drawn in a +counterclockwise direction from the start_angle to the +stop_angle
    +
    if start_angle > stop_angle, tau (tau == 2 * pi) will be added +to the stop_angle, if the resulting stop angle value is greater +than the start_angle the above start_angle < stop_angle case +applies, otherwise nothing will be drawn
    +
    if start_angle == stop_angle, nothing will be drawn
    +

    +
    +
    +

  • +
  • width (int) --

    (optional) used for line thickness (not to be confused +with the width value of the rect parameter)

    +
    +
    +
    if width == 0, nothing will be drawn
    +
    if width > 0, (default is 1) used for line thickness
    +
    if width < 0, same as width == 0
    +
    +
    +

    Note

    +

    When using width values > 1, the edge lines will only grow +inward from the original boundary of the rect parameter.

    +
    +
    +

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the position of the given rect +parameter and its width and height will be 0

+
+
Return type
+

Rect

+
+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+ +
+
+pygame.draw.line()¶
+
+
draw a straight line
+
line(surface, color, start_pos, end_pos) -> Rect
+
line(surface, color, start_pos, end_pos, width=1) -> Rect
+
+

Draws a straight line on the given surface. There are no endcaps. For thick +lines the ends are squared off.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • start_pos (tuple(int or float, int or float) or +list(int or float, int or float) or Vector2(int or float, int or float)) -- start position of the line, (x, y)

  • +
  • end_pos (tuple(int or float, int or float) or +list(int or float, int or float) or Vector2(int or float, int or float)) -- end position of the line, (x, y)

  • +
  • width (int) --

    (optional) used for line thickness

    +
    +
    if width >= 1, used for line thickness (default is 1)
    +
    if width < 1, nothing will be drawn
    +

    +
    +
    +

    Note

    +

    When using width values > 1, lines will grow as follows.

    +

    For odd width values, the thickness of each line grows with the +original line being in the center.

    +

    For even width values, the thickness of each line grows with the +original line being offset from the center (as there is no exact +center line drawn). As a result, lines with a slope < 1 +(horizontal-ish) will have 1 more pixel of thickness below the +original line (in the y direction). Lines with a slope >= 1 +(vertical-ish) will have 1 more pixel of thickness to the right of +the original line (in the x direction).

    +
    +

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the start_pos parameter value (float +values will be truncated) and its width and height will be 0

+
+
Return type
+

Rect

+
+
Raises
+

TypeError -- if start_pos or end_pos is not a sequence of +two numbers

+
+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+ +
+
+pygame.draw.lines()¶
+
+
draw multiple contiguous straight line segments
+
lines(surface, color, closed, points) -> Rect
+
lines(surface, color, closed, points, width=1) -> Rect
+
+

Draws a sequence of contiguous straight lines on the given surface. There are +no endcaps or miter joints. For thick lines the ends are squared off. +Drawing thick lines with sharp corners can have undesired looking results.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • closed (bool) -- if True an additional line segment is drawn between +the first and last points in the points sequence

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 2 or more (x, y) coordinates, where each +coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats and adjacent +coordinates will be connected by a line segment, e.g. for the +points [(x1, y1), (x2, y2), (x3, y3)] a line segment will be drawn +from (x1, y1) to (x2, y2) and from (x2, y2) to (x3, y3), +additionally if the closed parameter is True another line segment +will be drawn from (x3, y3) to (x1, y1)

  • +
  • width (int) --

    (optional) used for line thickness

    +
    +
    if width >= 1, used for line thickness (default is 1)
    +
    if width < 1, nothing will be drawn
    +

    +
    +
    +

    Note

    +

    When using width values > 1 refer to the width notes +of line() for details on how thick lines grow.

    +
    +

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the position of the first point in the +points parameter (float values will be truncated) and its width and +height will be 0

+
+
Return type
+

Rect

+
+
Raises
+
    +
  • ValueError -- if len(points) < 2 (must have at least 2 points)

  • +
  • TypeError -- if points is not a sequence or points does not +contain number pairs

  • +
+
+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+ +
+
+pygame.draw.aaline()¶
+
+
draw a straight antialiased line
+
aaline(surface, color, start_pos, end_pos) -> Rect
+
aaline(surface, color, start_pos, end_pos, blend=1) -> Rect
+
+

Draws a straight antialiased line on the given surface.

+

The line has a thickness of one pixel and the endpoints have a height and +width of one pixel each.

+
+
The way a line and its endpoints are drawn:

If both endpoints are equal, only a single pixel is drawn (after +rounding floats to nearest integer).

+

Otherwise if the line is not steep (i.e. if the length along the x-axis +is greater than the height along the y-axis):

+
+

For each endpoint:

+
+

If x, the endpoint's x-coordinate, is a whole number find +which pixels would be covered by it and draw them.

+

Otherwise:

+
+

Calculate the position of the nearest point with a whole number +for its x-coordinate, when extending the line past the +endpoint.

+

Find which pixels would be covered and how much by that point.

+

If the endpoint is the left one, multiply the coverage by (1 - +the decimal part of x).

+

Otherwise multiply the coverage by the decimal part of x.

+

Then draw those pixels.

+
+
e.g.:
+
The left endpoint of the line ((1, 1.3), (5, 3)) would +cover 70% of the pixel (1, 1) and 30% of the pixel +(1, 2) while the right one would cover 100% of the +pixel (5, 3).
+
The left endpoint of the line ((1.2, 1.4), (4.6, 3.1)) +would cover 56% (i.e. 0.8 * 70%) of the pixel (1, 1) +and 24% (i.e. 0.8 * 30%) of the pixel (1, 2) while +the right one would cover 42% (i.e. 0.6 * 70%) of the +pixel (5, 3) and 18% (i.e. 0.6 * 30%) of the pixel +(5, 4) while the right
+
+
+
+
+
+

Then for each point between the endpoints, along the line, whose +x-coordinate is a whole number:

+
+

Find which pixels would be covered and how much by that point and +draw them.

+
+
e.g.:
+
The points along the line ((1, 1), (4, 2.5)) would be +(2, 1.5) and (3, 2) and would cover 50% of the pixel +(2, 1), 50% of the pixel (2, 2) and 100% of the pixel +(3, 2).
+
The points along the line ((1.2, 1.4), (4.6, 3.1)) would +be (2, 1.8) (covering 20% of the pixel (2, 1) and 80% +of the pixel (2, 2)), (3, 2.3) (covering 70% of the +pixel (3, 2) and 30% of the pixel (3, 3)) and (4, +2.8) (covering 20% of the pixel (2, 1) and 80% of the +pixel (2, 2))
+
+
+
+
+
+

Otherwise do the same for steep lines as for non-steep lines except +along the y-axis instead of the x-axis (using y instead of x, +top instead of left and bottom instead of right).

+
+
+
+

Note

+

Regarding float values for coordinates, a point with coordinate +consisting of two whole numbers is considered being right in the center +of said pixel (and having a height and width of 1 pixel would therefore +completely cover it), while a point with coordinate where one (or both) +of the numbers have non-zero decimal parts would be partially covering +two (or four if both numbers have decimal parts) adjacent pixels, e.g. +the point (1.4, 2) covers 60% of the pixel (1, 2) and 40% of the +pixel (2,2).

+
+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • start_pos (tuple(int or float, int or float) or +list(int or float, int or float) or Vector2(int or float, int or float)) -- start position of the line, (x, y)

  • +
  • end_pos (tuple(int or float, int or float) or +list(int or float, int or float) or Vector2(int or float, int or float)) -- end position of the line, (x, y)

  • +
  • blend (int) -- (optional) if non-zero (default) the line will be blended +with the surface's existing pixel shades, otherwise it will overwrite them

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the start_pos parameter value (float +values will be truncated) and its width and height will be 0

+
+
Return type
+

Rect

+
+
Raises
+

TypeError -- if start_pos or end_pos is not a sequence of +two numbers

+
+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+ +
+
+pygame.draw.aalines()¶
+
+
draw multiple contiguous straight antialiased line segments
+
aalines(surface, color, closed, points) -> Rect
+
aalines(surface, color, closed, points, blend=1) -> Rect
+
+

Draws a sequence of contiguous straight antialiased lines on the given +surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
  • closed (bool) -- if True an additional line segment is drawn between +the first and last points in the points sequence

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 2 or more (x, y) coordinates, where each +coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats and adjacent +coordinates will be connected by a line segment, e.g. for the +points [(x1, y1), (x2, y2), (x3, y3)] a line segment will be drawn +from (x1, y1) to (x2, y2) and from (x2, y2) to (x3, y3), +additionally if the closed parameter is True another line segment +will be drawn from (x3, y3) to (x1, y1)

  • +
  • blend (int) -- (optional) if non-zero (default) each line will be blended +with the surface's existing pixel shades, otherwise the pixels will be +overwritten

  • +
+
+
Returns
+

a rect bounding the changed pixels, if nothing is drawn the +bounding rect's position will be the position of the first point in the +points parameter (float values will be truncated) and its width and +height will be 0

+
+
Return type
+

Rect

+
+
Raises
+
    +
  • ValueError -- if len(points) < 2 (must have at least 2 points)

  • +
  • TypeError -- if points is not a sequence or points does not +contain number pairs

  • +
+
+
+
+

Changed in pygame 2.0.0: Added support for keyword arguments.

+
+
+ +
+draw module example +
+

Example code for draw module.¶

+
+
+
# Import a library of functions called 'pygame'
+import pygame
+from math import pi
+ 
+# Initialize the game engine
+pygame.init()
+ 
+# Define the colors we will use in RGB format
+BLACK = (  0,   0,   0)
+WHITE = (255, 255, 255)
+BLUE =  (  0,   0, 255)
+GREEN = (  0, 255,   0)
+RED =   (255,   0,   0)
+ 
+# Set the height and width of the screen
+size = [400, 300]
+screen = pygame.display.set_mode(size)
+ 
+pygame.display.set_caption("Example code for the draw module")
+ 
+#Loop until the user clicks the close button.
+done = False
+clock = pygame.time.Clock()
+ 
+while not done:
+ 
+    # This limits the while loop to a max of 10 times per second.
+    # Leave this out and we will use all CPU we can.
+    clock.tick(10)
+     
+    for event in pygame.event.get(): # User did something
+        if event.type == pygame.QUIT: # If user clicked close
+            done=True # Flag that we are done so we exit this loop
+ 
+    # All drawing code happens after the for loop and but
+    # inside the main while done==False loop.
+     
+    # Clear the screen and set the screen background
+    screen.fill(WHITE)
+ 
+    # Draw on the screen a GREEN line from (0, 0) to (50, 30) 
+    # 5 pixels wide.
+    pygame.draw.line(screen, GREEN, [0, 0], [50,30], 5)
+ 
+    # Draw on the screen 3 BLACK lines, each 5 pixels wide.
+    # The 'False' means the first and last points are not connected.
+    pygame.draw.lines(screen, BLACK, False, [[0, 80], [50, 90], [200, 80], [220, 30]], 5)
+    
+    # Draw on the screen a GREEN line from (0, 50) to (50, 80) 
+    # Because it is an antialiased line, it is 1 pixel wide.
+    pygame.draw.aaline(screen, GREEN, [0, 50],[50, 80], True)
+
+    # Draw a rectangle outline
+    pygame.draw.rect(screen, BLACK, [75, 10, 50, 20], 2)
+     
+    # Draw a solid rectangle
+    pygame.draw.rect(screen, BLACK, [150, 10, 50, 20])
+
+    # Draw a rectangle with rounded corners
+    pygame.draw.rect(screen, GREEN, [115, 210, 70, 40], 10, border_radius=15)
+    pygame.draw.rect(screen, RED, [135, 260, 50, 30], 0, border_radius=10, border_top_left_radius=0,
+                     border_bottom_right_radius=15)
+
+    # Draw an ellipse outline, using a rectangle as the outside boundaries
+    pygame.draw.ellipse(screen, RED, [225, 10, 50, 20], 2) 
+
+    # Draw an solid ellipse, using a rectangle as the outside boundaries
+    pygame.draw.ellipse(screen, RED, [300, 10, 50, 20]) 
+ 
+    # This draws a triangle using the polygon command
+    pygame.draw.polygon(screen, BLACK, [[100, 100], [0, 200], [200, 200]], 5)
+  
+    # Draw an arc as part of an ellipse. 
+    # Use radians to determine what angle to draw.
+    pygame.draw.arc(screen, BLACK,[210, 75, 150, 125], 0, pi/2, 2)
+    pygame.draw.arc(screen, GREEN,[210, 75, 150, 125], pi/2, pi, 2)
+    pygame.draw.arc(screen, BLUE, [210, 75, 150, 125], pi,3*pi/2, 2)
+    pygame.draw.arc(screen, RED,  [210, 75, 150, 125], 3*pi/2, 2*pi, 2)
+    
+    # Draw a circle
+    pygame.draw.circle(screen, BLUE, [60, 250], 40)
+
+    # Draw only one circle quadrant
+    pygame.draw.circle(screen, BLUE, [250, 250], 40, 0, draw_top_right=True)
+    pygame.draw.circle(screen, RED, [250, 250], 40, 30, draw_top_left=True)
+    pygame.draw.circle(screen, GREEN, [250, 250], 40, 20, draw_bottom_left=True)
+    pygame.draw.circle(screen, BLACK, [250, 250], 40, 10, draw_bottom_right=True)
+
+    # Go ahead and update the screen with what we've drawn.
+    # This MUST happen after all the other drawing commands.
+    pygame.display.flip()
+ 
+# Be IDLE friendly
+pygame.quit()
+
+
+
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/event.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/event.html new file mode 100644 index 0000000..4b87787 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/event.html @@ -0,0 +1,715 @@ + + + + + + + + + pygame.event — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.event
+
+
pygame module for interacting with events and queues
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—internally process pygame event handlers
+—get events from the queue
+—get a single event from the queue
+—wait for a single event from the queue
+—test if event types are waiting on the queue
+—remove all events from the queue
+—get the string name from an event id
+—control which events are allowed on the queue
+—control which events are allowed on the queue
+—test if a type of event is blocked from the queue
+—control the sharing of input devices with other applications
+—test if the program is sharing input devices
+—place a new event on the queue
+—make custom user event type
+—create a new event object
+—pygame object for representing events
+

Pygame handles all its event messaging through an event queue. The routines in +this module help you manage that event queue. The input queue is heavily +dependent on the pygame.displaypygame module to control the display window and screen module. If the display has not been +initialized and a video mode not set, the event queue may not work properly.

+

The event queue has an upper limit on the number of events it can hold. When +the queue becomes full new events are quietly dropped. To prevent lost events, +especially input events which signal a quit command, your program must handle +events every frame (with pygame.event.get(), pygame.event.pump(), +pygame.event.wait(), pygame.event.peek() or pygame.event.clear()) +and process them. Not handling events may cause your system to decide your +program has locked up. To speed up queue processing use +pygame.event.set_blocked()control which events are allowed on the queue to limit which events get queued.

+

To get the state of various input devices, you can forego the event queue and +access the input devices directly with their appropriate modules: +pygame.mousepygame module to work with the mouse, pygame.keypygame module to work with the keyboard, and pygame.joystickPygame module for interacting with joysticks, gamepads, and trackballs.. If you use +this method, remember that pygame requires some form of communication with the +system window manager and other parts of the platform. To keep pygame in sync +with the system, you will need to call pygame.event.pump()internally process pygame event handlers to keep +everything current. Usually, this should be called once per game loop. +Note: Joysticks will not send any events until the device has been initialized.

+

The event queue contains pygame.event.EventTypepygame object for representing events event objects. +There are a variety of ways to access the queued events, from simply +checking for the existence of events, to grabbing them directly off the stack. +The event queue also offers some simple filtering which can slightly help +performance by blocking certain event types from the queue. Use +pygame.event.set_allowed()control which events are allowed on the queue and pygame.event.set_blocked()control which events are allowed on the queue to +change this filtering. By default, all event types can be placed on the queue.

+

All pygame.event.EventTypepygame object for representing events instances contain an event type identifier +and attributes specific to that event type. The event type identifier is +accessible as the pygame.event.EventType.typeevent type identifier. property. Any of the +event specific attributes can be accessed through the +pygame.event.EventType.__dict__event attribute dictionary attribute or directly as an attribute +of the event object (as member lookups are passed through to the object's +dictionary values). The event object has no method functions. Users can create +their own new events with the pygame.event.Event()create a new event object function.

+

The event type identifier is in between the values of NOEVENT and +NUMEVENTS. User defined events should have a value in the inclusive range +of USEREVENT to NUMEVENTS - 1. User defined events can get a custom +event number with pygame.event.custom_type()make custom user event type. +It is recommended all user events follow this system.

+

Events support equality and inequality comparisons. Two events are equal if +they are the same type and have identical attribute values.

+

While debugging and experimenting, you can print an event object for a quick +display of its type and members. The function pygame.event.event_name()get the string name from an event id +can be used to get a string representing the name of the event type.

+

Events that come from the system will have a guaranteed set of member +attributes based on the type. The following is a list event types with their +specific attributes.

+
QUIT              none
+ACTIVEEVENT       gain, state
+KEYDOWN           key, mod, unicode, scancode
+KEYUP             key, mod, unicode, scancode
+MOUSEMOTION       pos, rel, buttons, touch
+MOUSEBUTTONUP     pos, button, touch
+MOUSEBUTTONDOWN   pos, button, touch
+JOYAXISMOTION     joy (deprecated), instance_id, axis, value
+JOYBALLMOTION     joy (deprecated), instance_id, ball, rel
+JOYHATMOTION      joy (deprecated), instance_id, hat, value
+JOYBUTTONUP       joy (deprecated), instance_id, button
+JOYBUTTONDOWN     joy (deprecated), instance_id, button
+VIDEORESIZE       size, w, h
+VIDEOEXPOSE       none
+USEREVENT         code
+
+
+
+

Changed in pygame 2.0.0: The joy attribute was deprecated, instance_id was added.

+
+
+

Changed in pygame 2.0.1: The unicode attribute was added to KEYUP event.

+
+

You can also find a list of constants for keyboard keys +here.

+
+

+
+

On MacOSX when a file is opened using a pygame application, a USEREVENT +with its code attribute set to pygame.USEREVENT_DROPFILE is generated. +There is an additional attribute called filename where the name of the file +being accessed is stored.

+
USEREVENT         code=pygame.USEREVENT_DROPFILE, filename
+
+
+
+

New in pygame 1.9.2.

+
+
+

+
+

When compiled with SDL2, pygame has these additional events and their +attributes.

+
AUDIODEVICEADDED   which, iscapture
+AUDIODEVICEREMOVED which, iscapture
+FINGERMOTION       touch_id, finger_id, x, y, dx, dy
+FINGERDOWN         touch_id, finger_id, x, y, dx, dy
+FINGERUP           touch_id, finger_id, x, y, dx, dy
+MOUSEWHEEL         which, flipped, x, y, touch
+MULTIGESTURE       touch_id, x, y, pinched, rotated, num_fingers
+TEXTEDITING        text, start, length
+TEXTINPUT          text
+
+
+
+

New in pygame 1.9.5.

+
+
+

Changed in pygame 2.0.2: Fixed amount horizontal scroll (x, positive to the right and negative to the left).

+
+
+

Changed in pygame 2.0.2: The touch attribute was added to all the MOUSE events.

+
+

The touch attribute of MOUSE events indicates whether or not the events were generated +by a touch input device, and not a real mouse. You might want to ignore such events, if your application +already handles FINGERMOTION, FINGERDOWN and FINGERUP events.

+
+

+
+

Many new events were introduced in pygame 2.

+

pygame can recognize text or files dropped in its window. If a file +is dropped, DROPFILE event will be sent, file will be its path. +The DROPTEXT event is only supported on X11.

+

MIDIIN and MIDIOUT are events reserved for pygame.midipygame module for interacting with midi input and output. use.

+

pygame 2 also supports controller hot-plugging

+
DROPBEGIN
+DROPCOMPLETE
+DROPFILE                 file
+DROPTEXT                 text
+MIDIIN
+MIDIOUT
+CONTROLLERDEVICEADDED    device_index
+JOYDEVICEADDED           device_index
+CONTROLLERDEVICEREMOVED  instance_id
+JOYDEVICEREMOVED         instance_id
+CONTROLLERDEVICEREMAPPED instance_id
+
+
+

Also in this version, instance_id attributes were added to joystick events, +and the joy attribute was deprecated.

+
+

New in pygame 2.0.0.

+
+

Since pygame 2.0.1, there are a new set of events, called window events. +Here is a list of all window events, along with a short description

+
Event type                Short description
+
+WINDOWSHOWN            Window became shown
+WINDOWHIDDEN           Window became hidden
+WINDOWEXPOSED          Window got updated by some external event
+WINDOWMOVED            Window got moved
+WINDOWRESIZED          Window got resized
+WINDOWSIZECHANGED      Window changed its size
+WINDOWMINIMIZED        Window was minimized
+WINDOWMAXIMIZED        Window was maximized
+WINDOWRESTORED         Window was restored
+WINDOWENTER            Mouse entered the window
+WINDOWLEAVE            Mouse left the window
+WINDOWFOCUSGAINED      Window gained focus
+WINDOWFOCUSLOST        Window lost focus
+WINDOWCLOSE            Window was closed
+WINDOWTAKEFOCUS        Window was offered focus
+WINDOWHITTEST          Window has a special hit test
+
+
+

If SDL version used is less than 2.0.5, the last two events WINDOWTAKEFOCUS +and WINDOWHITTEST will not work.

+

Most these window events do not have any attributes, except WINDOWMOVED, +WINDOWRESIZED and WINDOWSIZECHANGED, these have x and y attributes

+
+

+
+
+
+pygame.event.pump()¶
+
+
internally process pygame event handlers
+
pump() -> None
+
+

For each frame of your game, you will need to make some sort of call to the +event queue. This ensures your program can internally interact with the rest +of the operating system. If you are not using other event functions in your +game, you should call pygame.event.pump() to allow pygame to handle +internal actions.

+

This function is not necessary if your program is consistently processing +events on the queue through the other pygame.eventpygame module for interacting with events and queues functions.

+

There are important things that must be dealt with internally in the event +queue. The main window may need to be repainted or respond to the system. If +you fail to make a call to the event queue for too long, the system may +decide your program has locked up.

+
+

Caution

+

This function should only be called in the thread that initialized pygame.displaypygame module to control the display window and screen.

+
+
+ +
+
+pygame.event.get()¶
+
+
get events from the queue
+
get(eventtype=None) -> Eventlist
+
get(eventtype=None, pump=True) -> Eventlist
+
get(eventtype=None, pump=True, exclude=None) -> Eventlist
+
+

This will get all the messages and remove them from the queue. If a type or +sequence of types is given only those messages will be removed from the +queue and returned.

+

If a type or sequence of types is passed in the exclude argument +instead, then all only other messages will be removed from the queue. If +an exclude parameter is passed, the eventtype parameter must be +None.

+

If you are only taking specific events from the queue, be aware that the +queue could eventually fill up with the events you are not interested.

+

If pump is True (the default), then pygame.event.pump()internally process pygame event handlers will be called.

+
+

Changed in pygame 1.9.5: Added pump argument

+
+
+

Changed in pygame 2.0.2: Added exclude argument

+
+
+ +
+
+pygame.event.poll()¶
+
+
get a single event from the queue
+
poll() -> EventType instance
+
+

Returns a single event from the queue. If the event queue is empty an event +of type pygame.NOEVENT will be returned immediately. The returned event +is removed from the queue.

+
+

Caution

+

This function should only be called in the thread that initialized pygame.displaypygame module to control the display window and screen.

+
+
+ +
+
+pygame.event.wait()¶
+
+
wait for a single event from the queue
+
wait() -> EventType instance
+
wait(timeout) -> EventType instance
+
+

Returns a single event from the queue. If the queue is empty this function +will wait until one is created. From pygame 2.0.0, if a timeout argument +is given, the function will return an event of type pygame.NOEVENT +if no events enter the queue in timeout milliseconds. The event is removed +from the queue once it has been returned. While the program is waiting it will +sleep in an idle state. This is important for programs that want to share the +system with other applications.

+
+

Changed in pygame 2.0.0.dev13: Added timeout argument

+
+
+

Caution

+

This function should only be called in the thread that initialized pygame.displaypygame module to control the display window and screen.

+
+
+ +
+
+pygame.event.peek()¶
+
+
test if event types are waiting on the queue
+
peek(eventtype=None) -> bool
+
peek(eventtype=None, pump=True) -> bool
+
+

Returns True if there are any events of the given type waiting on the +queue. If a sequence of event types is passed, this will return True if +any of those events are on the queue.

+

If pump is True (the default), then pygame.event.pump()internally process pygame event handlers will be called.

+
+

Changed in pygame 1.9.5: Added pump argument

+
+
+ +
+
+pygame.event.clear()¶
+
+
remove all events from the queue
+
clear(eventtype=None) -> None
+
clear(eventtype=None, pump=True) -> None
+
+

Removes all events from the queue. If eventtype is given, removes the given event +or sequence of events. This has the same effect as pygame.event.get()get events from the queue except None +is returned. It can be slightly more efficient when clearing a full event queue.

+

If pump is True (the default), then pygame.event.pump()internally process pygame event handlers will be called.

+
+

Changed in pygame 1.9.5: Added pump argument

+
+
+ +
+
+pygame.event.event_name()¶
+
+
get the string name from an event id
+
event_name(type) -> string
+
+

Returns a string representing the name (in CapWords style) of the given +event type.

+

"UserEvent" is returned for all values in the user event id range. +"Unknown" is returned when the event type does not exist.

+
+ +
+
+pygame.event.set_blocked()¶
+
+
control which events are allowed on the queue
+
set_blocked(type) -> None
+
set_blocked(typelist) -> None
+
set_blocked(None) -> None
+
+

The given event types are not allowed to appear on the event queue. By +default all events can be placed on the queue. It is safe to disable an +event type multiple times.

+

If None is passed as the argument, ALL of the event types are blocked +from being placed on the queue.

+
+ +
+
+pygame.event.set_allowed()¶
+
+
control which events are allowed on the queue
+
set_allowed(type) -> None
+
set_allowed(typelist) -> None
+
set_allowed(None) -> None
+
+

The given event types are allowed to appear on the event queue. By default, +all event types can be placed on the queue. It is safe to enable an event +type multiple times.

+

If None is passed as the argument, ALL of the event types are allowed +to be placed on the queue.

+
+ +
+
+pygame.event.get_blocked()¶
+
+
test if a type of event is blocked from the queue
+
get_blocked(type) -> bool
+
get_blocked(typelist) -> bool
+
+

Returns True if the given event type is blocked from the queue. If a +sequence of event types is passed, this will return True if any of those +event types are blocked.

+
+ +
+
+pygame.event.set_grab()¶
+
+
control the sharing of input devices with other applications
+
set_grab(bool) -> None
+
+

When your program runs in a windowed environment, it will share the mouse +and keyboard devices with other applications that have focus. If your +program sets the event grab to True, it will lock all input into your +program.

+

It is best to not always grab the input, since it prevents the user from +doing other things on their system.

+
+ +
+
+pygame.event.get_grab()¶
+
+
test if the program is sharing input devices
+
get_grab() -> bool
+
+

Returns True when the input events are grabbed for this application.

+
+ +
+
+pygame.event.post()¶
+
+
place a new event on the queue
+
post(Event) -> bool
+
+

Places the given event at the end of the event queue.

+

This is usually used for placing custom events on the event queue. +Any type of event can be posted, and the events posted can have any attributes.

+

This returns a boolean on whether the event was posted or not. Blocked events +cannot be posted, and this function returns False if you try to post them.

+
+

Changed in pygame 2.0.1: returns a boolean, previously returned None

+
+
+ +
+
+pygame.event.custom_type()¶
+
+
make custom user event type
+
custom_type() -> int
+
+

Reserves a pygame.USEREVENT for a custom use.

+

If too many events are made a pygame.errorstandard pygame exception is raised.

+
+

New in pygame 2.0.0.dev3.

+
+
+ +
+
+pygame.event.Event()¶
+
+
create a new event object
+
Event(type, dict) -> EventType instance
+
Event(type, **attributes) -> EventType instance
+
+

Creates a new event with the given type and attributes. The attributes can +come from a dictionary argument with string keys or from keyword arguments.

+
+ +
+
+pygame.event.EventType¶
+
+
pygame object for representing events
+
+ +++++ + + + + + + + + + + +
+—event type identifier.
+—event attribute dictionary
+

A pygame object that represents an event. User event instances are created +with an pygame.event.Event()create a new event object function call. The EventType type +is not directly callable. EventType instances support attribute +assignment and deletion.

+
+
+type¶
+
+
event type identifier.
+
type -> int
+
+

Read-only. The event type identifier. For user created event +objects, this is the type argument passed to +pygame.event.Event()create a new event object.

+

For example, some predefined event identifiers are QUIT and +MOUSEMOTION.

+
+ +
+
+__dict__¶
+
+
event attribute dictionary
+
__dict__ -> dict
+
+

Read-only. The event type specific attributes of an event. The +dict attribute is a synonym for backward compatibility.

+

For example, the attributes of a KEYDOWN event would be unicode, +key, and mod

+
+ +
+

New in pygame 1.9.2: Mutable attributes.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/examples.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/examples.html new file mode 100644 index 0000000..618e4e0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/examples.html @@ -0,0 +1,712 @@ + + + + + + + + + pygame.examples — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.examples
+
+
module of example programs
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—play the full aliens example
+—run a simple starfield example
+—hit the moving chimp
+—display animated objects on the screen
+—run a font rendering example
+—run a FreeType rendering example
+—display a vertical gradient
+—display pygame events
+—show various surfarray effects
+—load and play a sound
+—play various sndarray effects
+—display an animated liquid effect
+—display an animated 3D cube using OpenGL
+—access the clipboard
+—display multiple images bounce off each other using collision detection
+—show lots of sprites moving around
+—write an image file that is smoothscaled copy of an input file
+—demonstrate joystick functionality
+—demonstrate the various surface.fill method blend options
+—uses alternative additive fill to that of surface.fill
+—display two different custom cursors
+—display various pixelarray generated effects
+—interactively scale an image using smoothscale
+—run a midi example
+—run a Surface.scroll example that shows a magnified image
+—display video captured live from an attached camera
+—play an audio file
+

These examples should help get you started with pygame. Here is a brief rundown +of what you get. The source code for these examples is in the public domain. +Feel free to use for your own projects.

+

There are several ways to run the examples. First they can be run as +stand-alone programs. Second they can be imported and their main() methods +called (see below). Finally, the easiest way is to use the python -m option:

+
python -m pygame.examples.<example name> <example arguments>
+
+
+

eg:

+
python -m pygame.examples.scaletest someimage.png
+
+
+

Resources such as images and sounds for the examples are found in the +pygame/examples/data subdirectory.

+

You can find where the example files are installed by using the following +commands inside the python interpreter.

+
>>> import pygame.examples.scaletest
+>>> pygame.examples.scaletest.__file__
+'/usr/lib/python2.6/site-packages/pygame/examples/scaletest.py'
+
+
+

On each OS and version of Python the location will be slightly different. +For example on Windows it might be in 'C:/Python26/Lib/site-packages/pygame/examples/' +On Mac OS X it might be in '/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/pygame/examples/'

+

You can also run the examples in the python interpreter by calling each modules main() function.

+
>>> import pygame.examples.scaletest
+>>> pygame.examples.scaletest.main()
+
+
+

We're always on the lookout for more examples and/or example requests. Code +like this is probably the best way to start getting involved with python +gaming.

+

examples as a package is new to pygame 1.9.0. But most of the examples came with +pygame much earlier.

+
+
+aliens.main()¶
+
+
play the full aliens example
+
aliens.main() -> None
+
+

This started off as a port of the SDL demonstration, Aliens. Now it has +evolved into something sort of resembling fun. This demonstrates a lot of +different uses of sprites and optimized blitting. Also transparency, +colorkeys, fonts, sound, music, joystick, and more. (PS, my high score is +117! goodluck)

+
+ +
+
+stars.main()¶
+
+
run a simple starfield example
+
stars.main() -> None
+
+

A simple starfield example. You can change the center of perspective by +leftclicking the mouse on the screen.

+
+ +
+
+chimp.main()¶
+
+
hit the moving chimp
+
chimp.main() -> None
+
+

This simple example is derived from the line-by-line tutorial that comes +with pygame. It is based on a 'popular' web banner. Note there are comments +here, but for the full explanation, follow along in the tutorial.

+
+ +
+
+moveit.main()¶
+
+
display animated objects on the screen
+
moveit.main() -> None
+
+

This is the full and final example from the Pygame Tutorial, "How Do I Make +It Move". It creates 10 objects and animates them on the screen.

+

Note it's a bit scant on error checking, but it's easy to read. :] +Fortunately, this is python, and we needn't wrestle with a pile of error +codes.

+
+ +
+
+fonty.main()¶
+
+
run a font rendering example
+
fonty.main() -> None
+
+

Super quick, super simple application demonstrating the different ways to +render fonts with the font module

+
+ +
+
+freetype_misc.main()¶
+
+
run a FreeType rendering example
+
freetype_misc.main() -> None
+
+

A showcase of rendering features the pygame.freetype.FontCreate a new Font instance from a supported font file. +class provides in addition to those available with pygame.font.Fontcreate a new Font object from a file. +It is a demonstration of direct to surface rendering, with vertical text +and rotated text, opaque text and semi transparent text, horizontally +stretched text and vertically stretched text.

+
+ +
+
+vgrade.main()¶
+
+
display a vertical gradient
+
vgrade.main() -> None
+
+

Demonstrates creating a vertical gradient with pixelcopy and NumPy python. +The app will create a new gradient every half second and report the time +needed to create and display the image. If you're not prepared to start +working with the NumPy arrays, don't worry about the source for this one :]

+
+ +
+
+eventlist.main()¶
+
+
display pygame events
+
eventlist.main() -> None
+
+

Eventlist is a sloppy style of pygame, but is a handy tool for learning +about pygame events and input. At the top of the screen are the state of +several device values, and a scrolling list of events are displayed on the +bottom.

+

This is not quality 'ui' code at all, but you can see how to implement very +non-interactive status displays, or even a crude text output control.

+
+ +
+
+arraydemo.main()¶
+
+
show various surfarray effects
+
arraydemo.main(arraytype=None) -> None
+
+

Another example filled with various surfarray effects. It requires the +surfarray and image modules to be installed. This little demo can also make +a good starting point for any of your own tests with surfarray

+

The arraytype parameter is deprecated; passing any value besides 'numpy' +will raise ValueError.

+
+ +
+
+sound.main()¶
+
+
load and play a sound
+
sound.main(file_path=None) -> None
+
+

Extremely basic testing of the mixer module. Load a sound and play it. All +from the command shell, no graphics.

+

If provided, use the audio file 'file_path', otherwise use a default file.

+

sound.py optional command line argument: an audio file

+
+ +
+
+sound_array_demos.main()¶
+
+
play various sndarray effects
+
sound_array_demos.main(arraytype=None) -> None
+
+

Uses sndarray and NumPy to create offset faded copies of the +original sound. Currently it just uses hardcoded values for the number of +echoes and the delay. Easy for you to recreate as needed.

+

The arraytype parameter is deprecated; passing any value besides 'numpy' +will raise ValueError.

+
+ +
+
+liquid.main()¶
+
+
display an animated liquid effect
+
liquid.main() -> None
+
+

This example was created in a quick comparison with the BlitzBasic gaming +language. Nonetheless, it demonstrates a quick 8-bit setup (with colormap).

+
+ +
+
+glcube.main()¶
+
+
display an animated 3D cube using OpenGL
+
glcube.main() -> None
+
+

Using PyOpenGL and pygame, this creates a spinning 3D multicolored cube.

+
+ +
+
+scrap_clipboard.main()¶
+
+
access the clipboard
+
scrap_clipboard.main() -> None
+
+

A simple demonstration example for the clipboard support.

+
+ +
+
+mask.main()¶
+
+
display multiple images bounce off each other using collision detection
+
mask.main(*args) -> None
+
+

Positional arguments:

+
one or more image file names.
+
+
+

This pygame.masks demo will display multiple moving sprites bouncing off +each other. More than one sprite image can be provided.

+

If run as a program then mask.py takes one or more image files as +command line arguments.

+
+ +
+
+testsprite.main()¶
+
+
show lots of sprites moving around
+
testsprite.main(update_rects = True, use_static = False, use_FastRenderGroup = False, screen_dims = [640, 480], use_alpha = False, flags = 0) -> None
+
+

Optional keyword arguments:

+
update_rects - use the RenderUpdate sprite group class
+use_static - include non-moving images
+use_FastRenderGroup - Use the FastRenderGroup sprite group
+screen_dims - pygame window dimensions
+use_alpha - use alpha blending
+flags - additional display mode flags
+
+
+

Like the testsprite.c that comes with SDL, this pygame version shows +lots of sprites moving around.

+

If run as a stand-alone program then no command line arguments are taken.

+
+ +
+
+headless_no_windows_needed.main()¶
+
+
write an image file that is smoothscaled copy of an input file
+
headless_no_windows_needed.main(fin, fout, w, h) -> None
+
+

arguments:

+
fin - name of an input image file
+fout - name of the output file to create/overwrite
+w, h - size of the rescaled image, as integer width and height
+
+
+

How to use pygame with no windowing system, like on headless servers.

+

Thumbnail generation with scaling is an example of what you can do with +pygame.

+

NOTE: the pygame scale function uses MMX/SSE if available, and can be +run in multiple threads.

+

If headless_no_windows_needed.py is run as a program it takes the +following command line arguments:

+
-scale inputimage outputimage new_width new_height
+eg. -scale in.png outpng 50 50
+
+
+
+ +
+
+joystick.main()¶
+
+
demonstrate joystick functionality
+
joystick.main() -> None
+
+

A demo showing full joystick support.

+
+

New in pygame 2.0.2.

+
+
+ +
+
+blend_fill.main()¶
+
+
demonstrate the various surface.fill method blend options
+
blend_fill.main() -> None
+
+

A interactive demo that lets one choose which BLEND_xxx option to apply to a +surface.

+
+ +
+
+blit_blends.main()¶
+
+
uses alternative additive fill to that of surface.fill
+
blit_blends.main() -> None
+
+

Fake additive blending. Using NumPy. it doesn't clamp. Press r,g,b Somewhat +like blend_fill.

+
+ +
+
+cursors.main()¶
+
+
display two different custom cursors
+
cursors.main() -> None
+
+

Display an arrow or circle with crossbar cursor.

+
+ +
+
+pixelarray.main()¶
+
+
display various pixelarray generated effects
+
pixelarray.main() -> None
+
+

Display various pixelarray generated effects.

+
+ +
+
+scaletest.main()¶
+
+
interactively scale an image using smoothscale
+
scaletest.main(imagefile, convert_alpha=False, run_speed_test=True) -> None
+
+

arguments:

+
imagefile - file name of source image (required)
+convert_alpha - use convert_alpha() on the surf (default False)
+run_speed_test - (default False)
+
+
+

A smoothscale example that resized an image on the screen. Vertical and +horizontal arrow keys are used to change the width and height of the +displayed image. If the convert_alpha option is True then the source image +is forced to have source alpha, whether or not the original images does. If +run_speed_test is True then a background timing test is performed instead of +the interactive scaler.

+

If scaletest.py is run as a program then the command line options are:

+
ImageFile [-t] [-convert_alpha]
+[-t] = Run Speed Test
+[-convert_alpha] = Use convert_alpha() on the surf.
+
+
+
+ +
+
+midi.main()¶
+
+
run a midi example
+
midi.main(mode='output', device_id=None) -> None
+
+

Arguments:

+
mode - if 'output' run a midi keyboard output example
+          'input' run a midi event logger input example
+          'list' list available midi devices
+       (default 'output')
+device_id - midi device number; if None then use the default midi input or
+            output device for the system
+
+
+

The output example shows how to translate mouse clicks or computer keyboard +events into midi notes. It implements a rudimentary button widget and state +machine.

+

The input example shows how to translate midi input to pygame events.

+

With the use of a virtual midi patch cord the output and input examples can +be run as separate processes and connected so the keyboard output is +displayed on a console.

+

new to pygame 1.9.0

+
+ +
+
+scroll.main()¶
+
+
run a Surface.scroll example that shows a magnified image
+
scroll.main(image_file=None) -> None
+
+

This example shows a scrollable image that has a zoom factor of eight. It +uses the Surface.scroll() +function to shift the image on the display surface. +A clip rectangle protects a margin area. If called as a function, +the example accepts an optional image file path. If run as a program it +takes an optional file path command line argument. If no file is provided a +default image file is used.

+

When running click on a black triangle to move one pixel in the direction +the triangle points. Or use the arrow keys. Close the window or press +ESC to quit.

+
+ +
+
+camera.main()¶
+
+
display video captured live from an attached camera
+
camera.main() -> None
+
+

A simple live video player, it uses the first available camera it finds on +the system.

+
+ +
+
+playmus.main()¶
+
+
play an audio file
+
playmus.main(file_path) -> None
+
+

A simple music player with window and keyboard playback control. Playback can +be paused and rewound to the beginning.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/fastevent.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/fastevent.html new file mode 100644 index 0000000..37b3d14 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/fastevent.html @@ -0,0 +1,286 @@ + + + + + + + + + pygame.fastevent — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.fastevent
+
+
pygame module for interacting with events and queues
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize pygame.fastevent
+—returns True if the fastevent module is currently initialized
+—internally process pygame event handlers
+—wait for an event
+—get an available event
+—get all events from the queue
+—place an event on the queue
+

IMPORTANT NOTE: THIS MODULE IS DEPRECATED IN PYGAME 2.2

+

In older pygame versions before pygame 2, pygame.eventpygame module for interacting with events and queues was not well +suited for posting events from different threads. This module served as a +replacement (with less features) for multithreaded use. Now, the usage of this +module is highly discouraged in favour of use of the main pygame.eventpygame module for interacting with events and queues +module. This module will be removed in a future pygame version.

+

Below, the legacy docs of the module is provided

+
+
+pygame.fastevent.init()¶
+
+
initialize pygame.fastevent
+
init() -> None
+
+

Initialize the pygame.fastevent module.

+
+ +
+
+pygame.fastevent.get_init()¶
+
+
returns True if the fastevent module is currently initialized
+
get_init() -> bool
+
+

Returns True if the pygame.fastevent module is currently initialized.

+
+ +
+
+pygame.fastevent.pump()¶
+
+
internally process pygame event handlers
+
pump() -> None
+
+

For each frame of your game, you will need to make some sort of call to the +event queue. This ensures your program can internally interact with the rest +of the operating system.

+

This function is not necessary if your program is consistently processing +events on the queue through the other pygame.fasteventpygame module for interacting with events and queues functions.

+

There are important things that must be dealt with internally in the event +queue. The main window may need to be repainted or respond to the system. If +you fail to make a call to the event queue for too long, the system may +decide your program has locked up.

+
+ +
+
+pygame.fastevent.wait()¶
+
+
wait for an event
+
wait() -> Event
+
+

Returns the current event on the queue. If there are no messages +waiting on the queue, this will not return until one is available. +Sometimes it is important to use this wait to get events from the queue, +it will allow your application to idle when the user isn't doing anything +with it.

+
+ +
+
+pygame.fastevent.poll()¶
+
+
get an available event
+
poll() -> Event
+
+

Returns next event on queue. If there is no event waiting on the queue, +this will return an event with type NOEVENT.

+
+ +
+
+pygame.fastevent.get()¶
+
+
get all events from the queue
+
get() -> list of Events
+
+

This will get all the messages and remove them from the queue.

+
+ +
+
+pygame.fastevent.post()¶
+
+
place an event on the queue
+
post(Event) -> None
+
+

This will post your own event objects onto the event queue. You can post +any event type you want, but some care must be taken. For example, if you +post a MOUSEBUTTONDOWN event to the queue, it is likely any code receiving +the event will expect the standard MOUSEBUTTONDOWN attributes to be +available, like 'pos' and 'button'.

+

Because pygame.fastevent.post() may have to wait for the queue to empty, +you can get into a dead lock if you try to append an event on to a full +queue from the thread that processes events. For that reason I do not +recommend using this function in the main thread of an SDL program.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/font.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/font.html new file mode 100644 index 0000000..6520d9c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/font.html @@ -0,0 +1,698 @@ + + + + + + + + + pygame.font — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.font
+
+
pygame module for loading and rendering fonts
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize the font module
+—uninitialize the font module
+—true if the font module is initialized
+—get the filename of the default font
+—get all available fonts
+—find a specific font on the system
+—create a Font object from the system fonts
+—create a new Font object from a file
+

The font module allows for rendering TrueType fonts into a new Surface object. +It accepts any UCS-2 character ('u0001' to 'uFFFF'). This module is optional +and requires SDL_ttf as a dependency. You should test that pygame.fontpygame module for loading and rendering fonts +is available and initialized before attempting to use the module.

+

Most of the work done with fonts are done by using the actual Font objects. The +module by itself only has routines to initialize the module and create Font +objects with pygame.font.Font().

+

You can load fonts from the system by using the pygame.font.SysFont() +function. There are a few other functions to help lookup the system fonts.

+

Pygame comes with a builtin default font. This can always be accessed by +passing None as the font name.

+

To use the pygame.freetypeEnhanced pygame module for loading and rendering computer fonts based pygame.ftfont as +pygame.fontpygame module for loading and rendering fonts define the environment variable PYGAME_FREETYPE before the +first import of pygamethe top level pygame package. Module pygame.ftfont is a pygame.fontpygame module for loading and rendering fonts +compatible module that passes all but one of the font module unit tests: +it does not have the UCS-2 limitation of the SDL_ttf based font module, so +fails to raise an exception for a code point greater than 'uFFFF'. If +pygame.freetypeEnhanced pygame module for loading and rendering computer fonts is unavailable then the SDL_ttf font module will be +loaded instead.

+
+
+pygame.font.init()¶
+
+
initialize the font module
+
init() -> None
+
+

This method is called automatically by pygame.init(). It initializes the +font module. The module must be initialized before any other functions will +work.

+

It is safe to call this function more than once.

+
+ +
+
+pygame.font.quit()¶
+
+
uninitialize the font module
+
quit() -> None
+
+

Manually uninitialize SDL_ttf's font system. This is called automatically by +pygame.quit().

+

It is safe to call this function even if font is currently not initialized.

+
+ +
+
+pygame.font.get_init()¶
+
+
true if the font module is initialized
+
get_init() -> bool
+
+

Test if the font module is initialized or not.

+
+ +
+
+pygame.font.get_default_font()¶
+
+
get the filename of the default font
+
get_default_font() -> string
+
+

Return the filename of the system font. This is not the full path to the +file. This file can usually be found in the same directory as the font +module, but it can also be bundled in separate archives.

+
+ +
+
+pygame.font.get_fonts()¶
+
+
get all available fonts
+
get_fonts() -> list of strings
+
+

Returns a list of all the fonts available on the system. The names of the +fonts will be set to lowercase with all spaces and punctuation removed. This +works on most systems, but some will return an empty list if they cannot +find fonts.

+
+ +
+
+pygame.font.match_font()¶
+
+
find a specific font on the system
+
match_font(name, bold=False, italic=False) -> path
+
+

Returns the full path to a font file on the system. If bold or italic are +set to true, this will attempt to find the correct family of font.

+

The font name can also be an iterable of font names, a string of +comma-separated font names, or a bytes of comma-separated font names, in +which case the set of names will be searched in order. +If none of the given names are found, None is returned.

+
+

New in pygame 2.0.1: Accept an iterable of font names.

+
+

Example:

+
print pygame.font.match_font('bitstreamverasans')
+# output is: /usr/share/fonts/truetype/ttf-bitstream-vera/Vera.ttf
+# (but only if you have Vera on your system)
+
+
+
+ +
+
+pygame.font.SysFont()¶
+
+
create a Font object from the system fonts
+
SysFont(name, size, bold=False, italic=False) -> Font
+
+

Return a new Font object that is loaded from the system fonts. The font will +match the requested bold and italic flags. Pygame uses a small set of common +font aliases. If the specific font you ask for is not available, a reasonable +alternative may be used. If a suitable system font is not found this will +fall back on loading the default pygame font.

+

The font name can also be an iterable of font names, a string of +comma-separated font names, or a bytes of comma-separated font names, in +which case the set of names will be searched in order.

+
+

New in pygame 2.0.1: Accept an iterable of font names.

+
+
+ +
+
+pygame.font.Font¶
+
+
create a new Font object from a file
+
Font(filename, size) -> Font
+
Font(pathlib.Path, size) -> Font
+
Font(object, size) -> Font
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Gets or sets whether the font should be rendered in (faked) bold.
+—Gets or sets whether the font should be rendered in (faked) italics.
+—Gets or sets whether the font should be rendered with an underline.
+—draw text on a new Surface
+—determine the amount of space needed to render text
+—control if text is rendered with an underline
+—check if text will be rendered with an underline
+—enable fake rendering of bold text
+—check if text will be rendered bold
+—enable fake rendering of italic text
+—gets the metrics for each character in the passed string
+—check if the text will be rendered italic
+—get the line space of the font text
+—get the height of the font
+—get the ascent of the font
+—get the descent of the font
+

Load a new font from a given filename or a python file object. The size is +the height of the font in pixels. If the filename is None the pygame default +font will be loaded. If a font cannot be loaded from the arguments given an +exception will be raised. Once the font is created the size cannot be +changed.

+

Font objects are mainly used to render text into new Surface objects. The +render can emulate bold or italic features, but it is better to load from a +font with actual italic or bold glyphs. The rendered text can be regular +strings or unicode.

+
+
+bold¶
+
+
Gets or sets whether the font should be rendered in (faked) bold.
+
bold -> bool
+
+

Whether the font should be rendered in bold.

+

When set to True, this enables the bold rendering of text. This +is a fake stretching of the font that doesn't look good on many +font types. If possible load the font from a real bold font +file. While bold, the font will have a different width than when +normal. This can be mixed with the italic and underline modes.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+italic¶
+
+
Gets or sets whether the font should be rendered in (faked) italics.
+
italic -> bool
+
+

Whether the font should be rendered in italic.

+

When set to True, this enables fake rendering of italic +text. This is a fake skewing of the font that doesn't look good +on many font types. If possible load the font from a real italic +font file. While italic the font will have a different width +than when normal. This can be mixed with the bold and underline +modes.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+underline¶
+
+
Gets or sets whether the font should be rendered with an underline.
+
underline -> bool
+
+

Whether the font should be rendered in underline.

+

When set to True, all rendered fonts will include an +underline. The underline is always one pixel thick, regardless +of font size. This can be mixed with the bold and italic modes.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+render()¶
+
+
draw text on a new Surface
+
render(text, antialias, color, background=None) -> Surface
+
+

This creates a new Surface with the specified text rendered on it. pygame +provides no way to directly draw text on an existing Surface: instead you +must use Font.render() to create an image (Surface) of the text, then +blit this image onto another Surface.

+

The text can only be a single line: newline characters are not rendered. +Null characters ('x00') raise a TypeError. Both Unicode and char (byte) +strings are accepted. For Unicode strings only UCS-2 characters +('u0001' to 'uFFFF') were previously supported and any greater unicode +codepoint would raise a UnicodeError. Now, characters in the UCS-4 range +are supported. For char strings a LATIN1 encoding is assumed. The +antialias argument is a boolean: if true the characters will have smooth +edges. The color argument is the color of the text +[e.g.: (0,0,255) for blue]. The optional background argument is a color +to use for the text background. If no background is passed the area +outside the text will be transparent.

+

The Surface returned will be of the dimensions required to hold the text. +(the same as those returned by Font.size()). If an empty string is passed +for the text, a blank surface will be returned that is zero pixel wide and +the height of the font.

+

Depending on the type of background and antialiasing used, this returns +different types of Surfaces. For performance reasons, it is good to know +what type of image will be used. If antialiasing is not used, the return +image will always be an 8-bit image with a two-color palette. If the +background is transparent a colorkey will be set. Antialiased images are +rendered to 24-bit RGB images. If the background is transparent a +pixel alpha will be included.

+

Optimization: if you know that the final destination for the text (on the +screen) will always have a solid background, and the text is antialiased, +you can improve performance by specifying the background color. This will +cause the resulting image to maintain transparency information by +colorkey rather than (much less efficient) alpha values.

+

If you render '\n' an unknown char will be rendered. Usually a +rectangle. Instead you need to handle new lines yourself.

+

Font rendering is not thread safe: only a single thread can render text +at any time.

+
+

Changed in pygame 2.0.3: Rendering UCS_4 unicode works and does not +raise an exception. Use if hasattr(pygame.font, 'UCS_4'): to see if +pygame supports rendering UCS_4 unicode including more languages and +emoji.

+
+
+ +
+
+size()¶
+
+
determine the amount of space needed to render text
+
size(text) -> (width, height)
+
+

Returns the dimensions needed to render the text. This can be used to +help determine the positioning needed for text before it is rendered. It +can also be used for wordwrapping and other layout effects.

+

Be aware that most fonts use kerning which adjusts the widths for +specific letter pairs. For example, the width for "ae" will not always +match the width for "a" + "e".

+
+ +
+
+set_underline()¶
+
+
control if text is rendered with an underline
+
set_underline(bool) -> None
+
+

When enabled, all rendered fonts will include an underline. The underline +is always one pixel thick, regardless of font size. This can be mixed +with the bold and italic modes.

+
+

Note

+

This is the same as the underline attribute.

+
+
+ +
+
+get_underline()¶
+
+
check if text will be rendered with an underline
+
get_underline() -> bool
+
+

Return True when the font underline is enabled.

+
+
+

Note

+

This is the same as the underline attribute.

+
+
+
+ +
+
+set_bold()¶
+
+
enable fake rendering of bold text
+
set_bold(bool) -> None
+
+

Enables the bold rendering of text. This is a fake stretching of the font +that doesn't look good on many font types. If possible load the font from +a real bold font file. While bold, the font will have a different width +than when normal. This can be mixed with the italic and underline modes.

+
+

Note

+

This is the same as the bold attribute.

+
+
+ +
+
+get_bold()¶
+
+
check if text will be rendered bold
+
get_bold() -> bool
+
+

Return True when the font bold rendering mode is enabled.

+
+

Note

+

This is the same as the bold attribute.

+
+
+ +
+
+set_italic()¶
+
+
enable fake rendering of italic text
+
set_italic(bool) -> None
+
+

Enables fake rendering of italic text. This is a fake skewing of the font +that doesn't look good on many font types. If possible load the font from +a real italic font file. While italic the font will have a different +width than when normal. This can be mixed with the bold and underline +modes.

+
+

Note

+

This is the same as the italic attribute.

+
+
+ +
+
+metrics()¶
+
+
gets the metrics for each character in the passed string
+
metrics(text) -> list
+
+

The list contains tuples for each character, which contain the minimum +X offset, the maximum X offset, the minimum Y offset, the +maximum Y offset and the advance offset (bearing plus width) of the +character. [(minx, maxx, miny, maxy, advance), (minx, maxx, miny, maxy, +advance), ...]. None is entered in the list for each unrecognized +character.

+
+ +
+
+get_italic()¶
+
+
check if the text will be rendered italic
+
get_italic() -> bool
+
+

Return True when the font italic rendering mode is enabled.

+
+

Note

+

This is the same as the italic attribute.

+
+
+ +
+
+get_linesize()¶
+
+
get the line space of the font text
+
get_linesize() -> int
+
+

Return the height in pixels for a line of text with the font. When +rendering multiple lines of text this is the recommended amount of space +between lines.

+
+ +
+
+get_height()¶
+
+
get the height of the font
+
get_height() -> int
+
+

Return the height in pixels of the actual rendered text. This is the +average size for each glyph in the font.

+
+ +
+
+get_ascent()¶
+
+
get the ascent of the font
+
get_ascent() -> int
+
+

Return the height in pixels for the font ascent. The ascent is the number +of pixels from the font baseline to the top of the font.

+
+ +
+
+get_descent()¶
+
+
get the descent of the font
+
get_descent() -> int
+
+

Return the height in pixels for the font descent. The descent is the +number of pixels from the font baseline to the bottom of the font.

+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/freetype.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/freetype.html new file mode 100644 index 0000000..280456f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/freetype.html @@ -0,0 +1,1270 @@ + + + + + + + + + pygame.freetype — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.freetype
+
+
Enhanced pygame module for loading and rendering computer fonts
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Return the latest FreeType error
+—Return the FreeType version
+—Initialize the underlying FreeType library.
+—Shut down the underlying FreeType library.
+—Returns True if the FreeType module is currently initialized.
+—DEPRECATED: Use get_init() instead.
+—Return the glyph case size
+—Return the default pixel size in dots per inch
+—Set the default pixel size in dots per inch for the module
+—create a Font object from the system fonts
+—Get the filename of the default font
+—Create a new Font instance from a supported font file.
+

The pygame.freetype module is a replacement for pygame.fontpygame module for loading and rendering fonts. +It has all of the functionality of the original, plus many new features. +Yet is has absolutely no dependencies on the SDL_ttf library. +It is implemented directly on the FreeType 2 library. +The pygame.freetype module is not itself backward compatible with +pygame.fontpygame module for loading and rendering fonts. +Instead, use the pygame.ftfont module as a drop-in replacement +for pygame.fontpygame module for loading and rendering fonts.

+

All font file formats supported by FreeType can be rendered by +pygame.freetype, namely TTF, Type1, CFF, OpenType, +SFNT, PCF, FNT, BDF, PFR and Type42 fonts. +All glyphs having UTF-32 code points are accessible +(see Font.ucs4).

+

Most work on fonts is done using Font instances. +The module itself only has routines for initialization and creation +of Font objects. +You can load fonts from the system using the SysFont() function.

+

Extra support of bitmap fonts is available. Available bitmap sizes can +be listed (see Font.get_sizes()). For bitmap only fonts Font +can set the size for you (see the Font.size property).

+

For now undefined character codes are replaced with the .notdef +(not defined) character. +How undefined codes are handled may become configurable in a future release.

+

Pygame comes with a built-in default font. This can always be accessed by +passing None as the font name to the Font constructor.

+

Extra rendering features available to pygame.freetype.FontCreate a new Font instance from a supported font file. +are direct to surface rendering (see Font.render_to()), character kerning +(see Font.kerning), vertical layout (see Font.vertical), +rotation of rendered text (see Font.rotation), +and the strong style (see Font.strong). +Some properties are configurable, such as +strong style strength (see Font.strength) and underline positioning +(see Font.underline_adjustment). Text can be positioned by the upper +right corner of the text box or by the text baseline (see Font.origin). +Finally, a font's vertical and horizontal size can be adjusted separately +(see Font.size). +The pygame.examples.freetype_misc +example shows these features in use.

+

The pygame package does not import freetype automatically when +loaded. This module must be imported explicitly to be used.

+
import pygame
+import pygame.freetype
+
+
+
+

New in pygame 1.9.2: freetype

+
+
+
+pygame.freetype.get_error()¶
+
+
Return the latest FreeType error
+
get_error() -> str
+
get_error() -> None
+
+

Return a description of the last error which occurred in the FreeType2 +library, or None if no errors have occurred.

+
+ +
+
+pygame.freetype.get_version()¶
+
+
Return the FreeType version
+
get_version() -> (int, int, int)
+
+

Returns the version of the FreeType library in use by this module.

+

Note that the freetype module depends on the FreeType 2 library. +It will not compile with the original FreeType 1.0. Hence, the first element +of the tuple will always be "2".

+
+ +
+
+pygame.freetype.init()¶
+
+
Initialize the underlying FreeType library.
+
init(cache_size=64, resolution=72) -> None
+
+

This function initializes the underlying FreeType library and must be +called before trying to use any of the functionality of the freetype +module.

+

However, pygame.init()initialize all imported pygame modules will automatically call this function +if the freetype module is already imported. It is safe to call this +function more than once.

+

Optionally, you may specify a default cache_size for the Glyph cache: the +maximum number of glyphs that will be cached at any given time by the +module. Exceedingly small values will be automatically tuned for +performance. Also a default pixel resolution, in dots per inch, can +be given to adjust font scaling.

+
+ +
+
+pygame.freetype.quit()¶
+
+
Shut down the underlying FreeType library.
+
quit() -> None
+
+

This function closes the freetype module. After calling this +function, you should not invoke any class, method or function related to the +freetype module as they are likely to fail or might give unpredictable +results. It is safe to call this function even if the module hasn't been +initialized yet.

+
+ +
+
+pygame.freetype.get_init()¶
+
+
Returns True if the FreeType module is currently initialized.
+
get_init() -> bool
+
+

Returns True if the pygame.freetype module is currently initialized.

+
+

New in pygame 1.9.5.

+
+
+ +
+
+pygame.freetype.was_init()¶
+
+
DEPRECATED: Use get_init() instead.
+
was_init() -> bool
+
+

DEPRECATED: Returns True if the pygame.freetype module is currently +initialized. Use get_init() instead.

+
+ +
+
+pygame.freetype.get_cache_size()¶
+
+
Return the glyph case size
+
get_cache_size() -> long
+
+

See pygame.freetype.init()Initialize the underlying FreeType library..

+
+ +
+
+pygame.freetype.get_default_resolution()¶
+
+
Return the default pixel size in dots per inch
+
get_default_resolution() -> long
+
+

Returns the default pixel size, in dots per inch, for the module. +The default is 72 DPI.

+
+ +
+
+pygame.freetype.set_default_resolution()¶
+
+
Set the default pixel size in dots per inch for the module
+
set_default_resolution([resolution])
+
+

Set the default pixel size, in dots per inch, for the module. If the +optional argument is omitted or zero the resolution is reset to 72 DPI.

+
+ +
+
+pygame.freetype.SysFont()¶
+
+
create a Font object from the system fonts
+
SysFont(name, size, bold=False, italic=False) -> Font
+
+

Return a new Font object that is loaded from the system fonts. The font will +match the requested bold and italic flags. Pygame uses a small set of +common font aliases. If the specific font you ask for is not available, a +reasonable alternative may be used. If a suitable system font is not found +this will fall back on loading the default pygame font.

+

The font name can also be an iterable of font names, a string of +comma-separated font names, or a bytes of comma-separated font names, in +which case the set of names will be searched in order.

+
+

New in pygame 2.0.1: Accept an iterable of font names.

+
+
+ +
+
+pygame.freetype.get_default_font()¶
+
+
Get the filename of the default font
+
get_default_font() -> string
+
+

Return the filename of the default pygame font. This is not the full path +to the file. The file is usually in the same directory as the font module, +but can also be bundled in a separate archive.

+
+ +
+
+pygame.freetype.Font¶
+
+
Create a new Font instance from a supported font file.
+
Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font
+
Font(pathlib.Path) -> Font
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Proper font name.
+—Font file path
+—The default point size used in rendering
+—Return the size and offset of rendered text
+—Return the glyph metrics for the given text
+—The unscaled height of the font in font units
+—The unscaled ascent of the font in font units
+—The unscaled descent of the font in font units
+—The scaled ascent of the font in pixels
+—The scaled descent of the font in pixels
+—The scaled height of the font in pixels
+—The scaled bounding box height of the font in pixels
+—return the available sizes of embedded bitmaps
+—Return rendered text as a surface
+—Render text onto an existing surface
+—Return rendered text as a string of bytes
+—Render text into an array of ints
+—The font's style flags
+—The state of the font's underline style flag
+—The state of the font's strong style flag
+—The state of the font's oblique style flag
+—The state of the font's wide style flag
+—The strength associated with the strong or wide font styles
+—Adjustment factor for the underline position
+—Gets whether the font is fixed-width
+—the number of available bitmap sizes for the font
+—Gets whether the font is scalable
+—allow the use of embedded bitmaps in an outline font file
+—Font anti-aliasing mode
+—Character kerning mode
+—Font vertical mode
+—text rotation in degrees counterclockwise
+—default foreground color
+—default background color
+—Font render to text origin mode
+—padded boundary mode
+—Enable UCS-4 mode
+—Pixel resolution in dots per inch
+

Argument file can be either a string representing the font's filename, a +file-like object containing the font, or None; if None, a default, +Pygame, font is used.

+

Optionally, a size argument may be specified to set the default size in +points, which determines the size of the rendered characters. +The size can also be passed explicitly to each method call. +Because of the way the caching system works, specifying a default size on +the constructor doesn't imply a performance gain over manually passing +the size on each function call. If the font is bitmap and no size +is given, the default size is set to the first available size for the font.

+

If the font file has more than one font, the font to load can be chosen with +the index argument. An exception is raised for an out-of-range font index +value.

+

The optional resolution argument sets the pixel size, in dots per inch, +for use in scaling glyphs for this Font instance. If 0 then the default +module value, set by init(), is used. The Font object's +resolution can only be changed by re-initializing the Font instance.

+

The optional ucs4 argument, an integer, sets the default text translation +mode: 0 (False) recognize UTF-16 surrogate pairs, any other value (True), +to treat Unicode text as UCS-4, with no surrogate pairs. See +Font.ucs4.

+
+
+name¶
+
+
Proper font name.
+
name -> string
+
+

Read only. Returns the real (long) name of the font, as +recorded in the font file.

+
+ +
+
+path¶
+
+
Font file path
+
path -> unicode
+
+

Read only. Returns the path of the loaded font file

+
+ +
+
+size¶
+
+
The default point size used in rendering
+
size -> float
+
size -> (float, float)
+
+

Get or set the default size for text metrics and rendering. It can be +a single point size, given as a Python int or float, or a +font ppem (width, height) tuple. Size values are non-negative. +A zero size or width represents an undefined size. In this case +the size must be given as a method argument, or an exception is +raised. A zero width but non-zero height is a ValueError.

+

For a scalable font, a single number value is equivalent to a tuple +with width equal height. A font can be stretched vertically with +height set greater than width, or horizontally with width set +greater than height. For embedded bitmaps, as listed by get_sizes(), +use the nominal width and height to select an available size.

+

Font size differs for a non-scalable, bitmap, font. During a +method call it must match one of the available sizes returned by +method get_sizes(). If not, an exception is raised. +If the size is a single number, the size is first matched against the +point size value. If no match, then the available size with the +same nominal width and height is chosen.

+
+ +
+
+get_rect()¶
+
+
Return the size and offset of rendered text
+
get_rect(text, style=STYLE_DEFAULT, rotation=0, size=0) -> rect
+
+

Gets the final dimensions and origin, in pixels, of text using the +optional size in points, style, and rotation. For other +relevant render properties, and for any optional argument not given, +the default values set for the Font instance are used.

+

Returns a Rect instance containing the +width and height of the text's bounding box and the position of the +text's origin. +The origin is useful in aligning separately rendered pieces of text. +It gives the baseline position and bearing at the start of the text. +See the render_to() method for an example.

+

If text is a char (byte) string, its encoding is assumed to be +LATIN1.

+

Optionally, text can be None, which will return the bounding +rectangle for the text passed to a previous get_rect(), +render(), render_to(), render_raw(), or +render_raw_to() call. See render_to() for more +details.

+
+ +
+
+get_metrics()¶
+
+
Return the glyph metrics for the given text
+
get_metrics(text, size=0) -> [(...), ...]
+
+

Returns the glyph metrics for each character in text.

+

The glyph metrics are returned as a list of tuples. Each tuple gives +metrics of a single character glyph. The glyph metrics are:

+
(min_x, max_x, min_y, max_y, horizontal_advance_x, horizontal_advance_y)
+
+
+

The bounding box min_x, max_x, min_y, and max_y values are returned as +grid-fitted pixel coordinates of type int. The advance values are +float values.

+

The calculations are done using the font's default size in points. +Optionally you may specify another point size with the size argument.

+

The metrics are adjusted for the current rotation, strong, and oblique +settings.

+

If text is a char (byte) string, then its encoding is assumed to be +LATIN1.

+
+ +
+
+height¶
+
+
The unscaled height of the font in font units
+
height -> int
+
+

Read only. Gets the height of the font. This is the average value of all +glyphs in the font.

+
+ +
+
+ascender¶
+
+
The unscaled ascent of the font in font units
+
ascender -> int
+
+

Read only. Return the number of units from the font's baseline to +the top of the bounding box.

+
+ +
+
+descender¶
+
+
The unscaled descent of the font in font units
+
descender -> int
+
+

Read only. Return the height in font units for the font descent. +The descent is the number of units from the font's baseline to the +bottom of the bounding box.

+
+ +
+
+get_sized_ascender()¶
+
+
The scaled ascent of the font in pixels
+
get_sized_ascender(<size>=0) -> int
+
+

Return the number of units from the font's baseline to the top of the +bounding box. It is not adjusted for strong or rotation.

+
+ +
+
+get_sized_descender()¶
+
+
The scaled descent of the font in pixels
+
get_sized_descender(<size>=0) -> int
+
+

Return the number of pixels from the font's baseline to the top of the +bounding box. It is not adjusted for strong or rotation.

+
+ +
+
+get_sized_height()¶
+
+
The scaled height of the font in pixels
+
get_sized_height(<size>=0) -> int
+
+

Returns the height of the font. This is the average value of all +glyphs in the font. It is not adjusted for strong or rotation.

+
+ +
+
+get_sized_glyph_height()¶
+
+
The scaled bounding box height of the font in pixels
+
get_sized_glyph_height(<size>=0) -> int
+
+

Return the glyph bounding box height of the font in pixels. +This is the average value of all glyphs in the font. +It is not adjusted for strong or rotation.

+
+ +
+
+get_sizes()¶
+
+
return the available sizes of embedded bitmaps
+
get_sizes() -> [(int, int, int, float, float), ...]
+
get_sizes() -> []
+
+

Returns a list of tuple records, one for each point size +supported. Each tuple containing the point size, the height in pixels, +width in pixels, horizontal ppem (nominal width) in fractional pixels, +and vertical ppem (nominal height) in fractional pixels.

+
+ +
+
+render()¶
+
+
Return rendered text as a surface
+
render(text, fgcolor=None, bgcolor=None, style=STYLE_DEFAULT, rotation=0, size=0) -> (Surface, Rect)
+
+

Returns a new Surface, +with the text rendered to it +in the color given by 'fgcolor'. If no foreground color is given, +the default foreground color, fgcolor is used. +If bgcolor is given, the surface +will be filled with this color. When no background color is given, +the surface background is transparent, zero alpha. Normally the returned +surface has a 32 bit pixel size. However, if bgcolor is None +and anti-aliasing is disabled a monochrome 8 bit colorkey surface, +with colorkey set for the background color, is returned.

+

The return value is a tuple: the new surface and the bounding +rectangle giving the size and origin of the rendered text.

+

If an empty string is passed for text then the returned Rect is zero +width and the height of the font.

+

Optional fgcolor, style, rotation, and size arguments override +the default values set for the Font instance.

+

If text is a char (byte) string, then its encoding is assumed to be +LATIN1.

+

Optionally, text can be None, which will render the text +passed to a previous get_rect(), render(), render_to(), +render_raw(), or render_raw_to() call. +See render_to() for details.

+
+ +
+
+render_to()¶
+
+
Render text onto an existing surface
+
render_to(surf, dest, text, fgcolor=None, bgcolor=None, style=STYLE_DEFAULT, rotation=0, size=0) -> Rect
+
+

Renders the string text to the pygame.Surfacepygame object for representing images surf, +at position dest, a (x, y) surface coordinate pair. +If either x or y is not an integer it is converted to one if possible. +Any sequence where the first two items are x and y positional elements +is accepted, including a Rect instance. +As with render(), +optional fgcolor, style, rotation, and size argument are +available.

+

If a background color bgcolor is given, the text bounding box is +first filled with that color. The text is blitted next. +Both the background fill and text rendering involve full alpha blits. +That is, the alpha values of the foreground, background, and destination +target surface all affect the blit.

+

The return value is a rectangle giving the size and position of the +rendered text within the surface.

+

If an empty string is passed for text then the returned +Rect is zero width and the height of the font. +The rect will test False.

+

Optionally, text can be set None, which will re-render text +passed to a previous render_to(), get_rect(), render(), +render_raw(), or render_raw_to() call. Primarily, this +feature is an aid to using render_to() in combination with +get_rect(). An example:

+
def word_wrap(surf, text, font, color=(0, 0, 0)):
+    font.origin = True
+    words = text.split(' ')
+    width, height = surf.get_size()
+    line_spacing = font.get_sized_height() + 2
+    x, y = 0, line_spacing
+    space = font.get_rect(' ')
+    for word in words:
+        bounds = font.get_rect(word)
+        if x + bounds.width + bounds.x >= width:
+            x, y = 0, y + line_spacing
+        if x + bounds.width + bounds.x >= width:
+            raise ValueError("word too wide for the surface")
+        if y + bounds.height - bounds.y >= height:
+            raise ValueError("text to long for the surface")
+        font.render_to(surf, (x, y), None, color)
+        x += bounds.width + space.width
+    return x, y
+
+
+

When render_to() is called with the same +font properties ― size, style, strength, +wide, antialiased, vertical, rotation, +kerning, and use_bitmap_strikes ― as get_rect(), +render_to() will use the layout calculated by get_rect(). +Otherwise, render_to() will recalculate the layout if called +with a text string or one of the above properties has changed +after the get_rect() call.

+

If text is a char (byte) string, then its encoding is assumed to be +LATIN1.

+
+ +
+
+render_raw()¶
+
+
Return rendered text as a string of bytes
+
render_raw(text, style=STYLE_DEFAULT, rotation=0, size=0, invert=False) -> (bytes, (int, int))
+
+

Like render() but with the pixels returned as a byte string +of 8-bit gray-scale values. The foreground color is 255, the +background 0, useful as an alpha mask for a foreground pattern.

+
+ +
+
+render_raw_to()¶
+
+
Render text into an array of ints
+
render_raw_to(array, text, dest=None, style=STYLE_DEFAULT, rotation=0, size=0, invert=False) -> Rect
+
+

Render to an array object exposing an array struct interface. The array +must be two dimensional with integer items. The default dest value, +None, is equivalent to position (0, 0). See render_to(). +As with the other render methods, text can be None to +render a text string passed previously to another method.

+

The return value is a pygame.Rect()pygame object for storing rectangular coordinates giving the size and position of +the rendered text.

+
+ +
+
+style¶
+
+
The font's style flags
+
style -> int
+
+

Gets or sets the default style of the Font. This default style will be +used for all text rendering and size calculations unless overridden +specifically a render or get_rect() call. +The style value may be a bit-wise OR of one or more of the following +constants:

+
STYLE_NORMAL
+STYLE_UNDERLINE
+STYLE_OBLIQUE
+STYLE_STRONG
+STYLE_WIDE
+STYLE_DEFAULT
+
+
+

These constants may be found on the FreeType constants module. +Optionally, the default style can be modified or obtained accessing the +individual style attributes (underline, oblique, strong).

+

The STYLE_OBLIQUE and STYLE_STRONG styles are for +scalable fonts only. An attempt to set either for a bitmap font raises +an AttributeError. An attempt to set either for an inactive font, +as returned by Font.__new__(), raises a RuntimeError.

+

Assigning STYLE_DEFAULT to the style property leaves +the property unchanged, as this property defines the default. +The style property will never return STYLE_DEFAULT.

+
+ +
+
+underline¶
+
+
The state of the font's underline style flag
+
underline -> bool
+
+

Gets or sets whether the font will be underlined when drawing text. This +default style value will be used for all text rendering and size +calculations unless overridden specifically in a render or +get_rect() call, via the 'style' parameter.

+
+ +
+
+strong¶
+
+
The state of the font's strong style flag
+
strong -> bool
+
+

Gets or sets whether the font will be bold when drawing text. This +default style value will be used for all text rendering and size +calculations unless overridden specifically in a render or +get_rect() call, via the 'style' parameter.

+
+ +
+
+oblique¶
+
+
The state of the font's oblique style flag
+
oblique -> bool
+
+

Gets or sets whether the font will be rendered as oblique. This +default style value will be used for all text rendering and size +calculations unless overridden specifically in a render or +get_rect() call, via the style parameter.

+

The oblique style is only supported for scalable (outline) fonts. +An attempt to set this style on a bitmap font will raise an +AttributeError. If the font object is inactive, as returned by +Font.__new__(), setting this property raises a RuntimeError.

+
+ +
+
+wide¶
+
+
The state of the font's wide style flag
+
wide -> bool
+
+

Gets or sets whether the font will be stretched horizontally +when drawing text. It produces a result similar to +pygame.font.Fontcreate a new Font object from a file's bold. This style not available for +rotated text.

+
+ +
+
+strength¶
+
+
The strength associated with the strong or wide font styles
+
strength -> float
+
+

The amount by which a font glyph's size is enlarged for the +strong or wide transformations, as a fraction of the untransformed +size. For the wide style only the horizontal dimension is +increased. For strong text both the horizontal and vertical +dimensions are enlarged. A wide style of strength 0.08333 ( 1/12 ) is +equivalent to the pygame.font.Fontcreate a new Font object from a file bold style. +The default is 0.02778 ( 1/36 ).

+

The strength style is only supported for scalable (outline) fonts. +An attempt to set this property on a bitmap font will raise an +AttributeError. If the font object is inactive, as returned by +Font.__new__(), assignment to this property raises a RuntimeError.

+
+ +
+
+underline_adjustment¶
+
+
Adjustment factor for the underline position
+
underline_adjustment -> float
+
+

Gets or sets a factor which, when positive, is multiplied with the +font's underline offset to adjust the underline position. A negative +value turns an underline into a strike-through or overline. It is +multiplied with the ascender. Accepted values range between -2.0 and 2.0 +inclusive. A value of 0.5 closely matches Tango underlining. A value of +1.0 mimics pygame.font.Fontcreate a new Font object from a file underlining.

+
+ +
+
+fixed_width¶
+
+
Gets whether the font is fixed-width
+
fixed_width -> bool
+
+

Read only. Returns True if the font contains fixed-width +characters (for example Courier, Bitstream Vera Sans Mono, Andale Mono).

+
+ +
+
+fixed_sizes¶
+
+
the number of available bitmap sizes for the font
+
fixed_sizes -> int
+
+

Read only. Returns the number of point sizes for which the font contains +bitmap character images. If zero then the font is not a bitmap font. +A scalable font may contain pre-rendered point sizes as strikes.

+
+ +
+
+scalable¶
+
+
Gets whether the font is scalable
+
scalable -> bool
+
+

Read only. Returns True if the font contains outline glyphs. +If so, the point size is not limited to available bitmap sizes.

+
+ +
+
+use_bitmap_strikes¶
+
+
allow the use of embedded bitmaps in an outline font file
+
use_bitmap_strikes -> bool
+
+

Some scalable fonts include embedded bitmaps for particular point +sizes. This property controls whether or not those bitmap strikes +are used. Set it False to disable the loading of any bitmap +strike. Set it True, the default, to permit bitmap strikes +for a non-rotated render with no style other than wide or +underline. This property is ignored for bitmap fonts.

+

See also fixed_sizes and get_sizes().

+
+ +
+
+antialiased¶
+
+
Font anti-aliasing mode
+
antialiased -> bool
+
+

Gets or sets the font's anti-aliasing mode. This defaults to +True on all fonts, which are rendered with full 8 bit blending.

+

Set to False to do monochrome rendering. This should +provide a small speed gain and reduce cache memory size.

+
+ +
+
+kerning¶
+
+
Character kerning mode
+
kerning -> bool
+
+

Gets or sets the font's kerning mode. This defaults to False +on all fonts, which will be rendered without kerning.

+

Set to True to add kerning between character pairs, if supported +by the font, when positioning glyphs.

+
+ +
+
+vertical¶
+
+
Font vertical mode
+
vertical -> bool
+
+

Gets or sets whether the characters are laid out vertically rather +than horizontally. May be useful when rendering Kanji or some other +vertical script.

+

Set to True to switch to a vertical text layout. The default +is False, place horizontally.

+

Note that the Font class does not automatically determine +script orientation. Vertical layout must be selected explicitly.

+

Also note that several font formats (especially bitmap based ones) don't +contain the necessary metrics to draw glyphs vertically, so drawing in +those cases will give unspecified results.

+
+ +
+
+rotation¶
+
+
text rotation in degrees counterclockwise
+
rotation -> int
+
+

Gets or sets the baseline angle of the rendered text. The angle is +represented as integer degrees. The default angle is 0, with horizontal +text rendered along the X-axis, and vertical text along the Y-axis. +A positive value rotates these axes counterclockwise that many degrees. +A negative angle corresponds to a clockwise rotation. The rotation +value is normalized to a value within the range 0 to 359 inclusive +(eg. 390 -> 390 - 360 -> 30, -45 -> 360 + -45 -> 315, +720 -> 720 - (2 * 360) -> 0).

+

Only scalable (outline) fonts can be rotated. An attempt to change +the rotation of a bitmap font raises an AttributeError. +An attempt to change the rotation of an inactive font instance, as +returned by Font.__new__(), raises a RuntimeError.

+
+ +
+
+fgcolor¶
+
+
default foreground color
+
fgcolor -> Color
+
+

Gets or sets the default glyph rendering color. It is initially opaque +black ― (0, 0, 0, 255). Applies to render() and render_to().

+
+ +
+
+bgcolor¶
+
+
default background color
+
bgcolor -> Color
+
+

Gets or sets the default background rendering color. Initially it is +unset and text will render with a transparent background by default. +Applies to render() and render_to().

+
+ +
+

New in pygame 2.0.0.

+
+
+
+origin¶
+
+
Font render to text origin mode
+
origin -> bool
+
+

If set True, render_to() and render_raw_to() will +take the dest position to be that of the text origin, as opposed to +the top-left corner of the bounding box. See get_rect() for +details.

+
+ +
+
+pad¶
+
+
padded boundary mode
+
pad -> bool
+
+

If set True, then the text boundary rectangle will be inflated +to match that of font.Font. +Otherwise, the boundary rectangle is just large enough for the text.

+
+ +
+
+ucs4¶
+
+
Enable UCS-4 mode
+
ucs4 -> bool
+
+

Gets or sets the decoding of Unicode text. By default, the +freetype module performs UTF-16 surrogate pair decoding on Unicode text. +This allows 32-bit escape sequences ('Uxxxxxxxx') between 0x10000 and +0x10FFFF to represent their corresponding UTF-32 code points on Python +interpreters built with a UCS-2 Unicode type (on Windows, for instance). +It also means character values within the UTF-16 surrogate area (0xD800 +to 0xDFFF) are considered part of a surrogate pair. A malformed surrogate +pair will raise a UnicodeEncodeError. Setting ucs4 True turns +surrogate pair decoding off, allowing access the full UCS-4 character +range to a Python interpreter built with four-byte Unicode character +support.

+
+ +
+
+resolution¶
+
+
Pixel resolution in dots per inch
+
resolution -> int
+
+

Read only. Gets pixel size used in scaling font glyphs for this +Font instance.

+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/gfxdraw.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/gfxdraw.html new file mode 100644 index 0000000..67849f2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/gfxdraw.html @@ -0,0 +1,1058 @@ + + + + + + + + + pygame.gfxdraw — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.gfxdraw
+
+
pygame module for drawing shapes
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—draw a pixel
+—draw a horizontal line
+—draw a vertical line
+—draw a line
+—draw a rectangle
+—draw a filled rectangle
+—draw a circle
+—draw an antialiased circle
+—draw a filled circle
+—draw an ellipse
+—draw an antialiased ellipse
+—draw a filled ellipse
+—draw an arc
+—draw a pie
+—draw a trigon/triangle
+—draw an antialiased trigon/triangle
+—draw a filled trigon/triangle
+—draw a polygon
+—draw an antialiased polygon
+—draw a filled polygon
+—draw a textured polygon
+—draw a Bezier curve
+

EXPERIMENTAL!: This API may change or disappear in later pygame releases. If +you use this, your code may break with the next pygame release.

+

The pygame package does not import gfxdraw automatically when loaded, so it +must imported explicitly to be used.

+
import pygame
+import pygame.gfxdraw
+
+
+

For all functions the arguments are strictly positional and integers are +accepted for coordinates and radii. The color argument can be one of the +following formats:

+
+
+
+

The functions rectangle() and box() will accept any (x, y, w, h) +sequence for their rect argument, though pygame.Rectpygame object for storing rectangular coordinates instances are +preferred.

+

To draw a filled antialiased shape, first use the antialiased (aa*) version +of the function, and then use the filled (filled_*) version. +For example:

+
col = (255, 0, 0)
+surf.fill((255, 255, 255))
+pygame.gfxdraw.aacircle(surf, x, y, 30, col)
+pygame.gfxdraw.filled_circle(surf, x, y, 30, col)
+
+
+
+

Note

+

For threading, each of the functions releases the GIL during the C part of +the call.

+
+
+

Note

+

See the pygame.drawpygame module for drawing shapes module for alternative draw methods. +The pygame.gfxdraw module differs from the pygame.drawpygame module for drawing shapes module in +the API it uses and the different draw functions available. +pygame.gfxdraw wraps the primitives from the library called SDL_gfx, +rather than using modified versions.

+
+
+

New in pygame 1.9.0.

+
+
+
+pygame.gfxdraw.pixel()¶
+
+
draw a pixel
+
pixel(surface, x, y, color) -> None
+
+

Draws a single pixel, at position (x ,y), on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the pixel

  • +
  • y (int) -- y coordinate of the pixel

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.hline()¶
+
+
draw a horizontal line
+
hline(surface, x1, x2, y, color) -> None
+
+

Draws a straight horizontal line ((x1, y) to (x2, y)) on the given +surface. There are no endcaps.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x1 (int) -- x coordinate of one end of the line

  • +
  • x2 (int) -- x coordinate of the other end of the line

  • +
  • y (int) -- y coordinate of the line

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.vline()¶
+
+
draw a vertical line
+
vline(surface, x, y1, y2, color) -> None
+
+

Draws a straight vertical line ((x, y1) to (x, y2)) on the given +surface. There are no endcaps.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the line

  • +
  • y1 (int) -- y coordinate of one end of the line

  • +
  • y2 (int) -- y coordinate of the other end of the line

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.line()¶
+
+
draw a line
+
line(surface, x1, y1, x2, y2, color) -> None
+
+

Draws a straight line ((x1, y1) to (x2, y2)) on the given surface. +There are no endcaps.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x1 (int) -- x coordinate of one end of the line

  • +
  • y1 (int) -- y coordinate of one end of the line

  • +
  • x2 (int) -- x coordinate of the other end of the line

  • +
  • y2 (int) -- y coordinate of the other end of the line

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.rectangle()¶
+
+
draw a rectangle
+
rectangle(surface, rect, color) -> None
+
+

Draws an unfilled rectangle on the given surface. For a filled rectangle use +box().

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • rect (Rect) -- rectangle to draw, position and dimensions

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+

Note

+

The rect.bottom and rect.right attributes of a pygame.Rectpygame object for storing rectangular coordinates +always lie one pixel outside of its actual border. Therefore, these +values will not be included as part of the drawing.

+
+
+ +
+
+pygame.gfxdraw.box()¶
+
+
draw a filled rectangle
+
box(surface, rect, color) -> None
+
+

Draws a filled rectangle on the given surface. For an unfilled rectangle use +rectangle().

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • rect (Rect) -- rectangle to draw, position and dimensions

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+

Note

+

The rect.bottom and rect.right attributes of a pygame.Rectpygame object for storing rectangular coordinates +always lie one pixel outside of its actual border. Therefore, these +values will not be included as part of the drawing.

+
+
+

Note

+

The pygame.Surface.fill()fill Surface with a solid color method works just as well for drawing +filled rectangles. In fact pygame.Surface.fill()fill Surface with a solid color can be hardware +accelerated on some platforms with both software and hardware display +modes.

+
+
+ +
+
+pygame.gfxdraw.circle()¶
+
+
draw a circle
+
circle(surface, x, y, r, color) -> None
+
+

Draws an unfilled circle on the given surface. For a filled circle use +filled_circle().

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the circle

  • +
  • y (int) -- y coordinate of the center of the circle

  • +
  • r (int) -- radius of the circle

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.aacircle()¶
+
+
draw an antialiased circle
+
aacircle(surface, x, y, r, color) -> None
+
+

Draws an unfilled antialiased circle on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the circle

  • +
  • y (int) -- y coordinate of the center of the circle

  • +
  • r (int) -- radius of the circle

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.filled_circle()¶
+
+
draw a filled circle
+
filled_circle(surface, x, y, r, color) -> None
+
+

Draws a filled circle on the given surface. For an unfilled circle use +circle().

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the circle

  • +
  • y (int) -- y coordinate of the center of the circle

  • +
  • r (int) -- radius of the circle

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.ellipse()¶
+
+
draw an ellipse
+
ellipse(surface, x, y, rx, ry, color) -> None
+
+

Draws an unfilled ellipse on the given surface. For a filled ellipse use +filled_ellipse().

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the ellipse

  • +
  • y (int) -- y coordinate of the center of the ellipse

  • +
  • rx (int) -- horizontal radius of the ellipse

  • +
  • ry (int) -- vertical radius of the ellipse

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.aaellipse()¶
+
+
draw an antialiased ellipse
+
aaellipse(surface, x, y, rx, ry, color) -> None
+
+

Draws an unfilled antialiased ellipse on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the ellipse

  • +
  • y (int) -- y coordinate of the center of the ellipse

  • +
  • rx (int) -- horizontal radius of the ellipse

  • +
  • ry (int) -- vertical radius of the ellipse

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.filled_ellipse()¶
+
+
draw a filled ellipse
+
filled_ellipse(surface, x, y, rx, ry, color) -> None
+
+

Draws a filled ellipse on the given surface. For an unfilled ellipse use +ellipse().

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the ellipse

  • +
  • y (int) -- y coordinate of the center of the ellipse

  • +
  • rx (int) -- horizontal radius of the ellipse

  • +
  • ry (int) -- vertical radius of the ellipse

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.arc()¶
+
+
draw an arc
+
arc(surface, x, y, r, start_angle, stop_angle, color) -> None
+
+

Draws an arc on the given surface. For an arc with its endpoints connected +to its center use pie().

+

The two angle arguments are given in degrees and indicate the start and stop +positions of the arc. The arc is drawn in a clockwise direction from the +start_angle to the stop_angle. If start_angle == stop_angle, +nothing will be drawn

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the arc

  • +
  • y (int) -- y coordinate of the center of the arc

  • +
  • r (int) -- radius of the arc

  • +
  • start_angle (int) -- start angle in degrees

  • +
  • stop_angle (int) -- stop angle in degrees

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+

Note

+

This function uses degrees while the pygame.draw.arc()draw an elliptical arc function +uses radians.

+
+
+ +
+
+pygame.gfxdraw.pie()¶
+
+
draw a pie
+
pie(surface, x, y, r, start_angle, stop_angle, color) -> None
+
+

Draws an unfilled pie on the given surface. A pie is an arc() with its +endpoints connected to its center.

+

The two angle arguments are given in degrees and indicate the start and stop +positions of the pie. The pie is drawn in a clockwise direction from the +start_angle to the stop_angle. If start_angle == stop_angle, +a straight line will be drawn from the center position at the given angle, +to a length of the radius.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x (int) -- x coordinate of the center of the pie

  • +
  • y (int) -- y coordinate of the center of the pie

  • +
  • r (int) -- radius of the pie

  • +
  • start_angle (int) -- start angle in degrees

  • +
  • stop_angle (int) -- stop angle in degrees

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.trigon()¶
+
+
draw a trigon/triangle
+
trigon(surface, x1, y1, x2, y2, x3, y3, color) -> None
+
+

Draws an unfilled trigon (triangle) on the given surface. For a filled +trigon use filled_trigon().

+

A trigon can also be drawn using polygon() e.g. +polygon(surface, ((x1, y1), (x2, y2), (x3, y3)), color)

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x1 (int) -- x coordinate of the first corner of the trigon

  • +
  • y1 (int) -- y coordinate of the first corner of the trigon

  • +
  • x2 (int) -- x coordinate of the second corner of the trigon

  • +
  • y2 (int) -- y coordinate of the second corner of the trigon

  • +
  • x3 (int) -- x coordinate of the third corner of the trigon

  • +
  • y3 (int) -- y coordinate of the third corner of the trigon

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.aatrigon()¶
+
+
draw an antialiased trigon/triangle
+
aatrigon(surface, x1, y1, x2, y2, x3, y3, color) -> None
+
+

Draws an unfilled antialiased trigon (triangle) on the given surface.

+

An aatrigon can also be drawn using aapolygon() e.g. +aapolygon(surface, ((x1, y1), (x2, y2), (x3, y3)), color)

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x1 (int) -- x coordinate of the first corner of the trigon

  • +
  • y1 (int) -- y coordinate of the first corner of the trigon

  • +
  • x2 (int) -- x coordinate of the second corner of the trigon

  • +
  • y2 (int) -- y coordinate of the second corner of the trigon

  • +
  • x3 (int) -- x coordinate of the third corner of the trigon

  • +
  • y3 (int) -- y coordinate of the third corner of the trigon

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.filled_trigon()¶
+
+
draw a filled trigon/triangle
+
filled_trigon(surface, x1, y1, x2, y2, x3, y3, color) -> None
+
+

Draws a filled trigon (triangle) on the given surface. For an unfilled +trigon use trigon().

+

A filled_trigon can also be drawn using filled_polygon() e.g. +filled_polygon(surface, ((x1, y1), (x2, y2), (x3, y3)), color)

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • x1 (int) -- x coordinate of the first corner of the trigon

  • +
  • y1 (int) -- y coordinate of the first corner of the trigon

  • +
  • x2 (int) -- x coordinate of the second corner of the trigon

  • +
  • y2 (int) -- y coordinate of the second corner of the trigon

  • +
  • x3 (int) -- x coordinate of the third corner of the trigon

  • +
  • y3 (int) -- y coordinate of the third corner of the trigon

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+pygame.gfxdraw.polygon()¶
+
+
draw a polygon
+
polygon(surface, points, color) -> None
+
+

Draws an unfilled polygon on the given surface. For a filled polygon use +filled_polygon().

+

The adjacent coordinates in the points argument, as well as the first +and last points, will be connected by line segments. +e.g. For the points [(x1, y1), (x2, y2), (x3, y3)] a line segment will +be drawn from (x1, y1) to (x2, y2), from (x2, y2) to +(x3, y3), and from (x3, y3) to (x1, y1).

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 3 or more (x, y) coordinates, where each +coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats (float values +will be truncated)

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
Raises
+
    +
  • ValueError -- if len(points) < 3 (must have at least 3 points)

  • +
  • IndexError -- if len(coordinate) < 2 (each coordinate must have +at least 2 items)

  • +
+
+
+
+ +
+
+pygame.gfxdraw.aapolygon()¶
+
+
draw an antialiased polygon
+
aapolygon(surface, points, color) -> None
+
+

Draws an unfilled antialiased polygon on the given surface.

+

The adjacent coordinates in the points argument, as well as the first +and last points, will be connected by line segments. +e.g. For the points [(x1, y1), (x2, y2), (x3, y3)] a line segment will +be drawn from (x1, y1) to (x2, y2), from (x2, y2) to +(x3, y3), and from (x3, y3) to (x1, y1).

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 3 or more (x, y) coordinates, where each +coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats (float values +will be truncated)

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
Raises
+
    +
  • ValueError -- if len(points) < 3 (must have at least 3 points)

  • +
  • IndexError -- if len(coordinate) < 2 (each coordinate must have +at least 2 items)

  • +
+
+
+
+ +
+
+pygame.gfxdraw.filled_polygon()¶
+
+
draw a filled polygon
+
filled_polygon(surface, points, color) -> None
+
+

Draws a filled polygon on the given surface. For an unfilled polygon use +polygon().

+

The adjacent coordinates in the points argument, as well as the first +and last points, will be connected by line segments. +e.g. For the points [(x1, y1), (x2, y2), (x3, y3)] a line segment will +be drawn from (x1, y1) to (x2, y2), from (x2, y2) to +(x3, y3), and from (x3, y3) to (x1, y1).

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 3 or more (x, y) coordinates, where each +coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats (float values +will be truncated)`

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
Raises
+
    +
  • ValueError -- if len(points) < 3 (must have at least 3 points)

  • +
  • IndexError -- if len(coordinate) < 2 (each coordinate must have +at least 2 items)

  • +
+
+
+
+ +
+
+pygame.gfxdraw.textured_polygon()¶
+
+
draw a textured polygon
+
textured_polygon(surface, points, texture, tx, ty) -> None
+
+

Draws a textured polygon on the given surface. For better performance, the +surface and the texture should have the same format.

+

A per-pixel alpha texture blit to a per-pixel alpha surface will differ from +a pygame.Surface.blit()draw one image onto another blit. Also, a per-pixel alpha texture cannot be +used with an 8-bit per pixel destination.

+

The adjacent coordinates in the points argument, as well as the first +and last points, will be connected by line segments. +e.g. For the points [(x1, y1), (x2, y2), (x3, y3)] a line segment will +be drawn from (x1, y1) to (x2, y2), from (x2, y2) to +(x3, y3), and from (x3, y3) to (x1, y1).

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 3 or more (x, y) coordinates, where each +coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats (float values +will be truncated)

  • +
  • texture (Surface) -- texture to draw on the polygon

  • +
  • tx (int) -- x offset of the texture

  • +
  • ty (int) -- y offset of the texture

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
Raises
+
    +
  • ValueError -- if len(points) < 3 (must have at least 3 points)

  • +
  • IndexError -- if len(coordinate) < 2 (each coordinate must have +at least 2 items)

  • +
+
+
+
+ +
+
+pygame.gfxdraw.bezier()¶
+
+
draw a Bezier curve
+
bezier(surface, points, steps, color) -> None
+
+

Draws a Bézier curve on the given surface.

+
+
Parameters
+
    +
  • surface (Surface) -- surface to draw on

  • +
  • points (tuple(coordinate) or list(coordinate)) -- a sequence of 3 or more (x, y) coordinates used to form a +curve, where each coordinate in the sequence must be a +tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats (float values +will be truncated)

  • +
  • steps (int) -- number of steps for the interpolation, the minimum is 2

  • +
  • color (Color or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a +tuple (RGB[A])

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
Raises
+
    +
  • ValueError -- if steps < 2

  • +
  • ValueError -- if len(points) < 3 (must have at least 3 points)

  • +
  • IndexError -- if len(coordinate) < 2 (each coordinate must have +at least 2 items)

  • +
+
+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/image.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/image.html new file mode 100644 index 0000000..a708379 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/image.html @@ -0,0 +1,459 @@ + + + + + + + + + pygame.image — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.image
+
+
pygame module for image transfer
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—load new image from a file (or file-like object)
+—save an image to file (or file-like object)
+—get version number of the SDL_Image library being used
+—test if extended image formats can be loaded
+—transfer image to string buffer
+—create new Surface from a string buffer
+—create a new Surface that shares data inside a bytes buffer
+—load new BMP image from a file (or file-like object)
+—load an image from a file (or file-like object)
+—save a png/jpg image to file (or file-like object)
+

The image module contains functions for loading and saving pictures, as well as +transferring Surfaces to formats usable by other packages.

+

Note that there is no Image class; an image is loaded as a Surface object. The +Surface class allows manipulation (drawing lines, setting pixels, capturing +regions, etc.).

+

The image module is a required dependency of pygame, but it only optionally +supports any extended file formats. By default it can only load uncompressed +BMP images. When built with full image support, the pygame.image.load() +function can support the following formats.

+
+
    +
  • BMP

  • +
  • GIF (non-animated)

  • +
  • JPEG

  • +
  • LBM (and PBM, PGM, PPM)

  • +
  • PCX

  • +
  • PNG

  • +
  • PNM

  • +
  • SVG (limited support, using Nano SVG)

  • +
  • TGA (uncompressed)

  • +
  • TIFF

  • +
  • WEBP

  • +
  • XPM

  • +
+
+
+

New in pygame 2.0: Loading SVG, WebP, PNM

+
+

Saving images only supports a limited set of formats. You can save to the +following formats.

+
+
    +
  • BMP

  • +
  • JPEG

  • +
  • PNG

  • +
  • TGA

  • +
+
+

JPEG and JPG, as well as TIF and TIFF refer to the same file format

+
+

New in pygame 1.8: Saving PNG and JPEG files.

+
+
+
+pygame.image.load()¶
+
+
load new image from a file (or file-like object)
+
load(filename) -> Surface
+
load(fileobj, namehint="") -> Surface
+
+

Load an image from a file source. You can pass either a filename, a Python +file-like object, or a pathlib.Path.

+

Pygame will automatically determine the image type (e.g., GIF or bitmap) +and create a new Surface object from the data. In some cases it will need to +know the file extension (e.g., GIF images should end in ".gif"). If you +pass a raw file-like object, you may also want to pass the original filename +as the namehint argument.

+

The returned Surface will contain the same color format, colorkey and alpha +transparency as the file it came from. You will often want to call +Surface.convert() with no arguments, to create a copy that will draw +more quickly on the screen.

+

For alpha transparency, like in .png images, use the convert_alpha() +method after loading so that the image has per pixel transparency.

+

pygame may not always be built to support all image formats. At minimum it +will support uncompressed BMP. If pygame.image.get_extended() +returns 'True', you should be able to load most images (including PNG, JPG +and GIF).

+

You should use os.path.join() for compatibility.

+
eg. asurf = pygame.image.load(os.path.join('data', 'bla.png'))
+
+
+
+ +
+
+pygame.image.save()¶
+
+
save an image to file (or file-like object)
+
save(Surface, filename) -> None
+
save(Surface, fileobj, namehint="") -> None
+
+

This will save your Surface as either a BMP, TGA, PNG, or +JPEG image. If the filename extension is unrecognized it will default to +TGA. Both TGA, and BMP file formats create uncompressed files. +You can pass a filename, a pathlib.Path or a Python file-like object. +For file-like object, the image is saved to TGA format unless +a namehint with a recognizable extension is passed in.

+
+

Note

+

When saving to a file-like object, it seems that for most formats, +the object needs to be flushed after saving to it to make loading +from it possible.

+
+
+

Changed in pygame 1.8: Saving PNG and JPEG files.

+
+
+

Changed in pygame 2.0.0: The namehint parameter was added to make it possible +to save other formats than TGA to a file-like object. +Saving to a file-like object with JPEG is possible.

+
+
+ +
+
+pygame.image.get_sdl_image_version()¶
+
+
get version number of the SDL_Image library being used
+
get_sdl_image_version() -> None
+
get_sdl_image_version() -> (major, minor, patch)
+
+

If pygame is built with extended image formats, then this function will +return the SDL_Image library's version number as a tuple of 3 integers +(major, minor, patch). If not, then it will return None.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.image.get_extended()¶
+
+
test if extended image formats can be loaded
+
get_extended() -> bool
+
+

If pygame is built with extended image formats this function will return +True. It is still not possible to determine which formats will be available, +but generally you will be able to load them all.

+
+ +
+
+pygame.image.tostring()¶
+
+
transfer image to string buffer
+
tostring(Surface, format, flipped=False) -> string
+
+

Creates a string that can be transferred with the 'fromstring' method in +other Python imaging packages. Some Python image packages prefer their +images in bottom-to-top format (PyOpenGL for example). If you pass True for +the flipped argument, the string buffer will be vertically flipped.

+

The format argument is a string of one of the following values. Note that +only 8-bit Surfaces can use the "P" format. The other formats will work for +any Surface. Also note that other Python image packages support more formats +than pygame.

+
+
    +
  • P, 8-bit palettized Surfaces

  • +
  • RGB, 24-bit image

  • +
  • RGBX, 32-bit image with unused space

  • +
  • RGBA, 32-bit image with an alpha channel

  • +
  • ARGB, 32-bit image with alpha channel first

  • +
  • RGBA_PREMULT, 32-bit image with colors scaled by alpha channel

  • +
  • ARGB_PREMULT, 32-bit image with colors scaled by alpha channel, alpha channel first

  • +
+
+
+ +
+
+pygame.image.fromstring()¶
+
+
create new Surface from a string buffer
+
fromstring(string, size, format, flipped=False) -> Surface
+
+

This function takes arguments similar to pygame.image.tostring(). The +size argument is a pair of numbers representing the width and height. Once +the new Surface is created you can destroy the string buffer.

+

The size and format image must compute the exact same size as the passed +string buffer. Otherwise an exception will be raised.

+

See the pygame.image.frombuffer() method for a potentially faster way to +transfer images into pygame.

+
+ +
+
+pygame.image.frombuffer()¶
+
+
create a new Surface that shares data inside a bytes buffer
+
frombuffer(bytes, size, format) -> Surface
+
+

Create a new Surface that shares pixel data directly from a bytes buffer. +This method takes similar arguments to pygame.image.fromstring(), but +is unable to vertically flip the source data.

+

This will run much faster than pygame.image.fromstring()create new Surface from a string buffer, since no +pixel data must be allocated and copied.

+

It accepts the following 'format' arguments:

+
+
    +
  • P, 8-bit palettized Surfaces

  • +
  • RGB, 24-bit image

  • +
  • BGR, 24-bit image, red and blue channels swapped.

  • +
  • RGBX, 32-bit image with unused space

  • +
  • RGBA, 32-bit image with an alpha channel

  • +
  • ARGB, 32-bit image with alpha channel first

  • +
+
+
+ +
+
+pygame.image.load_basic()¶
+
+
load new BMP image from a file (or file-like object)
+
load_basic(file) -> Surface
+
+

Load an image from a file source. You can pass either a filename or a Python +file-like object, or a pathlib.Path.

+

This function only supports loading "basic" image format, ie BMP +format. +This function is always available, no matter how pygame was built.

+
+ +
+
+pygame.image.load_extended()¶
+
+
load an image from a file (or file-like object)
+
load_extended(filename) -> Surface
+
load_extended(fileobj, namehint="") -> Surface
+
+

This function is similar to pygame.image.load(), except that this +function can only be used if pygame was built with extended image format +support.

+

From version 2.0.1, this function is always available, but raises an +error if extended image formats are not supported. Previously, this +function may or may not be available, depending on the state of +extended image format support.

+
+

Changed in pygame 2.0.1.

+
+
+ +
+
+pygame.image.save_extended()¶
+
+
save a png/jpg image to file (or file-like object)
+
save_extended(Surface, filename) -> None
+
save_extended(Surface, fileobj, namehint="") -> None
+
+

This will save your Surface as either a PNG or JPEG image.

+

Incase the image is being saved to a file-like object, this function +uses the namehint argument to determine the format of the file being +saved. Saves to JPEG incase the namehint was not specified while +saving to file-like object.

+
+

Changed in pygame 2.0.1: This function is always available, but raises an +error if extended image formats are not supported. +Previously, this function may or may not be +available, depending on the state of extended image +format support.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/joystick.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/joystick.html new file mode 100644 index 0000000..03ffd08 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/joystick.html @@ -0,0 +1,984 @@ + + + + + + + + + pygame.joystick — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.joystick
+
+
Pygame module for interacting with joysticks, gamepads, and trackballs.
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + +
+—Initialize the joystick module.
+—Uninitialize the joystick module.
+—Returns True if the joystick module is initialized.
+—Returns the number of joysticks.
+—Create a new Joystick object.
+

The joystick module manages the joystick devices on a computer. +Joystick devices include trackballs and video-game-style +gamepads, and the module allows the use of multiple buttons and "hats". +Computers may manage multiple joysticks at a time.

+

Each instance of the Joystick class represents one gaming device plugged +into the computer. If a gaming pad has multiple joysticks on it, then the +joystick object can actually represent multiple joysticks on that single +game device.

+

For a quick way to initialise the joystick module and get a list of Joystick instances +use the following code:

+
pygame.joystick.init()
+joysticks = [pygame.joystick.Joystick(x) for x in range(pygame.joystick.get_count())]
+
+
+

The following event types will be generated by the joysticks

+
JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION
+
+
+

And in pygame 2, which supports hotplugging:

+
JOYDEVICEADDED JOYDEVICEREMOVED
+
+
+

Note that in pygame 2, joysticks events use a unique "instance ID". The device index +passed in the constructor to a Joystick object is not unique after devices have +been added and removed. You must call Joystick.get_instance_id() to find +the instance ID that was assigned to a Joystick on opening.

+

The event queue needs to be pumped frequently for some of the methods to work. +So call one of pygame.event.get, pygame.event.wait, or pygame.event.pump regularly.

+
+
+pygame.joystick.init()¶
+
+
Initialize the joystick module.
+
init() -> None
+
+

This function is called automatically by pygame.init().

+

It initializes the joystick module. The module must be initialized before any +other functions will work.

+

It is safe to call this function more than once.

+
+ +
+
+pygame.joystick.quit()¶
+
+
Uninitialize the joystick module.
+
quit() -> None
+
+

Uninitialize the joystick module. After you call this any existing joystick +objects will no longer work.

+

It is safe to call this function more than once.

+
+ +
+
+pygame.joystick.get_init()¶
+
+
Returns True if the joystick module is initialized.
+
get_init() -> bool
+
+

Test if the pygame.joystick.init() function has been called.

+
+ +
+
+pygame.joystick.get_count()¶
+
+
Returns the number of joysticks.
+
get_count() -> count
+
+

Return the number of joystick devices on the system. The count will be 0 +if there are no joysticks on the system.

+

When you create Joystick objects using Joystick(id), you pass an integer +that must be lower than this count.

+
+ +
+
+pygame.joystick.Joystick¶
+
+
Create a new Joystick object.
+
Joystick(id) -> Joystick
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize the Joystick
+—uninitialize the Joystick
+—check if the Joystick is initialized
+—get the device index (deprecated)
+—get the joystick instance id
+—get the joystick GUID
+—get the approximate power status of the device
+—get the Joystick system name
+—get the number of axes on a Joystick
+—get the current position of an axis
+—get the number of trackballs on a Joystick
+—get the relative position of a trackball
+—get the number of buttons on a Joystick
+—get the current button state
+—get the number of hat controls on a Joystick
+—get the position of a joystick hat
+—Start a rumbling effect
+—Stop any rumble effect playing
+

Create a new joystick to access a physical device. The id argument must be a +value from 0 to pygame.joystick.get_count() - 1.

+

Joysticks are initialised on creation and are shut down when deallocated. +Once the device is initialized the pygame event queue will start receiving +events about its input.

+
+

Changed in pygame 2.0.0: Joystick objects are now opened immediately on creation.

+
+
+
+init()¶
+
+
initialize the Joystick
+
init() -> None
+
+

Initialize the joystick, if it has been closed. It is safe to call this +even if the joystick is already initialized.

+
+

Deprecated since pygame 2.0.0: In future it will not be possible to reinitialise a closed Joystick +object. Will be removed in Pygame 2.1.

+
+
+ +
+
+quit()¶
+
+
uninitialize the Joystick
+
quit() -> None
+
+

Close a Joystick object. After this the pygame event queue will no longer +receive events from the device.

+

It is safe to call this more than once.

+
+ +
+
+get_init()¶
+
+
check if the Joystick is initialized
+
get_init() -> bool
+
+

Return True if the Joystick object is currently initialised.

+
+ +
+
+get_id()¶
+
+
get the device index (deprecated)
+
get_id() -> int
+
+

Returns the original device index for this device. This is the same +value that was passed to the Joystick() constructor. This method can +safely be called while the Joystick is not initialized.

+
+

Deprecated since pygame 2.0.0: The original device index is not useful in pygame 2. Use +get_instance_id() instead. Will be removed in Pygame 2.1.

+
+
+ +
+
+get_instance_id() int¶
+
+
get the joystick instance id
+
get_instance_id() -> int
+
+

Get the joystick instance ID. This matches the instance_id field +that is given in joystick events.

+
+

New in pygame 2.0.0dev11.

+
+
+ +
+
+get_guid() str¶
+
+
get the joystick GUID
+
get_guid() -> str
+
+

Get the GUID string. This identifies the exact hardware of the joystick +device.

+
+

New in pygame 2.0.0dev11.

+
+
+ +
+
+get_power_level() str¶
+
+
get the approximate power status of the device
+
get_power_level() -> str
+
+

Get a string giving the power status of the device.

+

One of: empty, low, medium, full, wired, max, or +unknown.

+
+

New in pygame 2.0.0dev11.

+
+
+ +
+
+get_name()¶
+
+
get the Joystick system name
+
get_name() -> string
+
+

Returns the system name for this joystick device. It is unknown what name +the system will give to the Joystick, but it should be a unique name that +identifies the device. This method can safely be called while the +Joystick is not initialized.

+
+ +
+
+get_numaxes()¶
+
+
get the number of axes on a Joystick
+
get_numaxes() -> int
+
+

Returns the number of input axes are on a Joystick. There will usually be +two for the position. Controls like rudders and throttles are treated as +additional axes.

+

The pygame.JOYAXISMOTION events will be in the range from -1.0 +to 1.0. A value of 0.0 means the axis is centered. Gamepad devices +will usually be -1, 0, or 1 with no values in between. Older +analog joystick axes will not always use the full -1 to 1 range, +and the centered value will be some area around 0.

+

Analog joysticks usually have a bit of noise in their axis, which will +generate a lot of rapid small motion events.

+
+ +
+
+get_axis()¶
+
+
get the current position of an axis
+
get_axis(axis_number) -> float
+
+

Returns the current position of a joystick axis. The value will range +from -1 to 1 with a value of 0 being centered. You may want +to take into account some tolerance to handle jitter, and joystick drift +may keep the joystick from centering at 0 or using the full range of +position values.

+

The axis number must be an integer from 0 to get_numaxes() - 1.

+

When using gamepads both the control sticks and the analog triggers are +usually reported as axes.

+
+ +
+
+get_numballs()¶
+
+
get the number of trackballs on a Joystick
+
get_numballs() -> int
+
+

Returns the number of trackball devices on a Joystick. These devices work +similar to a mouse but they have no absolute position; they only have +relative amounts of movement.

+

The pygame.JOYBALLMOTION event will be sent when the trackball is +rolled. It will report the amount of movement on the trackball.

+
+ +
+
+get_ball()¶
+
+
get the relative position of a trackball
+
get_ball(ball_number) -> x, y
+
+

Returns the relative movement of a joystick button. The value is a x, y +pair holding the relative movement since the last call to get_ball.

+

The ball number must be an integer from 0 to get_numballs() - 1.

+
+ +
+
+get_numbuttons()¶
+
+
get the number of buttons on a Joystick
+
get_numbuttons() -> int
+
+

Returns the number of pushable buttons on the joystick. These buttons +have a boolean (on or off) state.

+

Buttons generate a pygame.JOYBUTTONDOWN and pygame.JOYBUTTONUP +event when they are pressed and released.

+
+ +
+
+get_button()¶
+
+
get the current button state
+
get_button(button) -> bool
+
+

Returns the current state of a joystick button.

+
+ +
+
+get_numhats()¶
+
+
get the number of hat controls on a Joystick
+
get_numhats() -> int
+
+

Returns the number of joystick hats on a Joystick. Hat devices are like +miniature digital joysticks on a joystick. Each hat has two axes of +input.

+

The pygame.JOYHATMOTION event is generated when the hat changes +position. The position attribute for the event contains a pair of +values that are either -1, 0, or 1. A position of (0, 0) +means the hat is centered.

+
+ +
+
+get_hat()¶
+
+
get the position of a joystick hat
+
get_hat(hat_number) -> x, y
+
+

Returns the current position of a position hat. The position is given as +two values representing the x and y position for the hat. (0, 0) +means centered. A value of -1 means left/down and a value of 1 means +right/up: so (-1, 0) means left; (1, 0) means right; (0, 1) means +up; (1, 1) means upper-right; etc.

+

This value is digital, i.e., each coordinate can be -1, 0 or 1 +but never in-between.

+

The hat number must be between 0 and get_numhats() - 1.

+
+ +
+
+rumble()¶
+
+
Start a rumbling effect
+
rumble(low_frequency, high_frequency, duration) -> bool
+
+

Start a rumble effect on the joystick, with the specified strength ranging +from 0 to 1. Duration is length of the effect, in ms. Setting the duration +to 0 will play the effect until another one overwrites it or +Joystick.stop_rumble() is called. If an effect is already +playing, then it will be overwritten.

+

Returns True if the rumble was played successfully or False if the +joystick does not support it or pygame.version.SDL()tupled integers of the SDL library version is below 2.0.9.

+
+

New in pygame 2.0.2.

+
+
+ +
+
+stop_rumble()¶
+
+
Stop any rumble effect playing
+
stop_rumble() -> None
+
+

Stops any rumble effect playing on the joystick. See +Joystick.rumble() for more information.

+
+

New in pygame 2.0.2.

+
+
+ +
+ +
+joystick module example +
+

Example code for joystick module.¶

+
+
+
import pygame
+
+
+# Define some colors.
+BLACK = pygame.Color('black')
+WHITE = pygame.Color('white')
+
+
+# This is a simple class that will help us print to the screen.
+# It has nothing to do with the joysticks, just outputting the
+# information.
+class TextPrint(object):
+    def __init__(self):
+        self.reset()
+        self.font = pygame.font.Font(None, 20)
+
+    def tprint(self, screen, textString):
+        textBitmap = self.font.render(textString, True, BLACK)
+        screen.blit(textBitmap, (self.x, self.y))
+        self.y += self.line_height
+
+    def reset(self):
+        self.x = 10
+        self.y = 10
+        self.line_height = 15
+
+    def indent(self):
+        self.x += 10
+
+    def unindent(self):
+        self.x -= 10
+
+
+pygame.init()
+
+# Set the width and height of the screen (width, height).
+screen = pygame.display.set_mode((500, 700))
+
+pygame.display.set_caption("My Game")
+
+# Loop until the user clicks the close button.
+done = False
+
+# Used to manage how fast the screen updates.
+clock = pygame.time.Clock()
+
+# Initialize the joysticks.
+pygame.joystick.init()
+
+# Get ready to print.
+textPrint = TextPrint()
+
+# -------- Main Program Loop -----------
+while not done:
+    #
+    # EVENT PROCESSING STEP
+    #
+    # Possible joystick actions: JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN,
+    # JOYBUTTONUP, JOYHATMOTION
+    for event in pygame.event.get(): # User did something.
+        if event.type == pygame.QUIT: # If user clicked close.
+            done = True # Flag that we are done so we exit this loop.
+        elif event.type == pygame.JOYBUTTONDOWN:
+            print("Joystick button pressed.")
+        elif event.type == pygame.JOYBUTTONUP:
+            print("Joystick button released.")
+
+    #
+    # DRAWING STEP
+    #
+    # First, clear the screen to white. Don't put other drawing commands
+    # above this, or they will be erased with this command.
+    screen.fill(WHITE)
+    textPrint.reset()
+
+    # Get count of joysticks.
+    joystick_count = pygame.joystick.get_count()
+
+    textPrint.tprint(screen, "Number of joysticks: {}".format(joystick_count))
+    textPrint.indent()
+
+    # For each joystick:
+    for i in range(joystick_count):
+        joystick = pygame.joystick.Joystick(i)
+        joystick.init()
+
+        try:
+            jid = joystick.get_instance_id()
+        except AttributeError:
+            # get_instance_id() is an SDL2 method
+            jid = joystick.get_id()
+        textPrint.tprint(screen, "Joystick {}".format(jid))
+        textPrint.indent()
+
+        # Get the name from the OS for the controller/joystick.
+        name = joystick.get_name()
+        textPrint.tprint(screen, "Joystick name: {}".format(name))
+
+        try:
+            guid = joystick.get_guid()
+        except AttributeError:
+            # get_guid() is an SDL2 method
+            pass
+        else:
+            textPrint.tprint(screen, "GUID: {}".format(guid))
+
+        # Usually axis run in pairs, up/down for one, and left/right for
+        # the other.
+        axes = joystick.get_numaxes()
+        textPrint.tprint(screen, "Number of axes: {}".format(axes))
+        textPrint.indent()
+
+        for i in range(axes):
+            axis = joystick.get_axis(i)
+            textPrint.tprint(screen, "Axis {} value: {:>6.3f}".format(i, axis))
+        textPrint.unindent()
+
+        buttons = joystick.get_numbuttons()
+        textPrint.tprint(screen, "Number of buttons: {}".format(buttons))
+        textPrint.indent()
+
+        for i in range(buttons):
+            button = joystick.get_button(i)
+            textPrint.tprint(screen,
+                             "Button {:>2} value: {}".format(i, button))
+        textPrint.unindent()
+
+        hats = joystick.get_numhats()
+        textPrint.tprint(screen, "Number of hats: {}".format(hats))
+        textPrint.indent()
+
+        # Hat position. All or nothing for direction, not a float like
+        # get_axis(). Position is a tuple of int values (x, y).
+        for i in range(hats):
+            hat = joystick.get_hat(i)
+            textPrint.tprint(screen, "Hat {} value: {}".format(i, str(hat)))
+        textPrint.unindent()
+
+        textPrint.unindent()
+
+    #
+    # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
+    #
+
+    # Go ahead and update the screen with what we've drawn.
+    pygame.display.flip()
+
+    # Limit to 20 frames per second.
+    clock.tick(20)
+
+# Close the window and quit.
+# If you forget this line, the program will 'hang'
+# on exit if running from IDLE.
+pygame.quit()
+
+
+
+

Common Controller Axis Mappings

+

Controller mappings are drawn from the underlying SDL library which pygame uses and they differ +between pygame 1 and pygame 2. Below are a couple of mappings for two popular game pads.

+

Pygame 2

+

Axis and hat mappings are listed from -1 to +1.

+

X-Box 360 Controller (name: "Xbox 360 Controller")

+

In pygame 2 the X360 controller mapping has 6 Axes, 11 buttons and 1 hat.

+
    +
  • Left Stick:

    +
    Left -> Right   - Axis 0
    +Up   -> Down    - Axis 1
    +
    +
    +
  • +
  • Right Stick:

    +
    Left -> Right   - Axis 3
    +Up   -> Down    - Axis 4
    +
    +
    +
  • +
  • Left Trigger:

    +
    Out -> In       - Axis 2
    +
    +
    +
  • +
  • Right Trigger:

    +
    Out -> In       - Axis 5
    +
    +
    +
  • +
  • Buttons:

    +
    A Button        - Button 0
    +B Button        - Button 1
    +X Button        - Button 2
    +Y Button        - Button 3
    +Left Bumper     - Button 4
    +Right Bumper    - Button 5
    +Back Button     - Button 6
    +Start Button    - Button 7
    +L. Stick In     - Button 8
    +R. Stick In     - Button 9
    +Guide Button    - Button 10
    +
    +
    +
  • +
  • Hat/D-pad:

    +
    Down -> Up      - Y Axis
    +Left -> Right   - X Axis
    +
    +
    +
  • +
+

Playstation 4 Controller (name: "PS4 Controller")

+

In pygame 2 the PS4 controller mapping has 6 Axes and 16 buttons.

+
    +
  • Left Stick:

    +
    Left -> Right   - Axis 0
    +Up   -> Down    - Axis 1
    +
    +
    +
  • +
  • Right Stick:

    +
    Left -> Right   - Axis 2
    +Up   -> Down    - Axis 3
    +
    +
    +
  • +
  • Left Trigger:

    +
    Out -> In       - Axis 4
    +
    +
    +
  • +
  • Right Trigger:

    +
    Out -> In       - Axis 5
    +
    +
    +
  • +
  • Buttons:

    +
    Cross Button    - Button 0
    +Circle Button   - Button 1
    +Square Button   - Button 2
    +Triangle Button - Button 3
    +Share Button    - Button 4
    +PS Button       - Button 5
    +Options Button  - Button 6
    +L. Stick In     - Button 7
    +R. Stick In     - Button 8
    +Left Bumper     - Button 9
    +Right Bumper    - Button 10
    +D-pad Up        - Button 11
    +D-pad Down      - Button 12
    +D-pad Left      - Button 13
    +D-pad Right     - Button 14
    +Touch Pad Click - Button 15
    +
    +
    +
  • +
+

Pygame 1

+

Axis and hat mappings are listed from -1 to +1.

+

X-Box 360 Controller (name: "Controller (XBOX 360 For Windows)")

+

In pygame 1 the X360 controller mapping has 5 Axes, 10 buttons and 1 hat.

+
    +
  • Left Stick:

    +
    Left -> Right   - Axis 0
    +Up   -> Down    - Axis 1
    +
    +
    +
  • +
  • Right Stick:

    +
    Left -> Right   - Axis 4
    +Up   -> Down    - Axis 3
    +
    +
    +
  • +
  • Left Trigger & Right Trigger:

    +
    RT -> LT        - Axis 2
    +
    +
    +
  • +
  • Buttons:

    +
    A Button        - Button 0
    +B Button        - Button 1
    +X Button        - Button 2
    +Y Button        - Button 3
    +Left Bumper     - Button 4
    +Right Bumper    - Button 5
    +Back Button     - Button 6
    +Start Button    - Button 7
    +L. Stick In     - Button 8
    +R. Stick In     - Button 9
    +
    +
    +
  • +
  • Hat/D-pad:

    +
    Down -> Up      - Y Axis
    +Left -> Right   - X Axis
    +
    +
    +
  • +
+

Playstation 4 Controller (name: "Wireless Controller")

+

In pygame 1 the PS4 controller mapping has 6 Axes and 14 buttons and 1 hat.

+
    +
  • Left Stick:

    +
    Left -> Right   - Axis 0
    +Up   -> Down    - Axis 1
    +
    +
    +
  • +
  • Right Stick:

    +
    Left -> Right   - Axis 2
    +Up   -> Down    - Axis 3
    +
    +
    +
  • +
  • Left Trigger:

    +
    Out -> In       - Axis 5
    +
    +
    +
  • +
  • Right Trigger:

    +
    Out -> In       - Axis 4
    +
    +
    +
  • +
  • Buttons:

    +
    Cross Button    - Button 0
    +Circle Button   - Button 1
    +Square Button   - Button 2
    +Triangle Button - Button 3
    +Left Bumper     - Button 4
    +Right Bumper    - Button 5
    +L. Trigger(Full)- Button 6
    +R. Trigger(Full)- Button 7
    +Share Button    - Button 8
    +Options Button  - Button 9
    +L. Stick In     - Button 10
    +R. Stick In     - Button 11
    +PS Button       - Button 12
    +Touch Pad Click - Button 13
    +
    +
    +
  • +
  • Hat/D-pad:

    +
    Down -> Up      - Y Axis
    +Left -> Right   - X Axis
    +
    +
    +
  • +
+
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/key.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/key.html new file mode 100644 index 0000000..26cff91 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/key.html @@ -0,0 +1,628 @@ + + + + + + + + + pygame.key — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.key
+
+
pygame module to work with the keyboard
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—true if the display is receiving keyboard input from the system
+—get the state of all keyboard buttons
+—determine which modifier keys are being held
+—temporarily set which modifier keys are pressed
+—control how held keys are repeated
+—see how held keys are repeated
+—get the name of a key identifier
+—get the key identifier from a key name
+—start handling Unicode text input events
+—stop handling Unicode text input events
+—controls the position of the candidate list
+

This module contains functions for dealing with the keyboard.

+

The pygame.eventpygame module for interacting with events and queues queue gets pygame.KEYDOWN and pygame.KEYUP +events when the keyboard buttons are pressed and released. Both events have +key and mod attributes.

+
+
    +
  • key: an integer ID representing every key +on the keyboard

  • +
  • mod: a bitmask of all the modifier keys +that were in a pressed state when the event occurred

  • +
+
+

The pygame.KEYDOWN event has the additional attributes unicode and +scancode.

+
+
    +
  • unicode: a single character string that is the fully translated +character entered, this takes into account the shift and composition keys

  • +
  • scancode: the platform-specific key code, which could be different from +keyboard to keyboard, but is useful for key selection of weird keys like +the multimedia keys

  • +
+
+
+

New in pygame 2.0.0: The pygame.TEXTINPUT event is preferred to the unicode attribute +of pygame.KEYDOWN. The attribute text contains the input.

+
+

The following is a list of all the constants (from pygame.localspygame constants) used to +represent keyboard keys.

+

Portability note: The integers for key constants differ between pygame 1 and 2. +Always use key constants (K_a) rather than integers directly (97) so +that your key handling code works well on both pygame 1 and pygame 2.

+
pygame
+Constant      ASCII   Description
+---------------------------------
+K_BACKSPACE   \b      backspace
+K_TAB         \t      tab
+K_CLEAR               clear
+K_RETURN      \r      return
+K_PAUSE               pause
+K_ESCAPE      ^[      escape
+K_SPACE               space
+K_EXCLAIM     !       exclaim
+K_QUOTEDBL    "       quotedbl
+K_HASH        #       hash
+K_DOLLAR      $       dollar
+K_AMPERSAND   &       ampersand
+K_QUOTE               quote
+K_LEFTPAREN   (       left parenthesis
+K_RIGHTPAREN  )       right parenthesis
+K_ASTERISK    *       asterisk
+K_PLUS        +       plus sign
+K_COMMA       ,       comma
+K_MINUS       -       minus sign
+K_PERIOD      .       period
+K_SLASH       /       forward slash
+K_0           0       0
+K_1           1       1
+K_2           2       2
+K_3           3       3
+K_4           4       4
+K_5           5       5
+K_6           6       6
+K_7           7       7
+K_8           8       8
+K_9           9       9
+K_COLON       :       colon
+K_SEMICOLON   ;       semicolon
+K_LESS        <       less-than sign
+K_EQUALS      =       equals sign
+K_GREATER     >       greater-than sign
+K_QUESTION    ?       question mark
+K_AT          @       at
+K_LEFTBRACKET [       left bracket
+K_BACKSLASH   \       backslash
+K_RIGHTBRACKET ]      right bracket
+K_CARET       ^       caret
+K_UNDERSCORE  _       underscore
+K_BACKQUOTE   `       grave
+K_a           a       a
+K_b           b       b
+K_c           c       c
+K_d           d       d
+K_e           e       e
+K_f           f       f
+K_g           g       g
+K_h           h       h
+K_i           i       i
+K_j           j       j
+K_k           k       k
+K_l           l       l
+K_m           m       m
+K_n           n       n
+K_o           o       o
+K_p           p       p
+K_q           q       q
+K_r           r       r
+K_s           s       s
+K_t           t       t
+K_u           u       u
+K_v           v       v
+K_w           w       w
+K_x           x       x
+K_y           y       y
+K_z           z       z
+K_DELETE              delete
+K_KP0                 keypad 0
+K_KP1                 keypad 1
+K_KP2                 keypad 2
+K_KP3                 keypad 3
+K_KP4                 keypad 4
+K_KP5                 keypad 5
+K_KP6                 keypad 6
+K_KP7                 keypad 7
+K_KP8                 keypad 8
+K_KP9                 keypad 9
+K_KP_PERIOD   .       keypad period
+K_KP_DIVIDE   /       keypad divide
+K_KP_MULTIPLY *       keypad multiply
+K_KP_MINUS    -       keypad minus
+K_KP_PLUS     +       keypad plus
+K_KP_ENTER    \r      keypad enter
+K_KP_EQUALS   =       keypad equals
+K_UP                  up arrow
+K_DOWN                down arrow
+K_RIGHT               right arrow
+K_LEFT                left arrow
+K_INSERT              insert
+K_HOME                home
+K_END                 end
+K_PAGEUP              page up
+K_PAGEDOWN            page down
+K_F1                  F1
+K_F2                  F2
+K_F3                  F3
+K_F4                  F4
+K_F5                  F5
+K_F6                  F6
+K_F7                  F7
+K_F8                  F8
+K_F9                  F9
+K_F10                 F10
+K_F11                 F11
+K_F12                 F12
+K_F13                 F13
+K_F14                 F14
+K_F15                 F15
+K_NUMLOCK             numlock
+K_CAPSLOCK            capslock
+K_SCROLLOCK           scrollock
+K_RSHIFT              right shift
+K_LSHIFT              left shift
+K_RCTRL               right control
+K_LCTRL               left control
+K_RALT                right alt
+K_LALT                left alt
+K_RMETA               right meta
+K_LMETA               left meta
+K_LSUPER              left Windows key
+K_RSUPER              right Windows key
+K_MODE                mode shift
+K_HELP                help
+K_PRINT               print screen
+K_SYSREQ              sysrq
+K_BREAK               break
+K_MENU                menu
+K_POWER               power
+K_EURO                Euro
+K_AC_BACK             Android back button
+
+
+

The keyboard also has a list of modifier states (from pygame.localspygame constants) that +can be assembled by bitwise-ORing them together.

+
pygame
+Constant      Description
+-------------------------
+KMOD_NONE     no modifier keys pressed
+KMOD_LSHIFT   left shift
+KMOD_RSHIFT   right shift
+KMOD_SHIFT    left shift or right shift or both
+KMOD_LCTRL    left control
+KMOD_RCTRL    right control
+KMOD_CTRL     left control or right control or both
+KMOD_LALT     left alt
+KMOD_RALT     right alt
+KMOD_ALT      left alt or right alt or both
+KMOD_LMETA    left meta
+KMOD_RMETA    right meta
+KMOD_META     left meta or right meta or both
+KMOD_CAPS     caps lock
+KMOD_NUM      num lock
+KMOD_MODE     AltGr
+
+
+

The modifier information is contained in the mod attribute of the +pygame.KEYDOWN and pygame.KEYUP events. The mod attribute is a +bitmask of all the modifier keys that were in a pressed state when the event +occurred. The modifier information can be decoded using a bitwise AND (except +for KMOD_NONE, which should be compared using equals ==). For example:

+
for event in pygame.event.get():
+    if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
+        if event.mod == pygame.KMOD_NONE:
+            print('No modifier keys were in a pressed state when this '
+                  'event occurred.')
+        else:
+            if event.mod & pygame.KMOD_LSHIFT:
+                print('Left shift was in a pressed state when this event '
+                      'occurred.')
+            if event.mod & pygame.KMOD_RSHIFT:
+                print('Right shift was in a pressed state when this event '
+                      'occurred.')
+            if event.mod & pygame.KMOD_SHIFT:
+                print('Left shift or right shift or both were in a '
+                      'pressed state when this event occurred.')
+
+
+
+
+pygame.key.get_focused()¶
+
+
true if the display is receiving keyboard input from the system
+
get_focused() -> bool
+
+

Returns True when the display window has keyboard focus from the +system. If the display needs to ensure it does not lose keyboard focus, it +can use pygame.event.set_grab()control the sharing of input devices with other applications to grab all input.

+
+ +
+
+pygame.key.get_pressed()¶
+
+
get the state of all keyboard buttons
+
get_pressed() -> bools
+
+

Returns a sequence of boolean values representing the state of every key on +the keyboard. Use the key constant values to index the array. A True +value means that the button is pressed.

+
+

Note

+

Getting the list of pushed buttons with this function is not the proper +way to handle text entry from the user. There is no way to know the order +of keys pressed, and rapidly pushed keys can be completely unnoticed +between two calls to pygame.key.get_pressed(). There is also no way to +translate these pushed keys into a fully translated character value. See +the pygame.KEYDOWN events on the pygame.eventpygame module for interacting with events and queues queue for this +functionality.

+
+
+ +
+
+pygame.key.get_mods()¶
+
+
determine which modifier keys are being held
+
get_mods() -> int
+
+

Returns a single integer representing a bitmask of all the modifier keys +being held. Using bitwise operators you can test if specific +modifier keys are pressed.

+
+ +
+
+pygame.key.set_mods()¶
+
+
temporarily set which modifier keys are pressed
+
set_mods(int) -> None
+
+

Create a bitmask of the modifier key constants +you want to impose on your program.

+
+ +
+
+pygame.key.set_repeat()¶
+
+
control how held keys are repeated
+
set_repeat() -> None
+
set_repeat(delay) -> None
+
set_repeat(delay, interval) -> None
+
+

When the keyboard repeat is enabled, keys that are held down will generate +multiple pygame.KEYDOWN events. The delay parameter is the number of +milliseconds before the first repeated pygame.KEYDOWN event will be sent. +After that, another pygame.KEYDOWN event will be sent every interval +milliseconds. If a delay value is provided and an interval value is +not provided or is 0, then the interval will be set to the same value as +delay.

+

To disable key repeat call this function with no arguments or with delay +set to 0.

+

When pygame is initialized the key repeat is disabled.

+
+
Raises
+

ValueError -- if delay or interval is < 0

+
+
+
+

Changed in pygame 2.0.0: A ValueError is now raised (instead of a +pygame.error) if delay or interval is < 0.

+
+
+ +
+
+pygame.key.get_repeat()¶
+
+
see how held keys are repeated
+
get_repeat() -> (delay, interval)
+
+

Get the delay and interval keyboard repeat values. Refer to +pygame.key.set_repeat()control how held keys are repeated for a description of these values.

+
+

New in pygame 1.8.

+
+
+ +
+
+pygame.key.name()¶
+
+
get the name of a key identifier
+
name(key) -> string
+
+

Get the descriptive name of the button from a keyboard button id constant.

+
+ +
+
+pygame.key.key_code()¶
+
+
get the key identifier from a key name
+
key_code(name=string) -> int
+
+

Get the key identifier code from the descriptive name of the key. This +returns an integer matching one of the K_* keycodes. For example:

+
>>> pygame.key.key_code("return") == pygame.K_RETURN
+True
+>>> pygame.key.key_code("0") == pygame.K_0
+True
+>>> pygame.key.key_code("space") == pygame.K_SPACE
+True
+
+
+
+
Raises
+
    +
  • ValueError -- if the key name is not known.

  • +
  • NotImplementedError -- if used with SDL 1.

  • +
+
+
+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.key.start_text_input()¶
+
+
start handling Unicode text input events
+
start_text_input() -> None
+
+

Start receiving pygame.TEXTEDITING and pygame.TEXTINPUT +events. If applicable, show the on-screen keyboard or IME editor.

+

For many languages, key presses will automatically generate a +corresponding pygame.TEXTINPUT event. Special keys like +escape or function keys, and certain key combinations will not +generate pygame.TEXTINPUT events.

+

In other languages, entering a single symbol may require multiple +key presses, or a language-specific user interface. In this case, +pygame.TEXTINPUT events are preferable to pygame.KEYDOWN +events for text input.

+

A pygame.TEXTEDITING event is received when an IME composition +is started or changed. It contains the composition text, length, +and editing start position within the composition (attributes +text, length, and start, respectively). +When the composition is committed (or non-IME input is received), +a pygame.TEXTINPUT event is generated.

+

Text input events handling is on by default.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.key.stop_text_input()¶
+
+
stop handling Unicode text input events
+
stop_text_input() -> None
+
+

Stop receiving pygame.TEXTEDITING and pygame.TEXTINPUT +events. If an on-screen keyboard or IME editor was shown with +pygame.key.start_text_input(), hide it again.

+

Text input events handling is on by default.

+

To avoid triggering the IME editor or the on-screen keyboard +when the user is holding down a key during gameplay, text input +should be disabled once text entry is finished, or when the user +clicks outside of a text box.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.key.set_text_input_rect()¶
+
+
controls the position of the candidate list
+
set_text_input_rect(Rect) -> None
+
+

This sets the rectangle used for typing with an IME. +It controls where the candidate list will open, if supported.

+
+

New in pygame 2.0.0.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/locals.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/locals.html new file mode 100644 index 0000000..421554a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/locals.html @@ -0,0 +1,161 @@ + + + + + + + + + pygame.locals — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.locals
+
+
pygame constants
+
+

This module contains various constants used by pygame. Its contents are +automatically placed in the pygame module namespace. However, an application +can use pygame.locals to include only the pygame constants with a from +pygame.locals import *.

+

Detailed descriptions of the various constants can be found throughout the +pygame documentation. Here are the locations of some of them.

+
+
+
+
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mask.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mask.html new file mode 100644 index 0000000..ebe9a58 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mask.html @@ -0,0 +1,1123 @@ + + + + + + + + + pygame.mask — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.mask
+
+
pygame module for image masks.
+
+ +++++ + + + + + + + + + + + + + + +
+—Creates a Mask from the given surface
+—Creates a mask by thresholding Surfaces
+—pygame object for representing 2D bitmasks
+

Useful for fast pixel perfect collision detection. A mask uses 1 bit per-pixel +to store which parts collide.

+
+

New in pygame 1.8.

+
+
+

Changed in pygame 2.0.2: Mask functions now support keyword arguments.

+
+
+

Changed in pygame 2.0.2: Mask functions that take positions or offsets now +support pygame.math.Vector2a 2-Dimensional Vector arguments.

+
+
+
+pygame.mask.from_surface()¶
+
+
Creates a Mask from the given surface
+
from_surface(surface) -> Mask
+
from_surface(surface, threshold=127) -> Mask
+
+

Creates a Mask object from the given surface by setting all the +opaque pixels and not setting the transparent pixels.

+

If the surface uses a color-key, then it is used to decide which bits in +the resulting mask are set. All the pixels that are not equal to the +color-key are set and the pixels equal to the color-key are not set.

+

If a color-key is not used, then the alpha value of each pixel is used to +decide which bits in the resulting mask are set. All the pixels that have an +alpha value greater than the threshold parameter are set and the +pixels with an alpha value less than or equal to the threshold are +not set.

+
+
Parameters
+
    +
  • surface (Surface) -- the surface to create the mask from

  • +
  • threshold (int) -- (optional) the alpha threshold (default is 127) to +compare with each surface pixel's alpha value, if the surface is +color-keyed this parameter is ignored

  • +
+
+
Returns
+

a newly created Mask object from the given surface

+
+
Return type
+

Mask

+
+
+
+

Note

+

This function is used to create the masks for +pygame.sprite.collide_mask()Collision detection between two sprites, using masks..

+
+
+ +
+
+pygame.mask.from_threshold()¶
+
+
Creates a mask by thresholding Surfaces
+
from_threshold(surface, color) -> Mask
+
from_threshold(surface, color, threshold=(0, 0, 0, 255), othersurface=None, palette_colors=1) -> Mask
+
+

This is a more featureful method of getting a Mask from a surface.

+

If the optional othersurface is not used, all the pixels within the +threshold of the color parameter are set in the resulting mask.

+

If the optional othersurface is used, every pixel in the first surface +that is within the threshold of the corresponding pixel in +othersurface is set in the resulting mask.

+
+
Parameters
+
    +
  • surface (Surface) -- the surface to create the mask from

  • +
  • color (Color or int or tuple(int, int, int, [int]) or list[int, int, int, [int]]) -- color used to check if the surface's pixels are within the +given threshold range, this parameter is ignored if the optional +othersurface parameter is supplied

  • +
  • threshold (Color or int or tuple(int, int, int, [int]) or list[int, int, int, [int]]) -- (optional) the threshold range used to check the difference +between two colors (default is (0, 0, 0, 255))

  • +
  • othersurface (Surface) -- (optional) used to check whether the pixels of +the first surface are within the given threshold range of the pixels +from this surface (default is None)

  • +
  • palette_colors (int) -- (optional) indicates whether to use the palette +colors or not, a nonzero value causes the palette colors to be used and a +0 causes them not to be used (default is 1)

  • +
+
+
Returns
+

a newly created Mask object from the given surface

+
+
Return type
+

Mask

+
+
+
+ +
+
+pygame.mask.Mask¶
+
+
pygame object for representing 2D bitmasks
+
Mask(size=(width, height)) -> Mask
+
Mask(size=(width, height), fill=False) -> Mask
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Returns a new copy of the mask
+—Returns the size of the mask
+—Returns a Rect based on the size of the mask
+—Gets the bit at the given position
+—Sets the bit at the given position
+—Returns the point of intersection
+—Returns the number of overlapping set bits
+—Returns a mask of the overlapping set bits
+—Sets all bits to 1
+—Sets all bits to 0
+—Flips all the bits
+—Resizes a mask
+—Draws a mask onto another
+—Erases a mask from another
+—Returns the number of set bits
+—Returns the centroid of the set bits
+—Returns the orientation of the set bits
+—Returns a list of points outlining an object
+—Returns the convolution of this mask with another mask
+—Returns a mask containing a connected component
+—Returns a list of masks of connected components
+—Returns a list of bounding rects of connected components
+—Returns a surface with the mask drawn on it
+

A Mask object is used to represent a 2D bitmask. Each bit in +the mask represents a pixel. 1 is used to indicate a set bit and 0 is used +to indicate an unset bit. Set bits in a mask can be used to detect collisions +with other masks and their set bits.

+

A filled mask has all of its bits set to 1, conversely an +unfilled/cleared/empty mask has all of its bits set to 0. Masks can be +created unfilled (default) or filled by using the fill parameter. Masks +can also be cleared or filled using the pygame.mask.Mask.clear()Sets all bits to 0 and +pygame.mask.Mask.fill()Sets all bits to 1 methods respectively.

+

A mask's coordinates start in the top left corner at (0, 0) just like +pygame.Surfacepygame object for representing images. Individual bits can be accessed using the +pygame.mask.Mask.get_at()Gets the bit at the given position and pygame.mask.Mask.set_at()Sets the bit at the given position +methods.

+

The methods overlap(), overlap_area(), overlap_mask(), +draw(), erase(), and convolve() use an offset parameter +to indicate the offset of another mask's top left corner from the calling +mask's top left corner. The calling mask's top left corner is considered to +be the origin (0, 0). Offsets are a sequence of two values +(x_offset, y_offset). Positive and negative offset values are supported.

+
           0 to x (x_offset)
+           :    :
+   0 ..... +----:---------+
+   to      |    :         |
+   y .......... +-----------+
+(y_offset) |    | othermask |
+           |    +-----------+
+           | calling_mask |
+           +--------------+
+
+
+
+
Parameters
+
    +
  • size -- the dimensions of the mask (width and height)

  • +
  • fill (bool) -- (optional) create an unfilled mask (default: False) or +filled mask (True)

  • +
+
+
Returns
+

a newly created Mask object

+
+
Return type
+

Mask

+
+
+
+

Changed in pygame 2.0.0: Shallow copy support added. The Mask class supports the special +method __copy__() and shallow copying via copy.copy(mask).

+
+
+

Changed in pygame 2.0.0: Subclassing support added. The Mask class +can be used as a base class.

+
+
+

Changed in pygame 1.9.5: Added support for keyword arguments.

+
+
+

Changed in pygame 1.9.5: Added the optional keyword parameter fill.

+
+
+

Changed in pygame 1.9.5: Added support for masks with a width and/or a +height of 0.

+
+
+
+copy()¶
+
+
Returns a new copy of the mask
+
copy() -> Mask
+
+
+
Returns
+

a new copy of this mask, the new mask will have the same width, +height, and set/unset bits as the original

+
+
Return type
+

Mask

+
+
+
+

Note

+

If a mask subclass needs to copy any instance specific attributes +then it should override the __copy__() method. The overridden +__copy__() method needs to call super().__copy__() and then +copy the required data as in the following example code.

+
class SubMask(pygame.mask.Mask):
+    def __copy__(self):
+        new_mask = super().__copy__()
+        # Do any SubMask attribute copying here.
+        return new_mask
+
+
+
+
+

New in pygame 2.0.0.

+
+
+ +
+
+get_size()¶
+
+
Returns the size of the mask
+
get_size() -> (width, height)
+
+
+
Returns
+

the size of the mask, (width, height)

+
+
Return type
+

tuple(int, int)

+
+
+
+ +
+
+get_rect()¶
+
+
Returns a Rect based on the size of the mask
+
get_rect(**kwargs) -> Rect
+
+

Returns a new pygame.Rect()pygame object for storing rectangular coordinates object based on the size of this mask. +The rect's default position will be (0, 0) and its default width and +height will be the same as this mask's. The rect's attributes can be +altered via pygame.Rect()pygame object for storing rectangular coordinates attribute keyword arguments/values passed +into this method. As an example, a_mask.get_rect(center=(10, 5)) would +create a pygame.Rect()pygame object for storing rectangular coordinates based on the mask's size centered at the +given position.

+
+
Parameters
+

kwargs (dict) -- pygame.Rect()pygame object for storing rectangular coordinates attribute keyword arguments/values +that will be applied to the rect

+
+
Returns
+

a new pygame.Rect()pygame object for storing rectangular coordinates object based on the size of this mask +with any pygame.Rect()pygame object for storing rectangular coordinates attribute keyword arguments/values applied +to it

+
+
Return type
+

Rect

+
+
+
+

New in pygame 2.0.0.

+
+
+ +
+
+get_at()¶
+
+
Gets the bit at the given position
+
get_at(pos) -> int
+
+
+
Parameters
+

pos -- the position of the bit to get (x, y)

+
+
Returns
+

1 if the bit is set, 0 if the bit is not set

+
+
Return type
+

int

+
+
Raises
+

IndexError -- if the position is outside of the mask's bounds

+
+
+
+ +
+
+set_at()¶
+
+
Sets the bit at the given position
+
set_at(pos) -> None
+
set_at(pos, value=1) -> None
+
+
+
Parameters
+
    +
  • pos -- the position of the bit to set (x, y)

  • +
  • value (int) -- any nonzero int will set the bit to 1, 0 will set the +bit to 0 (default is 1)

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
Raises
+

IndexError -- if the position is outside of the mask's bounds

+
+
+
+ +
+
+overlap()¶
+
+
Returns the point of intersection
+
overlap(other, offset) -> (x, y)
+
overlap(other, offset) -> None
+
+

Returns the first point of intersection encountered between this mask and +other. A point of intersection is 2 overlapping set bits.

+

The current algorithm searches the overlapping area in +sizeof(unsigned long int) * CHAR_BIT bit wide column blocks (the value +of sizeof(unsigned long int) * CHAR_BIT is platform dependent, for +clarity it will be referred to as W). Starting at the top left corner +it checks bits 0 to W - 1 of the first row ((0, 0) to +(W - 1, 0)) then continues to the next row ((0, 1) to +(W - 1, 1)). Once this entire column block is checked, it continues to +the next one (W to 2 * W - 1). This is repeated until it finds a +point of intersection or the entire overlapping area is checked.

+
+
Parameters
+
    +
  • other (Mask) -- the other mask to overlap with this mask

  • +
  • offset -- the offset of other from this mask, for more +details refer to the Mask offset notes

  • +
+
+
Returns
+

point of intersection or None if no intersection

+
+
Return type
+

tuple(int, int) or NoneType

+
+
+
+ +
+
+overlap_area()¶
+
+
Returns the number of overlapping set bits
+
overlap_area(other, offset) -> numbits
+
+

Returns the number of overlapping set bits between between this mask and +other.

+

This can be useful for collision detection. An approximate collision +normal can be found by calculating the gradient of the overlapping area +through the finite difference.

+
dx = mask.overlap_area(other, (x + 1, y)) - mask.overlap_area(other, (x - 1, y))
+dy = mask.overlap_area(other, (x, y + 1)) - mask.overlap_area(other, (x, y - 1))
+
+
+
+
Parameters
+
    +
  • other (Mask) -- the other mask to overlap with this mask

  • +
  • offset -- the offset of other from this mask, for more +details refer to the Mask offset notes

  • +
+
+
Returns
+

the number of overlapping set bits

+
+
Return type
+

int

+
+
+
+ +
+
+overlap_mask()¶
+
+
Returns a mask of the overlapping set bits
+
overlap_mask(other, offset) -> Mask
+
+

Returns a Mask, the same size as this mask, containing the +overlapping set bits between this mask and other.

+
+
Parameters
+
    +
  • other (Mask) -- the other mask to overlap with this mask

  • +
  • offset -- the offset of other from this mask, for more +details refer to the Mask offset notes

  • +
+
+
Returns
+

a newly created Mask with the overlapping bits set

+
+
Return type
+

Mask

+
+
+
+ +
+
+fill()¶
+
+
Sets all bits to 1
+
fill() -> None
+
+

Sets all bits in the mask to 1.

+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+clear()¶
+
+
Sets all bits to 0
+
clear() -> None
+
+

Sets all bits in the mask to 0.

+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+invert()¶
+
+
Flips all the bits
+
invert() -> None
+
+

Flips all of the bits in the mask. All the set bits are cleared to 0 and +all the unset bits are set to 1.

+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+scale()¶
+
+
Resizes a mask
+
scale((width, height)) -> Mask
+
+

Creates a new Mask of the requested size with its bits scaled +from this mask.

+
+
Parameters
+

size -- the width and height (size) of the mask to create

+
+
Returns
+

a new Mask object with its bits scaled from this mask

+
+
Return type
+

Mask

+
+
Raises
+

ValueError -- if width < 0 or height < 0

+
+
+
+ +
+
+draw()¶
+
+
Draws a mask onto another
+
draw(other, offset) -> None
+
+

Performs a bitwise OR, drawing othermask onto this mask.

+
+
Parameters
+
    +
  • other (Mask) -- the mask to draw onto this mask

  • +
  • offset -- the offset of other from this mask, for more +details refer to the Mask offset notes

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+erase()¶
+
+
Erases a mask from another
+
erase(other, offset) -> None
+
+

Erases (clears) all bits set in other from this mask.

+
+
Parameters
+
    +
  • other (Mask) -- the mask to erase from this mask

  • +
  • offset -- the offset of other from this mask, for more +details refer to the Mask offset notes

  • +
+
+
Returns
+

None

+
+
Return type
+

NoneType

+
+
+
+ +
+
+count()¶
+
+
Returns the number of set bits
+
count() -> bits
+
+
+
Returns
+

the number of set bits in the mask

+
+
Return type
+

int

+
+
+
+ +
+
+centroid()¶
+
+
Returns the centroid of the set bits
+
centroid() -> (x, y)
+
+

Finds the centroid (the center mass of the set bits) for this mask.

+
+
Returns
+

a coordinate tuple indicating the centroid of the mask, it will +return (0, 0) if the mask has no bits set

+
+
Return type
+

tuple(int, int)

+
+
+
+ +
+
+angle()¶
+
+
Returns the orientation of the set bits
+
angle() -> theta
+
+

Finds the approximate orientation (from -90 to 90 degrees) of the set bits +in the mask. This works best if performed on a mask with only one +connected component.

+
+
Returns
+

the orientation of the set bits in the mask, it will return +0.0 if the mask has no bits set

+
+
Return type
+

float

+
+
+
+

Note

+

See connected_component() for details on how a connected +component is calculated.

+
+
+ +
+
+outline()¶
+
+
Returns a list of points outlining an object
+
outline() -> [(x, y), ...]
+
outline(every=1) -> [(x, y), ...]
+
+

Returns a list of points of the outline of the first connected component +encountered in the mask. To find a connected component, the mask is +searched per row (left to right) starting in the top left corner.

+

The every optional parameter skips set bits in the outline. For +example, setting it to 10 would return a list of every 10th set bit in the +outline.

+
+
Parameters
+

every (int) -- (optional) indicates the number of bits to skip over in +the outline (default is 1)

+
+
Returns
+

a list of points outlining the first connected component +encountered, an empty list is returned if the mask has no bits set

+
+
Return type
+

list[tuple(int, int)]

+
+
+
+

Note

+

See connected_component() for details on how a connected +component is calculated.

+
+
+ +
+
+convolve()¶
+
+
Returns the convolution of this mask with another mask
+
convolve(other) -> Mask
+
convolve(other, output=None, offset=(0, 0)) -> Mask
+
+

Convolve this mask with the given other Mask.

+
+
Parameters
+
    +
  • other (Mask) -- mask to convolve this mask with

  • +
  • output (Mask or NoneType) -- (optional) mask for output (default is None)

  • +
  • offset -- the offset of other from this mask, (default is +(0, 0))

  • +
+
+
Returns
+

a Mask with the (i - offset[0], j - offset[1]) bit +set, if shifting other (such that its bottom right corner is at +(i, j)) causes it to overlap with this mask

+

If an output Mask is specified, the output is drawn onto it and +it is returned. Otherwise a mask of size (MAX(0, width + other mask's +width - 1), MAX(0, height + other mask's height - 1)) is created and +returned.

+

+
+
Return type
+

Mask

+
+
+
+ +
+
+connected_component()¶
+
+
Returns a mask containing a connected component
+
connected_component() -> Mask
+
connected_component(pos) -> Mask
+
+

A connected component is a group (1 or more) of connected set bits +(orthogonally and diagonally). The SAUF algorithm, which checks 8 point +connectivity, is used to find a connected component in the mask.

+

By default this method will return a Mask containing the largest +connected component in the mask. Optionally, a bit coordinate can be +specified and the connected component containing it will be returned. If +the bit at the given location is not set, the returned Mask will +be empty (no bits set).

+
+
Parameters
+

pos -- (optional) selects the connected component that contains the +bit at this position

+
+
Returns
+

a Mask object (same size as this mask) with the largest +connected component from this mask, if this mask has no bits set then +an empty mask will be returned

+

If the pos parameter is provided then the mask returned will have +the connected component that contains this position. An empty mask will +be returned if the pos parameter selects an unset bit.

+

+
+
Return type
+

Mask

+
+
Raises
+

IndexError -- if the optional pos parameter is outside of the +mask's bounds

+
+
+
+ +
+
+connected_components()¶
+
+
Returns a list of masks of connected components
+
connected_components() -> [Mask, ...]
+
connected_components(minimum=0) -> [Mask, ...]
+
+

Provides a list containing a Mask object for each connected +component.

+
+
Parameters
+

minimum (int) -- (optional) indicates the minimum number of bits (to +filter out noise) per connected component (default is 0, which equates +to no minimum and is equivalent to setting it to 1, as a connected +component must have at least 1 bit set)

+
+
Returns
+

a list containing a Mask object for each connected +component, an empty list is returned if the mask has no bits set

+
+
Return type
+

list[Mask]

+
+
+
+

Note

+

See connected_component() for details on how a connected +component is calculated.

+
+
+ +
+
+get_bounding_rects()¶
+
+
Returns a list of bounding rects of connected components
+
get_bounding_rects() -> [Rect, ...]
+
+

Provides a list containing a bounding rect for each connected component.

+
+
Returns
+

a list containing a bounding rect for each connected component, +an empty list is returned if the mask has no bits set

+
+
Return type
+

list[Rect]

+
+
+
+

Note

+

See connected_component() for details on how a connected +component is calculated.

+
+
+ +
+
+to_surface()¶
+
+
Returns a surface with the mask drawn on it
+
to_surface() -> Surface
+
to_surface(surface=None, setsurface=None, unsetsurface=None, setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 255), dest=(0, 0)) -> Surface
+
+

Draws this mask on the given surface. Set bits (bits set to 1) and unset +bits (bits set to 0) can be drawn onto a surface.

+
+
Parameters
+
    +
  • surface (Surface or None) -- (optional) Surface to draw mask onto, if no surface is +provided one will be created (default is None, which will cause a +surface with the parameters +Surface(size=mask.get_size(), flags=SRCALPHA, depth=32) to be +created, drawn on, and returned)

  • +
  • setsurface (Surface or None) -- (optional) use this surface's color values to draw +set bits (default is None), if this surface is smaller than the +mask any bits outside its bounds will use the setcolor value

  • +
  • unsetsurface (Surface or None) -- (optional) use this surface's color values to draw +unset bits (default is None), if this surface is smaller than the +mask any bits outside its bounds will use the unsetcolor value

  • +
  • setcolor (Color or str or int or tuple(int, int, int, [int]) or +list(int, int, int, [int]) or None) -- (optional) color to draw set bits (default is +(255, 255, 255, 255), white), use None to skip drawing the set +bits, the setsurface parameter (if set) will takes precedence over +this parameter

  • +
  • unsetcolor (Color or str or int or tuple(int, int, int, [int]) or +list(int, int, int, [int]) or None) -- (optional) color to draw unset bits (default is +(0, 0, 0, 255), black), use None to skip drawing the unset +bits, the unsetsurface parameter (if set) will takes precedence +over this parameter

  • +
  • dest (Rect or tuple(int, int) or list(int, int) or Vector2(int, int)) -- (optional) surface destination of where to position the +topleft corner of the mask being drawn (default is (0, 0)), if a +Rect is used as the dest parameter, its x and y attributes +will be used as the destination, NOTE1: rects with a negative width +or height value will not be normalized before using their x and +y values, NOTE2: this destination value is only used to +position the mask on the surface, it does not offset the setsurface +and unsetsurface from the mask, they are always aligned with the +mask (i.e. position (0, 0) on the mask always corresponds to +position (0, 0) on the setsurface and unsetsurface)

  • +
+
+
Returns
+

the surface parameter (or a newly created surface if no +surface parameter was provided) with this mask drawn on it

+
+
Return type
+

Surface

+
+
Raises
+

ValueError -- if the setsurface parameter or unsetsurface +parameter does not have the same format (bytesize/bitsize/alpha) as +the surface parameter

+
+
+
+

Note

+

To skip drawing the set bits, both setsurface and setcolor must +be None. The setsurface parameter defaults to None, but +setcolor defaults to a color value and therefore must be set to +None.

+
+
+

Note

+

To skip drawing the unset bits, both unsetsurface and +unsetcolor must be None. The unsetsurface parameter +defaults to None, but unsetcolor defaults to a color value and +therefore must be set to None.

+
+
+

New in pygame 2.0.0.

+
+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/math.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/math.html new file mode 100644 index 0000000..1bec8a3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/math.html @@ -0,0 +1,1509 @@ + + + + + + + + + pygame.math — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.math
+
+
pygame module for vector classes
+
+ +++++ + + + + + + + + + + +
+—a 2-Dimensional Vector
+—a 3-Dimensional Vector
+

The pygame math module currently provides Vector classes in two and three +dimensions, Vector2 and Vector3 respectively.

+

They support the following numerical operations: vec+vec, vec-vec, +vec*number, number*vec, vec/number, vec//number, vec+=vec, +vec-=vec, vec*=number, vec/=number, vec//=number.

+

All these operations will be performed elementwise. +In addition vec*vec will perform a scalar-product (a.k.a. dot-product). +If you want to multiply every element from vector v with every element from +vector w you can use the elementwise method: v.elementwise() * w

+

The coordinates of a vector can be retrieved or set using attributes or +subscripts

+
v = pygame.Vector3()
+
+v.x = 5
+v[1] = 2 * v.x
+print(v[1]) # 10
+
+v.x == v[0]
+v.y == v[1]
+v.z == v[2]
+
+
+

Multiple coordinates can be set using slices or swizzling

+
v = pygame.Vector2()
+v.xy = 1, 2
+v[:] = 1, 2
+
+
+
+

New in pygame 1.9.2pre.

+
+
+

Changed in pygame 1.9.4: Removed experimental notice.

+
+
+

Changed in pygame 1.9.4: Allow scalar construction like GLSL Vector2(2) == Vector2(2.0, 2.0)

+
+
+

Changed in pygame 1.9.4: pygame.mathpygame module for vector classes required import. More convenient pygame.Vector2 and pygame.Vector3.

+
+
+
+pygame.math.Vector2¶
+
+
a 2-Dimensional Vector
+
Vector2() -> Vector2
+
Vector2(int) -> Vector2
+
Vector2(float) -> Vector2
+
Vector2(Vector2) -> Vector2
+
Vector2(x, y) -> Vector2
+
Vector2((x, y)) -> Vector2
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—calculates the dot- or scalar-product with the other vector
+—calculates the cross- or vector-product
+—returns the Euclidean magnitude of the vector.
+—returns the squared magnitude of the vector.
+—returns the Euclidean length of the vector.
+—returns the squared Euclidean length of the vector.
+—returns a vector with the same direction but length 1.
+—normalizes the vector in place so that its length is 1.
+—tests if the vector is normalized i.e. has length == 1.
+—scales the vector to a given length.
+—returns a vector reflected of a given normal.
+—reflect the vector of a given normal in place.
+—calculates the Euclidean distance to a given vector.
+—calculates the squared Euclidean distance to a given vector.
+—returns a linear interpolation to the given vector.
+—returns a spherical interpolation to the given vector.
+—The next operation will be performed elementwise.
+—rotates a vector by a given angle in degrees.
+—rotates a vector by a given angle in radians.
+—rotates the vector by a given angle in degrees in place.
+—rotates the vector by a given angle in radians in place.
+—rotates the vector by a given angle in radians in place.
+—calculates the angle to a given vector in degrees.
+—returns a tuple with radial distance and azimuthal angle.
+—Sets x and y from a polar coordinates tuple.
+—projects a vector onto another.
+—Returns a copy of itself.
+—Sets the coordinates of the vector.
+

Some general information about the Vector2 class.

+
+
+dot()¶
+
+
calculates the dot- or scalar-product with the other vector
+
dot(Vector2) -> float
+
+
+ +
+
+cross()¶
+
+
calculates the cross- or vector-product
+
cross(Vector2) -> Vector2
+
+

calculates the third component of the cross-product.

+
+ +
+
+magnitude()¶
+
+
returns the Euclidean magnitude of the vector.
+
magnitude() -> float
+
+

calculates the magnitude of the vector which follows from the +theorem: vec.magnitude() == math.sqrt(vec.x**2 + vec.y**2)

+
+ +
+
+magnitude_squared()¶
+
+
returns the squared magnitude of the vector.
+
magnitude_squared() -> float
+
+

calculates the magnitude of the vector which follows from the +theorem: vec.magnitude_squared() == vec.x**2 + vec.y**2. This +is faster than vec.magnitude() because it avoids the square root.

+
+ +
+
+length()¶
+
+
returns the Euclidean length of the vector.
+
length() -> float
+
+

calculates the Euclidean length of the vector which follows from the +Pythagorean theorem: vec.length() == math.sqrt(vec.x**2 + vec.y**2)

+
+ +
+
+length_squared()¶
+
+
returns the squared Euclidean length of the vector.
+
length_squared() -> float
+
+

calculates the Euclidean length of the vector which follows from the +Pythagorean theorem: vec.length_squared() == vec.x**2 + vec.y**2. +This is faster than vec.length() because it avoids the square root.

+
+ +
+
+normalize()¶
+
+
returns a vector with the same direction but length 1.
+
normalize() -> Vector2
+
+

Returns a new vector that has length equal to 1 and the same +direction as self.

+
+ +
+
+normalize_ip()¶
+
+
normalizes the vector in place so that its length is 1.
+
normalize_ip() -> None
+
+

Normalizes the vector so that it has length equal to 1. +The direction of the vector is not changed.

+
+ +
+
+is_normalized()¶
+
+
tests if the vector is normalized i.e. has length == 1.
+
is_normalized() -> Bool
+
+

Returns True if the vector has length equal to 1. Otherwise +it returns False.

+
+ +
+
+scale_to_length()¶
+
+
scales the vector to a given length.
+
scale_to_length(float) -> None
+
+

Scales the vector so that it has the given length. The direction of the +vector is not changed. You can also scale to length 0. If the vector +is the zero vector (i.e. has length 0 thus no direction) a +ValueError is raised.

+
+ +
+
+reflect()¶
+
+
returns a vector reflected of a given normal.
+
reflect(Vector2) -> Vector2
+
+

Returns a new vector that points in the direction as if self would bounce +of a surface characterized by the given surface normal. The length of the +new vector is the same as self's.

+
+ +
+
+reflect_ip()¶
+
+
reflect the vector of a given normal in place.
+
reflect_ip(Vector2) -> None
+
+

Changes the direction of self as if it would have been reflected of a +surface with the given surface normal.

+
+ +
+
+distance_to()¶
+
+
calculates the Euclidean distance to a given vector.
+
distance_to(Vector2) -> float
+
+
+ +
+
+distance_squared_to()¶
+
+
calculates the squared Euclidean distance to a given vector.
+
distance_squared_to(Vector2) -> float
+
+
+ +
+
+lerp()¶
+
+
returns a linear interpolation to the given vector.
+
lerp(Vector2, float) -> Vector2
+
+

Returns a Vector which is a linear interpolation between self and the +given Vector. The second parameter determines how far between self and +other the result is going to be. It must be a value between 0 and 1 +where 0 means self and 1 means other will be returned.

+
+ +
+
+slerp()¶
+
+
returns a spherical interpolation to the given vector.
+
slerp(Vector2, float) -> Vector2
+
+

Calculates the spherical interpolation from self to the given Vector. The +second argument - often called t - must be in the range [-1, 1]. It +parametrizes where - in between the two vectors - the result should be. +If a negative value is given the interpolation will not take the +complement of the shortest path.

+
+ +
+
+elementwise()¶
+
+
The next operation will be performed elementwise.
+
elementwise() -> VectorElementwiseProxy
+
+

Applies the following operation to each element of the vector.

+
+ +
+
+rotate()¶
+
+
rotates a vector by a given angle in degrees.
+
rotate(angle) -> Vector2
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise by the given angle in degrees. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_rad()¶
+
+
rotates a vector by a given angle in radians.
+
rotate_rad(angle) -> Vector2
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise by the given angle in radians. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.0.0.

+
+
+ +
+
+rotate_ip()¶
+
+
rotates the vector by a given angle in degrees in place.
+
rotate_ip(angle) -> None
+
+

Rotates the vector counterclockwise by the given angle in degrees. The +length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_ip_rad()¶
+
+
rotates the vector by a given angle in radians in place.
+
rotate_ip_rad(angle) -> None
+
+

DEPRECATED: Use rotate_rad_ip() instead.

+
+

New in pygame 2.0.0.

+
+
+

Deprecated since pygame 2.1.1.

+
+
+ +
+
+rotate_rad_ip()¶
+
+
rotates the vector by a given angle in radians in place.
+
rotate_rad_ip(angle) -> None
+
+

Rotates the vector counterclockwise by the given angle in radians. The +length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.1.1.

+
+
+ +
+
+angle_to()¶
+
+
calculates the angle to a given vector in degrees.
+
angle_to(Vector2) -> float
+
+

Returns the angle between self and the given vector.

+
+ +
+
+as_polar()¶
+
+
returns a tuple with radial distance and azimuthal angle.
+
as_polar() -> (r, phi)
+
+

Returns a tuple (r, phi) where r is the radial distance, and phi +is the azimuthal angle.

+
+ +
+
+from_polar()¶
+
+
Sets x and y from a polar coordinates tuple.
+
from_polar((r, phi)) -> None
+
+

Sets x and y from a tuple (r, phi) where r is the radial distance, and +phi is the azimuthal angle.

+
+ +
+
+project()¶
+
+
projects a vector onto another.
+
project(Vector2) -> Vector2
+
+

Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). +For a more detailed explanation see Wikipedia.

+
+

New in pygame 2.0.2.

+
+
+ +
+
+copy()¶
+
+
Returns a copy of itself.
+
copy() -> Vector2
+
+

Returns a new Vector2 having the same dimensions.

+
+

New in pygame 2.1.1.

+
+
+ +
+
+update()¶
+
+
Sets the coordinates of the vector.
+
update() -> None
+
update(int) -> None
+
update(float) -> None
+
update(Vector2) -> None
+
update(x, y) -> None
+
update((x, y)) -> None
+
+

Sets coordinates x and y in place.

+
+

New in pygame 1.9.5.

+
+
+ +
+ +
+
+pygame.math.Vector3¶
+
+
a 3-Dimensional Vector
+
Vector3() -> Vector3
+
Vector3(int) -> Vector3
+
Vector3(float) -> Vector3
+
Vector3(Vector3) -> Vector3
+
Vector3(x, y, z) -> Vector3
+
Vector3((x, y, z)) -> Vector3
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—calculates the dot- or scalar-product with the other vector
+—calculates the cross- or vector-product
+—returns the Euclidean magnitude of the vector.
+—returns the squared Euclidean magnitude of the vector.
+—returns the Euclidean length of the vector.
+—returns the squared Euclidean length of the vector.
+—returns a vector with the same direction but length 1.
+—normalizes the vector in place so that its length is 1.
+—tests if the vector is normalized i.e. has length == 1.
+—scales the vector to a given length.
+—returns a vector reflected of a given normal.
+—reflect the vector of a given normal in place.
+—calculates the Euclidean distance to a given vector.
+—calculates the squared Euclidean distance to a given vector.
+—returns a linear interpolation to the given vector.
+—returns a spherical interpolation to the given vector.
+—The next operation will be performed elementwise.
+—rotates a vector by a given angle in degrees.
+—rotates a vector by a given angle in radians.
+—rotates the vector by a given angle in degrees in place.
+—rotates the vector by a given angle in radians in place.
+—rotates the vector by a given angle in radians in place.
+—rotates a vector around the x-axis by the angle in degrees.
+—rotates a vector around the x-axis by the angle in radians.
+—rotates the vector around the x-axis by the angle in degrees in place.
+—rotates the vector around the x-axis by the angle in radians in place.
+—rotates the vector around the x-axis by the angle in radians in place.
+—rotates a vector around the y-axis by the angle in degrees.
+—rotates a vector around the y-axis by the angle in radians.
+—rotates the vector around the y-axis by the angle in degrees in place.
+—rotates the vector around the y-axis by the angle in radians in place.
+—rotates the vector around the y-axis by the angle in radians in place.
+—rotates a vector around the z-axis by the angle in degrees.
+—rotates a vector around the z-axis by the angle in radians.
+—rotates the vector around the z-axis by the angle in degrees in place.
+—rotates the vector around the z-axis by the angle in radians in place.
+—rotates the vector around the z-axis by the angle in radians in place.
+—calculates the angle to a given vector in degrees.
+—returns a tuple with radial distance, inclination and azimuthal angle.
+—Sets x, y and z from a spherical coordinates 3-tuple.
+—projects a vector onto another.
+—Returns a copy of itself.
+—Sets the coordinates of the vector.
+

Some general information about the Vector3 class.

+
+
+dot()¶
+
+
calculates the dot- or scalar-product with the other vector
+
dot(Vector3) -> float
+
+
+ +
+
+cross()¶
+
+
calculates the cross- or vector-product
+
cross(Vector3) -> Vector3
+
+

calculates the cross-product.

+
+ +
+
+magnitude()¶
+
+
returns the Euclidean magnitude of the vector.
+
magnitude() -> float
+
+

calculates the magnitude of the vector which follows from the +theorem: vec.magnitude() == math.sqrt(vec.x**2 + vec.y**2 + vec.z**2)

+
+ +
+
+magnitude_squared()¶
+
+
returns the squared Euclidean magnitude of the vector.
+
magnitude_squared() -> float
+
+

calculates the magnitude of the vector which follows from the +theorem: +vec.magnitude_squared() == vec.x**2 + vec.y**2 + vec.z**2. +This is faster than vec.magnitude() because it avoids the +square root.

+
+ +
+
+length()¶
+
+
returns the Euclidean length of the vector.
+
length() -> float
+
+

calculates the Euclidean length of the vector which follows from the +Pythagorean theorem: +vec.length() == math.sqrt(vec.x**2 + vec.y**2 + vec.z**2)

+
+ +
+
+length_squared()¶
+
+
returns the squared Euclidean length of the vector.
+
length_squared() -> float
+
+

calculates the Euclidean length of the vector which follows from the +Pythagorean theorem: +vec.length_squared() == vec.x**2 + vec.y**2 + vec.z**2. +This is faster than vec.length() because it avoids the square root.

+
+ +
+
+normalize()¶
+
+
returns a vector with the same direction but length 1.
+
normalize() -> Vector3
+
+

Returns a new vector that has length equal to 1 and the same +direction as self.

+
+ +
+
+normalize_ip()¶
+
+
normalizes the vector in place so that its length is 1.
+
normalize_ip() -> None
+
+

Normalizes the vector so that it has length equal to 1. The +direction of the vector is not changed.

+
+ +
+
+is_normalized()¶
+
+
tests if the vector is normalized i.e. has length == 1.
+
is_normalized() -> Bool
+
+

Returns True if the vector has length equal to 1. Otherwise it +returns False.

+
+ +
+
+scale_to_length()¶
+
+
scales the vector to a given length.
+
scale_to_length(float) -> None
+
+

Scales the vector so that it has the given length. The direction of the +vector is not changed. You can also scale to length 0. If the vector +is the zero vector (i.e. has length 0 thus no direction) a +ValueError is raised.

+
+ +
+
+reflect()¶
+
+
returns a vector reflected of a given normal.
+
reflect(Vector3) -> Vector3
+
+

Returns a new vector that points in the direction as if self would bounce +of a surface characterized by the given surface normal. The length of the +new vector is the same as self's.

+
+ +
+
+reflect_ip()¶
+
+
reflect the vector of a given normal in place.
+
reflect_ip(Vector3) -> None
+
+

Changes the direction of self as if it would have been reflected of a +surface with the given surface normal.

+
+ +
+
+distance_to()¶
+
+
calculates the Euclidean distance to a given vector.
+
distance_to(Vector3) -> float
+
+
+ +
+
+distance_squared_to()¶
+
+
calculates the squared Euclidean distance to a given vector.
+
distance_squared_to(Vector3) -> float
+
+
+ +
+
+lerp()¶
+
+
returns a linear interpolation to the given vector.
+
lerp(Vector3, float) -> Vector3
+
+

Returns a Vector which is a linear interpolation between self and the +given Vector. The second parameter determines how far between self an +other the result is going to be. It must be a value between 0 and +1, where 0 means self and 1 means other will be returned.

+
+ +
+
+slerp()¶
+
+
returns a spherical interpolation to the given vector.
+
slerp(Vector3, float) -> Vector3
+
+

Calculates the spherical interpolation from self to the given Vector. The +second argument - often called t - must be in the range [-1, 1]. It +parametrizes where - in between the two vectors - the result should be. +If a negative value is given the interpolation will not take the +complement of the shortest path.

+
+ +
+
+elementwise()¶
+
+
The next operation will be performed elementwise.
+
elementwise() -> VectorElementwiseProxy
+
+

Applies the following operation to each element of the vector.

+
+ +
+
+rotate()¶
+
+
rotates a vector by a given angle in degrees.
+
rotate(angle, Vector3) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise by the given angle in degrees around the given axis. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_rad()¶
+
+
rotates a vector by a given angle in radians.
+
rotate_rad(angle, Vector3) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise by the given angle in radians around the given axis. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.0.0.

+
+
+ +
+
+rotate_ip()¶
+
+
rotates the vector by a given angle in degrees in place.
+
rotate_ip(angle, Vector3) -> None
+
+

Rotates the vector counterclockwise around the given axis by the given +angle in degrees. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_ip_rad()¶
+
+
rotates the vector by a given angle in radians in place.
+
rotate_ip_rad(angle, Vector3) -> None
+
+

DEPRECATED: Use rotate_rad_ip() instead.

+
+

New in pygame 2.0.0.

+
+
+

Deprecated since pygame 2.1.1.

+
+
+ +
+
+rotate_rad_ip()¶
+
+
rotates the vector by a given angle in radians in place.
+
rotate_rad_ip(angle, Vector3) -> None
+
+

Rotates the vector counterclockwise around the given axis by the given +angle in radians. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.1.1.

+
+
+ +
+
+rotate_x()¶
+
+
rotates a vector around the x-axis by the angle in degrees.
+
rotate_x(angle) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise around the x-axis by the given angle in degrees. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_x_rad()¶
+
+
rotates a vector around the x-axis by the angle in radians.
+
rotate_x_rad(angle) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise around the x-axis by the given angle in radians. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.0.0.

+
+
+ +
+
+rotate_x_ip()¶
+
+
rotates the vector around the x-axis by the angle in degrees in place.
+
rotate_x_ip(angle) -> None
+
+

Rotates the vector counterclockwise around the x-axis by the given angle +in degrees. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_x_ip_rad()¶
+
+
rotates the vector around the x-axis by the angle in radians in place.
+
rotate_x_ip_rad(angle) -> None
+
+

DEPRECATED: Use rotate_x_rad_ip() instead.

+
+

New in pygame 2.0.0.

+
+
+

Deprecated since pygame 2.1.1.

+
+
+ +
+
+rotate_x_rad_ip()¶
+
+
rotates the vector around the x-axis by the angle in radians in place.
+
rotate_x_rad_ip(angle) -> None
+
+

Rotates the vector counterclockwise around the x-axis by the given angle +in radians. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.1.1.

+
+
+ +
+
+rotate_y()¶
+
+
rotates a vector around the y-axis by the angle in degrees.
+
rotate_y(angle) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise around the y-axis by the given angle in degrees. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_y_rad()¶
+
+
rotates a vector around the y-axis by the angle in radians.
+
rotate_y_rad(angle) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise around the y-axis by the given angle in radians. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.0.0.

+
+
+ +
+
+rotate_y_ip()¶
+
+
rotates the vector around the y-axis by the angle in degrees in place.
+
rotate_y_ip(angle) -> None
+
+

Rotates the vector counterclockwise around the y-axis by the given angle +in degrees. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_y_ip_rad()¶
+
+
rotates the vector around the y-axis by the angle in radians in place.
+
rotate_y_ip_rad(angle) -> None
+
+

DEPRECATED: Use rotate_y_rad_ip() instead.

+
+

New in pygame 2.0.0.

+
+
+

Deprecated since pygame 2.1.1.

+
+
+ +
+
+rotate_y_rad_ip()¶
+
+
rotates the vector around the y-axis by the angle in radians in place.
+
rotate_y_rad_ip(angle) -> None
+
+

Rotates the vector counterclockwise around the y-axis by the given angle +in radians. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.1.1.

+
+
+ +
+
+rotate_z()¶
+
+
rotates a vector around the z-axis by the angle in degrees.
+
rotate_z(angle) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise around the z-axis by the given angle in degrees. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_z_rad()¶
+
+
rotates a vector around the z-axis by the angle in radians.
+
rotate_z_rad(angle) -> Vector3
+
+

Returns a vector which has the same length as self but is rotated +counterclockwise around the z-axis by the given angle in radians. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.0.0.

+
+
+ +
+
+rotate_z_ip()¶
+
+
rotates the vector around the z-axis by the angle in degrees in place.
+
rotate_z_ip(angle) -> None
+
+

Rotates the vector counterclockwise around the z-axis by the given angle +in degrees. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+ +
+
+rotate_z_ip_rad()¶
+
+
rotates the vector around the z-axis by the angle in radians in place.
+
rotate_z_ip_rad(angle) -> None
+
+

DEPRECATED: Use rotate_z_rad_ip() instead.

+
+

Deprecated since pygame 2.1.1.

+
+
+ +
+
+rotate_z_rad_ip()¶
+
+
rotates the vector around the z-axis by the angle in radians in place.
+
rotate_z_rad_ip(angle) -> None
+
+

Rotates the vector counterclockwise around the z-axis by the given angle +in radians. The length of the vector is not changed. +(Note that due to pygame's inverted y coordinate system, the rotation +will look clockwise if displayed).

+
+

New in pygame 2.1.1.

+
+
+ +
+
+angle_to()¶
+
+
calculates the angle to a given vector in degrees.
+
angle_to(Vector3) -> float
+
+

Returns the angle between self and the given vector.

+
+ +
+
+as_spherical()¶
+
+
returns a tuple with radial distance, inclination and azimuthal angle.
+
as_spherical() -> (r, theta, phi)
+
+

Returns a tuple (r, theta, phi) where r is the radial distance, theta is +the inclination angle and phi is the azimuthal angle.

+
+ +
+
+from_spherical()¶
+
+
Sets x, y and z from a spherical coordinates 3-tuple.
+
from_spherical((r, theta, phi)) -> None
+
+

Sets x, y and z from a tuple (r, theta, phi) where r is the radial +distance, theta is the inclination angle and phi is the azimuthal angle.

+
+ +
+
+project()¶
+
+
projects a vector onto another.
+
project(Vector3) -> Vector3
+
+

Returns the projected vector. This is useful for collision detection in finding the components in a certain direction (e.g. in direction of the wall). +For a more detailed explanation see Wikipedia.

+
+

New in pygame 2.0.2.

+
+
+ +
+
+copy()¶
+
+
Returns a copy of itself.
+
copy() -> Vector3
+
+

Returns a new Vector3 having the same dimensions.

+
+

New in pygame 2.1.1.

+
+
+ +
+
+update()¶
+
+
Sets the coordinates of the vector.
+
update() -> None
+
update(int) -> None
+
update(float) -> None
+
update(Vector3) -> None
+
update(x, y, z) -> None
+
update((x, y, z)) -> None
+
+

Sets coordinates x, y, and z in place.

+
+

New in pygame 1.9.5.

+
+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/midi.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/midi.html new file mode 100644 index 0000000..c20b90f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/midi.html @@ -0,0 +1,845 @@ + + + + + + + + + pygame.midi — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.midi
+
+
pygame module for interacting with midi input and output.
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize the midi module
+—uninitialize the midi module
+—returns True if the midi module is currently initialized
+—Input is used to get midi input from midi devices.
+—Output is used to send midi to an output device
+—gets the number of devices.
+—gets default input device number
+—gets default output device number
+—returns information about a midi device
+—converts midi events to pygame events
+—returns the current time in ms of the PortMidi timer
+—Converts a frequency into a MIDI note. Rounds to the closest midi note.
+—Converts a midi note to a frequency.
+—Returns the Ansi Note name for a midi number.
+—exception that pygame.midi functions and classes can raise
+
+

New in pygame 1.9.0.

+
+

The midi module can send output to midi devices and get input from midi +devices. It can also list midi devices on the system.

+

The midi module supports real and virtual midi devices.

+

It uses the portmidi library. Is portable to which ever platforms portmidi +supports (currently Windows, Mac OS X, and Linux).

+

This uses pyportmidi for now, but may use its own bindings at some point in the +future. The pyportmidi bindings are included with pygame.

+
+

+
+
+

New in pygame 2.0.0.

+
+

These are pygame events (pygame.eventpygame module for interacting with events and queues) reserved for midi use. The +MIDIIN event is used by pygame.midi.midis2events()converts midi events to pygame events when converting +midi events to pygame events.

+
MIDIIN
+MIDIOUT
+
+
+
+

+
+
+
+pygame.midi.init()¶
+
+
initialize the midi module
+
init() -> None
+
+

Initializes the pygame.midipygame module for interacting with midi input and output. module. Must be called before using the +pygame.midipygame module for interacting with midi input and output. module.

+

It is safe to call this more than once.

+
+ +
+
+pygame.midi.quit()¶
+
+
uninitialize the midi module
+
quit() -> None
+
+

Uninitializes the pygame.midipygame module for interacting with midi input and output. module. If pygame.midi.init()initialize the midi module was +called to initialize the pygame.midipygame module for interacting with midi input and output. module, then this function will +be called automatically when your program exits.

+

It is safe to call this function more than once.

+
+ +
+
+pygame.midi.get_init()¶
+
+
returns True if the midi module is currently initialized
+
get_init() -> bool
+
+

Gets the initialization state of the pygame.midipygame module for interacting with midi input and output. module.

+
+
Returns
+

True if the pygame.midipygame module for interacting with midi input and output. module is currently initialized.

+
+
Return type
+

bool

+
+
+
+

New in pygame 1.9.5.

+
+
+ +
+
+pygame.midi.Input¶
+
+
Input is used to get midi input from midi devices.
+
Input(device_id) -> None
+
Input(device_id, buffer_size) -> None
+
+ +++++ + + + + + + + + + + + + + + +
+—closes a midi stream, flushing any pending buffers.
+—returns True if there's data, or False if not.
+—reads num_events midi events from the buffer.
+
+
Parameters
+
    +
  • device_id (int) -- midi device id

  • +
  • buffer_size (int) -- (optional) the number of input events to be buffered

  • +
+
+
+
+
+close()¶
+
+
closes a midi stream, flushing any pending buffers.
+
close() -> None
+
+

PortMidi attempts to close open streams when the application exits.

+
+

Note

+

This is particularly difficult under Windows.

+
+
+ +
+
+poll()¶
+
+
returns True if there's data, or False if not.
+
poll() -> bool
+
+

Used to indicate if any data exists.

+
+
Returns
+

True if there is data, False otherwise

+
+
Return type
+

bool

+
+
Raises
+

MidiException -- on error

+
+
+
+ +
+
+read()¶
+
+
reads num_events midi events from the buffer.
+
read(num_events) -> midi_event_list
+
+

Reads from the input buffer and gives back midi events.

+
+
Parameters
+

num_events (int) -- number of input events to read

+
+
Returns
+

the format for midi_event_list is +[[[status, data1, data2, data3], timestamp], ...]

+
+
Return type
+

list

+
+
+
+ +
+ +
+
+pygame.midi.Output¶
+
+
Output is used to send midi to an output device
+
Output(device_id) -> None
+
Output(device_id, latency=0) -> None
+
Output(device_id, buffer_size=256) -> None
+
Output(device_id, latency, buffer_size) -> None
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—terminates outgoing messages immediately
+—closes a midi stream, flushing any pending buffers.
+—turns a midi note off (note must be on)
+—turns a midi note on (note must be off)
+—select an instrument, with a value between 0 and 127
+—modify the pitch of a channel.
+—writes a list of midi data to the Output
+—writes up to 3 bytes of midi data to the Output
+—writes a timestamped system-exclusive midi message.
+

The buffer_size specifies the number of output events to be buffered +waiting for output. In some cases (see below) PortMidi does not buffer +output at all and merely passes data to a lower-level API, in which case +buffersize is ignored.

+

latency is the delay in milliseconds applied to timestamps to determine +when the output should actually occur. If latency is <<0, 0 is assumed.

+

If latency is zero, timestamps are ignored and all output is delivered +immediately. If latency is greater than zero, output is delayed until the +message timestamp plus the latency. In some cases, PortMidi can obtain +better timing than your application by passing timestamps along to the +device driver or hardware. Latency may also help you to synchronize midi +data to audio data by matching midi latency to the audio buffer latency.

+
+

Note

+

Time is measured relative to the time source indicated by time_proc. +Timestamps are absolute, not relative delays or offsets.

+
+
+
+abort()¶
+
+
terminates outgoing messages immediately
+
abort() -> None
+
+

The caller should immediately close the output port; this call may result +in transmission of a partial midi message. There is no abort for Midi +input because the user can simply ignore messages in the buffer and close +an input device at any time.

+
+ +
+
+close()¶
+
+
closes a midi stream, flushing any pending buffers.
+
close() -> None
+
+

PortMidi attempts to close open streams when the application exits.

+
+

Note

+

This is particularly difficult under Windows.

+
+
+ +
+
+note_off()¶
+
+
turns a midi note off (note must be on)
+
note_off(note, velocity=None, channel=0) -> None
+
+

Turn a note off in the output stream. The note must already be on for +this to work correctly.

+
+ +
+
+note_on()¶
+
+
turns a midi note on (note must be off)
+
note_on(note, velocity=None, channel=0) -> None
+
+

Turn a note on in the output stream. The note must already be off for +this to work correctly.

+
+ +
+
+set_instrument()¶
+
+
select an instrument, with a value between 0 and 127
+
set_instrument(instrument_id, channel=0) -> None
+
+

Select an instrument.

+
+ +
+
+pitch_bend()¶
+
+
modify the pitch of a channel.
+
set_instrument(value=0, channel=0) -> None
+
+

Adjust the pitch of a channel. The value is a signed integer +from -8192 to +8191. For example, 0 means "no change", +4096 is +typically a semitone higher, and -8192 is 1 whole tone lower (though +the musical range corresponding to the pitch bend range can also be +changed in some synthesizers).

+

If no value is given, the pitch bend is returned to "no change".

+
+

New in pygame 1.9.4.

+
+
+ +
+
+write()¶
+
+
writes a list of midi data to the Output
+
write(data) -> None
+
+

Writes series of MIDI information in the form of a list.

+
+
Parameters
+

data (list) -- data to write, the expected format is +[[[status, data1=0, data2=0, ...], timestamp], ...] +with the data# fields being optional

+
+
Raises
+

IndexError -- if more than 1024 elements in the data list

+
+
+

Example:

+
# Program change at time 20000 and 500ms later send note 65 with
+# velocity 100.
+write([[[0xc0, 0, 0], 20000], [[0x90, 60, 100], 20500]])
+
+
+
+

Note

+
    +
  • Timestamps will be ignored if latency = 0

  • +
  • To get a note to play immediately, send MIDI info with timestamp +read from function Time

  • +
  • Optional data fields: write([[[0xc0, 0, 0], 20000]]) is +equivalent to write([[[0xc0], 20000]])

  • +
+
+
+ +
+
+write_short()¶
+
+
writes up to 3 bytes of midi data to the Output
+
write_short(status) -> None
+
write_short(status, data1=0, data2=0) -> None
+
+

Output MIDI information of 3 bytes or less. The data fields are +optional and assumed to be 0 if omitted.

+

Examples of status byte values:

+
0xc0  # program change
+0x90  # note on
+# etc.
+
+
+

Example:

+
# note 65 on with velocity 100
+write_short(0x90, 65, 100)
+
+
+
+ +
+
+write_sys_ex()¶
+
+
writes a timestamped system-exclusive midi message.
+
write_sys_ex(when, msg) -> None
+
+

Writes a timestamped system-exclusive midi message.

+
+
Parameters
+
    +
  • msg (list[int] or str) -- midi message

  • +
  • when -- timestamp in milliseconds

  • +
+
+
+

Example:

+
midi_output.write_sys_ex(0, '\xF0\x7D\x10\x11\x12\x13\xF7')
+
+# is equivalent to
+
+midi_output.write_sys_ex(pygame.midi.time(),
+                         [0xF0, 0x7D, 0x10, 0x11, 0x12, 0x13, 0xF7])
+
+
+
+ +
+ +
+
+pygame.midi.get_count()¶
+
+
gets the number of devices.
+
get_count() -> num_devices
+
+

Device ids range from 0 to get_count() - 1

+
+ +
+
+pygame.midi.get_default_input_id()¶
+
+
gets default input device number
+
get_default_input_id() -> default_id
+
+

The following describes the usage details for this function and the +get_default_output_id() function.

+

Return the default device ID or -1 if there are no devices. The result +can be passed to the Input/Output class.

+

On a PC the user can specify a default device by setting an environment +variable. To use device #1, for example:

+
set PM_RECOMMENDED_INPUT_DEVICE=1
+or
+set PM_RECOMMENDED_OUTPUT_DEVICE=1
+
+
+

The user should first determine the available device ID by using the +supplied application "testin" or "testout".

+

In general, the registry is a better place for this kind of info. With +USB devices that can come and go, using integers is not very reliable +for device identification. Under Windows, if PM_RECOMMENDED_INPUT_DEVICE +(or PM_RECOMMENDED_OUTPUT_DEVICE) is NOT found in the environment, +then the default device is obtained by looking for a string in the registry +under:

+
HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
+or
+HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
+
+
+

The number of the first device with a substring that matches the +string exactly is returned. For example, if the string in the registry is +"USB" and device 1 is named "In USB MidiSport 1x1", then that will be +the default input because it contains the string "USB".

+

In addition to the name, get_device_info() returns "interf", which is +the interface name. The "interface" is the underlying software system or +API used by PortMidi to access devices. Supported interfaces:

+
MMSystem   # the only Win32 interface currently supported
+ALSA       # the only Linux interface currently supported
+CoreMIDI   # the only Mac OS X interface currently supported
+# DirectX - not implemented
+# OSS     - not implemented
+
+
+

To specify both the interface and the device name in the registry, separate +the two with a comma and a space. The string before the comma must be a +substring of the "interf" string and the string after the space must be a +substring of the "name" name string in order to match the device. e.g.:

+
MMSystem, In USB MidiSport 1x1
+
+
+
+

Note

+

In the current release, the default is simply the first device (the +input or output device with the lowest PmDeviceID).

+
+
+ +
+
+pygame.midi.get_default_output_id()¶
+
+
gets default output device number
+
get_default_output_id() -> default_id
+
+

See get_default_input_id() for usage details.

+
+ +
+
+pygame.midi.get_device_info()¶
+
+
returns information about a midi device
+
get_device_info(an_id) -> (interf, name, input, output, opened)
+
get_device_info(an_id) -> None
+
+

Gets the device info for a given id.

+
+
Parameters
+

an_id (int) -- id of the midi device being queried

+
+
Returns
+

if the id is out of range None is returned, otherwise +a tuple of (interf, name, input, output, opened) is returned.

+
+
    +
  • interf: string describing the device interface (e.g. 'ALSA')

  • +
  • name: string name of the device (e.g. 'Midi Through Port-0')

  • +
  • input: 1 if the device is an input device, otherwise 0

  • +
  • output: 1 if the device is an output device, otherwise 0

  • +
  • opened: 1 if the device is opened, otherwise 0

  • +
+
+

+
+
Return type
+

tuple or None

+
+
+
+ +
+
+pygame.midi.midis2events()¶
+
+
converts midi events to pygame events
+
midis2events(midi_events, device_id) -> [Event, ...]
+
+

Takes a sequence of midi events and returns list of pygame events.

+

The midi_events data is expected to be a sequence of +((status, data1, data2, data3), timestamp) midi events (all values +required).

+
+
Returns
+

a list of pygame events of event type MIDIIN

+
+
Return type
+

list

+
+
+
+ +
+
+pygame.midi.time()¶
+
+
returns the current time in ms of the PortMidi timer
+
time() -> time
+
+

The time is reset to 0 when the pygame.midipygame module for interacting with midi input and output. module is initialized.

+
+ +
+
+pygame.midi.frequency_to_midi()¶
+
+
Converts a frequency into a MIDI note. Rounds to the closest midi note.
+
frequency_to_midi(midi_note) -> midi_note
+
+

example:

+
frequency_to_midi(27.5) == 21
+
+
+
+

New in pygame 1.9.5.

+
+
+ +
+
+pygame.midi.midi_to_frequency()¶
+
+
Converts a midi note to a frequency.
+
midi_to_frequency(midi_note) -> frequency
+
+

example:

+
midi_to_frequency(21) == 27.5
+
+
+
+

New in pygame 1.9.5.

+
+
+ +
+
+pygame.midi.midi_to_ansi_note()¶
+
+
Returns the Ansi Note name for a midi number.
+
midi_to_ansi_note(midi_note) -> ansi_note
+
+

example:

+
midi_to_ansi_note(21) == 'A0'
+
+
+
+

New in pygame 1.9.5.

+
+
+ +
+
+exception pygame.midi.MidiException¶
+
+
exception that pygame.midi functions and classes can raise
+
MidiException(errno) -> None
+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mixer.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mixer.html new file mode 100644 index 0000000..c3dd8d6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mixer.html @@ -0,0 +1,995 @@ + + + + + + + + + pygame.mixer — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.mixer
+
+
pygame module for loading and playing sounds
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize the mixer module
+—preset the mixer init arguments
+—uninitialize the mixer
+—test if the mixer is initialized
+—stop playback of all sound channels
+—temporarily stop playback of all sound channels
+—resume paused playback of sound channels
+—fade out the volume on all sounds before stopping
+—set the total number of playback channels
+—get the total number of playback channels
+—reserve channels from being automatically used
+—find an unused channel
+—test if any sound is being mixed
+—get the mixer's SDL version
+—Create a new Sound object from a file or buffer object
+—Create a Channel object for controlling playback
+

This module contains classes for loading Sound objects and controlling +playback. The mixer module is optional and depends on SDL_mixer. Your program +should test that pygame.mixerpygame module for loading and playing sounds is available and initialized before using +it.

+

The mixer module has a limited number of channels for playback of sounds. +Usually programs tell pygame to start playing audio and it selects an available +channel automatically. The default is 8 simultaneous channels, but complex +programs can get more precise control over the number of channels and their +use.

+

All sound playback is mixed in background threads. When you begin to play a +Sound object, it will return immediately while the sound continues to play. A +single Sound object can also be actively played back multiple times.

+

The mixer also has a special streaming channel. This is for music playback and +is accessed through the pygame.mixer.musicpygame module for controlling streamed audio module. Consider using this +module for playing long running music. Unlike mixer module, the music module +streams the music from the files without loading music at once into memory.

+

The mixer module must be initialized like other pygame modules, but it has some +extra conditions. The pygame.mixer.init() function takes several optional +arguments to control the playback rate and sample size. Pygame will default to +reasonable values, but pygame cannot perform Sound resampling, so the mixer +should be initialized to match the values of your audio resources.

+

NOTE: For less laggy sound use a smaller buffer size. The default +is set to reduce the chance of scratchy sounds on some computers. You can +change the default buffer by calling pygame.mixer.pre_init()preset the mixer init arguments before +pygame.mixer.init()initialize the mixer module or pygame.init()initialize all imported pygame modules is called. For example: +pygame.mixer.pre_init(44100,-16,2, 1024)

+
+
+pygame.mixer.init()¶
+
+
initialize the mixer module
+
init(frequency=44100, size=-16, channels=2, buffer=512, devicename=None, allowedchanges=AUDIO_ALLOW_FREQUENCY_CHANGE | AUDIO_ALLOW_CHANNELS_CHANGE) -> None
+
+

Initialize the mixer module for Sound loading and playback. The default +arguments can be overridden to provide specific audio mixing. Keyword +arguments are accepted. For backwards compatibility, argument values of +0 are replaced with the startup defaults, except for allowedchanges, +where -1 is used. (startup defaults may be changed by a pre_init() call).

+

The size argument represents how many bits are used for each audio sample. +If the value is negative then signed sample values will be used. Positive +values mean unsigned audio samples will be used. An invalid value raises an +exception.

+

The channels argument is used to specify whether to use mono or stereo. 1 +for mono and 2 for stereo.

+

The buffer argument controls the number of internal samples used in the +sound mixer. The default value should work for most cases. It can be lowered +to reduce latency, but sound dropout may occur. It can be raised to larger +values to ensure playback never skips, but it will impose latency on sound +playback. The buffer size must be a power of two (if not it is rounded up to +the next nearest power of 2).

+

Some platforms require the pygame.mixerpygame module for loading and playing sounds module to be initialized +after the display modules have initialized. The top level pygame.init() +takes care of this automatically, but cannot pass any arguments to the mixer +init. To solve this, mixer has a function pygame.mixer.pre_init() to set +the proper defaults before the toplevel init is used.

+

When using allowedchanges=0 it will convert the samples at runtime to match +what the hardware supports. For example a sound card may not +support 16bit sound samples, so instead it will use 8bit samples internally. +If AUDIO_ALLOW_FORMAT_CHANGE is supplied, then the requested format will +change to the closest that SDL2 supports.

+

Apart from 0, allowedchanged accepts the following constants ORed together:

+
+
    +
  • AUDIO_ALLOW_FREQUENCY_CHANGE

  • +
  • AUDIO_ALLOW_FORMAT_CHANGE

  • +
  • AUDIO_ALLOW_CHANNELS_CHANGE

  • +
  • AUDIO_ALLOW_ANY_CHANGE

  • +
+
+

It is safe to call this more than once, but after the mixer is initialized +you cannot change the playback arguments without first calling +pygame.mixer.quit().

+
+

Changed in pygame 1.8: The default buffersize changed from 1024 to 3072.

+
+
+

Changed in pygame 1.9.1: The default buffersize changed from 3072 to 4096.

+
+
+

Changed in pygame 2.0.0: The default buffersize changed from 4096 to 512.

+
+
+

Changed in pygame 2.0.0: The default frequency changed from 22050 to 44100.

+
+
+

Changed in pygame 2.0.0: size can be 32 (32-bit floats).

+
+
+

Changed in pygame 2.0.0: channels can also be 4 or 6.

+
+
+

New in pygame 2.0.0: allowedchanges, devicename arguments added

+
+
+ +
+
+pygame.mixer.pre_init()¶
+
+
preset the mixer init arguments
+
pre_init(frequency=44100, size=-16, channels=2, buffer=512, devicename=None, allowedchanges=AUDIO_ALLOW_FREQUENCY_CHANGE | AUDIO_ALLOW_CHANNELS_CHANGE) -> None
+
+

Call pre_init to change the defaults used when the real +pygame.mixer.init() is called. Keyword arguments are accepted. The best +way to set custom mixer playback values is to call +pygame.mixer.pre_init() before calling the top level pygame.init(). +For backwards compatibility, argument values of 0 are replaced with the +startup defaults, except for allowedchanges, where -1 is used.

+
+

Changed in pygame 1.8: The default buffersize changed from 1024 to 3072.

+
+
+

Changed in pygame 1.9.1: The default buffersize changed from 3072 to 4096.

+
+
+

Changed in pygame 2.0.0: The default buffersize changed from 4096 to 512.

+
+
+

Changed in pygame 2.0.0: The default frequency changed from 22050 to 44100.

+
+
+

New in pygame 2.0.0: allowedchanges, devicename arguments added

+
+
+ +
+
+pygame.mixer.quit()¶
+
+
uninitialize the mixer
+
quit() -> None
+
+

This will uninitialize pygame.mixerpygame module for loading and playing sounds. All playback will stop and any +loaded Sound objects may not be compatible with the mixer if it is +reinitialized later.

+
+ +
+
+pygame.mixer.get_init()¶
+
+
test if the mixer is initialized
+
get_init() -> (frequency, format, channels)
+
+

If the mixer is initialized, this returns the playback arguments it is +using. If the mixer has not been initialized this returns None.

+
+ +
+
+pygame.mixer.stop()¶
+
+
stop playback of all sound channels
+
stop() -> None
+
+

This will stop all playback of all active mixer channels.

+
+ +
+
+pygame.mixer.pause()¶
+
+
temporarily stop playback of all sound channels
+
pause() -> None
+
+

This will temporarily stop all playback on the active mixer channels. The +playback can later be resumed with pygame.mixer.unpause()

+
+ +
+
+pygame.mixer.unpause()¶
+
+
resume paused playback of sound channels
+
unpause() -> None
+
+

This will resume all active sound channels after they have been paused.

+
+ +
+
+pygame.mixer.fadeout()¶
+
+
fade out the volume on all sounds before stopping
+
fadeout(time) -> None
+
+

This will fade out the volume on all active channels over the time argument +in milliseconds. After the sound is muted the playback will stop.

+
+ +
+
+pygame.mixer.set_num_channels()¶
+
+
set the total number of playback channels
+
set_num_channels(count) -> None
+
+

Sets the number of available channels for the mixer. The default value is 8. +The value can be increased or decreased. If the value is decreased, sounds +playing on the truncated channels are stopped.

+
+ +
+
+pygame.mixer.get_num_channels()¶
+
+
get the total number of playback channels
+
get_num_channels() -> count
+
+

Returns the number of currently active playback channels.

+
+ +
+
+pygame.mixer.set_reserved()¶
+
+
reserve channels from being automatically used
+
set_reserved(count) -> count
+
+

The mixer can reserve any number of channels that will not be automatically +selected for playback by Sounds. If sounds are currently playing on the +reserved channels they will not be stopped.

+

This allows the application to reserve a specific number of channels for +important sounds that must not be dropped or have a guaranteed channel to +play on.

+

Will return number of channels actually reserved, this may be less than requested +depending on the number of channels previously allocated.

+
+ +
+
+pygame.mixer.find_channel()¶
+
+
find an unused channel
+
find_channel(force=False) -> Channel
+
+

This will find and return an inactive Channel object. If there are no +inactive Channels this function will return None. If there are no +inactive channels and the force argument is True, this will find the +Channel with the longest running Sound and return it.

+
+ +
+
+pygame.mixer.get_busy()¶
+
+
test if any sound is being mixed
+
get_busy() -> bool
+
+

Returns True if the mixer is busy mixing any channels. If the mixer is +idle then this return False.

+
+ +
+
+pygame.mixer.get_sdl_mixer_version()¶
+
+
get the mixer's SDL version
+
get_sdl_mixer_version() -> (major, minor, patch)
+
get_sdl_mixer_version(linked=True) -> (major, minor, patch)
+
+
+
Parameters
+

linked (bool) -- if True (default) the linked version number is +returned, otherwise the compiled version number is returned

+
+
Returns
+

the mixer's SDL library version number (linked or compiled +depending on the linked parameter) as a tuple of 3 integers +(major, minor, patch)

+
+
Return type
+

tuple

+
+
+
+

Note

+

The linked and compile version numbers should be the same.

+
+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.mixer.Sound¶
+
+
Create a new Sound object from a file or buffer object
+
Sound(filename) -> Sound
+
Sound(file=filename) -> Sound
+
Sound(file=pathlib_path) -> Sound
+
Sound(buffer) -> Sound
+
Sound(buffer=buffer) -> Sound
+
Sound(object) -> Sound
+
Sound(file=object) -> Sound
+
Sound(array=object) -> Sound
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—begin sound playback
+—stop sound playback
+—stop sound playback after fading out
+—set the playback volume for this Sound
+—get the playback volume
+—count how many times this Sound is playing
+—get the length of the Sound
+—return a bytestring copy of the Sound samples.
+

Load a new sound buffer from a filename, a python file object or a readable +buffer object. Limited resampling will be performed to help the sample match +the initialize arguments for the mixer. A Unicode string can only be a file +pathname. A bytes object can be either a pathname or a buffer object. +Use the 'file' or 'buffer' keywords to avoid ambiguity; otherwise Sound may +guess wrong. If the array keyword is used, the object is expected to export +a new buffer interface (The object is checked for a buffer interface first.)

+

The Sound object represents actual sound sample data. Methods that change +the state of the Sound object will the all instances of the Sound playback. +A Sound object also exports a new buffer interface.

+

The Sound can be loaded from an OGG audio file or from an uncompressed +WAV.

+

Note: The buffer will be copied internally, no data will be shared between +it and the Sound object.

+

For now buffer and array support is consistent with sndarray.make_sound +for Numeric arrays, in that sample sign and byte order are ignored. This +will change, either by correctly handling sign and byte order, or by raising +an exception when different. Also, source samples are truncated to fit the +audio sample size. This will not change.

+
+

New in pygame 1.8: pygame.mixer.Sound(buffer)

+
+
+

New in pygame 1.9.2: pygame.mixer.SoundCreate a new Sound object from a file or buffer object keyword arguments and array interface support

+
+
+

New in pygame 2.0.1: pathlib.Path support on Python 3.

+
+
+
+play()¶
+
+
begin sound playback
+
play(loops=0, maxtime=0, fade_ms=0) -> Channel
+
+

Begin playback of the Sound (i.e., on the computer's speakers) on an +available Channel. This will forcibly select a Channel, so playback may +cut off a currently playing sound if necessary.

+

The loops argument controls how many times the sample will be repeated +after being played the first time. A value of 5 means that the sound will +be played once, then repeated five times, and so is played a total of six +times. The default value (zero) means the Sound is not repeated, and so +is only played once. If loops is set to -1 the Sound will loop +indefinitely (though you can still call stop() to stop it).

+

The maxtime argument can be used to stop playback after a given number of +milliseconds.

+

The fade_ms argument will make the sound start playing at 0 volume and +fade up to full volume over the time given. The sample may end before the +fade-in is complete.

+

This returns the Channel object for the channel that was selected.

+
+ +
+
+stop()¶
+
+
stop sound playback
+
stop() -> None
+
+

This will stop the playback of this Sound on any active Channels.

+
+ +
+
+fadeout()¶
+
+
stop sound playback after fading out
+
fadeout(time) -> None
+
+

This will stop playback of the sound after fading it out over the time +argument in milliseconds. The Sound will fade and stop on all actively +playing channels.

+
+ +
+
+set_volume()¶
+
+
set the playback volume for this Sound
+
set_volume(value) -> None
+
+

This will set the playback volume (loudness) for this Sound. This will +immediately affect the Sound if it is playing. It will also affect any +future playback of this Sound.

+
+
Parameters
+

value (float) --

volume in the range of 0.0 to 1.0 (inclusive)

+
+
If value < 0.0, the volume will not be changed
+
If value > 1.0, the volume will be set to 1.0
+
+

+
+
+
+ +
+
+get_volume()¶
+
+
get the playback volume
+
get_volume() -> value
+
+

Return a value from 0.0 to 1.0 representing the volume for this Sound.

+
+ +
+
+get_num_channels()¶
+
+
count how many times this Sound is playing
+
get_num_channels() -> count
+
+

Return the number of active channels this sound is playing on.

+
+ +
+
+get_length()¶
+
+
get the length of the Sound
+
get_length() -> seconds
+
+

Return the length of this Sound in seconds.

+
+ +
+
+get_raw()¶
+
+
return a bytestring copy of the Sound samples.
+
get_raw() -> bytes
+
+

Return a copy of the Sound object buffer as a bytes.

+
+

New in pygame 1.9.2.

+
+
+ +
+ +
+
+pygame.mixer.Channel¶
+
+
Create a Channel object for controlling playback
+
Channel(id) -> Channel
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—play a Sound on a specific Channel
+—stop playback on a Channel
+—temporarily stop playback of a channel
+—resume pause playback of a channel
+—stop playback after fading channel out
+—set the volume of a playing channel
+—get the volume of the playing channel
+—check if the channel is active
+—get the currently playing Sound
+—queue a Sound object to follow the current
+—return any Sound that is queued
+—have the channel send an event when playback stops
+—get the event a channel sends when playback stops
+

Return a Channel object for one of the current channels. The id must be a +value from 0 to the value of pygame.mixer.get_num_channels().

+

The Channel object can be used to get fine control over the playback of +Sounds. A channel can only playback a single Sound at time. Using channels +is entirely optional since pygame can manage them by default.

+
+
+play()¶
+
+
play a Sound on a specific Channel
+
play(Sound, loops=0, maxtime=0, fade_ms=0) -> None
+
+

This will begin playback of a Sound on a specific Channel. If the Channel +is currently playing any other Sound it will be stopped.

+

The loops argument has the same meaning as in Sound.play(): it is the +number of times to repeat the sound after the first time. If it is 3, the +sound will be played 4 times (the first time, then three more). If loops +is -1 then the playback will repeat indefinitely.

+

As in Sound.play(), the maxtime argument can be used to stop playback +of the Sound after a given number of milliseconds.

+

As in Sound.play(), the fade_ms argument can be used fade in the +sound.

+
+ +
+
+stop()¶
+
+
stop playback on a Channel
+
stop() -> None
+
+

Stop sound playback on a channel. After playback is stopped the channel +becomes available for new Sounds to play on it.

+
+ +
+
+pause()¶
+
+
temporarily stop playback of a channel
+
pause() -> None
+
+

Temporarily stop the playback of sound on a channel. It can be resumed at +a later time with Channel.unpause()

+
+ +
+
+unpause()¶
+
+
resume pause playback of a channel
+
unpause() -> None
+
+

Resume the playback on a paused channel.

+
+ +
+
+fadeout()¶
+
+
stop playback after fading channel out
+
fadeout(time) -> None
+
+

Stop playback of a channel after fading out the sound over the given time +argument in milliseconds.

+
+ +
+
+set_volume()¶
+
+
set the volume of a playing channel
+
set_volume(value) -> None
+
set_volume(left, right) -> None
+
+

Set the volume (loudness) of a playing sound. When a channel starts to +play its volume value is reset. This only affects the current sound. The +value argument is between 0.0 and 1.0.

+

If one argument is passed, it will be the volume of both speakers. If two +arguments are passed and the mixer is in stereo mode, the first argument +will be the volume of the left speaker and the second will be the volume +of the right speaker. (If the second argument is None, the first +argument will be the volume of both speakers.)

+

If the channel is playing a Sound on which set_volume() has also been +called, both calls are taken into account. For example:

+
sound = pygame.mixer.Sound("s.wav")
+channel = s.play()      # Sound plays at full volume by default
+sound.set_volume(0.9)   # Now plays at 90% of full volume.
+sound.set_volume(0.6)   # Now plays at 60% (previous value replaced).
+channel.set_volume(0.5) # Now plays at 30% (0.6 * 0.5).
+
+
+
+ +
+
+get_volume()¶
+
+
get the volume of the playing channel
+
get_volume() -> value
+
+

Return the volume of the channel for the current playing sound. This does +not take into account stereo separation used by +Channel.set_volume(). The Sound object also has its own volume +which is mixed with the channel.

+
+ +
+
+get_busy()¶
+
+
check if the channel is active
+
get_busy() -> bool
+
+

Returns True if the channel is actively mixing sound. If the channel +is idle this returns False.

+
+ +
+
+get_sound()¶
+
+
get the currently playing Sound
+
get_sound() -> Sound
+
+

Return the actual Sound object currently playing on this channel. If the +channel is idle None is returned.

+
+ +
+
+queue()¶
+
+
queue a Sound object to follow the current
+
queue(Sound) -> None
+
+

When a Sound is queued on a Channel, it will begin playing immediately +after the current Sound is finished. Each channel can only have a single +Sound queued at a time. The queued Sound will only play if the current +playback finished automatically. It is cleared on any other call to +Channel.stop() or Channel.play().

+

If there is no sound actively playing on the Channel then the Sound will +begin playing immediately.

+
+ +
+
+get_queue()¶
+
+
return any Sound that is queued
+
get_queue() -> Sound
+
+

If a Sound is already queued on this channel it will be returned. Once +the queued sound begins playback it will no longer be on the queue.

+
+ +
+
+set_endevent()¶
+
+
have the channel send an event when playback stops
+
set_endevent() -> None
+
set_endevent(type) -> None
+
+

When an endevent is set for a channel, it will send an event to the +pygame queue every time a sound finishes playing on that channel (not +just the first time). Use pygame.event.get() to retrieve the endevent +once it's sent.

+

Note that if you called Sound.play(n) or Channel.play(sound,n), +the end event is sent only once: after the sound has been played "n+1" +times (see the documentation of Sound.play).

+

If Channel.stop() or Channel.play() is called while the sound was +still playing, the event will be posted immediately.

+

The type argument will be the event id sent to the queue. This can be any +valid event type, but a good choice would be a value between +pygame.locals.USEREVENT and pygame.locals.NUMEVENTS. If no type +argument is given then the Channel will stop sending endevents.

+
+ +
+
+get_endevent()¶
+
+
get the event a channel sends when playback stops
+
get_endevent() -> type
+
+

Returns the event type to be sent every time the Channel finishes +playback of a Sound. If there is no endevent the function returns +pygame.NOEVENT.

+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mouse.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mouse.html new file mode 100644 index 0000000..e5d99d6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/mouse.html @@ -0,0 +1,406 @@ + + + + + + + + + pygame.mouse — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.mouse
+
+
pygame module to work with the mouse
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—get the state of the mouse buttons
+—get the mouse cursor position
+—get the amount of mouse movement
+—set the mouse cursor position
+—hide or show the mouse cursor
+—get the current visibility state of the mouse cursor
+—check if the display is receiving mouse input
+—set the mouse cursor to a new cursor
+—get the current mouse cursor
+

The mouse functions can be used to get the current state of the mouse device. +These functions can also alter the system cursor for the mouse.

+

When the display mode is set, the event queue will start receiving mouse +events. The mouse buttons generate pygame.MOUSEBUTTONDOWN and +pygame.MOUSEBUTTONUP events when they are pressed and released. These +events contain a button attribute representing which button was pressed. The +mouse wheel will generate pygame.MOUSEBUTTONDOWN and +pygame.MOUSEBUTTONUP events when rolled. The button will be set to 4 +when the wheel is rolled up, and to button 5 when the wheel is rolled down. +Whenever the mouse is moved it generates a pygame.MOUSEMOTION event. The +mouse movement is broken into small and accurate motion events. As the mouse +is moving many motion events will be placed on the queue. Mouse motion events +that are not properly cleaned from the event queue are the primary reason the +event queue fills up.

+

If the mouse cursor is hidden, and input is grabbed to the current display the +mouse will enter a virtual input mode, where the relative movements of the +mouse will never be stopped by the borders of the screen. See the functions +pygame.mouse.set_visible() and pygame.event.set_grab() to get this +configured.

+

Mouse Wheel Behavior in pygame 2

+

There is proper functionality for mouse wheel behaviour with pygame 2 supporting +pygame.MOUSEWHEEL events. The new events support horizontal and vertical +scroll movements, with signed integer values representing the amount scrolled +(x and y), as well as flipped direction (the set positive and +negative values for each axis is flipped). Read more about SDL2 +input-related changes here https://wiki.libsdl.org/MigrationGuide#Input

+

In pygame 2, the mouse wheel functionality can be used by listening for the +pygame.MOUSEWHEEL type of an event (Bear in mind they still emit +pygame.MOUSEBUTTONDOWN events like in pygame 1.x, as well). +When this event is triggered, a developer can access the appropriate Event object +with pygame.event.get(). The object can be used to access data about the mouse +scroll, such as which (it will tell you what exact mouse device trigger the event).

+
+
Code example of mouse scroll (tested on 2.0.0.dev7)¶
+
# Taken from husano896's PR thread (slightly modified)
+import pygame
+from pygame.locals import *
+pygame.init()
+screen = pygame.display.set_mode((640, 480))
+clock = pygame.time.Clock()
+
+def main():
+   while True:
+      for event in pygame.event.get():
+            if event.type == QUIT:
+               pygame.quit()
+               return
+            elif event.type == MOUSEWHEEL:
+               print(event)
+               print(event.x, event.y)
+               print(event.flipped)
+               print(event.which)
+               # can access properties with
+               # proper notation(ex: event.y)
+      clock.tick(60)
+
+# Execute game:
+main()
+
+
+
+
+
+pygame.mouse.get_pressed()¶
+
+
get the state of the mouse buttons
+
get_pressed(num_buttons=3) -> (button1, button2, button3)
+
get_pressed(num_buttons=5) -> (button1, button2, button3, button4, button5)
+
+

Returns a sequence of booleans representing the state of all the mouse +buttons. A true value means the mouse is currently being pressed at the time +of the call.

+

Note, to get all of the mouse events it is better to use either +pygame.event.wait() or pygame.event.get() and check all of those +events to see if they are MOUSEBUTTONDOWN, MOUSEBUTTONUP, or +MOUSEMOTION.

+

Note, that on X11 some X servers use middle button emulation. When you +click both buttons 1 and 3 at the same time a 2 button event +can be emitted.

+

Note, remember to call pygame.event.get() before this function. +Otherwise it will not work as expected.

+

To support five button mice, an optional parameter num_buttons has been +added in pygame 2. When this is set to 5, button4 and button5 +are added to the returned tuple. Only 3 and 5 are valid values +for this parameter.

+
+

Changed in pygame 2.0.0: num_buttons argument added

+
+
+ +
+
+pygame.mouse.get_pos()¶
+
+
get the mouse cursor position
+
get_pos() -> (x, y)
+
+

Returns the x and y position of the mouse cursor. The position is +relative to the top-left corner of the display. The cursor position can be +located outside of the display window, but is always constrained to the +screen.

+
+ +
+
+pygame.mouse.get_rel()¶
+
+
get the amount of mouse movement
+
get_rel() -> (x, y)
+
+

Returns the amount of movement in x and y since the previous call to +this function. The relative movement of the mouse cursor is constrained to +the edges of the screen, but see the virtual input mouse mode for a way +around this. Virtual input mode is described at the top of the page.

+
+ +
+
+pygame.mouse.set_pos()¶
+
+
set the mouse cursor position
+
set_pos([x, y]) -> None
+
+

Set the current mouse position to arguments given. If the mouse cursor is +visible it will jump to the new coordinates. Moving the mouse will generate +a new pygame.MOUSEMOTION event.

+
+ +
+
+pygame.mouse.set_visible()¶
+
+
hide or show the mouse cursor
+
set_visible(bool) -> bool
+
+

If the bool argument is true, the mouse cursor will be visible. This will +return the previous visible state of the cursor.

+
+ +
+
+pygame.mouse.get_visible()¶
+
+
get the current visibility state of the mouse cursor
+
get_visible() -> bool
+
+

Get the current visibility state of the mouse cursor. True if the mouse is +visible, False otherwise.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.mouse.get_focused()¶
+
+
check if the display is receiving mouse input
+
get_focused() -> bool
+
+

Returns true when pygame is receiving mouse input events (or, in windowing +terminology, is "active" or has the "focus").

+

This method is most useful when working in a window. By contrast, in +full-screen mode, this method always returns true.

+

Note: under MS Windows, the window that has the mouse focus also has the +keyboard focus. But under X-Windows, one window can receive mouse events and +another receive keyboard events. pygame.mouse.get_focused() indicates +whether the pygame window receives mouse events.

+
+ +
+
+pygame.mouse.set_cursor()¶
+
+
set the mouse cursor to a new cursor
+
set_cursor(pygame.cursors.Cursor) -> None
+
set_cursor(size, hotspot, xormasks, andmasks) -> None
+
set_cursor(hotspot, surface) -> None
+
set_cursor(constant) -> None
+
+

Set the mouse cursor to something new. This function accepts either an explicit +Cursor object or arguments to create a Cursor object.

+

See pygame.cursors.Cursorpygame object representing a cursor for help creating cursors and for examples.

+
+

Changed in pygame 2.0.1.

+
+
+ +
+
+pygame.mouse.get_cursor()¶
+
+
get the current mouse cursor
+
get_cursor() -> pygame.cursors.Cursor
+
+

Get the information about the mouse system cursor. The return value contains +the same data as the arguments passed into pygame.mouse.set_cursor()set the mouse cursor to a new cursor.

+
+

Note

+

Code that unpacked a get_cursor() call into +size, hotspot, xormasks, andmasks will still work, +assuming the call returns an old school type cursor.

+
+
+

Changed in pygame 2.0.1.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/music.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/music.html new file mode 100644 index 0000000..5be9b88 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/music.html @@ -0,0 +1,502 @@ + + + + + + + + + pygame.mixer.music — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.mixer.music
+
+
pygame module for controlling streamed audio
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Load a music file for playback
+—Unload the currently loaded music to free up resources
+—Start the playback of the music stream
+—restart music
+—stop the music playback
+—temporarily stop music playback
+—resume paused music
+—stop music playback after fading out
+—set the music volume
+—get the music volume
+—check if the music stream is playing
+—set position to play from
+—get the music play time
+—queue a sound file to follow the current
+—have the music send an event when playback stops
+—get the event a channel sends when playback stops
+

The music module is closely tied to pygame.mixerpygame module for loading and playing sounds. Use the music module +to control the playback of music in the sound mixer.

+

The difference between the music playback and regular Sound playback is that +the music is streamed, and never actually loaded all at once. The mixer system +only supports a single music stream at once.

+

On older pygame versions, MP3 support was limited under Mac and Linux. This +changed in pygame v2.0.2 which got improved MP3 support. Consider using +OGG file format for music as that can give slightly better compression than +MP3 in most cases.

+
+
+pygame.mixer.music.load()¶
+
+
Load a music file for playback
+
load(filename) -> None
+
load(fileobj, namehint="") -> None
+
+

This will load a music filename/file object and prepare it for playback. If +a music stream is already playing it will be stopped. This does not start +the music playing.

+

If you are loading from a file object, the namehint parameter can be used to specify +the type of music data in the object. For example: load(fileobj, "ogg").

+
+

Changed in pygame 2.0.2: Added optional namehint argument

+
+
+ +
+
+pygame.mixer.music.unload()¶
+
+
Unload the currently loaded music to free up resources
+
unload() -> None
+
+

This closes resources like files for any music that may be loaded.

+
+

New in pygame 2.0.0.

+
+
+ +
+
+pygame.mixer.music.play()¶
+
+
Start the playback of the music stream
+
play(loops=0, start=0.0, fade_ms=0) -> None
+
+

This will play the loaded music stream. If the music is already playing it +will be restarted.

+

loops is an optional integer argument, which is 0 by default, which +indicates how many times to repeat the music. The music repeats indefinitely if +this argument is set to -1.

+

start is an optional float argument, which is 0.0 by default, which +denotes the position in time from which the music starts playing. The starting +position depends on the format of the music played. MP3 and OGG use +the position as time in seconds. For MP3 files the start time position +selected may not be accurate as things like variable bit rate encoding and ID3 +tags can throw off the timing calculations. For MOD music it is the pattern +order number. Passing a start position will raise a NotImplementedError if +the start position cannot be set.

+

fade_ms is an optional integer argument, which is 0 by default, +which denotes the period of time (in milliseconds) over which the music +will fade up from volume level 0.0 to full volume (or the volume level +previously set by set_volume()). The sample may end before the fade-in +is complete. If the music is already streaming fade_ms is ignored.

+
+

Changed in pygame 2.0.0: Added optional fade_ms argument

+
+
+ +
+
+pygame.mixer.music.rewind()¶
+
+
restart music
+
rewind() -> None
+
+

Resets playback of the current music to the beginning. If pause() has +previoulsy been used to pause the music, the music will remain paused.

+
+

Note

+

rewind() supports a limited number of file types and notably +WAV files are NOT supported. For unsupported file types use play() +which will restart the music that's already playing (note that this +will start the music playing again even if previously paused).

+
+
+ +
+
+pygame.mixer.music.stop()¶
+
+
stop the music playback
+
stop() -> None
+
+

Stops the music playback if it is currently playing. +endevent will be triggered, if set. +It won't unload the music.

+
+ +
+
+pygame.mixer.music.pause()¶
+
+
temporarily stop music playback
+
pause() -> None
+
+

Temporarily stop playback of the music stream. It can be resumed with the +unpause() function.

+
+ +
+
+pygame.mixer.music.unpause()¶
+
+
resume paused music
+
unpause() -> None
+
+

This will resume the playback of a music stream after it has been paused.

+
+ +
+
+pygame.mixer.music.fadeout()¶
+
+
stop music playback after fading out
+
fadeout(time) -> None
+
+

Fade out and stop the currently playing music.

+

The time argument denotes the integer milliseconds for which the +fading effect is generated.

+

Note, that this function blocks until the music has faded out. Calls +to fadeout() and set_volume() will have no effect during +this time. If an event was set using set_endevent() it will be +called after the music has faded.

+
+ +
+
+pygame.mixer.music.set_volume()¶
+
+
set the music volume
+
set_volume(volume) -> None
+
+

Set the volume of the music playback.

+

The volume argument is a float between 0.0 and 1.0 that sets +the volume level. When new music is loaded the volume is reset to full +volume. If volume is a negative value it will be ignored and the +volume will remain set at the current level. If the volume argument +is greater than 1.0, the volume will be set to 1.0.

+
+ +
+
+pygame.mixer.music.get_volume()¶
+
+
get the music volume
+
get_volume() -> value
+
+

Returns the current volume for the mixer. The value will be between 0.0 +and 1.0.

+
+ +
+
+pygame.mixer.music.get_busy()¶
+
+
check if the music stream is playing
+
get_busy() -> bool
+
+

Returns True when the music stream is actively playing. When the music is +idle this returns False. In pygame 2.0.1 and above this function returns +False when the music is paused. In pygame 1 it returns True when the music +is paused.

+
+

Changed in pygame 2.0.1: Returns False when music paused.

+
+
+ +
+
+pygame.mixer.music.set_pos()¶
+
+
set position to play from
+
set_pos(pos) -> None
+
+

This sets the position in the music file where playback will start. +The meaning of "pos", a float (or a number that can be converted to a float), +depends on the music format.

+

For MOD files, pos is the integer pattern number in the module. +For OGG it is the absolute position, in seconds, from +the beginning of the sound. For MP3 files, it is the relative position, +in seconds, from the current position. For absolute positioning in an MP3 +file, first call rewind().

+

Other file formats are unsupported. Newer versions of SDL_mixer have +better positioning support than earlier ones. An SDLError is raised if a +particular format does not support positioning.

+

Function set_pos() calls underlining SDL_mixer function +Mix_SetMusicPosition.

+
+

New in pygame 1.9.2.

+
+
+ +
+
+pygame.mixer.music.get_pos()¶
+
+
get the music play time
+
get_pos() -> time
+
+

This gets the number of milliseconds that the music has been playing for. +The returned time only represents how long the music has been playing; it +does not take into account any starting position offsets.

+
+ +
+
+pygame.mixer.music.queue()¶
+
+
queue a sound file to follow the current
+
queue(filename) -> None
+
queue(fileobj, namehint="", loops=0) -> None
+
+

This will load a sound file and queue it. A queued sound file will begin as +soon as the current sound naturally ends. Only one sound can be queued at a +time. Queuing a new sound while another sound is queued will result in the +new sound becoming the queued sound. Also, if the current sound is ever +stopped or changed, the queued sound will be lost.

+

If you are loading from a file object, the namehint parameter can be used to specify +the type of music data in the object. For example: queue(fileobj, "ogg").

+

The following example will play music by Bach six times, then play music by +Mozart once:

+
pygame.mixer.music.load('bach.ogg')
+pygame.mixer.music.play(5)        # Plays six times, not five!
+pygame.mixer.music.queue('mozart.ogg')
+
+
+
+

Changed in pygame 2.0.2: Added optional namehint argument

+
+
+ +
+
+pygame.mixer.music.set_endevent()¶
+
+
have the music send an event when playback stops
+
set_endevent() -> None
+
set_endevent(type) -> None
+
+

This causes pygame to signal (by means of the event queue) when the music is +done playing. The argument determines the type of event that will be queued.

+

The event will be queued every time the music finishes, not just the first +time. To stop the event from being queued, call this method with no +argument.

+
+ +
+
+pygame.mixer.music.get_endevent()¶
+
+
get the event a channel sends when playback stops
+
get_endevent() -> type
+
+

Returns the event type to be sent every time the music finishes playback. If +there is no endevent the function returns pygame.NOEVENT.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/overlay.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/overlay.html new file mode 100644 index 0000000..c14d2a8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/overlay.html @@ -0,0 +1,231 @@ + + + + + + + + + pygame.Overlay — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Warning

+

This module is non functional in pygame 2.0 and above, unless you have manually compiled pygame with SDL1. +This module will not be supported in the future.

+
+
+
+pygame.Overlay¶
+
+
pygame object for video overlay graphics
+
Overlay(format, (width, height)) -> Overlay
+
+ +++++ + + + + + + + + + + + + + + +
+—set the overlay pixel data
+—control where the overlay is displayed
+—test if the Overlay is hardware accelerated
+

The Overlay objects provide support for accessing hardware video overlays. +Video overlays do not use standard RGB pixel formats, and can use +multiple resolutions of data to create a single image.

+

The Overlay objects represent lower level access to the display hardware. To +use the object you must understand the technical details of video overlays.

+

The Overlay format determines the type of pixel data used. Not all hardware +will support all types of overlay formats. Here is a list of available +format types:

+
YV12_OVERLAY, IYUV_OVERLAY, YUY2_OVERLAY, UYVY_OVERLAY, YVYU_OVERLAY
+
+
+

The width and height arguments control the size for the overlay image data. +The overlay image can be displayed at any size, not just the resolution of +the overlay.

+

The overlay objects are always visible, and always show above the regular +display contents.

+
+
+display()¶
+
+
set the overlay pixel data
+
display((y, u, v)) -> None
+
display() -> None
+
+

Display the YUV data in SDL's overlay planes. The y, u, and v arguments +are strings of binary data. The data must be in the correct format used +to create the Overlay.

+

If no argument is passed in, the Overlay will simply be redrawn with the +current data. This can be useful when the Overlay is not really hardware +accelerated.

+

The strings are not validated, and improperly sized strings could crash +the program.

+
+ +
+
+set_location()¶
+
+
control where the overlay is displayed
+
set_location(rect) -> None
+
+

Set the location for the overlay. The overlay will always be shown +relative to the main display Surface. This does not actually redraw the +overlay, it will be updated on the next call to Overlay.display().

+
+ +
+
+get_hardware()¶
+
+
test if the Overlay is hardware accelerated
+
get_hardware(rect) -> int
+
+

Returns a True value when the Overlay is hardware accelerated. If the +platform does not support acceleration, software rendering is used.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pixelarray.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pixelarray.html new file mode 100644 index 0000000..96c7e6f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pixelarray.html @@ -0,0 +1,486 @@ + + + + + + + + + pygame.PixelArray — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.PixelArray¶
+
+
pygame object for direct pixel access of surfaces
+
PixelArray(Surface) -> PixelArray
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Gets the Surface the PixelArray uses.
+—Returns the byte size of a pixel array item
+—Returns the number of dimensions.
+—Returns the array size.
+—Returns byte offsets for each array dimension.
+—Creates a new Surface from the current PixelArray.
+—Replaces the passed color in the PixelArray with another one.
+—Extracts the passed color from the PixelArray.
+—Compares the PixelArray with another one.
+—Exchanges the x and y axis.
+—Closes the PixelArray, and releases Surface lock.
+

The PixelArray wraps a Surface and provides direct access to the +surface's pixels. A pixel array can be one or two dimensional. +A two dimensional array, like its surface, is indexed [column, row]. +Pixel arrays support slicing, both for returning a subarray or +for assignment. A pixel array sliced on a single column or row +returns a one dimensional pixel array. Arithmetic and other operations +are not supported. A pixel array can be safely assigned to itself. +Finally, pixel arrays export an array struct interface, allowing +them to interact with pygame.pixelcopypygame module for general pixel array copying methods and NumPy +arrays.

+

A PixelArray pixel item can be assigned a raw integer values, a +pygame.Colorpygame object for color representations instance, or a (r, g, b[, a]) tuple.

+
pxarray[x, y] = 0xFF00FF
+pxarray[x, y] = pygame.Color(255, 0, 255)
+pxarray[x, y] = (255, 0, 255)
+
+
+

However, only a pixel's integer value is returned. So, to compare a pixel +to a particular color the color needs to be first mapped using +the Surface.map_rgb() method of the Surface object for which the +PixelArray was created.

+
pxarray = pygame.PixelArray(surface)
+# Check, if the first pixel at the topleft corner is blue
+if pxarray[0, 0] == surface.map_rgb((0, 0, 255)):
+    ...
+
+
+

When assigning to a range of of pixels, a non tuple sequence of colors or +a PixelArray can be used as the value. For a sequence, the length must +match the PixelArray width.

+
pxarray[a:b] = 0xFF00FF                   # set all pixels to 0xFF00FF
+pxarray[a:b] = (0xFF00FF, 0xAACCEE, ... ) # first pixel = 0xFF00FF,
+                                          # second pixel  = 0xAACCEE, ...
+pxarray[a:b] = [(255, 0, 255), (170, 204, 238), ...] # same as above
+pxarray[a:b] = [(255, 0, 255), 0xAACCEE, ...]        # same as above
+pxarray[a:b] = otherarray[x:y]            # slice sizes must match
+
+
+

For PixelArray assignment, if the right hand side array has a row length +of 1, then the column is broadcast over the target array's rows. An +array of height 1 is broadcast over the target's columns, and is equivalent +to assigning a 1D PixelArray.

+

Subscript slices can also be used to assign to a rectangular subview of +the target PixelArray.

+
# Create some new PixelArray objects providing a different view
+# of the original array/surface.
+newarray = pxarray[2:4, 3:5]
+otherarray = pxarray[::2, ::2]
+
+
+

Subscript slices can also be used to do fast rectangular pixel manipulations +instead of iterating over the x or y axis. The

+
pxarray[::2, :] = (0, 0, 0)               # Make even columns black.
+pxarray[::2] = (0, 0, 0)                  # Same as [::2, :]
+
+
+

During its lifetime, the PixelArray locks the surface, thus you explicitly +have to close() it once its not used any more and the surface should perform +operations in the same scope. It is best to use it as a context manager +using the with PixelArray(surf) as pixel_array: style. So it works on pypy too.

+

A simple : slice index for the column can be omitted.

+
pxarray[::2, ...] = (0, 0, 0)             # Same as pxarray[::2, :]
+pxarray[...] = (255, 0, 0)                # Same as pxarray[:]
+
+
+

A note about PixelArray to PixelArray assignment, for arrays with an +item size of 3 (created from 24 bit surfaces) pixel values are translated +from the source to the destinations format. The red, green, and blue +color elements of each pixel are shifted to match the format of the +target surface. For all other pixel sizes no such remapping occurs. +This should change in later pygame releases, where format conversions +are performed for all pixel sizes. To avoid code breakage when full mapped +copying is implemented it is suggested PixelArray to PixelArray copies be +only between surfaces of identical format.

+
+

New in pygame 1.9.4:

+
    +
  • close() method was added. For explicitly cleaning up.

  • +
  • being able to use PixelArray as a context manager for cleanup.

  • +
  • both of these are useful for when working without reference counting (pypy).

  • +
+
+
+

New in pygame 1.9.2:

+
    +
  • array struct interface

  • +
  • transpose method

  • +
  • broadcasting for a length 1 dimension

  • +
+
+
+

Changed in pygame 1.9.2:

+
    +
  • A 2D PixelArray can have a length 1 dimension. +Only an integer index on a 2D PixelArray returns a 1D array.

  • +
  • For assignment, a tuple can only be a color. Any other sequence type +is a sequence of colors.

  • +
+
+
+
+surface¶
+
+
Gets the Surface the PixelArray uses.
+
surface -> Surface
+
+

The Surface the PixelArray was created for.

+
+ +
+
+itemsize¶
+
+
Returns the byte size of a pixel array item
+
itemsize -> int
+
+

This is the same as Surface.get_bytesize() for the +pixel array's surface.

+
+

New in pygame 1.9.2.

+
+
+ +
+
+ndim¶
+
+
Returns the number of dimensions.
+
ndim -> int
+
+

A pixel array can be 1 or 2 dimensional.

+
+

New in pygame 1.9.2.

+
+
+ +
+
+shape¶
+
+
Returns the array size.
+
shape -> tuple of int's
+
+

A tuple or length ndim giving the length of each +dimension. Analogous to Surface.get_size().

+
+

New in pygame 1.9.2.

+
+
+ +
+
+strides¶
+
+
Returns byte offsets for each array dimension.
+
strides -> tuple of int's
+
+

A tuple or length ndim byte counts. When a stride is +multiplied by the corresponding index it gives the offset +of that index from the start of the array. A stride is negative +for an array that has is inverted (has a negative step).

+
+

New in pygame 1.9.2.

+
+
+ +
+
+make_surface()¶
+
+
Creates a new Surface from the current PixelArray.
+
make_surface() -> Surface
+
+

Creates a new Surface from the current PixelArray. Depending on the +current PixelArray the size, pixel order etc. will be different from the +original Surface.

+
# Create a new surface flipped around the vertical axis.
+sf = pxarray[:,::-1].make_surface ()
+
+
+
+

New in pygame 1.8.1.

+
+
+ +
+
+replace()¶
+
+
Replaces the passed color in the PixelArray with another one.
+
replace(color, repcolor, distance=0, weights=(0.299, 0.587, 0.114)) -> None
+
+

Replaces the pixels with the passed color in the PixelArray by changing +them them to the passed replacement color.

+

It uses a simple weighted Euclidean distance formula to calculate the +distance between the colors. The distance space ranges from 0.0 to 1.0 +and is used as threshold for the color detection. This causes the +replacement to take pixels with a similar, but not exactly identical +color, into account as well.

+

This is an in place operation that directly affects the pixels of the +PixelArray.

+
+

New in pygame 1.8.1.

+
+
+ +
+
+extract()¶
+
+
Extracts the passed color from the PixelArray.
+
extract(color, distance=0, weights=(0.299, 0.587, 0.114)) -> PixelArray
+
+

Extracts the passed color by changing all matching pixels to white, while +non-matching pixels are changed to black. This returns a new PixelArray +with the black/white color mask.

+

It uses a simple weighted Euclidean distance formula to calculate the +distance between the colors. The distance space ranges from 0.0 to 1.0 +and is used as threshold for the color detection. This causes the +extraction to take pixels with a similar, but not exactly identical +color, into account as well.

+
+

New in pygame 1.8.1.

+
+
+ +
+
+compare()¶
+
+
Compares the PixelArray with another one.
+
compare(array, distance=0, weights=(0.299, 0.587, 0.114)) -> PixelArray
+
+

Compares the contents of the PixelArray with those from the passed in +PixelArray. It returns a new PixelArray with a black/white color mask +that indicates the differences (black) of both arrays. Both PixelArray +objects must have identical bit depths and dimensions.

+

It uses a simple weighted Euclidean distance formula to calculate the +distance between the colors. The distance space ranges from 0.0 to 1.0 +and is used as a threshold for the color detection. This causes the +comparison to mark pixels with a similar, but not exactly identical +color, as white.

+
+

New in pygame 1.8.1.

+
+
+ +
+
+transpose()¶
+
+
Exchanges the x and y axis.
+
transpose() -> PixelArray
+
+

This method returns a new view of the pixel array with the rows and +columns swapped. So for a (w, h) sized array a (h, w) slice is returned. +If an array is one dimensional, then a length 1 x dimension is added, +resulting in a 2D pixel array.

+
+

New in pygame 1.9.2.

+
+
+ +
+
+close()¶
+
+
Closes the PixelArray, and releases Surface lock.
+
transpose() -> PixelArray
+
+

This method is for explicitly closing the PixelArray, and releasing +a lock on the Suface.

+
+

New in pygame 1.9.4.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pixelcopy.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pixelcopy.html new file mode 100644 index 0000000..518f753 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pixelcopy.html @@ -0,0 +1,262 @@ + + + + + + + + + pygame.pixelcopy — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.pixelcopy
+
+
pygame module for general pixel array copying
+
+ +++++ + + + + + + + + + + + + + + + + + + +
+—copy surface pixels to an array object
+—copy an array object to a surface
+—copy an array to another array, using surface format
+—Copy an array to a new surface
+

The pygame.pixelcopy module contains functions for copying between +surfaces and objects exporting an array structure interface. It is a backend +for pygame.surfarraypygame module for accessing surface pixel data using array interfaces, adding NumPy support. But pixelcopy is more +general, and intended for direct use.

+

The array struct interface exposes an array's data in a standard way. +It was introduced in NumPy. In Python 2.7 and above it is replaced by the +new buffer protocol, though the buffer protocol is still a work in progress. +The array struct interface, on the other hand, is stable and works with earlier +Python versions. So for now the array struct interface is the predominate way +pygame handles array introspection.

+

For 2d arrays of integer pixel values, the values are mapped to the +pixel format of the related surface. To get the actual color of a pixel +value use pygame.Surface.unmap_rgb()convert a mapped integer color value into a Color. 2d arrays can only be used +directly between surfaces having the same pixel layout.

+

New in pygame 1.9.2.

+
+
+pygame.pixelcopy.surface_to_array()¶
+
+
copy surface pixels to an array object
+
surface_to_array(array, surface, kind='P', opaque=255, clear=0) -> None
+
+

The surface_to_array function copies pixels from a Surface object +to a 2D or 3D array. Depending on argument kind and the target array +dimension, a copy may be raw pixel value, RGB, a color component slice, +or colorkey alpha transparency value. Recognized kind values are the +single character codes 'P', 'R', 'G', 'B', 'A', and 'C'. Kind codes are case +insensitive, so 'p' is equivalent to 'P'. The first two dimensions +of the target must be the surface size (w, h).

+

The default 'P' kind code does a direct raw integer pixel (mapped) value +copy to a 2D array and a 'RGB' pixel component (unmapped) copy to a 3D array +having shape (w, h, 3). For an 8 bit colormap surface this means the +table index is copied to a 2D array, not the table value itself. A 2D +array's item size must be at least as large as the surface's pixel +byte size. The item size of a 3D array must be at least one byte.

+

For the 'R', 'G', 'B', and 'A' copy kinds a single color component +of the unmapped surface pixels are copied to the target 2D array. +For kind 'A' and surfaces with source alpha (the surface was created with +the SRCALPHA flag), has a colorkey +(set with Surface.set_colorkey()), +or has a blanket alpha +(set with Surface.set_alpha()) +then the alpha values are those expected for a SDL surface. +If a surface has no explicit alpha value, then the target array +is filled with the value of the optional opaque surface_to_array +argument (default 255: not transparent).

+

Copy kind 'C' is a special case for alpha copy of a source surface +with colorkey. Unlike the 'A' color component copy, the clear +argument value is used for colorkey matches, opaque otherwise. +By default, a match has alpha 0 (totally transparent), while everything +else is alpha 255 (totally opaque). It is a more general implementation +of pygame.surfarray.array_colorkey()Copy the colorkey values into a 2d array.

+

Specific to surface_to_array, a ValueError is raised for target arrays +with incorrect shape or item size. A TypeError is raised for an incorrect +kind code. Surface specific problems, such as locking, raise a pygame.error.

+
+ +
+
+pygame.pixelcopy.array_to_surface()¶
+
+
copy an array object to a surface
+
array_to_surface(<surface>, <array>) -> None
+
+

See pygame.surfarray.blit_array()Blit directly from a array values.

+
+ +
+
+pygame.pixelcopy.map_array()¶
+
+
copy an array to another array, using surface format
+
map_array(<array>, <array>, <surface>) -> None
+
+

Map an array of color element values - (w, h, ..., 3) - to an array of +pixels - (w, h) according to the format of <surface>.

+
+ +
+
+pygame.pixelcopy.make_surface()¶
+
+
Copy an array to a new surface
+
pygame.pixelcopy.make_surface(array) -> Surface
+
+

Create a new Surface that best resembles the data and format of the array. +The array can be 2D or 3D with any sized integer values.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pygame.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pygame.html new file mode 100644 index 0000000..8a6bb9d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/pygame.html @@ -0,0 +1,693 @@ + + + + + + + + + pygame — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame
+
+
the top level pygame package
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize all imported pygame modules
+—uninitialize all pygame modules
+—returns True if pygame is currently initialized
+—standard pygame exception
+—get the current error message
+—set the current error message
+—get the version number of SDL
+—get the byte order of SDL
+—register a function to be called when pygame quits
+—Encode a Unicode or bytes object
+—Encode a Unicode or bytes object as a file system path
+

The pygame package represents the top-level package for others to use. Pygame +itself is broken into many submodules, but this does not affect programs that +use pygame.

+

As a convenience, most of the top-level variables in pygame have been placed +inside a module named pygame.localspygame constants. This is meant to be used with +from pygame.locals import *, in addition to import pygame.

+

When you import pygame all available pygame submodules are automatically +imported. Be aware that some of the pygame modules are considered optional, +and may not be available. In that case, pygame will provide a placeholder +object instead of the module, which can be used to test for availability.

+
+
+pygame.init()¶
+
+
initialize all imported pygame modules
+
init() -> (numpass, numfail)
+
+

Initialize all imported pygame modules. No exceptions will be raised if a +module fails, but the total number if successful and failed inits will be +returned as a tuple. You can always initialize individual modules manually, +but pygame.init()initialize all imported pygame modules is a convenient way to get everything started. The +init() functions for individual modules will raise exceptions when they +fail.

+

You may want to initialize the different modules separately to speed up your +program or to not use modules your game does not require.

+

It is safe to call this init() more than once as repeated calls will have +no effect. This is true even if you have pygame.quit() all the modules.

+
+ +
+
+pygame.quit()¶
+
+
uninitialize all pygame modules
+
quit() -> None
+
+

Uninitialize all pygame modules that have previously been initialized. When +the Python interpreter shuts down, this method is called regardless, so your +program should not need it, except when it wants to terminate its pygame +resources and continue. It is safe to call this function more than once as +repeated calls have no effect.

+
+

Note

+

Calling pygame.quit()uninitialize all pygame modules will not exit your program. Consider letting +your program end in the same way a normal Python program will end.

+
+
+ +
+
+pygame.get_init()¶
+
+
returns True if pygame is currently initialized
+
get_init() -> bool
+
+

Returns True if pygame is currently initialized.

+
+

New in pygame 1.9.5.

+
+
+ +
+
+exception pygame.error¶
+
+
standard pygame exception
+
raise pygame.error(message)
+
+

This exception is raised whenever a pygame or SDL operation fails. You +can catch any anticipated problems and deal with the error. The exception is +always raised with a descriptive message about the problem.

+

Derived from the RuntimeError exception, which can also be used to catch +these raised errors.

+
+ +
+
+pygame.get_error()¶
+
+
get the current error message
+
get_error() -> errorstr
+
+

SDL maintains an internal error message. This message will usually be +given to you when pygame.error()standard pygame exception is raised, so this function will +rarely be needed.

+
+ +
+
+pygame.set_error()¶
+
+
set the current error message
+
set_error(error_msg) -> None
+
+

SDL maintains an internal error message. This message will usually be +given to you when pygame.error()standard pygame exception is raised, so this function will +rarely be needed.

+
+ +
+
+pygame.get_sdl_version()¶
+
+
get the version number of SDL
+
get_sdl_version() -> major, minor, patch
+
+

Returns the three version numbers of the SDL library. This version is built +at compile time. It can be used to detect which features may or may not be +available through pygame.

+
+

New in pygame 1.7.0.

+
+
+ +
+
+pygame.get_sdl_byteorder()¶
+
+
get the byte order of SDL
+
get_sdl_byteorder() -> int
+
+

Returns the byte order of the SDL library. It returns 1234 for little +endian byte order and 4321 for big endian byte order.

+
+

New in pygame 1.8.

+
+
+ +
+
+pygame.register_quit()¶
+
+
register a function to be called when pygame quits
+
register_quit(callable) -> None
+
+

When pygame.quit()uninitialize all pygame modules is called, all registered quit functions are +called. Pygame modules do this automatically when they are initializing, so +this function will rarely be needed.

+
+ +
+
+pygame.encode_string()¶
+
+
Encode a Unicode or bytes object
+
encode_string([obj [, encoding [, errors [, etype]]]]) -> bytes or None
+
+

obj: If Unicode, encode; if bytes, return unaltered; if anything else, +return None; if not given, raise SyntaxError.

+

encoding (string): If present, encoding to use. The default is +'unicode_escape'.

+

errors (string): If given, how to handle unencodable characters. The default +is 'backslashreplace'.

+

etype (exception type): If given, the exception type to raise for an +encoding error. The default is UnicodeEncodeError, as returned by +PyUnicode_AsEncodedString(). For the default encoding and errors values +there should be no encoding errors.

+

This function is used in encoding file paths. Keyword arguments are +supported.

+
+

New in pygame 1.9.2: (primarily for use in unit tests)

+
+
+ +
+
+pygame.encode_file_path()¶
+
+
Encode a Unicode or bytes object as a file system path
+
encode_file_path([obj [, etype]]) -> bytes or None
+
+

obj: If Unicode, encode; if bytes, return unaltered; if anything else, +return None; if not given, raise SyntaxError.

+

etype (exception type): If given, the exception type to raise for an +encoding error. The default is UnicodeEncodeError, as returned by +PyUnicode_AsEncodedString().

+

This function is used to encode file paths in pygame. Encoding is to the +codec as returned by sys.getfilesystemencoding(). Keyword arguments are +supported.

+
+

New in pygame 1.9.2: (primarily for use in unit tests)

+
+
+ +
+ +
+
+
+
+pygame.version
+
+
small module containing version information
+
+ +++++ + + + + + + + + + + + + + + + + + + +
+—version number as a string
+—tupled integers of the version
+—repository revision of the build
+—tupled integers of the SDL library version
+

This module is automatically imported into the pygame package and can be used to +check which version of pygame has been imported.

+
+
+pygame.version.ver¶
+
+
version number as a string
+
ver = '1.2'
+
+

This is the version represented as a string. It can contain a micro release +number as well, e.g. '1.5.2'

+
+ +
+
+pygame.version.vernum¶
+
+
tupled integers of the version
+
vernum = (1, 5, 3)
+
+

This version information can easily be compared with other version +numbers of the same format. An example of checking pygame version numbers +would look like this:

+
if pygame.version.vernum < (1, 5):
+    print('Warning, older version of pygame (%s)' %  pygame.version.ver)
+    disable_advanced_features = True
+
+
+
+

New in pygame 1.9.6: Attributes major, minor, and patch.

+
+
vernum.major == vernum[0]
+vernum.minor == vernum[1]
+vernum.patch == vernum[2]
+
+
+
+

Changed in pygame 1.9.6: str(pygame.version.vernum) returns a string like "2.0.0" instead +of "(2, 0, 0)".

+
+
+

Changed in pygame 1.9.6: repr(pygame.version.vernum) returns a string like +"PygameVersion(major=2, minor=0, patch=0)" instead of "(2, 0, 0)".

+
+
+ +
+
+pygame.version.rev¶
+
+
repository revision of the build
+
rev = 'a6f89747b551+'
+
+

The Mercurial node identifier of the repository checkout from which this +package was built. If the identifier ends with a plus sign '+' then the +package contains uncommitted changes. Please include this revision number +in bug reports, especially for non-release pygame builds.

+

Important note: pygame development has moved to github, this variable is +obsolete now. As soon as development shifted to github, this variable started +returning an empty string "". +It has always been returning an empty string since v1.9.5.

+
+

Changed in pygame 1.9.5: Always returns an empty string "".

+
+
+ +
+
+pygame.version.SDL¶
+
+
tupled integers of the SDL library version
+
SDL = '(2, 0, 12)'
+
+

This is the SDL library version represented as an extended tuple. It also has +attributes 'major', 'minor' & 'patch' that can be accessed like this:

+
>>> pygame.version.SDL.major
+2
+
+
+

printing the whole thing returns a string like this:

+
>>> pygame.version.SDL
+SDLVersion(major=2, minor=0, patch=12)
+
+
+
+

New in pygame 2.0.0.

+
+
+ +

Setting Environment Variables

+

Some aspects of pygame's behaviour can be controlled by setting environment variables, they cover a wide +range of the library's functionality. Some of the variables are from pygame itself, while others come from +the underlying C SDL library that pygame uses.

+

In python, environment variables are usually set in code like this:

+
import os
+os.environ['NAME_OF_ENVIRONMENT_VARIABLE'] = 'value_to_set'
+
+
+

Or to preserve users ability to override the variable:

+
import os
+os.environ['ENV_VAR'] = os.environ.get('ENV_VAR', 'value')
+
+
+

If the variable is more useful for users of an app to set than the developer then they can set it like this:

+

Windows:

+
set NAME_OF_ENVIRONMENT_VARIABLE=value_to_set
+python my_application.py
+
+
+

Linux/Mac:

+
ENV_VAR=value python my_application.py
+
+
+

For some variables they need to be set before initialising pygame, some must be set before even importing pygame, +and others can simply be set right before the area of code they control is run.

+

Below is a list of environment variables, their settable values, and a brief description of what they do.

+
+

+
+

Pygame Environment Variables

+

These variables are defined by pygame itself.

+
+

+
+
PYGAME_DISPLAY - Experimental (subject to change)
+Set index of the display to use, "0" is the default.
+
+
+

This sets the display where pygame will open its window +or screen. The value set here will be used if set before +calling pygame.display.set_mode()Initialize a window or screen for display, and as long as no +'display' parameter is passed into pygame.display.set_mode()Initialize a window or screen for display.

+
+

+
+
PYGAME_FORCE_SCALE -
+Set to "photo" or "default".
+
+
+

This forces set_mode() to use the SCALED display mode and, +if "photo" is set, makes the scaling use the slowest, but +highest quality anisotropic scaling algorithm, if it is +available. Must be set before calling pygame.display.set_mode()Initialize a window or screen for display.

+
+

+
+
PYGAME_BLEND_ALPHA_SDL2 - New in pygame 2.0.0
+Set to "1" to enable the SDL2 blitter.
+
+
+

This makes pygame use the SDL2 blitter for all alpha +blending. The SDL2 blitter is sometimes faster than +the default blitter but uses a different formula so +the final colours may differ. Must be set before +pygame.init()initialize all imported pygame modules is called.

+
+

+
+
PYGAME_HIDE_SUPPORT_PROMPT -
+Set to "1" to hide the prompt.
+
+
+

This stops the welcome message popping up in the +console that tells you which version of python, +pygame & SDL you are using. Must be set before +importing pygame.

+
+

+
+
PYGAME_FREETYPE -
+Set to "1" to enable.
+
+
+

This switches the pygame.font module to a pure +freetype implementation that bypasses SDL_ttf. +See the font module for why you might want to +do this. Must be set before importing pygame.

+
+

+
+
PYGAME_CAMERA -
+Set to "opencv" or "vidcapture"
+
+
+

Forces the library backend used in the camera +module, overriding the platform defaults. Must +be set before calling pygame.camera.init()Module init.

+

In pygame 2.0.3, backends can be set programmatically instead, and the old +OpenCV backend has been replaced with one on top of "opencv-python," rather +than the old "highgui" OpenCV port. Also, there is a new native Windows +backend available.

+
+

+

+
+

SDL Environment Variables

+

These variables are defined by SDL.

+

For documentation on the environment variables available in +pygame 1 try here. +For Pygame 2, some selected environment variables are listed below.

+
+

+
+
SDL_VIDEO_CENTERED -
+Set to "1" to enable centering the window.
+
+
+

This will make the pygame window open in the centre of the display. +Must be set before calling pygame.display.set_mode()Initialize a window or screen for display.

+
+

+
+
SDL_VIDEO_WINDOW_POS -
+Set to "x,y" to position the top left corner of the window.
+
+
+

This allows control over the placement of the pygame window within +the display. Must be set before calling pygame.display.set_mode()Initialize a window or screen for display.

+
+

+
+
SDL_VIDEODRIVER -
+Set to "drivername" to change the video driver used.
+
+
+

On some platforms there are multiple video drivers available and +this allows users to pick between them. More information is available +here. Must be set before +calling pygame.init()initialize all imported pygame modules or pygame.display.init()Initialize the display module.

+
+

+
+
SDL_AUDIODRIVER -
+Set to "drivername" to change the audio driver used.
+
+
+

On some platforms there are multiple audio drivers available and +this allows users to pick between them. More information is available +here. Must be set before +calling pygame.init()initialize all imported pygame modules or pygame.mixer.init()initialize the mixer module.

+
+

+
+
SDL_VIDEO_ALLOW_SCREENSAVER
+Set to "1" to allow screensavers while pygame apps are running.
+
+
+

By default pygame apps disable screensavers while +they are running. Setting this environment variable allows users or +developers to change that and make screensavers run again.

+
+

+
+
SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
+Set to "0" to re-enable the compositor.
+
+
+

By default SDL tries to disable the X11 compositor for all pygame +apps. This is usually a good thing as it's faster, however if you +have an app which doesn't update every frame and are using linux +you may want to disable this bypass. The bypass has reported problems +on KDE linux. This variable is only used on x11/linux platforms.

+
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/rect.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/rect.html new file mode 100644 index 0000000..68dbfcd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/rect.html @@ -0,0 +1,672 @@ + + + + + + + + + pygame.Rect — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.Rect¶
+
+
pygame object for storing rectangular coordinates
+
Rect(left, top, width, height) -> Rect
+
Rect((left, top), (width, height)) -> Rect
+
Rect(object) -> Rect
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—copy the rectangle
+—moves the rectangle
+—moves the rectangle, in place
+—grow or shrink the rectangle size
+—grow or shrink the rectangle size, in place
+—sets the position and size of the rectangle
+—moves the rectangle inside another
+—moves the rectangle inside another, in place
+—crops a rectangle inside another
+—crops a line inside a rectangle
+—joins two rectangles into one
+—joins two rectangles into one, in place
+—the union of many rectangles
+—the union of many rectangles, in place
+—resize and move a rectangle with aspect ratio
+—correct negative sizes
+—test if one rectangle is inside another
+—test if a point is inside a rectangle
+—test if two rectangles overlap
+—test if one rectangle in a list intersects
+—test if all rectangles in a list intersect
+—test if one rectangle in a dictionary intersects
+—test if all rectangles in a dictionary intersect
+

Pygame uses Rect objects to store and manipulate rectangular areas. A Rect +can be created from a combination of left, top, width, and height values. +Rects can also be created from python objects that are already a Rect or +have an attribute named "rect".

+

Any pygame function that requires a Rect argument also accepts any of these +values to construct a Rect. This makes it easier to create Rects on the fly +as arguments to functions.

+

The Rect functions that change the position or size of a Rect return a new +copy of the Rect with the affected changes. The original Rect is not +modified. Some methods have an alternate "in-place" version that returns +None but affects the original Rect. These "in-place" methods are denoted +with the "ip" suffix.

+

The Rect object has several virtual attributes which can be used to move and +align the Rect:

+
x,y
+top, left, bottom, right
+topleft, bottomleft, topright, bottomright
+midtop, midleft, midbottom, midright
+center, centerx, centery
+size, width, height
+w,h
+
+
+

All of these attributes can be assigned to:

+
rect1.right = 10
+rect2.center = (20,30)
+
+
+

Assigning to size, width or height changes the dimensions of the rectangle; +all other assignments move the rectangle without resizing it. Notice that +some attributes are integers and others are pairs of integers.

+

If a Rect has a nonzero width or height, it will return True for a +nonzero test. Some methods return a Rect with 0 size to represent an invalid +rectangle. A Rect with a 0 size will not collide when using collision +detection methods (e.g. collidepoint(), colliderect(), etc.).

+

The coordinates for Rect objects are all integers. The size values can be +programmed to have negative values, but these are considered illegal Rects +for most operations.

+

There are several collision tests between other rectangles. Most python +containers can be searched for collisions against a single Rect.

+

The area covered by a Rect does not include the right- and bottom-most edge +of pixels. If one Rect's bottom border is another Rect's top border (i.e., +rect1.bottom=rect2.top), the two meet exactly on the screen but do not +overlap, and rect1.colliderect(rect2) returns false.

+
+

New in pygame 1.9.2: The Rect class can be subclassed. Methods such as copy() and move() +will recognize this and return instances of the subclass. +However, the subclass's __init__() method is not called, +and __new__() is assumed to take no arguments. So these methods should be +overridden if any extra attributes need to be copied.

+
+
+
+copy()¶
+
+
copy the rectangle
+
copy() -> Rect
+
+

Returns a new rectangle having the same position and size as the original.

+

New in pygame 1.9

+
+ +
+
+move()¶
+
+
moves the rectangle
+
move(x, y) -> Rect
+
+

Returns a new rectangle that is moved by the given offset. The x and y +arguments can be any integer value, positive or negative.

+
+ +
+
+move_ip()¶
+
+
moves the rectangle, in place
+
move_ip(x, y) -> None
+
+

Same as the Rect.move() method, but operates in place.

+
+ +
+
+inflate()¶
+
+
grow or shrink the rectangle size
+
inflate(x, y) -> Rect
+
+

Returns a new rectangle with the size changed by the given offset. The +rectangle remains centered around its current center. Negative values +will shrink the rectangle. Note, uses integers, if the offset given is +too small(< 2 > -2), center will be off.

+
+ +
+
+inflate_ip()¶
+
+
grow or shrink the rectangle size, in place
+
inflate_ip(x, y) -> None
+
+

Same as the Rect.inflate() method, but operates in place.

+
+ +
+
+update()¶
+
+
sets the position and size of the rectangle
+
update(left, top, width, height) -> None
+
update((left, top), (width, height)) -> None
+
update(object) -> None
+
+

Sets the position and size of the rectangle, in place. See +parameters for pygame.Rect()pygame object for storing rectangular coordinates for the parameters of this function.

+
+

New in pygame 2.0.1.

+
+
+ +
+
+clamp()¶
+
+
moves the rectangle inside another
+
clamp(Rect) -> Rect
+
+

Returns a new rectangle that is moved to be completely inside the +argument Rect. If the rectangle is too large to fit inside, it is +centered inside the argument Rect, but its size is not changed.

+
+ +
+
+clamp_ip()¶
+
+
moves the rectangle inside another, in place
+
clamp_ip(Rect) -> None
+
+

Same as the Rect.clamp() method, but operates in place.

+
+ +
+
+clip()¶
+
+
crops a rectangle inside another
+
clip(Rect) -> Rect
+
+

Returns a new rectangle that is cropped to be completely inside the +argument Rect. If the two rectangles do not overlap to begin with, a Rect +with 0 size is returned.

+
+ +
+
+clipline()¶
+
+
crops a line inside a rectangle
+
clipline(x1, y1, x2, y2) -> ((cx1, cy1), (cx2, cy2))
+
clipline(x1, y1, x2, y2) -> ()
+
clipline((x1, y1), (x2, y2)) -> ((cx1, cy1), (cx2, cy2))
+
clipline((x1, y1), (x2, y2)) -> ()
+
clipline((x1, y1, x2, y2)) -> ((cx1, cy1), (cx2, cy2))
+
clipline((x1, y1, x2, y2)) -> ()
+
clipline(((x1, y1), (x2, y2))) -> ((cx1, cy1), (cx2, cy2))
+
clipline(((x1, y1), (x2, y2))) -> ()
+
+

Returns the coordinates of a line that is cropped to be completely inside +the rectangle. If the line does not overlap the rectangle, then an empty +tuple is returned.

+

The line to crop can be any of the following formats (floats can be used +in place of ints, but they will be truncated):

+
+
    +
  • four ints

  • +
  • 2 lists/tuples/Vector2s of 2 ints

  • +
  • a list/tuple of four ints

  • +
  • a list/tuple of 2 lists/tuples/Vector2s of 2 ints

  • +
+
+
+
Returns
+

a tuple with the coordinates of the given line cropped to be +completely inside the rectangle is returned, if the given line does +not overlap the rectangle, an empty tuple is returned

+
+
Return type
+

tuple(tuple(int, int), tuple(int, int)) or ()

+
+
Raises
+

TypeError -- if the line coordinates are not given as one of the +above described line formats

+
+
+
+

Note

+

This method can be used for collision detection between a rect and a +line. See example code below.

+
+
+

Note

+

The rect.bottom and rect.right attributes of a +pygame.Rectpygame object for storing rectangular coordinates always lie one pixel outside of its actual border.

+
+
# Example using clipline().
+clipped_line = rect.clipline(line)
+
+if clipped_line:
+    # If clipped_line is not an empty tuple then the line
+    # collides/overlaps with the rect. The returned value contains
+    # the endpoints of the clipped line.
+    start, end = clipped_line
+    x1, y1 = start
+    x2, y2 = end
+else:
+    print("No clipping. The line is fully outside the rect.")
+
+
+
+

New in pygame 2.0.0.

+
+
+ +
+
+union()¶
+
+
joins two rectangles into one
+
union(Rect) -> Rect
+
+

Returns a new rectangle that completely covers the area of the two +provided rectangles. There may be area inside the new Rect that is not +covered by the originals.

+
+ +
+
+union_ip()¶
+
+
joins two rectangles into one, in place
+
union_ip(Rect) -> None
+
+

Same as the Rect.union() method, but operates in place.

+
+ +
+
+unionall()¶
+
+
the union of many rectangles
+
unionall(Rect_sequence) -> Rect
+
+

Returns the union of one rectangle with a sequence of many rectangles.

+
+ +
+
+unionall_ip()¶
+
+
the union of many rectangles, in place
+
unionall_ip(Rect_sequence) -> None
+
+

The same as the Rect.unionall() method, but operates in place.

+
+ +
+
+fit()¶
+
+
resize and move a rectangle with aspect ratio
+
fit(Rect) -> Rect
+
+

Returns a new rectangle that is moved and resized to fit another. The +aspect ratio of the original Rect is preserved, so the new rectangle may +be smaller than the target in either width or height.

+
+ +
+
+normalize()¶
+
+
correct negative sizes
+
normalize() -> None
+
+

This will flip the width or height of a rectangle if it has a negative +size. The rectangle will remain in the same place, with only the sides +swapped.

+
+ +
+
+contains()¶
+
+
test if one rectangle is inside another
+
contains(Rect) -> bool
+
+

Returns true when the argument is completely inside the Rect.

+
+ +
+
+collidepoint()¶
+
+
test if a point is inside a rectangle
+
collidepoint(x, y) -> bool
+
collidepoint((x,y)) -> bool
+
+

Returns true if the given point is inside the rectangle. A point along +the right or bottom edge is not considered to be inside the rectangle.

+
+

Note

+

For collision detection between a rect and a line the clipline() +method can be used.

+
+
+ +
+
+colliderect()¶
+
+
test if two rectangles overlap
+
colliderect(Rect) -> bool
+
+

Returns true if any portion of either rectangle overlap (except the +top+bottom or left+right edges).

+
+

Note

+

For collision detection between a rect and a line the clipline() +method can be used.

+
+
+ +
+
+collidelist()¶
+
+
test if one rectangle in a list intersects
+
collidelist(list) -> index
+
+

Test whether the rectangle collides with any in a sequence of rectangles. +The index of the first collision found is returned. If no collisions are +found an index of -1 is returned.

+
+ +
+
+collidelistall()¶
+
+
test if all rectangles in a list intersect
+
collidelistall(list) -> indices
+
+

Returns a list of all the indices that contain rectangles that collide +with the Rect. If no intersecting rectangles are found, an empty list is +returned.

+
+ +
+
+collidedict()¶
+
+
test if one rectangle in a dictionary intersects
+
collidedict(dict) -> (key, value)
+
collidedict(dict) -> None
+
collidedict(dict, use_values=0) -> (key, value)
+
collidedict(dict, use_values=0) -> None
+
+

Returns the first key and value pair that intersects with the calling +Rect object. If no collisions are found, None is returned. If +use_values is 0 (default) then the dict's keys will be used in the +collision detection, otherwise the dict's values will be used.

+
+

Note

+

Rect objects cannot be used as keys in a dictionary (they are not +hashable), so they must be converted to a tuple. +e.g. rect.collidedict({tuple(key_rect) : value})

+
+
+ +
+
+collidedictall()¶
+
+
test if all rectangles in a dictionary intersect
+
collidedictall(dict) -> [(key, value), ...]
+
collidedictall(dict, use_values=0) -> [(key, value), ...]
+
+

Returns a list of all the key and value pairs that intersect with the +calling Rect object. If no collisions are found an empty list is returned. +If use_values is 0 (default) then the dict's keys will be used in the +collision detection, otherwise the dict's values will be used.

+
+

Note

+

Rect objects cannot be used as keys in a dictionary (they are not +hashable), so they must be converted to a tuple. +e.g. rect.collidedictall({tuple(key_rect) : value})

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/scrap.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/scrap.html new file mode 100644 index 0000000..19e1979 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/scrap.html @@ -0,0 +1,458 @@ + + + + + + + + + pygame.scrap — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.scrap
+
+
pygame module for clipboard support.
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Initializes the scrap module.
+—Returns True if the scrap module is currently initialized.
+—Gets the data for the specified type from the clipboard.
+—Gets a list of the available clipboard types.
+—Places data into the clipboard.
+—Checks whether data for a given type is available in the clipboard.
+—Indicates if the clipboard ownership has been lost by the pygame application.
+—Sets the clipboard access mode.
+

EXPERIMENTAL!: This API may change or disappear in later pygame releases. If +you use this, your code may break with the next pygame release.

+

The scrap module is for transferring data to/from the clipboard. This allows +for cutting and pasting data between pygame and other applications. Some basic +data (MIME) types are defined and registered:

+
 pygame         string
+constant        value        description
+--------------------------------------------------
+SCRAP_TEXT   "text/plain"    plain text
+SCRAP_BMP    "image/bmp"     BMP encoded image data
+SCRAP_PBM    "image/pbm"     PBM encoded image data
+SCRAP_PPM    "image/ppm"     PPM encoded image data
+
+
+

pygame.SCRAP_PPM, pygame.SCRAP_PBM and pygame.SCRAP_BMP are +suitable for surface buffers to be shared with other applications. +pygame.SCRAP_TEXT is an alias for the plain text clipboard type.

+

Depending on the platform, additional types are automatically registered when +data is placed into the clipboard to guarantee a consistent sharing behaviour +with other applications. The following listed types can be used as strings to +be passed to the respective pygame.scrappygame module for clipboard support. module functions.

+

For Windows platforms, these additional types are supported automatically +and resolve to their internal definitions:

+
"text/plain;charset=utf-8"   UTF-8 encoded text
+"audio/wav"                  WAV encoded audio
+"image/tiff"                 TIFF encoded image data
+
+
+

For X11 platforms, these additional types are supported automatically and +resolve to their internal definitions:

+
"text/plain;charset=utf-8"   UTF-8 encoded text
+"UTF8_STRING"                UTF-8 encoded text
+"COMPOUND_TEXT"              COMPOUND text
+
+
+

User defined types can be used, but the data might not be accessible by other +applications unless they know what data type to look for. +Example: Data placed into the clipboard by +pygame.scrap.put("my_data_type", byte_data) can only be accessed by +applications which query the clipboard for the "my_data_type" data type.

+

For an example of how the scrap module works refer to the examples page +(pygame.examples.scrap_clipboard.main()access the clipboard) or the code directly in GitHub +(pygame/examples/scrap_clipboard.py).

+
+

New in pygame 1.8.

+
+
+

Note

+

The scrap module is currently only supported for Windows, X11 and Mac OS X. +On Mac OS X only text works at the moment - other types may be supported in +future releases.

+
+
+
+pygame.scrap.init()¶
+
+
Initializes the scrap module.
+
init() -> None
+
+

Initialize the scrap module.

+
+
Raises
+

pygame.errorstandard pygame exception -- if unable to initialize scrap module

+
+
+
+

Note

+

The scrap module requires pygame.display.set_mode()Initialize a window or screen for display be +called before being initialized.

+
+
+ +
+
+pygame.scrap.get_init()¶
+
+
Returns True if the scrap module is currently initialized.
+
get_init() -> bool
+
+

Gets the scrap module's initialization state.

+
+
Returns
+

True if the pygame.scrappygame module for clipboard support. module is currently +initialized, False otherwise

+
+
Return type
+

bool

+
+
+
+

New in pygame 1.9.5.

+
+
+ +
+
+pygame.scrap.get()¶
+
+
Gets the data for the specified type from the clipboard.
+
get(type) -> bytes | None
+
+

Retrieves the data for the specified type from the clipboard. The data is +returned as a byte string and might need further processing (such as +decoding to Unicode).

+
+
Parameters
+

type (string) -- data type to retrieve from the clipboard

+
+
Returns
+

data (bytes object) for the given type identifier or None if +no data for the given type is available

+
+
Return type
+

bytes | None

+
+
+
text = pygame.scrap.get(pygame.SCRAP_TEXT)
+if text:
+    print("There is text in the clipboard.")
+else:
+    print("There does not seem to be text in the clipboard.")
+
+
+
+ +
+
+pygame.scrap.get_types()¶
+
+
Gets a list of the available clipboard types.
+
get_types() -> list
+
+

Gets a list of data type string identifiers for the data currently +available on the clipboard. Each identifier can be used in the +pygame.scrap.get()Gets the data for the specified type from the clipboard. method to get the clipboard content of the +specific type.

+
+
Returns
+

list of strings of the available clipboard data types, if there +is no data in the clipboard an empty list is returned

+
+
Return type
+

list

+
+
+
for t in pygame.scrap.get_types():
+    if "text" in t:
+        # There is some content with the word "text" in its type string.
+        print(pygame.scrap.get(t))
+
+
+
+ +
+
+pygame.scrap.put()¶
+
+
Places data into the clipboard.
+
put(type, data) -> None
+
+

Places data for a given clipboard type into the clipboard. The data must +be a string buffer. The type is a string identifying the type of data to be +placed into the clipboard. This can be one of the predefined +pygame.SCRAP_PBM, pygame.SCRAP_PPM, pygame.SCRAP_BMP or +pygame.SCRAP_TEXT values or a user defined string identifier.

+
+
Parameters
+
    +
  • type (string) -- type identifier of the data to be placed into the +clipboard

  • +
  • data (bytes) -- data to be place into the clipboard, a bytes object

  • +
+
+
Raises
+

pygame.errorstandard pygame exception -- if unable to put the data into the clipboard

+
+
+
with open("example.bmp", "rb") as fp:
+    pygame.scrap.put(pygame.SCRAP_BMP, fp.read())
+# The image data is now on the clipboard for other applications to access
+# it.
+pygame.scrap.put(pygame.SCRAP_TEXT, b"A text to copy")
+pygame.scrap.put("Plain text", b"Data for user defined type 'Plain text'")
+
+
+
+ +
+
+pygame.scrap.contains()¶
+
+
Checks whether data for a given type is available in the clipboard.
+
contains(type) -> bool
+
+

Checks whether data for the given type is currently available in the +clipboard.

+
+
Parameters
+

type (string) -- data type to check availability of

+
+
Returns
+

True if data for the passed type is available in the +clipboard, False otherwise

+
+
Return type
+

bool

+
+
+
if pygame.scrap.contains(pygame.SCRAP_TEXT):
+    print("There is text in the clipboard.")
+if pygame.scrap.contains("own_data_type"):
+    print("There is stuff in the clipboard.")
+
+
+
+ +
+
+pygame.scrap.lost()¶
+
+
Indicates if the clipboard ownership has been lost by the pygame application.
+
lost() -> bool
+
+

Indicates if the clipboard ownership has been lost by the pygame +application.

+
+
Returns
+

True, if the clipboard ownership has been lost by the pygame +application, False if the pygame application still owns the clipboard

+
+
Return type
+

bool

+
+
+
if pygame.scrap.lost():
+    print("The clipboard is in use by another application.")
+
+
+
+ +
+
+pygame.scrap.set_mode()¶
+
+
Sets the clipboard access mode.
+
set_mode(mode) -> None
+
+

Sets the access mode for the clipboard. This is only of interest for X11 +environments where clipboard modes pygame.SCRAP_SELECTION (for mouse +selections) and pygame.SCRAP_CLIPBOARD (for the clipboard) are +available. Setting the mode to pygame.SCRAP_SELECTION in other +environments will not change the mode from pygame.SCRAP_CLIPBOARD.

+
+
Parameters
+

mode -- access mode, supported values are pygame.SCRAP_CLIPBOARD +and pygame.SCRAP_SELECTION (pygame.SCRAP_SELECTION only has an +effect when used on X11 platforms)

+
+
Raises
+

ValueError -- if the mode parameter is not +pygame.SCRAP_CLIPBOARD or pygame.SCRAP_SELECTION

+
+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sdl2_controller.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sdl2_controller.html new file mode 100644 index 0000000..ba0ff35 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sdl2_controller.html @@ -0,0 +1,569 @@ + + + + + + + + + pygame._sdl2.controller — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame._sdl2.controller
+
+
Pygame module to work with controllers.
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—initialize the controller module
+—Uninitialize the controller module.
+—Returns True if the controller module is initialized.
+—Sets the current state of events related to controllers
+—Gets the current state of events related to controllers
+—Get the number of joysticks connected
+—Check if the given joystick is supported by the game controller interface
+—Get the name of the contoller
+—Create a new Controller object.
+

This module offers control over common controller types like the dualshock 4 or +the xbox 360 controllers: They have two analog sticks, two triggers, two shoulder buttons, +a dpad, 4 buttons on the side, 2 (or 3) buttons in the middle.

+

Pygame uses xbox controllers naming conventions (like a, b, x, y for buttons) but +they always refer to the same buttons. For example CONTROLLER_BUTTON_X is +always the leftmost button of the 4 buttons on the right.

+

Controllers can generate the following events:

+
CONTROLLERAXISMOTION, CONTROLLERBUTTONDOWN, CONTROLLERBUTTONUP,
+CONTROLLERDEVICEREMAPPED, CONTROLLERDEVICEADDED, CONTROLLERDEVICEREMOVED
+
+
+

Additionally if pygame is built with SDL 2.0.14 or higher the following events can also be generated +(to get the version of sdl pygame is built with use pygame.version.SDL()tupled integers of the SDL library version):

+
CONTROLLERTOUCHPADDOWN, CONTROLLERTOUCHPADMOTION, CONTROLLERTOUCHPADUP
+
+
+

These events can be enabled/disabled by pygame._sdl2.controller.set_eventstate()Sets the current state of events related to controllers +Note that controllers can generate joystick events as well. This function only toggles +events related to controllers.

+
+

Note

+

See the pygame.joystickPygame module for interacting with joysticks, gamepads, and trackballs. for a more versatile but more advanced api.

+
+
+

New in pygame 2: This module requires SDL2.

+
+
+
+pygame._sdl2.controller.init()¶
+
+
initialize the controller module
+
init() -> None
+
+

Initialize the controller module.

+
+ +
+
+pygame._sdl2.controller.quit()¶
+
+
Uninitialize the controller module.
+
quit() -> None
+
+

Uninitialize the controller module.

+
+ +
+
+pygame._sdl2.controller.get_init()¶
+
+
Returns True if the controller module is initialized.
+
get_init() -> bool
+
+

Test if pygame._sdl2.controller.init() was called.

+
+
+
+ +
+
+pygame._sdl2.controller.set_eventstate()¶
+
+
Sets the current state of events related to controllers
+
set_eventstate(state) -> None
+
+

Enable or disable events connected to controllers.

+
+

Note

+

Controllers can still generate joystick events, which will not be toggled by this function.

+
+
+

Changed in pygame 2.0.2:: Changed return type from int to None

+
+
+ +
+
+pygame._sdl2.controller.get_eventstate()¶
+
+
Gets the current state of events related to controllers
+
get_eventstate() -> bool
+
+

Returns the current state of events related to controllers, True meaning +events will be posted.

+
+

New in pygame 2.0.2.

+
+
+ +
+
+pygame._sdl2.controller.get_count()¶
+
+
Get the number of joysticks connected
+
get_count() -> int
+
+

Get the number of joysticks connected.

+
+ +
+
+pygame._sdl2.controller.is_controller()¶
+
+
Check if the given joystick is supported by the game controller interface
+
is_controller(index) -> bool
+
+

Returns True if the index given can be used to create a controller object.

+
+ +
+
+pygame._sdl2.controller.name_forindex()¶
+
+
Get the name of the contoller
+
name_forindex(index) -> name or None
+
+

Returns the name of controller, or None if there's no name or the +index is invalid.

+
+ +
+
+pygame._sdl2.controller.Controller¶
+
+
+
Create a new Controller object.
+
Controller(index) -> Controller
+
+

Create a new Controller object. Index should be integer between +0 and pygame._sdl2.contoller.get_count(). Controllers also +can be created from a pygame.joystick.Joystick using +pygame._sdl2.controller.from_joystick. Controllers are +initialized on creation.

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—uninitialize the Controller
+—check if the Controller is initialized
+—Create a Controller from a pygame.joystick.Joystick object
+—Check if the Controller has been opened and is currently connected.
+—Returns a pygame.joystick.Joystick() object
+—Get the current state of a joystick axis
+—Get the current state of a button
+—Get the mapping assigned to the controller
+—Assign a mapping to the controller
+—Start a rumbling effect
+—Stop any rumble effect playing
+
+
+quit()¶
+
+
uninitialize the Controller
+
quit() -> None
+
+

Close a Controller object. After this the pygame event queue will no longer +receive events from the device.

+

It is safe to call this more than once.

+
+ +
+
+get_init()¶
+
+
check if the Controller is initialized
+
get_init() -> bool
+
+

Returns True if the Controller object is currently initialised.

+
+ +
+
+static from_joystick()¶
+
+
Create a Controller from a pygame.joystick.Joystick object
+
from_joystick(joystick) -> Controller
+
+

Create a Controller object from a pygame.joystick.Joystick object

+
+ +
+
+attached()¶
+
+
Check if the Controller has been opened and is currently connected.
+
attached() -> bool
+
+

Returns True if the Controller object is opened and connected.

+
+ +
+
+as_joystick()¶
+
+
Returns a pygame.joystick.Joystick() object
+
as_joystick() -> Joystick object
+
+

Returns a pygame.joystick.Joystick() object created from this controller's index

+
+ +
+
+get_axis()¶
+
+
Get the current state of a joystick axis
+
get_axis(axis) -> int
+
+

Get the current state of a trigger or joystick axis. +The axis argument must be one of the following constants:

+
CONTROLLER_AXIS_LEFTX, CONTROLLER_AXIS_LEFTY,
+CONTROLLER_AXIS_RIGHTX, CONTROLLER_AXIS_RIGHTY,
+CONTROLLER_AXIS_TRIGGERLEFT, CONTROLLER_AXIS_TRIGGERRIGHT
+
+
+

Joysticks can return a value between -32768 and 32767. Triggers however +can only return a value between 0 and 32768.

+
+ +
+
+get_button()¶
+
+
Get the current state of a button
+
get_button(button) -> bool
+
+

Get the current state of a button, True meaning it is pressed down. +The button argument must be one of the following constants:

+
CONTROLLER_BUTTON_A, CONTROLLER_BUTTON_B,
+CONTROLLER_BUTTON_X, CONTROLLER_BUTTON_Y
+CONTROLLER_BUTTON_DPAD_UP, CONTROLLER_BUTTON_DPAD_DOWN,
+CONTROLLER_BUTTON_DPAD_LEFT, CONTROLLER_BUTTON_DPAD_RIGHT,
+CONTROLLER_BUTTON_LEFTSHOULDER, CONTROLLER_BUTTON_RIGHTSHOULDER,
+CONTROLLER_BUTTON_LEFTSTICK, CONTROLLER_BUTTON_RIGHTSTICK,
+CONTROLLER_BUTTON_BACK, CONTROLLER_BUTTON_GUIDE,
+CONTROLLER_BUTTON_START
+
+
+
+ +
+
+get_mapping()¶
+
+
Get the mapping assigned to the controller
+
get_mapping() -> mapping
+
+

Returns a dict containing the mapping of the Controller. For more +information see Controller.set_mapping()

+
+

Changed in pygame 2.0.2:: Return type changed from str to dict

+
+
+ +
+
+set_mapping()¶
+
+
Assign a mapping to the controller
+
set_mapping(mapping) -> int
+
+

Rebind buttons, axes, triggers and dpads. The mapping should be a +dict containing all buttons, hats and axes. The easiest way to get this +is to use the dict returned by Controller.get_mapping(). To edit +this mapping assign a value to the original button. The value of the +dictionary must be a button, hat or axis represented in the following way:

+
    +
  • For a button use: bX where X is the index of the button.

  • +
  • For a hat use: hX.Y where X is the index and the Y is the direction (up: 1, right: 2, down: 3, left: 4).

  • +
  • For an axis use: aX where x is the index of the axis.

  • +
+

An example of mapping:

+
mapping = controller.get_mapping() # Get current mapping
+mapping["a"] = "b3" # Remap button a to y
+mapping["y"] = "b0" # Remap button y to a
+controller.set_mapping(mapping) # Set the mapping
+
+
+

The function will return 1 if a new mapping is added or 0 if an existing one is updated.

+
+

Changed in pygame 2.0.2:: Renamed from add_mapping to set_mapping

+
+
+

Changed in pygame 2.0.2:: Argument type changed from str to dict

+
+
+ +
+
+rumble()¶
+
+
Start a rumbling effect
+
rumble(low_frequency, high_frequency, duration) -> bool
+
+

Start a rumble effect on the controller, with the specified strength ranging +from 0 to 1. Duration is length of the effect, in ms. Setting the duration +to 0 will play the effect until another one overwrites it or +Controller.stop_rumble() is called. If an effect is already +playing, then it will be overwritten.

+

Returns True if the rumble was played successfully or False if the +controller does not support it or pygame.version.SDL()tupled integers of the SDL library version is below 2.0.9.

+
+

New in pygame 2.0.2.

+
+
+ +
+
+stop_rumble()¶
+
+
Stop any rumble effect playing
+
stop_rumble() -> None
+
+

Stops any rumble effect playing on the controller. See +Controller.rumble() for more information.

+
+

New in pygame 2.0.2.

+
+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sdl2_video.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sdl2_video.html new file mode 100644 index 0000000..eb80cd2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sdl2_video.html @@ -0,0 +1,1091 @@ + + + + + + + + + pygame.sdl2_video — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.sdl2_video
+
+

Warning

+

This module isn't ready for prime time yet, it's still in development. +These docs are primarily meant to help the pygame developers and super-early adopters +who are in communication with the developers. This API will change.

+
+ +++++ + + + + + + + + + + + + + + + + + + +
+—pygame object that represents a window
+—pygame object that representing a Texture.
+—Easy way to use a portion of a Texture without worrying about srcrect all the time.
+—Create a 2D rendering context for a window.
+
+
Experimental pygame module for porting new SDL video systems
+
+
+
+pygame._sdl2.video.Window¶
+
+
pygame object that represents a window
+
Window(title="pygame", size=(640, 480), position=None, fullscreen=False, fullscreen_desktop=False, keywords) -> Window
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Creates window using window created by pygame.display.set_mode().
+—Gets or sets whether the mouse is confined to the window.
+—Gets or sets the window's relative mouse motion state.
+—Enable windowed mode (exit fullscreen).
+—Enter fullscreen.
+—Gets or sets whether the window title.
+—Destroys the window.
+—Hide the window.
+—Show the window.
+—Raise the window above other windows and set the input focus. The "input_only" argument is only supported on X11.
+—Restore the size and position of a minimized or maximized window.
+—Maximize the window.
+—Minimize the window.
+—Gets and sets whether the window is resizable.
+—Add or remove the border from the window.
+—Set the icon for the window.
+—Get the unique window ID. *Read-only*
+—Gets and sets the window size.
+—Gets and sets the window position.
+—Gets and sets the window opacity. Between 0.0 (fully transparent) and 1.0 (fully opaque).
+—Gets and sets the brightness (gamma multiplier) for the display that owns the window.
+—Get the index of the display that owns the window. *Read-only*
+—Set the window as a modal for a parent window. This function is only supported on X11.
+
+
+classmethod from_display_module()¶
+
+
Creates window using window created by pygame.display.set_mode().
+
from_display_module() -> Window
+
+
+ +
+
+grab¶
+
+
Gets or sets whether the mouse is confined to the window.
+
grab -> bool
+
+
+ +
+
+relative_mouse¶
+
+
Gets or sets the window's relative mouse motion state.
+
relative_mouse -> bool
+
+
+ +
+
+set_windowed()¶
+
+
Enable windowed mode (exit fullscreen).
+
set_windowed() -> None
+
+
+ +
+
+set_fullscreen()¶
+
+
Enter fullscreen.
+
set_fullscreen(desktop=False) -> None
+
+
+ +
+
+title¶
+
+
Gets or sets whether the window title.
+
title -> string
+
+
+ +
+
+destroy()¶
+
+
Destroys the window.
+
destroy() -> None
+
+
+ +
+
+hide()¶
+
+
Hide the window.
+
hide() -> None
+
+
+ +
+
+show()¶
+
+
Show the window.
+
show() -> None
+
+
+ +
+
+focus()¶
+
+
Raise the window above other windows and set the input focus. The "input_only" argument is only supported on X11.
+
focus(input_only=False) -> None
+
+
+ +
+
+restore()¶
+
+
Restore the size and position of a minimized or maximized window.
+
restore() -> None
+
+
+ +
+
+maximize()¶
+
+
Maximize the window.
+
maximize() -> None
+
+
+ +
+
+minimize()¶
+
+
Minimize the window.
+
maximize() -> None
+
+
+ +
+
+resizable¶
+
+
Gets and sets whether the window is resizable.
+
resizable -> bool
+
+
+ +
+
+borderless¶
+
+
Add or remove the border from the window.
+
borderless -> bool
+
+
+ +
+
+set_icon()¶
+
+
Set the icon for the window.
+
set_icon(surface) -> None
+
+
+ +
+
+id¶
+
+
Get the unique window ID. *Read-only*
+
id -> int
+
+
+ +
+
+size¶
+
+
Gets and sets the window size.
+
size -> (int, int)
+
+
+ +
+
+position¶
+
+
Gets and sets the window position.
+
position -> (int, int) or WINDOWPOS_CENTERED or WINDOWPOS_UNDEFINED
+
+
+ +
+
+opacity¶
+
+
Gets and sets the window opacity. Between 0.0 (fully transparent) and 1.0 (fully opaque).
+
opacity -> float
+
+
+ +
+
+brightness¶
+
+
Gets and sets the brightness (gamma multiplier) for the display that owns the window.
+
brightness -> float
+
+
+ +
+
+display_index¶
+
+
Get the index of the display that owns the window. *Read-only*
+
display_index -> int
+
+
+ +
+
+set_modal_for()¶
+
+
Set the window as a modal for a parent window. This function is only supported on X11.
+
set_modal_for(Window) -> None
+
+
+ +
+ +
+
+pygame._sdl2.video.Texture¶
+
+
pygame object that representing a Texture.
+
Texture(renderer, size, depth=0, static=False, streaming=False, target=False) -> Texture
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Create a texture from an existing surface.
+—Gets the renderer associated with the Texture. *Read-only*
+—Gets the width of the Texture. *Read-only*
+—Gets the height of the Texture. *Read-only*
+—Gets and sets an additional alpha value multiplied into render copy operations.
+—Gets and sets the blend mode for the Texture.
+—Gets and sets an additional color value multiplied into render copy operations.
+—Get the rectangular area of the texture.
+—Copy a portion of the texture to the rendering target.
+—Update the texture with a Surface. WARNING: Slow operation, use sparingly.
+
+
+static from_surface()¶
+
+
Create a texture from an existing surface.
+
from_surface(renderer, surface) -> Texture
+
+
+ +
+
+renderer¶
+
+
Gets the renderer associated with the Texture. *Read-only*
+
renderer -> Renderer
+
+
+ +
+
+width¶
+
+
Gets the width of the Texture. *Read-only*
+
width -> int
+
+
+ +
+
+height¶
+
+
Gets the height of the Texture. *Read-only*
+
height -> int
+
+
+ +
+
+alpha¶
+
+
Gets and sets an additional alpha value multiplied into render copy operations.
+
alpha -> int
+
+
+ +
+
+blend_mode¶
+
+
Gets and sets the blend mode for the Texture.
+
blend_mode -> int
+
+
+ +
+
+color¶
+
+
Gets and sets an additional color value multiplied into render copy operations.
+
color -> color
+
+
+ +
+
+get_rect()¶
+
+
Get the rectangular area of the texture.
+
get_rect(**kwargs) -> Rect
+
+
+ +
+
+draw()¶
+
+
Copy a portion of the texture to the rendering target.
+
draw(srcrect=None, dstrect=None, angle=0, origin=None, flipX=False, flipY=False) -> None
+
+
+ +
+
+update()¶
+
+
Update the texture with a Surface. WARNING: Slow operation, use sparingly.
+
update(surface, area=None) -> None
+
+
+ +
+ +
+
+pygame._sdl2.video.Image¶
+
+
Easy way to use a portion of a Texture without worrying about srcrect all the time.
+
Image(textureOrImage, srcrect=None) -> Image
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Get the rectangular area of the Image.
+—Copy a portion of the Image to the rendering target.
+—Gets and sets the angle the Image draws itself with.
+—Gets and sets the origin. Origin=None means the Image will be rotated around its center.
+—Gets and sets whether the Image is flipped on the x axis.
+—Gets and sets whether the Image is flipped on the y axis.
+—Gets and sets the Image color modifier.
+—Gets and sets the Image alpha modifier.
+—Gets and sets the blend mode for the Image.
+—Gets and sets the Texture the Image is based on.
+—Gets and sets the Rect the Image is based on.
+
+
+get_rect()¶
+
+
Get the rectangular area of the Image.
+
get_rect() -> Rect
+
+
+ +
+
+draw()¶
+
+
Copy a portion of the Image to the rendering target.
+
draw(srcrect=None, dstrect=None) -> None
+
+
+ +
+
+angle¶
+
+
Gets and sets the angle the Image draws itself with.
+
angle -> float
+
+
+ +
+
+origin¶
+
+
Gets and sets the origin. Origin=None means the Image will be rotated around its center.
+
origin -> (float, float) or None.
+
+
+ +
+
+flipX¶
+
+
Gets and sets whether the Image is flipped on the x axis.
+
flipX -> bool
+
+
+ +
+
+flipY¶
+
+
Gets and sets whether the Image is flipped on the y axis.
+
flipY -> bool
+
+
+ +
+
+color¶
+
+
Gets and sets the Image color modifier.
+
color -> Color
+
+
+ +
+
+alpha¶
+
+
Gets and sets the Image alpha modifier.
+
alpha -> float
+
+
+ +
+
+blend_mode¶
+
+
Gets and sets the blend mode for the Image.
+
blend_mode -> int
+
+
+ +
+
+texture¶
+
+
Gets and sets the Texture the Image is based on.
+
texture -> Texture
+
+
+ +
+
+srcrect¶
+
+
Gets and sets the Rect the Image is based on.
+
srcrect -> Rect
+
+
+ +
+ +
+
+pygame._sdl2.video.Renderer¶
+
+
Create a 2D rendering context for a window.
+
Renderer(window, index=-1, accelerated=-1, vsync=False, target_texture=False) -> Renderer
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Easy way to create a Renderer.
+—Gets and sets the blend mode used by the drawing functions.
+—Gets and sets the color used by the drawing functions.
+—Clear the current rendering target with the drawing color.
+—Updates the screen with any new rendering since previous call.
+—Returns the drawing area on the target.
+—Set the drawing area on the target. If area is None, the entire target will be used.
+—Gets and sets the logical size.
+—Gets and sets the scale.
+—Gets and sets the render target. None represents the default target (the renderer).
+—For compatibility purposes. Textures created by different Renderers cannot be shared!
+—Draws a line.
+—Draws a point.
+—Draws a rectangle.
+—Fills a rectangle.
+—Read pixels from current render target and create a pygame.Surface. WARNING: Slow operation, use sparingly.
+
+
+classmethod from_window()¶
+
+
Easy way to create a Renderer.
+
from_window(window) -> Renderer
+
+
+ +
+
+draw_blend_mode¶
+
+
Gets and sets the blend mode used by the drawing functions.
+
draw_blend_mode -> int
+
+
+ +
+
+draw_color¶
+
+
Gets and sets the color used by the drawing functions.
+
draw_color -> Color
+
+
+ +
+
+clear()¶
+
+
Clear the current rendering target with the drawing color.
+
clear() -> None
+
+
+ +
+
+present()¶
+
+
Updates the screen with any new rendering since previous call.
+
present() -> None
+
+
+ +
+
+get_viewport()¶
+
+
Returns the drawing area on the target.
+
get_viewport() -> Rect
+
+
+ +
+
+set_viewport()¶
+
+
Set the drawing area on the target. If area is None, the entire target will be used.
+
set_viewport(area) -> None
+
+
+ +
+
+logical_size¶
+
+
Gets and sets the logical size.
+
logical_size -> (int width, int height)
+
+
+ +
+
+scale¶
+
+
Gets and sets the scale.
+
scale -> (float x_scale, float y_scale)
+
+
+ +
+
+target¶
+
+
Gets and sets the render target. None represents the default target (the renderer).
+
target -> Texture or None
+
+
+ +
+
+blit()¶
+
+
For compatibility purposes. Textures created by different Renderers cannot be shared!
+
blit(soure, dest, area=None, special_flags=0)-> Rect
+
+
+ +
+
+draw_line()¶
+
+
Draws a line.
+
draw_line(p1, p2) -> None
+
+
+ +
+
+draw_point()¶
+
+
Draws a point.
+
draw_point(point) -> None
+
+
+ +
+
+draw_rect()¶
+
+
Draws a rectangle.
+
draw_rect(rect)-> None
+
+
+ +
+
+fill_rect()¶
+
+
Fills a rectangle.
+
fill_rect(rect)-> None
+
+
+ +
+
+to_surface()¶
+
+
Read pixels from current render target and create a pygame.Surface. WARNING: Slow operation, use sparingly.
+
to_surface(surface=None, area=None)-> Surface
+
+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sndarray.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sndarray.html new file mode 100644 index 0000000..0551dbf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sndarray.html @@ -0,0 +1,274 @@ + + + + + + + + + pygame.sndarray — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.sndarray
+
+
pygame module for accessing sound sample data
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—copy Sound samples into an array
+—reference Sound samples into an array
+—convert an array into a Sound object
+—Sets the array system to be used for sound arrays
+—Gets the currently active array type.
+—Gets the array system types currently supported.
+

Functions to convert between NumPy arrays and Sound objects. This +module will only be functional when pygame can use the external NumPy +package. If NumPy can't be imported, surfarray becomes a MissingModule +object.

+

Sound data is made of thousands of samples per second, and each sample is the +amplitude of the wave at a particular moment in time. For example, in 22-kHz +format, element number 5 of the array is the amplitude of the wave after +5/22000 seconds.

+

The arrays are indexed by the X axis first, followed by the Y axis. +Each sample is an 8-bit or 16-bit integer, depending on the data format. A +stereo sound file has two values per sample, while a mono sound file only has +one.

+
+
+pygame.sndarray.array()¶
+
+
copy Sound samples into an array
+
array(Sound) -> array
+
+

Creates a new array for the sound data and copies the samples. The array +will always be in the format returned from pygame.mixer.get_init().

+
+ +
+
+pygame.sndarray.samples()¶
+
+
reference Sound samples into an array
+
samples(Sound) -> array
+
+

Creates a new array that directly references the samples in a Sound object. +Modifying the array will change the Sound. The array will always be in the +format returned from pygame.mixer.get_init().

+
+ +
+
+pygame.sndarray.make_sound()¶
+
+
convert an array into a Sound object
+
make_sound(array) -> Sound
+
+

Create a new playable Sound object from an array. The mixer module must be +initialized and the array format must be similar to the mixer audio format.

+
+ +
+
+pygame.sndarray.use_arraytype()¶
+
+
Sets the array system to be used for sound arrays
+
use_arraytype (arraytype) -> None
+
+

DEPRECATED: Uses the requested array type for the module functions. The +only supported arraytype is 'numpy'. Other values will raise ValueError. +Using this function will raise a DeprecationWarning. +.. ## pygame.sndarray.use_arraytype ##

+
+ +
+
+pygame.sndarray.get_arraytype()¶
+
+
Gets the currently active array type.
+
get_arraytype () -> str
+
+

DEPRECATED: Returns the currently active array type. This will be a value of the +get_arraytypes() tuple and indicates which type of array module is used +for the array creation. Using this function will raise a DeprecationWarning.

+
+

New in pygame 1.8.

+
+
+ +
+
+pygame.sndarray.get_arraytypes()¶
+
+
Gets the array system types currently supported.
+
get_arraytypes () -> tuple
+
+

DEPRECATED: Checks, which array systems are available and returns them as a tuple of +strings. The values of the tuple can be used directly in the +pygame.sndarray.use_arraytype()Sets the array system to be used for sound arrays () method. If no supported array +system could be found, None will be returned. Using this function will raise a +DeprecationWarning.

+
+

New in pygame 1.8.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sprite.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sprite.html new file mode 100644 index 0000000..7656f6e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/sprite.html @@ -0,0 +1,1381 @@ + + + + + + + + + pygame.sprite — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.sprite
+
+
pygame module with basic game object classes
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Simple base class for visible game objects.
+—A subclass of Sprite with more attributes and features.
+—A container class to hold and manage multiple Sprite objects.
+—Same as pygame.sprite.Group
+—Same as pygame.sprite.Group
+—Group sub-class that tracks dirty updates.
+—RenderUpdates sub-class that draws Sprites in order of addition.
+—LayeredUpdates is a sprite group that handles layers and draws like OrderedUpdates.
+—LayeredDirty group is for DirtySprite objects. Subclasses LayeredUpdates.
+—Group container that holds a single sprite.
+—Find sprites in a group that intersect another sprite.
+—Collision detection between two sprites, using rects.
+—Collision detection between two sprites, using rects scaled to a ratio.
+—Collision detection between two sprites, using circles.
+—Collision detection between two sprites, using circles scaled to a ratio.
+—Collision detection between two sprites, using masks.
+—Find all sprites that collide between two groups.
+—Simple test if a sprite intersects anything in a group.
+

This module contains several simple classes to be used within games. There is +the main Sprite class and several Group classes that contain Sprites. The use +of these classes is entirely optional when using pygame. The classes are fairly +lightweight and only provide a starting place for the code that is common to +most games.

+

The Sprite class is intended to be used as a base class for the different types +of objects in the game. There is also a base Group class that simply stores +sprites. A game could create new types of Group classes that operate on +specially customized Sprite instances they contain.

+

The basic Sprite class can draw the Sprites it contains to a Surface. The +Group.draw() method requires that each Sprite have a Surface.image +attribute and a Surface.rect. The Group.clear() method requires these +same attributes, and can be used to erase all the Sprites with background. +There are also more advanced Groups: pygame.sprite.RenderUpdates() and +pygame.sprite.OrderedUpdates().

+

Lastly, this module contains several collision functions. These help find +sprites inside multiple groups that have intersecting bounding rectangles. To +find the collisions, the Sprites are required to have a Surface.rect +attribute assigned.

+

The groups are designed for high efficiency in removing and adding Sprites to +them. They also allow cheap testing to see if a Sprite already exists in a +Group. A given Sprite can exist in any number of groups. A game could use some +groups to control object rendering, and a completely separate set of groups to +control interaction or player movement. Instead of adding type attributes or +bools to a derived Sprite class, consider keeping the Sprites inside organized +Groups. This will allow for easier lookup later in the game.

+

Sprites and Groups manage their relationships with the add() and +remove() methods. These methods can accept a single or multiple targets for +membership. The default initializers for these classes also takes a single or +list of targets for initial membership. It is safe to repeatedly add and remove +the same Sprite from a Group.

+

While it is possible to design sprite and group classes that don't derive from +the Sprite and AbstractGroup classes below, it is strongly recommended that you +extend those when you add a Sprite or Group class.

+

Sprites are not thread safe. So lock them yourself if using threads.

+
+
+pygame.sprite.Sprite¶
+
+
Simple base class for visible game objects.
+
Sprite(*groups) -> Sprite
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—method to control sprite behavior
+—add the sprite to groups
+—remove the sprite from groups
+—remove the Sprite from all Groups
+—does the sprite belong to any groups
+—list of Groups that contain this Sprite
+

The base class for visible game objects. Derived classes will want to +override the Sprite.update() and assign a Sprite.image and +Sprite.rect attributes. The initializer can accept any number of Group +instances to be added to.

+

When subclassing the Sprite, be sure to call the base initializer before +adding the Sprite to Groups. For example:

+
class Block(pygame.sprite.Sprite):
+
+    # Constructor. Pass in the color of the block,
+    # and its x and y position
+    def __init__(self, color, width, height):
+       # Call the parent class (Sprite) constructor
+       pygame.sprite.Sprite.__init__(self)
+
+       # Create an image of the block, and fill it with a color.
+       # This could also be an image loaded from the disk.
+       self.image = pygame.Surface([width, height])
+       self.image.fill(color)
+
+       # Fetch the rectangle object that has the dimensions of the image
+       # Update the position of this object by setting the values of rect.x and rect.y
+       self.rect = self.image.get_rect()
+
+
+
+
+update()¶
+
+
method to control sprite behavior
+
update(*args, **kwargs) -> None
+
+

The default implementation of this method does nothing; it's just a +convenient "hook" that you can override. This method is called by +Group.update() with whatever arguments you give it.

+

There is no need to use this method if not using the convenience method +by the same name in the Group class.

+
+ +
+
+add()¶
+
+
add the sprite to groups
+
add(*groups) -> None
+
+

Any number of Group instances can be passed as arguments. The Sprite will +be added to the Groups it is not already a member of.

+
+ +
+
+remove()¶
+
+
remove the sprite from groups
+
remove(*groups) -> None
+
+

Any number of Group instances can be passed as arguments. The Sprite will +be removed from the Groups it is currently a member of.

+
+ +
+
+kill()¶
+
+
remove the Sprite from all Groups
+
kill() -> None
+
+

The Sprite is removed from all the Groups that contain it. This won't +change anything about the state of the Sprite. It is possible to continue +to use the Sprite after this method has been called, including adding it +to Groups.

+
+ +
+
+alive()¶
+
+
does the sprite belong to any groups
+
alive() -> bool
+
+

Returns True when the Sprite belongs to one or more Groups.

+
+ +
+
+groups()¶
+
+
list of Groups that contain this Sprite
+
groups() -> group_list
+
+

Return a list of all the Groups that contain this Sprite.

+
+ +
+ +
+
+pygame.sprite.DirtySprite¶
+
+
A subclass of Sprite with more attributes and features.
+
DirtySprite(*groups) -> DirtySprite
+
+

Extra DirtySprite attributes with their default values:

+

dirty = 1

+
if set to 1, it is repainted and then set to 0 again
+if set to 2 then it is always dirty ( repainted each frame,
+flag is not reset)
+0 means that it is not dirty and therefore not repainted again
+
+
+

blendmode = 0

+
its the special_flags argument of blit, blendmodes
+
+
+

source_rect = None

+
source rect to use, remember that it is relative to
+topleft (0,0) of self.image
+
+
+

visible = 1

+
normally 1, if set to 0 it will not be repainted
+(you must set it dirty too to be erased from screen)
+
+
+

layer = 0

+
(READONLY value, it is read when adding it to the
+LayeredDirty, for details see doc of LayeredDirty)
+
+
+
+ +
+
+pygame.sprite.Group¶
+
+
A container class to hold and manage multiple Sprite objects.
+
Group(*sprites) -> Group
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—list of the Sprites this Group contains
+—duplicate the Group
+—add Sprites to this Group
+—remove Sprites from the Group
+—test if a Group contains Sprites
+—call the update method on contained Sprites
+—blit the Sprite images
+—draw a background over the Sprites
+—remove all Sprites
+

A simple container for Sprite objects. This class can be inherited to create +containers with more specific behaviors. The constructor takes any number of +Sprite arguments to add to the Group. The group supports the following +standard Python operations:

+
in      test if a Sprite is contained
+len     the number of Sprites contained
+bool    test if any Sprites are contained
+iter    iterate through all the Sprites
+
+
+

The Sprites in the Group are ordered only on python 3.6 and higher. +Below python 3.6 drawing and iterating over the Sprites is in no particular order.

+
+
+sprites()¶
+
+
list of the Sprites this Group contains
+
sprites() -> sprite_list
+
+

Return a list of all the Sprites this group contains. You can also get an +iterator from the group, but you cannot iterate over a Group while +modifying it.

+
+ +
+
+copy()¶
+
+
duplicate the Group
+
copy() -> Group
+
+

Creates a new Group with all the same Sprites as the original. If you +have subclassed Group, the new object will have the same (sub-)class as +the original. This only works if the derived class's constructor takes +the same arguments as the Group class's.

+
+ +
+
+add()¶
+
+
add Sprites to this Group
+
add(*sprites) -> None
+
+

Add any number of Sprites to this Group. This will only add Sprites that +are not already members of the Group.

+

Each sprite argument can also be a iterator containing Sprites.

+
+ +
+
+remove()¶
+
+
remove Sprites from the Group
+
remove(*sprites) -> None
+
+

Remove any number of Sprites from the Group. This will only remove +Sprites that are already members of the Group.

+

Each sprite argument can also be a iterator containing Sprites.

+
+ +
+
+has()¶
+
+
test if a Group contains Sprites
+
has(*sprites) -> bool
+
+

Return True if the Group contains all of the given sprites. This is +similar to using the "in" operator on the Group ("if sprite in group: +..."), which tests if a single Sprite belongs to a Group.

+

Each sprite argument can also be a iterator containing Sprites.

+
+ +
+
+update()¶
+
+
call the update method on contained Sprites
+
update(*args, **kwargs) -> None
+
+

Calls the update() method on all Sprites in the Group. The base +Sprite class has an update method that takes any number of arguments and +does nothing. The arguments passed to Group.update() will be passed +to each Sprite.

+

There is no way to get the return value from the Sprite.update() +methods.

+
+ +
+
+draw()¶
+
+
blit the Sprite images
+
draw(Surface) -> List[Rect]
+
+

Draws the contained Sprites to the Surface argument. This uses the +Sprite.image attribute for the source surface, and Sprite.rect +for the position.

+

The Group does not keep sprites in any order, so the draw order is +arbitrary.

+
+ +
+
+clear()¶
+
+
draw a background over the Sprites
+
clear(Surface_dest, background) -> None
+
+

Erases the Sprites used in the last Group.draw() call. The +destination Surface is cleared by filling the drawn Sprite positions with +the background.

+

The background is usually a Surface image the same dimensions as the +destination Surface. However, it can also be a callback function that +takes two arguments; the destination Surface and an area to clear. The +background callback function will be called several times each clear.

+

Here is an example callback that will clear the Sprites with solid red:

+
def clear_callback(surf, rect):
+    color = 255, 0, 0
+    surf.fill(color, rect)
+
+
+
+ +
+
+empty()¶
+
+
remove all Sprites
+
empty() -> None
+
+

Removes all Sprites from this Group.

+
+ +
+ +
+
+pygame.sprite.RenderPlain¶
+
+
Same as pygame.sprite.Group
+
+

This class is an alias to pygame.sprite.Group(). It has no additional functionality.

+
+ +
+
+pygame.sprite.RenderClear¶
+
+
Same as pygame.sprite.Group
+
+

This class is an alias to pygame.sprite.Group(). It has no additional functionality.

+
+ +
+
+pygame.sprite.RenderUpdates¶
+
+
Group sub-class that tracks dirty updates.
+
RenderUpdates(*sprites) -> RenderUpdates
+
+ +++++ + + + + + + +
+—blit the Sprite images and track changed areas
+

This class is derived from pygame.sprite.Group(). It has an extended +draw() method that tracks the changed areas of the screen.

+
+
+draw()¶
+
+
blit the Sprite images and track changed areas
+
draw(surface) -> Rect_list
+
+

Draws all the Sprites to the surface, the same as Group.draw(). This +method also returns a list of Rectangular areas on the screen that have +been changed. The returned changes include areas of the screen that have +been affected by previous Group.clear() calls.

+

The returned Rect list should be passed to pygame.display.update(). +This will help performance on software driven display modes. This type of +updating is usually only helpful on destinations with non-animating +backgrounds.

+
+ +
+ +
+
+pygame.sprite.OrderedUpdates()¶
+
+
RenderUpdates sub-class that draws Sprites in order of addition.
+
OrderedUpdates(*spites) -> OrderedUpdates
+
+

This class derives from pygame.sprite.RenderUpdates(). It maintains the +order in which the Sprites were added to the Group for rendering. This makes +adding and removing Sprites from the Group a little slower than regular +Groups.

+
+ +
+
+pygame.sprite.LayeredUpdates¶
+
+
LayeredUpdates is a sprite group that handles layers and draws like OrderedUpdates.
+
LayeredUpdates(*spites, **kwargs) -> LayeredUpdates
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—add a sprite or sequence of sprites to a group
+—returns a ordered list of sprites (first back, last top).
+—draw all sprites in the right order onto the passed surface.
+—returns a list with all sprites at that position.
+—returns the sprite at the index idx from the groups sprites
+—removes all sprites from a layer and returns them as a list.
+—returns a list of layers defined (unique), sorted from bottom up.
+—changes the layer of the sprite
+—returns the layer that sprite is currently in.
+—returns the top layer
+—returns the bottom layer
+—brings the sprite to front layer
+—moves the sprite to the bottom layer
+—returns the topmost sprite
+—returns all sprites from a layer, ordered by how they where added
+—switches the sprites from layer1 to layer2
+

This group is fully compatible with pygame.sprite.SpriteSimple base class for visible game objects..

+

You can set the default layer through kwargs using 'default_layer' and an +integer for the layer. The default layer is 0.

+

If the sprite you add has an attribute _layer then that layer will be used. +If the **kwarg contains 'layer' then the sprites passed will be added to +that layer (overriding the sprite.layer attribute). If neither sprite +has attribute layer nor **kwarg then the default layer is used to add the +sprites.

+
+

New in pygame 1.8.

+
+
+
+add()¶
+
+
add a sprite or sequence of sprites to a group
+
add(*sprites, **kwargs) -> None
+
+

If the sprite(s) have an attribute layer then that is used for the +layer. If **kwargs contains 'layer' then the sprite(s) will be added +to that argument (overriding the sprite layer attribute). If neither is +passed then the sprite(s) will be added to the default layer.

+
+ +
+
+sprites()¶
+
+
returns a ordered list of sprites (first back, last top).
+
sprites() -> sprites
+
+
+ +
+
+draw()¶
+
+
draw all sprites in the right order onto the passed surface.
+
draw(surface) -> Rect_list
+
+
+ +
+
+get_sprites_at()¶
+
+
returns a list with all sprites at that position.
+
get_sprites_at(pos) -> colliding_sprites
+
+

Bottom sprites first, top last.

+
+ +
+
+get_sprite()¶
+
+
returns the sprite at the index idx from the groups sprites
+
get_sprite(idx) -> sprite
+
+

Raises IndexOutOfBounds if the idx is not within range.

+
+ +
+
+remove_sprites_of_layer()¶
+
+
removes all sprites from a layer and returns them as a list.
+
remove_sprites_of_layer(layer_nr) -> sprites
+
+
+ +
+
+layers()¶
+
+
returns a list of layers defined (unique), sorted from bottom up.
+
layers() -> layers
+
+
+ +
+
+change_layer()¶
+
+
changes the layer of the sprite
+
change_layer(sprite, new_layer) -> None
+
+

sprite must have been added to the renderer. It is not checked.

+
+ +
+
+get_layer_of_sprite()¶
+
+
returns the layer that sprite is currently in.
+
get_layer_of_sprite(sprite) -> layer
+
+

If the sprite is not found then it will return the default layer.

+
+ +
+
+get_top_layer()¶
+
+
returns the top layer
+
get_top_layer() -> layer
+
+
+ +
+
+get_bottom_layer()¶
+
+
returns the bottom layer
+
get_bottom_layer() -> layer
+
+
+ +
+
+move_to_front()¶
+
+
brings the sprite to front layer
+
move_to_front(sprite) -> None
+
+

Brings the sprite to front, changing sprite layer to topmost layer (added +at the end of that layer).

+
+ +
+
+move_to_back()¶
+
+
moves the sprite to the bottom layer
+
move_to_back(sprite) -> None
+
+

Moves the sprite to the bottom layer, moving it behind all other layers +and adding one additional layer.

+
+ +
+
+get_top_sprite()¶
+
+
returns the topmost sprite
+
get_top_sprite() -> Sprite
+
+
+ +
+
+get_sprites_from_layer()¶
+
+
returns all sprites from a layer, ordered by how they where added
+
get_sprites_from_layer(layer) -> sprites
+
+

Returns all sprites from a layer, ordered by how they where added. It +uses linear search and the sprites are not removed from layer.

+
+ +
+
+switch_layer()¶
+
+
switches the sprites from layer1 to layer2
+
switch_layer(layer1_nr, layer2_nr) -> None
+
+

The layers number must exist, it is not checked.

+
+ +
+ +
+
+pygame.sprite.LayeredDirty¶
+
+
LayeredDirty group is for DirtySprite objects. Subclasses LayeredUpdates.
+
LayeredDirty(*spites, **kwargs) -> LayeredDirty
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—draw all sprites in the right order onto the passed surface.
+—used to set background
+—repaints the given area
+—clip the area where to draw. Just pass None (default) to reset the clip
+—clip the area where to draw. Just pass None (default) to reset the clip
+—changes the layer of the sprite
+—sets the threshold in milliseconds
+—sets the threshold in milliseconds
+

This group requires pygame.sprite.DirtySpriteA subclass of Sprite with more attributes and features. or any sprite that +has the following attributes:

+
image, rect, dirty, visible, blendmode (see doc of DirtySprite).
+
+
+

It uses the dirty flag technique and is therefore faster than the +pygame.sprite.RenderUpdatesGroup sub-class that tracks dirty updates. if you have many static sprites. It +also switches automatically between dirty rect update and full screen +drawing, so you do not have to worry what would be faster.

+

Same as for the pygame.sprite.GroupA container class to hold and manage multiple Sprite objects.. You can specify some +additional attributes through kwargs:

+
_use_update: True/False   default is False
+_default_layer: default layer where sprites without a layer are added.
+_time_threshold: threshold time for switching between dirty rect mode
+    and fullscreen mode, defaults to 1000./80  == 1000./fps
+
+
+
+

New in pygame 1.8.

+
+
+
+draw()¶
+
+
draw all sprites in the right order onto the passed surface.
+
draw(surface, bgd=None) -> Rect_list
+
+

You can pass the background too. If a background is already set, then the +bgd argument has no effect.

+
+ +
+
+clear()¶
+
+
used to set background
+
clear(surface, bgd) -> None
+
+
+ +
+
+repaint_rect()¶
+
+
repaints the given area
+
repaint_rect(screen_rect) -> None
+
+

screen_rect is in screen coordinates.

+
+ +
+
+set_clip()¶
+
+
clip the area where to draw. Just pass None (default) to reset the clip
+
set_clip(screen_rect=None) -> None
+
+
+ +
+
+get_clip()¶
+
+
clip the area where to draw. Just pass None (default) to reset the clip
+
get_clip() -> Rect
+
+
+ +
+
+change_layer()¶
+
+
changes the layer of the sprite
+
change_layer(sprite, new_layer) -> None
+
+

sprite must have been added to the renderer. It is not checked.

+
+ +
+
+set_timing_treshold()¶
+
+
sets the threshold in milliseconds
+
set_timing_treshold(time_ms) -> None
+
+

DEPRECATED: Use set_timing_threshold() instead.

+
+

Deprecated since pygame 2.1.1.

+
+
+ +
+
+set_timing_threshold()¶
+
+
sets the threshold in milliseconds
+
set_timing_threshold(time_ms) -> None
+
+

Defaults to 1000.0 / 80.0. This means that the screen will be painted +using the flip method rather than the update method if the update +method is taking so long to update the screen that the frame rate falls +below 80 frames per second.

+
+

New in pygame 2.1.1.

+
+
+
Raises
+

TypeError -- if time_ms is not int or float

+
+
+
+ +
+ +
+
+pygame.sprite.GroupSingle()¶
+
+
Group container that holds a single sprite.
+
GroupSingle(sprite=None) -> GroupSingle
+
+

The GroupSingle container only holds a single Sprite. When a new Sprite is +added, the old one is removed.

+

There is a special property, GroupSingle.sprite, that accesses the +Sprite that this Group contains. It can be None when the Group is empty. The +property can also be assigned to add a Sprite into the GroupSingle +container.

+
+ +
+
+pygame.sprite.spritecollide()¶
+
+
Find sprites in a group that intersect another sprite.
+
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
+
+

Return a list containing all Sprites in a Group that intersect with another +Sprite. Intersection is determined by comparing the Sprite.rect +attribute of each Sprite.

+

The dokill argument is a bool. If set to True, all Sprites that collide will +be removed from the Group.

+

The collided argument is a callback function used to calculate if two +sprites are colliding. it should take two sprites as values, and return a +bool value indicating if they are colliding. If collided is not passed, all +sprites must have a "rect" value, which is a rectangle of the sprite area, +which will be used to calculate the collision.

+

collided callables:

+
collide_rect, collide_rect_ratio, collide_circle,
+collide_circle_ratio, collide_mask
+
+
+

Example:

+
# See if the Sprite block has collided with anything in the Group block_list
+# The True flag will remove the sprite in block_list
+blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)
+
+# Check the list of colliding sprites, and add one to the score for each one
+for block in blocks_hit_list:
+    score +=1
+
+
+
+ +
+
+pygame.sprite.collide_rect()¶
+
+
Collision detection between two sprites, using rects.
+
collide_rect(left, right) -> bool
+
+

Tests for collision between two sprites. Uses the pygame rect colliderect +function to calculate the collision. Intended to be passed as a collided +callback function to the *collide functions. Sprites must have a "rect" +attributes.

+
+

New in pygame 1.8.

+
+
+ +
+
+pygame.sprite.collide_rect_ratio()¶
+
+
Collision detection between two sprites, using rects scaled to a ratio.
+
collide_rect_ratio(ratio) -> collided_callable
+
+

A callable class that checks for collisions between two sprites, using a +scaled version of the sprites rects.

+

Is created with a ratio, the instance is then intended to be passed as a +collided callback function to the *collide functions.

+

A ratio is a floating point number - 1.0 is the same size, 2.0 is twice as +big, and 0.5 is half the size.

+
+

New in pygame 1.8.1.

+
+
+ +
+
+pygame.sprite.collide_circle()¶
+
+
Collision detection between two sprites, using circles.
+
collide_circle(left, right) -> bool
+
+

Tests for collision between two sprites, by testing to see if two circles +centered on the sprites overlap. If the sprites have a "radius" attribute, +that is used to create the circle, otherwise a circle is created that is big +enough to completely enclose the sprites rect as given by the "rect" +attribute. Intended to be passed as a collided callback function to the +*collide functions. Sprites must have a "rect" and an optional "radius" +attribute.

+
+

New in pygame 1.8.1.

+
+
+ +
+
+pygame.sprite.collide_circle_ratio()¶
+
+
Collision detection between two sprites, using circles scaled to a ratio.
+
collide_circle_ratio(ratio) -> collided_callable
+
+

A callable class that checks for collisions between two sprites, using a +scaled version of the sprites radius.

+

Is created with a floating point ratio, the instance is then intended to be +passed as a collided callback function to the *collide functions.

+

A ratio is a floating point number - 1.0 is the same size, 2.0 is twice as +big, and 0.5 is half the size.

+

The created callable tests for collision between two sprites, by testing to +see if two circles centered on the sprites overlap, after scaling the +circles radius by the stored ratio. If the sprites have a "radius" +attribute, that is used to create the circle, otherwise a circle is created +that is big enough to completely enclose the sprites rect as given by the +"rect" attribute. Intended to be passed as a collided callback function to +the *collide functions. Sprites must have a "rect" and an optional "radius" +attribute.

+
+

New in pygame 1.8.1.

+
+
+ +
+
+pygame.sprite.collide_mask()¶
+
+
Collision detection between two sprites, using masks.
+
collide_mask(sprite1, sprite2) -> (int, int)
+
collide_mask(sprite1, sprite2) -> None
+
+

Tests for collision between two sprites, by testing if their bitmasks +overlap (uses pygame.mask.Mask.overlap()Returns the point of intersection). If the sprites have a +mask attribute, it is used as the mask, otherwise a mask is created from +the sprite's image (uses pygame.mask.from_surface()Creates a Mask from the given surface). Sprites must +have a rect attribute; the mask attribute is optional.

+

The first point of collision between the masks is returned. The collision +point is offset from sprite1's mask's topleft corner (which is always +(0, 0)). The collision point is a position within the mask and is not +related to the actual screen position of sprite1.

+

This function is intended to be passed as a collided callback function +to the group collide functions (see spritecollide(), +groupcollide(), spritecollideany()).

+
+

Note

+

To increase performance, create and set a mask attibute for all +sprites that will use this function to check for collisions. Otherwise, +each time this function is called it will create new masks.

+
+
+

Note

+

A new mask needs to be recreated each time a sprite's image is changed +(e.g. if a new image is used or the existing image is rotated).

+
+
# Example of mask creation for a sprite.
+sprite.mask = pygame.mask.from_surface(sprite.image)
+
+
+
+
Returns
+

first point of collision between the masks or None if no +collision

+
+
Return type
+

tuple(int, int) or NoneType

+
+
+
+

New in pygame 1.8.0.

+
+
+ +
+
+pygame.sprite.groupcollide()¶
+
+
Find all sprites that collide between two groups.
+
groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict
+
+

This will find collisions between all the Sprites in two groups. +Collision is determined by comparing the Sprite.rect attribute of +each Sprite or by using the collided function if it is not None.

+

Every Sprite inside group1 is added to the return dictionary. The value for +each item is the list of Sprites in group2 that intersect.

+

If either dokill argument is True, the colliding Sprites will be removed +from their respective Group.

+

The collided argument is a callback function used to calculate if two sprites are +colliding. It should take two sprites as values and return a bool value +indicating if they are colliding. If collided is not passed, then all +sprites must have a "rect" value, which is a rectangle of the sprite area, +which will be used to calculate the collision.

+
+ +
+
+pygame.sprite.spritecollideany()¶
+
+
Simple test if a sprite intersects anything in a group.
+
spritecollideany(sprite, group, collided = None) -> Sprite Collision with the returned sprite.
+
spritecollideany(sprite, group, collided = None) -> None No collision
+
+

If the sprite collides with any single sprite in the group, a single +sprite from the group is returned. On no collision None is returned.

+

If you don't need all the features of the pygame.sprite.spritecollide() function, this +function will be a bit quicker.

+

The collided argument is a callback function used to calculate if two sprites are +colliding. It should take two sprites as values and return a bool value +indicating if they are colliding. If collided is not passed, then all +sprites must have a "rect" value, which is a rectangle of the sprite area, +which will be used to calculate the collision.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/surface.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/surface.html new file mode 100644 index 0000000..dfd1d10 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/surface.html @@ -0,0 +1,1290 @@ + + + + + + + + + pygame.Surface — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.Surface¶
+
+
pygame object for representing images
+
Surface((width, height), flags=0, depth=0, masks=None) -> Surface
+
Surface((width, height), flags=0, Surface) -> Surface
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—draw one image onto another
+—draw many images onto another
+—change the pixel format of an image
+—change the pixel format of an image including per pixel alphas
+—create a new copy of a Surface
+—fill Surface with a solid color
+—Shift the surface image in place
+—Set the transparent colorkey
+—Get the current transparent colorkey
+—set the alpha value for the full Surface image
+—get the current Surface transparency value
+—lock the Surface memory for pixel access
+—unlock the Surface memory from pixel access
+—test if the Surface requires locking
+—test if the Surface is current locked
+—Gets the locks for the Surface
+—get the color value at a single pixel
+—set the color value for a single pixel
+—get the mapped color value at a single pixel
+—get the color index palette for an 8-bit Surface
+—get the color for a single entry in a palette
+—set the color palette for an 8-bit Surface
+—set the color for a single index in an 8-bit Surface palette
+—convert a color into a mapped color value
+—convert a mapped integer color value into a Color
+—set the current clipping area of the Surface
+—get the current clipping area of the Surface
+—create a new surface that references its parent
+—find the parent of a subsurface
+—find the top level parent of a subsurface
+—find the position of a child subsurface inside a parent
+—find the absolute position of a child subsurface inside its top level parent
+—get the dimensions of the Surface
+—get the width of the Surface
+—get the height of the Surface
+—get the rectangular area of the Surface
+—get the bit depth of the Surface pixel format
+—get the bytes used per Surface pixel
+—get the additional flags used for the Surface
+—get the number of bytes used per Surface row
+—the bitmasks needed to convert between a color and a mapped integer
+—set the bitmasks needed to convert between a color and a mapped integer
+—the bit shifts needed to convert between a color and a mapped integer
+—sets the bit shifts needed to convert between a color and a mapped integer
+—the significant bits used to convert between a color and a mapped integer
+—find the smallest rect containing data
+—return a buffer view of the Surface's pixels.
+—acquires a buffer object for the pixels of the Surface.
+—pixel buffer address
+

A pygame Surface is used to represent any image. The Surface has a fixed +resolution and pixel format. Surfaces with 8-bit pixels use a color palette +to map to 24-bit color.

+

Call pygame.Surface()pygame object for representing images to create a new image object. The Surface will +be cleared to all black. The only required arguments are the sizes. With no +additional arguments, the Surface will be created in a format that best +matches the display Surface.

+

The pixel format can be controlled by passing the bit depth or an existing +Surface. The flags argument is a bitmask of additional features for the +surface. You can pass any combination of these flags:

+
HWSURFACE    (obsolete in pygame 2) creates the image in video memory
+SRCALPHA     the pixel format will include a per-pixel alpha
+
+
+

Both flags are only a request, and may not be possible for all displays and +formats.

+

Advance users can combine a set of bitmasks with a depth value. The masks +are a set of 4 integers representing which bits in a pixel will represent +each color. Normal Surfaces should not require the masks argument.

+

Surfaces can have many extra attributes like alpha planes, colorkeys, source +rectangle clipping. These functions mainly effect how the Surface is blitted +to other Surfaces. The blit routines will attempt to use hardware +acceleration when possible, otherwise they will use highly optimized +software blitting methods.

+

There are three types of transparency supported in pygame: colorkeys, +surface alphas, and pixel alphas. Surface alphas can be mixed with +colorkeys, but an image with per pixel alphas cannot use the other modes. +Colorkey transparency makes a single color value transparent. Any pixels +matching the colorkey will not be drawn. The surface alpha value is a single +value that changes the transparency for the entire image. A surface alpha of +255 is opaque, and a value of 0 is completely transparent.

+

Per pixel alphas are different because they store a transparency value for +every pixel. This allows for the most precise transparency effects, but it +also the slowest. Per pixel alphas cannot be mixed with surface alpha and +colorkeys.

+

There is support for pixel access for the Surfaces. Pixel access on hardware +surfaces is slow and not recommended. Pixels can be accessed using the +get_at() and set_at() functions. These methods are fine for +simple access, but will be considerably slow when doing of pixel work with +them. If you plan on doing a lot of pixel level work, it is recommended to +use a pygame.PixelArraypygame object for direct pixel access of surfaces, which gives an array like view of the +surface. For involved mathematical manipulations try the +pygame.surfarraypygame module for accessing surface pixel data using array interfaces module (It's quite quick, but requires NumPy.)

+

Any functions that directly access a surface's pixel data will need that +surface to be lock()'ed. These functions can lock() and +unlock() the surfaces themselves without assistance. But, if a +function will be called many times, there will be a lot of overhead for +multiple locking and unlocking of the surface. It is best to lock the +surface manually before making the function call many times, and then +unlocking when you are finished. All functions that need a locked surface +will say so in their docs. Remember to leave the Surface locked only while +necessary.

+

Surface pixels are stored internally as a single number that has all the +colors encoded into it. Use the map_rgb() and +unmap_rgb() to convert between individual red, green, and blue +values into a packed integer for that Surface.

+

Surfaces can also reference sections of other Surfaces. These are created +with the subsurface() method. Any change to either Surface will +effect the other.

+

Each Surface contains a clipping area. By default the clip area covers the +entire Surface. If it is changed, all drawing operations will only effect +the smaller area.

+
+
+blit()¶
+
+
draw one image onto another
+
blit(source, dest, area=None, special_flags=0) -> Rect
+
+

Draws a source Surface onto this Surface. The draw can be positioned with +the dest argument. The dest argument can either be a pair of coordinates representing the position of +the upper left corner of the blit or a Rect, where the upper left corner of the rectangle will be used as the +position for the blit. The size of the destination rectangle does not +effect the blit.

+

An optional area rectangle can be passed as well. This represents a +smaller portion of the source Surface to draw.

+
+

New in pygame 1.8: Optional special_flags: BLEND_ADD, BLEND_SUB, +BLEND_MULT, BLEND_MIN, BLEND_MAX.

+
+
+

New in pygame 1.8.1: Optional special_flags: BLEND_RGBA_ADD, BLEND_RGBA_SUB, +BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX +BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, +BLEND_RGB_MIN, BLEND_RGB_MAX.

+
+
+

New in pygame 1.9.2: Optional special_flags: BLEND_PREMULTIPLIED

+
+
+

New in pygame 2.0.0: Optional special_flags: BLEND_ALPHA_SDL2 - Uses the SDL2 blitter for alpha blending, +this gives different results than the default blitter, which is modelled after SDL1, due to +different approximations used for the alpha blending formula. The SDL2 blitter also supports +RLE on alpha blended surfaces which the pygame one does not.

+
+

The return rectangle is the area of the affected pixels, excluding any +pixels outside the destination Surface, or outside the clipping area.

+

Pixel alphas will be ignored when blitting to an 8 bit Surface.

+

For a surface with colorkey or blanket alpha, a blit to self may give +slightly different colors than a non self-blit.

+
+ +
+
+blits()¶
+
+
draw many images onto another
+
blits(blit_sequence=((source, dest), ...), doreturn=1) -> [Rect, ...] or None
+
blits(((source, dest, area), ...)) -> [Rect, ...]
+
blits(((source, dest, area, special_flags), ...)) -> [Rect, ...]
+
+

Draws many surfaces onto this Surface. It takes a sequence as input, +with each of the elements corresponding to the ones of blit(). +It needs at minimum a sequence of (source, dest).

+
+
Parameters
+
    +
  • blit_sequence -- a sequence of surfaces and arguments to blit them, +they correspond to the blit() arguments

  • +
  • doreturn -- if True, return a list of rects of the areas changed, +otherwise return None

  • +
+
+
Returns
+

a list of rects of the areas changed if doreturn is +True, otherwise None

+
+
Return type
+

list or None

+
+
+

New in pygame 1.9.4.

+
+ +
+
+convert()¶
+
+
change the pixel format of an image
+
convert(Surface=None) -> Surface
+
convert(depth, flags=0) -> Surface
+
convert(masks, flags=0) -> Surface
+
+

Creates a new copy of the Surface with the pixel format changed. The new +pixel format can be determined from another existing Surface. Otherwise +depth, flags, and masks arguments can be used, similar to the +pygame.Surface()pygame object for representing images call.

+

If no arguments are passed the new Surface will have the same pixel +format as the display Surface. This is always the fastest format for +blitting. It is a good idea to convert all Surfaces before they are +blitted many times.

+

The converted Surface will have no pixel alphas. They will be stripped if +the original had them. See convert_alpha() for preserving or +creating per-pixel alphas.

+

The new copy will have the same class as the copied surface. This lets +as Surface subclass inherit this method without the need to override, +unless subclass specific instance attributes also need copying.

+
+ +
+
+convert_alpha()¶
+
+
change the pixel format of an image including per pixel alphas
+
convert_alpha(Surface) -> Surface
+
convert_alpha() -> Surface
+
+

Creates a new copy of the surface with the desired pixel format. The new +surface will be in a format suited for quick blitting to the given format +with per pixel alpha. If no surface is given, the new surface will be +optimized for blitting to the current display.

+

Unlike the convert() method, the pixel format for the new +image will not be exactly the same as the requested source, but it will +be optimized for fast alpha blitting to the destination.

+

As with convert() the returned surface has the same class as +the converted surface.

+
+ +
+
+copy()¶
+
+
create a new copy of a Surface
+
copy() -> Surface
+
+

Makes a duplicate copy of a Surface. The new surface will have the same +pixel formats, color palettes, transparency settings, and class as the +original. If a Surface subclass also needs to copy any instance specific +attributes then it should override copy().

+
+ +
+
+fill()¶
+
+
fill Surface with a solid color
+
fill(color, rect=None, special_flags=0) -> Rect
+
+

Fill the Surface with a solid color. If no rect argument is given the +entire Surface will be filled. The rect argument will limit the fill to a +specific area. The fill will also be contained by the Surface clip area.

+

The color argument can be either a RGB sequence, a RGBA sequence +or a mapped color index. If using RGBA, the Alpha (A part of +RGBA) is ignored unless the surface uses per pixel alpha (Surface has +the SRCALPHA flag).

+
+

New in pygame 1.8: Optional special_flags: BLEND_ADD, BLEND_SUB, +BLEND_MULT, BLEND_MIN, BLEND_MAX.

+
+
+

New in pygame 1.8.1: Optional special_flags: BLEND_RGBA_ADD, BLEND_RGBA_SUB, +BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX +BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, +BLEND_RGB_MIN, BLEND_RGB_MAX.

+
+

This will return the affected Surface area.

+
+ +
+
+scroll()¶
+
+
Shift the surface image in place
+
scroll(dx=0, dy=0) -> None
+
+

Move the image by dx pixels right and dy pixels down. dx and dy may be +negative for left and up scrolls respectively. Areas of the surface that +are not overwritten retain their original pixel values. Scrolling is +contained by the Surface clip area. It is safe to have dx and dy values +that exceed the surface size.

+
+

New in pygame 1.9.

+
+
+ +
+
+set_colorkey()¶
+
+
Set the transparent colorkey
+
set_colorkey(Color, flags=0) -> None
+
set_colorkey(None) -> None
+
+

Set the current color key for the Surface. When blitting this Surface +onto a destination, any pixels that have the same color as the colorkey +will be transparent. The color can be an RGB color or a mapped color +integer. If None is passed, the colorkey will be unset.

+

The colorkey will be ignored if the Surface is formatted to use per pixel +alpha values. The colorkey can be mixed with the full Surface alpha +value.

+

The optional flags argument can be set to pygame.RLEACCEL to provide +better performance on non accelerated displays. An RLEACCEL Surface +will be slower to modify, but quicker to blit as a source.

+
+ +
+
+get_colorkey()¶
+
+
Get the current transparent colorkey
+
get_colorkey() -> RGB or None
+
+

Return the current colorkey value for the Surface. If the colorkey is not +set then None is returned.

+
+ +
+
+set_alpha()¶
+
+
set the alpha value for the full Surface image
+
set_alpha(value, flags=0) -> None
+
set_alpha(None) -> None
+
+

Set the current alpha value for the Surface. When blitting this Surface +onto a destination, the pixels will be drawn slightly transparent. The +alpha value is an integer from 0 to 255, 0 is fully transparent and 255 +is fully opaque. If None is passed for the alpha value, then alpha +blending will be disabled, including per-pixel alpha.

+

This value is different than the per pixel Surface alpha. For a surface +with per pixel alpha, blanket alpha is ignored and None is returned.

+
+

Changed in pygame 2.0: per-surface alpha can be combined with per-pixel +alpha.

+
+

The optional flags argument can be set to pygame.RLEACCEL to provide +better performance on non accelerated displays. An RLEACCEL Surface +will be slower to modify, but quicker to blit as a source.

+
+ +
+
+get_alpha()¶
+
+
get the current Surface transparency value
+
get_alpha() -> int_value
+
+

Return the current alpha value for the Surface.

+
+ +
+
+lock()¶
+
+
lock the Surface memory for pixel access
+
lock() -> None
+
+

Lock the pixel data of a Surface for access. On accelerated Surfaces, the +pixel data may be stored in volatile video memory or nonlinear compressed +forms. When a Surface is locked the pixel memory becomes available to +access by regular software. Code that reads or writes pixel values will +need the Surface to be locked.

+

Surfaces should not remain locked for more than necessary. A locked +Surface can often not be displayed or managed by pygame.

+

Not all Surfaces require locking. The mustlock() method can +determine if it is actually required. There is no performance penalty for +locking and unlocking a Surface that does not need it.

+

All pygame functions will automatically lock and unlock the Surface data +as needed. If a section of code is going to make calls that will +repeatedly lock and unlock the Surface many times, it can be helpful to +wrap the block inside a lock and unlock pair.

+

It is safe to nest locking and unlocking calls. The surface will only be +unlocked after the final lock is released.

+
+ +
+
+unlock()¶
+
+
unlock the Surface memory from pixel access
+
unlock() -> None
+
+

Unlock the Surface pixel data after it has been locked. The unlocked +Surface can once again be drawn and managed by pygame. See the +lock() documentation for more details.

+

All pygame functions will automatically lock and unlock the Surface data +as needed. If a section of code is going to make calls that will +repeatedly lock and unlock the Surface many times, it can be helpful to +wrap the block inside a lock and unlock pair.

+

It is safe to nest locking and unlocking calls. The surface will only be +unlocked after the final lock is released.

+
+ +
+
+mustlock()¶
+
+
test if the Surface requires locking
+
mustlock() -> bool
+
+

Returns True if the Surface is required to be locked to access pixel +data. Usually pure software Surfaces do not require locking. This method +is rarely needed, since it is safe and quickest to just lock all Surfaces +as needed.

+

All pygame functions will automatically lock and unlock the Surface data +as needed. If a section of code is going to make calls that will +repeatedly lock and unlock the Surface many times, it can be helpful to +wrap the block inside a lock and unlock pair.

+
+ +
+
+get_locked()¶
+
+
test if the Surface is current locked
+
get_locked() -> bool
+
+

Returns True when the Surface is locked. It doesn't matter how many +times the Surface is locked.

+
+ +
+
+get_locks()¶
+
+
Gets the locks for the Surface
+
get_locks() -> tuple
+
+

Returns the currently existing locks for the Surface.

+
+ +
+
+get_at()¶
+
+
get the color value at a single pixel
+
get_at((x, y)) -> Color
+
+

Return a copy of the RGBA Color value at the given pixel. If the +Surface has no per pixel alpha, then the alpha value will always be 255 +(opaque). If the pixel position is outside the area of the Surface an +IndexError exception will be raised.

+

Getting and setting pixels one at a time is generally too slow to be used +in a game or realtime situation. It is better to use methods which +operate on many pixels at a time like with the blit, fill and draw +methods - or by using pygame.surfarraypygame module for accessing surface pixel data using array interfaces/pygame.PixelArraypygame object for direct pixel access of surfaces.

+

This function will temporarily lock and unlock the Surface as needed.

+
+

New in pygame 1.9: Returning a Color instead of tuple. Use tuple(surf.get_at((x,y))) +if you want a tuple, and not a Color. This should only matter if +you want to use the color as a key in a dict.

+
+
+ +
+
+set_at()¶
+
+
set the color value for a single pixel
+
set_at((x, y), Color) -> None
+
+

Set the RGBA or mapped integer color value for a single pixel. If the +Surface does not have per pixel alphas, the alpha value is ignored. +Setting pixels outside the Surface area or outside the Surface clipping +will have no effect.

+

Getting and setting pixels one at a time is generally too slow to be used +in a game or realtime situation.

+

This function will temporarily lock and unlock the Surface as needed.

+
+ +
+
+get_at_mapped()¶
+
+
get the mapped color value at a single pixel
+
get_at_mapped((x, y)) -> Color
+
+

Return the integer value of the given pixel. If the pixel position is +outside the area of the Surface an IndexError exception will be +raised.

+

This method is intended for pygame unit testing. It unlikely has any use +in an application.

+

This function will temporarily lock and unlock the Surface as needed.

+
+

New in pygame 1.9.2.

+
+
+ +
+
+get_palette()¶
+
+
get the color index palette for an 8-bit Surface
+
get_palette() -> [RGB, RGB, RGB, ...]
+
+

Return a list of up to 256 color elements that represent the indexed +colors used in an 8-bit Surface. The returned list is a copy of the +palette, and changes will have no effect on the Surface.

+

Returning a list of Color(with length 3) instances instead of tuples.

+
+

New in pygame 1.9.

+
+
+ +
+
+get_palette_at()¶
+
+
get the color for a single entry in a palette
+
get_palette_at(index) -> RGB
+
+

Returns the red, green, and blue color values for a single index in a +Surface palette. The index should be a value from 0 to 255.

+
+

New in pygame 1.9: Returning Color(with length 3) instance instead of a tuple.

+
+
+ +
+
+set_palette()¶
+
+
set the color palette for an 8-bit Surface
+
set_palette([RGB, RGB, RGB, ...]) -> None
+
+

Set the full palette for an 8-bit Surface. This will replace the colors in +the existing palette. A partial palette can be passed and only the first +colors in the original palette will be changed.

+

This function has no effect on a Surface with more than 8-bits per pixel.

+
+ +
+
+set_palette_at()¶
+
+
set the color for a single index in an 8-bit Surface palette
+
set_palette_at(index, RGB) -> None
+
+

Set the palette value for a single entry in a Surface palette. The index +should be a value from 0 to 255.

+

This function has no effect on a Surface with more than 8-bits per pixel.

+
+ +
+
+map_rgb()¶
+
+
convert a color into a mapped color value
+
map_rgb(Color) -> mapped_int
+
+

Convert an RGBA color into the mapped integer value for this Surface. +The returned integer will contain no more bits than the bit depth of the +Surface. Mapped color values are not often used inside pygame, but can be +passed to most functions that require a Surface and a color.

+

See the Surface object documentation for more information about colors +and pixel formats.

+
+ +
+
+unmap_rgb()¶
+
+
convert a mapped integer color value into a Color
+
unmap_rgb(mapped_int) -> Color
+
+

Convert an mapped integer color into the RGB color components for +this Surface. Mapped color values are not often used inside pygame, but +can be passed to most functions that require a Surface and a color.

+

See the Surface object documentation for more information about colors +and pixel formats.

+
+ +
+
+set_clip()¶
+
+
set the current clipping area of the Surface
+
set_clip(rect) -> None
+
set_clip(None) -> None
+
+

Each Surface has an active clipping area. This is a rectangle that +represents the only pixels on the Surface that can be modified. If +None is passed for the rectangle the full Surface will be available +for changes.

+

The clipping area is always restricted to the area of the Surface itself. +If the clip rectangle is too large it will be shrunk to fit inside the +Surface.

+
+ +
+
+get_clip()¶
+
+
get the current clipping area of the Surface
+
get_clip() -> Rect
+
+

Return a rectangle of the current clipping area. The Surface will always +return a valid rectangle that will never be outside the bounds of the +image. If the Surface has had None set for the clipping area, the +Surface will return a rectangle with the full area of the Surface.

+
+ +
+
+subsurface()¶
+
+
create a new surface that references its parent
+
subsurface(Rect) -> Surface
+
+

Returns a new Surface that shares its pixels with its new parent. The new +Surface is considered a child of the original. Modifications to either +Surface pixels will effect each other. Surface information like clipping +area and color keys are unique to each Surface.

+

The new Surface will inherit the palette, color key, and alpha settings +from its parent.

+

It is possible to have any number of subsurfaces and subsubsurfaces on +the parent. It is also possible to subsurface the display Surface if the +display mode is not hardware accelerated.

+

See get_offset() and get_parent() to learn more +about the state of a subsurface.

+

A subsurface will have the same class as the parent surface.

+
+ +
+
+get_parent()¶
+
+
find the parent of a subsurface
+
get_parent() -> Surface
+
+

Returns the parent Surface of a subsurface. If this is not a subsurface +then None will be returned.

+
+ +
+
+get_abs_parent()¶
+
+
find the top level parent of a subsurface
+
get_abs_parent() -> Surface
+
+

Returns the parent Surface of a subsurface. If this is not a subsurface +then this surface will be returned.

+
+ +
+
+get_offset()¶
+
+
find the position of a child subsurface inside a parent
+
get_offset() -> (x, y)
+
+

Get the offset position of a child subsurface inside of a parent. If the +Surface is not a subsurface this will return (0, 0).

+
+ +
+
+get_abs_offset()¶
+
+
find the absolute position of a child subsurface inside its top level parent
+
get_abs_offset() -> (x, y)
+
+

Get the offset position of a child subsurface inside of its top level +parent Surface. If the Surface is not a subsurface this will return (0, +0).

+
+ +
+
+get_size()¶
+
+
get the dimensions of the Surface
+
get_size() -> (width, height)
+
+

Return the width and height of the Surface in pixels.

+
+ +
+
+get_width()¶
+
+
get the width of the Surface
+
get_width() -> width
+
+

Return the width of the Surface in pixels.

+
+ +
+
+get_height()¶
+
+
get the height of the Surface
+
get_height() -> height
+
+

Return the height of the Surface in pixels.

+
+ +
+
+get_rect()¶
+
+
get the rectangular area of the Surface
+
get_rect(**kwargs) -> Rect
+
+

Returns a new rectangle covering the entire surface. This rectangle will +always start at (0, 0) with a width and height the same size as the image.

+

You can pass keyword argument values to this function. These named values +will be applied to the attributes of the Rect before it is returned. An +example would be mysurf.get_rect(center=(100, 100)) to create a +rectangle for the Surface centered at a given position.

+
+ +
+
+get_bitsize()¶
+
+
get the bit depth of the Surface pixel format
+
get_bitsize() -> int
+
+

Returns the number of bits used to represent each pixel. This value may +not exactly fill the number of bytes used per pixel. For example a 15 bit +Surface still requires a full 2 bytes.

+
+ +
+
+get_bytesize()¶
+
+
get the bytes used per Surface pixel
+
get_bytesize() -> int
+
+

Return the number of bytes used per pixel.

+
+ +
+
+get_flags()¶
+
+
get the additional flags used for the Surface
+
get_flags() -> int
+
+

Returns a set of current Surface features. Each feature is a bit in the +flags bitmask. Typical flags are RLEACCEL, SRCALPHA, and +SRCCOLORKEY.

+

Here is a more complete list of flags. A full list can be found in +SDL_video.h

+
SWSURFACE      0x00000000    # Surface is in system memory
+HWSURFACE      0x00000001    # (obsolete in pygame 2) Surface is in video memory
+ASYNCBLIT      0x00000004    # (obsolete in pygame 2) Use asynchronous blits if possible
+
+
+

See pygame.display.set_mode()Initialize a window or screen for display for flags exclusive to the +display surface.

+

Used internally (read-only)

+
HWACCEL        0x00000100    # Blit uses hardware acceleration
+SRCCOLORKEY    0x00001000    # Blit uses a source color key
+RLEACCELOK     0x00002000    # Private flag
+RLEACCEL       0x00004000    # Surface is RLE encoded
+SRCALPHA       0x00010000    # Blit uses source alpha blending
+PREALLOC       0x01000000    # Surface uses preallocated memory
+
+
+
+ +
+
+get_pitch()¶
+
+
get the number of bytes used per Surface row
+
get_pitch() -> int
+
+

Return the number of bytes separating each row in the Surface. Surfaces +in video memory are not always linearly packed. Subsurfaces will also +have a larger pitch than their real width.

+

This value is not needed for normal pygame usage.

+
+ +
+
+get_masks()¶
+
+
the bitmasks needed to convert between a color and a mapped integer
+
get_masks() -> (R, G, B, A)
+
+

Returns the bitmasks used to isolate each color in a mapped integer.

+

This value is not needed for normal pygame usage.

+
+ +
+
+set_masks()¶
+
+
set the bitmasks needed to convert between a color and a mapped integer
+
set_masks((r,g,b,a)) -> None
+
+

This is not needed for normal pygame usage.

+
+

Note

+

In SDL2, the masks are read-only and accordingly this method will raise +an AttributeError if called.

+
+
+

New in pygame 1.8.1.

+
+
+ +
+
+get_shifts()¶
+
+
the bit shifts needed to convert between a color and a mapped integer
+
get_shifts() -> (R, G, B, A)
+
+

Returns the pixel shifts need to convert between each color and a mapped +integer.

+

This value is not needed for normal pygame usage.

+
+ +
+
+set_shifts()¶
+
+
sets the bit shifts needed to convert between a color and a mapped integer
+
set_shifts((r,g,b,a)) -> None
+
+

This is not needed for normal pygame usage.

+
+

Note

+

In SDL2, the shifts are read-only and accordingly this method will raise +an AttributeError if called.

+
+
+

New in pygame 1.8.1.

+
+
+ +
+
+get_losses()¶
+
+
the significant bits used to convert between a color and a mapped integer
+
get_losses() -> (R, G, B, A)
+
+

Return the least significant number of bits stripped from each color in a +mapped integer.

+

This value is not needed for normal pygame usage.

+
+ +
+
+get_bounding_rect()¶
+
+
find the smallest rect containing data
+
get_bounding_rect(min_alpha = 1) -> Rect
+
+

Returns the smallest rectangular region that contains all the pixels in +the surface that have an alpha value greater than or equal to the minimum +alpha value.

+

This function will temporarily lock and unlock the Surface as needed.

+
+

New in pygame 1.8.

+
+
+ +
+
+get_view()¶
+
+
return a buffer view of the Surface's pixels.
+
get_view(<kind>='2') -> BufferProxy
+
+

Return an object which exports a surface's internal pixel buffer as +a C level array struct, Python level array interface or a C level +buffer interface. The new buffer protocol is supported.

+

The kind argument is the length 1 string '0', '1', '2', '3', +'r', 'g', 'b', or 'a'. The letters are case insensitive; +'A' will work as well. The argument can be either a Unicode or byte (char) +string. The default is '2'.

+

'0' returns a contiguous unstructured bytes view. No surface shape +information is given. A ValueError is raised if the surface's pixels +are discontinuous.

+

'1' returns a (surface-width * surface-height) array of continuous +pixels. A ValueError is raised if the surface pixels are +discontinuous.

+

'2' returns a (surface-width, surface-height) array of raw pixels. +The pixels are surface-bytesize-d unsigned integers. The pixel format is +surface specific. The 3 byte unsigned integers of 24 bit surfaces are +unlikely accepted by anything other than other pygame functions.

+

'3' returns a (surface-width, surface-height, 3) array of RGB color +components. Each of the red, green, and blue components are unsigned +bytes. Only 24-bit and 32-bit surfaces are supported. The color +components must be in either RGB or BGR order within the pixel.

+

'r' for red, 'g' for green, 'b' for blue, and 'a' for alpha return a +(surface-width, surface-height) view of a single color component within a +surface: a color plane. Color components are unsigned bytes. Both 24-bit +and 32-bit surfaces support 'r', 'g', and 'b'. Only 32-bit surfaces with +SRCALPHA support 'a'.

+

The surface is locked only when an exposed interface is accessed. +For new buffer interface accesses, the surface is unlocked once the +last buffer view is released. For array interface and old buffer +interface accesses, the surface remains locked until the BufferProxy +object is released.

+
+

New in pygame 1.9.2.

+
+
+ +
+
+get_buffer()¶
+
+
acquires a buffer object for the pixels of the Surface.
+
get_buffer() -> BufferProxy
+
+

Return a buffer object for the pixels of the Surface. The buffer can be +used for direct pixel access and manipulation. Surface pixel data is +represented as an unstructured block of memory, with a start address +and length in bytes. The data need not be contiguous. Any gaps are +included in the length, but otherwise ignored.

+

This method implicitly locks the Surface. The lock will be released when +the returned pygame.BufferProxypygame object to export a surface buffer through an array protocol object is garbage collected.

+
+

New in pygame 1.8.

+
+
+ +
+
+_pixels_address¶
+
+
pixel buffer address
+
_pixels_address -> int
+
+

The starting address of the surface's raw pixel bytes.

+
+

New in pygame 1.9.2.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/surfarray.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/surfarray.html new file mode 100644 index 0000000..b4d5c38 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/surfarray.html @@ -0,0 +1,571 @@ + + + + + + + + + pygame.surfarray — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.surfarray
+
+
pygame module for accessing surface pixel data using array interfaces
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—Copy pixels into a 2d array
+—Reference pixels into a 2d array
+—Copy pixels into a 3d array
+—Reference pixels into a 3d array
+—Copy pixel alphas into a 2d array
+—Reference pixel alphas into a 2d array
+—Copy red pixels into a 2d array
+—Reference pixel red into a 2d array.
+—Copy green pixels into a 2d array
+—Reference pixel green into a 2d array.
+—Copy blue pixels into a 2d array
+—Reference pixel blue into a 2d array.
+—Copy the colorkey values into a 2d array
+—Copy an array to a new surface
+—Blit directly from a array values
+—Map a 3d array into a 2d array
+—Sets the array system to be used for surface arrays
+—Gets the currently active array type.
+—Gets the array system types currently supported.
+

Functions to convert between NumPy arrays and Surface objects. This module +will only be functional when pygame can use the external NumPy package. +If NumPy can't be imported, surfarray becomes a MissingModule object.

+

Every pixel is stored as a single integer value to represent the red, green, +and blue colors. The 8-bit images use a value that looks into a colormap. Pixels +with higher depth use a bit packing process to place three or four values into +a single number.

+

The arrays are indexed by the X axis first, followed by the Y axis. +Arrays that treat the pixels as a single integer are referred to as 2D arrays. +This module can also separate the red, green, and blue color values into +separate indices. These types of arrays are referred to as 3D arrays, and the +last index is 0 for red, 1 for green, and 2 for blue.

+

The pixels of a 2D array as returned by array2d() and pixels2d() +are mapped to the specific surface. Use pygame.Surface.unmap_rgb()convert a mapped integer color value into a Color +to convert to a color, and pygame.Surface.map_rgb()convert a color into a mapped color value to get the surface +specific pixel value of a color. Integer pixel values can only be used directly +between surfaces with matching pixel layouts (see pygame.Surfacepygame object for representing images).

+

All functions that refer to "array" will copy the surface information to a new +numpy array. All functions that refer to "pixels" will directly reference the +pixels from the surface and any changes performed to the array will make changes +in the surface. As this last functions share memory with the surface, this one +will be locked during the lifetime of the array.

+
+
+pygame.surfarray.array2d()¶
+
+
Copy pixels into a 2d array
+
array2d(Surface) -> array
+
+

Copy the mapped (raw) pixels from a Surface +into a 2D array. +The bit depth of the surface will control the size of the integer values, +and will work for any type of pixel format.

+

This function will temporarily lock the Surface as pixels are copied +(see the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method).

+
+ +
+
+pygame.surfarray.pixels2d()¶
+
+
Reference pixels into a 2d array
+
pixels2d(Surface) -> array
+
+

Create a new 2D array that directly references the pixel values in a +Surface. Any changes to the array will affect the pixels in the Surface. +This is a fast operation since no data is copied.

+

Pixels from a 24-bit Surface cannot be referenced, but all other Surface bit +depths can.

+

The Surface this references will remain locked for the lifetime of the array, +since the array generated by this function shares memory with the surface. +See the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method.

+
+ +
+
+pygame.surfarray.array3d()¶
+
+
Copy pixels into a 3d array
+
array3d(Surface) -> array
+
+

Copy the pixels from a Surface into a 3D array. The bit depth of the surface +will control the size of the integer values, and will work for any type of +pixel format.

+

This function will temporarily lock the Surface as pixels are copied (see +the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method).

+
+ +
+
+pygame.surfarray.pixels3d()¶
+
+
Reference pixels into a 3d array
+
pixels3d(Surface) -> array
+
+

Create a new 3D array that directly references the pixel values in a +Surface. Any changes to the array will affect the pixels in the Surface. +This is a fast operation since no data is copied.

+

This will only work on Surfaces that have 24-bit or 32-bit formats. Lower +pixel formats cannot be referenced.

+

The Surface this references will remain locked for the lifetime of the array, +since the array generated by this function shares memory with the surface. +See the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method.

+
+ +
+
+pygame.surfarray.array_alpha()¶
+
+
Copy pixel alphas into a 2d array
+
array_alpha(Surface) -> array
+
+

Copy the pixel alpha values (degree of transparency) from a Surface into a +2D array. This will work for any type of Surface format. Surfaces without a +pixel alpha will return an array with all opaque values.

+

This function will temporarily lock the Surface as pixels are copied (see +the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method).

+
+ +
+
+pygame.surfarray.pixels_alpha()¶
+
+
Reference pixel alphas into a 2d array
+
pixels_alpha(Surface) -> array
+
+

Create a new 2D array that directly references the alpha values (degree of +transparency) in a Surface. Any changes to the array will affect the pixels +in the Surface. This is a fast operation since no data is copied.

+

This can only work on 32-bit Surfaces with a per-pixel alpha value.

+

The Surface this references will remain locked for the lifetime of the array, +since the array generated by this function shares memory with the surface. +See the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method.

+
+ +
+
+pygame.surfarray.array_red()¶
+
+
Copy red pixels into a 2d array
+
array_red(Surface) -> array
+
+

Copy the pixel red values from a Surface into a 2D array. This will work +for any type of Surface format.

+

This function will temporarily lock the Surface as pixels are copied (see +the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method).

+
+

New in pygame 2.0.2.

+
+
+ +
+
+pygame.surfarray.pixels_red()¶
+
+
Reference pixel red into a 2d array.
+
pixels_red (Surface) -> array
+
+

Create a new 2D array that directly references the red values in a Surface. +Any changes to the array will affect the pixels in the Surface. This is a +fast operation since no data is copied.

+

This can only work on 24-bit or 32-bit Surfaces.

+

The Surface this references will remain locked for the lifetime of the array, +since the array generated by this function shares memory with the surface. +See the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method.

+
+ +
+
+pygame.surfarray.array_green()¶
+
+
Copy green pixels into a 2d array
+
array_green(Surface) -> array
+
+

Copy the pixel green values from a Surface into a 2D array. This will work +for any type of Surface format.

+

This function will temporarily lock the Surface as pixels are copied (see +the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method).

+
+

New in pygame 2.0.2.

+
+
+ +
+
+pygame.surfarray.pixels_green()¶
+
+
Reference pixel green into a 2d array.
+
pixels_green (Surface) -> array
+
+

Create a new 2D array that directly references the green values in a +Surface. Any changes to the array will affect the pixels in the Surface. +This is a fast operation since no data is copied.

+

This can only work on 24-bit or 32-bit Surfaces.

+

The Surface this references will remain locked for the lifetime of the array, +since the array generated by this function shares memory with the surface. +See the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method.

+
+ +
+
+pygame.surfarray.array_blue()¶
+
+
Copy blue pixels into a 2d array
+
array_blue(Surface) -> array
+
+

Copy the pixel blue values from a Surface into a 2D array. This will work +for any type of Surface format.

+

This function will temporarily lock the Surface as pixels are copied (see +the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method).

+
+

New in pygame 2.0.2.

+
+
+ +
+
+pygame.surfarray.pixels_blue()¶
+
+
Reference pixel blue into a 2d array.
+
pixels_blue (Surface) -> array
+
+

Create a new 2D array that directly references the blue values in a Surface. +Any changes to the array will affect the pixels in the Surface. This is a +fast operation since no data is copied.

+

This can only work on 24-bit or 32-bit Surfaces.

+

The Surface this references will remain locked for the lifetime of the array, +since the array generated by this function shares memory with the surface. +See the pygame.Surface.lock()lock the Surface memory for pixel access - lock the Surface memory for pixel +access method.

+
+ +
+
+pygame.surfarray.array_colorkey()¶
+
+
Copy the colorkey values into a 2d array
+
array_colorkey(Surface) -> array
+
+

Create a new array with the colorkey transparency value from each pixel. If +the pixel matches the colorkey it will be fully transparent; otherwise it +will be fully opaque.

+

This will work on any type of Surface format. If the image has no colorkey a +solid opaque array will be returned.

+

This function will temporarily lock the Surface as pixels are copied.

+
+ +
+
+pygame.surfarray.make_surface()¶
+
+
Copy an array to a new surface
+
make_surface(array) -> Surface
+
+

Create a new Surface that best resembles the data and format on the array. +The array can be 2D or 3D with any sized integer values. Function +make_surface uses the array struct interface to acquire array properties, +so is not limited to just NumPy arrays. See pygame.pixelcopypygame module for general pixel array copying.

+

New in pygame 1.9.2: array struct interface support.

+
+ +
+
+pygame.surfarray.blit_array()¶
+
+
Blit directly from a array values
+
blit_array(Surface, array) -> None
+
+

Directly copy values from an array into a Surface. This is faster than +converting the array into a Surface and blitting. The array must be the same +dimensions as the Surface and will completely replace all pixel values. Only +integer, ASCII character and record arrays are accepted.

+

This function will temporarily lock the Surface as the new values are +copied.

+
+ +
+
+pygame.surfarray.map_array()¶
+
+
Map a 3d array into a 2d array
+
map_array(Surface, array3d) -> array2d
+
+

Convert a 3D array into a 2D array. This will use the given Surface format +to control the conversion. Palette surface formats are supported for NumPy +arrays.

+
+ +
+
+pygame.surfarray.use_arraytype()¶
+
+
Sets the array system to be used for surface arrays
+
use_arraytype (arraytype) -> None
+
+

DEPRECATED: Uses the requested array type for the module functions. +The only supported arraytype is 'numpy'. Other values will raise +ValueError. Using this function will raise a DeprecationWarning.

+
+ +
+
+pygame.surfarray.get_arraytype()¶
+
+
Gets the currently active array type.
+
get_arraytype () -> str
+
+

DEPRECATED: Returns the currently active array type. This will be a value of the +get_arraytypes() tuple and indicates which type of array module is used +for the array creation. Using this function will raise a DeprecationWarning.

+
+

New in pygame 1.8.

+
+
+ +
+
+pygame.surfarray.get_arraytypes()¶
+
+
Gets the array system types currently supported.
+
get_arraytypes () -> tuple
+
+

DEPRECATED: Checks, which array systems are available and returns them as a tuple of +strings. The values of the tuple can be used directly in the +pygame.surfarray.use_arraytype()Sets the array system to be used for surface arrays () method. If no supported array +system could be found, None will be returned. Using this function will raise a +DeprecationWarning.

+
+

New in pygame 1.8.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/tests.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/tests.html new file mode 100644 index 0000000..7131753 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/tests.html @@ -0,0 +1,241 @@ + + + + + + + + + pygame.tests — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.tests
+
+
Pygame unit test suite package
+
+ +++++ + + + + + + +
+—Run the pygame unit test suite
+

A quick way to run the test suite package from the command line is to import +the go submodule with the Python -m option:

+
python -m pygame.tests [<test options>]
+
+
+

Command line option --help displays a usage message. Available options +correspond to the pygame.tests.run()Run the pygame unit test suite arguments.

+

The xxxx_test submodules of the tests package are unit test suites for +individual parts of pygame. Each can also be run as a main program. This is +useful if the test, such as cdrom_test, is interactive.

+

For pygame development the test suite can be run from a pygame distribution +root directory. Program run_tests.py is provided for convenience, though +test/go.py can be run directly.

+

Module level tags control which modules are included in a unit test run. Tags +are assigned to a unit test module with a corresponding <name>_tags.py module. +The tags module has the global __tags__, a list of tag names. For example, +cdrom_test.py has a tag file cdrom_tags.py containing a tags list that +has the 'interactive' string. The 'interactive' tag indicates cdrom_test.py +expects user input. It is excluded from a run_tests.py or +pygame.tests.go run. Two other tags that are excluded are 'ignore' and +'subprocess_ignore'. These two tags indicate unit tests that will not run on a +particular platform, or for which no corresponding pygame module is available. +The test runner will list each excluded module along with the tag responsible.

+
+
+pygame.tests.run()¶
+
+
Run the pygame unit test suite
+
run(*args, **kwds) -> tuple
+
+

Positional arguments (optional):

+
The names of tests to include. If omitted then all tests are run. Test names
+need not include the trailing '_test'.
+
+
+

Keyword arguments:

+
incomplete - fail incomplete tests (default False)
+nosubprocess - run all test suites in the current process
+               (default False, use separate subprocesses)
+dump - dump failures/errors as dict ready to eval (default False)
+file - if provided, the name of a file into which to dump failures/errors
+timings - if provided, the number of times to run each individual test to
+          get an average run time (default is run each test once)
+exclude - A list of TAG names to exclude from the run
+show_output - show silenced stderr/stdout on errors (default False)
+all - dump all results, not just errors (default False)
+randomize - randomize order of tests (default False)
+seed - if provided, a seed randomizer integer
+multi_thread - if provided, the number of THREADS in which to run
+               subprocessed tests
+time_out - if subprocess is True then the time limit in seconds before
+           killing a test (default 30)
+fake - if provided, the name of the fake tests package in the
+       run_tests__tests subpackage to run instead of the normal
+       pygame tests
+python - the path to a python executable to run subprocessed tests
+         (default sys.executable)
+
+
+

Return value:

+
A tuple of total number of tests run, dictionary of error information.
+The dictionary is empty if no errors were recorded.
+
+
+

By default individual test modules are run in separate subprocesses. This +recreates normal pygame usage where pygame.init() and pygame.quit() +are called only once per program execution, and avoids unfortunate +interactions between test modules. Also, a time limit is placed on test +execution, so frozen tests are killed when there time allotment expired. Use +the single process option if threading is not working properly or if tests +are taking too long. It is not guaranteed that all tests will pass in single +process mode.

+

Tests are run in a randomized order if the randomize argument is True or a +seed argument is provided. If no seed integer is provided then the system +time is used.

+

Individual test modules may have a __tags__ attribute, a list of tag strings +used to selectively omit modules from a run. By default only 'interactive' +modules such as cdrom_test are ignored. An interactive module must be run +from the console as a Python program.

+

This function can only be called once per Python session. It is not +reentrant.

+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/time.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/time.html new file mode 100644 index 0000000..9a17b28 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/time.html @@ -0,0 +1,370 @@ + + + + + + + + + pygame.time — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.time
+
+
pygame module for monitoring time
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + +
+—get the time in milliseconds
+—pause the program for an amount of time
+—pause the program for an amount of time
+—repeatedly create an event on the event queue
+—create an object to help track time
+

Times in pygame are represented in milliseconds (1/1000 seconds). Most +platforms have a limited time resolution of around 10 milliseconds. This +resolution, in milliseconds, is given in the TIMER_RESOLUTION constant.

+
+
+pygame.time.get_ticks()¶
+
+
get the time in milliseconds
+
get_ticks() -> milliseconds
+
+

Return the number of milliseconds since pygame.init() was called. Before +pygame is initialized this will always be 0.

+
+ +
+
+pygame.time.wait()¶
+
+
pause the program for an amount of time
+
wait(milliseconds) -> time
+
+

Will pause for a given number of milliseconds. This function sleeps the +process to share the processor with other programs. A program that waits for +even a few milliseconds will consume very little processor time. It is +slightly less accurate than the pygame.time.delay() function.

+

This returns the actual number of milliseconds used.

+
+ +
+
+pygame.time.delay()¶
+
+
pause the program for an amount of time
+
delay(milliseconds) -> time
+
+

Will pause for a given number of milliseconds. This function will use the +processor (rather than sleeping) in order to make the delay more accurate +than pygame.time.wait().

+

This returns the actual number of milliseconds used.

+
+ +
+
+pygame.time.set_timer()¶
+
+
repeatedly create an event on the event queue
+
set_timer(event, millis) -> None
+
set_timer(event, millis, loops=0) -> None
+
+

Set an event to appear on the event queue every given number of milliseconds. +The first event will not appear until the amount of time has passed.

+

The event attribute can be a pygame.event.Event object or an integer +type that denotes an event.

+

loops is an integer that denotes the number of events posted. If 0 (default) +then the events will keep getting posted, unless explicitly stopped.

+

To disable the timer for such an event, call the function again with the same +event argument with millis argument set to 0.

+

It is also worth mentioning that a particular event type can only be put on a +timer once. In other words, there cannot be two timers for the same event type. +Setting an event timer for a particular event discards the old one for that +event type.

+

loops replaces the once argument, and this does not break backward +compatability

+
+

New in pygame 2.0.0.dev3: once argument added.

+
+
+

Changed in pygame 2.0.1: event argument supports pygame.event.Event object

+
+
+

New in pygame 2.0.1: added loops argument to replace once argument

+
+
+ +
+
+pygame.time.Clock¶
+
+
create an object to help track time
+
Clock() -> Clock
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + +
+—update the clock
+—update the clock
+—time used in the previous tick
+—actual time used in the previous tick
+—compute the clock framerate
+

Creates a new Clock object that can be used to track an amount of time. The +clock also provides several functions to help control a game's framerate.

+
+
+tick()¶
+
+
update the clock
+
tick(framerate=0) -> milliseconds
+
+

This method should be called once per frame. It will compute how many +milliseconds have passed since the previous call.

+

If you pass the optional framerate argument the function will delay to +keep the game running slower than the given ticks per second. This can be +used to help limit the runtime speed of a game. By calling +Clock.tick(40) once per frame, the program will never run at more +than 40 frames per second.

+

Note that this function uses SDL_Delay function which is not accurate on +every platform, but does not use much CPU. Use tick_busy_loop if you want +an accurate timer, and don't mind chewing CPU.

+
+ +
+
+tick_busy_loop()¶
+
+
update the clock
+
tick_busy_loop(framerate=0) -> milliseconds
+
+

This method should be called once per frame. It will compute how many +milliseconds have passed since the previous call.

+

If you pass the optional framerate argument the function will delay to +keep the game running slower than the given ticks per second. This can be +used to help limit the runtime speed of a game. By calling +Clock.tick_busy_loop(40) once per frame, the program will never run at +more than 40 frames per second.

+

Note that this function uses pygame.time.delay()pause the program for an amount of time, which uses lots +of CPU in a busy loop to make sure that timing is more accurate.

+
+

New in pygame 1.8.

+
+
+ +
+
+get_time()¶
+
+
time used in the previous tick
+
get_time() -> milliseconds
+
+

The number of milliseconds that passed between the previous two calls to +Clock.tick().

+
+ +
+
+get_rawtime()¶
+
+
actual time used in the previous tick
+
get_rawtime() -> milliseconds
+
+

Similar to Clock.get_time(), but does not include any time used +while Clock.tick() was delaying to limit the framerate.

+
+ +
+
+get_fps()¶
+
+
compute the clock framerate
+
get_fps() -> float
+
+

Compute your game's framerate (in frames per second). It is computed by +averaging the last ten calls to Clock.tick().

+
+ +
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/touch.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/touch.html new file mode 100644 index 0000000..554ffea --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/touch.html @@ -0,0 +1,240 @@ + + + + + + + + + pygame._sdl2.touch — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame._sdl2.touch
+
+
pygame module to work with touch input
+
+ +++++ + + + + + + + + + + + + + + + + + + +
+—get the number of touch devices
+—get the a touch device id for a given index
+—the number of active fingers for a given touch device
+—get information about an active finger
+
+

New in pygame 2: This module requires SDL2.

+
+
+
+pygame._sdl2.touch.get_num_devices()¶
+
+
get the number of touch devices
+
get_num_devices() -> int
+
+

Return the number of available touch devices.

+
+ +
+
+pygame._sdl2.touch.get_device()¶
+
+
get the a touch device id for a given index
+
get_device(index) -> touchid
+
+
+
Parameters
+

index (int) -- This number is at least 0 and less than the +number of devices.

+
+
+

Return an integer id associated with the given index.

+
+ +
+
+pygame._sdl2.touch.get_num_fingers()¶
+
+
the number of active fingers for a given touch device
+
get_num_fingers(touchid) -> int
+
+

Return the number of fingers active for the touch device +whose id is touchid.

+
+ +
+
+pygame._sdl2.touch.get_finger()¶
+
+
get information about an active finger
+
get_finger(touchid, index) -> int
+
+
+
Parameters
+
    +
  • touchid (int) -- The touch device id.

  • +
  • index (int) -- The index of the finger to return +information about, between 0 and the +number of active fingers.

  • +
+
+
+

Return a dict for the finger index active on touchid. +The dict contains these keys:

+
id         the id of the finger (an integer).
+x          the normalized x position of the finger, between 0 and 1.
+y          the normalized y position of the finger, between 0 and 1.
+pressure   the amount of pressure applied by the finger, between 0 and 1.
+
+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/transform.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/transform.html new file mode 100644 index 0000000..22500a2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/ref/transform.html @@ -0,0 +1,535 @@ + + + + + + + + + pygame.transform — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+
+pygame.transform
+
+
pygame module to transform surfaces
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+—flip vertically and horizontally
+—resize to new resolution
+—rotate an image
+—filtered scale and rotation
+—specialized image doubler
+—scale a surface to an arbitrary size smoothly
+—return smoothscale filter version in use: 'GENERIC', 'MMX', or 'SSE'
+—set smoothscale filter version to one of: 'GENERIC', 'MMX', or 'SSE'
+—gets a copy of an image with an interior area removed
+—find edges in a surface
+—find the average surface from many surfaces.
+—finds the average color of a surface
+—finds which, and how many pixels in a surface are within a threshold of a 'search_color' or a 'search_surf'.
+

A Surface transform is an operation that moves or resizes the pixels. All these +functions take a Surface to operate on and return a new Surface with the +results.

+

Some of the transforms are considered destructive. These means every time they +are performed they lose pixel data. Common examples of this are resizing and +rotating. For this reason, it is better to re-transform the original surface +than to keep transforming an image multiple times. (For example, suppose you +are animating a bouncing spring which expands and contracts. If you applied the +size changes incrementally to the previous images, you would lose detail. +Instead, always begin with the original image and scale to the desired size.)

+
+

Changed in pygame 2.0.2: transform functions now support keyword arguments.

+
+
+
+pygame.transform.flip()¶
+
+
flip vertically and horizontally
+
flip(surface, flip_x, flip_y) -> Surface
+
+

This can flip a Surface either vertically, horizontally, or both. +The arguments flip_x and flip_y are booleans that control whether +to flip each axis. Flipping a Surface is non-destructive and returns a new +Surface with the same dimensions.

+
+ +
+
+pygame.transform.scale()¶
+
+
resize to new resolution
+
scale(surface, size, dest_surface=None) -> Surface
+
+

Resizes the Surface to a new size, given as (width, height). +This is a fast scale operation that does not sample the results.

+

An optional destination surface can be used, rather than have it create a +new one. This is quicker if you want to repeatedly scale something. However +the destination must be the same size as the size (width, height) passed in. Also +the destination surface must be the same format.

+
+ +
+
+pygame.transform.rotate()¶
+
+
rotate an image
+
rotate(surface, angle) -> Surface
+
+

Unfiltered counterclockwise rotation. The angle argument represents degrees +and can be any floating point value. Negative angle amounts will rotate +clockwise.

+

Unless rotating by 90 degree increments, the image will be padded larger to +hold the new size. If the image has pixel alphas, the padded area will be +transparent. Otherwise pygame will pick a color that matches the Surface +colorkey or the topleft pixel value.

+
+ +
+
+pygame.transform.rotozoom()¶
+
+
filtered scale and rotation
+
rotozoom(surface, angle, scale) -> Surface
+
+

This is a combined scale and rotation transform. The resulting Surface will +be a filtered 32-bit Surface. The scale argument is a floating point value +that will be multiplied by the current resolution. The angle argument is a +floating point value that represents the counterclockwise degrees to rotate. +A negative rotation angle will rotate clockwise.

+
+ +
+
+pygame.transform.scale2x()¶
+
+
specialized image doubler
+
scale2x(surface, dest_surface=None) -> Surface
+
+

This will return a new image that is double the size of the original. It +uses the AdvanceMAME Scale2X algorithm which does a 'jaggie-less' scale of +bitmap graphics.

+

This really only has an effect on simple images with solid colors. On +photographic and antialiased images it will look like a regular unfiltered +scale.

+

An optional destination surface can be used, rather than have it create a +new one. This is quicker if you want to repeatedly scale something. However +the destination must be twice the size of the source surface passed in. Also +the destination surface must be the same format.

+
+ +
+
+pygame.transform.smoothscale()¶
+
+
scale a surface to an arbitrary size smoothly
+
smoothscale(surface, size, dest_surface=None) -> Surface
+
+

Uses one of two different algorithms for scaling each dimension of the input +surface as required. For shrinkage, the output pixels are area averages of +the colors they cover. For expansion, a bilinear filter is used. For the +x86-64 and i686 architectures, optimized MMX routines are included and +will run much faster than other machine types. The size is a 2 number +sequence for (width, height). This function only works for 24-bit or 32-bit +surfaces. An exception will be thrown if the input surface bit depth is less +than 24.

+
+

New in pygame 1.8.

+
+
+ +
+
+pygame.transform.get_smoothscale_backend()¶
+
+
return smoothscale filter version in use: 'GENERIC', 'MMX', or 'SSE'
+
get_smoothscale_backend() -> string
+
+

Shows whether or not smoothscale is using MMX or SSE acceleration. +If no acceleration is available then "GENERIC" is returned. For a x86 +processor the level of acceleration to use is determined at runtime.

+

This function is provided for pygame testing and debugging.

+
+ +
+
+pygame.transform.set_smoothscale_backend()¶
+
+
set smoothscale filter version to one of: 'GENERIC', 'MMX', or 'SSE'
+
set_smoothscale_backend(backend) -> None
+
+

Sets smoothscale acceleration. Takes a string argument. A value of 'GENERIC' +turns off acceleration. 'MMX' uses MMX instructions only. 'SSE' allows +SSE extensions as well. A value error is raised if type is not +recognized or not supported by the current processor.

+

This function is provided for pygame testing and debugging. If smoothscale +causes an invalid instruction error then it is a pygame/SDL bug that should +be reported. Use this function as a temporary fix only.

+
+ +
+
+pygame.transform.chop()¶
+
+
gets a copy of an image with an interior area removed
+
chop(surface, rect) -> Surface
+
+

Extracts a portion of an image. All vertical and horizontal pixels +surrounding the given rectangle area are removed. The corner areas (diagonal +to the rect) are then brought together. (The original image is not altered +by this operation.)

+

NOTE: If you want a "crop" that returns the part of an image within a +rect, you can blit with a rect to a new surface or copy a subsurface.

+
+ +
+
+pygame.transform.laplacian()¶
+
+
find edges in a surface
+
laplacian(surface, dest_surface=None) -> Surface
+
+

Finds the edges in a surface using the laplacian algorithm.

+
+

New in pygame 1.8.

+
+
+ +
+
+pygame.transform.average_surfaces()¶
+
+
find the average surface from many surfaces.
+
average_surfaces(surfaces, dest_surface=None, palette_colors=1) -> Surface
+
+

Takes a sequence of surfaces and returns a surface with average colors from +each of the surfaces.

+

palette_colors - if true we average the colors in palette, otherwise we +average the pixel values. This is useful if the surface is actually +greyscale colors, and not palette colors.

+

Note, this function currently does not handle palette using surfaces +correctly.

+
+

New in pygame 1.8.

+
+
+

New in pygame 1.9: palette_colors argument

+
+
+ +
+
+pygame.transform.average_color()¶
+
+
finds the average color of a surface
+
average_color(surface, rect=None) -> Color
+
+

Finds the average color of a Surface or a region of a surface specified by a +Rect, and returns it as a Color.

+
+ +
+
+pygame.transform.threshold()¶
+
+
finds which, and how many pixels in a surface are within a threshold of a 'search_color' or a 'search_surf'.
+
threshold(dest_surface, surface, search_color, threshold=(0,0,0,0), set_color=(0,0,0,0), set_behavior=1, search_surf=None, inverse_set=False) -> num_threshold_pixels
+
+

This versatile function can be used for find colors in a 'surf' close to a 'search_color' +or close to colors in a separate 'search_surf'.

+

It can also be used to transfer pixels into a 'dest_surf' that match or don't match.

+

By default it sets pixels in the 'dest_surf' where all of the pixels NOT within the +threshold are changed to set_color. If inverse_set is optionally set to True, +the pixels that ARE within the threshold are changed to set_color.

+

If the optional 'search_surf' surface is given, it is used to threshold against +rather than the specified 'set_color'. That is, it will find each pixel in the +'surf' that is within the 'threshold' of the pixel at the same coordinates +of the 'search_surf'.

+
+
Parameters
+
+
+
Return type
+

int

+
+
Returns
+

The number of pixels that are within the 'threshold' in 'surf' +compared to either 'search_color' or search_surf.

+
+
Examples
+

+
+

See the threshold tests for a full of examples: https://github.com/pygame/pygame/blob/master/test/transform_test.py

+
    def test_threshold_dest_surf_not_change(self):
+        """the pixels within the threshold.
+
+        All pixels not within threshold are changed to set_color.
+        So there should be none changed in this test.
+        """
+        (w, h) = size = (32, 32)
+        threshold = (20, 20, 20, 20)
+        original_color = (25, 25, 25, 25)
+        original_dest_color = (65, 65, 65, 55)
+        threshold_color = (10, 10, 10, 10)
+        set_color = (255, 10, 10, 10)
+
+        surf = pygame.Surface(size, pygame.SRCALPHA, 32)
+        dest_surf = pygame.Surface(size, pygame.SRCALPHA, 32)
+        search_surf = pygame.Surface(size, pygame.SRCALPHA, 32)
+
+        surf.fill(original_color)
+        search_surf.fill(threshold_color)
+        dest_surf.fill(original_dest_color)
+
+        # set_behavior=1, set dest_surface from set_color.
+        # all within threshold of third_surface, so no color is set.
+
+        THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR = 1
+        pixels_within_threshold = pygame.transform.threshold(
+            dest_surface=dest_surf,
+            surface=surf,
+            search_color=None,
+            threshold=threshold,
+            set_color=set_color,
+            set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR,
+            search_surf=search_surf,
+        )
+
+        # # Return, of pixels within threshold is correct
+        self.assertEqual(w * h, pixels_within_threshold)
+
+        # # Size of dest surface is correct
+        dest_rect = dest_surf.get_rect()
+        dest_size = dest_rect.size
+        self.assertEqual(size, dest_size)
+
+        # The color is not the change_color specified for every pixel As all
+        # pixels are within threshold
+
+        for pt in test_utils.rect_area_pts(dest_rect):
+            self.assertNotEqual(dest_surf.get_at(pt), set_color)
+            self.assertEqual(dest_surf.get_at(pt), original_dest_color)
+
+
+
+

New in pygame 1.8.

+
+
+

Changed in pygame 1.9.4: Fixed a lot of bugs and added keyword arguments. Test your code.

+
+
+ +
+ +
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/search.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/search.html new file mode 100644 index 0000000..1f1e1b3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/search.html @@ -0,0 +1,96 @@ + + + + + + + + Search — pygame v2.1.2 documentation + + + + + + + + + + + + + + + + + + +
+
+
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + + +
+ + + +
+ +
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/searchindex.js b/.venv/lib/python3.8/site-packages/pygame/docs/generated/searchindex.js new file mode 100644 index 0000000..8c8b866 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["c_api","c_api/base","c_api/bufferproxy","c_api/cdrom","c_api/color","c_api/display","c_api/event","c_api/freetype","c_api/mixer","c_api/rect","c_api/rwobject","c_api/slots","c_api/surface","c_api/surflock","c_api/version","filepaths","index","ref/bufferproxy","ref/camera","ref/cdrom","ref/color","ref/color_list","ref/cursors","ref/display","ref/draw","ref/event","ref/examples","ref/fastevent","ref/font","ref/freetype","ref/gfxdraw","ref/image","ref/joystick","ref/key","ref/locals","ref/mask","ref/math","ref/midi","ref/mixer","ref/mouse","ref/music","ref/overlay","ref/pixelarray","ref/pixelcopy","ref/pygame","ref/rect","ref/scrap","ref/sdl2_controller","ref/sdl2_video","ref/sndarray","ref/sprite","ref/surface","ref/surfarray","ref/tests","ref/time","ref/touch","ref/transform","tut/CameraIntro","tut/ChimpLineByLine","tut/DisplayModes","tut/ImportInit","tut/MakeGames","tut/MoveIt","tut/PygameIntro","tut/SpriteIntro","tut/SurfarrayIntro","tut/chimp.py","tut/en/Red_or_Black/1.Prolog/introduction","tut/en/Red_or_Black/2.Print_text/Basic TEMPLATE and OUTPUT","tut/en/Red_or_Black/3.Move_text/Basic PROCESS","tut/en/Red_or_Black/4.Control_text/Basic INPUT","tut/en/Red_or_Black/5.HP_bar/Advanced OUTPUT with Advanced PROCESS","tut/en/Red_or_Black/6.Buttons/Advanced INPUT with Advanced OUTPUT","tut/en/Red_or_Black/7.Game_board/Advanced OUTPUT and plus alpha","tut/en/Red_or_Black/8.Epilog/Epilog","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/1.\ud504\ub864\ub85c\uadf8/\uc18c\uac1c","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/2.\ud14d\uc2a4\ud2b8 \ucd9c\ub825/\uae30\ucd08 \ud15c\ud50c\ub9bf\uacfc \ucd9c\ub825","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/3.\ud14d\uc2a4\ud2b8 \uc774\ub3d9/\uae30\ucd08 \ucc98\ub9ac","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/4.\ud14d\uc2a4\ud2b8 \uc870\uc885/\uae30\ucd08 \uc785\ub825","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/5.HP\ubc14/\uc2ec\ud654 \ucd9c\ub825 \uadf8\ub9ac\uace0 \uc2ec\ud654 \ucc98\ub9ac","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/6.\ubc84\ud2bc\ub4e4/\uc2ec\ud654 \uc785\ub825 \uadf8\ub9ac\uace0 \uc2ec\ud654 \ucd9c\ub825","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/7.\uac8c\uc784\ud310/\uc2ec\ud654 \ucd9c\ub825 \uadf8\ub9ac\uace0 \uc870\uae08 \ub354","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/8.\uc5d0\ud544\ub85c\uadf8/\uc5d0\ud544\ub85c\uadf8","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/overview","tut/newbieguide","tut/tom_games2","tut/tom_games3","tut/tom_games4","tut/tom_games5","tut/tom_games6"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["c_api.rst","c_api/base.rst","c_api/bufferproxy.rst","c_api/cdrom.rst","c_api/color.rst","c_api/display.rst","c_api/event.rst","c_api/freetype.rst","c_api/mixer.rst","c_api/rect.rst","c_api/rwobject.rst","c_api/slots.rst","c_api/surface.rst","c_api/surflock.rst","c_api/version.rst","filepaths.rst","index.rst","ref/bufferproxy.rst","ref/camera.rst","ref/cdrom.rst","ref/color.rst","ref/color_list.rst","ref/cursors.rst","ref/display.rst","ref/draw.rst","ref/event.rst","ref/examples.rst","ref/fastevent.rst","ref/font.rst","ref/freetype.rst","ref/gfxdraw.rst","ref/image.rst","ref/joystick.rst","ref/key.rst","ref/locals.rst","ref/mask.rst","ref/math.rst","ref/midi.rst","ref/mixer.rst","ref/mouse.rst","ref/music.rst","ref/overlay.rst","ref/pixelarray.rst","ref/pixelcopy.rst","ref/pygame.rst","ref/rect.rst","ref/scrap.rst","ref/sdl2_controller.rst","ref/sdl2_video.rst","ref/sndarray.rst","ref/sprite.rst","ref/surface.rst","ref/surfarray.rst","ref/tests.rst","ref/time.rst","ref/touch.rst","ref/transform.rst","tut/CameraIntro.rst","tut/ChimpLineByLine.rst","tut/DisplayModes.rst","tut/ImportInit.rst","tut/MakeGames.rst","tut/MoveIt.rst","tut/PygameIntro.rst","tut/SpriteIntro.rst","tut/SurfarrayIntro.rst","tut/chimp.py.rst","tut/en/Red_or_Black/1.Prolog/introduction.rst","tut/en/Red_or_Black/2.Print_text/Basic TEMPLATE and OUTPUT.rst","tut/en/Red_or_Black/3.Move_text/Basic PROCESS.rst","tut/en/Red_or_Black/4.Control_text/Basic INPUT.rst","tut/en/Red_or_Black/5.HP_bar/Advanced OUTPUT with Advanced PROCESS.rst","tut/en/Red_or_Black/6.Buttons/Advanced INPUT with Advanced OUTPUT.rst","tut/en/Red_or_Black/7.Game_board/Advanced OUTPUT and plus alpha.rst","tut/en/Red_or_Black/8.Epilog/Epilog.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/1.\ud504\ub864\ub85c\uadf8/\uc18c\uac1c.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/2.\ud14d\uc2a4\ud2b8 \ucd9c\ub825/\uae30\ucd08 \ud15c\ud50c\ub9bf\uacfc \ucd9c\ub825.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/3.\ud14d\uc2a4\ud2b8 \uc774\ub3d9/\uae30\ucd08 \ucc98\ub9ac.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/4.\ud14d\uc2a4\ud2b8 \uc870\uc885/\uae30\ucd08 \uc785\ub825.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/5.HP\ubc14/\uc2ec\ud654 \ucd9c\ub825 \uadf8\ub9ac\uace0 \uc2ec\ud654 \ucc98\ub9ac.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/6.\ubc84\ud2bc\ub4e4/\uc2ec\ud654 \uc785\ub825 \uadf8\ub9ac\uace0 \uc2ec\ud654 \ucd9c\ub825.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/7.\uac8c\uc784\ud310/\uc2ec\ud654 \ucd9c\ub825 \uadf8\ub9ac\uace0 \uc870\uae08 \ub354.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/8.\uc5d0\ud544\ub85c\uadf8/\uc5d0\ud544\ub85c\uadf8.rst","tut/ko/\ube68\uac04\ube14\ub85d \uac80\uc740\ube14\ub85d/overview.rst","tut/newbieguide.rst","tut/tom_games2.rst","tut/tom_games3.rst","tut/tom_games4.rst","tut/tom_games5.rst","tut/tom_games6.rst"],objects:{"":[[14,0,1,"c.PG_MAJOR_VERSION","PG_MAJOR_VERSION"],[14,0,1,"c.PG_MINOR_VERSION","PG_MINOR_VERSION"],[14,0,1,"c.PG_PATCH_VERSION","PG_PATCH_VERSION"],[14,0,1,"c.PG_VERSIONNUM","PG_VERSIONNUM"],[14,0,1,"c.PG_VERSION_ATLEAST","PG_VERSION_ATLEAST"],[1,1,1,"c.import_pygame_base","import_pygame_base"],[1,1,1,"c.pgBuffer_AsArrayInterface","pgBuffer_AsArrayInterface"],[1,1,1,"c.pgBuffer_AsArrayStruct","pgBuffer_AsArrayStruct"],[1,1,1,"c.pgBuffer_Release","pgBuffer_Release"],[2,1,1,"c.pgBufproxy_Check","pgBufproxy_Check"],[2,1,1,"c.pgBufproxy_GetParent","pgBufproxy_GetParent"],[2,1,1,"c.pgBufproxy_New","pgBufproxy_New"],[2,1,1,"c.pgBufproxy_Trip","pgBufproxy_Trip"],[2,3,1,"c.pgBufproxy_Type","pgBufproxy_Type"],[3,4,1,"c.pgCDObject","pgCDObject"],[3,1,1,"c.pgCD_AsID","pgCD_AsID"],[3,1,1,"c.pgCD_Check","pgCD_Check"],[3,1,1,"c.pgCD_New","pgCD_New"],[3,3,1,"c.pgCD_Type","pgCD_Type"],[8,4,1,"c.pgChannelObject","pgChannelObject"],[8,1,1,"c.pgChannel_AsInt","pgChannel_AsInt"],[8,1,1,"c.pgChannel_Check","pgChannel_Check"],[8,1,1,"c.pgChannel_New","pgChannel_New"],[8,3,1,"c.pgChannel_Type","pgChannel_Type"],[4,1,1,"c.pgColor_Check","pgColor_Check"],[4,1,1,"c.pgColor_New","pgColor_New"],[4,1,1,"c.pgColor_NewLength","pgColor_NewLength"],[4,3,1,"c.pgColor_Type","pgColor_Type"],[1,1,1,"c.pgDict_AsBuffer","pgDict_AsBuffer"],[6,4,1,"c.pgEventObject","pgEventObject"],[6,1,1,"c.pgEvent_Check","pgEvent_Check"],[6,1,1,"c.pgEvent_FillUserEvent","pgEvent_FillUserEvent"],[6,1,1,"c.pgEvent_New","pgEvent_New"],[6,1,1,"c.pgEvent_New2","pgEvent_New2"],[6,4,1,"c.pgEvent_Type","pgEvent_Type"],[1,3,1,"c.pgExc_BufferError","pgExc_BufferError"],[1,3,1,"c.pgExc_SDLError","pgExc_SDLError"],[7,4,1,"c.pgFontObject","pgFontObject"],[7,1,1,"c.pgFont_Check","pgFont_Check"],[7,1,1,"c.pgFont_IS_ALIVE","pgFont_IS_ALIVE"],[7,1,1,"c.pgFont_New","pgFont_New"],[7,4,1,"c.pgFont_Type","pgFont_Type"],[13,4,1,"c.pgLifetimeLockObject","pgLifetimeLockObject"],[13,1,1,"c.pgLifetimeLock_Check","pgLifetimeLock_Check"],[13,3,1,"c.pgLifetimeLock_Type","pgLifetimeLock_Type"],[1,1,1,"c.pgObject_GetBuffer","pgObject_GetBuffer"],[10,1,1,"c.pgRWops_FromFileObject","pgRWops_FromFileObject"],[10,1,1,"c.pgRWops_FromObject","pgRWops_FromObject"],[10,1,1,"c.pgRWops_GetFileExtension","pgRWops_GetFileExtension"],[10,1,1,"c.pgRWops_IsFileObject","pgRWops_IsFileObject"],[10,1,1,"c.pgRWops_ReleaseObject","pgRWops_ReleaseObject"],[9,4,1,"c.pgRectObject","pgRectObject"],[9,1,1,"c.pgRect_AsRect","pgRect_AsRect"],[9,1,1,"c.pgRect_FromObject","pgRect_FromObject"],[9,1,1,"c.pgRect_New","pgRect_New"],[9,1,1,"c.pgRect_New4","pgRect_New4"],[9,1,1,"c.pgRect_Normalize","pgRect_Normalize"],[9,3,1,"c.pgRect_Type","pgRect_Type"],[8,4,1,"c.pgSoundObject","pgSoundObject"],[8,1,1,"c.pgSound_AsChunk","pgSound_AsChunk"],[8,1,1,"c.pgSound_Check","pgSound_Check"],[8,1,1,"c.pgSound_New","pgSound_New"],[8,3,1,"c.pgSound_Type","pgSound_Type"],[12,4,1,"c.pgSurfaceObject","pgSurfaceObject"],[12,1,1,"c.pgSurface_AsSurface","pgSurface_AsSurface"],[12,1,1,"c.pgSurface_Blit","pgSurface_Blit"],[12,1,1,"c.pgSurface_Check","pgSurface_Check"],[13,1,1,"c.pgSurface_Lock","pgSurface_Lock"],[13,1,1,"c.pgSurface_LockBy","pgSurface_LockBy"],[13,1,1,"c.pgSurface_LockLifetime","pgSurface_LockLifetime"],[12,1,1,"c.pgSurface_New","pgSurface_New"],[13,1,1,"c.pgSurface_Prep","pgSurface_Prep"],[12,3,1,"c.pgSurface_Type","pgSurface_Type"],[13,1,1,"c.pgSurface_UnLock","pgSurface_UnLock"],[13,1,1,"c.pgSurface_UnLockBy","pgSurface_UnLockBy"],[13,1,1,"c.pgSurface_Unprep","pgSurface_Unprep"],[5,4,1,"c.pgVidInfoObject","pgVidInfoObject"],[5,1,1,"c.pgVidInfo_AsVidInfo","pgVidInfo_AsVidInfo"],[5,1,1,"c.pgVidInfo_Check","pgVidInfo_Check"],[5,1,1,"c.pgVidInfo_New","pgVidInfo_New"],[5,3,1,"c.pgVidInfo_Type","pgVidInfo_Type"],[10,1,1,"c.pg_EncodeFilePath","pg_EncodeFilePath"],[10,1,1,"c.pg_EncodeString","pg_EncodeString"],[1,1,1,"c.pg_FloatFromObj","pg_FloatFromObj"],[1,1,1,"c.pg_FloatFromObjIndex","pg_FloatFromObjIndex"],[1,1,1,"c.pg_GetDefaultWindow","pg_GetDefaultWindow"],[1,1,1,"c.pg_GetDefaultWindowSurface","pg_GetDefaultWindowSurface"],[1,1,1,"c.pg_IntFromObj","pg_IntFromObj"],[1,1,1,"c.pg_IntFromObjIndex","pg_IntFromObjIndex"],[1,1,1,"c.pg_RGBAFromObj","pg_RGBAFromObj"],[1,1,1,"c.pg_RegisterQuit","pg_RegisterQuit"],[1,1,1,"c.pg_SetDefaultWindow","pg_SetDefaultWindow"],[1,1,1,"c.pg_SetDefaultWindowSurface","pg_SetDefaultWindowSurface"],[1,1,1,"c.pg_TwoFloatsFromObj","pg_TwoFloatsFromObj"],[1,1,1,"c.pg_TwoIntsFromObj","pg_TwoIntsFromObj"],[1,1,1,"c.pg_UintFromObj","pg_UintFromObj"],[1,1,1,"c.pg_UintFromObjIndex","pg_UintFromObjIndex"],[1,4,1,"c.pg_buffer","pg_buffer"],[1,1,1,"c.pg_mod_autoinit","pg_mod_autoinit"],[1,1,1,"c.pg_mod_autoquit","pg_mod_autoquit"],[44,5,0,"-","pygame"]],"pygame.BufferProxy":[[17,7,1,"","length"],[17,7,1,"","parent"],[17,7,1,"","raw"],[17,8,1,"","write"]],"pygame.Color":[[20,7,1,"","a"],[20,7,1,"","b"],[20,7,1,"","cmy"],[20,8,1,"","correct_gamma"],[20,7,1,"","g"],[20,7,1,"","hsla"],[20,7,1,"","hsva"],[20,7,1,"","i1i2i3"],[20,8,1,"","lerp"],[20,8,1,"","normalize"],[20,8,1,"","premul_alpha"],[20,7,1,"","r"],[20,8,1,"","set_length"],[20,8,1,"","update"]],"pygame.Overlay":[[41,8,1,"","display"],[41,8,1,"","get_hardware"],[41,8,1,"","set_location"]],"pygame.PixelArray":[[42,8,1,"","close"],[42,8,1,"","compare"],[42,8,1,"","extract"],[42,7,1,"","itemsize"],[42,8,1,"","make_surface"],[42,7,1,"","ndim"],[42,8,1,"","replace"],[42,7,1,"","shape"],[42,7,1,"","strides"],[42,7,1,"","surface"],[42,8,1,"","transpose"]],"pygame.Rect":[[45,8,1,"","clamp"],[45,8,1,"","clamp_ip"],[45,8,1,"","clip"],[45,8,1,"","clipline"],[45,8,1,"","collidedict"],[45,8,1,"","collidedictall"],[45,8,1,"","collidelist"],[45,8,1,"","collidelistall"],[45,8,1,"","collidepoint"],[45,8,1,"","colliderect"],[45,8,1,"","contains"],[45,8,1,"","copy"],[45,8,1,"","fit"],[45,8,1,"","inflate"],[45,8,1,"","inflate_ip"],[45,8,1,"","move"],[45,8,1,"","move_ip"],[45,8,1,"","normalize"],[45,8,1,"","union"],[45,8,1,"","union_ip"],[45,8,1,"","unionall"],[45,8,1,"","unionall_ip"],[45,8,1,"","update"]],"pygame.Surface":[[51,7,1,"","_pixels_address"],[51,8,1,"","blit"],[51,8,1,"","blits"],[51,8,1,"","convert"],[51,8,1,"","convert_alpha"],[51,8,1,"","copy"],[51,8,1,"","fill"],[51,8,1,"","get_abs_offset"],[51,8,1,"","get_abs_parent"],[51,8,1,"","get_alpha"],[51,8,1,"","get_at"],[51,8,1,"","get_at_mapped"],[51,8,1,"","get_bitsize"],[51,8,1,"","get_bounding_rect"],[51,8,1,"","get_buffer"],[51,8,1,"","get_bytesize"],[51,8,1,"","get_clip"],[51,8,1,"","get_colorkey"],[51,8,1,"","get_flags"],[51,8,1,"","get_height"],[51,8,1,"","get_locked"],[51,8,1,"","get_locks"],[51,8,1,"","get_losses"],[51,8,1,"","get_masks"],[51,8,1,"","get_offset"],[51,8,1,"","get_palette"],[51,8,1,"","get_palette_at"],[51,8,1,"","get_parent"],[51,8,1,"","get_pitch"],[51,8,1,"","get_rect"],[51,8,1,"","get_shifts"],[51,8,1,"","get_size"],[51,8,1,"","get_view"],[51,8,1,"","get_width"],[51,8,1,"","lock"],[51,8,1,"","map_rgb"],[51,8,1,"","mustlock"],[51,8,1,"","scroll"],[51,8,1,"","set_alpha"],[51,8,1,"","set_at"],[51,8,1,"","set_clip"],[51,8,1,"","set_colorkey"],[51,8,1,"","set_masks"],[51,8,1,"","set_palette"],[51,8,1,"","set_palette_at"],[51,8,1,"","set_shifts"],[51,8,1,"","subsurface"],[51,8,1,"","unlock"],[51,8,1,"","unmap_rgb"]],"pygame._sdl2":[[47,5,0,"-","controller"],[55,5,0,"-","touch"],[48,5,0,"-","video"]],"pygame._sdl2.controller":[[47,6,1,"","Controller"],[47,9,1,"","get_count"],[47,9,1,"","get_eventstate"],[47,9,1,"","get_init"],[47,9,1,"","init"],[47,9,1,"","is_controller"],[47,9,1,"","name_forindex"],[47,9,1,"","quit"],[47,9,1,"","set_eventstate"]],"pygame._sdl2.controller.Controller":[[47,8,1,"","as_joystick"],[47,8,1,"","attached"],[47,8,1,"","from_joystick"],[47,8,1,"","get_axis"],[47,8,1,"","get_button"],[47,8,1,"","get_init"],[47,8,1,"","get_mapping"],[47,8,1,"","quit"],[47,8,1,"","rumble"],[47,8,1,"","set_mapping"],[47,8,1,"","stop_rumble"]],"pygame._sdl2.touch":[[55,9,1,"","get_device"],[55,9,1,"","get_finger"],[55,9,1,"","get_num_devices"],[55,9,1,"","get_num_fingers"]],"pygame._sdl2.video":[[48,6,1,"","Image"],[48,6,1,"","Renderer"],[48,6,1,"","Texture"],[48,6,1,"","Window"]],"pygame._sdl2.video.Image":[[48,7,1,"","alpha"],[48,7,1,"","angle"],[48,7,1,"","blend_mode"],[48,7,1,"","color"],[48,8,1,"","draw"],[48,7,1,"","flipX"],[48,7,1,"","flipY"],[48,8,1,"","get_rect"],[48,7,1,"","origin"],[48,7,1,"","srcrect"],[48,7,1,"","texture"]],"pygame._sdl2.video.Renderer":[[48,8,1,"","blit"],[48,8,1,"","clear"],[48,7,1,"","draw_blend_mode"],[48,7,1,"","draw_color"],[48,8,1,"","draw_line"],[48,8,1,"","draw_point"],[48,8,1,"","draw_rect"],[48,8,1,"","fill_rect"],[48,8,1,"","from_window"],[48,8,1,"","get_viewport"],[48,7,1,"","logical_size"],[48,8,1,"","present"],[48,7,1,"","scale"],[48,8,1,"","set_viewport"],[48,7,1,"","target"],[48,8,1,"","to_surface"]],"pygame._sdl2.video.Texture":[[48,7,1,"","alpha"],[48,7,1,"","blend_mode"],[48,7,1,"","color"],[48,8,1,"","draw"],[48,8,1,"","from_surface"],[48,8,1,"","get_rect"],[48,7,1,"","height"],[48,7,1,"","renderer"],[48,8,1,"","update"],[48,7,1,"","width"]],"pygame._sdl2.video.Window":[[48,7,1,"","borderless"],[48,7,1,"","brightness"],[48,8,1,"","destroy"],[48,7,1,"","display_index"],[48,8,1,"","focus"],[48,8,1,"","from_display_module"],[48,7,1,"","grab"],[48,8,1,"","hide"],[48,7,1,"","id"],[48,8,1,"","maximize"],[48,8,1,"","minimize"],[48,7,1,"","opacity"],[48,7,1,"","position"],[48,7,1,"","relative_mouse"],[48,7,1,"","resizable"],[48,8,1,"","restore"],[48,8,1,"","set_fullscreen"],[48,8,1,"","set_icon"],[48,8,1,"","set_modal_for"],[48,8,1,"","set_windowed"],[48,8,1,"","show"],[48,7,1,"","size"],[48,7,1,"","title"]],"pygame.camera":[[18,6,1,"","Camera"],[18,9,1,"","colorspace"],[18,9,1,"","get_backends"],[18,9,1,"","init"],[18,9,1,"","list_cameras"]],"pygame.camera.Camera":[[18,8,1,"","get_controls"],[18,8,1,"","get_image"],[18,8,1,"","get_raw"],[18,8,1,"","get_size"],[18,8,1,"","query_image"],[18,8,1,"","set_controls"],[18,8,1,"","start"],[18,8,1,"","stop"]],"pygame.cdrom":[[19,6,1,"","CD"],[19,9,1,"","get_count"],[19,9,1,"","get_init"],[19,9,1,"","init"],[19,9,1,"","quit"]],"pygame.cdrom.CD":[[19,8,1,"","eject"],[19,8,1,"","get_all"],[19,8,1,"","get_busy"],[19,8,1,"","get_current"],[19,8,1,"","get_empty"],[19,8,1,"","get_id"],[19,8,1,"","get_init"],[19,8,1,"","get_name"],[19,8,1,"","get_numtracks"],[19,8,1,"","get_paused"],[19,8,1,"","get_track_audio"],[19,8,1,"","get_track_length"],[19,8,1,"","get_track_start"],[19,8,1,"","init"],[19,8,1,"","pause"],[19,8,1,"","play"],[19,8,1,"","quit"],[19,8,1,"","resume"],[19,8,1,"","stop"]],"pygame.cursors":[[22,6,1,"","Cursor"],[22,9,1,"","compile"],[22,9,1,"","load_xbm"]],"pygame.cursors.Cursor":[[22,8,1,"","copy"],[22,7,1,"","data"],[22,7,1,"","type"]],"pygame.display":[[23,9,1,"","Info"],[23,9,1,"","flip"],[23,9,1,"","get_active"],[23,9,1,"","get_allow_screensaver"],[23,9,1,"","get_caption"],[23,9,1,"","get_desktop_sizes"],[23,9,1,"","get_driver"],[23,9,1,"","get_init"],[23,9,1,"","get_num_displays"],[23,9,1,"","get_surface"],[23,9,1,"","get_window_size"],[23,9,1,"","get_wm_info"],[23,9,1,"","gl_get_attribute"],[23,9,1,"","gl_set_attribute"],[23,9,1,"","iconify"],[23,9,1,"","init"],[23,9,1,"","list_modes"],[23,9,1,"","mode_ok"],[23,9,1,"","quit"],[23,9,1,"","set_allow_screensaver"],[23,9,1,"","set_caption"],[23,9,1,"","set_gamma"],[23,9,1,"","set_gamma_ramp"],[23,9,1,"","set_icon"],[23,9,1,"","set_mode"],[23,9,1,"","set_palette"],[23,9,1,"","toggle_fullscreen"],[23,9,1,"","update"]],"pygame.draw":[[24,9,1,"","aaline"],[24,9,1,"","aalines"],[24,9,1,"","arc"],[24,9,1,"","circle"],[24,9,1,"","ellipse"],[24,9,1,"","line"],[24,9,1,"","lines"],[24,9,1,"","polygon"],[24,9,1,"","rect"]],"pygame.event":[[25,9,1,"","Event"],[25,6,1,"","EventType"],[25,9,1,"","clear"],[25,9,1,"","custom_type"],[25,9,1,"","event_name"],[25,9,1,"","get"],[25,9,1,"","get_blocked"],[25,9,1,"","get_grab"],[25,9,1,"","peek"],[25,9,1,"","poll"],[25,9,1,"","post"],[25,9,1,"","pump"],[25,9,1,"","set_allowed"],[25,9,1,"","set_blocked"],[25,9,1,"","set_grab"],[25,9,1,"","wait"]],"pygame.event.EventType":[[25,7,1,"","__dict__"],[25,7,1,"","type"]],"pygame.examples.aliens":[[26,9,1,"","main"]],"pygame.examples.arraydemo":[[26,9,1,"","main"]],"pygame.examples.blend_fill":[[26,9,1,"","main"]],"pygame.examples.blit_blends":[[26,9,1,"","main"]],"pygame.examples.camera":[[26,9,1,"","main"]],"pygame.examples.chimp":[[26,9,1,"","main"]],"pygame.examples.cursors":[[26,9,1,"","main"]],"pygame.examples.eventlist":[[26,9,1,"","main"]],"pygame.examples.fonty":[[26,9,1,"","main"]],"pygame.examples.freetype_misc":[[26,9,1,"","main"]],"pygame.examples.glcube":[[26,9,1,"","main"]],"pygame.examples.headless_no_windows_needed":[[26,9,1,"","main"]],"pygame.examples.joystick":[[26,9,1,"","main"]],"pygame.examples.liquid":[[26,9,1,"","main"]],"pygame.examples.mask":[[26,9,1,"","main"]],"pygame.examples.midi":[[26,9,1,"","main"]],"pygame.examples.moveit":[[26,9,1,"","main"]],"pygame.examples.pixelarray":[[26,9,1,"","main"]],"pygame.examples.playmus":[[26,9,1,"","main"]],"pygame.examples.scaletest":[[26,9,1,"","main"]],"pygame.examples.scrap_clipboard":[[26,9,1,"","main"]],"pygame.examples.scroll":[[26,9,1,"","main"]],"pygame.examples.sound":[[26,9,1,"","main"]],"pygame.examples.sound_array_demos":[[26,9,1,"","main"]],"pygame.examples.stars":[[26,9,1,"","main"]],"pygame.examples.testsprite":[[26,9,1,"","main"]],"pygame.examples.vgrade":[[26,9,1,"","main"]],"pygame.fastevent":[[27,9,1,"","get"],[27,9,1,"","get_init"],[27,9,1,"","init"],[27,9,1,"","poll"],[27,9,1,"","post"],[27,9,1,"","pump"],[27,9,1,"","wait"]],"pygame.font":[[28,6,1,"","Font"],[28,9,1,"","SysFont"],[28,9,1,"","get_default_font"],[28,9,1,"","get_fonts"],[28,9,1,"","get_init"],[28,9,1,"","init"],[28,9,1,"","match_font"],[28,9,1,"","quit"]],"pygame.font.Font":[[28,7,1,"","bold"],[28,8,1,"","get_ascent"],[28,8,1,"","get_bold"],[28,8,1,"","get_descent"],[28,8,1,"","get_height"],[28,8,1,"","get_italic"],[28,8,1,"","get_linesize"],[28,8,1,"","get_underline"],[28,7,1,"","italic"],[28,8,1,"","metrics"],[28,8,1,"","render"],[28,8,1,"","set_bold"],[28,8,1,"","set_italic"],[28,8,1,"","set_underline"],[28,8,1,"","size"],[28,7,1,"","underline"]],"pygame.freetype":[[29,6,1,"","Font"],[29,9,1,"","SysFont"],[29,9,1,"","get_cache_size"],[29,9,1,"","get_default_font"],[29,9,1,"","get_default_resolution"],[29,9,1,"","get_error"],[29,9,1,"","get_init"],[29,9,1,"","get_version"],[29,9,1,"","init"],[29,9,1,"","quit"],[29,9,1,"","set_default_resolution"],[29,9,1,"","was_init"]],"pygame.freetype.Font":[[29,7,1,"","antialiased"],[29,7,1,"","ascender"],[29,7,1,"","bgcolor"],[29,7,1,"","descender"],[29,7,1,"","fgcolor"],[29,7,1,"","fixed_sizes"],[29,7,1,"","fixed_width"],[29,8,1,"","get_metrics"],[29,8,1,"","get_rect"],[29,8,1,"","get_sized_ascender"],[29,8,1,"","get_sized_descender"],[29,8,1,"","get_sized_glyph_height"],[29,8,1,"","get_sized_height"],[29,8,1,"","get_sizes"],[29,7,1,"","height"],[29,7,1,"","kerning"],[29,7,1,"","name"],[29,7,1,"","oblique"],[29,7,1,"","origin"],[29,7,1,"","pad"],[29,7,1,"","path"],[29,8,1,"","render"],[29,8,1,"","render_raw"],[29,8,1,"","render_raw_to"],[29,8,1,"","render_to"],[29,7,1,"","resolution"],[29,7,1,"","rotation"],[29,7,1,"","scalable"],[29,7,1,"","size"],[29,7,1,"","strength"],[29,7,1,"","strong"],[29,7,1,"","style"],[29,7,1,"","ucs4"],[29,7,1,"","underline"],[29,7,1,"","underline_adjustment"],[29,7,1,"","use_bitmap_strikes"],[29,7,1,"","vertical"],[29,7,1,"","wide"]],"pygame.gfxdraw":[[30,9,1,"","aacircle"],[30,9,1,"","aaellipse"],[30,9,1,"","aapolygon"],[30,9,1,"","aatrigon"],[30,9,1,"","arc"],[30,9,1,"","bezier"],[30,9,1,"","box"],[30,9,1,"","circle"],[30,9,1,"","ellipse"],[30,9,1,"","filled_circle"],[30,9,1,"","filled_ellipse"],[30,9,1,"","filled_polygon"],[30,9,1,"","filled_trigon"],[30,9,1,"","hline"],[30,9,1,"","line"],[30,9,1,"","pie"],[30,9,1,"","pixel"],[30,9,1,"","polygon"],[30,9,1,"","rectangle"],[30,9,1,"","textured_polygon"],[30,9,1,"","trigon"],[30,9,1,"","vline"]],"pygame.image":[[31,9,1,"","frombuffer"],[31,9,1,"","fromstring"],[31,9,1,"","get_extended"],[31,9,1,"","get_sdl_image_version"],[31,9,1,"","load"],[31,9,1,"","load_basic"],[31,9,1,"","load_extended"],[31,9,1,"","save"],[31,9,1,"","save_extended"],[31,9,1,"","tostring"]],"pygame.joystick":[[32,6,1,"","Joystick"],[32,9,1,"","get_count"],[32,9,1,"","get_init"],[32,9,1,"","init"],[32,9,1,"","quit"]],"pygame.joystick.Joystick":[[32,8,1,"","get_axis"],[32,8,1,"","get_ball"],[32,8,1,"","get_button"],[32,8,1,"","get_guid"],[32,8,1,"","get_hat"],[32,8,1,"","get_id"],[32,8,1,"","get_init"],[32,8,1,"","get_instance_id"],[32,8,1,"","get_name"],[32,8,1,"","get_numaxes"],[32,8,1,"","get_numballs"],[32,8,1,"","get_numbuttons"],[32,8,1,"","get_numhats"],[32,8,1,"","get_power_level"],[32,8,1,"","init"],[32,8,1,"","quit"],[32,8,1,"","rumble"],[32,8,1,"","stop_rumble"]],"pygame.key":[[33,9,1,"","get_focused"],[33,9,1,"","get_mods"],[33,9,1,"","get_pressed"],[33,9,1,"","get_repeat"],[33,9,1,"","key_code"],[33,9,1,"","name"],[33,9,1,"","set_mods"],[33,9,1,"","set_repeat"],[33,9,1,"","set_text_input_rect"],[33,9,1,"","start_text_input"],[33,9,1,"","stop_text_input"]],"pygame.mask":[[35,6,1,"","Mask"],[35,9,1,"","from_surface"],[35,9,1,"","from_threshold"]],"pygame.mask.Mask":[[35,8,1,"","angle"],[35,8,1,"","centroid"],[35,8,1,"","clear"],[35,8,1,"","connected_component"],[35,8,1,"","connected_components"],[35,8,1,"","convolve"],[35,8,1,"","copy"],[35,8,1,"","count"],[35,8,1,"","draw"],[35,8,1,"","erase"],[35,8,1,"","fill"],[35,8,1,"","get_at"],[35,8,1,"","get_bounding_rects"],[35,8,1,"","get_rect"],[35,8,1,"","get_size"],[35,8,1,"","invert"],[35,8,1,"","outline"],[35,8,1,"","overlap"],[35,8,1,"","overlap_area"],[35,8,1,"","overlap_mask"],[35,8,1,"","scale"],[35,8,1,"","set_at"],[35,8,1,"","to_surface"]],"pygame.math":[[36,6,1,"","Vector2"],[36,6,1,"","Vector3"]],"pygame.math.Vector2":[[36,8,1,"","angle_to"],[36,8,1,"","as_polar"],[36,8,1,"","copy"],[36,8,1,"","cross"],[36,8,1,"","distance_squared_to"],[36,8,1,"","distance_to"],[36,8,1,"","dot"],[36,8,1,"","elementwise"],[36,8,1,"","from_polar"],[36,8,1,"","is_normalized"],[36,8,1,"","length"],[36,8,1,"","length_squared"],[36,8,1,"","lerp"],[36,8,1,"","magnitude"],[36,8,1,"","magnitude_squared"],[36,8,1,"","normalize"],[36,8,1,"","normalize_ip"],[36,8,1,"","project"],[36,8,1,"","reflect"],[36,8,1,"","reflect_ip"],[36,8,1,"","rotate"],[36,8,1,"","rotate_ip"],[36,8,1,"","rotate_ip_rad"],[36,8,1,"","rotate_rad"],[36,8,1,"","rotate_rad_ip"],[36,8,1,"","scale_to_length"],[36,8,1,"","slerp"],[36,8,1,"","update"]],"pygame.math.Vector3":[[36,8,1,"","angle_to"],[36,8,1,"","as_spherical"],[36,8,1,"","copy"],[36,8,1,"","cross"],[36,8,1,"","distance_squared_to"],[36,8,1,"","distance_to"],[36,8,1,"","dot"],[36,8,1,"","elementwise"],[36,8,1,"","from_spherical"],[36,8,1,"","is_normalized"],[36,8,1,"","length"],[36,8,1,"","length_squared"],[36,8,1,"","lerp"],[36,8,1,"","magnitude"],[36,8,1,"","magnitude_squared"],[36,8,1,"","normalize"],[36,8,1,"","normalize_ip"],[36,8,1,"","project"],[36,8,1,"","reflect"],[36,8,1,"","reflect_ip"],[36,8,1,"","rotate"],[36,8,1,"","rotate_ip"],[36,8,1,"","rotate_ip_rad"],[36,8,1,"","rotate_rad"],[36,8,1,"","rotate_rad_ip"],[36,8,1,"","rotate_x"],[36,8,1,"","rotate_x_ip"],[36,8,1,"","rotate_x_ip_rad"],[36,8,1,"","rotate_x_rad"],[36,8,1,"","rotate_x_rad_ip"],[36,8,1,"","rotate_y"],[36,8,1,"","rotate_y_ip"],[36,8,1,"","rotate_y_ip_rad"],[36,8,1,"","rotate_y_rad"],[36,8,1,"","rotate_y_rad_ip"],[36,8,1,"","rotate_z"],[36,8,1,"","rotate_z_ip"],[36,8,1,"","rotate_z_ip_rad"],[36,8,1,"","rotate_z_rad"],[36,8,1,"","rotate_z_rad_ip"],[36,8,1,"","scale_to_length"],[36,8,1,"","slerp"],[36,8,1,"","update"]],"pygame.midi":[[37,6,1,"","Input"],[37,10,1,"","MidiException"],[37,6,1,"","Output"],[37,9,1,"","frequency_to_midi"],[37,9,1,"","get_count"],[37,9,1,"","get_default_input_id"],[37,9,1,"","get_default_output_id"],[37,9,1,"","get_device_info"],[37,9,1,"","get_init"],[37,9,1,"","init"],[37,9,1,"","midi_to_ansi_note"],[37,9,1,"","midi_to_frequency"],[37,9,1,"","midis2events"],[37,9,1,"","quit"],[37,9,1,"","time"]],"pygame.midi.Input":[[37,8,1,"","close"],[37,8,1,"","poll"],[37,8,1,"","read"]],"pygame.midi.Output":[[37,8,1,"","abort"],[37,8,1,"","close"],[37,8,1,"","note_off"],[37,8,1,"","note_on"],[37,8,1,"","pitch_bend"],[37,8,1,"","set_instrument"],[37,8,1,"","write"],[37,8,1,"","write_short"],[37,8,1,"","write_sys_ex"]],"pygame.mixer":[[38,6,1,"","Channel"],[38,6,1,"","Sound"],[38,9,1,"","fadeout"],[38,9,1,"","find_channel"],[38,9,1,"","get_busy"],[38,9,1,"","get_init"],[38,9,1,"","get_num_channels"],[38,9,1,"","get_sdl_mixer_version"],[38,9,1,"","init"],[40,5,0,"-","music"],[38,9,1,"","pause"],[38,9,1,"","pre_init"],[38,9,1,"","quit"],[38,9,1,"","set_num_channels"],[38,9,1,"","set_reserved"],[38,9,1,"","stop"],[38,9,1,"","unpause"]],"pygame.mixer.Channel":[[38,8,1,"","fadeout"],[38,8,1,"","get_busy"],[38,8,1,"","get_endevent"],[38,8,1,"","get_queue"],[38,8,1,"","get_sound"],[38,8,1,"","get_volume"],[38,8,1,"","pause"],[38,8,1,"","play"],[38,8,1,"","queue"],[38,8,1,"","set_endevent"],[38,8,1,"","set_volume"],[38,8,1,"","stop"],[38,8,1,"","unpause"]],"pygame.mixer.Sound":[[38,8,1,"","fadeout"],[38,8,1,"","get_length"],[38,8,1,"","get_num_channels"],[38,8,1,"","get_raw"],[38,8,1,"","get_volume"],[38,8,1,"","play"],[38,8,1,"","set_volume"],[38,8,1,"","stop"]],"pygame.mixer.music":[[40,9,1,"","fadeout"],[40,9,1,"","get_busy"],[40,9,1,"","get_endevent"],[40,9,1,"","get_pos"],[40,9,1,"","get_volume"],[40,9,1,"","load"],[40,9,1,"","pause"],[40,9,1,"","play"],[40,9,1,"","queue"],[40,9,1,"","rewind"],[40,9,1,"","set_endevent"],[40,9,1,"","set_pos"],[40,9,1,"","set_volume"],[40,9,1,"","stop"],[40,9,1,"","unload"],[40,9,1,"","unpause"]],"pygame.mouse":[[39,9,1,"","get_cursor"],[39,9,1,"","get_focused"],[39,9,1,"","get_pos"],[39,9,1,"","get_pressed"],[39,9,1,"","get_rel"],[39,9,1,"","get_visible"],[39,9,1,"","set_cursor"],[39,9,1,"","set_pos"],[39,9,1,"","set_visible"]],"pygame.pixelcopy":[[43,9,1,"","array_to_surface"],[43,9,1,"","make_surface"],[43,9,1,"","map_array"],[43,9,1,"","surface_to_array"]],"pygame.scrap":[[46,9,1,"","contains"],[46,9,1,"","get"],[46,9,1,"","get_init"],[46,9,1,"","get_types"],[46,9,1,"","init"],[46,9,1,"","lost"],[46,9,1,"","put"],[46,9,1,"","set_mode"]],"pygame.sndarray":[[49,9,1,"","array"],[49,9,1,"","get_arraytype"],[49,9,1,"","get_arraytypes"],[49,9,1,"","make_sound"],[49,9,1,"","samples"],[49,9,1,"","use_arraytype"]],"pygame.sprite":[[50,6,1,"","DirtySprite"],[50,6,1,"","Group"],[50,9,1,"","GroupSingle"],[50,6,1,"","LayeredDirty"],[50,6,1,"","LayeredUpdates"],[50,9,1,"","OrderedUpdates"],[50,6,1,"","RenderClear"],[50,6,1,"","RenderPlain"],[50,6,1,"","RenderUpdates"],[50,6,1,"","Sprite"],[50,9,1,"","collide_circle"],[50,9,1,"","collide_circle_ratio"],[50,9,1,"","collide_mask"],[50,9,1,"","collide_rect"],[50,9,1,"","collide_rect_ratio"],[50,9,1,"","groupcollide"],[50,9,1,"","spritecollide"],[50,9,1,"","spritecollideany"]],"pygame.sprite.Group":[[50,8,1,"","add"],[50,8,1,"","clear"],[50,8,1,"","copy"],[50,8,1,"","draw"],[50,8,1,"","empty"],[50,8,1,"","has"],[50,8,1,"","remove"],[50,8,1,"","sprites"],[50,8,1,"","update"]],"pygame.sprite.LayeredDirty":[[50,8,1,"","change_layer"],[50,8,1,"","clear"],[50,8,1,"","draw"],[50,8,1,"","get_clip"],[50,8,1,"","repaint_rect"],[50,8,1,"","set_clip"],[50,8,1,"","set_timing_threshold"],[50,8,1,"","set_timing_treshold"]],"pygame.sprite.LayeredUpdates":[[50,8,1,"","add"],[50,8,1,"","change_layer"],[50,8,1,"","draw"],[50,8,1,"","get_bottom_layer"],[50,8,1,"","get_layer_of_sprite"],[50,8,1,"","get_sprite"],[50,8,1,"","get_sprites_at"],[50,8,1,"","get_sprites_from_layer"],[50,8,1,"","get_top_layer"],[50,8,1,"","get_top_sprite"],[50,8,1,"","layers"],[50,8,1,"","move_to_back"],[50,8,1,"","move_to_front"],[50,8,1,"","remove_sprites_of_layer"],[50,8,1,"","sprites"],[50,8,1,"","switch_layer"]],"pygame.sprite.RenderUpdates":[[50,8,1,"","draw"]],"pygame.sprite.Sprite":[[50,8,1,"","add"],[50,8,1,"","alive"],[50,8,1,"","groups"],[50,8,1,"","kill"],[50,8,1,"","remove"],[50,8,1,"","update"]],"pygame.surfarray":[[52,9,1,"","array2d"],[52,9,1,"","array3d"],[52,9,1,"","array_alpha"],[52,9,1,"","array_blue"],[52,9,1,"","array_colorkey"],[52,9,1,"","array_green"],[52,9,1,"","array_red"],[52,9,1,"","blit_array"],[52,9,1,"","get_arraytype"],[52,9,1,"","get_arraytypes"],[52,9,1,"","make_surface"],[52,9,1,"","map_array"],[52,9,1,"","pixels2d"],[52,9,1,"","pixels3d"],[52,9,1,"","pixels_alpha"],[52,9,1,"","pixels_blue"],[52,9,1,"","pixels_green"],[52,9,1,"","pixels_red"],[52,9,1,"","use_arraytype"]],"pygame.tests":[[53,9,1,"","run"]],"pygame.time":[[54,6,1,"","Clock"],[54,9,1,"","delay"],[54,9,1,"","get_ticks"],[54,9,1,"","set_timer"],[54,9,1,"","wait"]],"pygame.time.Clock":[[54,8,1,"","get_fps"],[54,8,1,"","get_rawtime"],[54,8,1,"","get_time"],[54,8,1,"","tick"],[54,8,1,"","tick_busy_loop"]],"pygame.transform":[[56,9,1,"","average_color"],[56,9,1,"","average_surfaces"],[56,9,1,"","chop"],[56,9,1,"","flip"],[56,9,1,"","get_smoothscale_backend"],[56,9,1,"","laplacian"],[56,9,1,"","rotate"],[56,9,1,"","rotozoom"],[56,9,1,"","scale"],[56,9,1,"","scale2x"],[56,9,1,"","set_smoothscale_backend"],[56,9,1,"","smoothscale"],[56,9,1,"","threshold"]],"pygame.version":[[44,11,1,"","SDL"],[44,11,1,"","rev"],[44,11,1,"","ver"],[44,11,1,"","vernum"]],pgBuffer_AsArrayInterface:[[1,2,1,"c.pgBuffer_AsArrayInterface","view_p"]],pgBuffer_AsArrayStruct:[[1,2,1,"c.pgBuffer_AsArrayStruct","view_p"]],pgBuffer_Release:[[1,2,1,"c.pgBuffer_Release","pg_view_p"]],pgBufproxy_Check:[[2,2,1,"c.pgBufproxy_Check","x"]],pgBufproxy_GetParent:[[2,2,1,"c.pgBufproxy_GetParent","obj"]],pgBufproxy_New:[[2,2,1,"c.pgBufproxy_New","get_buffer"],[2,2,1,"c.pgBufproxy_New","obj"]],pgBufproxy_Trip:[[2,2,1,"c.pgBufproxy_Trip","obj"]],pgCD_AsID:[[3,2,1,"c.pgCD_AsID","x"]],pgCD_Check:[[3,2,1,"c.pgCD_Check","x"]],pgCD_New:[[3,2,1,"c.pgCD_New","id"]],pgChannel_AsInt:[[8,2,1,"c.pgChannel_AsInt","x"]],pgChannel_Check:[[8,2,1,"c.pgChannel_Check","obj"]],pgChannel_New:[[8,2,1,"c.pgChannel_New","channelnum"]],pgColor_Check:[[4,2,1,"c.pgColor_Check","obj"]],pgColor_New:[[4,2,1,"c.pgColor_New","rgba"]],pgColor_NewLength:[[4,2,1,"c.pgColor_NewLength","length"],[4,2,1,"c.pgColor_NewLength","rgba"]],pgDict_AsBuffer:[[1,2,1,"c.pgDict_AsBuffer","dict"],[1,2,1,"c.pgDict_AsBuffer","flags"],[1,2,1,"c.pgDict_AsBuffer","pg_view_p"]],pgEventObject:[[6,3,1,"c.pgEventObject.type","type"]],pgEvent_Check:[[6,2,1,"c.pgEvent_Check","x"]],pgEvent_FillUserEvent:[[6,2,1,"c.pgEvent_FillUserEvent","e"],[6,2,1,"c.pgEvent_FillUserEvent","event"]],pgEvent_New2:[[6,2,1,"c.pgEvent_New2","dict"],[6,2,1,"c.pgEvent_New2","type"]],pgEvent_New:[[6,2,1,"c.pgEvent_New","event"]],pgFont_Check:[[7,2,1,"c.pgFont_Check","x"]],pgFont_IS_ALIVE:[[7,2,1,"c.pgFont_IS_ALIVE","o"]],pgFont_New:[[7,2,1,"c.pgFont_New","filename"],[7,2,1,"c.pgFont_New","font_index"]],pgLifetimeLockObject:[[13,3,1,"c.pgLifetimeLockObject.lockobj","lockobj"],[13,3,1,"c.pgLifetimeLockObject.surface","surface"]],pgLifetimeLock_Check:[[13,2,1,"c.pgLifetimeLock_Check","x"]],pgObject_GetBuffer:[[1,2,1,"c.pgObject_GetBuffer","flags"],[1,2,1,"c.pgObject_GetBuffer","obj"],[1,2,1,"c.pgObject_GetBuffer","pg_view_p"]],pgRWops_FromFileObject:[[10,2,1,"c.pgRWops_FromFileObject","obj"]],pgRWops_FromObject:[[10,2,1,"c.pgRWops_FromObject","obj"]],pgRWops_GetFileExtension:[[10,2,1,"c.pgRWops_GetFileExtension","rw"]],pgRWops_IsFileObject:[[10,2,1,"c.pgRWops_IsFileObject","rw"]],pgRWops_ReleaseObject:[[10,2,1,"c.pgRWops_ReleaseObject","context"]],pgRectObject:[[9,3,1,"c.pgRectObject.r","r"]],pgRect_AsRect:[[9,2,1,"c.pgRect_AsRect","obj"]],pgRect_FromObject:[[9,2,1,"c.pgRect_FromObject","obj"],[9,2,1,"c.pgRect_FromObject","temp"]],pgRect_New4:[[9,2,1,"c.pgRect_New4","h"],[9,2,1,"c.pgRect_New4","w"],[9,2,1,"c.pgRect_New4","x"],[9,2,1,"c.pgRect_New4","y"]],pgRect_New:[[9,2,1,"c.pgRect_New","r"]],pgRect_Normalize:[[9,2,1,"c.pgRect_Normalize","rect"]],pgSound_AsChunk:[[8,2,1,"c.pgSound_AsChunk","x"]],pgSound_Check:[[8,2,1,"c.pgSound_Check","obj"]],pgSound_New:[[8,2,1,"c.pgSound_New","chunk"]],pgSurface_AsSurface:[[12,2,1,"c.pgSurface_AsSurface","x"]],pgSurface_Blit:[[12,2,1,"c.pgSurface_Blit","dstobj"],[12,2,1,"c.pgSurface_Blit","dstrect"],[12,2,1,"c.pgSurface_Blit","srcobj"],[12,2,1,"c.pgSurface_Blit","srcrect"],[12,2,1,"c.pgSurface_Blit","the_args"]],pgSurface_Check:[[12,2,1,"c.pgSurface_Check","x"]],pgSurface_Lock:[[13,2,1,"c.pgSurface_Lock","surfobj"]],pgSurface_LockBy:[[13,2,1,"c.pgSurface_LockBy","lockobj"],[13,2,1,"c.pgSurface_LockBy","surfobj"]],pgSurface_LockLifetime:[[13,2,1,"c.pgSurface_LockLifetime","lockobj"],[13,2,1,"c.pgSurface_LockLifetime","surfobj"]],pgSurface_New:[[12,2,1,"c.pgSurface_New","s"]],pgSurface_Prep:[[13,2,1,"c.pgSurface_Prep","surfobj"]],pgSurface_UnLock:[[13,2,1,"c.pgSurface_UnLock","surfobj"]],pgSurface_UnLockBy:[[13,2,1,"c.pgSurface_UnLockBy","lockobj"],[13,2,1,"c.pgSurface_UnLockBy","surfobj"]],pgSurface_Unprep:[[13,2,1,"c.pgSurface_Unprep","surfobj"]],pgVidInfo_AsVidInfo:[[5,2,1,"c.pgVidInfo_AsVidInfo","obj"]],pgVidInfo_Check:[[5,2,1,"c.pgVidInfo_Check","x"]],pgVidInfo_New:[[5,2,1,"c.pgVidInfo_New","i"]],pg_EncodeFilePath:[[10,2,1,"c.pg_EncodeFilePath","eclass"],[10,2,1,"c.pg_EncodeFilePath","obj"]],pg_EncodeString:[[10,2,1,"c.pg_EncodeString","eclass"],[10,2,1,"c.pg_EncodeString","encoding"],[10,2,1,"c.pg_EncodeString","errors"],[10,2,1,"c.pg_EncodeString","obj"]],pg_FloatFromObj:[[1,2,1,"c.pg_FloatFromObj","obj"],[1,2,1,"c.pg_FloatFromObj","val"]],pg_FloatFromObjIndex:[[1,2,1,"c.pg_FloatFromObjIndex","index"],[1,2,1,"c.pg_FloatFromObjIndex","obj"],[1,2,1,"c.pg_FloatFromObjIndex","val"]],pg_IntFromObj:[[1,2,1,"c.pg_IntFromObj","obj"],[1,2,1,"c.pg_IntFromObj","val"]],pg_IntFromObjIndex:[[1,2,1,"c.pg_IntFromObjIndex","index"],[1,2,1,"c.pg_IntFromObjIndex","obj"],[1,2,1,"c.pg_IntFromObjIndex","val"]],pg_RGBAFromObj:[[1,2,1,"c.pg_RGBAFromObj","RGBA"],[1,2,1,"c.pg_RGBAFromObj","obj"]],pg_RegisterQuit:[[1,2,1,"c.pg_RegisterQuit","f"]],pg_SetDefaultWindow:[[1,2,1,"c.pg_SetDefaultWindow","win"]],pg_SetDefaultWindowSurface:[[1,2,1,"c.pg_SetDefaultWindowSurface","screen"]],pg_TwoFloatsFromObj:[[1,2,1,"c.pg_TwoFloatsFromObj","obj"],[1,2,1,"c.pg_TwoFloatsFromObj","val1"],[1,2,1,"c.pg_TwoFloatsFromObj","val2"]],pg_TwoIntsFromObj:[[1,2,1,"c.pg_TwoIntsFromObj","obj"],[1,2,1,"c.pg_TwoIntsFromObj","v2"],[1,2,1,"c.pg_TwoIntsFromObj","val1"]],pg_UintFromObj:[[1,2,1,"c.pg_UintFromObj","obj"],[1,2,1,"c.pg_UintFromObj","val"]],pg_UintFromObjIndex:[[1,2,1,"c.pg_UintFromObjIndex","_index"],[1,2,1,"c.pg_UintFromObjIndex","obj"],[1,2,1,"c.pg_UintFromObjIndex","val"]],pg_buffer:[[1,3,1,"c.pg_buffer.consumer","consumer"],[1,3,1,"c.pg_buffer.release_buffer","release_buffer"],[1,3,1,"c.pg_buffer.view","view"]],pg_mod_autoinit:[[1,2,1,"c.pg_mod_autoinit","modname"]],pg_mod_autoquit:[[1,2,1,"c.pg_mod_autoquit","modname"]],pygame:[[17,6,1,"","BufferProxy"],[20,6,1,"","Color"],[41,6,1,"","Overlay"],[42,6,1,"","PixelArray"],[45,6,1,"","Rect"],[51,6,1,"","Surface"],[18,5,0,"-","camera"],[19,5,0,"-","cdrom"],[22,5,0,"-","cursors"],[23,5,0,"-","display"],[24,5,0,"-","draw"],[44,9,1,"","encode_file_path"],[44,9,1,"","encode_string"],[44,10,1,"","error"],[25,5,0,"-","event"],[26,5,0,"-","examples"],[27,5,0,"-","fastevent"],[28,5,0,"-","font"],[29,5,0,"-","freetype"],[44,9,1,"","get_error"],[44,9,1,"","get_init"],[44,9,1,"","get_sdl_byteorder"],[44,9,1,"","get_sdl_version"],[30,5,0,"-","gfxdraw"],[31,5,0,"-","image"],[44,9,1,"","init"],[32,5,0,"-","joystick"],[33,5,0,"-","key"],[34,5,0,"-","locals"],[35,5,0,"-","mask"],[36,5,0,"-","math"],[37,5,0,"-","midi"],[38,5,0,"-","mixer"],[39,5,0,"-","mouse"],[43,5,0,"-","pixelcopy"],[44,9,1,"","quit"],[44,9,1,"","register_quit"],[46,5,0,"-","scrap"],[44,9,1,"","set_error"],[49,5,0,"-","sndarray"],[50,5,0,"-","sprite"],[52,5,0,"-","surfarray"],[53,5,0,"-","tests"],[54,5,0,"-","time"],[56,5,0,"-","transform"],[44,5,0,"-","version"]]},objnames:{"0":["c","macro","C macro"],"1":["c","function","C function"],"10":["py","exception","Python exception"],"11":["py","data","Python data"],"2":["c","functionParam","C function parameter"],"3":["c","member","C member"],"4":["c","type","C type"],"5":["py","module","Python module"],"6":["py","class","Python class"],"7":["py","attribute","Python attribute"],"8":["py","method","Python method"],"9":["py","function","Python function"]},objtypes:{"0":"c:macro","1":"c:function","10":"py:exception","11":"py:data","2":"c:functionParam","3":"c:member","4":"c:type","5":"py:module","6":"py:class","7":"py:attribute","8":"py:method","9":"py:function"},terms:{"0":[1,2,6,7,10,12,17,18,19,20,22,23,24,25,26,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,47,48,50,51,52,54,55,56,57,58,59,62,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,86,88,89],"02778":29,"08333":29,"0\uac1c":[78,79],"0\ucc28\uc6d0":81,"0d":73,"0dev11":32,"0x00":20,"0x00000000":51,"0x00000001":51,"0x00000004":51,"0x00000100":51,"0x00001000":51,"0x00002000":51,"0x00004000":51,"0x00010000":51,"0x01000000":51,"0x10":37,"0x10000":29,"0x10ffff":29,"0x11":37,"0x12":37,"0x13":37,"0x7d":37,"0x90":37,"0xaacce":42,"0xc0":37,"0xd800":29,"0xdfff":29,"0xf0":37,"0xf7":37,"0xff":20,"0xff00ff":42,"0xffff":23,"0xrrggbb":20,"0xrrggbbaa":20,"1":[1,2,3,4,6,9,12,14,16,17,18,20,22,23,24,25,26,28,29,30,31,32,33,35,36,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,58,59,60,62,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,83,84],"10":[23,24,26,32,35,36,45,54,56,58,62,63,65,66,68,69,70,71,72,73,76,77,78,79,80,81,84,85,88,89],"100":[20,24,37,51,57,62,65,84],"1000":[50,54,84],"1024":[37,38],"105":57,"1080":[23,59],"1080p":23,"10\ub610\ub294":79,"10\uc758":76,"10\uc774":77,"10th":35,"11":[32,68,69,76,77],"114":42,"115":24,"117":26,"11\uc744":77,"11\uc758":76,"11\uc774":77,"12":[11,29,32,44,58,66,68,76],"120":22,"1234":44,"125":24,"127":[35,37,71,72,73,79,80,81],"128":65,"1280":[58,66],"13":[32,63,65,68,76,89],"135":24,"14":[32,47,68,76],"140":[67,68,69,70,75,76,77,78],"145":57,"14\uc758":76,"15":[24,32,51,58,66,68,69,76,77],"150":[24,85],"15\uc758":76,"16":[15,22,23,29,32,38,49,59,65,68,76],"16711680":59,"16bit":38,"17":[63,68,76],"170":[42,57,58,66],"17\uc5d0\uc11c\uc758":76,"18":[24,58,63,66,68,76],"187":[58,66],"19":[68,76],"192":84,"1920":[23,59],"19\ub294":76,"19\uc5d0\uc11c":76,"1\uac1c":78,"1\uac1c\uc758":76,"1\uacfc":77,"1\uc778":75,"1\uc778\uc9c0":81,"1\uc904\uc9dc\ub9ac":76,"1\ucc28\uc6d0":81,"1\ucd08\uc5d0":77,"1d":[42,73],"1s":62,"1x1":37,"2":[1,9,15,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,47,50,51,52,54,55,56,57,58,61,63,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,83,84],"20":[22,24,32,45,56,65,68,71,72,73,76,79,80,81,84],"200":[24,63],"2000":[16,63],"20000":37,"2001":63,"2004":18,"204":42,"20500":37,"20\uc77c":79,"20\uc904\uc9dc\ub9ac":76,"21":[37,63],"210":24,"22":49,"220":[24,67,68,69,70,75,76,77,78],"22000":49,"22050":38,"225":24,"23":63,"235":[58,66],"238":[42,58,66],"2380":23,"24":[17,18,22,24,28,31,42,51,52,56,63,65],"240":[63,68,69,70,71,72,73,76,77,78,79,80,81],"24x24":22,"25":[56,58,66],"250":[24,85],"255":[1,20,24,28,29,30,35,42,43,50,51,56,57,58,59,65,68,69,70,71,72,73,76,77,78,79,80,81,84,85],"256":[23,37,51],"260":24,"27":37,"270":[72,73,80,81],"28":84,"299":42,"2\uac1c\ub97c":80,"2\uac1c\uc758":78,"2\ucc28\ub235":81,"2\ucc28\uc6d0":81,"2d":[16,35,42,43,48,52,63,64,65,68,73,76],"2d\uc6a9":76,"2pre":36,"2s":62,"2x2":65,"3":[1,17,18,20,22,23,24,28,30,31,32,33,36,37,38,39,42,43,44,47,50,51,61,62,63,64,65,67,68,69,70,71,73,76,77,78,79,81,83,84],"30":[22,24,29,30,38,45,53,57,63,71,84],"300":24,"3072":38,"30\uc73c\ub85c":79,"315":29,"32":[1,15,17,18,29,31,35,38,51,52,56,59,65,68,69,70,71,72,73,76,77,78,79,80,81],"320":[63,68,69,70,71,72,73,76,77,78,79,80,81],"325":[72,73,80,81],"32767":47,"32768":47,"32x32":23,"33":65,"35":[74,84],"359":29,"35\ub144":82,"36":[29,85],"360":[20,29,32,47,58,66],"390":29,"3\uac1c\uc758":[76,79],"3d":[23,26,43,52,63,65,67],"3f":32,"3rd":63,"3x3":65,"4":[1,4,9,17,20,24,28,29,32,33,36,37,38,39,42,47,51,56,57,58,59,60,61,62,65,66,68,69,70,71,72,73,76,77,78,79,80,81,83,84,86,89],"40":[22,24,54,56,62,63,71,72,73,79,80,81,84],"400":[22,23,24,57],"4096":[37,38],"42":24,"425":[72,73,80,81],"4321":44,"44100":38,"45":[29,72,73,80,81],"47":89,"480":[18,26,39,48,57,58,59,62,66,68,69,70,71,72,73,76,77,78,79,80,81,89],"480\uc73c\ub85c":76,"4\uac1c\uc758":[76,79],"4k":23,"4th":65,"5":[14,20,23,24,25,29,32,33,35,36,37,38,39,40,42,44,46,49,50,57,58,61,62,63,65,66,68,69,70,71,72,73,76,77,78,79,80,81,83,89],"50":[22,24,26,57,65,73,81,84,85],"500":32,"500m":37,"512":38,"55":56,"56":24,"587":42,"5\uac00":77,"5\uac1c\uc758":76,"5\uc5d0":78,"5x5":[73,81],"6":[24,26,32,33,38,44,50,61,62,65,68,69,70,71,73,76,77,78,79,81,83],"60":[22,24,37,38,39,58,66,69,71,72,73,77,79,80,81,89],"600":[22,59,62],"60\uc774\ub77c\ub294":77,"63":20,"64":[20,29,56,58,66],"640":[18,26,39,48,57,59,62,68,69,70,71,72,73,76,77,78,79,80,81,89],"640x480":[23,62,65],"65":[37,56],"65280":59,"6\uc744":79,"6x":84,"7":[23,32,33,43,44,63,65,68,69,70,71,76,77,78,79,83],"70":24,"700":[23,32],"72":29,"720":29,"75":[22,24,69,77],"7\uc5d0\uc11c":79,"7\uc758":76,"8":[15,17,18,20,22,23,24,26,28,29,30,31,32,33,35,38,42,43,44,46,49,50,51,52,54,56,59,62,63,65,68,69,70,76,77,78,83,89],"80":[24,50,58,66],"800":59,"8191":37,"8192":37,"8\uc758":76,"8bit":38,"9":[14,16,17,18,20,23,25,26,29,30,32,33,35,36,37,38,40,42,43,44,45,46,47,51,52,56,57,65,68,69,70,76,77,78],"90":[24,35,38,56,57,58,66,89],"97":33,"9\uc758":76,"\uac00":[76,77,81],"\uac00\ub2a5":80,"\uac00\ub3c5\uc131\uc744":76,"\uac00\ubcf4\uc790":78,"\uac00\uc18d":77,"\uac00\uc7a5":[76,79,82],"\uac00\uc815\ud558\uba74":76,"\uac00\uc815\ud574":75,"\uac00\uc9c0\uace0":[75,78,81],"\uac00\uc9c0\uae30":76,"\uac00\uc9c0\ub294":76,"\uac00\uc9c0\ubbc0\ub85c":[76,79],"\uac00\uc9c4\ub2e4":[76,80],"\uac00\uc9c4\ub2e4\ub294":75,"\uac00\uc9c8":82,"\uac01\uac01":79,"\uac01\uac01\uc758":79,"\uac04\ub2e8\ud558\ub2e4":81,"\uac10\uc18c\uc2dc\ud0a4\ub294":80,"\uac10\uc18c\ud55c\ub2e4":81,"\uac12":79,"\uac12\ub9cc\uc774":79,"\uac12\uc740":[76,79],"\uac12\uc744":[76,79,80],"\uac12\uc774":79,"\uac12\uc774\uace0":79,"\uac16\ub294":[81,82],"\uac19\ub2e4":[75,76,82],"\uac19\uc544":[77,81],"\uac19\uc740":[76,77,82],"\uac19\uc74c":81,"\uac19\uc774":[76,79],"\uac1c\ub150\uc740":82,"\uac1c\ub150\uc744":77,"\uac1c\ub150\uc774\ub2e4":82,"\uac1c\ubc1c\uc790\uac00":75,"\uac1c\uc120\uc758":81,"\uac1c\uc218\ub97c":81,"\uac1c\uc218\ub9cc\ud07c":79,"\uac1d\uccb4":[76,78],"\uac1d\uccb4\uc5d0":76,"\uac1d\uccb4\uc758":76,"\uac70\uc758":[75,76],"\uac71\uc815\ud558\uc9c0":82,"\uac78\ub9ac\ub294":76,"\uac80\uc740":[79,81],"\uac80\uc740\ube14\ub85d":16,"\uac83":[75,76,78],"\uac83\uacfc":[80,82],"\uac83\ub4e4\uc774":81,"\uac83\ub4e4\uc774\ub2e4":76,"\uac83\ubcf4\ub2e4":79,"\uac83\uc5d0":[75,77,78],"\uac83\uc5d0\ub9cc":75,"\uac83\uc740":[76,77,78,79,80,82],"\uac83\uc744":79,"\uac83\uc774":[75,76,79,80],"\uac83\uc774\uae30":78,"\uac83\uc774\ub2e4":[75,76,77,78,79,80,81,82],"\uac83\uc778\ub2e4":80,"\uac83\ucc98\ub7fc":[77,80],"\uac8c\uc784":[75,76,77,78,81,82],"\uac8c\uc784\ub3c4":[75,82],"\uac8c\uc784\ub9cc\uc758":82,"\uac8c\uc784\uc5d0\uc11c\uc758":79,"\uac8c\uc784\uc5d0\uc120":77,"\uac8c\uc784\uc5d4\uc9c4":75,"\uac8c\uc784\uc5d4\uc9c4\uc774\ub098":75,"\uac8c\uc784\uc740":[75,78,81],"\uac8c\uc784\uc744":[75,78,82],"\uac8c\uc784\uc758":[75,78],"\uac8c\uc784\uc774":[75,77,78,80,81,82],"\uac8c\uc784\uc774\ub098":82,"\uac8c\uc784\uc774\ub2e4":81,"\uac8c\uc784\uc774\ub77c\uace0":78,"\uac8c\uc784\uc774\ubbc0\ub85c":76,"\uac8c\uc784\ud310":83,"\uac8c\uc784\ud310\uc740":81,"\uac8c\uc784\ud310\uc744":81,"\uacaa\uc5b4":82,"\uacb0\uacfc":[76,77,78],"\uacb0\uacfc\uac00":77,"\uacb0\uacfc\ub294":[78,81],"\uacb0\uacfc\ub97c":77,"\uacb0\uacfc\ubb3c\ub4e4\uc744":76,"\uacb0\uacfc\ubb3c\uc740":76,"\uacb0\ub860\uc774":82,"\uacb0\uc815\ud560":76,"\uacbd\uc6b0":[76,78,79,80],"\uacbd\uc6b0\ub97c":78,"\uacbd\uc6b0\uc758":82,"\uacbd\ud5d8\uc774":78,"\uacc4\uc0b0\ub9cc":76,"\uacc4\uc0b0\uc744":80,"\uacc4\uc0b0\ud558\uae30":77,"\uacc4\uc0b0\ud574\uc57c":81,"\uacc4\uc18d":[75,76,78],"\uace0":81,"\uace0\uae09":75,"\uace0\ub824\ub418\uc5c8\uc744":82,"\uace0\ub824\ub418\uc9c0":77,"\uace0\ub824\ud558\uc5ec":76,"\uace0\ub974\ub294":81,"\uace0\uc815":77,"\uace0\uc815\ub418\uc5b4":[76,79],"\uace0\uc815\ub41c":76,"\uace0\uc815\ub420":77,"\uace0\uc815\uc2dc\ucf1c":77,"\uace0\uc815\uc2dc\ud0a4\ub294":77,"\uace0\uc815\uc2dc\ud0a8\ub2e4":76,"\uace0\uc815\ud558\ub294":77,"\uacf5\uac04":76,"\uacf5\uac04\uc0c1\uc5d0\uc11c\uc758":75,"\uacf5\uac04\uc744":[76,80],"\uacf5\uc774\ub098":77,"\uacfc":[76,80],"\uad00\ub828":75,"\uad6c\uc131":76,"\uad6c\uc131\ub41c\ub2e4":75,"\uad6c\uc131\ud560":75,"\uad6c\uc2dd\uc774\uace0":75,"\uad6c\uc5ed\uc744":76,"\uad6c\uc5ed\uc758":76,"\uad6c\uccb4\uc801\uc778":[78,80],"\uad6c\ud604\ud55c":[81,82],"\uad6c\ud604\ud55c\ub2e4\uba74":81,"\uad6c\ud604\ud560":82,"\uaddc\uce59":[78,82],"\uaddc\uce59\uc740":81,"\uaddc\uce59\uc744":[81,82],"\uaddc\uce59\uc774":82,"\uadf8":[75,76,77,79,80,81],"\uadf8\uac83\uc740":79,"\uadf8\uac83\uc774":[78,82],"\uadf8\uac83\uc774\ub2e4":76,"\uadf8\ub2e4\uc9c0":76,"\uadf8\ub798\uc11c":[75,76,77],"\uadf8\ub798\ud53d":75,"\uadf8\ub798\ud53d\uc744":75,"\uadf8\ub7ec\ub098":[77,78,82],"\uadf8\ub7ec\uba74":[75,77,81],"\uadf8\ub7ec\ubbc0\ub85c":[76,77,80,82],"\uadf8\ub7f0\uac00":78,"\uadf8\ub807\ub2e4\uba74":[76,77],"\uadf8\ub807\uc9c0":80,"\uadf8\ub824\uc9c0\uace0":76,"\uadf8\ub824\uc9c0\ub294":76,"\uadf8\ub824\uc9c4\ub2e4":81,"\uadf8\ub9ac\uace0":[75,76,77],"\uadf8\ub9ac\uae30":75,"\uadf8\ub9ac\ub294":[76,79],"\uadf8\ub9b0\ub2e4":[79,80],"\uadf9\uc18c\uc218\ub9cc\uc744":82,"\uae30\ub2a5":77,"\uae30\ub2a5\uc744":[76,77],"\uae30\ub85d\ud574\uc57c":80,"\uae30\ubc18":76,"\uae30\ubc18\uc73c\ub85c":76,"\uae30\ubc18\ud558\uc600\uae30":75,"\uae30\uc874\uc758":82,"\uae38\uc774\ub97c":76,"\uae4c\uba39\uc5b4\uc120":76,"\uae5c\ube61\uac70\ub9ac\ub294":75,"\uaf64":76,"\ub049\uaca8":75,"\ub05d\ub09c":76,"\ub05d\uc774":82,"\ub098":81,"\ub098\uac8c":82,"\ub098\ub220\uc9c8":76,"\ub098\ub294":78,"\ub098\uba74":76,"\ub098\uc544\uc84c\ub2e4":79,"\ub098\uc911\uc5d0":[76,77,78],"\ub098\ud0c0\ub0b8\ub2e4":80,"\ub09c\ub2e4":82,"\ub09c\uc218":82,"\ub09c\uc218\uac00":82,"\ub09c\uc218\uae4c\uc9c0":82,"\ub0ab\ub2e4":75,"\ub0b4\ubd80":[76,78],"\ub0b4\ubd80\ub97c":80,"\ub0b4\ubd80\uc5d0\uc11c":80,"\ub0b4\ubd80\uc600\ub2e4\uba74":80,"\ub0b4\uc5d0\uc11c":[77,81,82],"\ub0b4\uc6a9":76,"\ub0b4\uc6a9\uacfc":76,"\ub0b4\uc6a9\uc740":80,"\ub108\ubb34":77,"\ub118\uac8c":82,"\ub123\ub294\ub2e4\uba74":81,"\ub123\uc73c\uba74":81,"\ub124\ubc88\uc9f8":79,"\ub192\ub2e4\ub294":75,"\ub192\uc774\uae30":76,"\ub204\ub974\ub294":77,"\ub204\ub974\uba74":78,"\ub208\uc0ac\ud0dc":82,"\ub208\uc0ac\ud0dc\ub97c":82,"\ub20c\ub7ec":79,"\ub20c\ub824\uc788\ub294":78,"\ub20c\ub838\ub2e4":78,"\ub20c\ub9ac\uc9c0":78,"\ub20c\ub9b0":78,"\ub294":76,"\ub2a5\ub825\uc774\ub2e4":82,"\ub2e4\ub8e8\uaca0\ub2e4":78,"\ub2e4\ub8e8\uae30":78,"\ub2e4\ub974\uac8c":78,"\ub2e4\ub974\ub2e4":77,"\ub2e4\ub974\uc9c0":79,"\ub2e4\ub978":[76,78,82],"\ub2e4\uc2dc":79,"\ub2e4\uc591\uc131":78,"\ub2e4\uc591\ud788":81,"\ub2e4\uc6e0\ub2e4":82,"\ub2e4\uc74c":[76,79,80,81],"\ub2e4\uc911":77,"\ub2e4\ud589\ud788":75,"\ub2e8":77,"\ub2e8\uacc4\ub85c":78,"\ub2e8\uacc4\ub97c":76,"\ub2e8\uacc4\uc5d0\uc11c\uc758":78,"\ub2e8\uc0c9":76,"\ub2e8\uc21c\ud558\uae30":81,"\ub2e8\uc21c\ud558\ub2e4":[79,80],"\ub2e8\uc21c\ud558\uc9c0\ub9cc":81,"\ub2e8\uc21c\ud788":[78,81],"\ub2e8\uc5b4\ub294":78,"\ub2e8\uc810\uc740":77,"\ub2e8\uc810\uc774":75,"\ub2ec\ub77c\uc9c0\ubbc0\ub85c":77,"\ub2ec\ub77c\uc9c4\ub2e4":81,"\ub2ec\ub77c\uc9c8":78,"\ub2f4\uc558\ub294\ub370":79,"\ub2f9\uc5f0\ud55c":76,"\ub300\ub2e8\ud788":[81,82],"\ub300\ub85c":81,"\ub300\ubcf4\uac8c":75,"\ub300\uc0c1\uc5d0":82,"\ub300\uccb4\ud558\ub294\uac00":76,"\ub300\ud55c":76,"\ub354":[75,78,79,82],"\ub370":76,"\ub370\uc5d0\ub9cc":80,"\ub370\uc774\ud130":[78,81],"\ub370\uc774\ud130\uac00":76,"\ub370\uc774\ud130\ub4e4\uc744":79,"\ub370\uc774\ud130\ub97c":79,"\ub3c4":[76,78],"\ub3c4\uc6c0":78,"\ub3c4\uc6c0\uc774":78,"\ub3c4\uc911":76,"\ub3c4\ud615":75,"\ub3c4\ud615\uc744":79,"\ub3d9\uae30\ubd80\uc5ec":78,"\ub3d9\uc2dc\uc5d0":75,"\ub3d9\uc77c\ud55c":[76,79,80],"\ub3d9\uc77c\ud574\uc57c":80,"\ub3d9\uc791\ud558\ub3c4\ub85d":78,"\ub3d9\uce58\uad00\uacc4\ub77c\ub294":75,"\ub418\uae30":78,"\ub418\ub3cc\uc544\uac00\uc57c":76,"\ub418\uc5b4\uc57c\ub9cc":77,"\ub418\uc5c8\ub294\uc9c0":78,"\ub418\uc9c0":79,"\ub41c":[78,80],"\ub41c\ub2e4":[75,76,77,78,79,80,81,82],"\ub41c\ub2e4\uba74":82,"\ub420":[75,77,79],"\ub450":[79,80,81,82],"\ub450\uaed8":79,"\ub450\uaed8\ub9cc":80,"\ub450\ub294":[79,80],"\ub450\ub294\ub370":76,"\ub450\uba74":77,"\ub450\ubc88\uc9f8":[78,79],"\ub450\uc5c8\ub2e4":76,"\ub458":75,"\ub458\uc9f8":[75,80],"\ub4a4\uc5d0":78,"\ub4b7\ubd80\ubd84\uc5d0":77,"\ub4e4\uba74":79,"\ub4e4\uc5b4\uc11c":76,"\ub4e4\uc744":80,"\ub4f1":[75,78],"\ub4f1\uc740":82,"\ub4f1\uc758":75,"\ub514\ub809\ud1a0\ub9ac\uc5d0":76,"\ub514\uc2a4\ud50c\ub808\uc774":75,"\ub514\uc790\uc778\ud560":79,"\ub51c\ub808\ub9c8\uac00":75,"\ub51c\ub808\ub9c8\ub97c":75,"\ub51c\ub808\uc774":77,"\ub530\ub77c":[77,81],"\ub530\uc62c":79,"\ub530\uc838\uc11c":79,"\ub54c":[76,77,79,81,82],"\ub54c\ub9c8\ub2e4":79,"\ub54c\ub9cc":76,"\ub54c\ubb38\uc5d0":[75,76,77,78,81],"\ub54c\ubb38\uc774\ub2e4":[76,77,78,80,81],"\ub54c\uc758":79,"\ub610\ub294":[75,79,82],"\ub610\ud55c":[77,81],"\ub611\uac19\uc740":78,"\ub73b\uc774\ub2e4":78,"\ub77c\uace0":[78,82],"\ub77c\ub294":[76,82],"\ub77c\uc774\ube0c\ub7ec\ub9ac\uc774\uae30":75,"\ub77c\uc774\ube0c\ub7ec\ub9ac\uc774\ub2e4":75,"\ub80c\ub354\ub9c1":79,"\ub85c\uc9c1\uc740":79,"\ub85c\uc9c1\uc744":[76,78],"\ub85c\uc9c1\uc774":77,"\ub8e8\ud2b82":77,"\ub97c":[76,77,78,80],"\ub9c8\ub77c":82,"\ub9c8\uc6b0\uc2a4":[75,78,80],"\ub9c8\uc6b0\uc2a4\uac00up":80,"\ub9c8\uc9c0\ub9c9\uc5d0":76,"\ub9c8\uc9c0\ub9c9\uc73c\ub85c":[78,79],"\ub9c8\ucc2c\uac00\uc9c0\uc774\ub2e4":82,"\ub9cc":75,"\ub9cc\ub4dc\ub294":[75,80,82],"\ub9cc\ub4e0":75,"\ub9cc\ub4e0\ub2e4\uace0":75,"\ub9cc\ub4e4":[75,79,82],"\ub9cc\ub4e4\uace0":81,"\ub9cc\ub4e4\uc5b4":[79,81],"\ub9cc\ub4e4\uc5b4\uc11c":82,"\ub9cc\ub4e4\uc5b4\uc57c":80,"\ub9cc\ub4e4\uc5b4\uc9c4":81,"\ub9cc\ub4e4\uc5b4\uc9c4\ub2e4\uba74":82,"\ub9cc\ub4e4\uc5c8\ub2e4":79,"\ub9cc\uc57d":[76,79,80,82],"\ub9cc\uc744":[76,79],"\ub9ce\ub2e4":81,"\ub9ce\uc73c\ubbc0\ub85c":77,"\ub9ce\uc740":81,"\ub9d0\uc774\ub2e4":76,"\ub9d0\ud558\ub294":78,"\ub9d0\ud55c":76,"\ub9d0\ud588\ub2e4":82,"\ub9d0\ud588\ub4ef":76,"\ub9de\ub294\uac00":78,"\ub9de\uc744":76,"\ub9e4\uac1c":79,"\ub9e4\uc6b0":[78,82],"\uba39\ud788\ub294":76,"\uba3c\uc800":[78,79],"\uba54\ubaa8\ub9ac":75,"\uba64\ubc84":76,"\uba85\ub839\uc5b4\uac00":78,"\uba87":77,"\uba87\uba87":[76,78],"\ubaa8\ub2c8\ud130":76,"\ubaa8\ub450":[75,76],"\ubaa8\ub4c8\ub4e4\uc744":76,"\ubaa8\ub4e0":[75,76,78,81,82],"\ubaa9\ub85d\uc740":78,"\ubaa9\ud45c\uac00":75,"\ubab8\uc758":78,"\ubb34\uc5b8\uac00\ub97c":[76,78],"\ubb34\uc5c7\uc744":77,"\ubb34\uc5c7\uc774\ub4e0\uc9c0":82,"\ubb34\uc5c7\uc778\uac00":[77,79,82],"\ubb34\uc5c7\uc778\uc9c0\ub294":79,"\ubb34\uc791\uc704\ub85c":81,"\ubb34\ud55c":76,"\ubb36\uc744":80,"\ubb38\uad6c\uc774\ub2e4":76,"\ubb38\uc790\uc5f4\uc740":76,"\ubb38\uc790\uc5f4\uc774\ub2e4":76,"\ubb38\uc7a5\ub4e4":76,"\ubb38\uc81c\uac00":[76,77],"\ubb38\uc81c\ub97c":81,"\ubb3c\ub860":[75,76,77,78,79],"\ubb54\uac00":80,"\ubbf8\uce58\uac8c":82,"\ubc0f":78,"\ubc14\uafb8\uace0":77,"\ubc14\uafb8\ub294":77,"\ubc14\uafb8\ub294\uac00":79,"\ubc14\uafb8\uba74":81,"\ubc14\uafbc":78,"\ubc14\uafc0":77,"\ubc14\uafd4":79,"\ubc14\ub00c\ub294\uac00":77,"\ubc14\ub00c\ub294\uc9c0\ub97c":77,"\ubc14\ub294":79,"\ubc14\ub85c":[77,82],"\ubc18\ub4dc\uc2dc":[77,78],"\ubc18\ubcf5\ubb38":76,"\ubc18\uc601":78,"\ubc18\ud544\uc218\uc801\uc73c\ub85c":76,"\ubc18\ud658":76,"\ubc18\ud658\ud55c\ub2e4":[76,81],"\ubc1c\uc0dd":76,"\ubc1c\uc0dd\ud558\uba74":76,"\ubc1c\uc0dd\ud55c":76,"\ubc1c\uc804\ub41c":75,"\ubc1c\ud718\ub41c":82,"\ubc29\ubc95":78,"\ubc29\ubc95\uc740":[78,79],"\ubc29\ubc95\uc744":[77,78],"\ubc29\ubc95\uc774\ub2e4":79,"\ubc29\uc2dd":75,"\ubc29\ud5a5\uc73c\ub85c":78,"\ubc29\ud5a5\ud0a4\ub97c":78,"\ubc30\uc5f4\uacfc":81,"\ubc30\uc5f4\uc5d0\uc11c":81,"\ubc30\uc5f4\uc740":81,"\ubc30\uc5f4\uc744":[76,81],"\ubc30\uc5f4\uc774":81,"\ubc30\uc5f4\ucc98\ub7fc":81,"\ubc30\uc6b0\uace0":[78,82],"\ubc30\uc6b0\ub294":[75,76],"\ubc30\uc6b4\ub2e4":78,"\ubc30\ud2c0\uc2ed":75,"\ubc84\ud2bc":81,"\ubc84\ud2bc\ub4e4":83,"\ubc84\ud2bc\ub4e4\uc740":80,"\ubc84\ud2bc\ub4e4\uc744":80,"\ubc84\ud2bc\uc744":[77,80,81],"\ubc84\ud2bc\uc758":80,"\ubc88":77,"\ubc94\uc704\uac00":80,"\ubc95\uc744":81,"\ubcc0\uacbd\ud558\uba74\uc11c":79,"\ubcc0\uc218":[76,79,80],"\ubcc0\uc218\uac00":[76,77,79,80],"\ubcc0\uc218\ub294":[76,79],"\ubcc0\uc218\ub3c4":80,"\ubcc0\uc218\ub4e4\uc744":80,"\ubcc0\uc218\ub4e4\uc774":76,"\ubcc0\uc218\ub85c":[79,80],"\ubcc0\uc218\ub97c":[76,77,79,80],"\ubcc0\uc218\uc640":78,"\ubcc0\uc218\uc758":79,"\ubcc0\uc218\uc774\uace0":79,"\ubcc0\uc218\uc774\ub2e4":79,"\ubcc0\uc704\uac00":77,"\ubcc0\uc704\ub294":77,"\ubcc0\uc704\ub9cc":77,"\ubcc0\ud55c\ub2e4\uba74":79,"\ubcc0\ud560":79,"\ubcf4\uace0":75,"\ubcf4\ub2e4":77,"\ubcf4\uba74":[77,80],"\ubcf4\uc544\ub77c":[78,79,82],"\ubcf4\uc774\ub294":80,"\ubcf4\uc774\uc9c4":81,"\ubcf4\uc778\ub2e4":77,"\ubcf4\uc778\ub2e4\ub294":[75,77],"\ubcf4\uc77c":77,"\ubcf4\uc790":[75,82],"\ubcf4\ud1b5":75,"\ubcf5\uc18c\uc218\uc88c\ud45c\ub97c":75,"\ubcf5\uc7a1\ub3c4\ub294":77,"\ubcf5\uc7a1\ub3c4\ub97c":77,"\ubcf5\uc7a1\ud558\ub2e4":76,"\ubcf5\uc7a1\ud55c":75,"\ubcf8\uaca9\uc801\uc73c\ub85c":79,"\ubcfc\ub9cc":75,"\ubd80":83,"\ubd80\ubd84\uc5d0\uc11c":78,"\ubd80\ubd84\uc73c\ub85c":76,"\ubd80\ubd84\uc758":81,"\ubd80\uc5ec\ud560":82,"\ubd84\uc11d\ud558\uc9c0":77,"\ubd88\uacfc\ud558\ub2e4":78,"\ubd88\uacfc\ud558\ubbc0\ub85c":77,"\ubd88\ub9b4":78,"\ubd88\uc5f0\uc18d\uc801":79,"\ube14\ub85d":81,"\ube14\ub85d\uc744":81,"\ube14\ub85d\uc758":81,"\ube44\uad50\ud558\uc5ec":78,"\ube44\ud45c\uc900":75,"\ube44\ud558\uba74":76,"\ube48\ub3c4\uc5d0":77,"\ube60\ub978":75,"\ube60\ub97c\uae4c":77,"\ube60\uc9c0\uba74":75,"\ube68\uac04":[76,79,81],"\ube68\uac04\ube14\ub85d":16,"\ube68\ub9ac":75,"\ubfcc\uc694\ubfcc\uc694":75,"\uc0ac\uae30\uac00":80,"\uc0ac\ub78c\ub4e4\uc774":82,"\uc0ac\ub78c\uc774":82,"\uc0ac\uc2e4":81,"\uc0ac\uc6a9\ub418\uc5c8\uae30":80,"\uc0ac\uc6a9\ub418\uc5c8\uc9c0\ub9cc":80,"\uc0ac\uc6a9\ub41c":79,"\uc0ac\uc6a9\uc790\uac00":[76,80],"\uc0ac\uc6a9\ud558\uae30":76,"\uc0ac\uc6a9\ud558\ub294":76,"\uc0ac\uc6a9\ud558\uc5ec":76,"\uc0ac\uc6a9\ud560":76,"\uc0ac\uc774\uc5d0\ub294":75,"\uc0ac\uc774\uc758":75,"\uc0ac\uc9c4":75,"\uc0b4\ud3b4\ubcf4\uc790":76,"\uc0bd\uc785\ud558\uba74":76,"\uc0c1\uc138\ud558\uac8c":79,"\uc0c1\uc218":80,"\uc0c1\uc218\ub4e4\uc744":76,"\uc0c1\ud0dc":78,"\uc0c1\ud0dc\ub97c":75,"\uc0c1\ud638\uc791\uc6a9\uc774":78,"\uc0c8\ub85c\uc6b4":[79,81,82],"\uc0c9":[75,76,79],"\uc0c9\uc0c1":[76,79],"\uc0c9\uc0c1\uacfc":76,"\uc0c9\uc0c1\uc740":[76,81],"\uc0c9\uc0c1\uc744":[76,81],"\uc0c9\uc0c1\uc758":81,"\uc0c9\uc774":76,"\uc0dd\uac01\uc77c":80,"\uc0dd\uac01\ud574":82,"\uc0dd\uac01\ud574\ubcf4\uba74":78,"\uc0dd\uac01\ud574\ubd10\ub77c":78,"\uc0dd\uacbc\ub2e4":78,"\uc0dd\uae38":76,"\uc0dd\uc131\ud558\uace0":76,"\uc11c\ub85c":76,"\uc120\uc5b8":76,"\uc120\uc5b8\ub418\uc5b4\uc57c":76,"\uc120\ud0dd\uc801\uc73c\ub85c":75,"\uc120\ud0dd\uc801\uc778":77,"\uc120\ud0dd\uc9c0\ub294":81,"\uc120\ud589\ub418\uc5b4\uc57c":79,"\uc124\uba85\uc774":76,"\uc124\uba85\uc774\uc5c8\ub2e4":76,"\uc124\uba85\ud558\ub294":79,"\uc124\uba85\ud560":81,"\uc124\uc815":75,"\uc131\ubd84":76,"\uc131\ubd84\uc774":76,"\uc138\uace0":81,"\uc138\ud305":78,"\uc148\uc774\ub2e4":76,"\uc18c\uac1c":83,"\uc18c\ub9ac":[75,78],"\uc18c\uc124":82,"\uc18c\uc2a4":[75,76,77,78],"\uc18c\uc2a4\ucf54\ub4dc":76,"\uc18c\uc2a4\ucf54\ub4dc\uac00":76,"\uc18c\uc2a4\ucf54\ub4dc\ub294":76,"\uc18c\uc2a4\ucf54\ub4dc\ub97c":[75,76],"\uc18c\uc2a4\ucf54\ub4dc\uc5d0":76,"\uc18c\uc2a4\ucf54\ub4dc\uc640":75,"\uc18c\uc2a4\ucf54\ub4dc\uc758":76,"\uc18c\uc2a4\ud30c\uc77c\uc5d0":75,"\uc18c\uc2a4\ud30c\uc77c\uc740":75,"\uc18d":76,"\uc18d\ub3c4\ub97c":77,"\uc18d\ub3c4\uc5d0":77,"\uc18d\ub3c4\uc774\ub77c\ub294":77,"\uc18d\uc5d0\ub294":77,"\uc190\uac00\ub77d":78,"\uc190\uc744":75,"\uc218":[75,76,77,78,79,80,81,82],"\uc218\uac00":82,"\uc218\ub294":[77,82],"\uc218\ub3c4":82,"\uc218\ub97c":81,"\uc218\ub9ce\uc740":75,"\uc218\uc815\ud558\ub294":76,"\uc218\uc815\ud55c\ub2e4\uba74":76,"\uc218\uc900":75,"\uc218\ud589\ub418\uc5b4\uc57c":76,"\uc218\ud589\ud55c\ub2e4":76,"\uc21c\ucc28\uc801\uc73c\ub85c":76,"\uc26c\uc6b4":79,"\uc27d\uac8c":[77,79],"\uc27d\ub2e4":[77,78,80],"\uc2a4\ud06c\ub9b0":76,"\uc2b5\ub4dd\ud560":82,"\uc2dc\uac01\uc801":81,"\uc2dc\uac01\ud654":[78,79],"\uc2dc\uac04":[77,78,81],"\uc2dc\uac04\ubcf4\ub2e4":82,"\uc2dc\uac04\uc21c\uc73c\ub85c":76,"\uc2dc\uac04\uc740":77,"\uc2dc\uac04\uc744":[77,82],"\uc2dc\uac04\uc774":[76,82],"\uc2dc\uac04\uc774\ub2e4":82,"\uc2dc\ub3c4\ud574":75,"\uc2dc\uc2a4\ud15c\uc744":78,"\uc2dc\uc2a4\ud15c\uc774":76,"\uc2dc\uc791\ub418\uae30":77,"\uc2dc\uc791\ub420":77,"\uc2dc\uc791\ud558\ub294":77,"\uc2dc\ud589":82,"\uc2dd\uc758":75,"\uc2e0\uacbd":80,"\uc2e4\uc81c\ub85c":76,"\uc2e4\ud589":[76,77,78,81],"\uc2e4\ud589\ub418\uac70\ub098":75,"\uc2e4\ud589\ub418\ub294":[75,77],"\uc2e4\ud589\ub418\uba74":76,"\uc2e4\ud589\ub418\uc57c":77,"\uc2e4\ud589\ub418\uc5b4\uc57c":76,"\uc2e4\ud589\ub418\uc9c0":76,"\uc2e4\ud589\ub41c\ub2e4":76,"\uc2e4\ud589\ub41c\ub2e4\ub294":75,"\uc2e4\ud589\ub428":75,"\uc2e4\ud589\ud558\ub294":75,"\uc2eb\ub2e4\uba74":80,"\uc2ec\ud654":78,"\uc2ec\ud654\ub41c":78,"\uc2f6\ub2e4\uba74":75,"\uc2f6\uc744":76,"\uc368\uc57c":80,"\uc4f0\uba74":76,"\uc4f8":75,"\uc544\ub2c8\ub2e4":78,"\uc544\ub2c8\ub77c":77,"\uc544\ub2c8\ubbc0\ub85c":77,"\uc544\ub2c8\uc9c0\ub9cc":78,"\uc544\ub2cc":[75,76,77,79,80,81],"\uc544\ub2cc\uc9c0\ub97c":78,"\uc544\ub798\ub97c":79,"\uc544\ub798\uc640":79,"\uc544\ub798\ucabd\uc774":76,"\uc544\ub9c8\ub3c4":78,"\uc544\ubb34":82,"\uc544\ubb34\ub798\ub3c4":75,"\uc544\uc2a4\ud0a4\uc544\ud2b8\ub97c":75,"\uc544\uc774\ub514\uc5b4\ub294":79,"\uc544\uc774\ub514\uc5b4\ub97c":79,"\uc544\uc774\ub514\uc5b4\uc640":79,"\uc544\uc774\ub514\uc5b4\ucc98\ub7fc":80,"\uc544\uc9c1":[79,80],"\uc544\uc9c1\ub3c4":[77,80],"\uc548\ub418\ub294":77,"\uc548\ub41c\ub2e4":76,"\uc54a\ub294":[76,77],"\uc54a\ub294\ub2e4":[77,78,79,81],"\uc54a\ub2e4":79,"\uc54a\ub2e4\uba74":80,"\uc54a\uc558\uc9c0\ub9cc":78,"\uc54a\uc73c\uba74":76,"\uc54a\uc744":[76,77],"\uc54c":[76,80,81],"\uc54c\uace0":[77,78],"\uc54c\uace0\ub9ac\uc998\uc73c\ub85c":75,"\uc54c\uace0\ub9ac\uc998\uc758":78,"\uc54c\uace0\ub9ac\uc998\uc774":78,"\uc54c\ub809\uc138\uc774":82,"\uc54c\uc544\ub0b4\uae30":80,"\uc54c\uc544\ub0bc":[76,77],"\uc54c\uc544\uc57c":78,"\uc54c\uc558\ub2e4":77,"\uc54c\uce74\ub178\uc774\ub4dc\uc758":77,"\uc55e\ubd80\ubd84\uc5d0":77,"\uc55e\uc11c":76,"\uc55e\uc11c\uc11c":76,"\uc55e\uc5d0":78,"\uc560\ub2c8\uba54\uc774\uc158":[75,77],"\uc57d\uac04":78,"\uc57d\uac04\uc758":76,"\uc5b4\ub5a4":[76,82],"\uc5b4\ub5a4\uac00":79,"\uc5b4\ub5a8\uae4c":[76,80],"\uc5b4\ub5bb\uac8c":[76,77,79,80,81],"\uc5b4\ub835\uc9c0":76,"\uc5b4\uca0c\ub4e0":78,"\uc5b4\ucc0c\ub410\ub4e0":76,"\uc5b8\uae09\ud558\uaca0\ub2e4":76,"\uc5b8\uae09\ud55c":81,"\uc5b8\uc5b4\uc758":76,"\uc5bc\ub9c8\ub098":77,"\uc5bc\ub9cc\ud07c\uc758":82,"\uc5c5\ub370\uc774\ud2b8":77,"\uc5c5\ub370\uc774\ud2b8\uac00":76,"\uc5c5\ub370\uc774\ud2b8\ub418\uac70\ub098":76,"\uc5c5\ub370\uc774\ud2b8\ub418\uac8c":77,"\uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\ub294\uc9c0\ub97c":77,"\uc5c5\ub370\uc774\ud2b8\ub41c\ub2e4":78,"\uc5c5\ub370\uc774\ud2b8\ud558\ub294":[77,78,80],"\uc5c6\uace0":[78,80],"\uc5c6\uae30":[76,77,78],"\uc5c6\ub294":[75,76,82],"\uc5c6\ub2e4":[77,78,81],"\uc5c6\uc73c\ubbc0\ub85c":[77,80],"\uc5c6\uc774":[76,78],"\uc5d0":76,"\uc5d0\uc11c":82,"\uc5d0\uc120":76,"\uc5d0\ud544\ub85c\uadf8":83,"\uc5d4\uc9c4":75,"\uc5d4\uc9c4\uc5d0\ub3c4":75,"\uc5d4\uc9c4\uc740":75,"\uc5d4\uc9c4\uc744":75,"\uc5d4\uc9c4\uc758":75,"\uc5d4\ud130":75,"\uc5ec\uae30\uc11c":77,"\uc5ec\uae30\uc5d0":76,"\uc5ec\uae30\uc5d0\uc11c":82,"\uc5ec\ub7ec":75,"\uc5ec\ub7ec\uac00\uc9c0":76,"\uc5ec\uc804\ud788":79,"\uc5ec\uc9c0\uac00":81,"\uc5f0\uacb0\uc2dc\ud0a4\uba74\uc11c":82,"\uc5f0\uacb0\uc810\uc774":75,"\uc601\uc5ed":80,"\uc601\uc5ed\uacfc":80,"\uc601\uc5ed\uc744":80,"\uc601\uc5ed\uc774":80,"\uc601\ud5a5\uc744":82,"\uc601\ud654":82,"\uc608\ub97c":[76,79],"\uc608\uc2dc":75,"\uc608\uc2dc\uc774\ub2e4":82,"\uc608\uc678\uc801\uc73c\ub85c":76,"\uc608\uc81c\uc778":76,"\uc608\uce21\ub418\uae30":77,"\uc624\uac8c":76,"\uc624\uac8c\ub054":76,"\uc624\ub2f5\uc774\ub77c\uba74":81,"\uc624\ub2f5\uc77c":81,"\uc624\ub798":76,"\uc624\ub978\ucabd\uc774":76,"\uc624\ube0c\uc81d\ud2b8\uac04":78,"\uc624\ube0c\uc81d\ud2b8\uc758":77,"\uc624\uc9c1":[79,80],"\uc640":[76,77,78,80,82],"\uc640\uc57c":77,"\uc644\ubcbd\ud788":82,"\uc644\uc131\ub418\uc5c8\ub2e4":78,"\uc644\uc804\ud55c":80,"\uc644\uc804\ud788":78,"\uc65c":78,"\uc65c\ub098\ud558\uba74":76,"\uc65c\ub0d0\uba74":78,"\uc65c\ub0d0\ud558\uba74":[77,78],"\uc678\ubd80":75,"\uc678\uc758":76,"\uc694\uc18c":[78,81],"\uc694\uc18c\ub97c":76,"\uc694\uc57d\ud558\uc790\uba74":75,"\uc6a9":80,"\uc6a9\ub3c4\uac00":79,"\uc6a9\uc774\ub2e4":81,"\uc6b0":80,"\uc6b0\ub9ac\uac00":77,"\uc6b0\ub9ac\ub294":[77,78,81,82],"\uc6b0\ub9ac\uc758":82,"\uc6b0\uc120":[76,77,78,79,81],"\uc6c0\uc9c1\uc774\uac8c":77,"\uc6c0\uc9c1\uc774\ub294":77,"\uc6c0\uc9c1\uc778\ub2e4":[77,78],"\uc6c0\uc9c1\uc778\ub2e4\ub294":78,"\uc6c0\uc9c1\uc77c\uae4c":77,"\uc6d0\ub9ac\ub97c":76,"\uc704":79,"\uc704\uce58":[76,79],"\uc704\uce58\uac00":77,"\uc704\uce58\ub97c":76,"\uc704\uce58\uc5d0\uc11c\uc758":80,"\uc704\ud55c":[75,76,80,81],"\uc704\ud574":[76,78,82],"\uc704\ud574\uc11c\ub294":78,"\uc704\ud574\uc120":76,"\uc708\ub3c4\uc6b0":76,"\uc708\ub3c4\uc6b0\uc758":77,"\uc720\ub2c8\ud2f0":75,"\uc720\uc6a9\ud55c":76,"\uc73c\ub85c":[75,76],"\uc740":77,"\uc744":[77,78,81],"\uc74c\uc545":82,"\uc758":75,"\uc758\ubbf8\ub97c":80,"\uc758\ubbf8\ud558\uac8c":[77,80],"\uc758\ubbf8\ud558\uace0":78,"\uc758\ubbf8\ud558\uc9c0\ub294":78,"\uc758\ubbf8\ud55c\ub2e4":[76,77,78],"\uc774":[75,76,77,78,79,80,81,82],"\uc774\uac83\ub4e4\uc740":79,"\uc774\uac83\uc740":[76,77,81],"\uc774\uac83\uc774":[75,76,82],"\uc774\ub294":[77,78],"\uc774\ub2e4":76,"\uc774\ub3d9":83,"\uc774\ub780":75,"\uc774\ub7f0":[75,78],"\uc774\ub807\uac8c":75,"\uc774\ub8e8\uc5b4\uc9c4\ub2e4":[76,79],"\uc774\ub8e8\uc5b4\uc9d0\uc5d0":78,"\uc774\ub97c":77,"\uc774\ub984":78,"\uc774\ub984\uc73c\ub85c\ub294":78,"\uc774\ub984\uc744":78,"\uc774\ub984\uc758":76,"\uc774\ubbf8":82,"\uc774\ubbf8\uc9c0":[77,78,81],"\uc774\ubca4\ud2b8\uac00":[76,78],"\uc774\ubca4\ud2b8\ub4e4\uc740":76,"\uc774\ubca4\ud2b8\ub4e4\uc744":76,"\uc774\ubca4\ud2b8\ub4e4\uc758":76,"\uc774\ubca4\ud2b8\ub97c":[76,78,80],"\uc774\ubca4\ud2b8\uc801":75,"\uc774\ubcc4\uc744":76,"\uc774\uc0c1":[75,77],"\uc774\uc0c1\uc758":78,"\uc774\uc5d0":76,"\uc774\uc6a9\ud558\uba74":77,"\uc774\uc6a9\ud55c":75,"\uc774\uc6a9\ud560":78,"\uc774\uc720\ub294":80,"\uc774\uc720\uc774\ub2e4":[75,78,82],"\uc774\uc804":[77,78],"\uc774\uc804\uacfc":79,"\uc774\uc804\ubd80\ud130":78,"\uc774\uc804\uc5d0\ub294":78,"\uc774\uc804\uc758":[76,78,80],"\uc774\uc81c":[77,78,80,81,82],"\uc774\uc81c\ub294":79,"\uc774\uc820":77,"\uc774\ud574\ud558\ub294":76,"\uc774\ud574\ud560":[78,79],"\uc774\ud574\ud588\ub2e4\uba74":76,"\uc774\ud6c4":76,"\uc774\ud6c4\uc5d0":76,"\uc778\uc790\ub97c":78,"\uc778\ud130\ud398\uc774\uc2a4":78,"\uc778\ud130\ud398\uc774\uc2a4\ub97c":81,"\uc77c\ubc18\uc801\uc73c\ub85c":76,"\uc77c\ubd80":[76,77,78],"\uc77c\ubd80\ubd84":78,"\uc77c\ubd80\uc774\uae30":75,"\uc77c\uc5b4\ub0ac\ub294\uc9c0":80,"\uc77c\uc73c\ud0a4\ub294":82,"\uc77c\uc885\uc758":77,"\uc77c\uce58\ud558\uc9c0":76,"\uc784\ub9c8\ub204\uc5d8":82,"\uc784\uc740":77,"\uc785\ub825":[75,76,77,80],"\uc785\ub825\ubcf4\ub2e4":82,"\uc785\ub825\uc2dc\ud0a4\ub294":82,"\uc785\ub825\uc740":[75,80],"\uc785\ub825\uc744":[78,80],"\uc785\ub825\uc774":[77,78],"\uc785\ub825\uc774\ub098":77,"\uc785\ub825\uc774\ub780":80,"\uc785\ub825\ud558\ub294":78,"\uc785\ubb38\uc7a5\ubcbd\uc774":75,"\uc788\uac8c":[75,76],"\uc788\uace0":79,"\uc788\uae30":78,"\uc788\ub290\ub0d0":78,"\uc788\ub294":[75,76,80,81,82],"\uc788\ub294\ub370":79,"\uc788\ub2e4":[75,76,77,78,79,80,81,82],"\uc788\ub2e4\uace0":76,"\uc788\ub3c4\ub85d":79,"\uc788\uc5b4\uc57c":77,"\uc788\uc73c\uba74":75,"\uc788\uc744\uae4c":[75,76],"\uc788\uc74c":80,"\uc788\uc74c\uc744":[77,78,80],"\uc790\ub3d9\uc801\uc73c\ub85c":[76,77],"\uc790\uc8fc":77,"\uc791\ub3d9":76,"\uc791\ub3d9\ud558\uc9c0":77,"\uc791\uc131\ub418\uc5b4\uc57c":76,"\uc791\uc131\uc740":81,"\uc791\uc131\ud558\ub294":75,"\uc791\uc131\ud55c":75,"\uc791\uc5c5\uc774":76,"\uc791\uc740":[79,80],"\uc7a5":77,"\uc7a5\uc774":77,"\uc7a5\uc810\ub3c4":75,"\uc7a5\uc810\uc740":75,"\uc7a5\uc810\uc744":75,"\uc800\uae09":75,"\uc800\uc7a5\ud574":76,"\uc801\uc6a9\ub418\uc5b4":80,"\uc801\uc808\ud55c":[76,77,78,80],"\uc801\uc808\ud788":76,"\uc804\uc138\uacc4":82,"\uc804\uc5ed":76,"\uc804\uccb4":[76,81],"\uc804\uccb4\ub97c":79,"\uc804\ud600":78,"\uc808\ucc28\uc801\uc73c\ub85c":75,"\uc808\ucc28\uc801\uc774":75,"\uc810\uc218\ub3c4":78,"\uc810\uc740":80,"\uc811\uadfc\uc131\uc774":75,"\uc811\uadfc\ud560":75,"\uc815\ub2f5\uc774\uac70\ub098":81,"\uc815\ub2f5\uc774\ub77c\uba74":81,"\uc815\ub82c\ub41c\ub2e4":76,"\uc815\ub9ac\ub97c":77,"\uc815\ubcf4\ub4e4\uc740":76,"\uc815\ubcf4\ub97c":76,"\uc815\uc0ac\uac01\ud615":80,"\uc815\uc0ac\uac01\ud615\uc744":80,"\uc815\uc218":[79,81],"\uc815\uc2e0\uc5c6\uc774":77,"\uc815\uc758":78,"\uc815\uc911\uc559\uc5d0":76,"\uc815\uc911\uc559\uc73c\ub85c":76,"\uc815\uc911\uc559\uc740":76,"\uc815\uc911\uc559\uc744":76,"\uc815\uc9c0\ub41c":77,"\uc815\ud558\uace0":76,"\uc815\ud558\ub294":79,"\uc815\ud55c\ub2e4":76,"\uc815\ud560":[76,77],"\uc815\ud574\uc57c":76,"\uc815\ud574\uc838\uc57c":76,"\uc815\ud655\ud788\ub294":76,"\uc81c\uacf5\ud558\uae30":75,"\uc81c\uc57d\uc870\uac74":78,"\uc81c\uc678\ud654\uba74":75,"\uc81c\uc791":75,"\uc81c\ud55c\uc744":81,"\uc870\uac74\ubb38\uc774":76,"\uc870\uac74\uc774":76,"\uc870\uc808\ud558\uac8c":79,"\uc870\uc885":83,"\uc874\uc7ac\ud558\uace0":80,"\uc874\uc7ac\ud558\uae30":77,"\uc874\uc7ac\ud568\uc744":80,"\uc885\ub8cc":77,"\uc885\ub8cc\ub418\uac8c":76,"\uc885\ub8cc\ub418\uace0":78,"\uc885\ub8cc\ub418\uc5b4\uc57c":76,"\uc885\ub8cc\ub41c":76,"\uc885\ub8cc\ud558\uace0":76,"\uc885\ub8cc\ud558\ub294":77,"\uc885\ub958\uc758":76,"\uc88b\ub2e4\ub294":75,"\uc88b\uc740":[75,80],"\uc88b\uc744":79,"\uc88c\ud45c":76,"\uc88c\ud45c\ub97c":[78,79],"\uc88c\ud45c\uc5d0":77,"\uc8fc\ub294":75,"\uc8fc\ub85c":76,"\uc8fc\ubaa9\ud574\ub77c":78,"\uc8fc\uc5b4\uc9c4":76,"\uc904\ub4e4\uc740":78,"\uc911":[75,77],"\uc911\uc694\ud55c":82,"\uc990\uae38":78,"\uc99d\uac00\uc2dc\ud0a4\uac70\ub098":80,"\uc99d\uac00\ud558\uace0":81,"\uc9c0\uae08\uae4c\uc9c0":78,"\uc9c0\uae08\uc740":78,"\uc9c0\ub294":80,"\uc9c0\uc2dd\ub9cc\uc73c\ub85c":82,"\uc9c0\uc2dd\ubcf4\ub2e4":82,"\uc9c0\uc2dd\uc5d0":82,"\uc9c0\uc2dd\uc744":82,"\uc9c0\uc5d0":81,"\uc9c0\uc6b0\ub294":75,"\uc9c0\uc810\uc744":80,"\uc9c1\uad00\uc801\uc73c\ub85c":78,"\uc9c1\uad00\uc801\uc778":78,"\uc9c1\uc0ac\uac01\ud615":76,"\uc9c1\uc0ac\uac01\ud615\ub4e4\uc5d0":79,"\uc9c1\uc0ac\uac01\ud615\ub4e4\uc744":79,"\uc9c1\uc0ac\uac01\ud615\uc744":79,"\uc9c1\uc811":79,"\uc9c4\uc9dc":78,"\uc9c4\ud589\ub420":76,"\uc9c8":81,"\uc9c8\ub9b0\ub2e4\uba74":75,"\uc9d1\uc911\ud558\uba74":75,"\uc9dc\uc99d\ub0a0":81,"\ucc28\uc774\uc810\uc774":78,"\ucc28\uc774\uc810\uc774\ub2e4":78,"\ucc29\uc624\ub97c":82,"\ucc38\uace0":[76,77,78,79,80,81],"\ucc3d\uc758\uc801\uc778":82,"\ucc3e\uc544\ub0b4\uba74":77,"\ucc3e\uc744":79,"\ucc44\ub85c":78,"\ucc44\uc6b0\ub294":76,"\ucc98\ub9ac":[75,76,78],"\ucc98\ub9ac\uac00":[76,77],"\ucc98\ub9ac\ub294":75,"\ucc98\ub9ac\ub97c":79,"\ucc98\ub9ac\ub9cc\uc774":80,"\ucc98\ub9ac\uc758":76,"\ucc98\ub9ac\ud558\uace0":76,"\ucc98\ub9ac\ud558\ub294":80,"\ucc98\ub9ac\ud558\ub294\uc9c0\ub294":77,"\ucc98\ub9ac\ud558\ub824\uba74":81,"\ucc98\ub9ac\ud560":76,"\ucc9c\uc7ac\uc131\uc774\ub780":82,"\uccab\ubc88\uc9f8":79,"\uccab\uc9f8":[75,80],"\uccab\uc9f8\ub294":78,"\uccab\uc9f8\ub85c":76,"\uccb4\ud06c\ud558\ub294":76,"\ucd08\uae30\ud654\ub418\uac70\ub098":76,"\ucd08\uae30\ud654\ub41c\ub2e4":76,"\ucd08\uae30\ud654\ub428\uc744":77,"\ucd08\ub85d":76,"\ucd1d":79,"\ucd5c\ub300":79,"\ucd5c\ub300\uac12\uc774":79,"\ucd5c\ub300\ud55c":75,"\ucd5c\uc18c":76,"\ucd94\uac00":77,"\ucd94\uac00\ub418\uba74":81,"\ucd94\uac00\ub418\uc5c8\uace0":77,"\ucd94\uac00\ub418\uc5c8\ub2e4":[77,80],"\ucd94\uac00\ub41c":76,"\ucd94\uac00\ub41c\ub2e4":78,"\ucd94\uac00\uc801\uc73c\ub85c":78,"\ucd94\uac00\uc801\uc778":[76,77,78,79],"\ucd94\uac00\ud558\uac70\ub098":76,"\ucd94\uac00\ud558\ub294":[76,78],"\ucd94\uac00\ud558\ub824":81,"\ucd94\uac00\ud574\ubcf4\uc790":78,"\ucd94\uac00\ud574\uc57c":77,"\ucd9c\ub825":[75,78,79,81,83],"\ucd9c\ub825\ub418\uac8c":76,"\ucd9c\ub825\ub418\ub294":76,"\ucd9c\ub825\ub420":79,"\ucd9c\ub825\uc6a9\uc774\ub2e4":80,"\ucd9c\ub825\uc73c\ub85c":75,"\ucd9c\ub825\uc740":[75,78],"\ucd9c\ub825\uc744":[76,77,80],"\ucd9c\ub825\uc758":78,"\ucd9c\ub825\uc774":82,"\ucd9c\ub825\ud558\uae30":76,"\ucd9c\ub825\ud558\ub294":[76,78,79,81],"\ucd9c\ub825\ud55c\ub2e4":81,"\ucd9c\ub825\ud574\uc57c":81,"\ucda9\ub3cc":75,"\ucda9\ubd84\ud788":79,"\uce58\uace4":76,"\uce5c\uc219\ud55c":76,"\uce60\ud558\uae30":75,"\uce78\ud2b8\ub294":82,"\uce94\ubc84\uc2a4":[76,79],"\uce94\ubc84\uc2a4\ub97c":76,"\uce94\ubc84\uc2a4\uc5d0":76,"\uce94\ubc84\uc2a4\uc758":76,"\ucea1\uc158\uc5d0":76,"\ucee4\uc9c0\uac8c":82,"\ucee8\ud150\uce20":78,"\ucef4\ud4e8\ud130\ub294":82,"\ucef4\ud4e8\ud130\ub9c8\ub2e4":77,"\ucef4\ud4e8\ud130\uc5d0\uac8c":82,"\ucf54\ub4dc":[76,77,78,79,80,81],"\ucf54\ub4dc\uac00":76,"\ucf54\ub4dc\ub97c":75,"\ucf54\ub4dc\uc640":76,"\ucf54\ub529\ud558\uac8c":75,"\ucf54\ub529\ud574":75,"\ucf58\uc194":75,"\ucf58\uc194\uc5d0\uc11c":75,"\ud06c\uac8c":[79,81],"\ud06c\uace0":76,"\ud06c\uae30":76,"\ud06c\uae30\uac00":76,"\ud06c\uae30\ub97c":[76,81],"\ud06c\uae30\uc640":76,"\ud06c\ub2e4\ub294":82,"\ud06c\uba74":77,"\ud070":[75,78,79,80],"\ud074\uae4c":82,"\ud074\ub9ad":80,"\ud074\ub9ad\uc774":80,"\ud074\ub9ad\ud588\ub2e4":80,"\ud07c\uc744":76,"\ud0a4":78,"\ud0a4\ub294":78,"\ud0a4\ub4e4\ub3c4":78,"\ud0a4\ub97c":78,"\ud0a4\ubcf4\ub4dc":[75,78,79],"\ud0a4\ubcf4\ub4dc\uac00down\ub41c":80,"\ud0a4\ubcf4\ub4dc\uc5d0":78,"\ud0a4\uc758":78,"\ud0c4\ucc3d\uc5d0\uc11c":79,"\ud14c\ub450\ub9ac\ub97c":[79,81],"\ud14c\ud2b8\ub9ac\uc2a4\ub97c":82,"\ud14d\uc2a4\ud2b8":[76,83],"\ud14d\uc2a4\ud2b8\uac00":[76,79],"\ud14d\uc2a4\ud2b8\ub294":76,"\ud14d\uc2a4\ud2b8\ub97c":[76,77,79],"\ud14d\uc2a4\ud2b8\uc758":[76,77],"\ud14d\uc2a4\ud2b8\uc774\ub2e4":79,"\ud1b5\ud574":80,"\ud22c\uc790\ud588\uc744\uae4c":82,"\ud234\uc774":75,"\ud29c\ud1a0\ub9ac\uc5bc":[16,81],"\ud29c\ud1a0\ub9ac\uc5bc\uc740":82,"\ud2b8\ub9ac\uac70":78,"\ud2b8\ub9ac\uac70\ub418\uba74":76,"\ud2b9\uc131":[76,81],"\ud2b9\uc131\uc774":82,"\ud2b9\uc131\uc774\ub2e4":82,"\ud2b9\uc774\ud55c":80,"\ud2b9\uc815":[76,77,80],"\ud2b9\uc815\ud55c":80,"\ud30c\uc774\uac8c\uc784":[76,81],"\ud30c\uc774\uac8c\uc784\uc740":[75,76],"\ud30c\uc774\uac8c\uc784\uc744":75,"\ud30c\uc774\uac8c\uc784\uc758":[75,76,77,82],"\ud30c\uc774\uac8c\uc784\uc774":[75,76],"\ud30c\uc774\uc36c\uc5d0":75,"\ud30c\uc774\uc36c\uc758":[75,76],"\ud30c\uc77c":75,"\ud30c\uc77c\ub85c":81,"\ud30c\uc77c\uc744":[75,76],"\ud30c\uc77c\uc774\ub098":75,"\ud30c\uc9c0\ud2b8\ub178\ud504\uac00":82,"\ud310\ub2e8\ud558\ub294":78,"\ud310\uc815":80,"\ud3ec\ud568":82,"\ud3ed\ub113\uc740":82,"\ud3f0\ud2b8":76,"\ud3f0\ud2b8\ub97c":76,"\ud3f0\ud2b8\uc640":76,"\ud479":75,"\ud48d\uc131\ud558\uac8c":78,"\ud504\ub85c\uadf8\ub798\uba38\uac00":75,"\ud504\ub85c\uadf8\ub798\uba38\ub294":75,"\ud504\ub85c\uadf8\ub798\ubc0d":76,"\ud504\ub85c\uadf8\ub798\ubc0d\uacfc":82,"\ud504\ub85c\uadf8\ub798\ubc0d\uc740":82,"\ud504\ub85c\uadf8\ub798\ubc0d\uc758":82,"\ud504\ub85c\uadf8\ub7a8":[76,77],"\ud504\ub85c\uadf8\ub7a8\uacfc":75,"\ud504\ub85c\uadf8\ub7a8\uc5d0":81,"\ud504\ub85c\uadf8\ub7a8\uc5d0\uc120":78,"\ud504\ub85c\uadf8\ub7a8\uc740":[81,82],"\ud504\ub85c\uadf8\ub7a8\uc744":[76,77,79,82],"\ud504\ub85c\uadf8\ub7a8\uc758":[75,76,78],"\ud504\ub85c\uadf8\ub7a8\uc774":79,"\ud504\ub85c\uc81d\ud2b8":77,"\ud504\ub85c\uc81d\ud2b8\uac00":[76,78],"\ud504\ub85c\uc81d\ud2b8\ub294":[76,77,78],"\ud504\ub85c\uc81d\ud2b8\ub85c":76,"\ud504\ub85c\uc81d\ud2b8\uc5d0\uc11c":76,"\ud504\ub85c\uc81d\ud2b8\uc640":78,"\ud504\ub85c\uc81d\ud2b8\uc740":75,"\ud504\ub85c\uc81d\ud2b8\uc758":[76,77,78],"\ud504\ub85c\uc81d\ud2b8\uc774\uba70":76,"\ud504\ub864\ub85c\uadf8":83,"\ud504\ub864\ub85c\uadf8\uc5d0\uc11c":81,"\ud504\ub9b0\ud2b8":80,"\ud50c\ub808\uc774":[78,82],"\ud53c\ud0c0\uace0\ub77c\uc2a4":77,"\ud544\uc218\uc870\uac74\uc774":78,"\ud544\uc218\uc870\uac74\uc774\uae30":78,"\ud544\uc694":[75,76,81],"\ud544\uc694\ub85c":76,"\ud544\uc694\ud558\uac8c":80,"\ud544\uc694\ud558\ub2e4":[76,78],"\ud544\uc694\ud560":77,"\ud558\uaca0\ub2e4":79,"\ud558\uace0":81,"\ud558\uae30":[76,78],"\ud558\uae30\ub9cc":79,"\ud558\ub098":75,"\ud558\ub098\ub85c":80,"\ud558\ub098\ub9cc":81,"\ud558\ub098\uc758":[75,76,77,79,82],"\ud558\ub098\uc774\ub2e4":77,"\ud558\ub294":[76,77,80],"\ud558\ub294\ub370":80,"\ud558\ub294\uc9c0":76,"\ud558\ub2e4":75,"\ud558\uba74":[76,77,79],"\ud558\uc580":79,"\ud558\uc580\uc0c9":76,"\ud558\uc600\ub2e4":79,"\ud558\uc9c0\ub9cc":[75,76,77,79,80,82],"\ud55c":[77,80,82],"\ud55c\uad6d\uc5b4":16,"\ud55c\ub2e4":[76,77,78,79,80,81],"\ud55c\ubc88\ub9cc":76,"\ud55c\ubc88\ucbe4\uc740":75,"\ud560":[76,77,79,82],"\ud560\uae4c":79,"\ud560\ub2f9\ub41c":76,"\ud568\uc218":[76,77],"\ud568\uc218\uac00":[76,77],"\ud568\uc218\ub294":[76,77,80,81],"\ud568\uc218\ub4e4\uacfc":75,"\ud568\uc218\ub4e4\uc740":[75,76],"\ud568\uc218\ub4e4\uc744":[75,76],"\ud568\uc218\ub4e4\uc774":75,"\ud568\uc218\ub85c":75,"\ud568\uc218\ub97c":[76,79],"\ud568\uc218\ubcf4\ub2e4":77,"\ud568\uc218\uc5d0\uc120":79,"\ud568\uc218\uc640":[76,77],"\ud568\uc218\uc758":76,"\ud568\uc218\uc774\uae30":76,"\ud568\uc218\uc774\ub2e4":77,"\ud568\uc218\ud654":78,"\ud568\uc218\ud654\ub97c":79,"\ud56d\uc0c1":[75,76,78,80],"\ud574":75,"\ud574\uacb0\ud560":75,"\ud574\ub2f9":78,"\ud574\ub2f9\ub418\ub294":77,"\ud574\uc11c":81,"\ud574\uc57c":[76,79],"\ud5f7\uac08\ub9ac\uba74":76,"\ud604\uc7ac":79,"\ud615\uc2dd\uc5d0":76,"\ud615\uc2dd\uc744":76,"\ud615\uc2dd\uc774":76,"\ud638\ucd9c\ub418\ub294\ub370":76,"\ud638\ucd9c\ub418\uba74":76,"\ud638\ucd9c\ub418\uc5b4\uc57c":76,"\ud638\ucd9c\ub41c\ub2e4":76,"\ud638\ud658\uc131":75,"\ud654\ub824\ud55c":76,"\ud654\uba74":[76,79],"\ud654\uba74\uacfc":76,"\ud654\uba74\ubcf4\ub2e4\ub294":77,"\ud654\uba74\ubcf4\ud638\uae30\ucc98\ub7fc":77,"\ud654\uba74\uc744":[75,77],"\ud654\uba74\uc758":76,"\ud655\uc2e4\ud558\ub2e4":77,"\ud655\uc778\ud558\ub294":[75,79],"\ud655\uc778\ud558\ub77c":80,"\ud655\uc778\ud558\uba74":[79,80],"\ud655\uc778\ud560":[77,78,79],"\ud655\uc778\ud574\uc57c":78,"\ud655\uc815\ub41c\ub2e4\uba74":76,"\ud658\uacbd":75,"\ud658\uacbd\uacfc":75,"\ud658\uacbd\uc5d0\uc11c":75,"\ud658\uacbd\uc5d0\uc11c\uc758":75,"\ud658\uacbd\uc6a9":75,"\ud658\uacbd\uc740":75,"\ud65c\ub3d9\uc774\ub2e4":82,"\ud65c\uc131\ud654\ub418\uba74":80,"\ud65c\uc6a9\ud558\uace0":82,"\ud65c\uc6a9\ud55c":75,"\ud65c\uc6a9\ud574":75,"\ud69f\uc218\ub97c":77,"\ud6a8\uacfc\uac00":[80,82],"\ud6a8\uacfc\ub97c":81,"\ud6a8\uacfc\uc74c\uc744":81,"\ud6c4":[75,78],"\ud6e8\uc52c":79,"\ud765\ubbf8\ub85c\uc6b4":82,"always\ubb38":[76,77],"always\ubb38\uacfc":79,"always\ubb38\uc5d0":[76,77],"always\ubb38\uc5d0\uc11c":76,"always\ubb38\uc758":77,"always\ubb38\uc774":77,"b\u00e9zier":30,"b\uac12":76,"blit\uc774":76,"blit\ud568\uc218\ub294":76,"boolean":[25,28,32,33,39,56,64],"break":[18,30,33,46,54,62,88],"byte":[10,15,17,18,20,22,23,28,29,31,37,38,42,43,44,46,51],"c\ub85c":75,"case":[16,18,20,23,24,28,29,31,33,37,38,40,43,44,51,57,58,64,68,70,72,73,74,84,85,87,88],"catch":[44,64,89],"center\ub77c\ub294":76,"char":[1,7,10,28,29,51],"class":[0,16,17,19,20,26,29,31,32,35,37,38,45,51,57,61,62,66,84,86,89],"collidepoint\ub97c":80,"const":[1,7,10,70],"cui\uac00":75,"cui\uace0":75,"cui\ud658\uacbd\uc5d0\uc11c\ub9cc":76,"default":[1,15,18,20,22,23,24,25,26,28,29,31,33,35,37,38,40,43,44,45,48,50,51,53,54,56,58,59,63],"do":[9,16,18,19,20,22,23,24,25,26,27,29,32,35,41,42,44,45,50,51,56,57,58,59,60,61,63,64,65,67,68,69,74,85,86,87,88,89],"drawbuttons\uc5d0":80,"drawhp\ub77c\ub294":79,"else\ubb38\uc740":77,"event\ubb38":[76,78],"event\ubb38\uc5d0":80,"event\ubb38\uc5d0\uc11c":79,"event\ubb38\uc744":79,"event\ubb38\uc774":78,"export":[0,11,20,38,42,43,51],"fill\ud568\uc218\ub098":76,"final":[22,26,28,29,42,44,51,58,61,62,71,84,85,86,89],"float":[1,17,19,20,24,29,30,32,35,36,38,40,45,48,50,54,56,65],"fps\uac00":77,"fps\uac12\uc774":77,"fps\ub294":77,"fps\ub300\ub85c":77,"function":[0,1,8,9,10,13,16,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,35,37,38,39,40,41,43,44,45,46,47,48,49,50,51,52,53,54,56,57,58,60,61,63,64,66,67,68,69,70,72,73,84,85,87,88,89],"g\uac12":76,"gui\uac00":80,"gui\ub97c":76,"gui\uc5d0\uc11c\uc758":80,"gui\uc774\ubbc0\ub85c":76,"gui\uc774\uc9c0\ub9cc":81,"gui\uc784\uc744":76,"gui\ud658\uacbd\uc5d0\uc11c":76,"header\uc5d0\uc120":76,"header\uc758":78,"hp\ub294":[79,81],"hp\ub97c":[79,80],"hp\ubc14":83,"hp\uc744":79,"hp\uc758":79,"import":[1,11,16,18,22,24,25,26,27,28,29,30,32,34,36,38,39,44,49,52,53,59,61,62,63,64,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,86,89],"import\ud558\ub294":76,"in\ubb38\uc744":76,"initial\ubb38":76,"initial\ubb38\uc5d0":76,"initial\ubb38\uc758":77,"input\ud568\uc218\ub97c":76,"input\ud568\uc218\uc640\ub294":76,"int":[1,2,3,4,5,6,7,8,9,10,12,13,17,18,20,23,24,25,28,29,30,32,33,35,36,37,41,42,44,45,47,48,50,51,55,56,71,72,73,79,80,81],"it\uc744":81,"k_\uc2dc\ub9ac\uc988\uc774\ub2e4":78,"kewdown\uc740":78,"key\ub294":78,"keydown\uac00":80,"keyup\uc774\ub77c\ub294":78,"l_f4\ub4f1\uc774":78,"locals\ub85c\ubd80\ud130":78,"long":[7,25,27,29,35,38,40,44,50,53,63,64,84,88],"main\ud568\uc218\ub97c":79,"main\ud568\uc218\uc5d0":79,"mousebuttonup\uc774":80,"mytext\uac1d\uccb4\uc758":76,"mytext\ub77c\ub294":76,"mytextarea\ub294":76,"mytextarea\ub77c\ub294":76,"mytextfont\uac1d\uccb4\uc758":76,"mytextfont\ub77c\ub294":76,"name\uc5d0\uc11c":78,"new":[2,3,4,5,6,7,8,9,12,13,14,17,18,20,21,22,23,25,26,28,29,30,31,32,33,35,36,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,54,55,56,58,59,61,62,63,64,65,69,71,72,73,74,84,85,86,87,89],"null":[1,2,3,4,5,6,7,8,9,10,12,15,28],"play\ub77c\ub294":78,"play\ud55c\ub2e4":78,"pos\ub294":80,"print\ud568\uc218\ub098":76,"public":[26,86,89],"quit\uac19\uc740":76,"quit\ub77c\ub294":76,"r\uac12":76,"return":[1,2,3,4,5,6,7,8,9,10,12,13,14,15,17,18,19,20,22,23,24,25,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,62,63,64,65,66,68,71,72,73,79,80,81,84,85,86,87,88,89],"short":[25,63,65,84],"statement\uc5d0":77,"statement\uc5d0\uc11c":77,"static":[47,48,50,69],"super":[26,35,48,64],"switch":[22,23,29,44,50,59,89],"sys\ub294":76,"throw":[40,62,64],"tick\ud568\uc218\ub294":77,"true":[2,3,4,5,6,7,8,10,12,13,14,17,18,19,20,22,23,24,25,26,27,28,29,31,32,33,35,36,37,38,39,40,41,44,45,46,47,50,51,53,56,57,58,60,64,66,68,69,70,71,72,73,76,77,78,79,80,81,89],"try":[25,27,29,32,44,51,58,61,62,63,64,65,67,84,85,86,89],"ttf\ud30c\uc77c\ub85c":76,"ttf\ud655\uc7a5\uc790\ub97c":76,"update\ud568\uc218\uac00":76,"void":[1,9,13],"while":[0,17,19,22,24,25,28,30,31,32,38,39,40,42,43,44,49,50,51,54,57,62,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,87,89],"world\uac00":[77,78],"world\ub294":77,"world\uc758":77,"x\uac12\uacfc":80,"x\uc88c\ud45c\uac00":76,"y\uac12\uc744":80,"y\uc131\ubd84\uc744":76,"y\uc88c\ud45c\uac00":76,A:[1,5,8,9,12,13,15,16,17,18,20,22,23,24,25,26,29,30,32,33,35,38,39,40,42,43,45,46,49,50,51,53,54,56,58,63,64,85,86,89],AND:[18,33],ANDing:84,AS:18,And:[22,32,58,62,66,68,69,71,74,84,85,89],As:[24,29,35,38,39,44,51,52,56,57,58,64,65,67,68,84,85,86,88,89],At:[26,31,63,64,65],BE:18,BUT:18,BY:18,Be:[22,23,24,28,44,65],Being:63,But:[26,39,43,51,58,62,64,65,68,69,71,72,74,85,87,88],By:[23,25,29,31,35,39,43,44,51,53,54,56,57,61,62,64,85,86],FOR:18,For:[1,17,18,20,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,40,42,43,44,45,46,47,48,49,50,51,53,56,57,60,61,62,63,64,65,67,68,71,84,85,86,88],IF:18,IN:[18,27],IS:[18,27],If:[6,10,13,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,56,58,59,60,61,62,63,64,65,67,68,69,71,72,73,74,84,85,87,88,89],In:[17,18,22,23,27,29,30,31,32,33,36,37,39,40,43,44,51,54,57,58,59,61,62,63,64,65,68,70,72,84,85,86,87,88,89],Is:[37,50,63],It:[2,13,16,17,18,19,20,23,25,26,28,29,31,32,33,36,37,38,40,42,43,44,47,50,51,53,54,56,57,58,59,60,61,63,64,65,66,68,69,72,73,84,86,87,88,89],Its:34,NO:18,NOT:[18,37,40,56],No:[1,3,6,7,9,12,33,44,45,50,51,67,70],Not:[23,25,41,51,60,63,64,65,69,70],OF:18,ON:18,OR:[18,29,35],ORed:38,ORing:33,Of:[57,69,70,71],On:[1,2,3,4,5,6,7,8,9,10,18,23,25,26,37,40,44,46,50,51,56,63,66,84],One:[11,19,32,62,63,65,84],Or:[26,44,64,74],SUCH:18,THE:18,TO:[18,32],That:[29,56,58,62,63,64,67,68,69,70,71,73,74,84],The:[1,2,3,4,5,6,7,8,9,10,12,13,15,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,59,60,61,63,65,66,68,71,84,87,88],Then:[11,22,24,58,62,64,65,68,71,73,84,85,86,88,89],There:[18,19,20,22,23,24,25,26,27,28,30,32,33,37,39,45,46,50,51,57,58,59,60,62,64,65,69,70,72,73,85],These:[20,22,23,24,26,29,32,37,39,44,45,47,48,50,51,52,53,56,58,59,62,64,65,84,85,87],To:[22,23,25,28,30,33,35,37,38,39,40,41,42,43,47,50,54,57,58,65,87,88],Will:[3,5,6,7,12,32,38,54],With:[26,37,51,62,63,65,84,85,87,88],_:33,__copy__:35,__dict__:25,__file__:[26,58,66],__init__:[32,45,50,57,58,62,64,66,86,87,88,89],__main__:[66,71,72,73,79,80,81,85,89],__name__:[66,71,72,73,79,80,81,85,89],__new__:[29,45],__tags__:53,_camera:18,_default_lay:50,_freetyp:[0,11],_index:1,_layer:50,_pixels_address:51,_pygam:11,_sdl2:48,_spin:[58,66],_sprite__g:64,_spritegroup:64,_tag:53,_test:53,_time_threshold:50,_use_upd:50,_walk:[58,66],a0:37,a6f89747b551:44,a_mask:35,aa:[20,30,65],aaa:65,aacircl:30,aaellips:30,aalib:23,aalin:24,aapolygon:[24,30],aatrigon:30,abandon:18,abil:[44,58,63],abl:[31,42,58,63,84,89],abnorm:89,abort:37,about:[16,18,19,23,26,32,36,37,39,42,44,48,50,51,55,58,59,61,62,63,64,65,67,68,70,71,72,73,74,84,85,87,88],abov:[15,18,19,22,23,24,29,32,40,41,42,43,45,48,57,58,62,64,65,74,84,87],absent:23,absolut:[19,20,29,32,37,40,51,84],abspath:[58,66],abstractgroup:50,acceler:[23,24,30,41,48,51,56,63,69,84],accept:[15,24,26,28,29,30,31,38,39,45,50,51,52,62],access:[9,10,16,17,19,24,25,26,28,29,32,35,37,38,39,41,43,44,46,50,51,59,64,65,67,84],accord:[43,69],accordingli:51,account:[32,33,38,40,42,68,84],accur:[39,40,54],achiev:[62,86],acolor:20,acquir:[10,51,52],across:[22,23,58,62,65,66,84,87,88,89],act:[58,63,70,84],action:[25,32,63,65,88],activ:[23,38,39,40,49,51,52,55,72,74],activeev:[23,25],actual:[5,12,18,19,22,23,28,30,32,37,38,40,41,43,45,50,51,54,56,58,59,62,63,64,65,73,84,85,88],ad:[11,23,24,25,31,32,35,38,39,40,42,43,47,50,54,56,58,62,64,65,68,69,70,72,73,84,87,89],add:[23,29,48,50,57,58,61,62,63,64,65,68,69,70,89],add_intern:64,add_map:47,addit:[22,23,24,25,26,32,33,36,37,44,46,48,50,51],addition:[23,24,47],address:[17,51,84,86],adequ:72,adjac:[24,30],adjust:[9,20,23,28,29,37,71,89],admit:84,adopt:[48,61],advanc:[16,28,29,47,50,51,58,62,63,67,70],advancedinputoutput1:[72,80],advancedinputoutput2:[72,80],advancedinputoutput3:[72,80],advancedinputoutput4:[72,80],advancedinputoutput5:[72,80],advancedoutputalpha1:[73,81],advancedoutputalpha2:[73,81],advancedoutputalpha3:[73,81],advancedoutputprocess1:[71,79],advancedoutputprocess2:[71,79],advancedoutputprocess3:[71,79],advancedoutputprocess4:[71,79],advancedoutputprocess5:[71,79],advancedoutputprocess6:[71,79],advancemam:56,advantag:[59,63,64,67,84,87],advic:[63,84],advis:[18,22,23],ae:28,affect:[12,23,24,29,38,42,44,45,50,51,52,65,74],afraid:65,after:[17,19,23,24,29,31,32,33,37,38,40,47,49,50,51,57,58,59,62,63,65,68,69,70,84,89],again:[18,33,40,44,50,51,54,59,61,62,64,65,84,85,88,89],against:[29,45,56,57,64],ago:63,agp:65,ahead:[24,32],ai:[84,86,89],aid:29,aim:61,alexei:74,algorithm:[24,35,44,56,67,70],alia:[46,50],alias:[16,23,28,29,85],aliceblu:21,alien:[26,64],align:[29,35,45,84],aliv:[17,50,64],all:[16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,44,45,47,48,50,51,52,53,56,57,59,60,61,63,64,65,67,68,84,85,86,87,88],all_my_sprites_list:84,allblack:65,alloc:[31,38,68],allot:53,allow:[16,18,20,22,23,25,27,28,29,31,32,36,38,42,44,46,50,51,56,57,58,59,64,65,87,89],allowedchang:38,allsprit:[58,66],almost:[16,58,61,63,64,65,67,86],alon:[26,84],along:[24,25,26,29,37,45,53,63,64,65,66,74,84,87],alpha:[1,20,23,24,26,28,29,30,31,35,43,44,48,51,52,56,63,65,86],alreadi:[10,19,23,25,29,32,37,38,40,45,47,50,57,62,63,64,65,88],alsa:37,also:[11,13,15,16,17,19,20,22,23,25,26,28,29,30,31,33,35,36,37,38,39,40,42,44,45,47,50,51,52,53,54,56,57,58,59,60,61,62,63,64,65,68,69,72,73,84,85,86,87,88,89],alt:33,alter:[35,39,56],altern:[19,23,24,26,28,29,30,45],altgr:33,although:[58,68,86],alwai:[18,23,25,26,28,29,30,31,32,33,35,39,41,44,45,47,49,50,51,54,56,58,59,60,62,63,64,65,68,69,70,71,77,86,87,89],ambigu:38,among:18,amongst:16,amount:[23,25,28,29,32,39,54,55,56,58,63],ampersand:33,amplitud:49,an:[1,2,4,5,6,7,8,10,11,12,13,14,15,16,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,63,64,65,67,69,84,85,86,87,88,89],an_id:37,analog:[32,42,47],andal:29,andmask:[22,39],android:33,angl:[24,29,30,35,36,48,56,57,87,89],angle_to:36,angular:87,ani:[1,10,16,17,18,19,21,22,23,24,25,26,27,28,29,30,31,32,35,37,38,40,41,42,43,44,45,47,48,50,51,52,54,56,57,58,59,60,61,62,63,64,65,67,68,74,84,85,86,87,88],anim:[16,26,31,50,56,62,63,64,67],anisotrop:44,annoi:[16,61,73],anoth:[11,20,23,24,26,28,29,30,32,33,35,36,39,40,42,43,45,46,47,50,51,62,63,64,65,73,74,84,86,89],ansi:37,ansi_not:37,answer:[63,84],anti:[16,23,29,85],antialia:28,antialias:[24,28,29,30,56,58],anticip:[44,69],antiquewhit:21,antiquewhite1:21,antiquewhite2:21,antiquewhite3:21,antiquewhite4:21,anyon:64,anyth:[19,23,27,44,50,51,58,62,63,64,65,74,84],anywai:[65,70,84,85],anywher:[13,62],apart:38,api:[16,18,23,30,37,46,47,48,57],app:[23,26,44,57],appear:[16,23,25,54,62,63,64,70,84],append:[27,57,62,64,69,73,81,84],appli:[20,24,26,29,35,36,37,51,55,56,65,84],applic:[0,17,23,25,26,27,33,34,37,38,46,51,84,88],appreci:84,approach:61,appropri:[23,25,39,58,59,64,69,88],approxim:[32,35,51,57],aptitud:74,aqua:21,aquamarin:21,aquamarine1:21,aquamarine2:21,aquamarine3:21,aquamarine4:21,ar:[1,9,10,11,13,15,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,56,57,58,59,60,61,63,64,65,66,67,68,70,71,72,73,74,85,86,87,88,89],arang:65,arbitrari:[50,56,62],arbitrarili:23,arc:[24,30],arcad:[63,84],architectur:56,archiv:[28,29,84],area:[12,23,24,26,28,29,32,35,44,45,48,50,51,56,57,58,62,63,64,66,68,72,84,85,87,88,89],aren:[58,62,84],arg:[26,50,53],argb:31,argb_premult:31,argument:[1,2,4,10,12,16,17,18,19,20,22,23,24,25,26,28,29,30,31,32,33,35,36,38,39,40,41,43,44,45,47,48,50,51,53,54,56,58,59,62,64,84,85],aris:18,arithmet:[20,42,65],arkanoid:69,around:[23,24,26,32,36,39,42,45,48,54,57,58,59,62,63,65,84,85,86,89],arrai:[1,2,4,16,20,22,26,29,33,38,42,49,51,62,63,65,72,73,80],arrang:[65,68],array2d:[52,65],array3d:[52,65],array_alpha:[52,65],array_blu:52,array_colorkei:[43,52,65],array_green:52,array_r:52,array_to_surfac:43,arraydemo:[16,26,65],arraytyp:[26,49,52],arrayxd:65,arriv:84,arrow:[22,26,33,70],art:[67,74],articl:[62,63],as_joystick:47,as_polar:36,as_spher:36,ascend:29,ascent:[28,29],ascii:[15,22,33,52,67],asid:67,ask:[23,28,29,57,58,59,62,63,84],aspect:[44,45],assembl:[33,63],assertequ:56,assertnotequ:56,assign:[20,25,29,32,42,45,47,50,53,58,62,65,88],assist:51,associ:[3,8,29,48,55],assum:[1,10,12,20,28,29,37,39,45,57,61,64,86,88],asterisk:33,astonish:84,astyp:65,asurf:31,asyncblit:51,asynchron:51,attach:[10,23,26,47,57],attack:63,attempt:[18,28,29,37,51,58,60,65],attent:[63,70],attibut:50,attract:70,attribut:[6,23,25,27,28,29,30,32,33,34,35,36,39,44,45,50,51,53,54,58,64,85,87,88,89],attributeerror:[29,32,51,64],audio:[3,26,37,38,44,46,49,63],audio_allow_any_chang:38,audio_allow_channels_chang:38,audio_allow_format_chang:38,audio_allow_frequency_chang:38,audiodevicead:25,audiodeviceremov:25,author:[18,57,58,59,60,62,63,64,65,84],autom:84,automat:[13,19,23,28,29,30,31,32,33,34,37,38,44,46,50,51,58,60,65,68,69],avail:[0,1,3,10,18,20,22,23,26,27,28,29,30,31,37,38,41,44,46,49,51,52,53,55,56,58,59,60,62,63,64,65,84],avalanch:74,averag:[28,29,53,54,56,57,65,84],average_color:[56,57],average_surfac:[56,57],avid:84,avoid:[19,23,33,36,38,42,53,65,89],awai:[64,71,89],awar:[23,25,28,44,65],awhil:19,awkward:[62,84],ax:[23,29,32,47,87],axi:[24,25,29,32,36,39,42,47,48,49,52,56,85,87],axis_numb:32,azimuth:36,azur:21,azure1:21,azure2:21,azure3:21,azure4:21,b0:47,b3:47,b:[20,26,32,33,42,43,46,47,51,56,65],b_black:[73,81],b_height:[73,81],b_red:[73,81],b_width:[73,81],bach:40,back:[19,28,29,32,33,37,38,50,57,58,59,66,68,88,89],backend:[18,23,43,44,56,59],backgound:66,background:[24,26,28,29,38,50,57,61,64,66,84,85,87,89],backslash:33,backslashreplac:44,backspac:33,backward:[23,25,29,38,54],backyard:63,bad:84,bagic:[68,69,70,76,77,78],baker:63,ball:[25,32,61,63,67,68,69,70,71,72,73,75,76,77,78,79,80,81,85,86,88],ball_numb:32,ballrect:[63,67,68,69,70,71,72,73,75,76,77,78,79,80,81],ballsprit:89,banner:[16,26,58,66],bar:[71,79],barrier:67,base:[0,11,17,18,24,25,26,28,29,35,48,50,57,58,62,64,65,66,67,68,87,89],baselin:[28,29],basic:[16,18,20,23,26,31,46,58,61,62,63,64,65,76,84,86,87,88,89],bat:61,battleship:[67,75],bayer:18,bb:20,bdf:29,beam:22,bear:[28,29,39],beauti:62,becam:25,becaus:[24,27,29,36,37,51,58,61,64,65,67,68,69,70,72,73,84,85,86,87,88,89],becom:[23,25,29,38,40,49,51,52,62,63,64,65,68,73],been:[19,20,22,23,24,25,29,32,36,38,39,40,44,46,47,50,51,59,60,62,63,64,84,88,89],befor:[10,17,18,19,22,23,27,28,29,32,33,35,37,38,39,40,44,46,50,51,53,54,57,58,59,60,61,62,63,64,65,68,69,70,71,84,87,88],begin:[16,17,18,26,38,40,45,56,62,63,65,69,87],beginn:65,behalf:17,behavior:[39,50],behaviour:[23,39,44,46,89],behind:[16,23,50,87,88,89],beig:21,being:[9,18,23,24,25,31,32,33,35,37,38,39,40,42,46,63,64,69,84,88],believ:84,belong:[50,58,64,85],below:[18,20,23,24,26,27,32,37,44,45,47,50,57,62,71],bend:37,benefit:[62,64],beo:84,besid:[19,26,65],best:[13,18,23,25,26,35,38,42,43,51,52,59,62,63,64,65,72,84,86],bet:59,better:[23,28,30,37,39,40,51,56,62,63,64,65,71,84,85],between:[1,4,20,23,24,25,28,29,32,33,35,36,37,38,40,42,43,44,45,46,47,48,49,50,51,52,53,54,55,57,64,65,67,70,84],bezier:30,bg:57,bgcolor:29,bgd:50,bgr:[31,51,65],bid:58,big:[17,18,44,50,57,62,64,65,70,71,72],bigger:[16,23,58,73],biggest:[23,59],bilinear:56,bin:[66,85,86,87],binari:[16,18,20,22,41],bind:[37,59],bisqu:21,bisque1:21,bisque2:21,bisque3:21,bisque4:21,bit:[1,15,16,17,18,22,23,26,28,29,30,31,32,35,38,40,42,43,49,50,51,52,56,57,58,59,61,62,64,65,87,89],bitblt:62,bitmap:[22,29,31,56,62],bitmap_1:22,bitmap_2:22,bitmask:[22,33,35,50,51],bitsiz:[23,35,59],bitstream:[28,29],bitstreamverasan:28,bitwis:[23,33,35],bl:89,bla:31,black:[21,22,24,26,29,32,35,42,51,57,58,63,65,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84],blade:63,blanchedalmond:21,blank:[28,57,62,84,85],blanket:[43,51],blend:[20,24,26,29,44,48,51],blend_add:51,blend_alpha_sdl2:51,blend_fil:26,blend_max:51,blend_min:51,blend_mod:48,blend_mult:51,blend_premultipli:[20,51],blend_rgb_add:51,blend_rgb_max:51,blend_rgb_min:51,blend_rgb_mult:51,blend_rgb_sub:51,blend_rgba_add:51,blend_rgba_max:51,blend_rgba_min:51,blend_rgba_mult:51,blend_rgba_sub:51,blend_sub:51,blend_xxx:26,blendmod:50,blink:67,blit:[12,20,23,26,28,29,30,32,43,48,50,51,52,56,57,58,61,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,87,88,89],blit_arrai:[43,52,65],blit_blend:26,blit_hw:[23,59],blit_hw_a:[23,59],blit_hw_cc:[23,59],blit_sequ:51,blit_sw:[23,59],blit_sw_a:[23,59],blit_sw_cc:[23,59],blitter:[44,51,62],blitzbas:26,blob:[56,57],block:[17,18,25,35,40,50,51,57,58,73,84],block_list:50,blocks_hit_list:50,bloodi:63,blt:62,blue1:21,blue2:21,blue3:21,blue4:21,blue:[1,18,20,21,23,24,28,31,42,51,52,56,57,65,68,71,72,73,79,80,81,85,87],blueviolet:21,bluish:65,bmp:[31,46,62,63],board:[73,81],bodi:70,bold:[28,29],bomb:64,bonu:62,bool:[17,18,19,23,24,25,27,28,29,31,32,33,35,36,37,38,39,40,44,45,46,47,48,50,51,56],boom:64,boom_sound:64,border:[23,24,30,39,45,48],border_bottom_left_radiu:24,border_bottom_right_radiu:24,border_radiu:24,border_top_left_radiu:24,border_top_right_radiu:24,borderless:48,bore:[67,86],borrow:[1,63,86],both:[19,23,24,28,29,30,31,32,33,35,37,38,39,42,51,56,57,62,63,64,65,67,71,74,84,87,89],bother:[58,61,86],bottom:[23,24,26,28,29,30,31,35,45,50,63,67,68,69,70,71,72,73,75,76,77,78,79,80,81,89],bottomleft:[45,89],bottomright:[45,89],bounc:[26,36,56,63,89],bound:[22,24,29,35,50,51],boundari:[24,29],box:[24,29,30,32,33,57,64,88],br:89,bracket:[33,87],breakag:42,breakdown:63,brief:[19,26,44,61,62],briefli:85,bright:[18,48,57],brighten:23,brightmap:65,bring:[50,63],broadcast:[42,65],broken:[39,44],broken_x:22,brought:56,brown1:21,brown2:21,brown3:21,brown4:21,brown:21,bu:[65,84],buffer:[1,2,18,20,23,31,37,38,43,46,51,58,63,64,84],buffer_s:37,bufferproxi:[0,16,17,51],buffers:[37,38],bug:[20,44,56,63,89],build:[44,64,65,85,89],built:[15,18,29,31,44,47,85],builtin:[18,28,64],bullet:[64,84],bump:84,bumper:32,bunch:[57,64],bundl:[28,29],burlywood1:21,burlywood2:21,burlywood3:21,burlywood4:21,burlywood:21,busi:[18,19,38,54,62],button1:39,button2:39,button3:39,button4:39,button5:39,button:[24,25,26,27,32,33,39,47,58,61,62,69,73,80,84,85,88],bx:47,bye:68,bypass:44,byte_data:46,bytecod:63,byteord:17,bytes:[17,23,35,51,59],bytestr:38,c:[11,16,17,18,20,26,30,33,43,44,51,60,63,65,67,75],c_api:0,cach:29,cache_s:29,cadetblu:21,cadetblue1:21,cadetblue2:21,cadetblue3:21,cadetblue4:21,cadillac:64,calcnewpo:[87,89],calcul:[24,29,35,36,40,42,50,68,69,72,73,87,88],calibr:57,call:[1,10,13,17,18,19,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,44,45,46,47,48,50,51,53,54,57,58,59,60,61,62,63,64,65,66,68,69,70,84,85,86,87,88,89],callabl:[17,25,44,50],callback:[1,2,17,50],caller:37,calling_mask:35,cam:57,came:[26,31],camera:[16,26,44],camlist:57,can:[1,9,14,15,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,49,50,51,52,53,54,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,84,85,86,87,88,89],candid:[13,33],cannot:[15,18,23,25,28,30,38,40,45,48,50,51,52,54,62,63,64,69,84,86,89],canva:[68,71],cap:33,capabl:[23,59,63,86,87],capslock:33,caption:[23,68],captur:[16,18,26,31],capword:25,card:[23,38,65],care:[27,38,58,65,72,85],caret:33,carri:89,castl:63,categor:64,caught:63,caus:[2,18,20,25,28,35,40,42,56,58,64,65,66,68,84,88],caveat:23,cc:57,ccolor:57,cd:[3,19],cdrom:[0,63],cdrom_tag:53,cdrom_test:53,ceil:63,center:[24,26,30,32,35,44,45,48,50,51,57,66,68,69,70,71,72,73,76,77,78,79,80,81,85],centeri:45,centerx:[45,58,66,85],centr:44,centroid:[35,57],certain:[20,25,33,36,57,58,61,67,68,69,70,71,72,84],certainli:[62,65],cff:29,challeng:63,chanc:[38,65,84],chang:[16,18,20,23,24,25,26,28,29,30,31,32,33,35,36,37,38,39,40,42,44,45,46,47,48,49,50,51,52,54,56,57,58,59,61,63,64,65,68,69,70,71,73,84,87,88,89],change_color:56,change_lay:50,channel:[8,20,31,37,38,40,65,84],channelnum:8,char_bit:35,charact:[15,17,23,28,29,33,43,44,52,62,84,88],character:36,characterist:[20,74],charset:46,chart:65,chartreus:21,chartreuse1:21,chartreuse2:21,chartreuse3:21,chartreuse4:21,chase:64,chaser:64,chatroom:62,cheap:[50,64],check:[3,4,5,6,7,8,12,18,23,25,26,28,32,35,38,39,40,42,44,46,47,49,50,52,57,58,60,62,63,64,67,68,70,71,72,84,85,86,87,88,89],checkout:44,chew:54,chief:86,child:51,chimp:[16,26,61,64,86],chimpanze:16,chocol:21,chocolate1:21,chocolate2:21,chocolate3:21,chocolate4:21,choic:[18,23,38,57],choos:[18,22,23,26,59,61,65,73,84],chop:56,chord:84,chore:84,chose:73,chosen:[18,23,29],chromin:18,chunk:[8,17,89],circl:[22,24,26,30,32,50,57],circular:64,circumst:89,claim:18,clamp:[26,45],clamp_ip:45,clariti:35,clark:84,classless:[86,87],classmethod:48,claus:89,clean:[39,42,58,60,62,63,64],cleaner:[62,63],cleanli:[60,62,63,64,89],cleanup:42,clear:[16,24,25,32,33,35,38,43,48,50,51,62,63,64,67,75,84],clear_callback:50,clearer:60,clench:[58,66],click:[22,24,26,32,33,39,69,72,84,85],clip:[24,26,45,50,51,62],clipboard:[16,26],cliplin:45,clipped_lin:45,clist:57,clock:[22,24,32,39,54,58,66,69,70,71,72,73,77,78,79,80,81,89],clockwis:[29,30,36,56,87],clone:20,close:[10,18,22,23,24,25,26,29,32,37,40,42,47,56,57,62,65,85,86],close_to_play:64,close_to_player2:64,close_to_player3:64,closest:[23,37,38,59],cmy:20,co:[87,89],cocoa:23,code:[6,15,16,17,18,22,23,24,25,26,27,28,29,30,32,33,35,39,42,43,44,45,46,50,51,56,57,58,62,63,64,65,67,68,69,70,71,72,73,84,85,86,87,88,89],codec:44,codepoint:28,coercion:65,col:30,collect:[13,23,51,60],collid:[35,45,50,58,64,66,87,89],collide_circl:50,collide_circle_ratio:50,collide_mask:[35,50],collide_rect:50,collide_rect_ratio:50,collided_cal:50,collidedict:45,collidedictal:45,collidelist:45,collidelistal:45,collidepoint:[45,72,73,80,81,84,89],colliderect:[45,50,58,66,89],colliding_sprit:50,collis:[26,35,36,45,50,57,67,89],colon:33,color:[0,1,11,16,22,23,24,28,29,30,31,32,35,42,43,48,50,51,52,56,57,58,59,62,63,65,67,68,71,72,73,80,81,84,85],color_valu:20,colordict:21,colorkei:[23,26,28,29,31,43,51,52,56,58,63,65,66],colormap:[26,43,52],colorspac:18,colour:[20,44],column:[35,42,65,73,81],com:56,combin:[23,29,33,45,51,56,63,64,84,89],come:[10,25,26,28,29,37,44,57,62,63,64,65,66,84,87,88,89],comfort:[16,87],comma:[28,29,33,37,65],command:[24,25,26,32,53,65,67,68,69,70],commend:65,comment:[26,32,61,64,66,87,89],commerci:[16,63],commit:[33,63],common:[23,28,29,32,47,50,56,60,61,62,63,84],commun:[25,48,84],comp:84,compani:63,compar:[33,35,42,44,50,56,63,68,84],comparison:[20,25,26,42,65,70,74],compat:[23,25,28,29,31,38,48,50,54,67,86],compens:63,compil:[14,16,19,22,25,29,38,41,44],complement:36,complet:[24,33,38,40,45,50,51,52,63,64,84,89],complex:[38,58,63,64,67,69,84,86,87,88],complic:[68,84],compon:[20,35,36,43,51,65,68],composit:33,compositor:44,compound:46,compound_text:46,compress:[40,51],comput:[16,18,19,26,28,31,32,38,54,58,62,63,64,69,74,84],concept:[16,61,62,63,65,69,74],concern:[64,74],conclud:63,conclus:74,condit:[18,22,38,68,89],confid:84,configur:[16,23,29,39,65],confin:48,confus:[24,62,68,84],connect:[24,26,30,35,47,61,62,74,86],connected_compon:[35,57],consequ:86,consequenti:18,consid:[23,24,29,35,38,40,44,45,50,51,56,84,89],consider:51,consist:[19,24,25,27,38,46,61,65,85,86],consol:[26,44,53,67],constant:[16,22,25,29,33,38,39,44,46,47,54,57,60,68,72],constantli:84,constrain:[24,39,70],construct:[36,45,62],constructor:[29,32,50,58,64],consum:[1,54,65,84],contact:[57,58,59,60,62,63,64,65],contain:[0,7,9,10,16,19,22,23,24,25,28,29,31,32,33,34,35,37,38,39,43,45,46,47,50,51,53,55,58,59,62,63,64,66,84,86,88,89],content:[23,34,41,42,46,58,59,68,70],context:[10,23,42,48],contigu:[17,24,51],continu:[35,38,44,50,51,63,85],contol:47,contract:[18,56],contrast:[39,70,87],contribut:64,contributor:18,control:[3,5,18,22,25,26,28,29,32,33,34,38,41,44,50,51,52,53,54,56,58,59,60,61,63,69,70,78,84,86],controller_axis_lefti:47,controller_axis_leftx:47,controller_axis_righti:47,controller_axis_rightx:47,controller_axis_triggerleft:47,controller_axis_triggerright:47,controller_button_a:47,controller_button_b:47,controller_button_back:47,controller_button_dpad_down:47,controller_button_dpad_left:47,controller_button_dpad_right:47,controller_button_dpad_up:47,controller_button_guid:47,controller_button_i:47,controller_button_leftshould:47,controller_button_leftstick:47,controller_button_rightshould:47,controller_button_rightstick:47,controller_button_start:47,controller_button_x:47,controlleraxismot:47,controllerbuttondown:47,controllerbuttonup:47,controllerdevicead:[25,47],controllerdeviceremap:[25,47],controllerdeviceremov:[25,47],controllertouchpaddown:47,controllertouchpadmot:47,controllertouchpadup:47,convei:22,conveni:[36,44,50,53,62,63],convent:47,convers:[1,18,20,35,42,52,84],convert:[1,18,20,22,24,29,31,37,38,40,43,45,49,51,52,58,59,62,65,66,85,86,89],convert_alpha:[26,31,51,86,89],convolut:[35,65],convolv:35,cool:84,cooper:19,coord:57,coordin:[9,22,24,29,30,32,35,36,39,50,51,56,85,89],copi:[17,22,26,31,35,36,38,42,45,46,48,49,50,51,52,56,58,62,63,64,65,84,85],copyright:18,coral1:21,coral2:21,coral3:21,coral4:21,coral:21,cord:26,coremidi:37,corner:[24,29,30,35,39,42,44,50,51,56,57,58,62,84,89],cornflowerblu:21,cornsilk1:21,cornsilk2:21,cornsilk3:21,cornsilk4:21,cornsilk:21,correct:[18,22,28,41,45,56,62,65,68,73],correct_gamma:20,correctli:[37,38,56,57,58,62,65,84],correspond:[29,33,35,37,42,51,53,68,84,89],cost:84,could:[22,25,33,41,49,50,52,56,57,58,61,62,64,65,84,85,86,87],couldn:[86,89],count:[17,19,32,35,38,42,56,57,69,73],counterclockwis:[24,29,36,56],coupl:[32,58,62,64,65],courier:29,cours:[57,69,70,71,85,86,87,89],cover:[16,24,44,45,51,56,57,59,62,74,85],coverag:24,cpu:[24,54,67,75,84],cram:84,crash:[41,64],crate:87,creat:[1,2,6,7,16,17,19,20,21,22,23,25,26,28,29,31,32,33,35,38,39,41,42,43,45,47,48,49,50,51,52,54,56,57,59,63,64,65,66,74,84,85,86,87,88,89],create_graphics_screen:62,create_screen:62,creation:[29,32,47,49,50,52,62],creativ:74,crect:57,crimson:21,critic:84,critter:[58,66],crop:[45,56],cross:[32,36,58,63,65,84,86],crossbar:26,crossbon:22,crossfad:65,crosshair:22,crt:23,crucial:84,crude:[26,58,62],cryptic:64,cube:26,cui:[67,68,73,81],current:[14,18,19,22,23,25,26,27,28,29,32,35,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,56,58,59,63,64,67,68,69,71,84,87,88,89],current_h:[23,59],current_w:[23,59],currrent:23,cursor:[16,26,39,58,63,84],cursor_arg:22,cursor_index:22,cursorfil:22,curv:30,custom:[16,23,25,26,38,50,63],custom_typ:25,customis:86,cut:[38,46],cutout:62,cx1:45,cx2:45,cy1:45,cy2:45,cyan1:21,cyan2:21,cyan3:21,cyan4:21,cyan:21,d:[32,33,51,62,84,85,87],da:61,dai:[63,64],damag:18,dark:[58,63],darkblu:21,darkcyan:21,darken:23,darkgoldenrod1:21,darkgoldenrod2:21,darkgoldenrod3:21,darkgoldenrod4:21,darkgoldenrod:21,darkgrai:21,darkgreen:21,darkgrei:21,darkkhaki:21,darkmagenta:21,darkolivegreen1:21,darkolivegreen2:21,darkolivegreen3:21,darkolivegreen4:21,darkolivegreen:21,darkorang:21,darkorange1:21,darkorange2:21,darkorange3:21,darkorange4:21,darkorchid1:21,darkorchid2:21,darkorchid3:21,darkorchid4:21,darkorchid:21,darkr:21,darksalmon:21,darkseagreen1:21,darkseagreen2:21,darkseagreen3:21,darkseagreen4:21,darkseagreen:21,darkslateblu:21,darkslategrai:21,darkslategray1:21,darkslategray2:21,darkslategray3:21,darkslategray4:21,darkslategrei:21,darkturquois:21,darkviolet:21,data1:37,data2:37,data3:37,data:[0,2,16,17,18,19,22,26,31,35,37,38,39,40,41,43,46,51,56,58,62,63,65,66,68,70,71,72,73,84,86,89],data_dir:[58,66],datatyp:65,david:84,dead:27,deal:[33,44,65,68,84,88],dealloc:32,dealt:[25,27,88],death:63,debat:84,debug:[25,56,88],decapit:63,decept:72,decid:[25,27,35,58,61,64,68],decim:24,declar:[9,65],decod:[22,29,33,46,63],decor:85,decreas:[38,72],decrement:10,dedic:64,deeppink1:21,deeppink2:21,deeppink3:21,deeppink4:21,deeppink:21,deepskyblu:21,deepskyblue1:21,deepskyblue2:21,deepskyblue3:21,deepskyblue4:21,def:[29,32,35,39,50,56,57,58,62,64,66,71,72,73,79,80,81,85,86,87,88,89],default_id:37,default_lay:50,defin:[1,2,4,7,8,9,11,12,24,25,28,29,32,34,44,46,50,58,61,84,85,88],definit:[46,60,64,65,70],deflat:89,degre:[29,30,35,36,52,56,58,87,89],del:65,delai:[26,33,37,54,62,69,84],delet:[25,33,62,64,84],deliv:37,demo:[26,58,65],demonstr:[16,26,58,65],denot:[40,45,54],depend:[15,23,25,28,29,31,35,38,40,42,43,46,49,58,63,66,69,71,84],deprec:[23,25,26,27,29,32,36,49,50,52],deprecationwarn:[49,52],depth:[18,23,35,42,48,51,52,56,57,58,59,62],deriv:[26,44,50,58,64],descend:29,descent:[28,29],describ:[1,17,22,23,37,39,45,57,59,86],descript:[1,18,22,25,29,33,34,44,46,62,64],design:[17,50,61,62,63,64,68,71,88],desir:[18,51,56,58,59,84],desktop:[23,48,59],desper:62,dest:[29,35,48,51,56,65],dest_rect:56,dest_siz:56,dest_surf:56,dest_surfac:56,destin:[18,28,29,30,35,42,50,51,56,62,63,85],destroi:[1,31,48,64],destruct:[56,58],destsurfac:18,detail:[16,23,24,29,34,35,36,37,41,50,51,56,57,61,71,84],detect:[23,26,35,36,42,44,45,50,57],determin:[19,20,22,23,24,28,29,31,33,36,37,40,41,50,51,56,59,64,65,68,69,72,87],dev13:25,dev3:[25,54],dev7:39,dev8:24,dev:[18,57],develop:[23,39,44,48,53,63,67,86],devic:[16,18,19,25,26,32,33,37,39,47,55,57,59,63,84],device_id:[26,37],device_index:25,devicenam:38,dga2:84,dga:23,diagon:[35,56],diagram:[87,88],diamond:22,dict:[1,6,17,20,23,25,35,45,47,51,53,55],dictionari:[1,6,23,25,45,47,50,53,64,84],did:[18,24,32,62,63,64],didn:[84,85],diff:65,differ:[18,19,20,22,23,26,27,28,29,30,32,33,35,38,40,42,44,48,50,51,56,57,58,59,60,62,63,64,65,68,69,70,84,85,87,88],difficult:[37,61,63],digit:[20,32],dilemma:67,dimens:[17,18,23,24,26,28,29,30,35,36,42,43,45,50,51,52,56,58,65,89],dimension:[20,24,29,30,35,36,42,62,65],dimgrai:21,dimgrei:21,direct:[18,23,24,26,29,30,32,36,39,43,47,51,58,65,70,87,89],directfb:23,directli:[15,17,22,24,25,28,29,31,33,42,43,46,49,51,52,53,62,64,65,67,84],directmedia:63,directori:[23,28,29,53,58,62,67,68,86],directx:[23,37,63],dirti:[50,64],dirty_rect:84,dirtysprit:50,disabl:[23,25,29,33,44,47,51,54,58,66],disable_advanced_featur:44,disadvantag:[59,84],disallow:23,disappear:[18,30,46],disc:19,discard:54,disclaim:18,disconnect:61,discontinu:[17,51,67],discourag:27,discov:[63,84],discret:71,discuss:[64,84],disk:[50,84],displac:69,displai:[0,1,16,22,24,25,26,30,32,33,34,36,38,39,41,44,46,48,50,51,53,57,62,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,87,88,89],display_index:48,distanc:[36,42,56],distance_squared_to:36,distance_to:36,distil:84,distort:23,distribut:[16,18,53,58],dive:68,divers:61,divid:[33,61],divis:[20,22],dizzi:[58,66],doc:[23,27,48,50,51,70,78],document:[18,34,38,44,51,57,59,61,63,64,65,84,86,87],dodgerblu:21,dodgerblue1:21,dodgerblue2:21,dodgerblue3:21,dodgerblue4:21,doe:[4,5,8,13,19,22,23,24,25,26,28,29,30,32,33,35,37,38,40,41,43,44,45,46,47,50,51,54,56,57,58,62,64,65,68,84,86,88],doesn:[18,23,26,28,29,44,51,58,62,64,72,73,84,86,87,88,89],dokil:[50,64],dokill1:[50,64],dokill2:[50,64],dollar:33,domain:26,don:[18,24,26,29,32,50,54,56,57,58,61,62,63,64,65,68,70,74,85,86,87,89],done:[24,28,29,32,40,58,62,63,65,68,70,71,84,87],doreturn:51,dot:[29,36],doubl:[22,23,56,58,63,65,84],doublebuf:[23,84],doubler:56,down:[23,29,32,33,39,44,47,51,57,59,62,63,65,68,69,71,72,84,88],download:[63,65,89],dozen:84,dpad:47,dpi:29,drastic:85,draw:[16,20,23,28,29,31,32,35,48,50,51,57,62,63,64,66,67,68,71,72,73,79,80,81,84,85,89],draw_blend_mod:48,draw_bottom_left:24,draw_bottom_right:24,draw_circle_part:24,draw_color:48,draw_lin:48,draw_point:48,draw_rect:48,draw_top_left:24,draw_top_right:24,drawback:84,drawboard:[73,81],drawbutton:[72,73,80,81],drawhp:[71,72,73,79,80,81],drawn:[23,24,30,32,35,50,51,58,62,63,64,68,85],drawplain:58,dream:67,drew:62,drift:32,drive:[3,19],driven:[23,50,67],driver:[23,37,44,59,84],drivernam:44,drop:[25,29,38],dropbegin:25,dropcomplet:25,dropfil:25,dropout:38,droptext:25,dstobj:12,dstrect:[12,48],dualshock:47,due:[20,23,36,51],dull:84,dummi:[58,64,88],dump:53,dungeon:63,duplic:[50,51],durat:[32,47],dure:[18,23,29,30,33,40,42,52,65],dvd:[19,69,77],dx:[25,35,51,87,89],dy:[25,35,51,87,89],dynam:69,e:[6,20,23,24,28,30,31,32,33,35,36,37,38,44,45,50,57,61,85,87,88],each:[17,18,19,20,23,24,25,26,27,28,29,30,32,35,36,38,39,42,46,49,50,51,52,53,56,57,58,60,61,62,63,64,65,71,84,85,87,88,89],earli:[16,48,63,84],earlier:[26,40,43],easi:[26,48,59,60,61,62,63,64,65,70,84,88,89],easier:[45,50,59,60,62,87],easiest:[26,47,62,71,85],easili:[22,44,57,58,60,62,64,65,69,74,84,87],east:22,eat:64,echo:26,eclass:10,eclecti:57,ed:[51,64],edg:[24,28,39,45,56,89],edit:[33,47],editbox:84,editor:[33,84],effect:[16,23,25,26,28,32,40,44,46,47,50,51,56,57,58,62,63,64,65,73,84,86],effici:[16,23,25,28,50,62,64],effort:74,eg:[26,29,31],eight:26,either:[15,17,19,22,23,29,31,32,38,39,45,50,51,56,58,59,89],eject:19,element:[4,17,20,29,36,37,42,43,49,51,62,64,65],elementari:87,elementwis:36,elif:[32,39,58,66,69,70,71,72,73,77,78,79,80,81,88,89],ellips:[16,24,30],ellipt:[24,30],els:[1,9,32,33,43,44,45,46,58,59,64,66,69,84,86,89],elsewher:1,emb:23,embed:[23,29],emit:39,emoji:28,empti:[6,19,23,25,27,28,29,32,35,44,45,46,50,53,59,64],emul:[23,28,39,59],enabl:[23,25,28,29,33,44,47,48,63,67],encapsul:88,enclos:[24,50,68],encod:[10,15,22,28,29,40,44,46,51],encode_file_path:44,encode_str:44,encount:35,end:[19,24,25,30,31,33,38,40,44,45,50,58,61,62,63,66,69,74,84,85,88],end_index:65,end_po:24,endcap:[24,30],endev:[38,40],endian:[17,44],endpoint:[24,30,45],enemi:64,engin:[24,63,67,84],enhanc:[16,28,29],enjoy:70,enlarg:29,enough:[29,50,57,58,60,62,63,64,65,67,71,84],ensur:[25,27,33,38,63,84,85,86],enter:[25,28,33,39,48,58,67],entir:[16,19,23,24,35,38,48,50,51,62,63,64,68,69,70,72,84],entri:[17,33,51,84],enumer:18,env:[66,86],env_var:44,environ:[23,25,28,37,44,46,63,67,68,86,87],equal:[14,17,20,22,24,25,29,33,35,36,51,67,73],equat:[35,69],equip:65,equival:[17,29,35,37,42,43],eras:[32,35,50,58,62,63,64,84],err:[86,89],errno:37,error:[1,2,3,7,10,12,20,23,25,26,29,31,33,37,43,44,46,53,56,58,60,64,65,74,84,86,89],error_msg:44,errorstr:44,es:23,esc:26,escap:[15,29,33,58,89],especi:[25,29,44,64,65,84,86],essenti:65,etc:[31,32,37,42,45,61,65,67,84],etyp:44,euclidean:[36,42],euro:33,eval:53,evalu:58,even:[15,18,19,23,24,26,28,29,32,40,42,44,54,57,62,63,64,65,67,68,72,84],event:[0,16,18,22,23,24,26,32,33,34,37,38,39,40,47,54,57,61,62,63,66,67,69,71,72,73,75,76,77,78,79,80,81,89],event_nam:25,eventlist:[25,26],eventtyp:[6,25],eventu:[13,23,25],ever:[37,40,59],everi:[16,18,19,22,25,26,33,35,36,38,40,44,50,51,52,54,56,59,61,63,64,65,67,68,69,70,71,74,84,85,86,88],everyth:[25,43,44,62,63,64,65,66,67,68,73,84,85,89],evil:84,evolv:26,ex:[39,70],exact:[20,22,23,24,31,32,39,57,59,72,73],exactli:[37,42,45,51,58,62,63,64,65,68,84],examin:16,exampl:[11,16,17,20,22,23,24,25,27,28,29,30,31,32,33,35,37,38,39,40,44,45,46,47,49,50,51,53,56,57,60,61,62,63,64,67,68,70,71,74,84,85,86,87,88],exce:51,exceedingli:29,excel:[58,62,63],except:[1,3,4,5,6,7,8,9,10,12,15,20,22,23,24,25,28,29,31,32,33,37,38,44,45,46,51,56,60,61,65,69,86,89],exchang:42,excit:[58,62,63,65],exclaim:33,exclud:[17,25,51,53],exclus:[37,51],execut:[39,53,66,67,68,69,70,71,73],exemplari:18,exist:[18,19,22,23,24,25,28,29,32,37,47,48,50,51,64],exit:[23,24,32,37,44,48,62,63,67,68,69,70,71,72,73,75,76,77,78,79,80,81,85,86,89],exmapl:67,expand:[56,61],expans:56,expect:[20,27,37,38,39,43,53,57,62],expens:65,experi:[25,63,64,70],experiment:[18,23,30,36,44,46,48,57],expir:53,explain:[58,62,64,68,71,73,86,87,89],explan:[26,36,60,64,66,68,70,84],explanatori:58,explicit:[23,39,43],explicitli:[18,23,29,30,42,54,58,60],explor:63,explos:64,expos:[2,11,29,43,51],express:[18,20],extend:[17,24,31,44,50],extens:[1,2,3,4,5,7,8,9,10,12,13,16,31,56,63],extern:[22,25,49,52,67],extra:[22,29,38,45,50,51,58,59,62,63,64,65,70,89],extract:[42,56,58],extrem:[26,58,65],extsion:6,ey:63,f10:33,f11:33,f12:33,f13:33,f14:33,f15:33,f1:33,f2:33,f3:33,f4:33,f5:33,f6:33,f7:33,f8:33,f9:33,f:[1,17,18,33,84],face:16,fact:[30,64,84],factor:[26,29,65],fade:[26,38,40,65],fade_m:[38,40],fadeout:[38,40],fail:[23,25,27,28,29,44,53,58,60],failur:[1,2,4,5,6,8,9,53],fairli:[50,60,63,84,86,87],fake:[26,28,53],fall:[28,29,50],fals:[2,3,5,6,7,12,13,17,18,19,22,23,24,25,26,28,29,31,32,35,36,37,38,39,40,45,46,47,48,50,53,56,57,58,66,89],famili:28,familiar:[62,63,64,84,85,88],fantast:62,far:[20,36,58,62,71,84,87,88,89],farther:[58,62],fast:[32,35,42,51,52,56,58,62,63,64,65,69,84],faster:[31,36,44,50,52,56,58,64,65,67,84],fastest:[23,51,58,63],fastev:27,fastrendergroup:26,fault:84,favorit:84,favour:27,fbcon:23,featur:[16,23,26,27,28,29,35,44,50,51,59,64,65,87],fed:18,feed:89,feedback:23,feel:[26,62,64,84],felt:63,fetch:50,fever:[58,66],few:[22,28,54,62,63,64,65,74,84,86],ffff:15,fgcolor:29,field:[5,9,13,17,32,37],fighter:63,figur:62,file:[0,2,3,4,5,6,7,8,9,10,11,12,13,14,16,22,23,25,26,28,29,31,38,40,44,49,53,58,61,63,65,67,68,73,84,86],file_path:26,filenam:[7,22,25,28,29,31,38,40,62,86],fileobj:[31,40],filesystem:15,fill:[6,9,10,22,23,24,25,26,29,30,32,35,39,43,48,50,51,56,57,58,62,63,64,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,89],fill_rect:48,filled_:30,filled_circl:30,filled_ellips:30,filled_polygon:30,filled_trigon:30,filter:[16,25,35,56,65],fin:26,find:[16,18,19,23,24,25,26,28,32,35,36,38,50,51,56,57,58,59,62,63,64,65,69,71,84,87,89],find_channel:38,fine:[38,51,59,64,84],finer:57,finger:[55,70,86],finger_id:25,fingerdown:25,fingermot:25,fingerup:25,finish:[13,33,38,40,51,60,61,62,63,65],finit:35,fire:[65,84],firebrick1:21,firebrick2:21,firebrick3:21,firebrick4:21,firebrick:21,firmer:[61,63],first:[4,17,19,24,26,28,29,30,31,32,33,35,37,38,40,42,43,45,49,50,51,52,54,57,58,59,60,61,63,64,65,68,69,70,71,72,73,84,85,87,88,89],firstli:70,fist:[16,58,66],fist_offset:[58,66],fit:[18,23,29,38,45,51,85],five:[38,39,40],fix:[25,29,51,56,63,65,68,69,71,89],fixed_s:29,fixed_width:29,flag:[1,17,20,23,24,26,28,29,32,34,35,43,50,51,58,59,64,84],flame:65,flash:16,flavor:84,flexibl:[16,60,64,86,87],flickeri:84,flip:[18,22,23,24,25,31,32,35,39,42,45,48,50,56,57,58,63,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,89],flip_i:56,flip_x:56,flipi:48,flipx:48,flood:[73,81],floor:20,floralwhit:21,flourish:70,flush:[31,37],fly:[45,84,85,88],fnt:29,focu:[23,25,33,39,48,57,67],folder:58,folk:62,follow:[17,18,22,24,25,26,29,30,31,32,33,35,36,37,38,40,45,46,47,49,50,52,58,61,63,64,65,66,67,84],font:[7,16,26,32,44,58,60,63,66,68,69,70,71,72,73,76,77,78,79,80,81,84,85],font_index:[7,29],fonti:26,foo:84,fool:[63,65],forbidden:10,forc:[23,26,38,44,63],forcibl:38,forego:25,foreground:29,foreign:62,forestgreen:21,forev:62,forget:[32,64,87],form:[18,22,25,30,37,51,85],formal:62,format:[18,20,22,23,24,29,30,31,32,35,37,38,40,41,42,43,44,45,49,51,52,56,58,59,62,63,65,68,84,85],formula:[42,44,51,87],forth:58,fortun:[26,62,65],forward:33,found:[16,20,26,28,29,34,35,37,45,49,50,51,52,58,59,62,65,70,85,88],four:[4,19,22,23,24,29,45,52,70,89],fourth:[22,71],fout:26,fp:[46,50,69,77,84],fpsclock:[69,70,71,72,73,77,78,79,80,81],fraction:29,frame:[18,23,25,27,32,44,50,54,57,58,62,63,64,69,77,84,85,88,89],framebuff:23,framer:[16,18,54,57,58,84],framework:[26,63],free:[10,26,40,62,64],freedom:63,freetyp:[7,16,26,28,29,44],freetype2:29,freetype_misc:[26,29],frequenc:[37,38],frequency_to_midi:37,frequent:[32,62,64,69],friendli:[24,68],friendlier:63,frill:64,from:[0,4,6,7,9,10,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,53,56,57,58,60,61,63,64,65,67,68,69,70,71,72,73,76,77,78,79,80,81,84,85,86,87,88,89],from_display_modul:48,from_joystick:47,from_polar:36,from_spher:36,from_surfac:[35,48,50],from_threshold:[35,57],from_window:48,frombuff:31,fromstr:31,front:50,frozen:53,ftfont:[28,29],fuchsia:21,full:[23,25,26,27,28,29,31,32,38,39,40,42,50,51,56,57,58,59,62,63,64,66,70,85,86,89],fulli:[20,23,33,45,48,50,51,52,58,65,84],fullnam:[58,66,86,89],fullscreen:[23,34,48,50,59,84],fullscreen_desktop:48,fun:[26,57,62,63,65],fundament:61,funni:64,further:46,furthermor:[68,70,71],futur:[18,19,23,27,29,32,37,38,41,46,64],g:[20,23,24,26,28,30,31,33,36,37,42,43,44,45,50,51,56,87,88],gain:[23,25,29,57,84],gainsboro:21,game:[16,18,22,23,24,25,26,27,32,39,44,47,51,54,57,59,60,62,64,66,67,68,69,70,71,72,73,74,84,86,88,89],gameobject:62,gamepad:[25,47],gameplai:[33,64],gamma:[20,23,48],gap:[17,51],garbag:[13,51],gather:59,gaussian:65,gener:[1,16,23,25,26,31,32,33,36,37,39,40,42,47,51,52,56,57,58,63,64,65,68,73,86,89],generateboard:[73,81],geniu:74,geometri:[67,71],get:[2,16,18,19,20,22,23,24,25,26,27,28,29,31,32,33,35,37,38,39,40,42,43,44,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,62,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,85,86,87,88,89],get_abs_offset:51,get_abs_par:51,get_act:23,get_al:19,get_allow_screensav:23,get_alpha:[51,86,89],get_and_flip:57,get_arraytyp:[49,52],get_asc:28,get_at:[35,51,56,58,65,66,84],get_at_map:[20,51],get_axi:[32,47],get_backend:18,get_bal:32,get_bits:51,get_block:25,get_bold:28,get_bottom_lay:50,get_bounding_rect:[35,51],get_buff:[2,17,51],get_busi:[19,38,40],get_button:[32,47],get_bytes:[42,51],get_cache_s:29,get_capt:23,get_clip:[50,51],get_colorkei:51,get_control:[18,57],get_count:[19,32,37,47],get_curr:19,get_cursor:39,get_default_font:[28,29],get_default_input_id:37,get_default_output_id:37,get_default_resolut:29,get_desc:28,get_desktop_s:23,get_devic:55,get_device_info:37,get_driv:[23,59],get_empti:19,get_endev:[38,40],get_error:[29,44],get_eventst:47,get_extend:31,get_fing:55,get_flag:51,get_focus:[23,33,39],get_font:28,get_fp:54,get_grab:25,get_guid:32,get_hardwar:41,get_hat:32,get_height:[28,51],get_id:[19,32],get_imag:[18,57],get_init:[19,23,27,28,29,32,37,38,44,46,47,49,58,60,66],get_instance_id:32,get_ital:28,get_layer_of_sprit:50,get_length:38,get_lines:28,get_lock:51,get_loss:51,get_map:47,get_mask:51,get_metr:29,get_mod:33,get_nam:[19,32],get_num_channel:38,get_num_devic:55,get_num_displai:23,get_num_fing:55,get_numax:32,get_numbal:32,get_numbutton:32,get_numhat:32,get_numtrack:19,get_offset:51,get_palett:51,get_palette_at:51,get_par:51,get_paus:19,get_pitch:51,get_po:[39,40,58,66,84],get_power_level:32,get_press:[33,39,84],get_queu:38,get_raw:[18,38],get_rawtim:54,get_rect:[29,35,48,50,51,56,58,62,63,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,85,86,87,88,89],get_rel:39,get_repeat:33,get_sdl_byteord:44,get_sdl_image_vers:31,get_sdl_mixer_vers:38,get_sdl_vers:[23,44],get_shift:51,get_siz:[18,29,35,42,51,58,66,85,89],get_sized_ascend:29,get_sized_descend:29,get_sized_glyph_height:29,get_sized_height:29,get_smoothscale_backend:56,get_sound:38,get_sprit:50,get_sprites_at:50,get_sprites_from_lay:50,get_surfac:[23,58,66,87,88,89],get_tick:54,get_tim:54,get_top_lay:50,get_top_sprit:50,get_track_audio:19,get_track_length:19,get_track_start:19,get_typ:46,get_underlin:28,get_vers:29,get_view:[17,51],get_viewport:48,get_vis:39,get_volum:[38,40],get_width:[51,58,66],get_window_s:23,get_wm_info:23,getbufferproc:2,getch:[67,75],getfilesystemencod:[15,44],getopt:[86,89],gfxdraw:[11,16,24,30],gg:20,ggi:23,ghost:64,ghostwhit:21,gif:[31,63,71,72,73,79,80,81,84],gil:[10,30],github:[44,46,56],give:[17,22,23,29,32,37,40,42,50,51,58,59,60,61,65,74,84,85,86,87],given:[9,14,17,20,22,23,24,25,28,29,30,32,35,36,37,38,39,44,45,46,47,50,51,52,54,55,56,58,59,64,68,70,73,84],gl:23,gl_accelerated_visu:23,gl_accum_alpha_s:23,gl_accum_blue_s:23,gl_accum_green_s:23,gl_accum_red_s:23,gl_alpha_s:23,gl_buffer_s:23,gl_context_flag:23,gl_context_major_vers:23,gl_context_minor_vers:23,gl_context_profile_:23,gl_context_profile_compat:23,gl_context_profile_cor:23,gl_context_profile_mask:23,gl_context_release_behavior:23,gl_depth_siz:23,gl_framebuffer_srgb_cap:23,gl_get_attribut:23,gl_multisamplebuff:23,gl_multisamplesampl:23,gl_set_attribut:23,gl_share_with_current_context:23,gl_stencil_s:23,gl_stereo:23,glcube:26,glitch:89,global:[53,60,68,86,89],glsl:36,glue:89,glyph:[28,29],gnu:[86,89],go:[20,22,24,32,36,37,51,53,57,58,59,60,61,64,65,66,68,70,74,84,85,86,88,89],goal:63,goe:[58,62,64,68,89],gold1:21,gold2:21,gold3:21,gold4:21,gold:21,golden:59,goldenrod1:21,goldenrod2:21,goldenrod3:21,goldenrod4:21,goldenrod:21,gone:[62,89],good:[18,22,23,26,28,38,44,51,58,61,62,63,64,65,67,68,84,85,86,87,89],goodluck:26,got:[25,40,57,62,64,84],gotten:62,grab:[25,33,39,48,64,89],grace:89,gradient:[26,35,65],grahic:23,grai:[21,29,71,72,73,79,80,81],graphic:[23,26,56,58,59,62,63,64,65,67,68,84],grasp:[61,89],grave:33,gray0:21,gray100:21,gray10:21,gray11:21,gray12:21,gray13:21,gray14:21,gray15:21,gray16:21,gray17:21,gray18:21,gray19:21,gray1:21,gray20:21,gray21:21,gray22:21,gray23:21,gray24:21,gray25:21,gray26:21,gray27:21,gray28:21,gray29:21,gray2:21,gray30:21,gray31:21,gray32:21,gray33:21,gray34:21,gray35:21,gray36:21,gray37:21,gray38:21,gray39:21,gray3:21,gray40:21,gray41:21,gray42:21,gray43:21,gray44:21,gray45:21,gray46:21,gray47:21,gray48:21,gray49:21,gray4:21,gray50:21,gray51:21,gray52:21,gray53:21,gray54:21,gray55:21,gray56:21,gray57:21,gray58:21,gray59:21,gray5:21,gray60:21,gray61:21,gray62:21,gray63:21,gray64:21,gray65:21,gray66:21,gray67:21,gray68:21,gray69:21,gray6:21,gray70:21,gray71:21,gray72:21,gray73:21,gray74:21,gray75:21,gray76:21,gray77:21,gray78:21,gray79:21,gray7:21,gray80:21,gray81:21,gray82:21,gray83:21,gray84:21,gray85:21,gray86:21,gray87:21,gray88:21,gray89:21,gray8:21,gray90:21,gray91:21,gray92:21,gray93:21,gray94:21,gray95:21,gray96:21,gray97:21,gray98:21,gray99:21,gray9:21,great:[23,57,63,65,84],greater:[17,24,28,29,33,35,37,40,51,74],green1:21,green2:21,green3:21,green4:21,green:[1,18,20,21,23,24,42,51,52,57,58,65,68,69,70,71,72,73,76,77,78,79,80,81,85],greenyellow:21,grei:[21,58],grey0:21,grey100:21,grey10:21,grey11:21,grey12:21,grey13:21,grey14:21,grey15:21,grey16:21,grey17:21,grey18:21,grey19:21,grey1:21,grey20:21,grey21:21,grey22:21,grey23:21,grey24:21,grey25:21,grey26:21,grey27:21,grey28:21,grey29:21,grey2:21,grey30:21,grey31:21,grey32:21,grey33:21,grey34:21,grey35:21,grey36:21,grey37:21,grey38:21,grey39:21,grey3:21,grey40:21,grey41:21,grey42:21,grey43:21,grey44:21,grey45:21,grey46:21,grey47:21,grey48:21,grey49:21,grey4:21,grey50:21,grey51:21,grey52:21,grey53:21,grey54:21,grey55:21,grey56:21,grey57:21,grey58:21,grey59:21,grey5:21,grey60:21,grey61:21,grey62:21,grey63:21,grey64:21,grey65:21,grey66:21,grey67:21,grey68:21,grey69:21,grey6:21,grey70:21,grey71:21,grey72:21,grey73:21,grey74:21,grey75:21,grey76:21,grey77:21,grey78:21,grey79:21,grey7:21,grey80:21,grey81:21,grey82:21,grey83:21,grey84:21,grey85:21,grey86:21,grey87:21,grey88:21,grey89:21,grey8:21,grey90:21,grey91:21,grey92:21,grey93:21,grey94:21,grey95:21,grey96:21,grey97:21,grey98:21,grey99:21,grey9:21,greyscal:56,grid:29,ground:62,group1:[50,64],group2:[50,64],group:[26,35,50,58,63,86],group_list:50,groupcollid:[50,64,87],groupmulti:64,groupsingl:[50,64],grow:[24,45,84],guarante:[18,23,25,38,46,53],guess:[38,84],gui:[63,67,71,72,73,75,79],guid:[16,32,57,85],gun:71,h:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,20,25,26,30,33,42,43,45,51,56],ha:[1,2,11,16,17,18,19,22,23,24,25,26,27,28,29,31,32,33,35,36,38,39,40,42,43,44,45,46,47,49,50,51,52,53,54,56,57,58,59,60,62,63,64,65,67,68,69,70,71,72,73,84,86,87,89],habit:87,had:[51,63,64,84],hadn:84,half:[26,50,63,84],hand:[20,22,42,43,58,60,62,84,87],handi:[26,61,86,87,88,89],handili:89,handl:[10,15,16,23,25,28,29,32,33,38,43,44,50,56,59,61,63,64,65,66,70,87,89],handler:[25,27],hang:[32,84],happen:[19,24,58,60,63,64,84,85,88,89],hard:[62,63,65,68],hardcod:26,harder:[61,65],hardest:64,hardwar:[22,23,24,30,32,37,38,41,51,63,64,65],harmless:23,hasattr:28,hash:33,hashabl:45,hasn:29,hat:[25,32,47],hat_numb:32,have:[1,4,9,11,18,19,20,22,23,24,25,26,27,28,29,30,32,33,35,36,38,40,41,42,43,44,45,47,50,51,52,53,54,56,57,58,59,60,61,62,63,64,65,67,68,69,70,72,73,84,85,86,87,88,89],haven:84,he:[58,84,85],headach:84,header:[0,1,2,3,4,5,6,7,8,9,10,12,13,14,68,70,76],headless:26,headless_no_windows_need:26,heavili:25,hei:84,height:[9,18,22,23,24,26,28,29,31,32,35,41,42,45,48,50,51,56,59,62,63,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84],held:[33,88],hello:[68,69,70,76,77,78,85],help:[16,22,25,26,28,32,33,37,38,39,48,50,51,53,54,58,59,61,64,65,70,86,87,88],helper:63,henc:29,here:[18,23,25,26,34,35,39,41,44,50,51,58,59,60,64,65,66,68,70,84,85,86,87,88,89],hex:20,hflip:[18,57],hi:[58,62],hidden:[23,25,39],hide:[33,39,44,48,58],high:[0,23,26,50,67,69,84],high_frequ:[32,47],higher:[16,23,37,47,50,52,59,63,64],highest:44,highgui:44,highli:[27,51],him:[58,62],hint:[23,84],hit:[25,26,58,61,84,85,87,88],hitbox:[58,66],hkey_local_machin:37,hline:30,hmm:62,hold:[1,25,28,32,33,50,56,64,65,70],holdov:64,home:33,honeydew1:21,honeydew2:21,honeydew3:21,honeydew4:21,honeydew:21,hook:50,hoonwhitecatr:[68,69,70,71,72,73,76,77,78,79,80,81],hope:64,hopefulli:[62,65,87],horizont:[18,24,25,26,29,30,39,56,58,65],horizontal_advance_i:29,horizontal_advance_x:29,hot:25,hotpink1:21,hotpink2:21,hotpink3:21,hotpink4:21,hotpink:21,hotplug:32,hotspot:[22,39],hour:84,how:[15,16,19,20,23,24,26,29,31,32,33,35,36,38,40,44,46,50,51,54,56,58,61,63,64,65,68,69,70,71,72,73,74,84,87,88,89],howev:[13,18,29,34,42,44,45,47,50,56,67,68,69,70,74,84],hp:[70,71,72,73,78,79,80,81],hsl:20,hsla:20,hsv:[18,20,57],hsva:20,html:[20,70,78],http:[39,56,70,78,86,89],hue:18,human:[63,74,86],humung:63,hundr:63,husano896:39,hw:[23,59],hwaccel:51,hwsurfac:[23,51,65,84],hx:47,i1:20,i1i2i3:20,i2:20,i3:20,i686:56,i:[1,5,16,17,18,22,24,26,27,32,33,35,36,38,45,57,61,63,64,65,68,70,71,72,73,79,80,81,84,85,86,87,88,89],iceberg:64,icon:[23,48],iconifi:23,icontitl:23,id3:40,id:[3,19,23,25,32,33,37,38,48,55],idea:[23,51,57,59,61,63,65,70,71,72,73,86,87],ident:[25,42,72],identif:37,identifi:[3,25,32,33,44,46,57],idiom:[58,84],idl:[24,25,27,32,38,40],idx:50,ie:[31,65,84],ignor:[25,29,35,37,38,40,51,53,84],illeg:45,illus:62,illustr:[62,65,87],im:33,imag:[12,13,16,18,20,22,23,26,28,29,30,41,46,48,50,52,56,58,59,61,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,86,87,88,89],image_fil:26,imagefil:26,imagin:[62,85,89],imgsurfac:65,immanuel:74,immedi:[23,25,32,37,38,58],immut:20,implement:[10,11,13,17,18,20,23,26,29,37,42,43,44,50,61,63,64,73,74,84,89],impli:[18,29],implicitli:[23,51],import_pygame_bas:[1,11],importantli:62,importerror:[65,86,89],impos:[33,38],imprecis:84,impress:63,improperli:41,improv:[24,28,40,58,73,84],inact:[29,38],inadequ:84,incas:31,inch:29,incident:18,inclin:36,includ:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,16,18,22,23,26,28,29,30,31,32,34,37,44,45,50,51,53,54,56,58,61,63,64,65,67,70,74,84,86,87],inclus:[1,4,20,25,29,38],incom:16,incomplet:53,incorrect:43,increas:[23,29,38,50,68,72,74,84],incred:[74,84],increment:[11,56,65],inde:4,indefinit:[38,40],indent:[32,61],independ:[58,86],index:[1,7,16,18,19,23,29,32,33,42,43,44,45,47,48,49,50,51,52,55,62,65,84],indexerror:[30,35,37,51],indexexcept:17,indexoutofbound:50,indianr:21,indianred1:21,indianred2:21,indianred3:21,indianred4:21,indic:[12,23,24,25,30,35,37,39,40,42,45,46,49,50,52,53,65],indigo:21,indirect:18,individu:[16,19,29,35,44,51,53,58,84],ineffici:58,inequ:25,infinit:[58,63,68],inflat:[29,45,58,66,84,89],inflate_ip:45,influenc:23,info:[5,23,37,59,86],inform:[6,14,16,17,18,19,23,28,32,33,36,37,39,47,51,52,53,55,59,62,64,65,68],inherit:[50,51,64,86,87],init:[1,18,19,22,23,24,27,28,29,32,37,38,39,44,46,47,53,54,58,59,62,63,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,85,89],initi:[16,18,19,23,24,25,27,28,29,32,33,34,37,38,44,46,47,49,50,51,54,57,59,63,64,66,68,69,77,87],initialis:[32,44,47,61,85,88,89],innat:74,inner:72,input:[16,18,23,25,26,32,33,39,48,51,53,56,57,61,63,66,67,68,69,72,73,74,78,84,87],input_onli:48,inputimag:26,insensit:[43,51],insert:[33,69,73],insid:[22,24,26,31,44,45,50,51,57,58,63,65,71,72,89],insight:84,inspir:16,instal:[16,18,26,58,65,84],instanc:[2,3,4,5,6,7,8,9,12,13,17,19,25,26,29,30,32,35,38,42,45,50,51,58,61,64,85,87,88],instance_id:[25,32],instead:[9,15,20,22,23,24,25,26,28,29,32,33,36,38,42,44,50,51,53,56,62,64,65,69,84,87],instruct:56,instrument:37,instrument_id:37,int32:65,int8:65,int_valu:51,integ:[1,9,14,17,19,20,23,24,26,29,30,31,32,33,37,38,39,40,42,43,44,45,47,49,50,51,52,53,54,55,65,71,73],integr:18,intend:[43,50,51],interact:[6,16,26,33,34,42,47,50,53,63,65,70,87],interest:[25,46,63,67,74,84],interf:37,interfac:[1,17,20,29,33,37,38,42,43,47,51,57,63,70,73],interior:56,intern:[0,13,23,25,27,38,44,46,51,58,68,70,84,89],interpol:[20,30,36],interpret:[15,26,29,44,61,84],interrupt:18,intersect:[35,45,50,64,67],interv:33,intial:[58,66],intimid:65,intric:63,intro_bal:63,introduc:[23,25,43,61,62,65,87,89],introduct:[16,60,67,75],introspect:43,intuit:70,invalid:[1,23,38,45,47,56],invalu:87,inverse_set:56,invert:[29,35,36,42],invis:85,invok:[17,29,88],involv:[16,26,29,51,64,84,87,89],inward:24,ip:45,irc:84,is_control:47,is_norm:36,iscaptur:25,ish:24,isn:[19,27,48,57,61,62,64,69,71,84,88],isol:[51,57],issu:[23,62,63],ital:[28,29],item:[29,30,42,43,50,62,64],items:42,iter:[28,29,42,50,64,85,88,89],its:[2,10,13,17,18,23,24,25,29,30,32,35,36,37,38,42,44,45,46,48,50,51,58,59,61,62,63,64,68,69,70,84,85,86,87,88,89],itself:[13,18,28,29,36,42,43,44,48,51,58,61,62,63,64,87,88,89],ivori:21,ivory1:21,ivory2:21,ivory3:21,ivory4:21,iyuv_overlai:41,j:[33,35,73,81],jaggi:56,jid:32,jitter:32,job:[63,64],joi:25,join:[31,45,58,66,86,89],joint:24,journal:84,joyaxismot:[25,32],joyballmot:[25,32],joybuttondown:[25,32],joybuttonup:[25,32],joydevicead:[25,32],joydeviceremov:[25,32],joyhatmot:[25,32],joystick:[16,25,26,47,63,84,85,88],joystick_count:32,jp:18,jpeg:[31,84],jpg:[31,63],jumbl:84,jump:[39,62,63,65,84],just:[24,26,29,30,32,35,38,40,41,50,51,52,53,56,57,58,59,60,63,64,65,67,68,69,70,71,73,84,85,87,88,89],k:[33,36],k_0:33,k_1:33,k_2:33,k_3:33,k_4:33,k_5:33,k_6:33,k_7:33,k_8:[33,70,78],k_9:33,k_:[33,34,70],k_a:[33,70,78,89],k_ac_back:33,k_ampersand:33,k_asterisk:33,k_at:33,k_b:33,k_backquot:33,k_backslash:33,k_backspac:33,k_break:33,k_c:33,k_capslock:33,k_caret:33,k_clear:33,k_colon:33,k_comma:33,k_d:33,k_delet:[33,70,78],k_dollar:33,k_down:[33,70,71,72,73,78,79,80,81,88,89],k_e:33,k_end:33,k_equal:33,k_escap:[22,33,57,58,66],k_euro:33,k_exclaim:33,k_f10:33,k_f11:33,k_f12:33,k_f13:33,k_f14:33,k_f15:33,k_f1:33,k_f2:33,k_f3:33,k_f4:[33,70],k_f5:33,k_f6:33,k_f7:33,k_f8:33,k_f9:33,k_f:[33,84],k_g:33,k_greater:33,k_h:33,k_hash:33,k_help:33,k_home:33,k_i:33,k_insert:33,k_j:33,k_k:33,k_kp0:33,k_kp1:33,k_kp2:33,k_kp3:33,k_kp4:33,k_kp5:33,k_kp6:33,k_kp7:33,k_kp8:33,k_kp9:33,k_kp_divid:33,k_kp_enter:33,k_kp_equal:33,k_kp_minu:33,k_kp_multipli:33,k_kp_period:33,k_kp_plu:33,k_l:[33,70,78],k_lalt:33,k_lctrl:[33,70,78],k_left:[33,70,78],k_leftbracket:33,k_leftparen:33,k_less:33,k_lmeta:33,k_lshift:33,k_lsuper:33,k_m:33,k_menu:33,k_minu:33,k_mode:33,k_n:33,k_numlock:33,k_o:33,k_p:33,k_pagedown:33,k_pageup:33,k_paus:33,k_period:33,k_plu:33,k_power:33,k_print:33,k_q:33,k_question:33,k_quot:33,k_quotedbl:33,k_r:33,k_ralt:33,k_rctrl:33,k_return:33,k_right:[33,70,78],k_rightbracket:33,k_rightparen:33,k_rmeta:33,k_rshift:33,k_rsuper:33,k_scrollock:33,k_semicolon:33,k_slash:33,k_space:33,k_sysreq:33,k_t:[33,84],k_tab:33,k_u:33,k_underscor:33,k_up:[33,70,71,72,73,78,79,80,81,88,89],k_v:33,k_w:33,k_x:33,k_y:33,k_z:[33,89],kanji:29,kant:74,kde:44,keep:[13,17,25,32,50,54,56,57,58,59,60,61,62,63,64,84,88],kei:[16,17,20,22,23,25,26,33,34,35,45,51,55,57,58,61,63,64,66,70,71,72,73,74,78,79,80,81,84,88,89],kern:[28,29],kewdown:78,key_cod:33,key_rect:45,keyboard:[16,25,26,34,39,62,63,67,70,84,85],keycod:33,keydown:[22,25,33,34,57,58,62,66,70,71,72,73,78,79,80,81,88,89],keypad:33,keypress:84,keyup:[25,33,34,70,88,89],keyword:[16,18,24,25,26,35,38,44,48,51,53,56],khaki1:21,khaki2:21,khaki3:21,khaki4:21,khaki:21,khz:49,kick:61,kill:[50,53,64],kind:[37,43,51,61,65,67,84],kmod_alt:33,kmod_cap:33,kmod_ctrl:33,kmod_lalt:33,kmod_lctrl:33,kmod_lmeta:33,kmod_lshift:33,kmod_meta:33,kmod_mod:33,kmod_non:33,kmod_num:33,kmod_ralt:33,kmod_rctrl:33,kmod_rmeta:33,kmod_rshift:33,kmod_shift:33,know:[18,28,31,33,46,57,58,62,64,65,67,68,69,70,73,87,88,89],knowledg:[74,84],known:[33,84],korean:16,kwarg:[35,48,50,51],kwd:53,l:[32,33],l_margin:[73,81],lack:23,laggi:38,laid:[29,61],landscap:62,lang:84,languag:[26,28,33,62,63,84],lantinga:63,laplacian:56,larg:[16,29,43,45,51,61,64,65,87],larger:[23,38,51,56,58,65,67],largest:[35,57,59,63,65],last:[24,25,29,30,32,50,51,52,54,58,62,63,64,65,68,84],lastli:[22,50,58,59,62,64,65],late:84,latenc:[37,38,84],later:[18,23,30,37,38,42,46,50,57,58,59,60,62,63,64,65,68,86,89],latest:[29,70],latin1:[28,29],lavend:21,lavenderblush1:21,lavenderblush2:21,lavenderblush3:21,lavenderblush4:21,lavenderblush:21,lawngreen:21,layer1:50,layer1_nr:50,layer2:50,layer2_nr:50,layer:[50,63,64],layer_nr:50,layereddirti:50,layeredupd:50,layout:[17,28,29,43,52,88],lbm:31,leak:62,learn:[26,51,59,62,63,65,67,68,69,70,73,74,84],learnt:89,least:[14,24,30,35,43,51,55,61,65,67,70,84],leav:[24,29,51,65],left:[20,23,24,25,29,32,33,35,38,39,44,45,47,50,51,57,58,62,63,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,86,87,88,89],leftclick:26,leftmost:47,legaci:[27,67,75],lemonchiffon1:21,lemonchiffon2:21,lemonchiffon3:21,lemonchiffon4:21,lemonchiffon:21,len:[17,20,22,24,30,50,64,65],length:[1,4,9,17,19,20,23,24,25,30,32,33,36,38,42,47,51,58,68,87],length_squar:36,leonidovich:74,lerp:[20,36],less:[24,25,27,28,33,35,37,38,54,55,56,67,85],lesson:84,let:[10,20,21,26,44,51,58,59,61,63,65,69,70,71,84,88],letter:[19,28,51],level:[0,16,17,20,23,28,37,38,40,41,51,53,56,63,64,65,67,70],lgpl:16,li:84,liabil:18,liabl:18,lib:26,librari:[18,24,26,29,30,31,32,37,38,44,47,59,62,63,67],libsdl:39,licens:[16,86,89],lie:[30,45],life:[57,59],lifetim:[13,42,52,65],lightblu:21,lightblue1:21,lightblue2:21,lightblue3:21,lightblue4:21,lightcor:21,lightcyan1:21,lightcyan2:21,lightcyan3:21,lightcyan4:21,lightcyan:21,lightgoldenrod1:21,lightgoldenrod2:21,lightgoldenrod3:21,lightgoldenrod4:21,lightgoldenrod:21,lightgoldenrodyellow:21,lightgrai:21,lightgreen:21,lightgrei:21,lightpink1:21,lightpink2:21,lightpink3:21,lightpink4:21,lightpink:21,lightsalmon1:21,lightsalmon2:21,lightsalmon3:21,lightsalmon4:21,lightsalmon:21,lightseagreen:21,lightskyblu:21,lightskyblue1:21,lightskyblue2:21,lightskyblue3:21,lightskyblue4:21,lightslateblu:21,lightslategrai:21,lightslategrei:21,lightsteelblu:21,lightsteelblue1:21,lightsteelblue2:21,lightsteelblue3:21,lightsteelblue4:21,lightweight:[50,84],lightyellow1:21,lightyellow2:21,lightyellow3:21,lightyellow4:21,lightyellow:21,like:[1,10,16,18,20,22,23,24,26,27,29,31,32,33,34,35,36,38,39,40,42,44,47,50,51,56,57,58,59,60,61,62,63,64,65,68,69,70,72,73,74,84,85,86,87,88,89],lime:21,limegreen:21,limit:[18,19,23,24,25,28,29,31,32,38,40,51,52,53,54,60,63,64,65,73,84],line:[16,24,26,28,30,31,32,45,48,53,60,61,62,63,64,65,66,68,69,70,84,87,89],line_height:32,line_spac:29,linear:[20,23,36,50,65],linearli:51,linen:21,link:38,linux:[18,23,37,40,44,57,63,84],liquid:[26,62],list:[16,18,19,20,22,23,24,25,26,27,28,29,30,32,33,34,35,37,41,44,45,46,50,51,53,58,59,64,65,68,70,84],list_camera:[18,57],list_mod:[23,59],listen:39,littl:[17,22,26,44,50,54,58,59,61,62,64,65,68,84,87,88],live:[16,26,65],ll:[22,58,61,62,63,64,65,84,85,87,88,89],load:[8,10,16,18,22,26,30,31,40,50,59,61,62,63,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,87,89],load_background_imag:62,load_bas:31,load_extend:31,load_imag:[58,66],load_player_imag:62,load_png:[86,87,88,89],load_sound:[58,66,86],load_xbm:22,local:[9,16,33,34,38,39,44,57,58,60,61,68,69,70,71,72,73,76,77,78,79,80,81,85,86,89],locat:[26,34,35,39,41,58,68,69,70,71,72,73,84],lock:[13,17,24,25,27,33,42,43,50,51,52,84],lockobj:13,logger:26,logic:[48,58,61,63,65,68,69,70,71,84,86],logical_s:48,logo:23,loki:63,longer:[19,32,38,47,87,88],longest:38,look:[22,24,28,36,37,44,46,52,56,57,58,62,63,64,65,68,69,70,72,84,85,86,87,88,89],lookout:26,lookup:[23,25,28,50,58,65],loop:[24,25,32,38,40,54,57,61,62,63,64,66,68,84,86,87,88,89],lose:[19,23,33,56,84],loss:[18,23,58,59],lost:[25,40,46,62],lostsprit:64,lot:[26,32,51,54,56,58,59,61,62,63,64,65,67,73,84,85,87,89],loud:38,love:84,low:[32,63,67],low_frequ:[32,47],lower:[19,23,32,37,38,41,52,84],lowercas:28,lowest:37,lowli:84,ls:18,lt:32,luck:65,luckili:84,luma:18,m:[20,26,33,53,58,65,84],mac:[18,23,26,37,40,44,46,63,84],machin:[26,56,64,65,84],maco:22,macosx:25,macro:[3,4,5,6,7,8,9,12,14],made:[3,6,7,12,16,23,25,49,62,71,73,74,84,87],magazin:[16,71],magenta1:21,magenta2:21,magenta3:21,magenta4:21,magenta:21,magic:87,magnifi:26,magnitud:36,magnitude_squar:36,mai:[1,2,18,19,20,22,23,25,27,28,29,30,31,32,33,37,38,40,43,44,45,46,51,53,57,59,62,63,64,65,84],mail:[62,84],main:[18,24,25,26,27,32,39,41,46,50,53,57,61,62,64,65,66,71,72,73,79,80,81,85,86,87,88,89],main_dir:[58,66],mainli:[28,51,62,64,65],maintain:[28,44,50,61],major:[14,23,31,38,44,63],make:[0,16,20,22,24,25,26,27,31,38,42,44,45,50,51,52,54,56,57,58,59,60,63,64,65,67,68,70,71,72,73,74,84,85,86,87,88,89],make_sound:[38,49],make_surfac:[42,43,52],maker:[67,74],malform:29,man:67,manag:[16,19,25,32,38,42,50,51,59,63,64],mani:[19,23,24,25,28,29,33,38,39,40,44,45,50,51,54,56,58,59,62,63,84],manipul:[16,31,42,45,51,63,65,84,87],manner:[59,87],manual:[19,28,29,41,44,51,57,64,84],map:[17,20,24,32,42,43,47,51,52,59,65],map_arrai:[43,52],map_rgb:[20,24,42,51,52],mapped_int:51,margin:[26,68,71,72,73,80,81],mario:88,mark:[33,42,57,63],maroon1:21,maroon2:21,maroon3:21,maroon4:21,maroon:21,mask:[22,23,26,29,42,50,51,59,84],maskfil:22,mass:35,master:[56,65,67],match:[17,23,28,29,32,33,37,38,42,43,51,52,56,58,59,64,65,68],match_font:28,materi:18,math:[24,30,35,36,86,87,89],mathemat:[51,61,65],matter:[31,51,62,64,84,87],max:[24,32,35,57,71],max_i:29,max_x:29,maxhp:[71,72,73,79,80,81],maxi:28,maxim:[25,48],maximum:[23,28,29,84],maxtim:38,maxx:28,mayb:[62,64,70,84,86],me:[59,63,65],mean:[20,22,23,24,29,32,33,36,37,38,39,40,43,47,48,50,56,57,58,59,62,63,64,68,69,70,71,72,84,88],meant:[44,48,64,84],measur:[24,37,84,87],mechanim:67,mechanin:75,mediev:63,medium:32,mediumaquamarin:21,mediumblu:21,mediumorchid1:21,mediumorchid2:21,mediumorchid3:21,mediumorchid4:21,mediumorchid:21,mediumpurpl:21,mediumpurple1:21,mediumpurple2:21,mediumpurple3:21,mediumpurple4:21,mediumseagreen:21,mediumslateblu:21,mediumspringgreen:21,mediumturquois:21,mediumvioletr:21,meet:[45,84],megabyt:23,member:[25,50,59,64,68,84],membership:[50,64],memori:[17,23,24,29,38,51,52,62,67,84],mental:74,mention:[54,58,64,68,70,84],menu:[23,33,61],merchant:18,mercuri:44,mere:[37,84],merg:64,merrili:89,messag:[25,27,37,44,53,58,86,89],messi:64,met:[18,22],meta:33,method:[9,10,12,15,16,17,19,20,22,23,24,25,26,28,29,30,31,32,35,36,38,39,40,42,44,45,46,49,50,51,52,54,58,59,61,62,63,64,65,71,73,84,85,86,87,89],metric:[28,29],mice:39,micro:44,microsoft:23,midbottom:45,middl:[39,47,57,62,65],midi:[25,26],midi_ev:37,midi_event_list:37,midi_not:37,midi_output:37,midi_to_ansi_not:37,midi_to_frequ:37,midiexcept:37,midiin:[25,37],midiout:[25,37],midis2ev:37,midisport:37,midleft:[45,88,89],midnightblu:21,midright:[45,88,89],midtop:45,might:[20,23,25,26,29,44,46,60,61,62,64,65,84,86,87,89],mighti:62,migrationguid:39,milli:54,millisecond:[25,33,37,38,40,50,54,63,84],mime:46,mimic:29,min:[24,57],min_alpha:51,min_i:29,min_x:29,mind:[39,54,60,65],minhp:73,mini:28,miniatur:32,minim:[23,25,48,64],minimum:[23,24,28,30,31,35,51],minor:[14,31,38,44,63],mintcream:21,minu:33,minx:28,mirror:[23,58],miss:[58,66,84],missingmodul:[49,52],mistyros:21,mistyrose1:21,mistyrose2:21,mistyrose3:21,mistyrose4:21,misunderstand:84,miter:24,mix:[28,38,51,63,65,84],mix_chunk:8,mix_setmusicposit:40,mixer:[0,16,26,38,44,49,58,66],mizuno:18,mmsystem:37,mmx:[26,56],moccasin:21,mod:[25,33,34,40,67],mod_:34,modal:48,mode:[16,20,23,24,25,26,28,29,30,33,38,39,44,46,48,50,51,53,58,63,65],mode_ok:[23,59],model:51,modif:[13,18,51,86],modifi:[29,30,33,34,37,39,45,48,49,50,51,64,65,85],modnam:1,modul:[0,1,2,3,4,5,6,7,8,9,10,12,13,16,17,21,26,34,41,42,48,51,53,59,60,61,62,65,66,68,87,89],modulu:20,moment:[46,49,84,87],momma:62,monitor:[23,34,59,63,68],monkei:[58,63,66],mono:[29,38,49],monochrom:29,monster:64,month:63,moon:84,more:[16,18,19,20,23,24,25,26,28,29,30,31,32,35,36,37,38,39,42,43,44,47,50,51,54,57,58,59,60,61,62,63,64,70,71,73,74,85,86,87,88,89],most:[19,23,24,25,26,28,29,31,38,39,40,44,45,50,51,54,57,59,60,61,63,64,65,74,84,85],mostli:70,motion:[32,39,48,63,87,89],motiv:70,mous:[16,22,23,25,26,32,46,48,58,61,62,63,66,67,70,72,84,85,88],mousebuttondown:[22,25,27,39,58,66,84],mousebuttonup:[25,39,58,66,72,73,80,81,84],mousemot:[25,39],mousewheel:[25,39],movabl:[88,89],move:[16,25,26,39,44,45,50,51,56,58,61,63,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,87,88,89],move_and_draw_all_game_object:62,move_ip:[45,58,66],move_to_back:50,move_to_front:50,movedown:[70,78,88,89],moveit:[26,62],movement:[32,39,50,58,87,88],movepo:[88,89],moveright:[69,70,77,78],moveup:[69,77,88,89],movi:74,mozart:40,mp3:40,ms:[32,37,39,47],msg:37,msmf:18,much:[18,24,26,28,31,54,56,58,59,60,61,62,63,65,67,68,71,73,74,84,85,87,89],multi_thread:53,multicolor:26,multidimension:65,multigestur:25,multimedia:[33,63,84],multipl:[1,7,18,19,22,23,24,25,26,28,32,33,36,38,41,44,50,51,56,57,61,63,64,65,69],multipli:[20,24,29,33,36,42,48,56,65],multisampl:23,multithread:27,music:[8,16,26,37,38,40,63,74,86],must:[1,2,4,17,18,19,20,22,23,24,25,27,28,29,30,31,32,35,36,37,38,41,42,43,44,45,46,47,49,50,51,52,53,56,58,59,60,63,64,65,68,72],mustlock:51,mutabl:25,mute:38,my:[26,32,61,62,63,65,84,86,89],my_appl:44,my_data_typ:46,mygroup:64,myscreen:[68,69,70,71,72,73,76,77,78,79,80,81],mysprit:64,mysurf:51,mytext:[68,69,70,71,72,73,76,77,78,79,80,81],mytextarea:[68,69,70,71,72,73,76,77,78,79,80,81],mytextfont:[68,69,70,71,72,73,76,77,78,79,80,81],n:[28,33,38,65],name:[1,10,18,19,20,23,25,26,28,29,32,33,37,44,45,47,50,51,53,58,59,61,62,63,64,65,66,68,70,86,88,89],name_forindex:47,name_of_environment_vari:44,namehint:[31,40],namespac:[34,58,60],nano:31,nasti:86,nativ:[16,18,22,44,57,84],natur:[40,65,74,84,86],navajowhit:21,navajowhite1:21,navajowhite2:21,navajowhite3:21,navajowhite4:21,navi:21,navyblu:21,ndim:42,nearest:[24,38],nearli:[64,67],neater:88,necessari:[23,25,27,29,38,51,60,61,64,68,84,85,87,88,89],necessarili:63,need:[11,16,18,19,22,23,25,26,27,28,31,32,33,35,42,44,45,46,50,51,53,57,58,59,60,62,63,64,65,66,67,68,69,70,71,72,73,85,86,87,88,89],needless:[68,71,73,84],needn:[26,61,89],neg:[9,10,17,25,29,35,36,38,39,40,42,45,51,56,65,89],neglig:18,neither:[50,69,70],nest:51,net:[86,89],network:[61,86],never:[29,32,38,39,40,51,54,57,63,65,84],new_height:26,new_lay:50,new_mask:35,new_width:26,newarrai:42,newbi:16,newcom:84,newer:[23,40],newest:68,newli:[20,35],newlin:28,newpo:[58,66,87,88,89],newrect:64,newtonian:63,next:[18,27,29,30,35,36,38,41,46,58,64,68,73,85,88,89],nice:[58,61,62,65,87],nirav:57,node:44,noevent:[25,27,38,40,84],nofram:23,nois:[32,35,57],nomin:29,non:[17,19,23,24,26,29,31,33,41,42,44,50,51,56,58,64,84,86],none:[17,18,19,20,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,56,58,60,65,66,85,86,87,89],nonesound:[58,66],nonetheless:26,nonetyp:[30,35,50],nonlinear:51,nonzero:[35,45],nor:[50,70],normal:[9,12,19,20,28,29,35,36,44,45,50,51,53,55,58,64,65,71,87,89],normalize_ip:36,north:22,northeast:22,northwest:22,nosubprocess:53,notabl:[23,40,87],notat:39,notdef:29,note1:35,note2:35,note:[18,19,20,23,24,25,26,27,29,31,32,33,35,36,37,38,39,40,42,44,45,47,54,56,57,58,59,60,62,64,66,84,86,87,89],note_off:37,note_on:37,noteworthi:63,noth:[19,23,24,30,32,50,58,59,63,64,84,85,86],notic:[18,36,45,62,68,69,70,72,84,87,88,89],notifi:64,notimplementederror:[33,40],novel:74,now:[26,27,28,29,32,33,35,37,38,43,44,46,56,57,58,61,62,63,64,65,67,68,69,70,71,72,73,74,84,85,87,89],nowadai:63,nrp:57,num:[33,72,73,80,81],num_button:39,num_devic:37,num_ev:37,num_fing:25,num_threshold_pixel:56,num_track:19,number:[1,8,11,17,18,19,20,23,24,25,26,28,29,30,31,32,33,35,36,37,38,40,42,44,47,49,50,51,52,53,54,55,56,62,64,65,69,73,74,84],numbit:35,numer:[36,38],numev:[25,38],numfail:44,numlock:33,numpass:44,numpi:[1,16,26,42,43,49,51,52,63],o:[7,22,33,62],obj:[1,2,4,5,8,9,10,44],object:[1,2,4,5,6,7,9,10,12,13,16,18,19,21,22,23,24,25,26,27,28,29,30,31,32,35,38,39,40,43,44,46,47,48,49,52,54,56,57,59,61,62,63,64,65,66,68,69,70,86,89],obliqu:29,obscur:[61,84],obsolet:[23,44,51],obtain:[29,37],obviou:61,obvious:[64,85],occasion:64,occupi:85,occur:[29,33,37,38,42,68],octob:63,odd:[24,65,89],ofcod:89,off:[18,24,25,26,29,32,37,38,40,45,56,58,61,62,64,84,89],offcourt:89,offer:[22,23,25,47,57],offici:62,offset:[17,24,26,28,29,30,35,37,40,42,45,50,51,58],often:[19,31,36,51,61,63,65,69,84,86],ogg:[38,40],oh:89,ok:[65,84,85,87],okai:[69,72],old:[23,39,44,50,51,54,58,62,64,74,84,88],older:[27,32,40,44,64],oldest:68,oldlac:21,oliv:21,olivedrab1:21,olivedrab2:21,olivedrab3:21,olivedrab4:21,olivedrab:21,omit:[29,37,42,53],onc:[18,19,23,25,28,29,31,32,33,35,37,38,40,42,44,47,51,53,54,58,60,63,64,65,84,85,86,87,89],one:[17,18,20,22,23,24,25,26,27,28,29,30,31,32,33,35,38,39,40,42,43,44,45,46,47,49,50,51,52,54,56,57,58,59,60,61,62,63,64,65,67,68,69,70,71,73,84,85,86,87,88,89],ones:[29,40,51,60,64,87,88],onli:[3,17,18,19,20,23,24,25,28,29,31,32,34,35,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,53,54,56,57,58,59,60,61,62,63,64,65,67,68,72,73,74,84,85,86,87,89],ons:63,onscreen:23,onto:[12,20,27,28,29,30,35,36,50,51,58,62,63,65,68,84,85,87],ooo:84,opac:[48,84],opaqu:[20,26,29,35,43,48,51,52,65,84],open:[1,7,10,16,18,19,23,25,32,33,37,44,46,47,57,58,63,85,86],opencv:[18,44,57],opengl:[23,26],opentyp:29,oper:[15,20,22,23,24,25,27,33,36,42,44,45,48,50,51,52,56,64,65,85],operand:65,oppos:29,optim:[23,26,28,51,56,64,65],optimis:84,option:[1,17,18,20,23,24,26,28,29,30,31,32,35,37,38,39,40,43,44,50,51,53,54,56,57,58,60,62,65,84],orang:21,orange1:21,orange2:21,orange3:21,orange4:21,orangered1:21,orangered2:21,orangered3:21,orangered4:21,orchid1:21,orchid2:21,orchid3:21,orchid4:21,orchid:21,order:[1,18,23,28,29,33,37,38,40,42,44,50,51,53,54,58,62,65,68,84,89],orderedupd:50,ordinari:64,org:[39,58,59,60,62,63,64,65,70,78],organ:[16,50,64,84],organis:61,orient:[29,35,62],origin:[10,20,22,23,24,26,29,31,32,35,42,45,47,48,50,51,56,58,62,63,64,65,66],original_color:56,original_dest_color:56,orthogon:35,os:[10,18,23,26,31,32,37,44,46,58,66,84,86,89],oss:37,osx:[23,63],other:[0,18,19,20,23,24,25,26,27,28,29,30,31,32,33,35,36,38,40,42,43,44,45,46,48,49,50,51,52,53,54,56,57,58,61,62,63,64,68,70,71,84,85,86,87,88,89],otherarrai:42,othermask:35,othersurfac:35,otherwis:[1,2,6,10,13,17,18,22,23,24,26,29,31,35,36,37,38,39,43,45,46,50,51,52,56,62,72,73,89],ouput:[68,76],our:[58,62,63,65,66,74,85],out:[18,24,29,32,35,37,38,40,57,58,59,61,62,64,65,84,85,86,87,88,89],outdat:67,outer:72,outgo:37,outlin:[24,29,35,57],outpng:26,output:[18,25,26,28,32,35,56,67,69,70,73,74,84,88],outputimag:26,outsid:[24,28,30,33,35,39,45,51,56,58,63,68],over:[22,23,29,35,38,40,42,44,47,50,57,60,62,63,64,65,66,84,86],overal:65,overboard:86,overcom:67,overflow:65,overhead:[51,64,84],overlai:64,overlap:[35,45,50,62,64,84,87,89],overlap_area:35,overlap_mask:35,overlin:29,overrid:[18,29,35,44,50,51,63,84],overridden:[23,29,35,38,45],overwrit:[17,24,26,32,47],overwritten:[24,32,47,51],own:[13,22,25,26,27,37,38,46,48,59,61,63,65,84,86],own_data_typ:46,owner:13,ownership:46,p1:48,p2:48,p:[31,33,43],pac:64,pack:[23,51,52,59],packag:[16,26,28,29,30,31,49,52,58,60,63,65],pacman:64,pad:[29,32,56],page:[33,39,46,65],pai:70,pain:84,painless:86,paint:[50,85],pair:[13,23,24,28,29,31,32,45,51],pajitnov:74,palegoldenrod:21,palegreen1:21,palegreen2:21,palegreen3:21,palegreen4:21,palegreen:21,palett:[23,28,31,35,51,52,56],palette_color:[35,56],paleturquois:21,paleturquoise1:21,paleturquoise2:21,paleturquoise3:21,paleturquoise4:21,palevioletr:21,palevioletred1:21,palevioletred2:21,palevioletred3:21,palevioletred4:21,panic:84,papayawhip:21,paper:84,parallax:84,param:24,paramet:[18,20,23,24,25,26,29,30,31,33,35,36,37,38,39,40,44,45,46,51,55,56,57,70,71],parametr:36,parent:[2,13,17,48,50,51],parenthesi:33,pars:[84,87],part:[23,24,25,29,30,35,51,53,56,62,63,64,65,68,69,70,72,86],parti:[18,63],partial:[23,24,37,51,84],particular:[18,29,40,42,49,50,53,54,59,84],particularli:37,pass:[9,10,15,17,18,19,20,22,23,24,25,26,28,29,31,32,35,37,38,39,40,41,42,44,46,50,51,53,54,56,58,62,63,64,66,86],past:[24,46,58,63,84],patch:[14,26,31,38,44,63],patel:57,path:[7,10,16,25,26,28,29,31,36,38,44,53,57,58,66,86,89],pathlib:[28,29,31,38],pathlib_path:38,pathnam:[38,58,86],pattern:[29,40],paus:[19,26,33,38,40,54,86],pbm:[31,46],pc:[17,37],pcf:29,pci:65,pcx:31,peachpuff1:21,peachpuff2:21,peachpuff3:21,peachpuff4:21,peachpuff:21,peek:25,pellet:64,penalti:[51,59,64],pend:37,peopl:[16,58,61,62,63,84],per:[16,24,25,29,30,31,32,35,49,50,51,52,53,54,58,59,63,65,69,77,84,88,89],perfect:35,perfectli:84,perform:[12,23,25,26,28,29,30,35,36,38,42,50,51,52,56,57,59,64,65,84,85,88],perhap:[62,64,84,86],period:[33,40],permiss:23,permit:[15,18,29],person:[62,63,88],perspect:[26,63],peru:21,pete:[58,59,60,62,63,64,65,84],pfr:29,pg:[22,58,66],pg_buffer:[1,2],pg_encodefilepath:10,pg_encodestr:10,pg_floatfromobj:1,pg_floatfromobjindex:1,pg_getdefaultwindow:1,pg_getdefaultwindowsurfac:1,pg_intfromobj:1,pg_intfromobjindex:1,pg_major_vers:14,pg_minor_vers:14,pg_mod_autoinit:1,pg_mod_autoquit:1,pg_patch_vers:14,pg_registerquit:1,pg_rgbafromobj:[1,11],pg_setdefaultwindow:1,pg_setdefaultwindowsurfac:1,pg_twofloatsfromobj:1,pg_twointsfromobj:1,pg_uintfromobj:1,pg_uintfromobjindex:1,pg_version_atleast:14,pg_versionnum:14,pg_view_p:1,pgbuffer_asarrayinterfac:1,pgbuffer_asarraystruct:1,pgbuffer_releas:1,pgbufproxy_check:2,pgbufproxy_getpar:2,pgbufproxy_new:2,pgbufproxy_trip:2,pgbufproxy_typ:2,pgcd_asid:3,pgcd_check:3,pgcd_new:3,pgcd_type:3,pgcdobject:3,pgchannel_asint:8,pgchannel_check:8,pgchannel_new:8,pgchannel_typ:8,pgchannelobject:8,pgcolor_check:4,pgcolor_new:4,pgcolor_newlength:4,pgcolor_typ:4,pgdict_asbuff:1,pgevent_check:6,pgevent_filluserev:6,pgevent_new2:6,pgevent_new:6,pgevent_typ:6,pgeventobject:6,pgexc_buffererror:1,pgexc_sdlerror:1,pgfont_check:7,pgfont_is_al:7,pgfont_new:7,pgfont_typ:7,pgfontobject:7,pglifetimelock_check:13,pglifetimelock_typ:13,pglifetimelockobject:13,pgm:31,pgobject_getbuff:1,pgrect_asrect:9,pgrect_fromobject:9,pgrect_new4:9,pgrect_new:9,pgrect_norm:9,pgrect_typ:9,pgrectobject:9,pgrwops_fromfileobject:10,pgrwops_fromobject:10,pgrwops_getfileextens:10,pgrwops_isfileobject:10,pgrwops_releaseobject:10,pgsound_aschunk:8,pgsound_check:8,pgsound_new:8,pgsound_typ:8,pgsoundobject:8,pgsurface_assurfac:12,pgsurface_blit:12,pgsurface_check:12,pgsurface_lock:13,pgsurface_lockbi:13,pgsurface_locklifetim:13,pgsurface_new:12,pgsurface_prep:13,pgsurface_typ:12,pgsurface_unlock:13,pgsurface_unlockbi:13,pgsurface_unprep:13,pgsurfaceobject:[1,12,13],pgvidinfo_asvidinfo:5,pgvidinfo_check:5,pgvidinfo_new:5,pgvidinfo_typ:5,pgvidinfoobject:5,pgyam:[0,5],phase:[69,70],phi:36,photo:44,photograph:56,physic:[17,23,32,61,63,86,88,89],pi:[24,89],pick:[23,44,56,59,62,64],pictur:[31,63],pie:30,piec:[29,84],pile:26,pinch:25,pink1:21,pink2:21,pink3:21,pink4:21,pink:21,pipe:23,pitch:[37,51,62],pitch_bend:37,pixel2d:65,pixel3d:65,pixel:[16,17,18,20,22,23,24,26,28,29,30,31,35,41,45,48,51,56,58,59,63,65,85,88,89],pixel_arrai:42,pixelarrai:[16,26,42,51],pixelcopi:[26,42,43,52],pixelformat:18,pixels2d:[52,65],pixels3d:[52,65],pixels_alpha:[52,65],pixels_blu:52,pixels_green:52,pixels_r:52,pixels_within_threshold:56,place:[1,23,25,27,29,34,36,37,39,42,44,45,46,50,51,52,53,58,62,63,64,65,68,84,87,88],placehold:44,placement:44,plai:[8,16,19,23,26,32,37,40,47,58,62,63,64,66,70,74,84,86,89],plain:46,plan:[51,63],plane:[41,51],plant:65,platform:[16,18,23,24,25,30,33,35,37,38,41,44,46,53,54,57,58,59,63,84,86,88],playabl:49,playback:[19,26,38,40,63],player1:[61,89],player2:89,player:[26,50,58,61,62,64,68,70,73,74,84,88,89],playerimag:62,playerpo:62,playersprit:89,playmu:26,playstat:32,pleas:[23,44,84],plenti:61,plot:61,plu:[28,29,33,37,44,64],plug:[25,32],plum1:21,plum2:21,plum3:21,plum4:21,plum:21,pm_recommended_input_devic:37,pm_recommended_output_devic:37,pmdeviceid:37,png:[26,31,58,63,65,66,67,68,69,70,75,76,77,78,84,86,87,88,89],pnm:31,po:[25,27,35,40,50,58,62,66,72,73,80,81],point:[15,17,19,20,22,24,26,28,29,30,35,36,37,45,48,50,56,57,62,63,64,65,67,72,84,89],pointer:[9,12,58,84],polar:36,polish:[22,63],poll:[25,27,37,84],polygon:[24,30],pong:[61,86,89],poor:61,poorli:61,pop:44,popular:[26,32,66],port:[18,26,37,44,48],portabl:[33,37,63],portion:[12,23,45,48,51,56,63,64],portmidi:37,posit:[1,9,17,19,22,23,24,25,26,28,29,30,32,33,35,38,39,40,44,45,48,50,51,53,55,57,58,62,63,64,66,68,69,84,85,87,88,89],possibl:[18,23,28,29,31,32,50,51,57,58,61,63,65,67,68],post:[25,27,38,47,54],potenti:[31,64,84,88],powderblu:21,power:[18,32,33,38,64,74],ppem:29,ppm:[31,46],pr:39,practic:[84,86],pre:[20,29],pre_init:38,prealloc:51,prebuilt:16,preced:[35,68],precis:[23,38,51,65,84],predecessor:[65,84],predefin:[25,46],predomin:43,prefer:[23,30,31,33],prefix:58,prematur:84,premul_alpha:20,prepar:[26,40,66],present:[44,48,60,63,64],preserv:[20,44,45,51],preset:[22,38],press:[26,32,33,39,47,57,58,62,70,71,84],pressur:55,pretend:62,pretti:[59,62,64,65,84,85,88,89],prevar:84,prevent:[25,89],prevent_display_stretch:23,previou:[1,23,29,38,39,48,50,54,56,58,62,64,65,69,70,72,73,85,89],previoulsi:40,previous:[18,20,25,28,29,31,38,40,44,59,85],primari:39,primarili:[29,44,48],prime:48,primer:65,primit:30,principl:[88,89],print:[25,28,32,33,36,39,44,45,46,57,58,59,62,66,68,70,71,72,73,76,84,86,89],printboard:[73,81],printf:[67,75],prior:23,prioriti:18,privat:[51,58],probabl:[26,62,64,68,84,85],problem:[43,44,58,61,69,84,86],procedur:[67,68],process:[18,25,26,27,32,46,52,53,54,60,62,67,68,70,72,73,77,84],processor:[54,56],procur:18,produc:[29,84],product:[36,61],profil:[23,84],profit:18,program:[16,19,23,25,27,32,33,37,38,41,44,45,53,54,58,61,62,63,64,65,66,67,68,69,70,71,73,74,84,85,86,87,88],programm:[63,64,84,86,87],programmat:44,progress:43,project:[26,36,61,63,67,68,69,70,71,72,73,76,77,78,79,80,81,84,86,87,89],prolog:73,promis:[58,62],prompt:[44,63,65],proper:[29,33,38,39,64,65],properli:[10,16,25,39,53,58,62,64,65,84],properti:[17,25,29,39,50,52],propos:63,protect:[26,58],protocol:[2,16,43,51],proud:62,provid:[16,18,20,23,26,27,28,29,33,35,36,38,41,42,44,45,50,51,53,54,56,57,58,67,84,86,87],proxi:[2,17],ps4:32,ps:[26,32],pseudo:87,pt:56,pull:[18,58,66],pummel:[58,66],pump:[25,27,32,88,89],punch:[58,63,66],punch_sound:[58,66],punchabl:58,punctuat:28,pure:[44,51],purpl:21,purple1:21,purple2:21,purple3:21,purple4:21,purpos:[1,18,48,57,61],push:[33,71,88],pushabl:32,put:[32,46,54,60,61,63,64,66,68,85,86,87],puyopuyo:[67,75],pxarrai:42,py:[16,23,26,44,46,53,56,61,62,63,65],py_buff:[1,17],pybuf:1,pybuffer_releaseproc:1,pycdio:19,pygam:[2,11,15,21,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,89],pygame_blend_add:12,pygame_blend_alpha_sdl2:[12,44],pygame_blend_max:12,pygame_blend_min:12,pygame_blend_mult:12,pygame_blend_premultipli:12,pygame_blend_rgba_add:12,pygame_blend_rgba_max:12,pygame_blend_rgba_min:12,pygame_blend_rgba_mult:12,pygame_blend_rgba_sub:12,pygame_blend_sub:12,pygame_bufferproxi:2,pygame_camera:44,pygame_displai:44,pygame_force_scal:44,pygame_freetyp:[7,28,44],pygame_hide_support_prompt:44,pygame_mix:8,pygameapi_base_numslot:11,pygamevers:44,pyobject:[1,2,3,4,5,6,7,8,9,10,12,13],pyopengl:[26,31,63],pypi:42,pyportmidi:37,pysdl:[63,84],pythagorean:[36,69],python26:26,python2:26,python3:18,python:[1,2,3,4,5,6,7,8,9,10,12,13,15,16,17,18,19,22,26,28,29,31,38,43,44,45,50,51,53,58,60,61,62,64,66,67,68,85,86,87],pytypeobject:[2,3,4,5,8,9,12,13],pyunicode_asencodedstr:[10,44],q:33,qce:18,quadrant:24,quadruplet:[24,30],quake3:63,qualiti:[26,44,58,63],quaternion:67,queri:[23,37,46,59],query_imag:[18,57],question:[33,62,84],queu:[25,38,40],queue:[6,18,23,32,33,34,37,38,39,40,47,54,58,63,84,88],quick:[25,26,32,51,53,60,64,65,84,88],quicker:[50,51,56,64],quickest:51,quickli:[31,58,59,62,63,64,65,84],quietli:25,quit:[1,19,22,23,24,25,26,28,29,32,37,38,39,44,47,51,53,57,58,62,63,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,85,88,89],quiz:73,quot:33,quotedbl:33,r:[9,20,26,30,32,33,36,42,43,51,56,64,65,71,72,73,79,80,81],r_margin:[72,73,80,81],radial:[36,65],radian:[24,30,36,87,89],radii:[24,30],radiu:[24,30,50],radom:74,rais:[1,2,3,4,5,6,7,8,9,10,15,17,20,22,23,24,25,26,28,29,30,31,33,35,36,37,38,40,43,44,45,46,48,49,50,51,52,56,57,60,65,86,89],ramp:23,ran:85,rand:89,randint:[73,81,89],random:[53,61,73,74,81,86,89],randomli:73,rang:[20,23,24,25,28,29,32,35,36,37,38,42,44,47,50,57,62,63,65,68,71,72,73,79,80,81],rank:84,rapid:32,rapidli:[33,62],rare:[44,51,84],rate:[38,40,50,69,84],rather:[28,29,30,33,44,50,54,56,69,85,87],ratio:[45,50],raw:[17,31,42,43,51,52,65],rb:46,re:[22,26,29,44,56,59,61,62,64,71,85,87,88],reach:63,read:[10,17,20,25,26,29,37,39,46,48,50,51,61,62,65,84],readabl:[20,38,68,86],readi:[18,22,32,48,53,57,58,62,63],readlin:22,readm:16,readonli:50,real:[25,28,29,37,38,51,57,59,62,64,70,84],realist:[86,89],realiti:59,realiz:[63,84],realli:[23,41,56,58,59,62,63,64,65,70,85,87,88],realtim:[51,63,65],reason:[23,27,28,29,38,39,56,57,61,62,64,84,87],rebel:63,rebind:47,recalcul:29,recap:61,receiv:[23,27,32,33,39,47,84],recent:[58,63,64,65],reciev:23,recogn:[17,25,29,43,45,56],recogniz:31,recommend:[23,25,27,28,50,51],recommended_input_devic:37,recommended_output_devic:37,recompil:89,record:[18,29,52,53,72,84],recreat:[26,50,53],rect1:45,rect2:45,rect:[0,16,24,29,30,33,35,41,45,48,50,51,56,57,58,62,63,64,66,71,72,73,79,80,81,86,87,88,89],rect_area_pt:56,rect_list:50,rect_sequ:45,rectangl:[9,16,23,24,26,28,29,30,33,45,48,50,51,56,64,68,84,85,87,89],rectangle_list:23,rectangular:[9,29,30,35,42,48,50,51,62,63,71,84],rectstyl:84,red1:21,red2:21,red3:21,red4:21,red:[1,18,20,21,23,24,31,42,50,51,52,65,68,69,70,71,72,73,76,77,78,79,80,81,85],redimg:65,redistribut:18,redraw:[41,62],redrawn:[23,41],reduc:[29,38,64,88],reentrant:53,ref:[70,78],refcount:10,refer:[1,13,17,23,24,31,33,35,42,46,47,49,51,52,57,58,59,62,63,64,65,68,69,70,71,72,73,84,88],referenc:[52,58,64,65],reflect:36,reflect_ip:36,regard:[24,68],regardless:[28,44],region:[31,51,56,86],regist:[1,44,46,87],register_quit:44,registri:37,regular:[16,23,24,28,40,41,50,51,56,64],regularli:32,reinit:[88,89],reiniti:38,reinitialis:32,rel:[25,32,37,39,40,41,48,50,61,86],relat:[1,23,29,34,39,43,47,50,84],relationship:50,relative_mous:48,releas:[1,13,17,18,23,29,30,32,33,37,39,42,44,46,51,58,63,84,86,88,89],release_buff:1,relev:[29,84],reli:[9,88],reliabl:[18,23,37,84],remain:[20,40,45,51,52,85],remap:[42,47],rememb:[25,39,50,51,59,61,63,64,65,68,73,84,86,87],remind:68,remov:[13,17,18,25,27,28,32,36,48,50,56,58,64,89],remove_intern:64,remove_sprites_of_lay:50,renam:47,render:[16,23,24,26,32,41,48,50,58,63,66,68,69,70,71,72,73,76,77,78,79,80,81,84,85,87,88],render_raw:29,render_raw_to:29,render_to:29,renderclear:[50,64],renderplain:[50,58,64,66,89],renderupd:[26,50,64],renderupdatesdraw:64,repaint:[25,27,50],repaint_rect:50,repcolor:42,repeat:[23,33,35,38,40,44,61],repeatedli:[50,51,54,56,58],replac:[1,23,27,29,38,42,43,44,51,52,54,62,65,68],report:[26,32,44,56,84],repositori:[44,84],repr:44,repres:[1,12,13,14,16,17,19,20,22,23,24,25,29,31,32,33,35,38,39,40,41,44,45,47,48,52,54,56,58,62,63,64,65,84,87],represent:[1,4,9,16,21,24,30,42,56,58],reproduc:18,request:[1,23,26,28,29,35,38,49,51,52,59],requir:[17,18,23,25,26,28,31,33,35,36,37,38,44,45,46,47,50,51,55,56,59,61,62,63,64,65,68,70,84,85,86,88],resampl:38,rescal:26,resembl:[26,43,52],reserv:[25,37,38],reset:[29,32,37,38,40,50,58,89],resist:84,resiz:[16,23,25,26,35,45,48,56,58,85],resolut:[23,29,41,51,54,56,58,59],resolv:[46,68,87],resourc:[26,38,40,44,59,61,66,87],respect:[1,22,23,24,33,35,36,46,50,51],respond:[25,27,62],respons:53,rest:[19,25,27,57,58,62,63,65,84],restart:[40,86],restor:[23,25,48],restrict:51,result:[20,24,28,29,35,36,37,40,42,51,53,56,58,62,63,64,65,68,69,70,73,76,84,89],resultscreen:[69,70,77,78],resum:[19,38,40],retail:63,retain:[18,51],retrac:23,retriev:[14,36,38,46,84,88],reus:[18,86],reusabl:86,rev:44,revers:[1,58,63,65,89],revis:[44,61,63],reward:[58,63],rewind:40,rewound:26,rgb:[18,20,23,24,28,30,31,41,43,51,57,58,63,65,84,85],rgba:[1,4,20,23,24,30,31,51],rgba_premult:31,rgbarrai:65,rgbx:31,rich:58,rid:57,ridicul:61,right:[20,23,24,25,29,30,32,33,35,38,42,44,45,47,50,51,58,62,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,87,88,89],rle:51,rleaccel:[51,58,66],rleaccelok:51,road:62,roll:[32,39,64],root:[36,53,84],rosybrown1:21,rosybrown2:21,rosybrown3:21,rosybrown4:21,rosybrown:21,rotat:[25,26,29,36,48,50,56,58,63,66],rotate_i:36,rotate_ip:36,rotate_ip_rad:36,rotate_rad:36,rotate_rad_ip:36,rotate_x:36,rotate_x_ip:36,rotate_x_ip_rad:36,rotate_x_rad:36,rotate_x_rad_ip:36,rotate_y_ip:36,rotate_y_ip_rad:36,rotate_y_rad:36,rotate_y_rad_ip:36,rotate_z:36,rotate_z_ip:36,rotate_z_ip_rad:36,rotate_z_rad:36,rotate_z_rad_ip:36,rotozoom:56,round:[20,24,37,38,88],routin:[1,18,25,28,29,51,56,58,59,68],row1:65,row2:65,row:[35,42,51,65],royalblu:21,royalblue1:21,royalblue2:21,royalblue3:21,royalblue4:21,rr:20,rrggbb:20,rrggbbaa:20,rt:32,rudder:32,rudimentari:26,ruin:65,rule:[59,70,73,74,89],rumbl:[32,47],run:[23,25,26,31,32,38,44,53,54,56,58,59,62,63,64,65,66,67,84,85,89],run_speed_test:26,run_test:53,run_tests__test:53,rundown:26,runner:53,runtim:[23,38,54,56,63],runtimeerror:[29,44],rw:10,rwobject:0,rx:30,ry:30,s:[9,12,13,15,17,20,22,23,24,25,26,28,29,31,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,50,51,54,58,59,61,63,64,65,67,68,69,70,71,74,84,85,86,87,88,89],saddlebrown:21,safe:[19,23,25,28,29,32,37,38,42,44,47,50,51,57,60],sai:[51,61,62,64,68,84],said:[24,61,68,70,74,84,85],sake:85,salmon1:21,salmon2:21,salmon3:21,salmon4:21,salmon:21,sam:63,same:[10,18,19,20,22,23,24,25,28,29,30,31,32,33,35,36,38,39,42,43,44,45,47,50,51,52,54,56,57,58,59,62,63,64,65,67,68,70,71,72,73,84,85,89],sampl:[16,20,38,40,56,57,62,64,65,88],san:29,sandybrown:21,satisfactori:86,satisfi:23,satur:18,sauf:35,save:[16,18,31,61,63,84],save_extend:31,saw:[57,58,62],scalabl:29,scalar:[36,58],scale2x:56,scale:[18,23,24,26,29,31,35,36,44,48,50,56,58,63,65,66],scale_to_length:36,scaledown:65,scaler:26,scaletest:26,scaleup:65,scan:19,scancod:[25,33],scanf:[67,75],scanlin:62,scant:26,scene:57,school:39,scipi:65,scope:[42,65],score:[26,50,61,70,89],scoreboard:61,scoreup:61,scrap:[16,46],scrap_bmp:46,scrap_clipboard:[26,46],scrap_pbm:46,scrap_ppm:46,scrap_select:46,scrap_text:46,scratch:64,scratchi:38,screen:[1,5,16,22,24,25,26,28,31,32,33,34,39,44,45,46,48,50,51,57,58,59,61,63,64,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,87,88,89],screen_dim:26,screen_height:23,screen_rect:50,screen_width:23,screensav:[23,44,69],screenshot:58,script:[29,60,63,66],scroll:[25,26,39,51,62,63,64,84],scrollabl:26,scroller:84,scrollock:33,sdl1:[19,23,41,51],sdl2:[25,32,38,39,44,47,51,55],sdl:[1,3,6,8,10,12,13,23,25,26,27,32,33,38,41,43,44,47,48,56,59,63,84],sdl_audiodriv:44,sdl_delai:54,sdl_event:6,sdl_gfx:30,sdl_hint_video_allow_screensav:23,sdl_imag:[31,63],sdl_mixer:[38,40],sdl_rect:[9,12],sdl_rwop:10,sdl_surfac:12,sdl_ttf:[28,29,44],sdl_video:51,sdl_video_allow_screensav:44,sdl_video_cent:44,sdl_video_window_po:44,sdl_video_x11_net_wm_bypass_compositor:44,sdl_videodriv:[23,44],sdl_videoinfo:5,sdl_window:1,sdl_windowid:23,sdlerror:40,sdlversion:44,seagreen1:21,seagreen2:21,seagreen3:21,seagreen4:21,seagreen:21,search:[16,28,29,35,45,50,56],search_color:56,search_surf:56,seashel:21,seashell1:21,seashell2:21,seashell3:21,seashell4:21,second:[17,19,20,22,24,26,30,32,36,38,40,42,49,50,53,54,58,60,62,63,65,69,70,71,72,77,84,88,89],secondari:84,section:[19,51,58,61,62,64,65,67,68,86,89],secur:86,see:[18,19,20,23,24,26,28,29,30,31,32,33,35,36,37,38,39,43,44,45,47,50,51,52,56,57,58,60,61,62,63,64,65,84,85,87,88,89],seed:[53,65],seek:10,seem:[31,46,61,62,63,64,68,72,73,84,85,87],seemingli:61,seen:[23,61,65,88,89],segment:[24,30],select:[18,23,29,33,35,37,38,40,44,46,53,59,67,69,73,84],self:[20,32,35,36,50,51,56,57,58,62,64,66,87,88,89],sell:62,semi:[26,84],semicolon:33,semiton:37,send:[25,37,38,40,89],sens:[62,87,88,89],sensit:84,sent:[23,25,32,33,38,40],separ:[18,22,26,28,29,37,38,44,50,51,52,53,56,57,61,62,64,65,72,84,86],sequenc:[1,9,15,22,23,24,25,29,30,33,35,37,39,42,45,50,51,56,59,63,64,84,88],sequenti:24,seri:[37,63,70],serv:[27,63],server:[26,39],servic:18,session:53,set:[1,7,17,18,20,22,23,24,25,28,29,31,32,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,56,58,60,62,65,67,68,70,71,72,73,84,85,86,87,88,89],set_allow:25,set_allow_screensav:23,set_alpha:[43,51],set_at:[35,51,65,84],set_behavior:56,set_block:[25,84],set_bold:28,set_capt:[22,23,24,32,58,66,68,69,70,71,72,73,76,77,78,79,80,81,85,89],set_clip:[50,51],set_color:56,set_colorkei:[43,51,58,66,84],set_control:[18,57],set_cursor:[22,39],set_default_resolut:29,set_endev:[38,40],set_error:44,set_eventst:47,set_fullscreen:48,set_gamma:23,set_gamma_ramp:23,set_grab:[25,33,39],set_icon:[23,48],set_instru:37,set_ital:28,set_length:20,set_loc:41,set_map:47,set_mask:51,set_mod:[1,22,23,24,32,33,34,39,44,46,48,51,57,58,59,62,63,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,89],set_modal_for:48,set_num_channel:38,set_palett:[23,51],set_palette_at:51,set_po:[39,40],set_repeat:33,set_reserv:38,set_shift:51,set_smoothscale_backend:56,set_text_input_rect:33,set_tim:54,set_timing_threshold:50,set_timing_treshold:50,set_underlin:28,set_viewport:48,set_vis:[39,58,66],set_volum:[38,40],set_window:48,setcolor:35,setsurfac:35,settabl:44,setup:[22,26],sever:[16,22,23,24,26,29,38,45,50,54,59,60,62,63,64,65,84,85,89],sf:42,sfnt:29,sg:22,shade:24,shall:18,shallow:35,shape:[16,17,42,43,51,63,65,72,84],share:[16,25,28,31,32,33,38,46,48,51,52,54],sharp:24,she:85,shell:26,shift:[23,26,33,35,42,44,51,59,65],shinner:[58,59,60,62,63,64,65,84],shoot:63,shortcut:64,shorter:[23,58,65],shortest:36,shot:64,should:[18,19,22,23,25,26,28,29,30,31,32,33,35,36,37,38,42,44,45,47,50,51,54,56,58,59,61,62,63,64,65,68,69,84,85,86,87,89],shoulder:47,shouldn:88,show:[22,23,26,29,33,39,41,48,53,56,57,58,59,62,65,88,89],show_output:53,showcas:[26,84],shown:[23,25,33,41,57,62,87],shrink:[45,84,89],shrinkag:56,shrunk:51,shtml:[86,89],shut:[23,29,32,44,69],shutdown:[8,63],side:[23,24,42,45,47,58,61,63,87,88],sienna1:21,sienna2:21,sienna3:21,sienna4:21,sienna:21,sign:[17,33,37,38,39,44],signal:[25,40,85],signific:51,silenc:53,silent:[23,60,84],silver:21,similar:[29,31,32,42,49,50,51,54,58,62,64,65,85,86,88,89],simpl:[16,22,23,24,25,26,32,42,50,51,56,57,58,60,61,62,63,64,65,66,67,69,71,72,73,74,84,85,86,89],simpler:[62,63,64],simplest:[57,71],simpli:[22,25,37,41,44,50,57,58,59,62,63,65,67,84,85,87,89],simul:[58,67],simultan:[38,67],sin:[87,89],sinc:[19,23,25,31,32,36,38,39,44,48,50,51,52,54,57,58,59,60,62,63,64,65,84],singl:[17,20,22,23,24,25,28,29,30,32,33,38,40,41,42,43,45,50,51,52,53,58,60,61,62,63,64,65,67,68,69,70,71,73,85],sit:84,site:26,situat:[23,51,58,64,65],six:[38,40,61,63],sizabl:84,size:[9,17,18,22,23,24,25,26,28,29,31,35,38,39,41,42,43,45,48,50,51,52,56,57,58,59,61,62,63,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85],sizeabl:23,sizeal:22,sizenesw:22,sizenws:22,sizeof:35,sizer_x_str:22,sizer_xy_str:22,sizer_y_str:22,skew:28,skip:[23,35,38,58],skyblu:21,skyblue1:21,skyblue2:21,skyblue3:21,skyblue4:21,sl:22,slash:[22,33],slateblu:21,slateblue1:21,slateblue2:21,slateblue3:21,slateblue4:21,slategrai:21,slategray1:21,slategray2:21,slategray3:21,slategray4:21,slategrei:21,sleep:[25,54],slerp:36,slice:[36,42,43,65,84],slight:[58,64],slightli:[20,25,26,39,40,51,54,58,64,84,85],slope:24,sloppi:26,slot:0,slow:[23,48,51,57,59,62,65,84,85],slower:[24,50,51,54,62,84],slowest:[44,51,85],small:[18,22,23,28,29,32,39,44,45,58,62,63,65,71,72,85,87],smaller:[18,23,35,38,45,51,71,72,84],smallest:[23,51,59],smart:[62,64],smooth:[28,58,84],smoother:62,smoothli:[56,62,84],smoothscal:[26,56],sn9c101:18,snapshot:57,sndarrai:[16,26,38,49,63],snow1:21,snow2:21,snow3:21,snow4:21,snow:21,so:[11,17,18,22,23,24,26,28,29,30,31,32,33,36,38,42,43,44,45,50,51,52,53,56,58,59,61,63,64,65,67,68,69,70,71,72,74,84,85,86,87,88,89],socket:[86,89],soften:65,softwar:[16,18,23,24,30,37,41,50,51,58,63,84],solarwolf:[63,84],solid:[24,28,30,50,51,52,56,65],solut:[84,85],solv:[38,61,67],some:[16,18,22,23,24,25,27,28,29,30,31,32,34,36,37,38,39,42,44,45,46,50,56,57,58,59,60,61,63,64,65,67,68,69,70,72,84,85,86,87,88,89],someimag:26,someth:[18,24,26,32,39,56,57,59,62,63,64,65,68,69,70,84,85],sometim:[22,23,27,44,64,84],somewhat:[26,64],somewher:[64,68],sonix:18,soon:[40,44,64,88],sophist:[86,87],sorri:[57,59],sort:[23,25,26,27,50,58,59,62,64,84,89],sound:[8,16,26,40,58,61,63,64,66,67,70,73,84,86],sound_array_demo:26,sour:48,sourc:[16,18,26,31,37,38,42,43,50,51,56,58,61,62,63,64,67,68,69,70,84,86,89],source_rect:50,sourcecod:[68,69,70,76,77,78],south:22,southeast:22,southwest:22,space:[20,28,29,31,33,37,42,62],sparingli:48,speak:[85,86],speaker:38,special:[18,22,23,25,33,35,38,43,50,56,58,59,62,64,65,68,84,85],special_flag:[48,50,51],specif:[23,25,28,29,33,35,38,43,46,50,51,52,57,58,59,64,65,68,70,71,72,84],specifi:[14,18,21,23,28,29,31,32,35,37,38,40,46,47,50,56,59,65,84,89],sped:24,speed:[25,26,29,44,54,62,63,64,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,87,88,89],spend:84,spent:[63,74,84],spheric:36,spin:[26,58,66,88,89],spite:50,split:[22,29,58,63,64,66,84],sport:63,spot:84,spread:84,spring:56,springgreen1:21,springgreen2:21,springgreen3:21,springgreen4:21,springgreen:21,sprite1:50,sprite2:50,sprite:[16,26,35,50,61,66,84,88,89],sprite_dict:50,sprite_list:50,spritecollid:[50,64,87],spritecollideani:50,spritedict:64,sprites_click:84,sqrt:[36,69],squar:[24,32,36,72],src:65,src_c:[0,11,14],srcalpha:[35,43,51,56],srccolorkei:51,srcobj:12,srcrect:[12,48],sse:[26,56],stabl:43,stack:25,stage:57,stai:[61,65,70],stand:26,standard:[1,22,25,27,41,43,44,46,50,57,58,59,61,63,64,65,68],star:26,starfield:[26,84],start:[1,17,18,19,24,25,26,29,30,32,33,35,38,39,40,42,44,45,47,50,51,57,58,59,61,62,63,64,65,66,67,69,74,84,85,86,88,89],start_angl:[24,30],start_index:65,start_po:24,start_text_input:33,startup:[8,38],state:[23,25,26,29,31,32,33,37,38,39,46,47,48,50,51,58,64,66,67,84,88,89],statement:[68,69,70,71,72,89],stationari:64,statu:[26,32,37,70],stderr:53,stdin:65,stdout:53,steelblu:21,steelblue1:21,steelblue2:21,steelblue3:21,steelblue4:21,steep:24,stencil:23,step:[16,30,32,42,58,65,68,70,73,84,88],stereo:[23,38,49],stick:[32,47,62,65,84],still:[23,31,38,39,43,46,47,48,51,57,58,61,62,63,64,65,71,72,73,84,85,88,89],stop:[1,18,19,24,30,32,33,38,39,40,44,47,54,57,62,64,84,88,89],stop_angl:[24,30],stop_rumbl:[32,47],stop_text_input:33,store:[9,22,23,25,29,30,35,50,51,52,58,62,64,68],str:[17,18,20,29,32,35,37,44,47,49,52,71,72,73,79,80,81],straight:[24,30,62,63,65,86],straighten:62,straightforward:[62,63],strang:[23,63],strateg:84,strategi:84,stream:[16,37,38,48,63],strength:[29,32,47],stress:63,stretch:[23,26,28,29],strict:[18,61],strictli:[30,85],stride:[17,42],strike:29,string:[10,15,17,18,19,20,22,23,25,28,29,31,32,33,37,38,41,44,46,48,49,51,52,53,56,68,84,86,87],strip:51,stripe:65,stroke:24,strong:29,strongli:[23,50],struct:[1,3,5,6,7,8,10,17,29,42,43,51,52],structur:[8,11,43,85,87,88],stuck:62,studi:[65,87],studio:63,stuff:[46,57,61,62,65],style:[25,26,29,32,42,63,86],style_default:29,style_norm:29,style_obliqu:29,style_strong:29,style_underlin:29,style_wid:29,sub:[50,84],subarrai:42,subclass:[2,3,4,5,6,7,8,12,13,17,20,35,45,50,51],subdirectori:[26,58],subgroup:67,subject:[23,44],submask:35,submit:84,submodul:[44,53],subpackag:53,subprocess:53,subprocess_ignor:53,subscript:[36,42],subsect:62,subsequ:57,subset:[22,23,84],substanti:84,substitut:[18,22],substr:37,subsubsurfac:51,subsurfac:[13,51,56],subtract:89,subview:42,succe:[18,23],succeed:18,success:[1,2,6,9,10,12,23,44,84],successfulli:[32,47],sudden:63,sufac:42,suffix:45,suggest:[42,58,84],suit:[27,51,59],suitabl:[23,28,29,46,63,68,86],sum:[24,74],summari:64,summer:63,suppli:[18,35,37,38,57,58,65,85],support:[1,7,17,18,19,20,22,23,24,25,26,28,29,31,32,33,35,36,37,38,39,40,41,42,43,44,47,48,49,50,51,52,54,56,57,58,59,63,84,87],suppos:[56,84],sure:[11,50,54,57,58,63,64,65,69,70,84,89],surf:[22,26,29,30,42,50,51,56],surfac:[0,1,2,11,13,16,18,20,22,23,24,26,28,29,30,31,35,36,39,41,43,46,48,50,51,57,58,59,62,63,64,66,85,89],surface_dest:50,surface_to_arrai:43,surfarrai:[16,17,26,43,49,51,52,63,84],surfdemo_show:65,surflock:0,surfobj:13,surpris:[62,63],surrog:[15,29],surround:[56,85],suspend:84,svg:31,svgalib:23,swap:[23,31,42,45],swatch:20,swig:63,switch_lay:50,swizzl:36,swsurfac:[51,84],sy:[15,44,53,62,63,67,68,69,70,71,72,73,75,76,77,78,79,80,81,86,89],symbol:33,sync:[23,25],synchron:37,synonym:25,syntax:[65,84],syntaxerror:44,synthes:37,sysfont:[28,29],sysrq:33,system:[15,16,18,19,22,23,25,26,27,28,29,32,33,36,37,39,40,44,48,49,51,52,53,59,68,70,84,85,88],system_cursor_arrow:22,system_cursor_crosshair:22,system_cursor_hand:22,system_cursor_ibeam:22,system_cursor_no:22,system_cursor_sizeal:22,system_cursor_sizen:22,system_cursor_sizenesw:22,system_cursor_sizenws:22,system_cursor_sizew:22,system_cursor_wait:22,system_cursor_waitarrow:22,systemexit:[86,89],t:[18,19,23,24,26,27,28,29,32,33,36,40,44,46,48,49,50,51,52,54,56,57,58,61,62,63,64,65,68,69,70,71,72,73,74,85,86,87,88,89],ta:61,tab:33,tabl:[23,43],tag:[40,53],taka:18,takafumi:18,take:[15,22,24,25,26,29,31,32,33,35,36,37,38,40,42,45,50,51,53,56,57,58,59,63,64,65,68,84,85,86,88,89],taken:[4,21,23,26,27,38,39,87],talk:62,tan1:21,tan2:21,tan3:21,tan4:21,tan:21,tango:29,target:[29,42,43,45,48,50,58,64,66],target_textur:48,task:[59,86],tau:24,teach:62,teal:21,technic:41,techniqu:[50,84],tell:[10,38,39,44,58,59,62,64,84,88],temp:9,temporari:[56,65],temporarili:[19,24,33,38,40,51,52],tempt:84,temptat:84,ten:54,tenni:[88,89],term:[62,64,85],termin:[1,37,44,58,68],terminolog:[39,84],terrain1:62,terrain2:62,test:[16,19,25,26,28,29,31,32,33,36,38,39,41,44,45,47,50,51,56,58,59,60,65],test_threshold_dest_surf_not_chang:56,test_util:56,testin:37,testout:37,testsprit:26,tetri:74,text:[25,26,28,29,33,46,66,68,69,70,71,84,85,87],textbitmap:32,textedit:[25,33],textinput:[25,33],textmarker_str:22,textpo:[58,66,85],textprint:32,textstr:32,textur:[30,48],textured_polygon:30,textureorimag:48,tga:[31,63],than:[16,18,19,23,24,25,26,28,29,30,31,32,33,35,36,37,38,40,44,45,47,50,51,52,54,55,56,57,58,59,60,62,63,64,65,69,70,74,85,86,87,88,89],thank:88,the_arg:12,the_dirty_rectangl:84,thei:[1,11,18,19,22,23,24,25,26,28,29,32,35,36,38,39,44,45,46,47,50,51,56,58,59,62,63,64,65,68,69,71,72,86,87,88,89],them:[16,22,24,25,26,27,31,33,34,35,38,42,44,49,50,51,52,57,58,59,62,63,65,71,84,85,86,87,88,89],themselv:[51,61,64,85],theorem:36,theori:18,therefor:[24,30,35,50,64,84,87],theta:[35,36],thi:[1,2,3,4,5,6,7,9,10,11,12,13,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,72,73,74,84,85,86,87,88,89],thick:[24,28],thickarrow_str:22,thin:58,thing:[19,25,27,40,44,57,58,59,60,61,62,63,64,65,85,87],think:[18,23,62,64,65,70,74,84,89],third:[17,18,22,30,36,62,63,65,71,85],third_surfac:56,thirteen:16,thistl:21,thistle1:21,thistle2:21,thistle3:21,thistle4:21,thorough:89,those:[16,18,22,23,24,25,26,28,29,39,42,43,50,57,61,62,64,65,84,86,87,88,89],though:[18,23,30,37,38,43,53,61,65,85,86,87,88,89],thought:84,thousand:49,thread:[10,18,25,26,27,28,30,38,39,50,53,57],three:[36,38,44,51,52,64,65,68,84,85,88],threshold:[35,42,50,56],threshold_behavior_from_search_color:56,threshold_color:56,throttl:32,through:[2,18,25,27,29,35,37,38,44,50,51,58,61,62,63,64,74,84,85,88],throughout:34,thrown:[56,89],thru:63,thu:[36,42,87],thumbnail:26,ti:40,tick:[22,24,32,39,54,58,66,69,71,72,73,77,79,80,81,89],tick_busy_loop:54,tie:[57,72],tif:31,tiff:[31,46],tile:62,time:[14,16,18,19,22,23,24,25,26,28,29,32,34,37,38,39,40,44,48,49,50,51,53,56,57,58,59,61,62,63,64,65,66,67,68,69,70,71,72,73,74,77,78,79,80,81,84,85,86,87,89],time_m:50,time_out:53,time_proc:37,timeout:25,timer:[37,54],timer_resolut:[34,54],timestamp:37,tini:[23,84],tip:[16,64,84],titl:[23,48,58,63,68],tl:89,to_surfac:[35,48],todo:64,togeth:[33,38,56,61,63,65,86],toggl:[22,47],toggle_fullscreen:23,toler:32,tom:[86,89],tomato1:21,tomato2:21,tomato3:21,tomato4:21,tomato:21,tomchanc:[86,89],tompong:[61,89],tone:37,too:[25,27,29,42,45,50,51,53,58,62,65,67,68,69,73,84,85],took:63,tool:[26,64,67,87],top:[16,23,24,26,28,29,31,35,38,39,45,50,51,62,63,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,86,89],topic:16,topleft:[35,42,45,50,56,58,62,66,89],toplevel:38,topmost:50,topright:[45,89],tort:18,tortur:84,tostr:31,total:[38,43,44,53,62,68,86],touch:[25,32,84],touch_id:25,touchid:55,toward:[23,61,63],tp:[67,75],tprint:32,tr:89,traceback:[65,86],track:[16,19,50,54,57,62,64,84],trackbal:[25,47],tradition:22,trail:[53,63],train:84,trait:[68,74],transfer:[16,46,56,61,65],transform:[16,18,29,57,58,62,63,65,66],transform_test:56,translat:[9,15,26,29,33,42],transluc:84,transmiss:37,transpar:[23,24,26,28,29,31,35,43,48,51,52,56,58,62,63,84,86],transpos:42,travel:89,treat:[23,29,32,52,65],tree:57,trend:63,tri:[44,62,63,84,86],tri_left:22,tri_right:22,trial:[74,84],triangl:[24,26,30,32],trick:89,tricki:[64,65,84],trickier:65,trigger:[32,33,39,40,47,68,70],trigon:30,trigonometri:87,triplet:[23,24,30,58],truetyp:[16,28,58,63],truncat:[24,30,38,45,65],truth:64,ttf:[28,29,68,69,70,71,72,73,76,77,78,79,80,81],tune:[29,64],tupl:[9,17,19,20,22,23,24,28,29,30,31,32,35,36,37,38,39,42,44,45,47,49,50,51,52,53,62,65,84],turn:[29,37,56,58,62,64,66,84],turquois:21,turquoise1:21,turquoise2:21,turquoise3:21,turquoise4:21,turtl:68,tutori:[26,61,63,66,73,74,85,89],tweak:64,twice:[50,56,65],twitch:84,two:[1,16,18,19,22,23,24,25,26,28,29,30,32,33,35,36,37,38,42,43,45,47,49,50,53,54,56,58,62,63,64,65,70,71,72,73,84,85,87],tx:30,ty:30,type1:29,type42:29,type:[1,2,3,4,5,6,7,8,9,10,12,13,15,16,17,20,22,23,24,25,27,28,29,30,31,32,33,34,35,37,38,39,40,41,42,44,45,46,47,49,50,51,52,54,56,57,58,59,62,63,65,66,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,87,88,89],typeerror:[24,28,43,45,50],typelist:25,typestr:17,typic:[18,23,37,51,58],u0001:28,u4:17,u:[15,17,33,41],u_margin:[73,81],uc:[15,28,29],ucs4:29,ucs_4:28,uffff:[15,28],ufo:63,ufunc:65,ugh:84,ui:26,uint32:1,uint8:[1,4],uint:65,uklinux:[86,89],ultim:74,unabl:[31,46,64],unaccept:84,unalt:[44,84],unari:20,unavail:[23,28,58,84],unchang:[15,20,23,29],uncommit:44,uncommon:58,uncompress:[31,38],undefin:[29,65],under:[16,37,39,40,63,84,86,89],underli:[29,32,37,44],underlin:[28,29,40],underline_adjust:29,underneath:65,underscor:[33,58],understand:[16,41,59,61,62,63,64,65,68,69,70,71,84,85,86,88,89],understood:71,undesir:24,unencod:44,unfamiliar:[62,84],unfil:[30,35],unfilt:56,unfortun:[53,84],unicod:[15,25,28,29,33,38,44,46,51],unicode_escap:44,unicodeencodeerror:[29,44],unicodeerror:28,unind:32,uniniti:[18,19,23,28,32,37,38,44,47],union:[45,64,84],union_ip:45,unional:45,unionall_ip:45,uniqu:[32,48,50,51,62,67,72,74],unit:[28,29,44,51],uniti:[67,75],unix:[22,23,63,84],unknown:[23,25,28,32],unless:[7,19,23,29,31,41,46,51,54,56,58,62,64,68,69,84,87,88],unlik:[38,43,51,65,88],unload:40,unlock:[24,51,84],unmap:43,unmap_rgb:[20,24,43,51,52],unmodifi:18,unnorm:67,unnot:33,unpack:[20,39],unpaus:[19,38,40],unplay:[23,63],unpredict:29,unpunch:[58,66],unreal:[63,67,75],unrealist:89,unrecogn:[28,31],unrel:23,unscal:29,unset:[29,35,51,89],unsetcolor:35,unsetsurfac:35,unsign:[1,17,20,35,38,51,65],unspecifi:29,unstructur:51,unsupport:[18,40],until:[18,23,24,25,27,32,35,37,40,47,51,54,57,62,64,66,84,85,88],untransform:29,unus:[31,38],unwieldi:84,up:[18,22,23,24,25,27,32,33,37,38,39,40,42,44,47,50,51,58,60,62,64,65,71,72,73,80,81,84,85,86,87,88],updat:[12,20,23,24,25,32,36,41,44,45,47,48,50,54,59,60,61,62,63,64,66,68,69,70,71,72,73,76,77,78,79,80,81,84,85,87,88,89],update_rect:26,upon:89,upper:[25,29,32,51,57],us:[0,1,9,10,11,14,15,16,17,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53,54,56,58,59,61,62,63,64,65,66,67,68,71,72,85,86,87,88,89],usabl:[20,31,57],usag:[23,27,37,51,53],usb:37,use_alpha:26,use_arraytyp:[49,52],use_bitmap_strik:29,use_fastrendergroup:26,use_stat:26,use_valu:45,user:[6,16,18,23,24,25,27,32,33,37,44,46,51,53,58,61,62,63,64,65,67,72,84,85,87],userev:[25,38],userevent_dropfil:25,userrect:84,usr:[26,28,66,85,86,87],usual:[23,25,28,29,32,38,44,50,51,58,59,60,62,63,64,65,70,84,86],utf8_str:46,utf:[15,29,46],util:[63,74,84],uxxxxxxxx:[15,29],uyvy_overlai:41,v1:44,v2:[1,40],v3:17,v4l2:[18,57],v:[17,20,33,36,41],val1:1,val2:1,val:[1,65],valid:[17,23,38,39,41,51,57,64],valu:[1,4,9,10,17,18,19,20,22,23,24,25,26,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,56,58,59,62,64,65,68,69,71,72,84,85,88,89],value_to_set:44,valueerror:[17,22,24,26,29,30,33,35,36,43,46,49,51,52,57,65],vanish:84,vari:71,variabl:[17,22,23,28,37,40,44,57,58,63,64,65,69,70,71,74,89],variant:22,varieti:[25,63,70,84],variou:[1,16,22,25,26,34,84,89],ve:[24,32,58,61,62,63,64,65,84,85,87,88,89],vec:36,vector2:[24,30,35,36,45],vector3:36,vector:[24,30,35,61,89],vectorelementwiseproxi:36,veloc:[37,69],ver:44,vera:[28,29],veri:[18,23,26,37,54,58,60,62,63,64,65,70,84,87,88,89],verifi:23,vernum:44,versatil:[47,56],version:[0,12,17,23,25,26,27,29,30,31,32,38,40,43,45,47,50,56,57,58,60,61,62,63,64,84,86,89],vertic:[18,23,24,26,29,30,31,39,42,56,58,65],vflip:[18,57],vgl:23,vgrade:[26,65],via:[18,23,29,35,57],vidcaptur:44,video0:[18,57],video:[23,25,26,32,44,48,51,57,59,64,84],video_mem:[23,59],videocaptur:[18,57],videoexpos:[23,25],videoinfo:[23,59],videores:[23,25],vidinfo:[23,59],view:[1,2,16,42,51],view_p:1,violet:21,violetr:21,violetred1:21,violetred2:21,violetred3:21,violetred4:21,virtual:[23,26,37,39,45],visibl:[17,22,23,39,41,50,58,59,62,63],vision:[16,18],visit:63,vista:23,visual:[70,71,72,73],visualis:87,vline:30,volatil:51,volum:[16,38,40,63],vsync:[23,48],w:[9,25,26,30,33,35,36,42,43,45,56],wa:[10,16,19,20,22,23,24,25,26,27,31,32,33,35,37,38,39,40,42,43,44,47,54,57,58,62,63,64,65,68,70,73,84,86,88],wai:[18,23,24,25,26,28,29,31,32,33,38,39,43,44,47,48,50,53,58,60,61,62,63,64,65,71,85,87,88,89],wait:[22,23,25,27,32,37,39,54,58,84],waitarrow:22,walk:[58,66],wall:[36,57,89],want:[18,20,23,25,27,31,32,33,36,44,50,51,54,56,57,58,59,61,62,63,64,65,67,68,84,85,86,87,88,89],wargam:84,warn:[44,48,58,66],warranti:18,warrior:63,was_init:29,wasn:63,wast:[84,87],watch:[16,57,61,85],wav:[38,40,46,58,66],wave:49,wayland:[22,23],we:[22,24,26,32,56,57,58,60,62,63,64,65,67,68,69,70,71,72,73,74,84,85,86,87,88,89],weak:[13,17,84],web:[26,66],webcam:18,webp:31,websit:63,week:63,weight:[42,65],weird:33,welcom:44,well:[1,2,13,22,24,27,30,31,33,39,42,44,47,51,56,61,62,63,64,65,84,89],were:[1,11,23,25,28,33,50,53,62,63,64,69,84,86,89],west:22,what:[16,20,22,23,24,26,28,32,38,39,44,46,50,57,58,59,60,61,63,64,65,68,69,71,74,85,86,88,89],whatev:[50,57,58,61,64,84],wheat1:21,wheat2:21,wheat3:21,wheat4:21,wheat:21,wheel:39,when:[13,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,37,38,39,40,41,42,44,45,46,49,50,51,52,53,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,73,74,84,85,87,88,89],whenev:[23,39,44,65],where:[11,16,17,19,20,22,23,24,25,26,29,30,33,35,36,38,39,40,41,42,44,46,47,50,51,53,56,57,58,62,64,65,68,84,85,88,89],wherea:[23,87],wherev:65,whether:[18,23,25,26,28,29,35,38,39,45,46,48,56,61,71,72,84,85],which:[1,13,15,17,18,20,22,23,24,25,26,28,29,31,32,33,35,36,37,38,39,40,42,44,45,46,47,49,50,51,52,53,54,56,58,59,60,61,62,63,64,65,67,68,69,70,71,72,73,74,85,86,87,88,89],whiff:[58,66],whiff_sound:[58,66],whilst:23,white:[21,22,24,32,35,42,57,58,68,69,70,71,72,73,76,77,78,79,80,81,85],whitesmok:21,whitespac:61,who:[16,48,61,62,64,84,89],whole:[23,24,37,44,62,84,87],whoop:62,whose:[24,55],why:[44,63,70,74,87],wide:[24,28,29,35,44,62,63],wider:18,widget:26,width:[9,18,22,23,24,26,28,29,31,32,35,41,42,45,48,50,51,56,59,62,63,67,68,69,70,71,72,73,75,76,77,78,79,80,81,84],wiki:39,wikipedia:36,win32:37,win:[1,58,66,73],windib:23,window:[1,5,16,18,25,26,27,29,32,33,34,37,39,44,46,48,51,58,59,62,63,68,69,84,85,88],window_surfac:23,windowclos:25,windowent:25,windowev:23,windowevent_minim:23,windowexpos:25,windowfocusgain:25,windowfocuslost:25,windowhidden:25,windowhittest:25,windowleav:25,windowmaxim:25,windowminim:25,windowmov:25,windowpos_cent:48,windowpos_undefin:48,windowres:25,windowrestor:25,windowshown:25,windowsizechang:25,windowtakefocu:25,wire:32,wireless:32,wisdom:65,wise:[29,65],wish:86,within:[13,17,22,29,33,35,44,50,51,56,68,74,84],without:[18,22,23,24,29,38,42,45,48,50,51,52,58,59,62,63,64,65,69,70,72,88,89],wm:[23,59],won:[40,50,58,61,62,64,65,84,86,87,88],wonder:[57,84],word:[29,46,54,62,64,88],word_wrap:29,wordwrap:28,work:[18,19,20,22,23,24,25,26,28,29,30,31,32,34,35,37,38,42,43,46,50,51,52,53,56,57,58,59,61,62,63,64,65,68,69,85,87,89],world:[57,68,69,70,74,76,77,78,84],worri:[26,48,50,64,65,74,84],wors:[58,84],worst:61,worth:54,would:[17,22,24,25,28,35,36,38,44,50,51,56,57,58,60,61,62,63,64,65,84,85,87,88,89],wow:68,wrap:[2,5,10,17,22,30,42,51,63],wrapper:[10,84],wrestl:26,writabl:17,write:[1,10,17,26,37,51,61,63,67,84,86,87],write_short:37,write_sys_ex:37,written:[16,24,60,61,63,64,84,86,87],wrong:[38,65,84],wrote:84,www:[70,78,86,89],x00:[15,28],x10:37,x11:[23,25,37,39,44,46,48],x12:37,x13:37,x1:[24,30,45],x2:[24,30,45],x360:32,x3:[24,30],x4:84,x7d:37,x86:56,x:[2,3,5,6,7,8,9,12,13,22,23,24,25,26,28,29,30,32,33,35,36,37,39,42,44,45,46,47,48,49,50,51,52,55,58,59,62,68,69,70,72,73,76,77,78,80,81,84,85,87,89],x_offset:35,x_scale:48,xbm:22,xbox:[32,47],xf0:37,xf7:37,xfade:65,xor:22,xormask:[22,39],xpm:31,xx:22,xxx:22,xxxx:22,xxxx_test:53,xxxxx:22,xy:[36,89],y1:[24,30,45],y2:[24,30,45],y3:[24,30],y:[9,20,22,23,24,25,28,29,30,32,33,35,36,39,41,42,44,45,47,48,49,50,51,52,55,58,62,66,68,69,70,72,73,77,78,80,81,84,85,87,88,89],y_offset:35,y_scale:48,ye:[65,67,85],yeah:71,year:[63,74,84],yellow1:21,yellow2:21,yellow3:21,yellow4:21,yellow:21,yellowgreen:21,yet:[29,48,65,84,88],you:[11,16,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,36,37,38,39,40,41,42,44,46,50,51,54,56,57,58,59,60,61,63,64,65,67,68,71,72,74,85,86,87,88,89],your:[16,18,19,22,23,25,26,27,28,30,31,33,37,38,44,46,54,56,57,58,59,60,61,63,65,67,70,85,86,87,88,89],yourself:[28,50,58,63,65,86,87],yup:84,yuv:[18,41,57],yuy2_overlai:41,yv12_overlai:41,yvyu_overlai:41,z:[33,36,87,89],zero:[19,20,24,28,29,36,37,38,59,64,65,89],zine:63,zip:84,zoom:26},titles:["pygame C API","High level API exported by pygame.base","Class BufferProxy API exported by pgyame.bufferproxy","API exported by pygame.cdrom","Class Color API exported by pygame.color","API exported by pygame.display","API exported by pygame.event","API exported by pygame._freetype","API exported by pygame.mixer","Class Rect API exported by pygame.rect","API exported by pygame.rwobject","Slots and c_api - Making functions and data available from other modules","Class Surface API exported by pygame.surface","API exported by pygame.surflock","API exported by pygame.version","File Path Function Arguments","Pygame Front Page","pygame.BufferProxy","pygame.camera","pygame.cdrom","pygame.Color","Named Colors","pygame.cursors","pygame.display","pygame.draw","pygame.event","pygame.examples","pygame.fastevent","pygame.font","pygame.freetype","pygame.gfxdraw","pygame.image","pygame.joystick","pygame.key","pygame.locals","pygame.mask","pygame.math","pygame.midi","pygame.mixer","pygame.mouse","pygame.mixer.music","pygame.Overlay","pygame.PixelArray","pygame.pixelcopy","pygame","pygame.Rect","pygame.scrap","pygame._sdl2.controller","pygame.sdl2_video","pygame.sndarray","pygame.sprite","pygame.Surface","pygame.surfarray","pygame.tests","pygame.time","pygame._sdl2.touch","pygame.transform","Pygame Tutorials - Camera Module Introduction","Pygame Tutorials - Line By Line Chimp Example","Pygame Tutorials - Setting Display Modes","Pygame Tutorials - Import and Initialize","Making Games With Pygame","Pygame Tutorials - Help! How Do I Move An Image?","Pygame Intro","Pygame Tutorials - Sprite Module Introduction","Pygame Tutorials - Surfarray Introduction","pygame/examples/chimp.py","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","Author: Youngwook Kim (Korean)","\ud55c\uad6d\uc5b4 \ud29c\ud1a0\ub9ac\uc5bc","A Newbie Guide to pygame","Revision: Pygame fundamentals","Kicking things off","Game object classes","User-controllable objects","Putting it all together"],titleterms:{"1":[61,85,86,87,88,89],"2":[62,85,86,87,89],"3":[85,86,88,89],"4":[85,87],"5":[85,88],"6":89,"\uadf8\ub9ac\uace0":[79,80,81],"\uae30\ubc18\uacfc":76,"\uae30\ubc18\uc73c\ub85c\uc758":76,"\uae30\ubcf8":76,"\uae30\ucd08":[76,77,78],"\ub354":81,"\ubc84\ud2bc":80,"\uc0c8\ub85c\uc6b4":78,"\uc2ec\ud654":[79,80],"\uc5d0\ud544\ub85c\uadf8":82,"\uc65c":75,"\uc6c0\uc9c1\uc774\uae30":77,"\uc704\ud55c":77,"\uc774\ubca4\ud2b8":[76,78],"\uc785\ub825":78,"\uc785\ub825\uc740":78,"\uc785\ubb38":76,"\uc870\uac74":77,"\uc870\uae08":81,"\ucc98\ub9ac":[77,79],"\ucd9c\ub825":[76,80],"\ud29c\ud1a0\ub9ac\uc5bc":83,"\ud30c\uc774\uac8c\uc784":75,"\ud504\ub864\ub85c\uadf8":75,"\ud558\ud544":75,"\ud55c\uad6d\uc5b4":83,"\ud568\uc218\ud654":79,"\ud615\uc2dd\uacfc":76,"\ud654\uba74\uc774":77,"class":[2,4,9,12,36,50,58,64,87,88],"do":[62,84],"export":[1,2,3,4,5,6,7,8,9,10,12,13,14,17],"function":[11,15,59,62,65,71,86],"import":[57,58,60,65],"new":70,"while":58,A:[61,62,84,87,88],AND:63,By:58,Into:68,It:62,NO:84,On:[58,62],The:[58,62,64,85,86,89],There:84,To:62,With:61,_freetyp:7,_sdl2:[47,55],access:[42,49,52],advanc:[64,65,71,72],all:[58,62,89],alpha:[73,84],an:[17,62],anim:[69,84],api:[0,1,2,3,4,5,6,7,8,9,10,12,13,14],ar:[62,84],argument:15,arrai:[17,43,52],audio:[19,40],author:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],avail:11,back:62,background:[58,62],ball:[87,89],base:1,basic:[50,57,59,68,69,70,85],bat:[88,89],blit:[62,85],bother:84,buffer:17,bufferproxi:2,bufferproxypygam:17,button:72,c:[0,1,2,3,4,5,6,7,8,9,10,12,13],c_api:11,camera:[18,57],camerapygam:18,captur:57,cdrom:[3,19],cdrompygam:19,center:58,chang:62,chimp:[58,66],clipboard:46,close:63,code:61,collis:[64,84],color:[4,20,21],colorkei:84,colorpygam:20,colorspac:57,com:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],comfort:84,common:64,comput:[29,57],connect:57,constant:34,contact:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],contain:44,content:61,control:[19,23,40,47,57,88],controllerpygam:47,convert:84,coordin:[45,62],copi:43,creat:[58,62],cursor:22,cursorspygam:22,da:85,data:[11,49,52],decid:59,definit:62,detect:[64,84],direct:42,dirti:84,displai:[5,23,58,59],displaypygam:23,distract:84,divers:[87,88],document:16,don:84,draw:[24,30,58],drawpygam:24,driven:68,entir:58,epilog:74,event:[6,25,27,58,68,70,84,85,88],eventpygam:25,everyth:58,exampl:[26,58,59,65,66],examplesmodul:26,extend:64,fasteventpygam:27,file:15,finish:[58,89],first:[62,86],font:[28,29],fontpygam:28,freetypeenhanc:29,friend:84,from:[11,62],front:16,fundament:85,game:[50,58,61,63,85,87],gamepad:32,gener:43,get:84,gfxdrawpygam:30,gmail:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],go:62,graduat:65,graphic:41,group:64,gui:[68,76],guid:84,handl:[58,62,86],hardwar:84,help:62,here:62,hero:62,high:1,histori:[63,64],hit:89,how:[59,62],i:62,imag:[31,35,51,57,62],imagepygam:31,inform:44,init:[57,60],initi:[58,60],input:[37,55,58,62,70],interact:[25,27,32,37],interfac:52,intro:63,introduct:[57,58,59,61,63,64,65],issu:84,joystick:32,joystickpygam:32,just:62,keyboard:33,keypygam:33,kick:86,kim:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],know:84,korean:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],lesson:64,let:[62,89],level:[1,44],line:[58,86],list:[57,62],live:57,load:[28,29,38,58,86],localspygam:34,lock:65,loop:[58,85],main:58,make:[11,61,62],manag:84,mani:64,map:62,mask:[35,57],maskpygam:35,mathpygam:36,midi:37,midipygam:37,mix:64,mixer:[8,40],mixerpygam:38,mode:59,modul:[11,18,19,22,23,24,25,27,28,29,30,31,32,33,35,36,37,38,39,40,43,44,46,47,49,50,52,54,55,56,57,58,63,64,86],monitor:54,more:[65,84],mous:39,mousepygam:39,move:62,movement:62,multipl:62,musicpygam:40,mysteri:62,name:21,need:84,newbi:84,next:62,note:61,numer:65,numpi:65,object:[17,20,41,42,45,50,51,58,85,87,88],off:86,other:[11,65],output:[37,68,72],over:58,overlai:41,overlaypygam:41,overview:63,own:[62,64],packag:[44,53],page:16,part:84,path:15,perfect:84,pgyam:2,physic:87,pixel:[42,43,52,62,84],pixelarraypygam:42,pixelcopypygam:43,plai:38,plu:73,prepar:58,problem:64,process:[69,71],product:89,program:26,prolog:67,protocol:17,put:[58,62,89],py:[14,66],pygam:[0,1,3,4,5,6,7,8,9,10,12,13,14,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,84,85,88],pygameth:44,python:[63,65,84],pythoni:84,queue:[25,27],quit:60,re:84,realli:84,recogn:84,rect:[9,84],rectangular:45,rectpygam:45,refer:16,render:[28,29,64],repres:51,represent:20,resourc:[22,58,86],revis:85,rule:84,rumia0601:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],rwobject:10,s:62,sampl:49,scene:58,scrappygam:46,screen:[23,62],sdl2_video:48,set:[59,69],setup:58,shape:[24,30],side:[84,89],simpl:[87,88],singl:57,six:84,slot:11,smooth:62,sndarraypygam:49,so:62,some:62,sound:[38,49],sprite:[58,64,87],spritepygam:50,src_c:[1,2,3,4,5,6,7,8,9,10,12,13],src_py:14,step:62,store:45,stream:[40,57],style:61,subsystem:84,suit:53,support:46,surfac:[12,17,42,52,56,65,84],surfacepygam:51,surfarrai:65,surfarraypygam:52,surflock:13,t:84,ta:85,tabl:61,take:62,tast:63,templat:68,test:53,testspygam:53,text:58,than:84,thei:84,them:64,thing:[84,86],threshold:57,through:17,time:54,timepygam:54,togeth:[62,64,89],top:44,touch:55,touchpygam:55,trackbal:32,transfer:31,transform:56,transformpygam:56,transpar:65,troubl:84,tutori:[16,57,58,59,60,62,64,65],type:64,unit:53,updat:58,us:[18,52,57,84],user:88,vector:[36,87],version:[14,44],versionsmal:44,video:41,vision:57,vs:84,wai:84,what:[62,84],which:84,why:67,window:23,work:[33,39,47,55,84],worth:84,you:[62,84],youngwook:[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82],your:[62,64,84]}}) \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/CameraIntro.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/CameraIntro.html new file mode 100644 index 0000000..eada64a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/CameraIntro.html @@ -0,0 +1,378 @@ + + + + + + + + + Pygame Tutorials - Camera Module Introduction — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Camera Module Introduction¶

+
+
Author
+

by Nirav Patel

+
+
Contact
+

nrp@eclecti.cc

+
+
+

Pygame 1.9 comes with support for interfacing cameras, allowing you to capture +still images, watch live streams, and do some simple computer vision. This +tutorial will cover all of those use cases, providing code samples you can base +your app or game on. You can refer to the reference documentation +for the full API.

+
+

Note

+

As of Pygame 1.9, the camera module offers native support for cameras +that use v4l2 on Linux. There is support for other platforms via Videocapture +or OpenCV, but this guide will focus on the native module. Most of the code +will be valid for other platforms, but certain things like controls will not +work. The module is also marked as EXPERIMENTAL, meaning the API could +change in subsequent versions.

+
+
+

Import and Init¶

+
import pygame
+import pygame.camera
+from pygame.locals import *
+
+pygame.init()
+pygame.camera.init()
+
+
+

As the camera module is optional, it needs to be imported and initialized +manually as shown above.

+
+
+

Capturing a Single Image¶

+

Now we will go over the simplest case of opening a camera and capturing a frame +as a surface. In the below example, we assume that there is a camera at +/dev/video0 on the computer, and initialize it with a size of 640 by 480. +The surface called image is whatever the camera was seeing when get_image() was +called.

+
cam = pygame.camera.Camera("/dev/video0",(640,480))
+cam.start()
+image = cam.get_image()
+
+
+
+

Listing Connected Cameras¶

+

You may be wondering, what if we don't know the exact path of the camera? +We can ask the module to provide a list of cameras attached to the +computer and initialize the first camera in the list.

+
camlist = pygame.camera.list_cameras()
+if camlist:
+    cam = pygame.camera.Camera(camlist[0],(640,480))
+
+
+
+
+

Using Camera Controls¶

+

Most cameras support controls like flipping the image and changing brightness. +set_controls() and get_controls() can be used at any point after using start().

+
cam.set_controls(hflip = True, vflip = False)
+print camera.get_controls()
+
+
+
+
+
+

Capturing a Live Stream¶

+

The rest of this tutorial will be based around capturing a live stream of +images. For this, we will be using the class below. As described, it will +simply blit a constant stream of camera frames to the screen, effectively +showing live video. It is basically what you would expect, looping get_image(), +blitting to the display surface, and flipping it. For performance reasons, +we will be supplying the camera with the same surface to use each time.

+
class Capture(object):
+    def __init__(self):
+        self.size = (640,480)
+        # create a display surface. standard pygame stuff
+        self.display = pygame.display.set_mode(self.size, 0)
+
+        # this is the same as what we saw before
+        self.clist = pygame.camera.list_cameras()
+        if not self.clist:
+            raise ValueError("Sorry, no cameras detected.")
+        self.cam = pygame.camera.Camera(self.clist[0], self.size)
+        self.cam.start()
+
+        # create a surface to capture to.  for performance purposes
+        # bit depth is the same as that of the display surface.
+        self.snapshot = pygame.surface.Surface(self.size, 0, self.display)
+
+    def get_and_flip(self):
+        # if you don't want to tie the framerate to the camera, you can check
+        # if the camera has an image ready.  note that while this works
+        # on most cameras, some will never return true.
+        if self.cam.query_image():
+            self.snapshot = self.cam.get_image(self.snapshot)
+
+        # blit it to the display surface.  simple!
+        self.display.blit(self.snapshot, (0,0))
+        pygame.display.flip()
+
+    def main(self):
+        going = True
+        while going:
+            events = pygame.event.get()
+            for e in events:
+                if e.type == QUIT or (e.type == KEYDOWN and e.key == K_ESCAPE):
+                    # close the camera safely
+                    self.cam.stop()
+                    going = False
+
+            self.get_and_flip()
+
+
+

Since get_image() is a blocking call that could take quite a bit of time on a +slow camera, this example uses query_image() to see if the camera is ready. +This allows you to separate the framerate of your game from that of your camera. +It is also possible to have the camera capturing images in a separate thread, +for approximately the same performance gain, if you find that your camera does +not support the query_image() function correctly.

+
+
+

Basic Computer Vision¶

+

By using the camera, transform, and mask modules, pygame can do some basic +computer vision.

+
+

Colorspaces¶

+

When initializing a camera, colorspace is an optional parameter, with 'RGB', +'YUV', and 'HSV' as the possible choices. YUV and HSV are both generally more +useful for computer vision than RGB, and allow you to more easily threshold by +color, something we will look at later in the tutorial.

+
self.cam = pygame.camera.Camera(self.clist[0], self.size, "RGB")
+
+
+../_images/camera_rgb.jpg +
self.cam = pygame.camera.Camera(self.clist[0], self.size, "YUV")
+
+
+../_images/camera_yuv.jpg +
self.cam = pygame.camera.Camera(self.clist[0], self.size, "HSV")
+
+
+../_images/camera_hsv.jpg +
+
+

Thresholding¶

+

Using the threshold() function from the transform module, one can do simple +green screen like effects, or isolate specifically colored objects in a scene. +In the below example, we threshold out just the green tree and make the rest +of the image black. Check the reference documentation for details on the +threshold function.

+
self.thresholded = pygame.surface.Surface(self.size, 0, self.display)
+self.snapshot = self.cam.get_image(self.snapshot)
+pygame.transform.threshold(self.thresholded,self.snapshot,(0,255,0),(90,170,170),(0,0,0),2)
+
+
+../_images/camera_thresholded.jpg +

Of course, this is only useful if you already know the exact color of the object +you are looking for. To get around this and make thresholding usable in the +real world, we need to add a calibration stage where we identify the color of an +object and use it to threshold against. We will be using the average_color() +function of the transform module to do this. Below is an example calibration +function that you could loop until an event like a key press, and an image of +what it would look like. The color inside the box will be the one that is +used for the threshold. Note that we are using the HSV colorspace in the below +images.

+
def calibrate(self):
+    # capture the image
+    self.snapshot = self.cam.get_image(self.snapshot)
+    # blit it to the display surface
+    self.display.blit(self.snapshot, (0,0))
+    # make a rect in the middle of the screen
+    crect = pygame.draw.rect(self.display, (255,0,0), (145,105,30,30), 4)
+    # get the average color of the area inside the rect
+    self.ccolor = pygame.transform.average_color(self.snapshot, crect)
+    # fill the upper left corner with that color
+    self.display.fill(self.ccolor, (0,0,50,50))
+    pygame.display.flip()
+
+
+../_images/camera_average.jpg +
pygame.transform.threshold(self.thresholded,self.snapshot,self.ccolor,(30,30,30),(0,0,0),2)
+
+
+../_images/camera_thresh.jpg +

You can use the same idea to do a simple green screen/blue screen, by first +getting a background image and then thresholding against it. The below example +just has the camera pointed at a blank white wall in HSV colorspace.

+
def calibrate(self):
+    # capture a bunch of background images
+    bg = []
+    for i in range(0,5):
+      bg.append(self.cam.get_image(self.background))
+    # average them down to one to get rid of some noise
+    pygame.transform.average_surfaces(bg,self.background)
+    # blit it to the display surface
+    self.display.blit(self.background, (0,0))
+    pygame.display.flip()
+
+
+../_images/camera_background.jpg +
pygame.transform.threshold(self.thresholded,self.snapshot,(0,255,0),(30,30,30),(0,0,0),1,self.background)
+
+
+../_images/camera_green.jpg +
+
+

Using the Mask Module¶

+

The stuff above is great if you just want to display images, but with the +mask module, you can also use a camera as an +input device for a game. For example, going back to the example of +thresholding out a specific object, we can find the position of that object and +use it to control an on screen object.

+
def get_and_flip(self):
+    self.snapshot = self.cam.get_image(self.snapshot)
+    # threshold against the color we got before
+    mask = pygame.mask.from_threshold(self.snapshot, self.ccolor, (30, 30, 30))
+    self.display.blit(self.snapshot,(0,0))
+    # keep only the largest blob of that color
+    connected = mask.connected_component()
+    # make sure the blob is big enough that it isn't just noise
+    if mask.count() > 100:
+        # find the center of the blob
+        coord = mask.centroid()
+        # draw a circle with size variable on the size of the blob
+        pygame.draw.circle(self.display, (0,255,0), coord, max(min(50,mask.count()/400),5))
+    pygame.display.flip()
+
+
+../_images/camera_mask.jpg +

This is just the most basic example. You can track multiple different colored +blobs, find the outlines of objects, have collision detection between real life +and in game objects, get the angle of an object to allow for even finer control, +and more. Have fun!

+
+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/ChimpLineByLine.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/ChimpLineByLine.html new file mode 100644 index 0000000..b9a9e39 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/ChimpLineByLine.html @@ -0,0 +1,597 @@ + + + + + + + + + Pygame Tutorials - Line By Line Chimp Example — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Line By Line Chimp¶

+
+
Author
+

Pete Shinners

+
+
Contact
+

pete@shinners.org

+
+
+
+
+
+

Introduction¶

+

In the pygame examples there is a simple example named "chimp". +This example simulates a punchable monkey moving around the screen with +promises of riches and reward. The example itself is very simple, and a +bit thin on error-checking code. This example program demonstrates many of +pygame's abilities, like creating a window, loading images and sounds, +rendering text, and basic event and mouse handling.

+

The program and images can be found inside the standard source distribution +of pygame. You can run it by running python -m pygame.examples.chimp in +your terminal.

+

This tutorial will go through the code block by block. Explaining how +the code works. There will also be mention of how the code could be improved +and what error checking could help out.

+

This is an excellent tutorial for people getting their first look at +the pygame code. Once pygame is fully installed, you can find +and run the chimp demo for yourself in the examples directory.

+
+

(no, this is not a banner ad, it's the screenshot)

+chimp game banner +

Full Source

+
+
+
+

Import Modules¶

+

This is the code that imports all the needed modules into your program. +It also checks for the availability of some of the optional pygame modules.

+
# Import Modules
+import os
+import pygame as pg
+
+if not pg.font:
+    print("Warning, fonts disabled")
+if not pg.mixer:
+    print("Warning, sound disabled")
+
+main_dir = os.path.split(os.path.abspath(__file__))[0]
+data_dir = os.path.join(main_dir, "data")
+
+
+

First, we import the standard "os" python module. This allow +us to do things like create platform independent file paths.

+

In the next line, we import the pygame package. In our case, we import +pygame as pg, so that all of the functionality of pygame is able to +be referenced from the namespace pg.

+

Some pygame modules are optional, and if they aren't found, +they evaluate to False. Because of that, we decide to print +a nice warning message if the font or +mixer modules in pygame are not available. +(Although they will only be unavailable in very uncommon situations).

+

Lastly, we prepare two paths for the rest of the code to use. +main_dir uses the os.path module and the __file__ variable provided +by Python to locate the game's python file, and extract the folder from +that path. It then prepares the variable data_dir to tell the +loading functions exactly where to look.

+
+
+

Loading Resources¶

+

Here we have two functions we can use to load images and sounds. We will +look at each function individually in this section.

+
def load_image(name, colorkey=None, scale=1):
+    fullname = os.path.join(data_dir, name)
+    image = pg.image.load(fullname)
+
+    size = image.get_size()
+    size = (size[0] * scale, size[1] * scale)
+    image = pg.transform.scale(image, size)
+
+    image = image.convert()
+    if colorkey is not None:
+        if colorkey == -1:
+            colorkey = image.get_at((0, 0))
+        image.set_colorkey(colorkey, pg.RLEACCEL)
+    return image, image.get_rect()
+
+
+

This function takes the name of an image to load. It also optionally +takes an argument it can use to set a colorkey for the image, and an argument +to scale the image. A colorkey is used in graphics to represent a color of the +image that is transparent.

+

The first thing this function does is create a full pathname to the file. +In this example all the resources are in a "data" subdirectory. By using +the os.path.join function, a pathname will be created that works for whatever +platform the game is running on.

+

Next we load the image using the pygame.image.load()load new image from a file (or file-like object) function. +After the image is loaded, we make an important +call to the convert() function. This makes a new copy of a Surface and converts +its color format and depth to match the display. This means blitting the +image to the screen will happen as quickly as possible.

+

We then scale the image, using the pygame.transform.scale()resize to new resolution function. +This function takes a Surface and the size it should be scaled to. To scale +by a scalar, we can get the size and scale the x and y by the scalar.

+

Last, we set the colorkey for the image. If the user supplied an argument +for the colorkey argument we use that value as the colorkey for the image. +This would usually just be a color RGB value, like (255, 255, 255) for +white. You can also pass a value of -1 as the colorkey. In this case the +function will lookup the color at the topleft pixel of the image, and use +that color for the colorkey.

+
def load_sound(name):
+    class NoneSound:
+        def play(self):
+            pass
+
+    if not pg.mixer or not pg.mixer.get_init():
+        return NoneSound()
+
+    fullname = os.path.join(data_dir, name)
+    sound = pg.mixer.Sound(fullname)
+
+    return sound
+
+
+

Next is the function to load a sound file. The first thing this function +does is check to see if the pygame.mixerpygame module for loading and playing sounds module was imported correctly. +If not, it returns a small class instance that has a dummy play method. +This will act enough like a normal Sound object for this game to run without +any extra error checking.

+

This function is similar to the image loading function, but handles some +different problems. First we create a full path to the sound image, and +load the sound file. Then we simply return the loaded Sound object.

+
+
+

Game Object Classes¶

+

Here we create two classes to represent the objects in our game. Almost +all the logic for the game goes into these two classes. We will look over +them one at a time here.

+
class Fist(pg.sprite.Sprite):
+    """moves a clenched fist on the screen, following the mouse"""
+
+    def __init__(self):
+        pg.sprite.Sprite.__init__(self)  # call Sprite initializer
+        self.image, self.rect = load_image("fist.png", -1)
+        self.fist_offset = (-235, -80)
+        self.punching = False
+
+    def update(self):
+        """move the fist based on the mouse position"""
+        pos = pg.mouse.get_pos()
+        self.rect.topleft = pos
+        self.rect.move_ip(self.fist_offset)
+        if self.punching:
+            self.rect.move_ip(15, 25)
+
+    def punch(self, target):
+        """returns true if the fist collides with the target"""
+        if not self.punching:
+            self.punching = True
+            hitbox = self.rect.inflate(-5, -5)
+            return hitbox.colliderect(target.rect)
+
+    def unpunch(self):
+        """called to pull the fist back"""
+        self.punching = False
+
+
+

Here we create a class to represent the players fist. It is derived from +the Sprite class included in the pygame.spritepygame module with basic game object classes module. The __init__ function +is called when new instances of this class are created. The first thing +we do is be sure to call the __init__ function for our base class. This +allows the Sprite's __init__ function to prepare our object for use as a +sprite. This game uses one of the sprite drawing Group classes. These classes +can draw sprites that have an "image" and "rect" attribute. By simply changing +these two attributes, the renderer will draw the current image at the current +position.

+

All sprites have an update() method. This function is typically called +once per frame. It is where you should put code that moves and updates +the variables for the sprite. The update() method for the fist moves the +fist to the location of the mouse pointer. It also offsets the fist position +slightly if the fist is in the "punching" state.

+

The following two functions punch() and unpunch() change the punching +state for the fist. The punch() method also returns a true value if the fist +is colliding with the given target sprite.

+
class Chimp(pg.sprite.Sprite):
+    """moves a monkey critter across the screen. it can spin the
+    monkey when it is punched."""
+
+    def __init__(self):
+        pg.sprite.Sprite.__init__(self)  # call Sprite intializer
+        self.image, self.rect = load_image("chimp.png", -1, 4)
+        screen = pg.display.get_surface()
+        self.area = screen.get_rect()
+        self.rect.topleft = 10, 90
+        self.move = 18
+        self.dizzy = False
+
+    def update(self):
+        """walk or spin, depending on the monkeys state"""
+        if self.dizzy:
+            self._spin()
+        else:
+            self._walk()
+
+    def _walk(self):
+        """move the monkey across the screen, and turn at the ends"""
+        newpos = self.rect.move((self.move, 0))
+        if not self.area.contains(newpos):
+            if self.rect.left < self.area.left or self.rect.right > self.area.right:
+                self.move = -self.move
+                newpos = self.rect.move((self.move, 0))
+                self.image = pg.transform.flip(self.image, True, False)
+        self.rect = newpos
+
+    def _spin(self):
+        """spin the monkey image"""
+        center = self.rect.center
+        self.dizzy = self.dizzy + 12
+        if self.dizzy >= 360:
+            self.dizzy = False
+            self.image = self.original
+        else:
+            rotate = pg.transform.rotate
+            self.image = rotate(self.original, self.dizzy)
+        self.rect = self.image.get_rect(center=center)
+
+    def punched(self):
+        """this will cause the monkey to start spinning"""
+        if not self.dizzy:
+            self.dizzy = True
+            self.original = self.image
+
+
+

The Chimp class is doing a little more work than the fist, but nothing +more complex. This class will move the chimp back and forth across the +screen. When the monkey is punched, he will spin around to exciting effect. +This class is also derived from the base Sprite +class, and is initialized the same as the fist. While initializing, the class +also sets the attribute "area" to be the size of the display screen.

+

The update function for the chimp simply looks at the current "dizzy" +state, which is true when the monkey is spinning from a punch. It calls either +the _spin or _walk method. These functions are prefixed with an underscore. +This is just a standard python idiom which suggests these methods should +only be used by the Chimp class. We could go so far as to give them a double +underscore, which would tell python to really try to make them private +methods, but we don't need such protection. :)

+

The _walk method creates a new position for the monkey by moving the current +rect by a given offset. If this new position crosses outside the display +area of the screen, it reverses the movement offset. It also mirrors the +image using the pygame.transform.flip()flip vertically and horizontally function. This is a crude effect +that makes the monkey look like he's turning the direction he is moving.

+

The _spin method is called when the monkey is currently "dizzy". The dizzy +attribute is used to store the current amount of rotation. When the monkey +has rotated all the way around (360 degrees) it resets the monkey image +back to the original, non-rotated version. Before calling the +pygame.transform.rotate()rotate an image function, you'll see the code makes a local +reference to the function simply named "rotate". There is no need to do that +for this example, it is just done here to keep the following line's length a +little shorter. Note that when calling the rotate function, we are always +rotating from the original monkey image. When rotating, there is a slight loss +of quality. Repeatedly rotating the same image and the quality would get worse +each time. Also, when rotating an image, the size of the image will actually +change. This is because the corners of the image will be rotated out, making +the image bigger. We make sure the center of the new image matches the center +of the old image, so it rotates without moving.

+

The last method is punched() which tells the sprite to enter its dizzy +state. This will cause the image to start spinning. It also makes a copy +of the current image named "original".

+
+
+

Initialize Everything¶

+

Before we can do much with pygame, we need to make sure its modules +are initialized. In this case we will also open a simple graphics window. +Now we are in the main() function of the program, which actually runs everything.

+
pg.init()
+screen = pg.display.set_mode((1280, 480), pg.SCALED)
+pg.display.set_caption("Monkey Fever")
+pg.mouse.set_visible(False)
+
+
+

The first line to initialize pygame takes care of a bit of +work for us. It checks through the imported pygame modules and attempts +to initialize each one of them. It is possible to go back and check if modules +failed to initialize, but we won't bother here. It is also possible to +take a lot more control and initialize each specific module by hand. That +type of control is generally not needed, but is available if you desire.

+

Next we set up the display graphics mode. Note that the pygame.displaypygame module to control the display window and screen +module is used to control all the display settings. In this case we are +asking for a 1280 by 480 window, with the SCALED display flag. +This automatically scales up the window for displays much larger than the +window.

+

Last we set the window title and turn off the mouse cursor for our +window. Very basic to do, and now we have a small black window ready to +do our bidding. Usually the cursor defaults to visible, so there is no need +to really set the state unless we want to hide it.

+
+
+

Create The Background¶

+

Our program is going to have text message in the background. It would +be nice for us to create a single surface to represent the background and +repeatedly use that. The first step is to create the surface.

+
background = pg.Surface(screen.get_size())
+background = background.convert()
+background.fill((170, 238, 187))
+
+
+

This creates a new surface for us that is the same size as the display +window. Note the extra call to convert() after creating the Surface. The +convert with no arguments will make sure our background is the same format +as the display window, which will give us the fastest results.

+

We also fill the entire background with a certain green color. The fill() +function usually takes an RGB triplet as arguments, but supports many +input formats. See the pygame.Colorpygame object for color representations for all the color formats.

+
+
+

Put Text On The Background, Centered¶

+

Now that we have a background surface, lets get the text rendered to it. We +only do this if we see the pygame.fontpygame module for loading and rendering fonts module has imported properly. +If not, we just skip this section.

+
if pg.font:
+    font = pg.font.Font(None, 64)
+    text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10))
+    textpos = text.get_rect(centerx=background.get_width() / 2, y=10)
+    background.blit(text, textpos)
+
+
+

As you see, there are a couple steps to getting this done. First we +must create the font object and render it into a new surface. We then find +the center of that new surface and blit (paste) it onto the background.

+

The font is created with the font module's Font() constructor. Usually +you will pass the name of a TrueType font file to this function, but we +can also pass None, which will use a default font. The Font constructor +also needs to know the size of font we want to create.

+

We then render that font into a new surface. The render function creates +a new surface that is the appropriate size for our text. In this case +we are also telling render to create antialiased text (for a nice smooth +look) and to use a dark grey color.

+

Next we need to find the centered position of the text on our display. +We create a "Rect" object from the text dimensions, which allows us to +easily assign it to the screen center.

+

Finally we blit (blit is like a copy or paste) the text onto the background +image.

+
+
+

Display The Background While Setup Finishes¶

+

We still have a black window on the screen. Lets show our background +while we wait for the other resources to load.

+
screen.blit(background, (0, 0))
+pygame.display.flip()
+
+
+

This will blit our entire background onto the display window. The +blit is self explanatory, but what about this flip routine?

+

In pygame, changes to the display surface are not immediately visible. +Normally, a display must be updated in areas that have changed for them +to be visible to the user. In this case the flip() function works nicely +because it simply handles the entire window area.

+
+
+

Prepare Game Object¶

+

Here we create all the objects that the game is going to need.

+
whiff_sound = load_sound("whiff.wav")
+punch_sound = load_sound("punch.wav")
+chimp = Chimp()
+fist = Fist()
+allsprites = pg.sprite.RenderPlain((chimp, fist))
+clock = pg.time.Clock()
+
+
+

First we load two sound effects using the load_sound function we defined +above. Then we create an instance of each of our sprite classes. And lastly +we create a sprite Group which will contain all +our sprites.

+

We actually use a special sprite group named RenderPlain. This sprite group can draw all the sprites it +contains to the screen. It is called RenderPlain because there are actually +more advanced Render groups. But for our game, we just need simple drawing. We +create the group named "allsprites" by passing a list with all the sprites that +should belong in the group. We could later on add or remove sprites from this +group, but in this game we won't need to.

+

The clock object we create will be used to help control our game's framerate. +we will use it in the main loop of our game to make sure it doesn't run too fast.

+
+
+

Main Loop¶

+

Nothing much here, just an infinite loop.

+
going = True
+while going:
+    clock.tick(60)
+
+
+

All games run in some sort of loop. The usual order of things is to +check on the state of the computer and user input, move and update the +state of all the objects, and then draw them to the screen. You'll see +that this example is no different.

+

We also make a call to our clock object, which will make sure our game +doesn't run faster than 60 frames per second.

+
+
+

Handle All Input Events¶

+

This is an extremely simple case of working the event queue.

+
for event in pg.event.get():
+    if event.type == pg.QUIT:
+        going = False
+    elif event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE:
+        going = False
+    elif event.type == pg.MOUSEBUTTONDOWN:
+        if fist.punch(chimp):
+            punch_sound.play()  # punch
+            chimp.punched()
+        else:
+            whiff_sound.play()  # miss
+    elif event.type == pg.MOUSEBUTTONUP:
+        fist.unpunch()
+
+
+

First we get all the available Events from pygame and loop through each +of them. The first two tests see if the user has quit our game, or pressed +the escape key. In these cases we just set going to False, allowing +us out of the infinite loop.

+

Next we just check to see if the mouse button was pressed or released. +If the button was pressed, we ask the fist object if it has collided with +the chimp. We play the appropriate sound effect, and if the monkey was hit, +we tell him to start spinning (by calling his punched() method).

+
+
+

Update the Sprites¶

+
allsprites.update()
+
+
+

Sprite groups have an update() method, which simply calls the update method +for all the sprites it contains. Each of the objects will move around, depending +on which state they are in. This is where the chimp will move one step side +to side, or spin a little farther if he was recently punched.

+
+
+

Draw The Entire Scene¶

+

Now that all the objects are in the right place, time to draw them.

+
screen.blit(background, (0, 0))
+allsprites.draw(screen)
+pygame.display.flip()
+
+
+

The first blit call will draw the background onto the entire screen. This +erases everything we saw from the previous frame (slightly inefficient, but +good enough for this game). Next we call the draw() method of the sprite +container. Since this sprite container is really an instance of the "DrawPlain" +sprite group, it knows how to draw our sprites. Lastly, we flip() the contents +of pygame's software double buffer to the screen. This makes everything we've +drawn visible all at once.

+
+
+

Game Over¶

+

User has quit, time to clean up.

+
pg.quit()
+
+
+

Cleaning up the running game in pygame is extremely simple. +Since all variables are automatically destructed, we don't really have to do +anything, but calling pg.quit() explicitly cleans up pygame's internals.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/DisplayModes.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/DisplayModes.html new file mode 100644 index 0000000..84603cd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/DisplayModes.html @@ -0,0 +1,314 @@ + + + + + + + + + Pygame Tutorials - Setting Display Modes — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Setting Display Modes¶

+
+
Author
+

Pete Shinners

+
+
Contact
+

pete@shinners.org

+
+
+
+

Introduction¶

+

Setting the display mode in pygame creates a visible image surface +on the monitor. +This surface can either cover the full screen, or be windowed +on platforms that support a window manager. +The display surface is nothing more than a standard pygame surface object. +There are special functions needed in the pygame.displaypygame module to control the display window and screen +module to keep the image surface contents updated on the monitor.

+

Setting the display mode in pygame is an easier task than with most +graphic libraries. +The advantage is if your display mode is not available, +pygame will emulate the display mode that you asked for. +Pygame will select a display resolution and color depth that best matches +the settings you have requested, +then allow you to access the display with the format you have requested. +In reality, since the pygame.displaypygame module to control the display window and screen module is +a binding around the SDL library, SDL is really doing all this work.

+

There are advantages and disadvantages to setting the display mode in this +manner. +The advantage is that if your game requires a specific display mode, +your game will run on platforms that do not support your requirements. +It also makes life easier when you're getting something started, +it is always easy to go back later and make the mode selection a little more +particular. +The disadvantage is that what you request is not always what you will get. +There is also a performance penalty when the display mode must be emulated. +This tutorial will help you understand the different methods for querying +the platforms display capabilities, and setting the display mode for your game.

+
+
+

Setting Basics¶

+

The first thing to learn about is how to actually set the current display mode. +The display mode may be set at any time after the pygame.displaypygame module to control the display window and screen +module has been initialized. +If you have previously set the display mode, +setting it again will change the current mode. +Setting the display mode is handled with the function +pygame.display.set_mode((width, height), flags, depth)Initialize a window or screen for display. +The only required argument in this function is a sequence containing +the width and height of the new display mode. +The depth flag is the requested bits per pixel for the surface. +If the given depth is 8, pygame will create a color-mapped surface. +When given a higher bit depth, pygame will use a packed color mode. +Much more information about depths and color modes can be found in the +documentation for the display and surface modules. +The default value for depth is 0. +When given an argument of 0, pygame will select the best bit depth to use, +usually the same as the system's current bit depth. +The flags argument lets you control extra features for the display mode. +Again, more information about this is found in the pygame reference documents.

+
+
+

How to Decide¶

+

So how do you select a display mode that is going to work best with your +graphic resources and the platform your game is running on? +There are several methods for gathering information about the display device. +All of these methods must be called after the display module has been +initialized, but you likely want to call them before setting the display mode. +First, pygame.display.Info()Create a video display information object +will return a special object type of VidInfo, +which can tell you a lot about the graphics driver capabilities. +The function +pygame.display.list_modes(depth, flags)Get list of available fullscreen modes +can be used to find the supported graphic modes by the system. +pygame.display.mode_ok((width, height), flags, depth)Pick the best color depth for a display mode takes the same arguments as +set_mode(), +but returns the closest matching bit depth to the one you request. +Lastly, pygame.display.get_driver()Get the name of the pygame display backend +will return the name of the graphics driver selected by pygame.

+

Just remember the golden rule. +Pygame will work with pretty much any display mode you request. +Some display modes will need to be emulated, +which will slow your game down, +since pygame will need to convert every update you make to the +"real" display mode. The best bet is to always let pygame +choose the best bit depth, +and convert all your graphic resources to that format when they are loaded. +You let pygame choose its bit depth by calling +set_mode() +with no depth argument or a depth of 0, +or you can call +mode_ok() +to find a closest matching bit depth to what you need.

+

When your display mode is windowed, +you usually must match the same bit depth as the desktop. +When you are fullscreen, some platforms can switch to any bit depth that +best suits your needs. +You can find the depth of the current desktop if you get a VidInfo object +before ever setting your display mode.

+

After setting the display mode, +you can find out information about its settings by getting a VidInfo object, +or by calling any of the Surface.get* methods on the display surface.

+
+
+

Functions¶

+

These are the routines you can use to determine the most appropriate +display mode. +You can find more information about these functions in the display module +documentation.

+
+

pygame.display.mode_ok(size, flags, depth)Pick the best color depth for a display mode

+
+

This function takes the exact same arguments as pygame.display.set_mode(). +It returns the best available bit depth for the mode you have described. +If this returns zero, +then the desired display mode is not available without emulation.

+
+

pygame.display.list_modes(depth, flags)Get list of available fullscreen modes

+
+

Returns a list of supported display modes with the requested +depth and flags. +An empty list is returned when there are no modes. +The flags argument defaults to FULLSCREEN. +If you specify your own flags without FULLSCREEN, +you will likely get a return value of -1. +This means that any display size is fine, since the display will be windowed. +Note that the listed modes are sorted largest to smallest.

+
+

pygame.display.Info()Create a video display information object

+
+

This function returns an object with many members describing +the display device. +Printing the VidInfo object will quickly show you all the +members and values for this object.

+
>>> import pygame.display
+>>> pygame.display.init()
+>>> info = pygame.display.Info()
+>>> print(info)
+<VideoInfo(hw = 0, wm = 1,video_mem = 0
+        blit_hw = 0, blit_hw_CC = 0, blit_hw_A = 0,
+        blit_sw = 0, blit_sw_CC = 0, blit_sw_A = 0,
+        bitsize  = 32, bytesize = 4,
+        masks =  (16711680, 65280, 255, 0),
+        shifts = (16, 8, 0, 0),
+        losses =  (0, 0, 0, 8),
+        current_w = 1920, current_h = 1080
+>
+
+
+
+
+

You can test all these flags as simply members of the VidInfo object.

+
+
+

Examples¶

+

Here are some examples of different methods to init the graphics display. +They should help you get an idea of how to go about setting your display mode.

+
>>> #give me the best depth with a 640 x 480 windowed display
+>>> pygame.display.set_mode((640, 480))
+
+>>> #give me the biggest 16-bit display available
+>>> modes = pygame.display.list_modes(16)
+>>> if not modes:
+...     print('16-bit not supported')
+... else:
+...     print('Found Resolution:', modes[0])
+...     pygame.display.set_mode(modes[0], FULLSCREEN, 16)
+
+>>> #need an 8-bit surface, nothing else will do
+>>> if pygame.display.mode_ok((800, 600), 0, 8) != 8:
+...     print('Can only work with an 8-bit display, sorry')
+... else:
+...     pygame.display.set_mode((800, 600), 0, 8)
+
+
+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/ImportInit.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/ImportInit.html new file mode 100644 index 0000000..caf1ff8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/ImportInit.html @@ -0,0 +1,197 @@ + + + + + + + + + Pygame Tutorials - Import and Initialize — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Import and Initialize¶

+
+
Author
+

Pete Shinners

+
+
Contact
+

pete@shinners.org

+
+
+

Getting pygame imported and initialized is a very simple process. It is also +flexible enough to give you control over what is happening. Pygame is a +collection of different modules in a single python package. Some of the +modules are written in C, and some are written in python. Some modules +are also optional, and might not always be present.

+

This is just a quick introduction on what is going on when you import pygame. +For a clearer explanation definitely see the pygame examples.

+
+

Import¶

+

First we must import the pygame package. Since pygame version 1.4 this +has been updated to be much easier. Most games will import all of pygame like this.

+
import pygame
+from pygame.locals import *
+
+
+

The first line here is the only necessary one. It imports all the available pygame +modules into the pygame package. The second line is optional, and puts a limited +set of constants and functions into the global namespace of your script.

+

An important thing to keep in mind is that several pygame modules are optional. +For example, one of these is the font module. When you "import pygame", pygame +will check to see if the font module is available. If the font module is available +it will be imported as "pygame.font". If the module is not available, "pygame.font" +will be set to None. This makes it fairly easy to later on test if the font module is available.

+
+
+

Init¶

+

Before you can do much with pygame, you will need to initialize it. The most common +way to do this is just make one call.

+
pygame.init()
+
+
+

This will attempt to initialize all the pygame modules for you. Not all pygame modules +need to be initialized, but this will automatically initialize the ones that do. You can +also easily initialize each pygame module by hand. For example to only initialize the +font module you would just call.

+
pygame.font.init()
+
+
+

Note that if there is an error when you initialize with "pygame.init()", it will silently fail. +When hand initializing modules like this, any errors will raise an exception. Any +modules that must be initialized also have a "get_init()" function, which will return true +if the module has been initialized.

+

It is safe to call the init() function for any module more than once.

+
+
+

Quit¶

+

Modules that are initialized also usually have a quit() function that will clean up. +There is no need to explicitly call these, as pygame will cleanly quit all the +initialized modules when python finishes.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/MakeGames.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/MakeGames.html new file mode 100644 index 0000000..082e75e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/MakeGames.html @@ -0,0 +1,237 @@ + + + + + + + + + Making Games With Pygame — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Making Games With Pygame¶

+
+
+
+

Table of Contents¶

+

1. Introduction

+
+
+

2. Revision: Pygame fundamentals

+
+
+

3. Kicking things off

+
+
+

4. Game object classes

+
+
+

5. User-controllable objects

+
+
+

6. Putting it all together

+
+
+
+
+

1. Introduction¶

+

First of all, I will assume you have read the Line By Line Chimp +tutorial, which introduces the basics of Python and pygame. Give it a read before reading this +tutorial, as I won't bother repeating what that tutorial says (or at least not in as much detail). This tutorial is aimed at those +who understand how to make a ridiculously simple little "game", and who would like to make a relatively simple game like Pong. +It introduces you to some concepts of game design, some simple mathematics to work out ball physics, and some ways to keep your +game easy to maintain and expand.

+

All the code in this tutorial works toward implementing TomPong, +a game I've written. By the end of the tutorial, you should not only have a firmer grasp of pygame, but +you should also understand how TomPong works, and how to make your own version.

+

Now, for a brief recap of the basics of pygame. A common method of organising the code for a game is to divide it into the following +six sections:

+
+
    +
  • Load modules which are required in the game. Standard stuff, except that you should +remember to import the pygame local names as well as the pygame module itself

  • +
  • Resource handling classes; define some classes to handle your most basic resources, +which will be loading images and sounds, as well as connecting and disconnecting to and from networks, loading save game +files, and any other resources you might have.

  • +
  • Game object classes; define the classes for your game object. In the pong example, +these will be one for the player's bat (which you can initialise multiple times, one for each player in the game), and one +for the ball (which can again have multiple instances). If you're going to have a nice in-game menu, it's also a good idea to make a +menu class.

  • +
  • Any other game functions; define other necessary functions, such as scoreboards, menu +handling, etc. Any code that you could put into the main game logic, but that would make understanding said logic harder, should +be put into its own function. So as plotting a scoreboard isn't game logic, it should be moved into a function.

  • +
  • Initialise the game, including the pygame objects themselves, the background, the game +objects (initialising instances of the classes) and any other little bits of code you might want to add in.

  • +
  • The main loop, into which you put any input handling (i.e. watching for users hitting +keys/mouse buttons), the code for updating the game objects, and finally for updating the screen.

  • +
+
+

Every game you make will have some or all of those sections, possibly with more of your own. For the purposes of this tutorial, I will +write about how TomPong is laid out, and the ideas I write about can be transferred to almost any kind of game you might make. I will +also assume that you want to keep all of the code in a single file, but if you're making a reasonably large game, it's often a good +idea to source certain sections into module files. Putting the game object classes into a file called objects.py, for +example, can help you keep game logic separate from game objects. If you have a lot of resource handling code, it can also be handy +to put that into resources.py. You can then from objects,resources import * to import all of the +classes and functions.

+
+
+

1.1. A note on coding styles¶

+

The first thing to remember when approaching any programming project is to decide on a coding style, and stay consistent. Python +solves a lot of the problems because of its strict interpretation of whitespace and indentation, but you can still choose the size +of your indentations, whether you put each module import on a new line, how you comment code, etc. You'll see how I do all of this +in the code examples; you needn't use my style, but whatever style you adopt, use it all the way through the program code. Also try +to document all of your classes, and comment on any bits of code that seem obscure, though don't start commenting the obvious. I've +seen plenty of people do the following:

+
player1.score += scoreup        # Add scoreup to player1 score
+
+
+

The worst code is poorly laid out, with seemingly random changes in style, and poor documentation. Poor code is not only annoying +for other people, but it also makes it difficult for you to maintain.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/MoveIt.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/MoveIt.html new file mode 100644 index 0000000..254322e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/MoveIt.html @@ -0,0 +1,539 @@ + + + + + + + + + Pygame Tutorials - Help! How Do I Move An Image? — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Help! How Do I Move An Image?¶

+
+
Author
+

Pete Shinners

+
+
Contact
+

pete@shinners.org

+
+
+

Many people new to programming and graphics have a hard time figuring +out how to make an image move around the screen. Without understanding +all the concepts, it can be very confusing. You're not the first person +to be stuck here, I'll do my best to take things step by step. We'll even +try to end with methods of keeping your animations efficient.

+

Note that we won't be teaching you to program with python in this article, +just introduce you to some of the basics with pygame.

+
+

Just Pixels On The Screen¶

+

Pygame has a display Surface. This is basically an image that is visible +on the screen, and the image is made up of pixels. The main way you change +these pixels is by calling the blit() function. This copies the pixels +from one image onto another.

+

This is the first thing to understand. When you blit an image onto the +screen, you are simply changing the color of the pixels on the screen. +Pixels aren't added or moved, we just change the colors of the pixels already +on the screen. These images you blit to the screen are also Surfaces in +pygame, but they are in no way connected to the display Surface. When they +are blitted to the screen they are copied into the display, but you still +have a unique copy of the original.

+

With this brief description. Perhaps you can already understand what +is needed to "move" an image. We don't actually move anything at all. We +simply blit the image in a new position. But before we draw the image in +the new position, we'll need to "erase" the old one. Otherwise the image +will be visible in two places on the screen. By rapidly erasing the image +and redrawing it in a new place, we achieve the "illusion" of movement.

+

Through the rest of this tutorial we will break this process down into +simpler steps. Even explaining the best ways to have multiple images moving +around the screen. You probably already have questions. Like, how do we +"erase" the image before drawing it in a new position? Perhaps you're still +totally lost? Well hopefully the rest of this tutorial can straighten things +out for you.

+
+
+

Let's Go Back A Step¶

+

Perhaps the concept of pixels and images is still a little foreign to +you? Well good news, for the next few sections we are going to use code that +does everything we want, it just doesn't use pixels. We're going to create +a small python list of 6 numbers, and imagine it represents some fantastic +graphics we could see on the screen. It might actually be surprising how +closely this represents exactly what we'll later be doing with real graphics.

+

So let's begin by creating our screen list and fill it with a beautiful +landscape of 1s and 2s.

+
>>> screen = [1, 1, 2, 2, 2, 1]
+>>> print screen
+[1, 1, 2, 2, 2, 1]
+
+
+

Now we've created our background. It's not going to be very exciting +unless we also draw a player on the screen. We'll create a mighty hero +that looks like the number 8. Let's stick him near the middle of the map +and see what it looks like.

+
>>> screen[3] = 8
+>>> print screen
+[1, 1, 2, 8, 2, 1]
+
+
+

This might have been as far as you've gotten if you jumped right in doing +some graphics programming with pygame. You've got some nice looking stuff +on the screen, but it cannot move anywhere. Perhaps now that our screen +is just a list of numbers, it's easier to see how to move him?

+
+
+

Making The Hero Move¶

+

Before we can start moving the character. We need to keep track of some +sort of position for him. In the last section when we drew him, we just picked +an arbitrary position. Let's do it a little more officially this time.

+
>>> playerpos = 3
+>>> screen[playerpos] = 8
+>>> print screen
+[1, 1, 2, 8, 2, 1]
+
+
+

Now it is pretty easy to move him to a new position. We simply change +the value of playerpos, and draw him on the screen again.

+
>>> playerpos = playerpos - 1
+>>> screen[playerpos] = 8
+>>> print screen
+[1, 1, 8, 8, 2, 1]
+
+
+

Whoops. Now we can see two heroes. One in the old position, and one +in his new position. This is exactly the reason we need to "erase" the hero +in his old position before we draw him in the new position. To erase him, +we need to change that value in the list back to what it was before the hero +was there. That means we need to keep track of the values on the screen before +the hero replaced them. There's several way you could do this, but the easiest +is usually to keep a separate copy of the screen background. This means +we need to make some changes to our little game.

+
+
+

Creating A Map¶

+

What we want to do is create a separate list we will call our background. +We will create the background so it looks like our original screen did, +with 1s and 2s. Then we will copy each item from the background to the screen. +After that we can finally draw our hero back onto the screen.

+
>>> background = [1, 1, 2, 2, 2, 1]
+>>> screen = [0]*6                         #a new blank screen
+>>> for i in range(6):
+...     screen[i] = background[i]
+>>> print screen
+[1, 1, 2, 2, 2, 1]
+>>> playerpos = 3
+>>> screen[playerpos] = 8
+>>> print screen
+[1, 1, 2, 8, 2, 1]
+
+
+

It may seem like a lot of extra work. We're no farther off than we were +before the last time we tried to make him move. But this time we have the +extra information we need to move him properly.

+
+
+

Making The Hero Move (Take 2)¶

+

This time it will be easy to move the hero around. First we will erase +the hero from his old position. We do this by copying the correct value +from the background onto the screen. Then we will draw the character in his +new position on the screen

+
>>> print screen
+[1, 1, 2, 8, 2, 1]
+>>> screen[playerpos] = background[playerpos]
+>>> playerpos = playerpos - 1
+>>> screen[playerpos] = 8
+>>> print screen
+[1, 1, 8, 2, 2, 1]
+
+
+

There it is. The hero has moved one space to the left. We can use this +same code to move him to the left again.

+
>>> screen[playerpos] = background[playerpos]
+>>> playerpos = playerpos - 1
+>>> screen[playerpos] = 8
+>>> print screen
+[1, 8, 2, 2, 2, 1]
+
+
+

Excellent! This isn't exactly what you'd call smooth animation. But with +a couple small changes, we'll make this work directly with graphics on +the screen.

+
+
+

Definition: "blit"¶

+

In the next sections we will transform our program from using lists to +using real graphics on the screen. When displaying the graphics we will +use the term blit frequently. If you are new to doing graphics +work, you are probably unfamiliar with this common term.

+

BLIT: Basically, blit means to copy graphics from one image +to another. A more formal definition is to copy an array of data +to a bitmapped array destination. You can think of blit as just +"assigning" pixels. Much like setting values in our screen-list +above, blitting assigns the color of pixels in our image.

+

Other graphics libraries will use the word bitblt, or just blt, +but they are talking about the same thing. It is basically copying +memory from one place to another. Actually, it is a bit more advanced than +straight copying of memory, since it needs to handle things like pixel +formats, clipping, and scanline pitches. Advanced blitters can also +handle things like transparency and other special effects.

+
+
+

Going From The List To The Screen¶

+

To take the code we see in the above to examples and make them work with +pygame is very straightforward. We'll pretend we have loaded some pretty +graphics and named them "terrain1", "terrain2", and "hero". Where before +we assigned numbers to a list, we now blit graphics to the screen. Another +big change, instead of using positions as a single index (0 through 5), we +now need a two dimensional coordinate. We'll pretend each of the graphics +in our game is 10 pixels wide.

+
>>> background = [terrain1, terrain1, terrain2, terrain2, terrain2, terrain1]
+>>> screen = create_graphics_screen()
+>>> for i in range(6):
+...     screen.blit(background[i], (i*10, 0))
+>>> playerpos = 3
+>>> screen.blit(playerimage, (playerpos*10, 0))
+
+
+

Hmm, that code should seem very familiar, and hopefully more importantly; +the code above should make a little sense. Hopefully my illustration of setting +simple values in a list shows the similarity of setting pixels on the screen +(with blit). The only part that's really extra work is converting the player position +into coordinates on the screen. For now we just use a crude (playerpos*10, 0) , +but we can certainly do better than that. Now let's move the player +image over a space. This code should have no surprises.

+
>>> screen.blit(background[playerpos], (playerpos*10, 0))
+>>> playerpos = playerpos - 1
+>>> screen.blit(playerimage, (playerpos*10, 0))
+
+
+

There you have it. With this code we've shown how to display a simple background +with a hero's image on it. Then we've properly moved that hero one space +to the left. So where do we go from here? Well for one the code is still +a little awkward. First thing we'll want to do is find a cleaner way to represent +the background and player position. Then perhaps a bit of smoother, real +animation.

+
+
+

Screen Coordinates¶

+

To position an object on the screen, we need to tell the blit() function +where to put the image. In pygame we always pass positions as an (X,Y) coordinate. +This represents the number of pixels to the right, and the number of pixels +down to place the image. The top-left corner of a Surface is coordinate (0, +0). Moving to the right a little would be (10, 0), and then moving down just +as much would be (10, 10). When blitting, the position argument represents +where the topleft corner of the source should be placed on the destination.

+

Pygame comes with a convenient container for these coordinates, it is a +Rect. The Rect basically represents a rectangular area in these coordinates. +It has topleft corner and a size. The Rect comes with a lot of convenient +methods which help you move and position them. In our next examples we will +represent the positions of our objects with the Rects.

+

Also know that many functions in pygame expect Rect arguments. All of these +functions can also accept a simple tuple of 4 elements (left, top, width, +height). You aren't always required to use these Rect objects, but you will +mainly want to. Also, the blit() function can accept a Rect as its position +argument, it simply uses the topleft corner of the Rect as the real position.

+
+
+

Changing The Background¶

+

In all our previous sections, we've been storing the background as a list +of different types of ground. That is a good way to create a tile-based game, +but we want smooth scrolling. To make that a little easier, we're going to +change the background into a single image that covers the whole screen. This +way, when we want to "erase" our objects (before redrawing them) we only need +to blit the section of the erased background onto the screen.

+

By passing an optional third Rect argument to blit, we tell blit to only +use that subsection of the source image. You'll see that in use below as we +erase the player image.

+

Also note, now when we finish drawing to the screen, we call pygame.display.update() +which will show everything we've drawn onto the screen.

+
+
+

Smooth Movement¶

+

To make something appear to move smoothly, we only want to move it a couple +pixels at a time. Here is the code to make an object move smoothly across +the screen. Based on what we already now know, this should look pretty simple.

+
>>> screen = create_screen()
+>>> player = load_player_image()
+>>> background = load_background_image()
+>>> screen.blit(background, (0, 0))        #draw the background
+>>> position = player.get_rect()
+>>> screen.blit(player, position)          #draw the player
+>>> pygame.display.update()                #and show it all
+>>> for x in range(100):                   #animate 100 frames
+...     screen.blit(background, position, position) #erase
+...     position = position.move(2, 0)     #move player
+...     screen.blit(player, position)      #draw new player
+...     pygame.display.update()            #and show it all
+...     pygame.time.delay(100)             #stop the program for 1/10 second
+
+
+

There you have it. This is all the code that is needed to smoothly animate +an object across the screen. We can even use a pretty background character. +Another benefit of doing the background this way, the image for the player +can have transparency or cutout sections and it will still draw correctly +over the background (a free bonus).

+

We also throw in a call to pygame.time.delay() at the end of our loop above. +This slows down our program a little, otherwise it might run so fast you might +not see it.

+
+
+

So, What Next?¶

+

Well there we have it. Hopefully this article has done everything it promised +to do. But, at this point the code really isn't ready for the next best-selling +game. How do we easily have multiple moving objects? What exactly are those +mysterious functions like load_player_image()? We also need a way to get simple +user input, and loop for more than 100 frames. We'll take the example we +have here, and turn it into an object oriented creation that would make momma +proud.

+
+
+

First, The Mystery Functions¶

+

Full information on these types of functions can be found in other tutorials +and reference. The pygame.image module has a load() function which will do +what we want. The lines to load the images should become this.

+
>>> player = pygame.image.load('player.bmp').convert()
+>>> background = pygame.image.load('liquid.bmp').convert()
+
+
+

We can see that's pretty simple, the load function just takes a filename +and returns a new Surface with the loaded image. After loading we make a call +to the Surface method, convert(). Convert returns us a new Surface of the +image, but now converted to the same pixel format as our display. Since the +images will be the same format at the screen, they will blit very quickly. +If we did not convert, the blit() function is slower, since it has to convert +from one type of pixel to another as it goes.

+

You may also have noticed that both the load() and convert() return new +Surfaces. This means we're really creating two Surfaces on each of these +lines. In other programming languages, this results in a memory leak (not +a good thing). Fortunately Python is smart enough to handle this, and pygame +will properly clean up the Surface we end up not using.

+

The other mystery function we saw in the above example was create_screen(). +In pygame it is simple to create a new window for graphics. The code to create +a 640x480 surface is below. By passing no other arguments, pygame will just +pick the best color depth and pixel format for us.

+
>>> screen = pygame.display.set_mode((640, 480))
+
+
+
+
+

Handling Some Input¶

+

We desperately need to change the main loop to look for any user input, (like +when the user closes the window). We need to add "event handling" to our +program. All graphical programs use this Event Based design. The program +gets events like "keyboard pressed" or "mouse moved" from the computer. Then +the program responds to the different events. Here's what the code should +look like. Instead of looping for 100 frames, we'll keep looping until the +user asks us to stop.

+
>>> while 1:
+...     for event in pygame.event.get():
+...         if event.type in (QUIT, KEYDOWN):
+...             sys.exit()
+...     move_and_draw_all_game_objects()
+
+
+

What this code simply does is, first loop forever, then check if there are +any events from the user. We exit the program if the user presses the keyboard +or the close button on the window. After we've checked all the events we +move and draw our game objects. (We'll also erase them before they move, +too)

+
+
+

Moving Multiple Images¶

+

Here's the part where we're really going to change things around. Let's +say we want 10 different images moving around on the screen. A good way to +handle this is to use python's classes. We'll create a class that represents +our game object. This object will have a function to move itself, and then +we can create as many as we like. The functions to draw and move the object +need to work in a way where they only move one frame (or one step) at a time. +Here's the python code to create our class.

+
>>> class GameObject:
+...     def __init__(self, image, height, speed):
+...         self.speed = speed
+...         self.image = image
+...         self.pos = image.get_rect().move(0, height)
+...     def move(self):
+...         self.pos = self.pos.move(0, self.speed)
+...         if self.pos.right > 600:
+...             self.pos.left = 0
+
+
+

So we have two functions in our class. The init function constructs our object. +It positions the object and sets its speed. The move method moves the object +one step. If it's gone too far, it moves the object back to the left.

+
+
+

Putting It All Together¶

+

Now with our new object class, we can put together the entire game. Here +is what the main function for our program will look like.

+
>>> screen = pygame.display.set_mode((640, 480))
+>>> player = pygame.image.load('player.bmp').convert()
+>>> background = pygame.image.load('background.bmp').convert()
+>>> screen.blit(background, (0, 0))
+>>> objects = []
+>>> for x in range(10):                    #create 10 objects</i>
+...     o = GameObject(player, x*40, x)
+...     objects.append(o)
+>>> while 1:
+...     for event in pygame.event.get():
+...         if event.type in (QUIT, KEYDOWN):
+...             sys.exit()
+...     for o in objects:
+...         screen.blit(background, o.pos, o.pos)
+...     for o in objects:
+...         o.move()
+...         screen.blit(o.image, o.pos)
+...     pygame.display.update()
+...     pygame.time.delay(100)
+
+
+

And there it is. This is the code we need to animate 10 objects on the screen. +The only point that might need explaining is the two loops we use to clear +all the objects and draw all the objects. In order to do things properly, +we need to erase all the objects before drawing any of them. In our sample +here it may not matter, but when objects are overlapping, using two loops +like this becomes important.

+
+
+

You Are On Your Own From Here¶

+

So what would be next on your road to learning? Well first playing around +with this example a bit. The full running version of this example is available +in the pygame examples directory. It is the example named +moveit.py . +Take a look at the code and play with it, run it, learn it.

+

Things you may want to work on is maybe having more than one type of object. +Finding a way to cleanly "delete" objects when you don't want to show them +any more. Also updating the display.update() call to pass a list of the areas +on-screen that have changed.

+

There are also other tutorials and examples in pygame that cover these +issues. So when you're ready to keep learning, keep on reading. :-)

+

Lastly, you can feel free to come to the pygame mailing list or chatroom +with any questions on this stuff. There's always folks on hand who can help +you out with this sort of business.

+

Lastly, have fun, that's what games are for!

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/PygameIntro.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/PygameIntro.html new file mode 100644 index 0000000..686796d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/PygameIntro.html @@ -0,0 +1,418 @@ + + + + + + + + + Pygame Intro — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Python Pygame Introduction¶

+
+
Author
+

Pete Shinners

+
+
Contact
+

pete@shinners.org

+
+
+

This article is an introduction to the pygame library +for Python programmers. +The original version appeared in the Py Zine, +volume 1 issue 3. This version contains minor revisions, to +create an all-around better article. Pygame is a Python extension +library that wraps the SDL library +and its helpers.

+
+

HISTORY¶

+

Pygame started in the summer of 2000. Being a C programmer of many +years, I discovered both Python and SDL at about the same time. You are +already familiar with Python, which was at version 1.5.2. You may need +an introduction to SDL, which is the Simple DirectMedia Layer. +Created by Sam Lantinga, SDL is a cross-platform C library for +controlling multimedia, comparable to DirectX. It has been used for +hundreds of commercial and open source games. I was impressed at how clean +and straightforward both projects were and it wasn't long before I +realized mixing Python and SDL was an interesting proposal.

+

I discovered a small project already under-way with exactly the same +idea, PySDL. Created by Mark Baker, PySDL was a straightforward +implementation of SDL as a Python extension. The interface was cleaner +than a generic SWIG wrapping, but I felt it forced a "C style" of code. +The sudden death of PySDL prompted me to take on a new project of my +own.

+

I wanted to put together a project that really took advantage of +Python. My goal was to make it easy to do the simple things, and +straightforward to do the difficult things. Pygame was started in +October, 2000. Six months later pygame version 1.0 was released.

+
+
+

TASTE¶

+

I find the best way to understand a new library is to jump straight +into an example. In the early days of pygame, I created a bouncing ball +animation with 7 lines of code. Let's take a look at a friendlier +version of that same thing. This should be simple enough to follow +along, and a complete breakdown follows.

+../_images/intro_ball.gif +
 1import sys, pygame
+ 2pygame.init()
+ 3
+ 4size = width, height = 320, 240
+ 5speed = [2, 2]
+ 6black = 0, 0, 0
+ 7
+ 8screen = pygame.display.set_mode(size)
+ 9
+10ball = pygame.image.load("intro_ball.gif")
+11ballrect = ball.get_rect()
+12
+13while 1:
+14    for event in pygame.event.get():
+15        if event.type == pygame.QUIT: sys.exit()
+16
+17    ballrect = ballrect.move(speed)
+18    if ballrect.left < 0 or ballrect.right > width:
+19        speed[0] = -speed[0]
+20    if ballrect.top < 0 or ballrect.bottom > height:
+21        speed[1] = -speed[1]
+22
+23    screen.fill(black)
+24    screen.blit(ball, ballrect)
+25    pygame.display.flip()
+
+
+

This is as simple as you can get for a bouncing animation. +First we see importing and initializing pygame is nothing noteworthy. +The import pygame imports the package with all the available +pygame modules. +The call to pygame.init() initializes each of these modules.

+

On line 8 we create a +graphical window with the call to pygame.display.set_mode(). +Pygame and SDL make this easy by defaulting to the best graphics modes +for the graphics hardware. You can override the mode and SDL will +compensate for anything the hardware cannot do. Pygame represents +images as Surface objects. +The display.set_mode() function creates a new Surface +object that represents the actual displayed graphics. Any drawing you +do to this Surface will become visible on the monitor.

+

At line 10 we load +our ball image. Pygame supports a variety of image formats through the +SDL_image library, including BMP, JPG, PNG, TGA, and GIF. +The pygame.image.load() function +returns us a Surface with the ball data. The Surface will keep any +colorkey or alpha transparency from the file. After loading the ball +image we create a variable named ballrect. Pygame comes with a +convenient utility object type named Rect, +which represents a rectangular area. Later, in the animation part of +the code, we will see what the Rect objects can do.

+

At this point, line 13, +our program is initialized and ready to run. Inside an infinite loop we +check for user input, move the ball, and then draw the ball. If you are +familiar with GUI programming, you have had experience with events and +event loops. In pygame this is no different, +we check if a QUIT event has happened. If so we +simply exit the program, pygame will ensure everything is cleanly +shutdown.

+

It is time to update our position for the ball. +Lines 17 moves the ballrect variable by the current speed. +Lines 18 thru 21 reverse the speed if the ball has moved outside the screen. +Not exactly Newtonian physics, but it is all we need.

+

On line 23 we erase +the screen by filling it with a black RGB color. If you have never +worked with animations this may seem strange. You may be asking "Why do +we need to erase anything, why don't we just move the ball on the +screen?" That is not quite the way computer animation works. Animation +is nothing more than a series of single images, which when displayed in +sequence do a very good job of fooling the human eye into seeing +motion. The screen is just a single image that the user sees. If we did +not take the time to erase the ball from the screen, we would actually +see a "trail" of the ball as we continuously draw the ball in its new +positions.

+

On line 24 we draw the ball image onto the screen. +Drawing of images is handled by the +Surface.blit() method. +A blit basically means copying pixel colors from one image to another. +We pass the blit method a source Surface +to copy from, and a position to place the source onto the destination.

+

The last thing we need to do is actually update the visible display. +Pygame manages the display with a double buffer. When we are finished +drawing we call the pygame.display.flip()Update the full display Surface to the screen method. +This makes everything we have drawn on the screen Surface +become visible. This buffering makes sure we only see completely drawn +frames on the screen. Without it, the user would see the half completed +parts of the screen as they are being created.

+

That concludes this short introduction to pygame. Pygame also has +modules to do things like input handling for the keyboard, mouse, and +joystick. It can mix audio and decode streaming music. +With the Surfaces you can draw simple +shapes, rotate and scale the picture, and even manipulate the pixels of +an image in realtime as numpy arrays. +Pygame also has the ability to act as a +cross platform display layer for PyOpenGL. Most of the pygame modules +are written in C, few are actually done in Python.

+

The pygame website has full reference documentation for every pygame +function and tutorials for all ranges of users. The pygame source comes +with many examples of things like monkey punching and UFO shooting.

+
+
+

PYTHON AND GAMING¶

+

"Is Python suitable for gaming?" The answer is, "It depends on the +game."

+

Python is actually quite capable at running games. It will likely even +surprise you how much is possible in under 30 milliseconds. Still, it +is not hard to reach the ceiling once your game begins to get more +complex. Any game running in realtime will be making full use of the +computer.

+../_images/intro_blade.jpg +

Over the past several years there has been an interesting trend in game development, +the move towards higher level languages. Usually a game is split into +two major parts. The game engine, which must be as fast as possible, +and the game logic, which makes the engine actually do something. It +wasn't long ago when the engine of a game was written in assembly, with +portions written in C. Nowadays, C has moved to the game engine, while +often the game itself is written in higher level scripting languages. +Games like Quake3 and Unreal run these scripts as portable bytecode.

+

In early 2001, developer Rebel Act Studios finished their game, +Severance: Blade of Darkness. Using their own custom 3D engine, the +rest of the game is written with Python. The game is a bloody action +3rd person perspective fighter. You control medieval warriors into +intricate decapitating combination attacks while exploring dungeons and +castles. You can download third party add-ons for this game, and find +they are nothing more than Python source files.

+

More recently, Python has been used in a variety of games like Freedom +Force, and Humungous' Backyard Sports Series.

+../_images/intro_freedom.jpg +

Pygame and SDL serve as an excellent C engine for 2D games. +Games will still find the largest part of their runtime is spent +inside SDL handling the graphics. +SDL can take advantage of graphics hardware acceleration. +Enabling this can change a game from running around 40 frames per +second to over 200 frames per second. When you see your Python game +running at 200 frames per second, you realize that Python and games can +work together.

+

It is impressive how well both Python and SDL work on multiple +platforms. For example, in May of 2001 I released my own full pygame +project, SolarWolf, an arcade style action game. One thing that has +surprised me is that one year later there has been no need for any +patches, bug fixes, or updates. The game was developed entirely on +windows, but runs on Linux, Mac OSX, and many Unixes without any extra +work on my end.

+

Still, there are very clear limitations. The best way to manage +hardware accelerated graphics is not always the way to get fastest +results from software rendering. Hardware support is not available on +all platforms. When a game gets more complex, it often must commit to +one or the other. SDL has some other design limitations, things like +full screen scrolling graphics can quickly bring your game down to +unplayable speeds. While SDL is not suitable for all types of games, +remember companies like Loki have used SDL to run a wide variety of +retail quality titles.

+

Pygame is fairly low-level when it comes to writing games. You'll +quickly find yourself needing to wrap common functions into your own +game environment. The great thing about this is there is nothing inside +pygame to get in your way. Your program is in full control of +everything. The side effect of that is you will find yourself borrowing +a lot of code to get a more advanced framework put together. You'll +need a better understanding of what you are doing.

+
+
+

CLOSING¶

+

Developing games is very rewarding, there is something exciting about +being able to see and interact with the code you've written. Pygame +currently has almost 30 other projects using it. Several of them are +ready to play now. You may be surprised to visit the pygame website, +and see what other users have been able to do with Python.

+

One thing that has caught my attention is the amount of people coming +to Python for the first time to try game development. I can see why +games are a draw for new programmers, but it can be difficult since +creating games requires a firmer understanding of the language. I've +tried to support this group of users by writing many examples and +pygame tutorials for people new to these concepts.

+

In the end, my advice is to keep it simple. I cannot stress this +enough. If you are planning to create your first game, there is a +lot to learn. Even a simpler game will challenge your designs, and +complex games don't necessarily mean fun games. When you understand +Python, you can use pygame to create a simple game in only one or two +weeks. From there you'll need a surprising amount of time to add +the polish to make that into a full presentable game.

+
+

Pygame Modules Overview¶

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

cdrom

playback

cursors

load cursor images, includes standard cursors

display

control the display window or screen

draw

draw simple shapes onto a Surface

event

manage events and the event queue

font

create and render TrueType fonts

image

save and load images

joystick

manage joystick devices

key

manage the keyboard

mouse

manage the mouse

sndarray

manipulate sounds with numpy

surfarray

manipulate images with numpy

time

control timing

transform

scale, rotate, and flip images

+
+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/SpriteIntro.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/SpriteIntro.html new file mode 100644 index 0000000..c15e56f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/SpriteIntro.html @@ -0,0 +1,498 @@ + + + + + + + + + Pygame Tutorials - Sprite Module Introduction — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Sprite Module Introduction¶

+
+
Author
+

Pete Shinners

+
+
Contact
+

pete@shinners.org

+
+
+

Pygame version 1.3 comes with a new module, pygame.sprite. This module is +written in Python and includes some higher-level classes to manage your game +objects. By using this module to its full potential, you can easily manage and +draw your game objects. The sprite classes are very optimized, so it's likely +your game will run faster with the sprite module than without.

+

The sprite module is also meant to be very generic. It turns out you can use it +with nearly any type of gameplay. All this flexibility comes with a slight +penalty, it needs a little understanding to properly use it. The +reference documentation for the sprite module can keep +you running, but you'll probably need a bit more explanation of how to use +pygame.sprite in your own game.

+

Several of the pygame examples (like "chimp" and "aliens") have been updated to +use the sprite module. You may want to look into those first to see what this +sprite module is all about. The chimp module even has its own line-by-line +tutorial, which may help get more an understanding of programming with python +and pygame.

+

Note that this introduction will assume you have a bit of experience +programming with python, and are somewhat familiar with the different parts of +creating a simple game. In this tutorial the word "reference" is occasionally +used. This represents a python variable. Variables in python are references, +so you can have several variables all pointing to the same object.

+
+

History Lesson¶

+

The term "sprite" is a holdover from older computer and game machines. These +older boxes were unable to draw and erase normal graphics fast enough for them +to work as games. These machines had special hardware to handle game like +objects that needed to animate very quickly. These objects were called +"sprites" and had special limitations, but could be drawn and updated very +fast. They usually existed in special overlay buffers in the video. These days +computers have become generally fast enough to handle sprite like objects +without dedicated hardware. The term sprite is still used to represent just +about anything in a 2D game that is animated.

+
+
+

The Classes¶

+

The sprite module comes with two main classes. The first is Sprite, which should be used as a base class for all your game +objects. This class doesn't really do anything on its own, it just includes +several functions to help manage the game object. The other type of class is +Group. The Group class is a container for +different Sprite objects. There are actually several different types of +group classes. Some of the Groups can draw all the elements they contain, +for example.

+

This is all there really is to it. We'll start with a description of what each +type of class does, and then discuss the proper ways to use these two classes.

+
+
+

The Sprite Class¶

+

As mentioned before, the Sprite class is designed to be a base class for all +your game objects. You cannot really use it on its own, as it only has several +methods to help it work with the different Group classes. The sprite keeps +track of which groups it belongs to. +The class constructor (__init__ method) takes an argument of a +Group (or list of Groups) the Sprite instance should belong to. +You can also change the Group membership for the Sprite with the +add() and +remove() methods. +There is also a groups() method, +which returns a list of the current groups containing the sprite.

+

When using the your Sprite classes it's best to think of them as "valid" or +"alive" when they are belonging to one or more Groups. When you remove the +instance from all groups pygame will clean up the object. (Unless you have your +own references to the instance somewhere else.) The kill() method removes the sprite from all groups it +belongs to. This will cleanly delete the sprite object. If you've put some +little games together, you'll know sometimes cleanly deleting a game object can +be tricky. The sprite also comes with an alive() method, which returns true if it is still a +member of any groups.

+
+
+

The Group Class¶

+

The Group class is just a simple container. Similar to the sprite, it has +an add() and remove() method which can change which sprites belong to +the group. You also can pass a sprite or list of sprites to the constructor +(__init__() method) to create a Group instance that contains some +initial sprites.

+

The Group has a few other methods like empty() to remove all sprites from the group and +copy() which will return a copy of the group +with all the same members. Also the has() +method will quickly check if the Group contains a sprite or list of +sprites.

+

The other function you will use frequently is the sprites() method. This returns an object that can be +looped on to access every sprite the group contains. Currently this is just a +list of the sprites, but in later version of python this will likely use +iterators for better performance.

+

As a shortcut, the Group also has an update() method, which will call an update() method on +every sprite in the group. Passing the same arguments to each one. Usually in a +game you need some function that updates the state of a game object. It's very +easy to call your own methods using the Group.sprites() method, but this is +a shortcut that's used enough to be included. Also note that the base +Sprite class has a "dummy" update() method that takes any sort of +arguments and does nothing.

+

Lastly, the Group has a couple other methods that allow you to use it with +the builtin len() function, getting the number of sprites it contains, and +the "truth" operator, which allows you to do "if mygroup:" to check if the +group has any sprites.

+
+
+

Mixing Them Together¶

+

At this point the two classes seem pretty basic. Not doing a lot more than you +can do with a simple list and your own class of game objects. But there are +some big advantages to using the Sprite and Group together. A sprite +can belong to as many groups as you want. Remember as soon as it belongs to no +groups, it will usually be cleared up (unless you have other "non-group" +references to that object).

+

The first big thing is a fast simple way to categorize sprites. For example, +say we had a Pacman-like game. We could make separate groups for the different +types of objects in the game. Ghosts, Pac, and Pellets. When Pac eats a power +pellet, we can change the state for all ghost objects by effecting everything +in the Ghost group. This is quicker and simpler than looping through a list +of all the game objects and checking which ones are ghosts.

+

Adding and removing groups and sprites from each other is a very fast +operation, quicker than using lists to store everything. Therefore you can very +efficiently change group memberships. Groups can be used to work like simple +attributes for each game object. Instead of tracking some attribute like +"close_to_player" for a bunch of enemy objects, you could add them to a +separate group. Then when you need to access all the enemies that are near the +player, you already have a list of them, instead of going through a list of all +the enemies, checking for the "close_to_player" flag. Later on your game could +add multiple players, and instead of adding more "close_to_player2", +"close_to_player3" attributes, you can easily add them to different groups or +each player.

+

Another important benefit of using the Sprites and Groups is that the groups +cleanly handle the deleting (or killing) of game objects. In a game where many +objects are referencing other objects, sometimes deleting an object can be the +hardest part, since it can't go away until it is not referenced by anyone. Say +we have an object that is "chasing" another object. The chaser can keep a +simple Group that references the object (or objects) it is chasing. If the +object being chased happens to be destroyed, we don't need to worry about +notifying the chaser to stop chasing. The chaser can see for itself that its +group is now empty, and perhaps find a new target.

+

Again, the thing to remember is that adding and removing sprites from groups is +a very cheap/fast operation. You may be best off by adding many groups to +contain and organize your game objects. Some could even be empty for large +portions of the game, there isn't any penalties for managing your game like +this.

+
+
+

The Many Group Types¶

+

The above examples and reasons to use Sprites and Groups are only a tip +of the iceberg. Another advantage is that the sprite module comes with several +different types of Groups. These groups all work just like a regular old +Group, but they also have added functionality (or slightly different +functionality). Here's a list of the Group classes included with the +sprite module.

+
+

Group

+
+

This is the standard "no frills" group mainly explained above. Most of the +other Groups are derived from this one, but not all.

+
+

GroupSingle

+
+

This works exactly like the regular Group class, but it only contains +the most recently added sprite. Therefore when you add a sprite to this group, +it "forgets" about any previous sprites it had. Therefore it always contains +only one or zero sprites.

+
+

RenderPlain

+
+

This is a standard group derived from Group. It has a draw() method +that draws all the sprites it contains to the screen (or any Surface). For +this to work, it requires all sprites it contains to have a "image" and "rect" +attributes. It uses these to know what to blit, and where to blit it.

+
+

RenderClear

+
+

This is derived from the RenderPlain group, and adds a method named +clear(). This will erase the previous position of all drawn sprites. It +uses a background image to fill in the areas where the sprite were. It is smart +enough to handle deleted sprites and properly clear them from the screen when +the clear() method is called.

+
+

RenderUpdates

+
+

This is the Cadillac of rendering Groups. It is inherited from +RenderClear, but changes the draw() method to also return a list of +pygame Rects, which represent all the areas on screen that have been +changed.

+
+
+

That is the list of different groups available We'll discuss more about these +rendering groups in the next section. There's nothing stopping you from +creating your own Group classes as well. They are just python code, so you can +inherit from one of these and add/change whatever you want. In the future I +hope we can add a couple more Groups to this list. A GroupMulti which +is like the GroupSingle, but can hold up to a given number of sprites (in +some sort of circular buffer?). Also a super-render group that can clear the +position of the old sprites without needing a background image to do it (by +grabbing a copy of the screen before blitting). Who knows really, but in the +future we can add more useful classes to this list.

+
+
+

The Rendering Groups¶

+

From above we can see there are three different rendering groups. We could +probably just get away with the RenderUpdates one, but it adds overhead not +really needed for something like a scrolling game. So we have a couple tools +here, pick the right one for the right job.

+

For a scrolling type game, where the background completely changes every frame, +we obviously don't need to worry about python's update rectangles in the call +to display.update(). You should definitely go with the RenderPlain +group here to manage your rendering.

+

For games where the background is more stationary, you definitely don't want +pygame updating the entire screen (since it doesn't need to). This type of game +usually involves erasing the old position of each object, then drawing it in a +new place for each frame. This way we are only changing what is necessary. +Most of the time you will just want to use the RenderUpdates class here. +Since you will also want to pass this list of changes to the +display.update() function.

+

The RenderUpdates class also does a good job at minimizing overlapping +areas in the list of updated rectangles. If the previous position and current +position of an object overlap, it will merge them into a single rectangle. +Combined with the fact that it properly handles deleted objects, this is +one powerful Group class. If you've written a game that manages the changed +rectangles for the objects in a game, you know this the cause for a lot of +messy code in your game. Especially once you start to throw in objects that can +be deleted at any time. All this work is reduced to a clear() and +draw() method with this monster class. Plus with the overlap checking, it +is likely faster than when you did it manually.

+

Also note that there's nothing stopping you from mixing and matching these +render groups in your game. You should definitely use multiple rendering groups +when you want to do layering with your sprites. Also if the screen is split +into multiple sections, perhaps each section of the screen should use an +appropriate render group?

+
+
+

Collision Detection¶

+

The sprite module also comes with two very generic collision detection +functions. For more complex games, these really won't work for you, but you +can easily grab the source code for them, and modify them as needed.

+

Here's a summary of what they are, and what they do.

+
+

spritecollide(sprite, group, dokill) -> list

+
+

This checks for collisions between a single sprite and the sprites in a group. +It requires a "rect" attribute for all the sprites used. It returns a list of +all the sprites that overlap with the first sprite. The "dokill" argument is a +boolean argument. If it is true, the function will call the kill() method +on all the sprites. This means the last reference to each sprite is probably in +the returned list. Once the list goes away so do the sprites. A quick example +of using this in a loop

+
>>> for bomb in sprite.spritecollide(player, bombs, 1):
+...     boom_sound.play()
+...     Explosion(bomb, 0)
+
+
+

This finds all the sprites in the "bomb" group that collide with the player. +Because of the "dokill" argument it deletes all the crashed bombs. For each +bomb that did collide, it plays a "boom" sound effect, and creates a new +Explosion where the bomb was. (Note, the Explosion class here knows to +add each instance to the appropriate class, so we don't need to store it in a +variable, that last line might feel a little "funny" to you python programmers.

+
+

groupcollide(group1, group2, dokill1, dokill2) -> dictionary

+
+

This is similar to the spritecollide function, but a little more complex. +It checks for collisions for all the sprites in one group, to the sprites in +another. There is a dokill argument for the sprites in each list. When +dokill1 is true, the colliding sprites in group1 will be kill()``ed. +When ``dokill2 is true, we get the same results for group2. The +dictionary it returns works like this; each key in the dictionary is a sprite +from group1 that had a collision. The value for that key is a list of the +sprites that it collided with. Perhaps another quick code sample explains it +best

+
>>> for alien in sprite.groupcollide(aliens, shots, 1, 1).keys()
+...     boom_sound.play()
+...     Explosion(alien, 0)
+...     kills += 1
+
+
+

This code checks for the collisions between player bullets and all the aliens +they might intersect. In this case we only loop over the dictionary keys, but +we could loop over the values() or items() if we wanted to do something +to the specific shots that collided with aliens. If we did loop over the +values() we would be looping through lists that contain sprites. The same +sprite may even appear more than once in these different loops, since the same +"shot" could have collided against multiple "aliens".

+
+
+

Those are the basic collision functions that come with pygame. It should be +easy to roll your own that perhaps use something different than the "rect" +attribute. Or maybe try to fine-tweak your code a little more by directly +effecting the collision object, instead of building a list of the collision? +The code in the sprite collision functions is very optimized, but you could +speed it up slightly by taking out some functionality you don't need.

+
+
+

Common Problems¶

+

Currently there is one main problem that catches new users. When you derive +your new sprite class with the Sprite base, you must call the +Sprite.__init__() method from your own class __init__() method. If you +forget to call the Sprite.__init__() method, you get a cryptic error, like +this

+
AttributeError: 'mysprite' instance has no attribute '_Sprite__g'
+
+
+
+
+

Extending Your Own Classes (Advanced)¶

+

Because of speed concerns, the current Group classes try to only do exactly +what they need, and not handle a lot of general situations. If you decide you +need extra features, you may want to create your own Group class.

+

The Sprite and Group classes were designed to be extended, so feel free +to create your own Group classes to do specialized things. The best place +to start is probably the actual python source code for the sprite module. +Looking at the current Sprite groups should be enough example on how to +create your own.

+

For example, here is the source code for a rendering Group that calls a +render() method for each sprite, instead of just blitting an "image" +variable from it. Since we want it to also handle updated areas, we will start +with a copy of the original RenderUpdates group, here is the code:

+
class RenderUpdatesDraw(RenderClear):
+    """call sprite.draw(screen) to render sprites"""
+    def draw(self, surface):
+        dirty = self.lostsprites
+        self.lostsprites = []
+        for s, r in self.spritedict.items():
+            newrect = s.draw(screen) #Here's the big change
+            if r is 0:
+                dirty.append(newrect)
+            else:
+                dirty.append(newrect.union(r))
+            self.spritedict[s] = newrect
+        return dirty
+
+
+

Following is more information on how you could create your own Sprite and +Group objects from scratch.

+

The Sprite objects only "require" two methods. "add_internal()" and +"remove_internal()". These are called by the Group classes when they are +removing a sprite from themselves. The add_internal() and +remove_internal() have a single argument which is a group. Your Sprite +will need some way to also keep track of the Groups it belongs to. You will +likely want to try to match the other methods and arguments to the real +Sprite class, but if you're not going to use those methods, you sure don't +need them.

+

It is almost the same requirements for creating your own Group. In fact, if +you look at the source you'll see the GroupSingle isn't derived from the +Group class, it just implements the same methods so you can't really tell +the difference. Again you need an "add_internal()" and "remove_internal()" +method that the sprites call when they want to belong or remove themselves from +the group. The add_internal() and remove_internal() have a single +argument which is a sprite. The only other requirement for the Group +classes is they have a dummy attribute named "_spritegroup". It doesn't matter +what the value is, as long as the attribute is present. The Sprite classes can +look for this attribute to determine the difference between a "group" and any +ordinary python container. (This is important, because several sprite methods +can take an argument of a single group, or a sequence of groups. Since they +both look similar, this is the most flexible way to "see" the difference.)

+

You should go through the code for the sprite module. While the code is a bit +"tuned", it's got enough comments to help you follow along. There's even a +TODO section in the source if you feel like contributing.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/SurfarrayIntro.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/SurfarrayIntro.html new file mode 100644 index 0000000..1fc6151 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/SurfarrayIntro.html @@ -0,0 +1,661 @@ + + + + + + + + + Pygame Tutorials - Surfarray Introduction — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

Surfarray Introduction¶

+
+
Author
+

Pete Shinners

+
+
Contact
+

pete@shinners.org

+
+
+
+

Introduction¶

+

This tutorial will attempt to introduce users to both NumPy and the pygame +surfarray module. To beginners, the code that uses surfarray can be quite +intimidating. But actually there are only a few concepts to understand and +you will be up and running. Using the surfarray module, it becomes possible +to perform pixel level operations from straight python code. The performance +can become quite close to the level of doing the code in C.

+

You may just want to jump down to the "Examples" section to get an +idea of what is possible with this module, then start at the beginning here +to work your way up.

+

Now I won't try to fool you into thinking everything is very easy. To get +more advanced effects by modifying pixel values is very tricky. Just mastering +Numeric Python (SciPy's original array package was Numeric, the predecessor of NumPy) +takes a lot of learning. In this tutorial I'll be sticking with +the basics and using a lot of examples in an attempt to plant seeds of wisdom. +After finishing the tutorial you should have a basic handle on how the surfarray +works.

+
+
+

Numeric Python¶

+

If you do not have the python NumPy package installed, +you will need to do that now. +You can download the package from the +NumPy Downloads Page +To make sure NumPy is working for you, +you should get something like this from the interactive python prompt.

+
>>> from numpy import *                    #import numeric
+>>> a = array((1,2,3,4,5))                 #create an array
+>>> a                                      #display the array
+array([1, 2, 3, 4, 5])
+>>> a[2]                                   #index into the array
+3
+>>> a*2                                    #new array with twiced values
+array([ 2,  4,  6,  8, 10])
+
+
+

As you can see, the NumPy module gives us a new data type, the array. +This object holds an array of fixed size, and all values inside are of the same +type. The arrays can also be multidimensional, which is how we will use them +with images. There's a bit more to it than this, but it is enough to get us +started.

+

If you look at the last command above, you'll see that mathematical operations +on NumPy arrays apply to all values in the array. This is called "element-wise +operations". These arrays can also be sliced like normal lists. The slicing +syntax is the same as used on standard python objects. +(so study up if you need to :] ). +Here are some more examples of working with arrays.

+
>>> len(a)                                 #get array size
+5
+>>> a[2:]                                  #elements 2 and up
+array([3, 4, 5])
+>>> a[:-2]                                 #all except last 2
+array([1, 2, 3])
+>>> a[2:] + a[:-2]                         #add first and last
+array([4, 6, 8])
+>>> array((1,2,3)) + array((3,4))          #add arrays of wrong sizes
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+ValueError: operands could not be broadcast together with shapes (3,) (2,)
+
+
+

We get an error on the last commend, because we try add together two arrays +that are different sizes. In order for two arrays two operate with each other, +including comparisons and assignment, they must have the same dimensions. It is +very important to know that the new arrays created from slicing the original all +reference the same values. So changing the values in a slice also changes the +original values. It is important how this is done.

+
>>> a                                      #show our starting array
+array([1, 2, 3, 4, 5])
+>>> aa = a[1:3]                            #slice middle 2 elements
+>>> aa                                     #show the slice
+array([2, 3])
+>>> aa[1] = 13                             #chance value in slice
+>>> a                                      #show change in original
+array([ 1, 2, 13,  4,  5])
+>>> aaa = array(a)                         #make copy of array
+>>> aaa                                    #show copy
+array([ 1, 2, 13,  4,  5])
+>>> aaa[1:4] = 0                           #set middle values to 0
+>>> aaa                                    #show copy
+array([1, 0, 0, 0, 5])
+>>> a                                      #show original again
+array([ 1, 2, 13,  4,  5])
+
+
+

Now we will look at small arrays with two +dimensions. Don't be too worried, getting started it is the same as having a +two dimensional tuple (a tuple inside a tuple). Let's get started with +two dimensional arrays.

+
>>> row1 = (1,2,3)                         #create a tuple of vals
+>>> row2 = (3,4,5)                         #another tuple
+>>> (row1,row2)                            #show as a 2D tuple
+((1, 2, 3), (3, 4, 5))
+>>> b = array((row1, row2))                #create a 2D array
+>>> b                                      #show the array
+array([[1, 2, 3],
+       [3, 4, 5]])
+>>> array(((1,2),(3,4),(5,6)))             #show a new 2D array
+array([[1, 2],
+       [3, 4],
+       [5, 6]])
+
+
+

Now with this two +dimensional array (from now on as "2D") we can index specific values +and do slicing on both dimensions. Simply using a comma to separate the indices +allows us to lookup/slice in multiple dimensions. Just using ":" as an +index (or not supplying enough indices) gives us all the values in +that dimension. Let's see how this works.

+
>>> b                                      #show our array from above
+array([[1, 2, 3],
+       [3, 4, 5]])
+>>> b[0,1]                                 #index a single value
+2
+>>> b[1,:]                                 #slice second row
+array([3, 4, 5])
+>>> b[1]                                   #slice second row (same as above)
+array([3, 4, 5])
+>>> b[:,2]                                 #slice last column
+array([3, 5])
+>>> b[:,:2]                                #slice into a 2x2 array
+array([[1, 2],
+       [3, 4]])
+
+
+

Ok, stay with me here, this is about as hard as it gets. When using NumPy +there is one more feature to slicing. Slicing arrays also allow you to specify +a slice increment. The syntax for a slice with increment is +start_index : end_index : increment.

+
>>> c = arange(10)                         #like range, but makes an array
+>>> c                                      #show the array
+array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+>>> c[1:6:2]                               #slice odd values from 1 to 6
+array([1, 3, 5])
+>>> c[4::4]                                #slice every 4th val starting at 4
+array([4, 8])
+>>> c[8:1:-1]                              #slice 1 to 8, reversed
+array([8, 7, 6, 5, 4, 3, 2])
+
+
+

Well that is it. There's enough information there to get you started using +NumPy with the surfarray module. There's certainly a lot more to NumPy, but +this is only an introduction. Besides, we want to get on to the fun stuff, +correct?

+
+
+

Import Surfarray¶

+

In order to use the surfarray module we need to import it. Since both surfarray +and NumPy are optional components for pygame, it is nice to make sure they +import correctly before using them. In these examples I'm going to import +NumPy into a variable named N. This will let you know which functions +I'm using are from the NumPy package. +(and is a lot shorter than typing NumPy before each function)

+
try:
+    import numpy as N
+    import pygame.surfarray as surfarray
+except ImportError:
+    raise ImportError, "NumPy and Surfarray are required."
+
+
+
+
+

Surfarray Introduction¶

+

There are two main types of functions in surfarray. One set of functions for +creating an array that is a copy of a surface pixel data. The other functions +create a referenced copy of the array pixel data, so that changes to the array +directly affect the original surface. There are other functions that allow you +to access any per-pixel alpha values as arrays along with a few other helpful +functions. We will look at these other functions later on.

+

When working with these surface arrays, there are two ways of representing the +pixel values. First, they can be represented as mapped integers. This type of +array is a simple 2D array with a single integer representing the surface's +mapped color value. This type of array is good for moving parts of an image +around. The other type of array uses three RGB values to represent each pixel +color. This type of array makes it extremely simple to do types of effects that +change the color of each pixel. This type of array is also a little trickier to +deal with, since it is essentially a 3D numeric array. Still, once you get your +mind into the right mode, it is not much harder than using the normal 2D arrays.

+

The NumPy module uses a machine's natural number types to represent the data +values, so a NumPy array can consist of integers that are 8-bits, 16-bits, and 32-bits. +(the arrays can also use other types like floats and doubles, but for our image +manipulation we mainly need to worry about the integer types). +Because of this limitation of integer sizes, you must take a little extra care +that the type of arrays that reference pixel data can be properly mapped to a +proper type of data. The functions create these arrays from surfaces are:

+
+
+surfarray.pixels2d(surface)
+

Creates a 2D array (integer pixel values) that reference the original surface data. +This will work for all surface formats except 24-bit.

+
+ +
+
+surfarray.array2d(surface)
+

Creates a 2D array (integer pixel values) that is copied from any type of surface.

+
+ +
+
+surfarray.pixels3d(surface)
+

Creates a 3D array (RGB pixel values) that reference the original surface data. +This will only work on 24-bit and 32-bit surfaces that have RGB or BGR formatting.

+
+ +
+
+surfarray.array3d(surface)
+

Creates a 3D array (RGB pixel values) that is copied from any type of surface.

+
+ +

Here is a small chart that might better illustrate what types of functions +should be used on which surfaces. As you can see, both the arrayXD functions +will work with any type of surface.

+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

32-bit

24-bit

16-bit

8-bit(c-map)

pixel2d

yes

yes

yes

array2d

yes

yes

yes

yes

pixel3d

yes

yes

array3d

yes

yes

yes

yes

+
+
+

Examples¶

+

With this information, we are equipped to start trying things with surface +arrays. The following are short little demonstrations that create a NumPy +array and display them in pygame. These different tests are found in the +arraydemo.py example. There is a simple function named surfdemo_show +that displays an array on the screen.

+
+
+allblack +
allblack = N.zeros((128, 128))
+surfdemo_show(allblack, 'allblack')
+
+
+

Our first example creates an all black array. Whenever you need +to create a new numeric array of a specific size, it is best to use the +zeros function. Here we create a 2D array of all zeros and display +it.

+
+
+
+
+striped +
striped = N.zeros((128, 128, 3))
+striped[:] = (255, 0, 0)
+striped[:,::3] = (0, 255, 255)
+surfdemo_show(striped, 'striped')
+
+
+

Here we are dealing with a 3D array. We start by creating an all red image. +Then we slice out every third row and assign it to a blue/green color. As you +can see, we can treat the 3D arrays almost exactly the same as 2D arrays, just +be sure to assign them 3 values instead of a single mapped integer.

+
+
+
+
+rgbarray +
imgsurface = pygame.image.load('surfarray.png')
+rgbarray = surfarray.array3d(imgsurface)
+surfdemo_show(rgbarray, 'rgbarray')
+
+
+

Here we load an image with the image module, then convert it to a 3D +array of integer RGB color elements. An RGB copy of a surface always +has the colors arranged as a[r,c,0] for the red component, +a[r,c,1] for the green component, and a[r,c,2] for blue. This can then +be used without caring how the pixels of the actual surface are configured, +unlike a 2D array which is a copy of the mapped +(raw) surface pixels. We will use this image in the rest of the samples.

+
+
+
+
+flipped +
flipped = rgbarray[:,::-1]
+surfdemo_show(flipped, 'flipped')
+
+
+

Here we flip the image vertically. All we need to do is take the original +image array and slice it using a negative increment.

+
+
+
+
+scaledown +
scaledown = rgbarray[::2,::2]
+surfdemo_show(scaledown, 'scaledown')
+
+
+

Based on the last example, scaling an image down is pretty logical. We just +slice out all the pixels using an increment of 2 vertically and horizontally.

+
+
+
+
+scaleup +
shape = rgbarray.shape
+scaleup = N.zeros((shape[0]*2, shape[1]*2, shape[2]))
+scaleup[::2,::2,:] = rgbarray
+scaleup[1::2,::2,:] = rgbarray
+scaleup[:,1::2] = scaleup[:,::2]
+surfdemo_show(scaleup, 'scaleup')
+
+
+

Scaling the image up is a little more work, but is similar to the previous +scaling down, we do it all with slicing. First we create an array that is +double the size of our original. First we copy the original array into every +other pixel of the new array. Then we do it again for every other pixel doing +the odd columns. At this point we have the image scaled properly going across, +but every other row is black, so we simply need to copy each row to the one +underneath it. Then we have an image doubled in size.

+
+
+
+
+redimg +
redimg = N.array(rgbarray)
+redimg[:,:,1:] = 0
+surfdemo_show(redimg, 'redimg')
+
+
+

Now we are using 3D arrays to change the colors. Here we +set all the values in green and blue to zero. +This leaves us with just the red channel.

+
+
+
+
+soften +
factor = N.array((8,), N.int32)
+soften = N.array(rgbarray, N.int32)
+soften[1:,:]  += rgbarray[:-1,:] * factor
+soften[:-1,:] += rgbarray[1:,:] * factor
+soften[:,1:]  += rgbarray[:,:-1] * factor
+soften[:,:-1] += rgbarray[:,1:] * factor
+soften //= 33
+surfdemo_show(soften, 'soften')
+
+
+

Here we perform a 3x3 convolution filter that will soften our image. +It looks like a lot of steps here, but what we are doing is shifting +the image 1 pixel in each direction and adding them all together (with some +multiplication for weighting). Then average all the values. It's no Gaussian, +but it's fast. One point with NumPy arrays, the precision of arithmetic +operations is determined by the array with the largest data type. +So if factor was not declared as a 1 element array of type numpy.int32, +the multiplications would be performed using numpy.int8, the 8 bit integer +type of each rgbarray element. This will cause value truncation. The soften +array must also be declared to have a larger integer size than rgbarray to +avoid truncation.

+
+
+
+
+xfade +
src = N.array(rgbarray)
+dest = N.zeros(rgbarray.shape)
+dest[:] = 20, 50, 100
+diff = (dest - src) * 0.50
+xfade = src + diff.astype(N.uint)
+surfdemo_show(xfade, 'xfade')
+
+
+

Lastly, we are cross fading between the original image and a solid bluish +image. Not exciting, but the dest image could be anything, and changing the 0.50 +multiplier will let you choose any step in a linear crossfade between two images.

+
+
+
+
+

Hopefully by this point you are starting to see how surfarray can be used to +perform special effects and transformations that are only possible at the pixel +level. At the very least, you can use the surfarray to do a lot of Surface.set_at() +Surface.get_at() type operations very quickly. But don't think you are finished +yet, there is still much to learn.

+
+
+

Surface Locking¶

+

Like the rest of pygame, surfarray will lock any Surfaces it needs to +automatically when accessing pixel data. There is one extra thing to be aware +of though. When creating the pixel arrays, the original surface will +be locked during the lifetime of that pixel array. This is important to remember. +Be sure to "del" the pixel array or let it go out of scope +(ie, when the function returns, etc).

+

Also be aware that you really don't want to be doing much (if any) +direct pixel access on hardware surfaces (HWSURFACE). This is because +the actual surface data lives on the graphics card, and transferring pixel +changes over the PCI/AGP bus is not fast.

+
+
+

Transparency¶

+

The surfarray module has several methods for accessing a Surface's alpha/colorkey +values. None of the alpha functions are affected by overall transparency of a +Surface, just the pixel alpha values. Here's the list of those functions.

+
+
+surfarray.pixels_alpha(surface)
+

Creates a 2D array (integer pixel values) that references the original +surface alpha data. +This will only work on 32-bit images with an 8-bit alpha component.

+
+ +
+
+surfarray.array_alpha(surface)
+

Creates a 2D array (integer pixel values) that is copied from any +type of surface. +If the surface has no alpha values, +the array will be fully opaque values (255).

+
+ +
+
+surfarray.array_colorkey(surface)
+

Creates a 2D array (integer pixel values) that is set to transparent +(0) wherever that pixel color matches the Surface colorkey.

+
+ +
+
+

Other Surfarray Functions¶

+

There are only a few other functions available in surfarray. You can get a better +list with more documentation on the +surfarray reference page. +There is one very useful function though.

+
+
+surfarray.blit_array(surface, array)
+

This will transfer any type of 2D or 3D surface array onto a Surface +of the same dimensions. +This surfarray blit will generally be faster than assigning an array to a +referenced pixel array. +Still, it should not be as fast as normal Surface blitting, +since those are very optimized.

+
+ +
+
+

More Advanced NumPy¶

+

There's a couple last things you should know about NumPy arrays. When dealing +with very large arrays, like the kind that are 640x480 big, there are some extra +things you should be careful about. Mainly, while using the operators like + and +* on the arrays makes them easy to use, it is also very expensive on big arrays. +These operators must make new temporary copies of the array, that are then +usually copied into another array. This can get very time consuming. Fortunately, +all the NumPy operators come with special functions that can perform the +operation "in place". For example, you would want to replace +screen[:] = screen + brightmap with the much faster +add(screen, brightmap, screen). +Anyway, you'll want to read up on the NumPy UFunc +documentation for more about this. +It is important when dealing with the arrays.

+

Another thing to be aware of when working with NumPy arrays is the datatype +of the array. Some of the arrays (especially the mapped pixel type) often return +arrays with an unsigned 8-bit value. These arrays will easily overflow if you are +not careful. NumPy will use the same coercion that you find in C programs, so +mixing an operation with 8-bit numbers and 32-bit numbers will give a result as +32-bit numbers. You can convert the datatype of an array, but definitely be +aware of what types of arrays you have, if NumPy gets in a situation where +precision would be ruined, it will raise an exception.

+

Lastly, be aware that when assigning values into the 3D arrays, they must be +between 0 and 255, or you will get some undefined truncating.

+
+
+

Graduation¶

+

Well there you have it. My quick primer on Numeric Python and surfarray. +Hopefully now you see what is possible, and even if you never use them for +yourself, you do not have to be afraid when you see code that does. Look into +the vgrade example for more numeric array action. There are also some "flame" +demos floating around that use surfarray to create a realtime fire effect.

+

Best of all, try some things on your own. Take it slow at first and build up, +I've seen some great things with surfarray already like radial gradients and +more. Good Luck.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/chimp.py.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/chimp.py.html new file mode 100644 index 0000000..70f9e86 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/chimp.py.html @@ -0,0 +1,342 @@ + + + + + + + + + pygame/examples/chimp.py — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
#!/usr/bin/env python
+""" pygame.examples.chimp
+
+This simple example is used for the line-by-line tutorial
+that comes with pygame. It is based on a 'popular' web banner.
+Note there are comments here, but for the full explanation,
+follow along in the tutorial.
+"""
+
+
+# Import Modules
+import os
+import pygame as pg
+
+if not pg.font:
+    print("Warning, fonts disabled")
+if not pg.mixer:
+    print("Warning, sound disabled")
+
+main_dir = os.path.split(os.path.abspath(__file__))[0]
+data_dir = os.path.join(main_dir, "data")
+
+
+# functions to create our resources
+def load_image(name, colorkey=None, scale=1):
+    fullname = os.path.join(data_dir, name)
+    image = pg.image.load(fullname)
+    image = image.convert()
+
+    size = image.get_size()
+    size = (size[0] * scale, size[1] * scale)
+    image = pg.transform.scale(image, size)
+
+    if colorkey is not None:
+        if colorkey == -1:
+            colorkey = image.get_at((0, 0))
+        image.set_colorkey(colorkey, pg.RLEACCEL)
+    return image, image.get_rect()
+
+
+def load_sound(name):
+    class NoneSound:
+        def play(self):
+            pass
+
+    if not pg.mixer or not pg.mixer.get_init():
+        return NoneSound()
+
+    fullname = os.path.join(data_dir, name)
+    sound = pg.mixer.Sound(fullname)
+
+    return sound
+
+
+# classes for our game objects
+class Fist(pg.sprite.Sprite):
+    """moves a clenched fist on the screen, following the mouse"""
+
+    def __init__(self):
+        pg.sprite.Sprite.__init__(self)  # call Sprite initializer
+        self.image, self.rect = load_image("fist.png", -1)
+        self.fist_offset = (-235, -80)
+        self.punching = False
+
+    def update(self):
+        """move the fist based on the mouse position"""
+        pos = pg.mouse.get_pos()
+        self.rect.topleft = pos
+        self.rect.move_ip(self.fist_offset)
+        if self.punching:
+            self.rect.move_ip(15, 25)
+
+    def punch(self, target):
+        """returns true if the fist collides with the target"""
+        if not self.punching:
+            self.punching = True
+            hitbox = self.rect.inflate(-5, -5)
+            return hitbox.colliderect(target.rect)
+
+    def unpunch(self):
+        """called to pull the fist back"""
+        self.punching = False
+
+
+class Chimp(pg.sprite.Sprite):
+    """moves a monkey critter across the screen. it can spin the
+    monkey when it is punched."""
+
+    def __init__(self):
+        pg.sprite.Sprite.__init__(self)  # call Sprite intializer
+        self.image, self.rect = load_image("chimp.png", -1, 4)
+        screen = pg.display.get_surface()
+        self.area = screen.get_rect()
+        self.rect.topleft = 10, 90
+        self.move = 18
+        self.dizzy = False
+
+    def update(self):
+        """walk or spin, depending on the monkeys state"""
+        if self.dizzy:
+            self._spin()
+        else:
+            self._walk()
+
+    def _walk(self):
+        """move the monkey across the screen, and turn at the ends"""
+        newpos = self.rect.move((self.move, 0))
+        if not self.area.contains(newpos):
+            if self.rect.left < self.area.left or self.rect.right > self.area.right:
+                self.move = -self.move
+                newpos = self.rect.move((self.move, 0))
+                self.image = pg.transform.flip(self.image, True, False)
+        self.rect = newpos
+
+    def _spin(self):
+        """spin the monkey image"""
+        center = self.rect.center
+        self.dizzy = self.dizzy + 12
+        if self.dizzy >= 360:
+            self.dizzy = False
+            self.image = self.original
+        else:
+            rotate = pg.transform.rotate
+            self.image = rotate(self.original, self.dizzy)
+        self.rect = self.image.get_rect(center=center)
+
+    def punched(self):
+        """this will cause the monkey to start spinning"""
+        if not self.dizzy:
+            self.dizzy = True
+            self.original = self.image
+
+
+def main():
+    """this function is called when the program starts.
+    it initializes everything it needs, then runs in
+    a loop until the function returns."""
+    # Initialize Everything
+    pg.init()
+    screen = pg.display.set_mode((1280, 480), pg.SCALED)
+    pg.display.set_caption("Monkey Fever")
+    pg.mouse.set_visible(False)
+
+    # Create The Backgound
+    background = pg.Surface(screen.get_size())
+    background = background.convert()
+    background.fill((170, 238, 187))
+
+    # Put Text On The Background, Centered
+    if pg.font:
+        font = pg.font.Font(None, 64)
+        text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10))
+        textpos = text.get_rect(centerx=background.get_width() / 2, y=10)
+        background.blit(text, textpos)
+
+    # Display The Background
+    screen.blit(background, (0, 0))
+    pg.display.flip()
+
+    # Prepare Game Objects
+    whiff_sound = load_sound("whiff.wav")
+    punch_sound = load_sound("punch.wav")
+    chimp = Chimp()
+    fist = Fist()
+    allsprites = pg.sprite.RenderPlain((chimp, fist))
+    clock = pg.time.Clock()
+
+    # Main Loop
+    going = True
+    while going:
+        clock.tick(60)
+
+        # Handle Input Events
+        for event in pg.event.get():
+            if event.type == pg.QUIT:
+                going = False
+            elif event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE:
+                going = False
+            elif event.type == pg.MOUSEBUTTONDOWN:
+                if fist.punch(chimp):
+                    punch_sound.play()  # punch
+                    chimp.punched()
+                else:
+                    whiff_sound.play()  # miss
+            elif event.type == pg.MOUSEBUTTONUP:
+                fist.unpunch()
+
+        allsprites.update()
+
+        # Draw Everything
+        screen.blit(background, (0, 0))
+        allsprites.draw(screen)
+        pg.display.flip()
+
+    pg.quit()
+
+
+# Game Over
+
+
+# this calls the 'main' function when this script is executed
+if __name__ == "__main__":
+    main()
+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/newbieguide.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/newbieguide.html new file mode 100644 index 0000000..b53d935 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/newbieguide.html @@ -0,0 +1,475 @@ + + + + + + + + + A Newbie Guide to pygame — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

A Newbie Guide to pygame¶

+

or Things I learned by trial and error so you don't have to,

+

or How I learned to stop worrying and love the blit.

+

Pygame is a python wrapper for SDL, written by Pete Shinners. What this +means is that, using pygame, you can write games or other multimedia +applications in Python that will run unaltered on any of SDL's supported +platforms (Windows, Unix, Mac, BeOS and others).

+

Pygame may be easy to learn, but the world of graphics programming can be +pretty confusing to the newcomer. I wrote this to try to distill the practical +knowledge I've gained over the past year or so of working with pygame, and its +predecessor, PySDL. I've tried to rank these suggestions in order of +importance, but how relevant any particular hint is will depend on your own +background and the details of your project.

+
+

Get comfortable working in Python.¶

+

The most important thing is to feel confident using python. Learning something +as potentially complicated as graphics programming will be a real chore if +you're also unfamiliar with the language you're using. Write a few sizable +non-graphical programs in python -- parse some text files, write a guessing +game or a journal-entry program or something. Get comfortable with string and +list manipulation -- know how to split, slice and combine strings and lists. +Know how import works -- try writing a program that is spread across +several source files. Write your own functions, and practice manipulating +numbers and characters; know how to convert between the two. Get to the point +where the syntax for using lists and dictionaries is second-nature -- you don't +want to have to run to the documentation every time you need to slice a list or +sort a set of keys. Resist the temptation to run to a mailing list, +comp.lang.python, or IRC when you run into trouble. Instead, fire up the +interpreter and play with the problem for a few hours. Print out the Python +2.0 Quick Reference and keep it by your computer.

+

This may sound incredibly dull, but the confidence you'll gain through your +familiarity with python will work wonders when it comes time to write your +game. The time you spend making python code second-nature will be nothing +compared to the time you'll save when you're writing real code.

+
+
+

Recognize which parts of pygame you really need.¶

+

Looking at the jumble of classes at the top of the pygame Documentation index +may be confusing. The important thing is to realize that you can do a great +deal with only a tiny subset of functions. Many classes you'll probably never +use -- in a year, I haven't touched the Channel, Joystick, cursors, +Userrect, surfarray or version functions.

+
+
+

Know what a surface is.¶

+

The most important part of pygame is the surface. Just think of a surface as a +blank piece of paper. You can do a lot of things with a surface -- you can +draw lines on it, fill parts of it with color, copy images to and from it, and +set or read individual pixel colors on it. A surface can be any size (within +reason) and you can have as many of them as you like (again, within reason). +One surface is special -- the one you create with +pygame.display.set_mode(). This 'display surface' represents the screen; +whatever you do to it will appear on the user's screen. You can only have one +of these -- that's an SDL limitation, not a pygame one.

+

So how do you create surfaces? As mentioned above, you create the special +'display surface' with pygame.display.set_mode(). You can create a surface +that contains an image by using image.load(), or you can make a surface +that contains text with font.render(). You can even create a surface that +contains nothing at all with Surface().

+

Most of the surface functions are not critical. Just learn blit(), +fill(), set_at() and get_at(), and you'll be fine.

+
+
+

Use surface.convert().¶

+

When I first read the documentation for surface.convert(), I didn't think +it was something I had to worry about. 'I only use PNGs, therefore everything I +do will be in the same format. So I don't need convert()';. It turns out I +was very, very wrong.

+

The 'format' that convert() refers to isn't the file format (ie PNG, +JPEG, GIF), it's what's called the 'pixel format'. This refers to the +particular way that a surface records individual colors in a specific pixel. +If the surface format isn't the same as the display format, SDL will have to +convert it on-the-fly for every blit -- a fairly time-consuming process. Don't +worry too much about the explanation; just note that convert() is necessary +if you want to get any kind of speed out of your blits.

+

How do you use convert? Just call it after creating a surface with the +image.load() function. Instead of just doing:

+
surface = pygame.image.load('foo.png')
+
+
+

Do:

+
surface = pygame.image.load('foo.png').convert()
+
+
+

It's that easy. You just need to call it once per surface, when you load an +image off the disk. You'll be pleased with the results; I see about a 6x +increase in blitting speed by calling convert().

+

The only times you don't want to use convert() is when you really need to +have absolute control over an image's internal format -- say you were writing +an image conversion program or something, and you needed to ensure that the +output file had the same pixel format as the input file. If you're writing a +game, you need speed. Use convert().

+
+
+

Dirty rect animation.¶

+

The most common cause of inadequate frame rates in pygame programs results from +misunderstanding the pygame.display.update() function. With pygame, merely +drawing something to the display surface doesn't cause it to appear on the +screen -- you need to call pygame.display.update(). There are three ways +of calling this function:

+
+
    +
  • pygame.display.update() -- This updates the whole window (or the whole screen for fullscreen displays).

  • +
  • pygame.display.flip() -- This does the same thing, and will also do the right thing if you're using double-buffered hardware acceleration, which you're not, so on to...

  • +
  • pygame.display.update(a rectangle or some list of rectangles) -- This updates just the rectangular areas of the screen you specify.

  • +
+
+

Most people new to graphics programming use the first option -- they update the +whole screen every frame. The problem is that this is unacceptably slow for +most people. Calling update() takes 35 milliseconds on my machine, which +doesn't sound like much, until you realize that 1000 / 35 = 28 frames per +second maximum. And that's with no game logic, no blits, no input, no AI, +nothing. I'm just sitting there updating the screen, and 28 fps is my maximum +framerate. Ugh.

+

The solution is called 'dirty rect animation'. Instead of updating the whole +screen every frame, only the parts that changed since the last frame are +updated. I do this by keeping track of those rectangles in a list, then +calling update(the_dirty_rectangles) at the end of the frame. In detail +for a moving sprite, I:

+
+
    +
  • Blit a piece of the background over the sprite's current location, erasing it.

  • +
  • Append the sprite's current location rectangle to a list called dirty_rects.

  • +
  • Move the sprite.

  • +
  • Draw the sprite at its new location.

  • +
  • Append the sprite's new location to my dirty_rects list.

  • +
  • Call display.update(dirty_rects)

  • +
+
+

The difference in speed is astonishing. Consider that SolarWolf has dozens of +constantly moving sprites updating smoothly, and still has enough time left +over to display a parallax starfield in the background, and update that too.

+

There are two cases where this technique just won't work. The first is where +the whole window or screen really is being updated every frame -- think of a +smooth-scrolling engine like an overhead real-time strategy game or a +side-scroller. So what do you do in this case? Well, the short answer is -- +don't write this kind of game in pygame. The long answer is to scroll in steps +of several pixels at a time; don't try to make scrolling perfectly smooth. +Your player will appreciate a game that scrolls quickly, and won't notice the +background jumping along too much.

+

A final note -- not every game requires high framerates. A strategic wargame +could easily get by on just a few updates per second -- in this case, the added +complexity of dirty rect animation may not be necessary.

+
+
+

There is NO rule six.¶

+
+
+

Hardware surfaces are more trouble than they're worth.¶

+

Especially in pygame 2, because HWSURFACE now does nothing

+

If you've been looking at the various flags you can use with +pygame.display.set_mode(), you may have thought like this: Hey, +HWSURFACE! Well, I want that -- who doesn't like hardware acceleration. Ooo... +DOUBLEBUF; well, that sounds fast, I guess I want that too!. It's not +your fault; we've been trained by years of 3-d gaming to believe that hardware +acceleration is good, and software rendering is slow.

+

Unfortunately, hardware rendering comes with a long list of drawbacks:

+
+
    +
  • It only works on some platforms. Windows machines can usually get hardware surfaces if you ask for them. Most other platforms can't. Linux, for example, may be able to provide a hardware surface if X4 is installed, if DGA2 is working properly, and if the moons are aligned correctly. If a hardware surface is unavailable, SDL will silently give you a software surface instead.

  • +
  • It only works fullscreen.

  • +
  • It complicates per-pixel access. If you have a hardware surface, you need to Lock the surface before writing or reading individual pixel values on it. If you don't, Bad Things Happen. Then you need to quickly Unlock the surface again, before the OS gets all confused and starts to panic. Most of this process is automated for you in pygame, but it's something else to take into account.

  • +
  • You lose the mouse pointer. If you specify HWSURFACE (and actually get it), your pointer will usually just vanish (or worse, hang around in a half-there, half-not flickery state). You'll need to create a sprite to act as a manual mouse pointer, and you'll need to worry about pointer acceleration and sensitivity. What a pain.

  • +
  • It might be slower anyway. Many drivers are not accelerated for the types of drawing that we do, and since everything has to be blitted across the video bus (unless you can cram your source surface into video memory as well), it might end up being slower than software access anyway.

  • +
+
+

Hardware rendering has its place. It works pretty reliably under Windows, so +if you're not interested in cross-platform performance, it may provide you with +a substantial speed increase. However, it comes at a cost -- increased +headaches and complexity. It's best to stick with good old reliable +SWSURFACE until you're sure you know what you're doing.

+
+
+

Don't get distracted by side issues.¶

+

Sometimes, new game programmers spend too much time worrying about issues that +aren't really critical to their game's success. The desire to get secondary +issues 'right' is understandable, but early in the process of creating a game, +you cannot even know what the important questions are, let alone what answers +you should choose. The result can be a lot of needless prevarication.

+

For example, consider the question of how to organize your graphics files. +Should each frame have its own graphics file, or each sprite? Perhaps all the +graphics should be zipped up into one archive? A great deal of time has been +wasted on a lot of projects, asking these questions on mailing lists, debating +the answers, profiling, etc, etc. This is a secondary issue; any time spent +discussing it should have been spent coding the actual game.

+

The insight here is that it is far better to have a 'pretty good' solution that +was actually implemented, than a perfect solution that you never got around to +writing.

+
+
+

Rects are your friends.¶

+

Pete Shinners' wrapper may have cool alpha effects and fast blitting speeds, +but I have to admit my favorite part of pygame is the lowly Rect class. A +rect is simply a rectangle -- defined only by the position of its top left +corner, its width, and its height. Many pygame functions take rects as +arguments, and they also take 'rectstyles', a sequence that has the same values +as a rect. So if I need a rectangle that defines the area between 10, 20 and +40, 50, I can do any of the following:

+
rect = pygame.Rect(10, 20, 30, 30)
+rect = pygame.Rect((10, 20, 30, 30))
+rect = pygame.Rect((10, 20), (30, 30))
+rect = (10, 20, 30, 30)
+rect = ((10, 20, 30, 30))
+
+
+

If you use any of the first three versions, however, you get access to Rect's +utility functions. These include functions to move, shrink and inflate rects, +find the union of two rects, and a variety of collision-detection functions.

+

For example, suppose I'd like to get a list of all the sprites that contain a +point (x, y) -- maybe the player clicked there, or maybe that's the current +location of a bullet. It's simple if each sprite has a .rect member -- I just +do:

+
sprites_clicked = [sprite for sprite in all_my_sprites_list if sprite.rect.collidepoint(x, y)]
+
+
+

Rects have no other relation to surfaces or graphics functions, other than the +fact that you can use them as arguments. You can also use them in places that +have nothing to do with graphics, but still need to be defined as rectangles. +Every project I discover a few new places to use rects where I never thought +I'd need them.

+
+
+

Don't bother with pixel-perfect collision detection.¶

+

So you've got your sprites moving around, and you need to know whether or not they're bumping into one another. It's tempting to write something like the following:

+
+
    +
  • Check to see if the rects are in collision. If they aren't, ignore them.

  • +
  • For each pixel in the overlapping area, see if the corresponding pixels from both sprites are opaque. If so, there's a collision.

  • +
+
+

There are other ways to do this, with ANDing sprite masks and so on, but any +way you do it in pygame, it's probably going to be too slow. For most games, +it's probably better just to do 'sub-rect collision' -- create a rect for each +sprite that's a little smaller than the actual image, and use that for +collisions instead. It will be much faster, and in most cases the player won't +notice the imprecision.

+
+
+

Managing the event subsystem.¶

+

Pygame's event system is kind of tricky. There are actually two different ways +to find out what an input device (keyboard, mouse or joystick) is doing.

+

The first is by directly checking the state of the device. You do this by +calling, say, pygame.mouse.get_pos() or pygame.key.get_pressed(). +This will tell you the state of that device at the moment you call the +function.

+

The second method uses the SDL event queue. This queue is a list of events -- +events are added to the list as they're detected, and they're deleted from the +queue as they're read off.

+

There are advantages and disadvantages to each system. State-checking (system +1) gives you precision -- you know exactly when a given input was made -- if +mouse.get_pressed([0]) is 1, that means that the left mouse button is +down right at this moment. The event queue merely reports that the +mouse was down at some time in the past; if you check the queue fairly often, +that can be ok, but if you're delayed from checking it by other code, input +latency can grow. Another advantage of the state-checking system is that it +detects "chording" easily; that is, several states at the same time. If you +want to know whether the t and f keys are down at the same time, just +check:

+
if (key.get_pressed[K_t] and key.get_pressed[K_f]):
+    print "Yup!"
+
+
+

In the queue system, however, each keypress arrives in the queue as a +completely separate event, so you'd need to remember that the t key was +down, and hadn't come up yet, while checking for the f key. A little more +complicated.

+

The state system has one great weakness, however. It only reports what the +state of the device is at the moment it's called; if the user hits a mouse +button then releases it just before a call to mouse.get_pressed(), the +mouse button will return 0 -- get_pressed() missed the mouse button press +completely. The two events, MOUSEBUTTONDOWN and MOUSEBUTTONUP, will +still be sitting in the event queue, however, waiting to be retrieved and +processed.

+

The lesson is: choose the system that meets your requirements. If you don't +have much going on in your loop -- say you're just sitting in a while 1 +loop, waiting for input, use get_pressed() or another state function; the +latency will be lower. On the other hand, if every keypress is crucial, but +latency isn't as important -- say your user is typing something in an editbox, +use the event queue. Some keypresses may be slightly late, but at least you'll +get them all.

+

A note about event.poll() vs. wait() -- poll() may seem better, +since it doesn't block your program from doing anything while it's waiting for +input -- wait() suspends the program until an event is received. +However, poll() will consume 100% of available CPU time while it runs, +and it will fill the event queue with NOEVENTS. Use set_blocked() to +select just those event types you're interested in -- your queue will be much +more manageable.

+
+
+

Colorkey vs. Alpha.¶

+

There's a lot of confusion around these two techniques, and much of it comes from the terminology used.

+

'Colorkey blitting' involves telling pygame that all pixels of a certain color +in a certain image are transparent instead of whatever color they happen to be. +These transparent pixels are not blitted when the rest of the image is blitted, +and so don't obscure the background. This is how we make sprites that aren't +rectangular in shape. Simply call surface.set_colorkey(color), where +color is an RGB tuple -- say (0,0,0). This would make every pixel in the source +image transparent instead of black.

+

'Alpha' is different, and it comes in two flavors. 'Image alpha' applies to the +whole image, and is probably what you want. Properly known as 'translucency', +alpha causes each pixel in the source image to be only partially opaque. +For example, if you set a surface's alpha to 192 and then blitted it onto a +background, 3/4 of each pixel's color would come from the source image, and 1/4 +from the background. Alpha is measured from 255 to 0, where 0 is completely +transparent, and 255 is completely opaque. Note that colorkey and alpha +blitting can be combined -- this produces an image that is fully transparent in +some spots, and semi-transparent in others.

+

'Per-pixel alpha' is the other flavor of alpha, and it's more complicated. +Basically, each pixel in the source image has its own alpha value, from 0 to +255. Each pixel, therefore, can have a different opacity when blitted onto a +background. This type of alpha can't be mixed with colorkey blitting, +and it overrides per-image alpha. Per-pixel alpha is rarely used in +games, and to use it you have to save your source image in a graphic +editor with a special alpha channel. It's complicated -- don't use it +yet.

+
+
+

Do things the pythony way.¶

+

A final note (this isn't the least important one; it just comes at the end). +Pygame is a pretty lightweight wrapper around SDL, which is in turn a pretty +lightweight wrapper around your native OS graphics calls. Chances are pretty +good that if your code is still slow, and you've done the things I've mentioned +above, then the problem lies in the way you're addressing your data in python. +Certain idioms are just going to be slow in python no matter what you do. +Luckily, python is a very clear language -- if a piece of code looks awkward or +unwieldy, chances are its speed can be improved, too. Read over Python +Performance Tips for some great advice on how you can improve the speed of +your code. That said, premature optimisation is the root of all evil; if it's +just not fast enough, don't torture the code trying to make it faster. Some +things are just not meant to be :)

+

There you go. Now you know practically everything I know about using pygame. +Now, go write that game!

+
+

David Clark is an avid pygame user and the editor of the Pygame Code +Repository, a showcase for community-submitted python game code. He is also +the author of Twitch, an entirely average pygame arcade game.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games2.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games2.html new file mode 100644 index 0000000..9d891c0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games2.html @@ -0,0 +1,240 @@ + + + + + + + + + Revision: Pygame fundamentals — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

2. Revision: Pygame fundamentals¶

+
+

2.1. The basic Pygame game¶

+

For the sake of revision, and to ensure that you are familiar with the basic structure of a Pygame program, I'll briefly run through +a basic Pygame program, which will display no more than a window with some text in it, that should, by the end, look something like +this (though of course the window decoration will probably be different on your system):

+../_images/tom_basic.png +

The full code for this example looks like this:

+
#!/usr/bin/python
+
+import pygame
+from pygame.locals import *
+
+def main():
+    # Initialise screen
+    pygame.init()
+    screen = pygame.display.set_mode((150, 50))
+    pygame.display.set_caption('Basic Pygame program')
+
+    # Fill background
+    background = pygame.Surface(screen.get_size())
+    background = background.convert()
+    background.fill((250, 250, 250))
+
+    # Display some text
+    font = pygame.font.Font(None, 36)
+    text = font.render("Hello There", 1, (10, 10, 10))
+    textpos = text.get_rect()
+    textpos.centerx = background.get_rect().centerx
+    background.blit(text, textpos)
+
+    # Blit everything to the screen
+    screen.blit(background, (0, 0))
+    pygame.display.flip()
+
+    # Event loop
+    while 1:
+        for event in pygame.event.get():
+            if event.type == QUIT:
+                return
+
+        screen.blit(background, (0, 0))
+        pygame.display.flip()
+
+
+if __name__ == '__main__': main()
+
+
+
+
+

2.2. Basic Pygame objects¶

+

As you can see, the code consists of three main objects: the screen, the background, and the text. Each of these objects is created +by first calling an instance of an in-built Pygame object, and then modifying it to fit our needs. The screen is a slightly special +case, because we still modify the display through Pygame calls, rather than calling methods belonging to the screen object. But for +all other Pygame objects, we first create the object as a copy of a Pygame object, giving it some attributes, and build our game +objects from them.

+

With the background, we first create a Pygame Surface object, and make it the size of the screen. We then perform the convert() +operation to convert the Surface to a single pixel format. This is more obviously necessary when we have several images and surfaces, +all of different pixel formats, which makes rendering them quite slow. By converting all the surfaces, we can drastically speed up +rendering times. Finally, we fill the background surface with white (255, 255, 255). These values are RGB (Red Green +Blue), and can be worked out from any good paint program.

+

With the text, we require more than one object. First, we create a font object, which defines which font to use, and the size of the +font. Then we create a text object, by using the render method that belongs to our font object, supplying three arguments: +the text to be rendered, whether or not it should be anti-aliased (1=yes, 0=no), and the color of the text (again in RGB format). Next +we create a third text object, which gets the rectangle for the text. The easiest way to understand this is to imagine drawing a +rectangle that will surround all of the text; you can then use this rectangle to get/set the position of the text on the screen. So +in this example we get the rectangle, set its centerx attribute to be the centerx attribute of the +background (so the text's center will be the same as the background's center, i.e. the text will be centered on the screen on the x +axis). We could also set the y coordinate, but it's not any different so I left the text at the top of the screen. As the screen is +small anyway, it didn't seem necessary.

+
+
+

2.3. Blitting¶

+

Now we have created our game objects, we need to actually render them. If we didn't and we ran the program, we'd just see a +blank window, and the objects would remain invisible. The term used for rendering objects is blitting, which is where +you copy the pixels belonging to said object onto the destination object. So to render the background object, you blit it onto the +screen. In this example, to make things simple, we blit the text onto the background (so the background will now have a copy of the +text on it), and then blit the background onto the screen.

+

Blitting is one of the slowest operations in any game, so you need to be careful not to blit too much onto the screen in every frame. +If you have a background image, and a ball flying around the screen, then you could blit the background and then the ball in every +frame, which would cover up the ball's previous position and render the new ball, but this would be pretty slow. A better solution is +to blit the background onto the area that the ball previously occupied, which can be found by the ball's previous rectangle, and then +blitting the ball, so that you are only blitting two small areas.

+
+
+

2.4. The event loop¶

+

Once you've set the game up, you need to put it into a loop so that it will continuously run until the user signals that he/she wants +to exit. So you start an open while loop, and then for each iteration of the loop, which will be each frame of the game, +update the game. The first thing is to check for any Pygame events, which will be the user hitting the keyboard, clicking a mouse +button, moving a joystick, resizing the window, or trying to close it. In this case, we simply want to watch out for for user trying +to quit the game by closing the window, in which case the game should return, which will end the while loop. +Then we simply need to re-blit the background, and flip (update) the display to have everything drawn. OK, as nothing moves or happens +in this example, we don't strictly speaking need to re-blit the background in every iteration, but I put it in because when things are +moving around on the screen, you will need to do all your blitting here.

+
+
+

2.5. Ta-da!¶

+

And that's it - your most basic Pygame game! All games will take a form similar to this, but with lots more code for the actual game +functions themselves, which are more to do your with programming, and less guided in structure by the workings of Pygame. This is what +this tutorial is really about, and will now go onto.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games3.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games3.html new file mode 100644 index 0000000..47b2947 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games3.html @@ -0,0 +1,220 @@ + + + + + + + + + Kicking things off — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

3. Kicking things off¶

+

The first sections of code are relatively simple, and, once written, can usually be reused in every game you consequently make. They +will do all of the boring, generic tasks like loading modules, loading images, opening networking connections, playing music, and so +on. They will also include some simple but effective error handling, and any customisation you wish to provide on top of functions +provided by modules like sys and pygame.

+
+

3.1. The first lines, and loading modules¶

+

First off, you need to start off your game and load up your modules. It's always a good idea to set a few things straight at the top of +the main source file, such as the name of the file, what it contains, the license it is under, and any other helpful info you might +want to give those will will be looking at it. Then you can load modules, with some error checking so that Python doesn't print out +a nasty traceback, which non-programmers won't understand. The code is fairly simple, so I won't bother explaining any of it:

+
#!/usr/bin/env python
+#
+# Tom's Pong
+# A simple pong game with realistic physics and AI
+# http://www.tomchance.uklinux.net/projects/pong.shtml
+#
+# Released under the GNU General Public License
+
+VERSION = "0.4"
+
+try:
+    import sys
+    import random
+    import math
+    import os
+    import getopt
+    import pygame
+    from socket import *
+    from pygame.locals import *
+except ImportError, err:
+    print "couldn't load module. %s" % (err)
+    sys.exit(2)
+
+
+
+
+

3.2. Resource handling functions¶

+

In the Line By Line Chimp example, the first code to be written was for loading images and sounds. As these +were totally independent of any game logic or game objects, they were written as separate functions, and were written first so +that later code could make use of them. I generally put all my code of this nature first, in their own, classless functions; these +will, generally speaking, be resource handling functions. You can of course create classes for these, so that you can group them +together, and maybe have an object with which you can control all of your resources. As with any good programming environment, it's up +to you to develop your own best practice and style.

+

It's always a good idea to write your own resource handling functions, +because although Pygame has methods for opening images and sounds, and other modules will have their methods of opening other +resources, those methods can take up more than one line, they can require consistent modification by yourself, and they often don't +provide satisfactory error handling. Writing resource handling functions gives you sophisticated, reusable code, and gives you more +control over your resources. Take this example of an image loading function:

+
def load_png(name):
+    """ Load image and return image object"""
+    fullname = os.path.join('data', name)
+    try:
+        image = pygame.image.load(fullname)
+        if image.get_alpha() is None:
+            image = image.convert()
+        else:
+            image = image.convert_alpha()
+    except pygame.error, message:
+        print 'Cannot load image:', fullname
+        raise SystemExit, message
+    return image, image.get_rect()
+
+
+

Here we make a more sophisticated image loading function than the one provided by pygame.image.load()load new image from a file (or file-like object). Note that +the first line of the function is a documentation string describing what the function does, and what object(s) it returns. The +function assumes that all of your images are in a directory called data, and so it takes the filename and creates the full pathname, +for example data/ball.png, using the os module to ensure cross-platform compatibility. Then it +tries to load the image, and convert any alpha regions so you can achieve transparency, and it returns a more human-readable error +if there's a problem. Finally it returns the image object, and its rect.

+

You can make similar functions for loading any other resources, such as loading sounds. You can also make resource handling classes, +to give you more flexibility with more complex resources. For example, you could make a music class, with an __init__ +function that loads the sound (perhaps borrowing from a load_sound() function), a function to pause the music, and a +function to restart. Another handy resource handling class is for network connections. Functions to open sockets, pass data with +suitable security and error checking, close sockets, finger addresses, and other network tasks, can make writing a game with network +capabilities relatively painless.

+

Remember the chief task of these functions/classes is to ensure that by the time you get around to writing game object classes, +and the main loop, there's almost nothing left to do. Class inheritance can make these basic classes especially handy. Don't go +overboard though; functions which will only be used by one class should be written as part of that class, not as a global +function.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games4.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games4.html new file mode 100644 index 0000000..246afb9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games4.html @@ -0,0 +1,249 @@ + + + + + + + + + Game object classes — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

4. Game object classes¶

+

Once you've loaded your modules, and written your resource handling functions, you'll want to get on to writing some game objects. +The way this is done is fairly simple, though it can seem complex at first. You write a class for each type of object in the game, +and then create an instance of those classes for the objects. You can then use those classes' methods to manipulate the objects, +giving objects some motion and interactive capabilities. So your game, in pseudo-code, will look like this:

+
#!/usr/bin/python
+
+# [load modules here]
+
+# [resource handling functions here]
+
+class Ball:
+    # [ball functions (methods) here]
+    # [e.g. a function to calculate new position]
+    # [and a function to check if it hits the side]
+
+def main:
+    # [initiate game environment here]
+
+    # [create new object as instance of ball class]
+    ball = Ball()
+
+    while 1:
+        # [check for user input]
+
+        # [call ball's update function]
+        ball.update()
+
+
+

This is, of course, a very simple example, and you'd need to put in all the code, instead of those little bracketed comments. But +you should get the basic idea. You crate a class, into which you put all the functions for a ball, including __init__, +which would create all the ball's attributes, and update, which would move the ball to its new position, before blitting +it onto the screen in this position.

+

You can then create more classes for all of your other game objects, and then create instances of them so that you can handle them +easily in the main function and the main program loop. Contrast this with initiating the ball in the main +function, and then having lots of classless functions to manipulate a set ball object, and you'll hopefully see why using classes is +an advantage: It allows you to put all of the code for each object in one place; it makes using objects easier; it makes adding new +objects, and manipulating them, more flexible. Rather than adding more code for each new ball object, you could simply create new +instances of the Ball class for each new ball object. Magic!

+
+

4.1. A simple ball class¶

+

Here is a simple class with the functions necessary for creating a ball object that will, if the update function is called +in the main loop, move across the screen:

+
class Ball(pygame.sprite.Sprite):
+    """A ball that will move across the screen
+    Returns: ball object
+    Functions: update, calcnewpos
+    Attributes: area, vector"""
+
+    def __init__(self, vector):
+        pygame.sprite.Sprite.__init__(self)
+        self.image, self.rect = load_png('ball.png')
+        screen = pygame.display.get_surface()
+        self.area = screen.get_rect()
+        self.vector = vector
+
+    def update(self):
+        newpos = self.calcnewpos(self.rect,self.vector)
+        self.rect = newpos
+
+    def calcnewpos(self,rect,vector):
+        (angle,z) = vector
+        (dx,dy) = (z*math.cos(angle),z*math.sin(angle))
+        return rect.move(dx,dy)
+
+
+

Here we have the Ball class, with an __init__ function that sets the ball up, an update +function that changes the ball's rectangle to be in the new position, and a calcnewpos function to calculate the ball's +new position based on its current position, and the vector by which it is moving. I'll explain the physics in a moment. The one other +thing to note is the documentation string, which is a little bit longer this time, and explains the basics of the class. These strings +are handy not only to yourself and other programmers looking at the code, but also for tools to parse your code and document it. They +won't make much of a difference in small programs, but with large ones they're invaluable, so it's a good habit to get into.

+
+

4.1.1. Diversion 1: Sprites¶

+

The other reason for creating a class for each object is sprites. Each image you render in your game will be a sprite object, and so +to begin with, the class for each object should inherit the Sprite class. +This is a really nice feature of Python - class +inheritance. Now the Ball class has all of the functions that come with the Sprite class, and any object +instances of the Ball class will be registered by Pygame as sprites. Whereas with text and the background, which don't +move, it's OK to blit the object onto the background, Pygame handles sprite objects in a different manner, which you'll see when we +look at the whole program's code.

+

Basically, you create both a ball object, and a sprite object for that ball, and you then call the ball's update function on the +sprite object, thus updating the sprite. Sprites also give you sophisticated ways of determining if two objects have collided. +Normally you might just check in the main loop to see if their rectangles overlap, but that would involve a lot of code, which would +be a waste because the Sprite class provides two functions (spritecollide and groupcollide) +to do this for you.

+
+
+

4.1.2. Diversion 2: Vector physics¶

+

Other than the structure of the Ball class, the notable thing about this code is the vector physics, used to calculate +the ball's movement. With any game involving angular movement, you won't get very far unless you're comfortable with trigonometry, so +I'll just introduce the basics you need to know to make sense of the calcnewpos function.

+

To begin with, you'll notice that the ball has an attribute vector, which is made up of angle and z. +The angle is measured in radians, and will give you the direction in which the ball is moving. Z is the speed at which the ball +moves. So by using this vector, we can determine the direction and speed of the ball, and therefore how much it will move on the x and +y axes:

+../_images/tom_radians.png +

The diagram above illustrates the basic maths behind vectors. In the left hand diagram, you can see the ball's projected movement +represented by the blue line. The length of that line (z) represents its speed, and the angle is the direction in which +it will move. The angle for the ball's movement will always be taken from the x axis on the right, and it is measured clockwise from +that line, as shown in the diagram.

+

From the angle and speed of the ball, we can then work out how much it has moved along the x and y axes. We need to do this because +Pygame doesn't support vectors itself, and we can only move the ball by moving its rectangle along the two axes. So we need to +resolve the angle and speed into its movement on the x axis (dx) and on the y axis (dy). This is a simple matter of +trigonometry, and can be done with the formulae shown in the diagram.

+

If you've studied elementary trigonometry before, none of this should be news to you. But just in case you're forgetful, here are some +useful formulae to remember, that will help you visualise the angles (I find it easier to visualise angles in degrees than in radians!)

+../_images/tom_formulae.png +
+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games5.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games5.html new file mode 100644 index 0000000..3271cb5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games5.html @@ -0,0 +1,238 @@ + + + + + + + + + User-controllable objects — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

5. User-controllable objects¶

+

So far you can create a Pygame window, and render a ball that will fly across the screen. The next step is to make some bats which +the user can control. This is potentially far more simple than the ball, because it requires no physics (unless your user-controlled +object will move in ways more complex than up and down, e.g. a platform character like Mario, in which case you'll need more physics). +User-controllable objects are pretty easy to create, thanks to Pygame's event queue system, as you'll see.

+
+

5.1. A simple bat class¶

+

The principle behind the bat class is similar to that of the ball class. You need an __init__ function to initialise the +ball (so you can create object instances for each bat), an update function to perform per-frame changes on the bat before +it is blitted the bat to the screen, and the functions that will define what this class will actually do. Here's some sample code:

+
class Bat(pygame.sprite.Sprite):
+    """Movable tennis 'bat' with which one hits the ball
+    Returns: bat object
+    Functions: reinit, update, moveup, movedown
+    Attributes: which, speed"""
+
+    def __init__(self, side):
+        pygame.sprite.Sprite.__init__(self)
+        self.image, self.rect = load_png('bat.png')
+        screen = pygame.display.get_surface()
+        self.area = screen.get_rect()
+        self.side = side
+        self.speed = 10
+        self.state = "still"
+        self.reinit()
+
+    def reinit(self):
+        self.state = "still"
+        self.movepos = [0,0]
+        if self.side == "left":
+            self.rect.midleft = self.area.midleft
+        elif self.side == "right":
+            self.rect.midright = self.area.midright
+
+    def update(self):
+        newpos = self.rect.move(self.movepos)
+        if self.area.contains(newpos):
+            self.rect = newpos
+        pygame.event.pump()
+
+    def moveup(self):
+        self.movepos[1] = self.movepos[1] - (self.speed)
+        self.state = "moveup"
+
+    def movedown(self):
+        self.movepos[1] = self.movepos[1] + (self.speed)
+        self.state = "movedown"
+
+
+

As you can see, this class is very similar to the ball class in its structure. But there are differences in what each function does. +First of all, there is a reinit function, which is used when a round ends, and the bat needs to be set back in its starting place, +with any attributes set back to their necessary values. Next, the way in which the bat is moved is a little more complex than with the +ball, because here its movement is simple (up/down), but it relies on the user telling it to move, unlike the ball which just keeps +moving in every frame. To make sense of how the ball moves, it is helpful to look at a quick diagram to show the sequence of events:

+../_images/tom_event-flowchart.png +

What happens here is that the person controlling the bat pushes down on the key that moves the bat up. For each iteration of the main +game loop (for every frame), if the key is still held down, then the state attribute of that bat object will be set to +"moving", and the moveup function will be called, causing the ball's y position to be reduced by the value of the +speed attribute (in this example, 10). In other words, so long as the key is held down, the bat will move up the screen +by 10 pixels per frame. The state attribute isn't used here yet, but it's useful to know if you're dealing with spin, or +would like some useful debugging output.

+

As soon as the player lets go of that key, the second set of boxes is invoked, and the state attribute of the bat object +will be set back to "still", and the movepos attribute will be set back to [0,0], meaning that when the update function is called, it won't move the bat any more. So when the player lets go of the key, the bat stops moving. Simple!

+
+

5.1.1. Diversion 3: Pygame events¶

+

So how do we know when the player is pushing keys down, and then releasing them? With the Pygame event queue system, dummy! It's a +really easy system to use and understand, so this shouldn't take long :) You've already seen the event queue in action in the basic +Pygame program, where it was used to check if the user was quitting the application. The code for moving the bat is about as simple +as that:

+
for event in pygame.event.get():
+    if event.type == QUIT:
+        return
+    elif event.type == KEYDOWN:
+        if event.key == K_UP:
+            player.moveup()
+        if event.key == K_DOWN:
+            player.movedown()
+    elif event.type == KEYUP:
+        if event.key == K_UP or event.key == K_DOWN:
+            player.movepos = [0,0]
+            player.state = "still"
+
+
+

Here assume that you've already created an instance of a bat, and called the object player. You can see the familiar +layout of the for structure, which iterates through each event found in the Pygame event queue, which is retrieved with +the event.get() function. As the user hits keys, pushes mouse buttons and moves the joystick about, those actions are +pumped into the Pygame event queue, and left there until dealt with. So in each iteration of the main game loop, you go through +these events, checking if they're ones you want to deal with, and then dealing with them appropriately. The event.pump() +function that was in the Bat.update function is then called in every iteration to pump out old events, and keep the queue +current.

+

First we check if the user is quitting the program, and quit it if they are. Then we check if any keys are being pushed down, and if +they are, we check if they're the designated keys for moving the bat up and down. If they are, then we call the appropriate moving +function, and set the player state appropriately (though the states moveup and movedown and changed in the moveup() and +movedown() functions, which makes for neater code, and doesn't break encapsulation, which means that you +assign attributes to the object itself, without referring to the name of the instance of that object). Notice here we have three +states: still, moveup, and movedown. Again, these come in handy if you want to debug or calculate spin. We also check if any keys have +been "let go" (i.e. are no longer being held down), and again if they're the right keys, we stop the bat from moving.

+
+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games6.html b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games6.html new file mode 100644 index 0000000..a94ffd7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/docs/generated/tut/tom_games6.html @@ -0,0 +1,436 @@ + + + + + + + + + Putting it all together — pygame v2.1.2 documentation + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+ +
+
+

6. Putting it all together¶

+

So far you've learnt all the basics necessary to build a simple game. You should understand how to create Pygame objects, how Pygame +displays objects, how it handles events, and how you can use physics to introduce some motion into your game. Now I'll just show how +you can take all those chunks of code and put them together into a working game. What we need first is to let the ball hit the sides +of the screen, and for the bat to be able to hit the ball, otherwise there's not going to be much game play involved. We do this +using Pygame's collision methods.

+
+

6.1. Let the ball hit sides¶

+

The basics principle behind making it bounce of the sides is easy to grasp. You grab the coordinates of the four corners of the ball, +and check to see if they correspond with the x or y coordinate of the edge of the screen. So if the top right and top left corners both +have a y coordinate of zero, you know that the ball is currently on the top edge of the screen. We do all this in the update function, +after we've worked out the new position of the ball.

+
if not self.area.contains(newpos):
+      tl = not self.area.collidepoint(newpos.topleft)
+      tr = not self.area.collidepoint(newpos.topright)
+      bl = not self.area.collidepoint(newpos.bottomleft)
+      br = not self.area.collidepoint(newpos.bottomright)
+      if tr and tl or (br and bl):
+              angle = -angle
+      if tl and bl:
+              self.offcourt(player=2)
+      if tr and br:
+              self.offcourt(player=1)
+
+self.vector = (angle,z)
+
+
+

Here we check to see if the area +contains the new position of the ball (it always should, so we needn't have an else clause, +though in other circumstances you might want to consider it. We then check if the coordinates for the four corners +are colliding with the area's edges, and create objects for each result. If they are, the objects will have a value of 1, +or True. If they don't, then the value will be None, or False. We then see if it has hit the top or bottom, and if it +has we change the ball's direction. Handily, using radians we can do this by simply reversing its positive/negative value. +We also check to see if the ball has gone off the sides, and if it has we call the offcourt function. +This, in my game, resets the ball, adds 1 point to the score of the player specified when calling the function, and displays the new score.

+

Finally, we recompile the vector based on the new angle. And that is it. The ball will now merrily bounce off the walls and go +offcourt with good grace.

+
+
+

6.2. Let the ball hit bats¶

+

Making the ball hit the bats is very similar to making it hit the sides of the screen. We still use the collide method, but this time +we check to see if the rectangles for the ball and either bat collide. In this code I've also put in some extra code to avoid various +glitches. You'll find that you'll have to put all sorts of extra code in to avoid glitches and bugs, so it's good to get used to seeing +it.

+
else:
+    # Deflate the rectangles so you can't catch a ball behind the bat
+    player1.rect.inflate(-3, -3)
+    player2.rect.inflate(-3, -3)
+
+    # Do ball and bat collide?
+    # Note I put in an odd rule that sets self.hit to 1 when they collide, and unsets it in the next
+    # iteration. this is to stop odd ball behaviour where it finds a collision *inside* the
+    # bat, the ball reverses, and is still inside the bat, so bounces around inside.
+    # This way, the ball can always escape and bounce away cleanly
+    if self.rect.colliderect(player1.rect) == 1 and not self.hit:
+        angle = math.pi - angle
+        self.hit = not self.hit
+    elif self.rect.colliderect(player2.rect) == 1 and not self.hit:
+        angle = math.pi - angle
+        self.hit = not self.hit
+    elif self.hit:
+        self.hit = not self.hit
+self.vector = (angle,z)
+
+
+

We start this section with an else statement, because this carries on from the previous chunk of code to check if the ball +hits the sides. It makes sense that if it doesn't hit the sides, it might hit a bat, so we carry on the conditional statement. The +first glitch to fix is to shrink the players' rectangles by 3 pixels in both dimensions, to stop the bat catching a ball that goes +behind them (if you imagine you just move the bat so that as the ball travels behind it, the rectangles overlap, and so normally the +ball would then have been "hit" - this prevents that).

+

Next we check if the rectangles collide, with one more glitch fix. Notice that I've commented on these odd bits of code - it's always +good to explain bits of code that are abnormal, both for others who look at your code, and so you understand it when you come back to +it. The without the fix, the ball might hit a corner of the bat, change direction, and one frame later still find itself inside the +bat. Then it would again think it has been hit, and change its direction. This can happen several times, making the ball's motion +completely unrealistic. So we have a variable, self.hit, which we set to True when it has been hit, and False one frame +later. When we check if the rectangles have collided, we also check if self.hit is True/False, to stop internal bouncing.

+

The important code here is pretty easy to understand. All rectangles have a colliderect +function, into which you feed the rectangle of another object, which returns True if the rectangles do overlap, and False if not. +If they do, we can change the direction by subtracting the current angle from pi (again, a handy trick you can do with radians, +which will adjust the angle by 90 degrees and send it off in the right direction; you might find at this point that a thorough +understanding of radians is in order!). Just to finish the glitch checking, we switch self.hit back to False if it's the frame +after they were hit.

+

We also then recompile the vector. You would of course want to remove the same line in the previous chunk of code, so that you only do +this once after the if-else conditional statement. And that's it! The combined code will now allow the ball to hit sides and bats.

+
+
+

6.3. The Finished product¶

+

The final product, with all the bits of code thrown together, as well as some other bits ofcode to glue it all together, will look +like this:

+
#
+# Tom's Pong
+# A simple pong game with realistic physics and AI
+# http://www.tomchance.uklinux.net/projects/pong.shtml
+#
+# Released under the GNU General Public License
+
+VERSION = "0.4"
+
+try:
+    import sys
+    import random
+    import math
+    import os
+    import getopt
+    import pygame
+    from socket import *
+    from pygame.locals import *
+except ImportError, err:
+    print "couldn't load module. %s" % (err)
+    sys.exit(2)
+
+def load_png(name):
+    """ Load image and return image object"""
+    fullname = os.path.join('data', name)
+    try:
+        image = pygame.image.load(fullname)
+        if image.get_alpha is None:
+            image = image.convert()
+        else:
+            image = image.convert_alpha()
+    except pygame.error, message:
+        print 'Cannot load image:', fullname
+        raise SystemExit, message
+    return image, image.get_rect()
+
+class Ball(pygame.sprite.Sprite):
+    """A ball that will move across the screen
+    Returns: ball object
+    Functions: update, calcnewpos
+    Attributes: area, vector"""
+
+    def __init__(self, (xy), vector):
+        pygame.sprite.Sprite.__init__(self)
+        self.image, self.rect = load_png('ball.png')
+        screen = pygame.display.get_surface()
+        self.area = screen.get_rect()
+        self.vector = vector
+        self.hit = 0
+
+    def update(self):
+        newpos = self.calcnewpos(self.rect,self.vector)
+        self.rect = newpos
+        (angle,z) = self.vector
+
+        if not self.area.contains(newpos):
+            tl = not self.area.collidepoint(newpos.topleft)
+            tr = not self.area.collidepoint(newpos.topright)
+            bl = not self.area.collidepoint(newpos.bottomleft)
+            br = not self.area.collidepoint(newpos.bottomright)
+            if tr and tl or (br and bl):
+                angle = -angle
+            if tl and bl:
+                #self.offcourt()
+                angle = math.pi - angle
+            if tr and br:
+                angle = math.pi - angle
+                #self.offcourt()
+        else:
+            # Deflate the rectangles so you can't catch a ball behind the bat
+            player1.rect.inflate(-3, -3)
+            player2.rect.inflate(-3, -3)
+
+            # Do ball and bat collide?
+            # Note I put in an odd rule that sets self.hit to 1 when they collide, and unsets it in the next
+            # iteration. this is to stop odd ball behaviour where it finds a collision *inside* the
+            # bat, the ball reverses, and is still inside the bat, so bounces around inside.
+            # This way, the ball can always escape and bounce away cleanly
+            if self.rect.colliderect(player1.rect) == 1 and not self.hit:
+                angle = math.pi - angle
+                self.hit = not self.hit
+            elif self.rect.colliderect(player2.rect) == 1 and not self.hit:
+                angle = math.pi - angle
+                self.hit = not self.hit
+            elif self.hit:
+                self.hit = not self.hit
+        self.vector = (angle,z)
+
+    def calcnewpos(self,rect,vector):
+        (angle,z) = vector
+        (dx,dy) = (z*math.cos(angle),z*math.sin(angle))
+        return rect.move(dx,dy)
+
+class Bat(pygame.sprite.Sprite):
+    """Movable tennis 'bat' with which one hits the ball
+    Returns: bat object
+    Functions: reinit, update, moveup, movedown
+    Attributes: which, speed"""
+
+    def __init__(self, side):
+        pygame.sprite.Sprite.__init__(self)
+        self.image, self.rect = load_png('bat.png')
+        screen = pygame.display.get_surface()
+        self.area = screen.get_rect()
+        self.side = side
+        self.speed = 10
+        self.state = "still"
+        self.reinit()
+
+    def reinit(self):
+        self.state = "still"
+        self.movepos = [0,0]
+        if self.side == "left":
+            self.rect.midleft = self.area.midleft
+        elif self.side == "right":
+            self.rect.midright = self.area.midright
+
+    def update(self):
+        newpos = self.rect.move(self.movepos)
+        if self.area.contains(newpos):
+            self.rect = newpos
+        pygame.event.pump()
+
+    def moveup(self):
+        self.movepos[1] = self.movepos[1] - (self.speed)
+        self.state = "moveup"
+
+    def movedown(self):
+        self.movepos[1] = self.movepos[1] + (self.speed)
+        self.state = "movedown"
+
+
+def main():
+    # Initialise screen
+    pygame.init()
+    screen = pygame.display.set_mode((640, 480))
+    pygame.display.set_caption('Basic Pong')
+
+    # Fill background
+    background = pygame.Surface(screen.get_size())
+    background = background.convert()
+    background.fill((0, 0, 0))
+
+    # Initialise players
+    global player1
+    global player2
+    player1 = Bat("left")
+    player2 = Bat("right")
+
+    # Initialise ball
+    speed = 13
+    rand = ((0.1 * (random.randint(5,8))))
+    ball = Ball((0,0),(0.47,speed))
+
+    # Initialise sprites
+    playersprites = pygame.sprite.RenderPlain((player1, player2))
+    ballsprite = pygame.sprite.RenderPlain(ball)
+
+    # Blit everything to the screen
+    screen.blit(background, (0, 0))
+    pygame.display.flip()
+
+    # Initialise clock
+    clock = pygame.time.Clock()
+
+    # Event loop
+    while 1:
+        # Make sure game doesn't run at more than 60 frames per second
+        clock.tick(60)
+
+        for event in pygame.event.get():
+            if event.type == QUIT:
+                return
+            elif event.type == KEYDOWN:
+                if event.key == K_a:
+                    player1.moveup()
+                if event.key == K_z:
+                    player1.movedown()
+                if event.key == K_UP:
+                    player2.moveup()
+                if event.key == K_DOWN:
+                    player2.movedown()
+            elif event.type == KEYUP:
+                if event.key == K_a or event.key == K_z:
+                    player1.movepos = [0,0]
+                    player1.state = "still"
+                if event.key == K_UP or event.key == K_DOWN:
+                    player2.movepos = [0,0]
+                    player2.state = "still"
+
+        screen.blit(background, ball.rect, ball.rect)
+        screen.blit(background, player1.rect, player1.rect)
+        screen.blit(background, player2.rect, player2.rect)
+        ballsprite.update()
+        playersprites.update()
+        ballsprite.draw(screen)
+        playersprites.draw(screen)
+        pygame.display.flip()
+
+
+if __name__ == '__main__': main()
+
+
+

As well as showing you the final product, I'll point you back to TomPong, upon which all of this is based. Download it, have a look +at the source code, and you'll see a full implementation of pong using all of the code you've seen in this tutorial, as well as lots of +other code I've added in various versions, such as some extra physics for spinning, and various other bug and glitch fixes.

+

Oh, find TomPong at http://www.tomchance.uklinux.net/projects/pong.shtml.

+
+
+
+ + +

+
+Edit on GitHub +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/draw.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/draw.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..6502620 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/draw.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/draw.pyi b/.venv/lib/python3.8/site-packages/pygame/draw.pyi new file mode 100644 index 0000000..8a24089 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/draw.pyi @@ -0,0 +1,74 @@ +from typing import Optional, Sequence + +from pygame.rect import Rect +from pygame.surface import Surface + +from ._common import _ColorValue, _Coordinate, _RectValue + +def rect( + surface: Surface, + color: _ColorValue, + rect: _RectValue, + width: int = 0, + border_radius: int = -1, + border_top_left_radius: int = -1, + border_top_right_radius: int = -1, + border_bottom_left_radius: int = -1, + border_bottom_right_radius: int = -1, +) -> Rect: ... +def polygon( + surface: Surface, + color: _ColorValue, + points: Sequence[_Coordinate], + width: int = 0, +) -> Rect: ... +def circle( + surface: Surface, + color: _ColorValue, + center: _Coordinate, + radius: float, + width: int = 0, + draw_top_right: Optional[bool] = None, + draw_top_left: Optional[bool] = None, + draw_bottom_left: Optional[bool] = None, + draw_bottom_right: Optional[bool] = None, +) -> Rect: ... +def ellipse( + surface: Surface, color: _ColorValue, rect: _RectValue, width: int = 0 +) -> Rect: ... +def arc( + surface: Surface, + color: _ColorValue, + rect: _RectValue, + start_angle: float, + stop_angle: float, + width: int = 1, +) -> Rect: ... +def line( + surface: Surface, + color: _ColorValue, + start_pos: _Coordinate, + end_pos: _Coordinate, + width: int = 1, +) -> Rect: ... +def lines( + surface: Surface, + color: _ColorValue, + closed: bool, + points: Sequence[_Coordinate], + width: int = 1, +) -> Rect: ... +def aaline( + surface: Surface, + color: _ColorValue, + start_pos: _Coordinate, + end_pos: _Coordinate, + blend: int = 1, +) -> Rect: ... +def aalines( + surface: Surface, + color: _ColorValue, + closed: bool, + points: Sequence[_Coordinate], + blend: int = 1, +) -> Rect: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/draw_py.py b/.venv/lib/python3.8/site-packages/pygame/draw_py.py new file mode 100644 index 0000000..edd1c0c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/draw_py.py @@ -0,0 +1,564 @@ +# -*- coding: utf-8 -*- +"""Pygame Drawing algorithms written in Python. (Work in Progress) + +Implement Pygame's Drawing Algorithms in a Python version for testing +and debugging. +""" + +from collections import namedtuple +from math import floor, ceil + + +# H E L P E R F U N C T I O N S # + +# fractional part of x + + +def frac(value): + """return fractional part of x""" + return value - floor(value) + + +def inv_frac(value): + """return inverse fractional part of x""" + return 1 - (value - floor(value)) # eg, 1 - frac(x) + + +BoundingBox = namedtuple("BoundingBox", ["left", "top", "right", "bottom"]) +Point = namedtuple("Point", ["x", "y"]) + + +# L O W L E V E L D R A W F U N C T I O N S # +# (They are too low-level to be translated into python, right?) + + +def set_at(surf, in_x, in_y, color): + """Set the color of a pixel in a surface""" + surf.set_at((in_x, in_y), color) + + +def draw_pixel(surf, pos, color, bright, blend=True): + """draw one blended pixel with given brightness.""" + try: + other_col = surf.get_at(pos) if blend else (0, 0, 0, 0) + except IndexError: # pixel outside the surface + return + new_color = tuple( + (bright * col + (1 - bright) * pix) for col, pix in zip(color, other_col) + ) + # FIXME what should happen if only one, color or surf_col, has alpha? + surf.set_at(pos, new_color) + + +def _drawhorzline(surf, color, x_from, in_y, x_to): + if x_from == x_to: + surf.set_at((x_from, in_y), color) + return + + start, end = (x_from, x_to) if x_from <= x_to else (x_to, x_from) + for line_x in range(start, end + 1): + surf.set_at((line_x, in_y), color) + + +def _drawvertline(surf, color, in_x, y_from, y_to): + if y_from == y_to: + surf.set_at((in_x, y_from), color) + return + + start, end = (y_from, y_to) if y_from <= y_to else (y_to, y_from) + for line_y in range(start, end + 1): + surf.set_at((in_x, line_y), color) + + +# I N T E R N A L D R A W L I N E F U N C T I O N S # + + +def _clip_and_draw_horizline(surf, color, x_from, in_y, x_to): + """draw clipped horizontal line.""" + # check Y inside surf + clip = surf.get_clip() + if in_y < clip.y or in_y >= clip.y + clip.h: + return + + x_from = max(x_from, clip.x) + x_to = min(x_to, clip.x + clip.w - 1) + + # check any x inside surf + if x_to < clip.x or x_from >= clip.x + clip.w: + return + + _drawhorzline(surf, color, x_from, in_y, x_to) + + +def _clip_and_draw_vertline(surf, color, in_x, y_from, y_to): + """draw clipped vertical line.""" + # check X inside surf + clip = surf.get_clip() + + if in_x < clip.x or in_x >= clip.x + clip.w: + return + + y_from = max(y_from, clip.y) + y_to = min(y_to, clip.y + clip.h - 1) + + # check any y inside surf + if y_to < clip.y or y_from >= clip.y + clip.h: + return + + _drawvertline(surf, color, in_x, y_from, y_to) + + +# These constants xxx_EDGE are "outside-the-bounding-box"-flags +LEFT_EDGE = 0x1 +RIGHT_EDGE = 0x2 +BOTTOM_EDGE = 0x4 +TOP_EDGE = 0x8 + + +def encode(pos, b_box): + """returns a code that defines position with respect to a bounding box""" + # we use the fact that python interprets booleans (the inequalities) + # as 0/1, and then multiply them with the xxx_EDGE flags + return ( + (pos[0] < b_box.left) * LEFT_EDGE + + (pos[0] > b_box.right) * RIGHT_EDGE + + (pos[1] < b_box.top) * TOP_EDGE + + (pos[1] > b_box.bottom) * BOTTOM_EDGE + ) + + +def clip_line(line, b_box, use_float=False): + """Algorithm to calculate the clipped line. + + We calculate the coordinates of the part of the line segment within the + bounding box (defined by left, top, right, bottom). The we write + the coordinates of the line segment into "line", much like the C-algorithm. + With `use_float` True, clip_line is usable for float-clipping. + + Returns: true if the line segment cuts the bounding box (false otherwise) + """ + + def inside(code): + return not code + + def accept(code_a, code_b): + return not (code_a or code_b) + + def reject(code_a, code_b): + return code_a and code_b + + assert isinstance(line, list) + x_1, y_1, x_2, y_2 = line + dtype = float if use_float else int + + while True: + # the coordinates are progressively modified with the codes, + # until they are either rejected or correspond to the final result. + code1 = encode((x_1, y_1), b_box) + code2 = encode((x_2, y_2), b_box) + + if accept(code1, code2): + # write coordinates into "line" ! + line[:] = x_1, y_1, x_2, y_2 + return True + if reject(code1, code2): + return False + + # We operate on the (x_1, y_1) point, + # and swap if it is inside the bbox: + if inside(code1): + x_1, x_2 = x_2, x_1 + y_1, y_2 = y_2, y_1 + code1, code2 = code2, code1 + slope = (y_2 - y_1) / float(x_2 - x_1) if (x_2 != x_1) else 1.0 + # Each case, if true, means that we are outside the border: + # calculate x_1 and y_1 to be the "first point" inside the bbox... + if code1 & LEFT_EDGE: + y_1 += dtype((b_box.left - x_1) * slope) + x_1 = b_box.left + elif code1 & RIGHT_EDGE: + y_1 += dtype((b_box.right - x_1) * slope) + x_1 = b_box.right + elif code1 & BOTTOM_EDGE: + if x_2 != x_1: + x_1 += dtype((b_box.bottom - y_1) / slope) + y_1 = b_box.bottom + elif code1 & TOP_EDGE: + if x_2 != x_1: + x_1 += dtype((b_box.top - y_1) / slope) + y_1 = b_box.top + + +def _draw_line(surf, color, start, end): + """draw a non-horizontal line (without anti-aliasing).""" + # Variant of https://en.wikipedia.org/wiki/Bresenham's_line_algorithm + # + # This strongly differs from craw.c implementation, because we use a + # "slope" variable (instead of delta_x and delta_y) and a "error" variable. + # And we can not do pointer-arithmetic with "BytesPerPixel", like in + # the C-algorithm. + if start.x == end.x: + # This case should not happen... + raise ValueError + + slope = abs((end.y - start.y) / (end.x - start.x)) + error = 0.0 + + if slope < 1: + # Here, it's a rather horizontal line + + # 1. check in which octants we are & set init values + if end.x < start.x: + start.x, end.x = end.x, start.x + start.y, end.y = end.y, start.y + line_y = start.y + dy_sign = 1 if (start.y < end.y) else -1 + + # 2. step along x coordinate + for line_x in range(start.x, end.x + 1): + set_at(surf, line_x, line_y, color) + error += slope + if error >= 0.5: + line_y += dy_sign + error -= 1 + else: + # Case of a rather vertical line + + # 1. check in which octants we are & set init values + if start.y > end.y: + start.x, end.x = end.x, start.x + start.y, end.y = end.y, start.y + line_x = start.x + slope = 1 / slope + dx_sign = 1 if (start.x < end.x) else -1 + + # 2. step along y coordinate + for line_y in range(start.y, end.y + 1): + set_at(surf, line_x, line_y, color) + error += slope + if error >= 0.5: + line_x += dx_sign + error -= 1 + + +def _draw_aaline(surf, color, start, end, blend): + """draw an anti-aliased line. + + The algorithm yields identical results with _draw_line for horizontal, + vertical or diagonal lines, and results changes smoothly when changing + any of the endpoint coordinates. + + Note that this yields strange results for very short lines, eg + a line from (0, 0) to (0, 1) will draw 2 pixels, and a line from + (0, 0) to (0, 1.1) will blend 10 % on the pixel (0, 2). + """ + # The different requirements that we have on an antialiasing algorithm + # implies to make some compromises: + # 1. We want smooth evolution wrt to the 4 endpoint coordinates + # (this means also that we want a smooth evolution when the angle + # passes +/- 45° + # 2. We want the same behavior when swapping the endpoints + # 3. We want understandable results for the endpoint values + # (eg we want to avoid half-integer values to draw a simple plain + # horizontal or vertical line between two integer l endpoints) + # + # This implies to somehow make the line artificially 1 pixel longer + # and to draw a full pixel when we have the endpoints are identical. + d_x = end.x - start.x + d_y = end.y - start.y + + if d_x == 0 and d_y == 0: + # For smoothness reasons, we could also do some blending here, + # but it seems overshoot... + set_at(surf, int(start.x), int(start.y), color) + return + + if start.x > end.x or start.y > end.y: + start.x, end.x = end.x, start.x + start.y, end.y = end.y, start.y + d_x = -d_x + d_y = -d_y + + if abs(d_x) >= abs(d_y): + slope = d_y / d_x + + def draw_two_pixel(in_x, float_y, factor): + flr_y = floor(float_y) + draw_pixel(surf, (in_x, flr_y), color, factor * inv_frac(float_y), blend) + draw_pixel(surf, (in_x, flr_y + 1), color, factor * frac(float_y), blend) + + _draw_aaline_dx(d_x, slope, end, start, draw_two_pixel) + else: + slope = d_x / d_y + + def draw_two_pixel(float_x, in_y, factor): + fl_x = floor(float_x) + draw_pixel(surf, (fl_x, in_y), color, factor * inv_frac(float_x), blend) + draw_pixel(surf, (fl_x + 1, in_y), color, factor * frac(float_x), blend) + + _draw_aaline_dy(d_y, slope, end, start, draw_two_pixel) + + +def _draw_aaline_dy(d_y, slope, end, start, draw_two_pixel): + g_y = ceil(start.y) + g_x = start.x + (g_y - start.y) * slope + # 1. Draw start of the segment + if start.y < g_y: + draw_two_pixel(g_x - slope, floor(start.y), inv_frac(start.y)) + # 2. Draw end of the segment + rest = frac(end.y) + s_y = ceil(end.y) + if rest > 0: + s_x = start.x + slope * (d_y + 1 - rest) + draw_two_pixel(s_x, s_y, rest) + else: + s_y += 1 + # 3. loop for other points + for line_y in range(g_y, s_y): + line_x = g_x + slope * (line_y - g_y) + draw_two_pixel(line_x, line_y, 1) + + +def _draw_aaline_dx(d_x, slope, end, start, draw_two_pixel): + # A and G are respectively left and right to the "from" point, but + # with integer-x-coordinate, (and only if from_x is not integer). + # Hence they appear in following order on the line in general case: + # A from-pt G . . . to-pt S + # |------*-------|--- . . . ---|-----*------|- + g_x = ceil(start.x) + g_y = start.y + (g_x - start.x) * slope + # 1. Draw start of the segment if we have a non-integer-part + if start.x < g_x: + # this corresponds to the point "A" + draw_two_pixel(floor(start.x), g_y - slope, inv_frac(start.x)) + # 2. Draw end of the segment: we add one pixel for homogeneity reasons + rest = frac(end.x) + s_x = ceil(end.x) + if rest > 0: + # Again we draw only if we have a non-integer-part + s_y = start.y + slope * (d_x + 1 - rest) + draw_two_pixel(s_x, s_y, rest) + else: + s_x += 1 + # 3. loop for other points + for line_x in range(g_x, s_x): + line_y = g_y + slope * (line_x - g_x) + draw_two_pixel(line_x, line_y, 1) + + +# C L I P A N D D R A W L I N E F U N C T I O N S # + + +def _clip_and_draw_line(surf, rect, color, pts): + """clip the line into the rectangle and draw if needed. + + Returns true if anything has been drawn, else false.""" + # "pts" is a list with the four coordinates of the two endpoints + # of the line to be drawn : pts = x1, y1, x2, y2. + # The data format is like that to stay closer to the C-algorithm. + if not clip_line( + pts, BoundingBox(rect.x, rect.y, rect.x + rect.w - 1, rect.y + rect.h - 1) + ): + # The line segment defined by "pts" is not crossing the rectangle + return 0 + if pts[1] == pts[3]: # eg y1 == y2 + _drawhorzline(surf, color, pts[0], pts[1], pts[2]) + elif pts[0] == pts[2]: # eg x1 == x2 + _drawvertline(surf, color, pts[0], pts[1], pts[3]) + else: + _draw_line(surf, color, Point(pts[0], pts[1]), Point(pts[2], pts[3])) + return 1 + + +def _clip_and_draw_line_width(surf, rect, color, line, width): + yinc = xinc = 0 + if abs(line[0] - line[2]) > abs(line[1] - line[3]): + yinc = 1 + else: + xinc = 1 + newpts = line[:] + if _clip_and_draw_line(surf, rect, color, newpts): + anydrawn = 1 + frame = newpts[:] + else: + anydrawn = 0 + frame = [10000, 10000, -10000, -10000] + + for loop in range(1, width // 2 + 1): + newpts[0] = line[0] + xinc * loop + newpts[1] = line[1] + yinc * loop + newpts[2] = line[2] + xinc * loop + newpts[3] = line[3] + yinc * loop + if _clip_and_draw_line(surf, rect, color, newpts): + anydrawn = 1 + frame[0] = min(newpts[0], frame[0]) + frame[1] = min(newpts[1], frame[1]) + frame[2] = max(newpts[2], frame[2]) + frame[3] = max(newpts[3], frame[3]) + + if loop * 2 < width: + newpts[0] = line[0] - xinc * loop + newpts[1] = line[1] - yinc * loop + newpts[2] = line[2] - xinc * loop + newpts[3] = line[3] - yinc * loop + if _clip_and_draw_line(surf, rect, color, newpts): + anydrawn = 1 + frame[0] = min(newpts[0], frame[0]) + frame[1] = min(newpts[1], frame[1]) + frame[2] = max(newpts[2], frame[2]) + frame[3] = max(newpts[3], frame[3]) + + return anydrawn + + +def _clip_and_draw_aaline(surf, rect, color, line, blend): + """draw anti-aliased line between two endpoints.""" + if not clip_line( + line, + BoundingBox(rect.x - 1, rect.y - 1, rect.x + rect.w, rect.y + rect.h), + use_float=True, + ): + return # TODO Rect(rect.x, rect.y, 0, 0) + _draw_aaline(surf, color, Point(line[0], line[1]), Point(line[2], line[3]), blend) + return # TODO Rect(-- affected area --) + + +# D R A W L I N E F U N C T I O N S # + + +def draw_aaline(surf, color, from_point, to_point, blend=True): + """draw anti-aliased line between two endpoints.""" + line = [from_point[0], from_point[1], to_point[0], to_point[1]] + return _clip_and_draw_aaline(surf, surf.get_clip(), color, line, blend) + + +def draw_line(surf, color, from_point, to_point, width=1): + """draw anti-aliased line between two endpoints.""" + line = [from_point[0], from_point[1], to_point[0], to_point[1]] + return _clip_and_draw_line_width(surf, surf.get_clip(), color, line, width) + + +# M U L T I L I N E F U N C T I O N S # + + +def _multi_lines( + surf, + color, + closed, # pylint: disable=too-many-arguments + points, + width=1, + blend=False, + aaline=False, +): + """draw several lines, either anti-aliased or not.""" + # The code for anti-aliased or not is almost identical, so it's factorized + if len(points) <= 2: + raise TypeError + line = [0] * 4 # store x1, y1 & x2, y2 of the lines to be drawn + + xlist = [pt[0] for pt in points] + ylist = [pt[1] for pt in points] + line[0] = xlist[0] + line[1] = ylist[0] + b_box = BoundingBox(left=xlist[0], right=xlist[0], top=ylist[0], bottom=ylist[0]) + + for line_x, line_y in points[1:]: + b_box.left = min(b_box.left, line_x) + b_box.right = max(b_box.right, line_x) + b_box.top = min(b_box.top, line_y) + b_box.bottom = max(b_box.bottom, line_y) + + rect = surf.get_clip() + for loop in range(1, len(points)): + + line[0] = xlist[loop - 1] + line[1] = ylist[loop - 1] + line[2] = xlist[loop] + line[3] = ylist[loop] + if aaline: + _clip_and_draw_aaline(surf, rect, color, line, blend) + else: + _clip_and_draw_line_width(surf, rect, color, line, width) + + if closed: + line[0] = xlist[len(points) - 1] + line[1] = ylist[len(points) - 1] + line[2] = xlist[0] + line[3] = ylist[0] + if aaline: + _clip_and_draw_aaline(surf, rect, color, line, blend) + else: + _clip_and_draw_line_width(surf, rect, color, line, width) + + # TODO Rect(...) + + +def draw_lines(surf, color, closed, points, width=1): + """draw several lines connected through the points.""" + return _multi_lines(surf, color, closed, points, width, aaline=False) + + +def draw_aalines(surf, color, closed, points, blend=True): + """draw several anti-aliased lines connected through the points.""" + return _multi_lines(surf, color, closed, points, blend=blend, aaline=True) + + +def draw_polygon(surface, color, points, width): + """Draw a polygon""" + if width: + draw_lines(surface, color, 1, points, width) + return # TODO Rect(...) + num_points = len(points) + point_x = [x for x, y in points] + point_y = [y for x, y in points] + + miny = min(point_y) + maxy = max(point_y) + + if miny == maxy: + minx = min(point_x) + maxx = max(point_x) + _clip_and_draw_horizline(surface, color, minx, miny, maxx) + return # TODO Rect(...) + + for y_coord in range(miny, maxy + 1): + x_intersect = [] + for i in range(num_points): + _draw_polygon_inner_loop(i, point_x, point_y, y_coord, x_intersect) + + x_intersect.sort() + for i in range(0, len(x_intersect), 2): + _clip_and_draw_horizline( + surface, color, x_intersect[i], y_coord, x_intersect[i + 1] + ) + + # special case : horizontal border lines + for i in range(num_points): + i_prev = i - 1 if i else num_points - 1 + if miny < point_y[i] == point_y[i_prev] < maxy: + _clip_and_draw_horizline( + surface, color, point_x[i], point_y[i], point_x[i_prev] + ) + + return # TODO Rect(...) + + +def _draw_polygon_inner_loop(index, point_x, point_y, y_coord, x_intersect): + i_prev = index - 1 if index else len(point_x) - 1 + + y_1 = point_y[i_prev] + y_2 = point_y[index] + + if y_1 < y_2: + x_1 = point_x[i_prev] + x_2 = point_x[index] + elif y_1 > y_2: + y_2 = point_y[i_prev] + y_1 = point_y[index] + x_2 = point_x[i_prev] + x_1 = point_x[index] + else: # special case handled below + return + + if (y_2 > y_coord >= y_1) or ((y_coord == max(point_y)) and (y_coord <= y_2)): + x_intersect.append((y_coord - y_1) * (x_2 - x_1) // (y_2 - y_1) + x_1) diff --git a/.venv/lib/python3.8/site-packages/pygame/event.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/event.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..5467d8b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/event.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/event.pyi b/.venv/lib/python3.8/site-packages/pygame/event.pyi new file mode 100644 index 0000000..85d7a5d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/event.pyi @@ -0,0 +1,32 @@ +from typing import Any, Dict, List, Optional, SupportsInt, Tuple, Union, overload + +class Event: + type: int + __dict__: Dict[str, Any] + __hash__: None # type: ignore + @overload + def __init__(self, type: int, dict: Dict[str, Any]) -> None: ... + @overload + def __init__(self, type: int, **attributes: Any) -> None: ... + def __getattr__(self, name: str) -> Any: ... + +_EventTypes = Union[SupportsInt, Tuple[SupportsInt, ...], List[SupportsInt]] + +def pump() -> None: ... +def get( + eventtype: Optional[_EventTypes] = None, + pump: Any = True, + exclude: Optional[_EventTypes] = None, +) -> List[Event]: ... +def poll() -> Event: ... +def wait(timeout: int = 0) -> Event: ... +def peek(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> bool: ... +def clear(eventtype: Optional[_EventTypes] = None, pump: Any = True) -> None: ... +def event_name(type: int) -> str: ... +def set_blocked(type: Optional[_EventTypes]) -> None: ... +def set_allowed(type: Optional[_EventTypes]) -> None: ... +def get_blocked(type: _EventTypes) -> bool: ... +def set_grab(grab: bool) -> None: ... +def get_grab() -> bool: ... +def post(event: Event) -> bool: ... +def custom_type() -> int: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/README.rst b/.venv/lib/python3.8/site-packages/pygame/examples/README.rst new file mode 100644 index 0000000..a319922 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/README.rst @@ -0,0 +1,142 @@ +These examples should help get you started with pygame. Here is a +brief rundown of what you get. The source code for all examples +is in the public domain. Feel free to use for your own projects. + +aliens.py + This started off as a port of the SDL demonstration, Aliens. + Now it has evolved into something sort of resembling fun. + This demonstrates a lot of different uses of sprites and + optimized blitting. Also transparancy, colorkeys, fonts, sound, + music, joystick, and more. (PS, my high score is 117! goodluck) + +arraydemo.py + Another example filled with various surfarray effects. + It requires the surfarray and image modules to be installed. + This little demo can also make a good starting point for any of + your own tests with surfarray + +audiocapture.py + Record sound from a microphone, and play back the recorded sound. + +blend_fill.py + BLEND_ing colors in different ways with Surface.fill(). + +blit_blends.py + BLEND_ing colors Surface.blit(). + +camera.py + Basic image capturing and display using pygame.camera + +cursors.py + Make custom cursors :) + +dropevent.py + Drag and drop files. Using the following events. + DROPBEGIN, DROPCOMPLETE, DROPTEXT, DROPFILE + +eventlist.py + Learn about pygame events and input. + Watch the events fly by. Click the mouse, and see the mouse + event come up. Press a keyboard key, and see the key up event. + +font_viewer.py + Display all available fonts in a scrolling window. + +fonty.py + Super quick, super simple application demonstrating + the different ways to render fonts with the font module + +freetype_misc.py + FreeType is a world famous font project. + +glcube.py + Using PyOpenGL and Pygame, this creates a spinning 3D multicolored cube. + +headless_no_windows_needed.py + For using pygame in scripts. + +liquid.py + This example was created in a quick comparison with the + BlitzBasic gaming language. Nonetheless, it demonstrates a quick + 8-bit setup (with colormap). + +mask.py + Single bit pixel manipulation. Fast for collision detection, + and also good for computer vision. + +midi.py + For connecting pygame to musical equipment. + +moveit.py + A very simple example of moving stuff. + +music_drop_fade.py + Fade in and play music from a list while observing + several events. Uses fade_ms added in pygame2, as well as set_endevent, + set_volume, drag and drop events, and the scrap module. + +overlay.py + An old way of displaying video content. + +pixelarray.py + Process whole arrays of pixels at a time. + Like numpy, but for pixels, and also built into pygame. + +playmus.py + Simple music playing example. + +prevent_display_stretching.py + A windows specific example. + +scaletest.py + Showing how to scale Surfaces. + +scrap_clipboard.py + A simple demonstration example for the clipboard support. + +setmodescale.py + SCALED allows you to work in 320x200 and have it show up big. + It handles mouse scaling and selection of a good sized window depending + on the display. + +sound.py + Extremely basic testing of the mixer module. Load a + sound and play it. All from the command shell, no graphics. + +sound_array_demos.py + Echo, delay and other array based processing of sounds. + +sprite_texture.py + Shows how to use hardware Image Textures with pygame.sprite. + +stars.py + A simple starfield example. You can change the center of + perspective by leftclicking the mouse on the screen. + +testsprite.py + More of a test example. If you're interested in how to use sprites, + then check out the aliens.py example instead. + +textinput.py + A little "console" where you can write in text. + Shows how to use the TEXTEDITING and TEXTINPUT events. + +vgrade.py + Demonstrates creating a vertical gradient with + Numpy. The app will create a new gradient every half + second and report the time needed to create and display the + image. If you're not prepared to start working with the + Numpy arrays, don't worry about the source for this one :] + +video.py + It explores some new video APIs in pygame 2. + Including multiple windows, Textures, and such. + +data/ + Directory with the resources for the examples. + +There's LOTS of examples on the pygame website, and on places like github. + +We're always on the lookout for more examples and/or example +requests. Code like this is probably the best way to start +getting involved with Python gaming. diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__init__.py b/.venv/lib/python3.8/site-packages/pygame/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..5273750 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/aacircle.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/aacircle.cpython-38.pyc new file mode 100644 index 0000000..8a558e3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/aacircle.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/aliens.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/aliens.cpython-38.pyc new file mode 100644 index 0000000..46b7de5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/aliens.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/arraydemo.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/arraydemo.cpython-38.pyc new file mode 100644 index 0000000..25b20e6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/arraydemo.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/audiocapture.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/audiocapture.cpython-38.pyc new file mode 100644 index 0000000..56617fd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/audiocapture.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/blend_fill.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/blend_fill.cpython-38.pyc new file mode 100644 index 0000000..bab4bd1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/blend_fill.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/blit_blends.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/blit_blends.cpython-38.pyc new file mode 100644 index 0000000..e85818f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/blit_blends.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/camera.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/camera.cpython-38.pyc new file mode 100644 index 0000000..79884c0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/camera.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/chimp.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/chimp.cpython-38.pyc new file mode 100644 index 0000000..7ccd97d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/chimp.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/cursors.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/cursors.cpython-38.pyc new file mode 100644 index 0000000..197fb41 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/cursors.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/dropevent.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/dropevent.cpython-38.pyc new file mode 100644 index 0000000..caef596 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/dropevent.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/eventlist.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/eventlist.cpython-38.pyc new file mode 100644 index 0000000..96f19f2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/eventlist.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/font_viewer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/font_viewer.cpython-38.pyc new file mode 100644 index 0000000..441a733 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/font_viewer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/fonty.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/fonty.cpython-38.pyc new file mode 100644 index 0000000..d32858b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/fonty.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/freetype_misc.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/freetype_misc.cpython-38.pyc new file mode 100644 index 0000000..fb44d6d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/freetype_misc.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/glcube.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/glcube.cpython-38.pyc new file mode 100644 index 0000000..48a787d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/glcube.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/headless_no_windows_needed.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/headless_no_windows_needed.cpython-38.pyc new file mode 100644 index 0000000..9cd2f1d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/headless_no_windows_needed.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/joystick.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/joystick.cpython-38.pyc new file mode 100644 index 0000000..0f98f23 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/joystick.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/liquid.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/liquid.cpython-38.pyc new file mode 100644 index 0000000..d466d03 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/liquid.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/mask.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/mask.cpython-38.pyc new file mode 100644 index 0000000..9395b5d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/mask.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/midi.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/midi.cpython-38.pyc new file mode 100644 index 0000000..21a42c0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/midi.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/moveit.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/moveit.cpython-38.pyc new file mode 100644 index 0000000..3beccbb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/moveit.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/music_drop_fade.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/music_drop_fade.cpython-38.pyc new file mode 100644 index 0000000..a5f09a1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/music_drop_fade.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/pixelarray.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/pixelarray.cpython-38.pyc new file mode 100644 index 0000000..6c2e09a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/pixelarray.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/playmus.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/playmus.cpython-38.pyc new file mode 100644 index 0000000..9694137 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/playmus.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/prevent_display_stretching.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/prevent_display_stretching.cpython-38.pyc new file mode 100644 index 0000000..78dc96c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/prevent_display_stretching.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/resizing_new.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/resizing_new.cpython-38.pyc new file mode 100644 index 0000000..51c0dfb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/resizing_new.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scaletest.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scaletest.cpython-38.pyc new file mode 100644 index 0000000..ae2daf8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scaletest.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scrap_clipboard.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scrap_clipboard.cpython-38.pyc new file mode 100644 index 0000000..45d6d56 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scrap_clipboard.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scroll.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scroll.cpython-38.pyc new file mode 100644 index 0000000..e669bbb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/scroll.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/setmodescale.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/setmodescale.cpython-38.pyc new file mode 100644 index 0000000..0f3fa60 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/setmodescale.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sound.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sound.cpython-38.pyc new file mode 100644 index 0000000..c6a24bc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sound.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sound_array_demos.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sound_array_demos.cpython-38.pyc new file mode 100644 index 0000000..f12a337 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sound_array_demos.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sprite_texture.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sprite_texture.cpython-38.pyc new file mode 100644 index 0000000..c6bfb0c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/sprite_texture.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/stars.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/stars.cpython-38.pyc new file mode 100644 index 0000000..ea5bea2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/stars.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/testsprite.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/testsprite.cpython-38.pyc new file mode 100644 index 0000000..b3afcd7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/testsprite.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/textinput.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/textinput.cpython-38.pyc new file mode 100644 index 0000000..078f33b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/textinput.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/vgrade.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/vgrade.cpython-38.pyc new file mode 100644 index 0000000..720c3fb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/vgrade.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/video.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/video.cpython-38.pyc new file mode 100644 index 0000000..75729a8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/__pycache__/video.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/aacircle.py b/.venv/lib/python3.8/site-packages/pygame/examples/aacircle.py new file mode 100644 index 0000000..de3733d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/aacircle.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +"""Proof of concept gfxdraw example""" + +import pygame +import pygame.gfxdraw + + +def main(): + pygame.init() + screen = pygame.display.set_mode((500, 500)) + screen.fill((255, 0, 0)) + s = pygame.Surface(screen.get_size(), pygame.SRCALPHA, 32) + pygame.draw.line(s, (0, 0, 0), (250, 250), (250 + 200, 250)) + + width = 1 + for a_radius in range(width): + radius = 200 + pygame.gfxdraw.aacircle(s, 250, 250, radius - a_radius, (0, 0, 0)) + + screen.blit(s, (0, 0)) + + pygame.draw.circle(screen, "green", (50, 100), 10) + pygame.draw.circle(screen, "black", (50, 100), 10, 1) + + pygame.display.flip() + try: + while 1: + event = pygame.event.wait() + if event.type == pygame.QUIT: + break + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE or event.unicode == "q": + break + pygame.display.flip() + finally: + pygame.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/aliens.py b/.venv/lib/python3.8/site-packages/pygame/examples/aliens.py new file mode 100644 index 0000000..9fecb3f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/aliens.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python +""" pygame.examples.aliens + +Shows a mini game where you have to defend against aliens. + +What does it show you about pygame? + +* pg.sprite, the difference between Sprite and Group. +* dirty rectangle optimization for processing for speed. +* music with pg.mixer.music, including fadeout +* sound effects with pg.Sound +* event processing, keyboard handling, QUIT handling. +* a main loop frame limited with a game clock from pg.time.Clock +* fullscreen switching. + + +Controls +-------- + +* Left and right arrows to move. +* Space bar to shoot +* f key to toggle between fullscreen. + +""" + +import random +import os + +# import basic pygame modules +import pygame as pg + +# see if we can load more than standard BMP +if not pg.image.get_extended(): + raise SystemExit("Sorry, extended image module required") + + +# game constants +MAX_SHOTS = 2 # most player bullets onscreen +ALIEN_ODDS = 22 # chances a new alien appears +BOMB_ODDS = 60 # chances a new bomb will drop +ALIEN_RELOAD = 12 # frames between new aliens +SCREENRECT = pg.Rect(0, 0, 640, 480) +SCORE = 0 + +main_dir = os.path.split(os.path.abspath(__file__))[0] + + +def load_image(file): + """loads an image, prepares it for play""" + file = os.path.join(main_dir, "data", file) + try: + surface = pg.image.load(file) + except pg.error: + raise SystemExit('Could not load image "%s" %s' % (file, pg.get_error())) + return surface.convert() + + +def load_sound(file): + """because pygame can be be compiled without mixer.""" + if not pg.mixer: + return None + file = os.path.join(main_dir, "data", file) + try: + sound = pg.mixer.Sound(file) + return sound + except pg.error: + print("Warning, unable to load, %s" % file) + return None + + +# Each type of game object gets an init and an update function. +# The update function is called once per frame, and it is when each object should +# change its current position and state. +# +# The Player object actually gets a "move" function instead of update, +# since it is passed extra information about the keyboard. + + +class Player(pg.sprite.Sprite): + """Representing the player as a moon buggy type car.""" + + speed = 10 + bounce = 24 + gun_offset = -11 + images = [] + + def __init__(self): + pg.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom) + self.reloading = 0 + self.origtop = self.rect.top + self.facing = -1 + + def move(self, direction): + if direction: + self.facing = direction + self.rect.move_ip(direction * self.speed, 0) + self.rect = self.rect.clamp(SCREENRECT) + if direction < 0: + self.image = self.images[0] + elif direction > 0: + self.image = self.images[1] + self.rect.top = self.origtop - (self.rect.left // self.bounce % 2) + + def gunpos(self): + pos = self.facing * self.gun_offset + self.rect.centerx + return pos, self.rect.top + + +class Alien(pg.sprite.Sprite): + """An alien space ship. That slowly moves down the screen.""" + + speed = 13 + animcycle = 12 + images = [] + + def __init__(self): + pg.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect() + self.facing = random.choice((-1, 1)) * Alien.speed + self.frame = 0 + if self.facing < 0: + self.rect.right = SCREENRECT.right + + def update(self): + self.rect.move_ip(self.facing, 0) + if not SCREENRECT.contains(self.rect): + self.facing = -self.facing + self.rect.top = self.rect.bottom + 1 + self.rect = self.rect.clamp(SCREENRECT) + self.frame = self.frame + 1 + self.image = self.images[self.frame // self.animcycle % 3] + + +class Explosion(pg.sprite.Sprite): + """An explosion. Hopefully the Alien and not the player!""" + + defaultlife = 12 + animcycle = 3 + images = [] + + def __init__(self, actor): + pg.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(center=actor.rect.center) + self.life = self.defaultlife + + def update(self): + """called every time around the game loop. + + Show the explosion surface for 'defaultlife'. + Every game tick(update), we decrease the 'life'. + + Also we animate the explosion. + """ + self.life = self.life - 1 + self.image = self.images[self.life // self.animcycle % 2] + if self.life <= 0: + self.kill() + + +class Shot(pg.sprite.Sprite): + """a bullet the Player sprite fires.""" + + speed = -11 + images = [] + + def __init__(self, pos): + pg.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(midbottom=pos) + + def update(self): + """called every time around the game loop. + + Every tick we move the shot upwards. + """ + self.rect.move_ip(0, self.speed) + if self.rect.top <= 0: + self.kill() + + +class Bomb(pg.sprite.Sprite): + """A bomb the aliens drop.""" + + speed = 9 + images = [] + + def __init__(self, alien): + pg.sprite.Sprite.__init__(self, self.containers) + self.image = self.images[0] + self.rect = self.image.get_rect(midbottom=alien.rect.move(0, 5).midbottom) + + def update(self): + """called every time around the game loop. + + Every frame we move the sprite 'rect' down. + When it reaches the bottom we: + + - make an explosion. + - remove the Bomb. + """ + self.rect.move_ip(0, self.speed) + if self.rect.bottom >= 470: + Explosion(self) + self.kill() + + +class Score(pg.sprite.Sprite): + """to keep track of the score.""" + + def __init__(self): + pg.sprite.Sprite.__init__(self) + self.font = pg.font.Font(None, 20) + self.font.set_italic(1) + self.color = "white" + self.lastscore = -1 + self.update() + self.rect = self.image.get_rect().move(10, 450) + + def update(self): + """We only update the score in update() when it has changed.""" + if SCORE != self.lastscore: + self.lastscore = SCORE + msg = "Score: %d" % SCORE + self.image = self.font.render(msg, 0, self.color) + + +def main(winstyle=0): + # Initialize pygame + if pg.get_sdl_version()[0] == 2: + pg.mixer.pre_init(44100, 32, 2, 1024) + pg.init() + if pg.mixer and not pg.mixer.get_init(): + print("Warning, no sound") + pg.mixer = None + + fullscreen = False + # Set the display mode + winstyle = 0 # |FULLSCREEN + bestdepth = pg.display.mode_ok(SCREENRECT.size, winstyle, 32) + screen = pg.display.set_mode(SCREENRECT.size, winstyle, bestdepth) + + # Load images, assign to sprite classes + # (do this before the classes are used, after screen setup) + img = load_image("player1.gif") + Player.images = [img, pg.transform.flip(img, 1, 0)] + img = load_image("explosion1.gif") + Explosion.images = [img, pg.transform.flip(img, 1, 1)] + Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")] + Bomb.images = [load_image("bomb.gif")] + Shot.images = [load_image("shot.gif")] + + # decorate the game window + icon = pg.transform.scale(Alien.images[0], (32, 32)) + pg.display.set_icon(icon) + pg.display.set_caption("Pygame Aliens") + pg.mouse.set_visible(0) + + # create the background, tile the bgd image + bgdtile = load_image("background.gif") + background = pg.Surface(SCREENRECT.size) + for x in range(0, SCREENRECT.width, bgdtile.get_width()): + background.blit(bgdtile, (x, 0)) + screen.blit(background, (0, 0)) + pg.display.flip() + + # load the sound effects + boom_sound = load_sound("boom.wav") + shoot_sound = load_sound("car_door.wav") + if pg.mixer: + music = os.path.join(main_dir, "data", "house_lo.wav") + pg.mixer.music.load(music) + pg.mixer.music.play(-1) + + # Initialize Game Groups + aliens = pg.sprite.Group() + shots = pg.sprite.Group() + bombs = pg.sprite.Group() + all = pg.sprite.RenderUpdates() + lastalien = pg.sprite.GroupSingle() + + # assign default groups to each sprite class + Player.containers = all + Alien.containers = aliens, all, lastalien + Shot.containers = shots, all + Bomb.containers = bombs, all + Explosion.containers = all + Score.containers = all + + # Create Some Starting Values + global score + alienreload = ALIEN_RELOAD + clock = pg.time.Clock() + + # initialize our starting sprites + global SCORE + player = Player() + Alien() # note, this 'lives' because it goes into a sprite group + if pg.font: + all.add(Score()) + + # Run our main loop whilst the player is alive. + while player.alive(): + + # get input + for event in pg.event.get(): + if event.type == pg.QUIT: + return + if event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE: + return + elif event.type == pg.KEYDOWN: + if event.key == pg.K_f: + if not fullscreen: + print("Changing to FULLSCREEN") + screen_backup = screen.copy() + screen = pg.display.set_mode( + SCREENRECT.size, winstyle | pg.FULLSCREEN, bestdepth + ) + screen.blit(screen_backup, (0, 0)) + else: + print("Changing to windowed mode") + screen_backup = screen.copy() + screen = pg.display.set_mode( + SCREENRECT.size, winstyle, bestdepth + ) + screen.blit(screen_backup, (0, 0)) + pg.display.flip() + fullscreen = not fullscreen + + keystate = pg.key.get_pressed() + + # clear/erase the last drawn sprites + all.clear(screen, background) + + # update all the sprites + all.update() + + # handle player input + direction = keystate[pg.K_RIGHT] - keystate[pg.K_LEFT] + player.move(direction) + firing = keystate[pg.K_SPACE] + if not player.reloading and firing and len(shots) < MAX_SHOTS: + Shot(player.gunpos()) + if pg.mixer: + shoot_sound.play() + player.reloading = firing + + # Create new alien + if alienreload: + alienreload = alienreload - 1 + elif not int(random.random() * ALIEN_ODDS): + Alien() + alienreload = ALIEN_RELOAD + + # Drop bombs + if lastalien and not int(random.random() * BOMB_ODDS): + Bomb(lastalien.sprite) + + # Detect collisions between aliens and players. + for alien in pg.sprite.spritecollide(player, aliens, 1): + if pg.mixer: + boom_sound.play() + Explosion(alien) + Explosion(player) + SCORE = SCORE + 1 + player.kill() + + # See if shots hit the aliens. + for alien in pg.sprite.groupcollide(aliens, shots, 1, 1).keys(): + if pg.mixer: + boom_sound.play() + Explosion(alien) + SCORE = SCORE + 1 + + # See if alien boms hit the player. + for bomb in pg.sprite.spritecollide(player, bombs, 1): + if pg.mixer: + boom_sound.play() + Explosion(player) + Explosion(bomb) + player.kill() + + # draw the scene + dirty = all.draw(screen) + pg.display.update(dirty) + + # cap the framerate at 40fps. Also called 40HZ or 40 times per second. + clock.tick(40) + + if pg.mixer: + pg.mixer.music.fadeout(1000) + pg.time.wait(1000) + + +# call the "main" function if running this script +if __name__ == "__main__": + main() + pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/arraydemo.py b/.venv/lib/python3.8/site-packages/pygame/examples/arraydemo.py new file mode 100644 index 0000000..c35f376 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/arraydemo.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +""" pygame.examples.arraydemo + +Welcome to the arraydemo! + +Use the numpy array package to manipulate pixels. + +This demo will show you a few things: + +* scale up, scale down, flip, +* cross fade +* soften +* put stripes on it! + +""" + + +import os + +import pygame as pg +from pygame import surfarray + +main_dir = os.path.split(os.path.abspath(__file__))[0] + + +def surfdemo_show(array_img, name): + "displays a surface, waits for user to continue" + screen = pg.display.set_mode(array_img.shape[:2], 0, 32) + surfarray.blit_array(screen, array_img) + pg.display.flip() + pg.display.set_caption(name) + while 1: + e = pg.event.wait() + if e.type == pg.MOUSEBUTTONDOWN: + break + elif e.type == pg.KEYDOWN and e.key == pg.K_s: + # pg.image.save(screen, name+'.bmp') + # s = pg.Surface(screen.get_size(), 0, 32) + # s = s.convert_alpha() + # s.fill((0,0,0,255)) + # s.blit(screen, (0,0)) + # s.fill((222,0,0,50), (0,0,40,40)) + # pg.image.save_extended(s, name+'.png') + # pg.image.save(s, name+'.png') + # pg.image.save(screen, name+'_screen.png') + # pg.image.save(s, name+'.tga') + pg.image.save(screen, name + ".png") + elif e.type == pg.QUIT: + pg.quit() + raise SystemExit() + + +def main(): + """show various surfarray effects""" + import numpy as N + from numpy import int32, uint8, uint + + pg.init() + print("Using %s" % surfarray.get_arraytype().capitalize()) + print("Press the mouse button to advance image.") + print('Press the "s" key to save the current image.') + + # allblack + allblack = N.zeros((128, 128), int32) + surfdemo_show(allblack, "allblack") + + # striped + # the element type is required for N.zeros in numpy else + # an array of float is returned. + striped = N.zeros((128, 128, 3), int32) + striped[:] = (255, 0, 0) + striped[:, ::3] = (0, 255, 255) + surfdemo_show(striped, "striped") + + # rgbarray + imagename = os.path.join(main_dir, "data", "arraydemo.bmp") + imgsurface = pg.image.load(imagename) + rgbarray = surfarray.array3d(imgsurface) + surfdemo_show(rgbarray, "rgbarray") + + # flipped + flipped = rgbarray[:, ::-1] + surfdemo_show(flipped, "flipped") + + # scaledown + scaledown = rgbarray[::2, ::2] + surfdemo_show(scaledown, "scaledown") + + # scaleup + # the element type is required for N.zeros in numpy else + # an #array of floats is returned. + shape = rgbarray.shape + scaleup = N.zeros((shape[0] * 2, shape[1] * 2, shape[2]), int32) + scaleup[::2, ::2, :] = rgbarray + scaleup[1::2, ::2, :] = rgbarray + scaleup[:, 1::2] = scaleup[:, ::2] + surfdemo_show(scaleup, "scaleup") + + # redimg + redimg = N.array(rgbarray) + redimg[:, :, 1:] = 0 + surfdemo_show(redimg, "redimg") + + # soften + # having factor as an array forces integer upgrade during multiplication + # of rgbarray, even for numpy. + factor = N.array((8,), int32) + soften = N.array(rgbarray, int32) + soften[1:, :] += rgbarray[:-1, :] * factor + soften[:-1, :] += rgbarray[1:, :] * factor + soften[:, 1:] += rgbarray[:, :-1] * factor + soften[:, :-1] += rgbarray[:, 1:] * factor + soften //= 33 + surfdemo_show(soften, "soften") + + # crossfade (50%) + src = N.array(rgbarray) + dest = N.zeros(rgbarray.shape) # dest is float64 by default. + dest[:] = 20, 50, 100 + diff = (dest - src) * 0.50 + xfade = src + diff.astype(uint) + surfdemo_show(xfade, "xfade") + + # alldone + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/audiocapture.py b/.venv/lib/python3.8/site-packages/pygame/examples/audiocapture.py new file mode 100644 index 0000000..aa50fcf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/audiocapture.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +""" pygame.examples.audiocapture + +A pygame 2 experiment. + +* record sound from a microphone +* play back the recorded sound +""" +import pygame as pg +import time + +from pygame._sdl2 import ( + get_audio_device_names, + AudioDevice, + AUDIO_F32, + AUDIO_ALLOW_FORMAT_CHANGE, +) +from pygame._sdl2.mixer import set_post_mix + + +pg.mixer.pre_init(44100, 32, 2, 512) +pg.init() + +# init_subsystem(INIT_AUDIO) +names = get_audio_device_names(True) +print(names) + +sounds = [] +sound_chunks = [] + + +def callback(audiodevice, audiomemoryview): + """This is called in the sound thread. + + Note, that the frequency and such you request may not be what you get. + """ + # print(type(audiomemoryview), len(audiomemoryview)) + # print(audiodevice) + sound_chunks.append(bytes(audiomemoryview)) + + +def postmix_callback(postmix, audiomemoryview): + """This is called in the sound thread. + + At the end of mixing we get this data. + """ + print(type(audiomemoryview), len(audiomemoryview)) + print(postmix) + + +set_post_mix(postmix_callback) + +audio = AudioDevice( + devicename=names[0], + iscapture=True, + frequency=44100, + audioformat=AUDIO_F32, + numchannels=2, + chunksize=512, + allowed_changes=AUDIO_ALLOW_FORMAT_CHANGE, + callback=callback, +) +# start recording. +audio.pause(0) + +print(audio) + +print("recording with '%s'" % names[0]) +time.sleep(5) + + +print("Turning data into a pg.mixer.Sound") +sound = pg.mixer.Sound(buffer=b"".join(sound_chunks)) + +print("playing back recorded sound") +sound.play() +time.sleep(5) +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/blend_fill.py b/.venv/lib/python3.8/site-packages/pygame/examples/blend_fill.py new file mode 100644 index 0000000..301888b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/blend_fill.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +""" pygame.examples.blend_fill + +BLEND_ing colors in different ways with Surface.fill(). + +Keyboard Controls: + +* Press R, G, B to increase the color channel values, +* 1-9 to set the step range for the increment, +* A - ADD, S- SUB, M- MULT, - MIN, + MAX to change the blend modes + +""" +import os +import pygame as pg +from pygame import K_1, K_2, K_3, K_4, K_5, K_6, K_7, K_8, K_9 + + +def usage(): + print("Press R, G, B to increase the color channel values,") + print("1-9 to set the step range for the increment,") + print("A - ADD, S- SUB, M- MULT, - MIN, + MAX") + print(" to change the blend modes") + + +main_dir = os.path.split(os.path.abspath(__file__))[0] +data_dir = os.path.join(main_dir, "data") + + +def main(): + color = [0, 0, 0] + changed = False + blendtype = 0 + step = 5 + + pg.init() + screen = pg.display.set_mode((640, 480), 0, 32) + screen.fill((100, 100, 100)) + + image = pg.image.load(os.path.join(data_dir, "liquid.bmp")).convert() + blendimage = pg.image.load(os.path.join(data_dir, "liquid.bmp")).convert() + screen.blit(image, (10, 10)) + screen.blit(blendimage, (200, 10)) + + pg.display.flip() + pg.key.set_repeat(500, 30) + usage() + + going = True + while going: + for event in pg.event.get(): + if event.type == pg.QUIT: + going = False + + if event.type == pg.KEYDOWN: + usage() + + if event.key == pg.K_ESCAPE: + going = False + + if event.key == pg.K_r: + color[0] += step + if color[0] > 255: + color[0] = 0 + changed = True + + elif event.key == pg.K_g: + color[1] += step + if color[1] > 255: + color[1] = 0 + changed = True + + elif event.key == pg.K_b: + color[2] += step + if color[2] > 255: + color[2] = 0 + changed = True + + elif event.key == pg.K_a: + blendtype = pg.BLEND_ADD + changed = True + elif event.key == pg.K_s: + blendtype = pg.BLEND_SUB + changed = True + elif event.key == pg.K_m: + blendtype = pg.BLEND_MULT + changed = True + elif event.key == pg.K_PLUS: + blendtype = pg.BLEND_MAX + changed = True + elif event.key == pg.K_MINUS: + blendtype = pg.BLEND_MIN + changed = True + + elif event.key in (K_1, K_2, K_3, K_4, K_5, K_6, K_7, K_8, K_9): + step = int(event.unicode) + + if changed: + screen.fill((100, 100, 100)) + screen.blit(image, (10, 10)) + blendimage.blit(image, (0, 0)) + # blendimage.fill (color, (0, 0, 20, 20), blendtype) + blendimage.fill(color, None, blendtype) + screen.blit(blendimage, (200, 10)) + print( + "Color: %s, Pixel (0,0): %s" + % (tuple(color), [blendimage.get_at((0, 0))]) + ) + changed = False + pg.display.flip() + + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/blit_blends.py b/.venv/lib/python3.8/site-packages/pygame/examples/blit_blends.py new file mode 100644 index 0000000..8bc8978 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/blit_blends.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +""" pygame.examples.blit_blends + +Blending colors in different ways with different blend modes. + +It also shows some tricks with the surfarray. +Including how to do additive blending. + + +Keyboard Controls +----------------- + +* R, G, B - add a bit of Red, Green, or Blue. +* A - Add blend mode +* S - Subtractive blend mode +* M - Multiply blend mode +* = key BLEND_MAX blend mode. +* - key BLEND_MIN blend mode. +* 1, 2, 3, 4 - use different images. + +""" +import os +import pygame as pg +import time + +main_dir = os.path.split(os.path.abspath(__file__))[0] +data_dir = os.path.join(main_dir, "data") + +try: + import pygame.surfarray + import numpy +except ImportError: + print("no surfarray for you! install numpy") + + +def main(): + pg.init() + pg.mixer.quit() # remove ALSA underflow messages for Debian squeeze + screen = pg.display.set_mode((640, 480)) + + im1 = pg.Surface(screen.get_size()) + # im1= im1.convert() + im1.fill((100, 0, 0)) + + im2 = pg.Surface(screen.get_size()) + im2.fill((0, 50, 0)) + # we make a srcalpha copy of it. + # im3= im2.convert(SRCALPHA) + im3 = im2 + im3.set_alpha(127) + + images = {} + images[pg.K_1] = im2 + images[pg.K_2] = pg.image.load(os.path.join(data_dir, "chimp.png")) + images[pg.K_3] = pg.image.load(os.path.join(data_dir, "alien3.gif")) + images[pg.K_4] = pg.image.load(os.path.join(data_dir, "liquid.bmp")) + img_to_blit = im2.convert() + iaa = img_to_blit.convert_alpha() + + blits = {} + blits[pg.K_a] = pg.BLEND_ADD + blits[pg.K_s] = pg.BLEND_SUB + blits[pg.K_m] = pg.BLEND_MULT + blits[pg.K_EQUALS] = pg.BLEND_MAX + blits[pg.K_MINUS] = pg.BLEND_MIN + + blitsn = {} + blitsn[pg.K_a] = "BLEND_ADD" + blitsn[pg.K_s] = "BLEND_SUB" + blitsn[pg.K_m] = "BLEND_MULT" + blitsn[pg.K_EQUALS] = "BLEND_MAX" + blitsn[pg.K_MINUS] = "BLEND_MIN" + + screen.blit(im1, (0, 0)) + pg.display.flip() + clock = pg.time.Clock() + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + + going = True + while going: + clock.tick(60) + + for event in pg.event.get(): + if event.type == pg.QUIT: + going = False + if event.type == pg.KEYDOWN: + usage() + + if event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE: + going = False + + elif event.type == pg.KEYDOWN and event.key in images.keys(): + img_to_blit = images[event.key] + iaa = img_to_blit.convert_alpha() + + elif event.type == pg.KEYDOWN and event.key in blits.keys(): + t1 = time.time() + # blits is a dict keyed with key -> blit flag. eg BLEND_ADD. + im1.blit(img_to_blit, (0, 0), None, blits[event.key]) + t2 = time.time() + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + print("time to do:%s:" % (t2 - t1)) + + elif event.type == pg.KEYDOWN and event.key in [pg.K_t]: + + for bkey in blits.keys(): + t1 = time.time() + + for x in range(300): + im1.blit(img_to_blit, (0, 0), None, blits[bkey]) + + t2 = time.time() + + # show which key we're doing... + onedoing = blitsn[bkey] + print("time to do :%s: is :%s:" % (onedoing, t2 - t1)) + + elif event.type == pg.KEYDOWN and event.key in [pg.K_o]: + t1 = time.time() + # blits is a dict keyed with key -> blit flag. eg BLEND_ADD. + im1.blit(iaa, (0, 0)) + t2 = time.time() + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + print("time to do:%s:" % (t2 - t1)) + + elif event.type == pg.KEYDOWN and event.key == pg.K_SPACE: + # this additive blend without clamp two surfaces. + # im1.set_alpha(127) + # im1.blit(im1, (0,0)) + # im1.set_alpha(255) + t1 = time.time() + + im1p = pygame.surfarray.pixels2d(im1) + im2p = pygame.surfarray.pixels2d(im2) + im1p += im2p + del im1p + del im2p + t2 = time.time() + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + print("time to do:%s:" % (t2 - t1)) + + elif event.type == pg.KEYDOWN and event.key in [pg.K_z]: + t1 = time.time() + im1p = pygame.surfarray.pixels3d(im1) + im2p = pygame.surfarray.pixels3d(im2) + im1p16 = im1p.astype(numpy.uint16) + im2p16 = im1p.astype(numpy.uint16) + im1p16 += im2p16 + im1p16 = numpy.minimum(im1p16, 255) + pygame.surfarray.blit_array(im1, im1p16) + + del im1p + del im2p + t2 = time.time() + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + print("time to do:%s:" % (t2 - t1)) + + elif event.type == pg.KEYDOWN and event.key in [pg.K_r, pg.K_g, pg.K_b]: + # this adds one to each pixel. + colmap = {} + colmap[pg.K_r] = 0x10000 + colmap[pg.K_g] = 0x00100 + colmap[pg.K_b] = 0x00001 + im1p = pygame.surfarray.pixels2d(im1) + im1p += colmap[event.key] + del im1p + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + + elif event.type == pg.KEYDOWN and event.key == pg.K_p: + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + + elif event.type == pg.KEYDOWN and event.key == pg.K_f: + # this additive blend without clamp two surfaces. + + t1 = time.time() + im1.set_alpha(127) + im1.blit(im2, (0, 0)) + im1.set_alpha(255) + + t2 = time.time() + print("one pixel is:%s:" % [im1.get_at((0, 0))]) + print("time to do:%s:" % (t2 - t1)) + + screen.blit(im1, (0, 0)) + pg.display.flip() + + pg.quit() + + +def usage(): + print("press keys 1-5 to change image to blit.") + print("A - ADD, S- SUB, M- MULT, - MIN, + MAX") + print("T - timing test for special blend modes.") + + +if __name__ == "__main__": + usage() + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/camera.py b/.venv/lib/python3.8/site-packages/pygame/examples/camera.py new file mode 100644 index 0000000..34fc46e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/camera.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +""" pygame.examples.camera + +Basic image capturing and display using pygame.camera + +Keyboard controls +----------------- + +- 0, start camera 0. +- 1, start camera 1. +- 9, start camera 9. +- 10, start camera... wait a minute! There's not 10 key! +""" +import pygame as pg +import pygame.camera + + +class VideoCapturePlayer(object): + + size = (640, 480) + + def __init__(self, **argd): + self.__dict__.update(**argd) + super(VideoCapturePlayer, self).__init__(**argd) + + # create a display surface. standard pygame stuff + self.display = pg.display.set_mode(self.size) + self.init_cams(0) + + def init_cams(self, which_cam_idx): + + # gets a list of available cameras. + self.clist = pygame.camera.list_cameras() + print(self.clist) + + if not self.clist: + raise ValueError("Sorry, no cameras detected.") + + try: + cam_id = self.clist[which_cam_idx] + except IndexError: + cam_id = self.clist[0] + + # creates the camera of the specified size and in RGB colorspace + self.camera = pygame.camera.Camera(cam_id, self.size, "RGB") + + # starts the camera + self.camera.start() + + self.clock = pg.time.Clock() + + # create a surface to capture to. for performance purposes, you want the + # bit depth to be the same as that of the display surface. + self.snapshot = pg.surface.Surface(self.size, 0, self.display) + + def get_and_flip(self): + # if you don't want to tie the framerate to the camera, you can check and + # see if the camera has an image ready. note that while this works + # on most cameras, some will never return true. + + self.snapshot = self.camera.get_image(self.display) + + # if 0 and self.camera.query_image(): + # # capture an image + + # self.snapshot = self.camera.get_image(self.snapshot) + + # if 0: + # self.snapshot = self.camera.get_image(self.snapshot) + # # self.snapshot = self.camera.get_image() + + # # blit it to the display surface. simple! + # self.display.blit(self.snapshot, (0, 0)) + # else: + + # self.snapshot = self.camera.get_image(self.display) + # # self.display.blit(self.snapshot, (0,0)) + + pg.display.flip() + + def main(self): + going = True + while going: + events = pg.event.get() + for e in events: + if e.type == pg.QUIT or (e.type == pg.KEYDOWN and e.key == pg.K_ESCAPE): + going = False + if e.type == pg.KEYDOWN: + if e.key in range(pg.K_0, pg.K_0 + 10): + self.init_cams(e.key - pg.K_0) + + self.get_and_flip() + self.clock.tick() + pygame.display.set_caption(f"CAMERA! ({self.clock.get_fps():.2f} FPS)") + + +def main(): + pg.init() + pygame.camera.init() + VideoCapturePlayer().main() + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/chimp.py b/.venv/lib/python3.8/site-packages/pygame/examples/chimp.py new file mode 100644 index 0000000..4612be5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/chimp.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +""" pygame.examples.chimp + +This simple example is used for the line-by-line tutorial +that comes with pygame. It is based on a 'popular' web banner. +Note there are comments here, but for the full explanation, +follow along in the tutorial. +""" + + +# Import Modules +import os +import pygame as pg + +if not pg.font: + print("Warning, fonts disabled") +if not pg.mixer: + print("Warning, sound disabled") + +main_dir = os.path.split(os.path.abspath(__file__))[0] +data_dir = os.path.join(main_dir, "data") + + +# functions to create our resources +def load_image(name, colorkey=None, scale=1): + fullname = os.path.join(data_dir, name) + image = pg.image.load(fullname) + image = image.convert() + + size = image.get_size() + size = (size[0] * scale, size[1] * scale) + image = pg.transform.scale(image, size) + + if colorkey is not None: + if colorkey == -1: + colorkey = image.get_at((0, 0)) + image.set_colorkey(colorkey, pg.RLEACCEL) + return image, image.get_rect() + + +def load_sound(name): + class NoneSound: + def play(self): + pass + + if not pg.mixer or not pg.mixer.get_init(): + return NoneSound() + + fullname = os.path.join(data_dir, name) + sound = pg.mixer.Sound(fullname) + + return sound + + +# classes for our game objects +class Fist(pg.sprite.Sprite): + """moves a clenched fist on the screen, following the mouse""" + + def __init__(self): + pg.sprite.Sprite.__init__(self) # call Sprite initializer + self.image, self.rect = load_image("fist.png", -1) + self.fist_offset = (-235, -80) + self.punching = False + + def update(self): + """move the fist based on the mouse position""" + pos = pg.mouse.get_pos() + self.rect.topleft = pos + self.rect.move_ip(self.fist_offset) + if self.punching: + self.rect.move_ip(15, 25) + + def punch(self, target): + """returns true if the fist collides with the target""" + if not self.punching: + self.punching = True + hitbox = self.rect.inflate(-5, -5) + return hitbox.colliderect(target.rect) + + def unpunch(self): + """called to pull the fist back""" + self.punching = False + + +class Chimp(pg.sprite.Sprite): + """moves a monkey critter across the screen. it can spin the + monkey when it is punched.""" + + def __init__(self): + pg.sprite.Sprite.__init__(self) # call Sprite intializer + self.image, self.rect = load_image("chimp.png", -1, 4) + screen = pg.display.get_surface() + self.area = screen.get_rect() + self.rect.topleft = 10, 90 + self.move = 18 + self.dizzy = False + + def update(self): + """walk or spin, depending on the monkeys state""" + if self.dizzy: + self._spin() + else: + self._walk() + + def _walk(self): + """move the monkey across the screen, and turn at the ends""" + newpos = self.rect.move((self.move, 0)) + if not self.area.contains(newpos): + if self.rect.left < self.area.left or self.rect.right > self.area.right: + self.move = -self.move + newpos = self.rect.move((self.move, 0)) + self.image = pg.transform.flip(self.image, True, False) + self.rect = newpos + + def _spin(self): + """spin the monkey image""" + center = self.rect.center + self.dizzy = self.dizzy + 12 + if self.dizzy >= 360: + self.dizzy = False + self.image = self.original + else: + rotate = pg.transform.rotate + self.image = rotate(self.original, self.dizzy) + self.rect = self.image.get_rect(center=center) + + def punched(self): + """this will cause the monkey to start spinning""" + if not self.dizzy: + self.dizzy = True + self.original = self.image + + +def main(): + """this function is called when the program starts. + it initializes everything it needs, then runs in + a loop until the function returns.""" + # Initialize Everything + pg.init() + screen = pg.display.set_mode((1280, 480), pg.SCALED) + pg.display.set_caption("Monkey Fever") + pg.mouse.set_visible(False) + + # Create The Backgound + background = pg.Surface(screen.get_size()) + background = background.convert() + background.fill((170, 238, 187)) + + # Put Text On The Background, Centered + if pg.font: + font = pg.font.Font(None, 64) + text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10)) + textpos = text.get_rect(centerx=background.get_width() / 2, y=10) + background.blit(text, textpos) + + # Display The Background + screen.blit(background, (0, 0)) + pg.display.flip() + + # Prepare Game Objects + whiff_sound = load_sound("whiff.wav") + punch_sound = load_sound("punch.wav") + chimp = Chimp() + fist = Fist() + allsprites = pg.sprite.RenderPlain((chimp, fist)) + clock = pg.time.Clock() + + # Main Loop + going = True + while going: + clock.tick(60) + + # Handle Input Events + for event in pg.event.get(): + if event.type == pg.QUIT: + going = False + elif event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE: + going = False + elif event.type == pg.MOUSEBUTTONDOWN: + if fist.punch(chimp): + punch_sound.play() # punch + chimp.punched() + else: + whiff_sound.play() # miss + elif event.type == pg.MOUSEBUTTONUP: + fist.unpunch() + + allsprites.update() + + # Draw Everything + screen.blit(background, (0, 0)) + allsprites.draw(screen) + pg.display.flip() + + pg.quit() + + +# Game Over + + +# this calls the 'main' function when this script is executed +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/cursors.py b/.venv/lib/python3.8/site-packages/pygame/examples/cursors.py new file mode 100644 index 0000000..09e5d1f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/cursors.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +""" pygame.examples.cursors + +Click a mouse button (if you have one!) and the cursor changes. + +""" +import pygame as pg + + +arrow = ( + "xX ", + "X.X ", + "X..X ", + "X...X ", + "X....X ", + "X.....X ", + "X......X ", + "X.......X ", + "X........X ", + "X.........X ", + "X......XXXXX ", + "X...X..X ", + "X..XX..X ", + "X.X XX..X ", + "XX X..X ", + "X X..X ", + " X..X ", + " X..X ", + " X..X ", + " XX ", + " ", + " ", + " ", + " ", +) + + +no = ( + " ", + " ", + " XXXXXX ", + " XX......XX ", + " X..........X ", + " X....XXXX....X ", + " X...XX XX...X ", + " X.....X X...X ", + " X..X...X X..X ", + " X...XX...X X...X ", + " X..X X...X X..X ", + " X..X X...X X..X ", + " X..X X.,.X X..X ", + " X..X X...X X..X ", + " X...X X...XX...X ", + " X..X X...X..X ", + " X...X X.....X ", + " X...XX X...X ", + " X....XXXXX...X ", + " X..........X ", + " XX......XX ", + " XXXXXX ", + " ", + " ", +) + + +def TestCursor(arrow): + hotspot = None + for y, line in enumerate(arrow): + for x, char in enumerate(line): + if char in ["x", ",", "O"]: + hotspot = x, y + break + if hotspot is not None: + break + if hotspot is None: + raise Exception("No hotspot specified for cursor '%s'!" % arrow) + s2 = [] + for line in arrow: + s2.append(line.replace("x", "X").replace(",", ".").replace("O", "o")) + cursor, mask = pg.cursors.compile(s2, "X", ".", "o") + size = len(arrow[0]), len(arrow) + pg.mouse.set_cursor(size, hotspot, cursor, mask) + + +def main(): + pg.init() + pg.font.init() + font = pg.font.Font(None, 24) + bg = pg.display.set_mode((800, 600), 0, 24) + bg.fill((255, 255, 255)) + bg.blit(font.render("Click to advance", 1, (0, 0, 0)), (0, 0)) + pg.display.update() + for cursor in [no, arrow]: + TestCursor(cursor) + going = True + while going: + pg.event.pump() + for e in pg.event.get(): + if e.type == pg.MOUSEBUTTONDOWN: + going = False + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/BGR.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/BGR.png new file mode 100644 index 0000000..f5dba74 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/BGR.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.gif new file mode 100644 index 0000000..c4497e0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.jpg b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.jpg new file mode 100644 index 0000000..6d110a4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.png new file mode 100644 index 0000000..471d6a4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien1.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/alien2.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien2.gif new file mode 100644 index 0000000..8df05a3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien2.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/alien2.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien2.png new file mode 100644 index 0000000..aef5ace Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien2.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/alien3.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien3.gif new file mode 100644 index 0000000..5305d41 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien3.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/alien3.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien3.png new file mode 100644 index 0000000..90d0f7c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/alien3.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/arraydemo.bmp b/.venv/lib/python3.8/site-packages/pygame/examples/data/arraydemo.bmp new file mode 100644 index 0000000..ad96338 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/arraydemo.bmp differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/asprite.bmp b/.venv/lib/python3.8/site-packages/pygame/examples/data/asprite.bmp new file mode 100644 index 0000000..cc96356 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/asprite.bmp differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/background.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/background.gif new file mode 100644 index 0000000..5041ce6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/background.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/black.ppm b/.venv/lib/python3.8/site-packages/pygame/examples/data/black.ppm new file mode 100644 index 0000000..698a52c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/data/black.ppm @@ -0,0 +1,3076 @@ +P3 +# Created by GIMP version 2.10.20 PNM plug-in +32 32 +255 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/blue.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/blue.gif new file mode 100644 index 0000000..98c6fd6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/blue.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/blue.mpg b/.venv/lib/python3.8/site-packages/pygame/examples/data/blue.mpg new file mode 100644 index 0000000..60dceca Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/blue.mpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/bomb.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/bomb.gif new file mode 100644 index 0000000..f885cbb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/bomb.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/boom.wav b/.venv/lib/python3.8/site-packages/pygame/examples/data/boom.wav new file mode 100644 index 0000000..f19126a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/boom.wav differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/brick.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/brick.png new file mode 100644 index 0000000..cfe37a3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/brick.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/car_door.wav b/.venv/lib/python3.8/site-packages/pygame/examples/data/car_door.wav new file mode 100644 index 0000000..60acf9e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/car_door.wav differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/chimp.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/chimp.png new file mode 100644 index 0000000..9bf37b1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/chimp.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/city.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/city.png new file mode 100644 index 0000000..202da5c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/city.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/crimson.pnm b/.venv/lib/python3.8/site-packages/pygame/examples/data/crimson.pnm new file mode 100644 index 0000000..28501e9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/data/crimson.pnm @@ -0,0 +1,5 @@ +P6 +# CREATOR: GIMP PNM Filter Version 1.1 +32 32 +255 +Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü<Ü< \ No newline at end of file diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/danger.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/danger.gif new file mode 100644 index 0000000..106d69c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/danger.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/explosion1.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/explosion1.gif new file mode 100644 index 0000000..fabec16 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/explosion1.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/fist.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/fist.png new file mode 100644 index 0000000..9097629 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/fist.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/green.pcx b/.venv/lib/python3.8/site-packages/pygame/examples/data/green.pcx new file mode 100644 index 0000000..c0aea8d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/green.pcx differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/grey.pgm b/.venv/lib/python3.8/site-packages/pygame/examples/data/grey.pgm new file mode 100644 index 0000000..b181a5d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/data/grey.pgm @@ -0,0 +1,1028 @@ +P2 +# Created by GIMP version 2.10.20 PNM plug-in +32 32 +255 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 +120 diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.mp3 b/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.mp3 new file mode 100644 index 0000000..4c26994 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.mp3 differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.ogg b/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.ogg new file mode 100644 index 0000000..e050848 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.ogg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.wav b/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.wav new file mode 100644 index 0000000..68a96b8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/house_lo.wav differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/laplacian.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/laplacian.png new file mode 100644 index 0000000..8d064f5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/laplacian.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/liquid.bmp b/.venv/lib/python3.8/site-packages/pygame/examples/data/liquid.bmp new file mode 100644 index 0000000..c4f12eb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/liquid.bmp differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/midikeys.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/midikeys.png new file mode 100644 index 0000000..74ecb86 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/midikeys.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/player1.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/player1.gif new file mode 100644 index 0000000..6c4eda7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/player1.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/punch.wav b/.venv/lib/python3.8/site-packages/pygame/examples/data/punch.wav new file mode 100644 index 0000000..aa3f56c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/punch.wav differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/purple.xpm b/.venv/lib/python3.8/site-packages/pygame/examples/data/purple.xpm new file mode 100644 index 0000000..7798cc1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/data/purple.xpm @@ -0,0 +1,36 @@ +/* XPM */ +static char * C:\Users\Kristof\Documents\purple_xpm[] = { +"32 32 1 1", +" c #FF00FF", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/red.jpg b/.venv/lib/python3.8/site-packages/pygame/examples/data/red.jpg new file mode 100644 index 0000000..11a9aa0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/red.jpg differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/sans.ttf b/.venv/lib/python3.8/site-packages/pygame/examples/data/sans.ttf new file mode 100644 index 0000000..09fac2f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/sans.ttf differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/scarlet.webp b/.venv/lib/python3.8/site-packages/pygame/examples/data/scarlet.webp new file mode 100644 index 0000000..cd0a15c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/scarlet.webp differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/secosmic_lo.wav b/.venv/lib/python3.8/site-packages/pygame/examples/data/secosmic_lo.wav new file mode 100644 index 0000000..867f802 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/secosmic_lo.wav differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/shot.gif b/.venv/lib/python3.8/site-packages/pygame/examples/data/shot.gif new file mode 100644 index 0000000..18de528 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/shot.gif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/static.png b/.venv/lib/python3.8/site-packages/pygame/examples/data/static.png new file mode 100644 index 0000000..fb3b057 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/static.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/teal.svg b/.venv/lib/python3.8/site-packages/pygame/examples/data/teal.svg new file mode 100644 index 0000000..85f4149 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/data/teal.svg @@ -0,0 +1,9 @@ + + teal + + + Layer 1 + + + + diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/turquoise.tif b/.venv/lib/python3.8/site-packages/pygame/examples/data/turquoise.tif new file mode 100644 index 0000000..39b3620 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/turquoise.tif differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/whiff.wav b/.venv/lib/python3.8/site-packages/pygame/examples/data/whiff.wav new file mode 100644 index 0000000..3954efa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/whiff.wav differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/data/yellow.tga b/.venv/lib/python3.8/site-packages/pygame/examples/data/yellow.tga new file mode 100644 index 0000000..d0124fe Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/examples/data/yellow.tga differ diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/dropevent.py b/.venv/lib/python3.8/site-packages/pygame/examples/dropevent.py new file mode 100644 index 0000000..e812558 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/dropevent.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +""" pygame.examples.dropfile + +Drag and drop an image on here. + +Uses these events: + +* DROPBEGIN +* DROPCOMPLETE +* DROPTEXT +* DROPFILE +""" +import pygame as pg + +if pg.get_sdl_version() < (2, 0, 0): + raise Exception("This example requires SDL2.") + +pg.init() + + +def main(): + + Running = True + surf = pg.display.set_mode((640, 480)) + font = pg.font.SysFont("Arial", 24) + clock = pg.time.Clock() + + spr_file_text = font.render("Feed me some file or image!", 1, (255, 255, 255)) + spr_file_text_rect = spr_file_text.get_rect() + spr_file_text_rect.center = surf.get_rect().center + + spr_file_image = None + spr_file_image_rect = None + + while Running: + for ev in pg.event.get(): + if ev.type == pg.QUIT: + Running = False + elif ev.type == pg.DROPBEGIN: + print(ev) + print("File drop begin!") + elif ev.type == pg.DROPCOMPLETE: + print(ev) + print("File drop complete!") + elif ev.type == pg.DROPTEXT: + print(ev) + spr_file_text = font.render(ev.text, 1, (255, 255, 255)) + spr_file_text_rect = spr_file_text.get_rect() + spr_file_text_rect.center = surf.get_rect().center + elif ev.type == pg.DROPFILE: + print(ev) + spr_file_text = font.render(ev.file, 1, (255, 255, 255)) + spr_file_text_rect = spr_file_text.get_rect() + spr_file_text_rect.center = surf.get_rect().center + + # Try to open the file if it's an image + filetype = ev.file[-3:] + if filetype in ["png", "bmp", "jpg"]: + spr_file_image = pg.image.load(ev.file).convert() + spr_file_image.set_alpha(127) + spr_file_image_rect = spr_file_image.get_rect() + spr_file_image_rect.center = surf.get_rect().center + + surf.fill((0, 0, 0)) + surf.blit(spr_file_text, spr_file_text_rect) + if spr_file_image: + surf.blit(spr_file_image, spr_file_image_rect) + + pg.display.flip() + clock.tick(30) + + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/eventlist.py b/.venv/lib/python3.8/site-packages/pygame/examples/eventlist.py new file mode 100644 index 0000000..b329586 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/eventlist.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +""" pygame.examples.eventlist + +Learn about pygame events and input. + +At the top of the screen are the state of several device values, +and a scrolling list of events are displayed on the bottom. + +""" + +usage = """ +Mouse Controls +============== + +- 1st button on mouse (left click) to toggle events 'grabed'. +- 3rd button on mouse (right click) to toggle mouse visible. +- The window can be resized. +- Mouse the mouse around to see mouse events. +- If events grabbed and mouse invisible show virtual mouse coords. + + +Keyboard Joystick Controls +========================== + +- press keys up an down to see events. +- you can see joystick events if any are plugged in. +- press "c" to toggle events generated by controllers. +""" + +import pygame as pg + +try: + import pygame._sdl2.controller + + pygame._sdl2.controller.init() + SDL2 = True +except ImportError: + SDL2 = False + +img_on_off = [] +font = None +last_key = None + +# these are a running counter of mouse.get_rel() calls. +virtual_x = 0 +virtual_y = 0 + + +def showtext(win, pos, text, color, bgcolor): + textimg = font.render(text, 1, color, bgcolor) + win.blit(textimg, pos) + return pos[0] + textimg.get_width() + 5, pos[1] + + +def drawstatus(win): + global virtual_x, virtual_y + bgcolor = 50, 50, 50 + win.fill(bgcolor, (0, 0, 640, 120)) + win.blit(font.render("Status Area", 1, (155, 155, 155), bgcolor), (2, 2)) + + pos = showtext(win, (10, 30), "Mouse Focus", (255, 255, 255), bgcolor) + win.blit(img_on_off[pg.mouse.get_focused()], pos) + + pos = showtext( + win, (pos[0] + 50, pos[1]), "Mouse visible", (255, 255, 255), bgcolor + ) + win.blit(img_on_off[pg.mouse.get_visible()], pos) + + pos = showtext(win, (330, 30), "Keyboard Focus", (255, 255, 255), bgcolor) + win.blit(img_on_off[pg.key.get_focused()], pos) + + pos = showtext(win, (10, 60), "Mouse Position(rel)", (255, 255, 255), bgcolor) + rel = pg.mouse.get_rel() + virtual_x += rel[0] + virtual_y += rel[1] + + mouse_data = tuple(list(pg.mouse.get_pos()) + list(rel)) + p = "%s, %s (%s, %s)" % mouse_data + showtext(win, pos, p, bgcolor, (255, 255, 55)) + + pos = showtext(win, (330, 60), "Last Keypress", (255, 255, 255), bgcolor) + if last_key: + p = "%d, %s" % (last_key, pg.key.name(last_key)) + else: + p = "None" + showtext(win, pos, p, bgcolor, (255, 255, 55)) + + pos = showtext(win, (10, 90), "Input Grabbed", (255, 255, 255), bgcolor) + win.blit(img_on_off[pg.event.get_grab()], pos) + + is_virtual_mouse = pg.event.get_grab() and not pg.mouse.get_visible() + pos = showtext(win, (330, 90), "Virtual Mouse", (255, 255, 255), bgcolor) + win.blit(img_on_off[is_virtual_mouse], pos) + if is_virtual_mouse: + p = "%s, %s" % (virtual_x, virtual_y) + showtext(win, (pos[0] + 50, pos[1]), p, bgcolor, (255, 255, 55)) + + +def drawhistory(win, history): + img = font.render("Event History Area", 1, (155, 155, 155), (0, 0, 0)) + win.blit(img, (2, 132)) + ypos = 450 + h = list(history) + h.reverse() + for line in h: + r = win.blit(line, (10, ypos)) + win.fill(0, (r.right, r.top, 620, r.height)) + ypos -= font.get_height() + + +def draw_usage_in_history(history, text): + lines = text.split("\n") + for line in lines: + if line == "" or "===" in line: + continue + img = font.render(line, 1, (50, 200, 50), (0, 0, 0)) + history.append(img) + + +def main(): + pg.init() + print(usage) + + win = pg.display.set_mode((640, 480), pg.RESIZABLE) + pg.display.set_caption("Mouse Focus Workout. h key for help") + + global font + font = pg.font.Font(None, 26) + + global img_on_off + img_on_off.append(font.render("Off", 1, (0, 0, 0), (255, 50, 50))) + img_on_off.append(font.render("On", 1, (0, 0, 0), (50, 255, 50))) + + # stores surfaces of text representing what has gone through the event queue + history = [] + + # let's turn on the joysticks just so we can play with em + for x in range(pg.joystick.get_count()): + if SDL2 and pg._sdl2.controller.is_controller(x): + c = pg._sdl2.controller.Controller(x) + txt = "Enabled controller: " + c.name + else: + j = pg.joystick.Joystick(x) + txt = "Enabled joystick: " + j.get_name() + + img = font.render(txt, 1, (50, 200, 50), (0, 0, 0)) + history.append(img) + if not pg.joystick.get_count(): + img = font.render("No Joysticks to Initialize", 1, (50, 200, 50), (0, 0, 0)) + history.append(img) + + going = True + while going: + for e in pg.event.get(): + if e.type == pg.KEYDOWN: + if e.key == pg.K_ESCAPE: + going = False + else: + global last_key + last_key = e.key + if e.key == pg.K_h: + draw_usage_in_history(history, usage) + if SDL2 and e.key == pg.K_c: + current_state = pg._sdl2.controller.get_eventstate() + pg._sdl2.controller.set_eventstate(not current_state) + + if e.type == pg.MOUSEBUTTONDOWN and e.button == 1: + pg.event.set_grab(not pg.event.get_grab()) + + if e.type == pg.MOUSEBUTTONDOWN and e.button == 3: + pg.mouse.set_visible(not pg.mouse.get_visible()) + + if e.type != pg.MOUSEMOTION: + txt = "%s: %s" % (pg.event.event_name(e.type), e.dict) + img = font.render(txt, 1, (50, 200, 50), (0, 0, 0)) + history.append(img) + history = history[-13:] + + if e.type == pg.VIDEORESIZE: + win = pg.display.set_mode(e.size, pg.RESIZABLE) + + if e.type == pg.QUIT: + going = False + + drawstatus(win) + drawhistory(win, history) + + pg.display.flip() + pg.time.wait(10) + + pg.quit() + raise SystemExit + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/font_viewer.py b/.venv/lib/python3.8/site-packages/pygame/examples/font_viewer.py new file mode 100644 index 0000000..7170fd4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/font_viewer.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python +""" pygame.examples.font_viewer +Scroll through your system fonts from a list of surfaces or one huge buffer. + +This example exhibits: +* iterate over available fonts using font.get_fonts and font.SysFont() +* click and drag using mouse input +* scrolling with the scroll wheel +* save a surface to disk +* work with a very large surface +* simple mouse and keyboard scroll speed acceleration + +By default this example uses the fonts returned by pygame.font.get_fonts() +and opens them using pygame.font.SysFont(). +Alternatively, you may pass a path to the command line. The TTF files found +in that directory will be used instead. + +Mouse Controls: +* Use the mouse wheel or click and drag to scroll + +Keyboard Controls: +* Press up or down to scroll +* Press escape to exit +""" +import sys +import os + +import pygame as pg + +use_big_surface = False # draw into large buffer and save png file + + +class FontViewer: + """ + This example is encapsulated by the fontviewer class + It initializes the pygame window, handles input, and draws itself + to the screen. + """ + + KEY_SCROLL_SPEED = 10 + MOUSE_SCROLL_SPEED = 50 + + def __init__(self, **dparams): + pg.init() + self.font_dir = dparams.get("folder", None) + + # create a window that uses 80 percent of the screen + info = pg.display.Info() + w = info.current_w + h = info.current_h + pg.display.set_mode((int(w * 0.8), int(h * 0.8))) + self.font_size = h // 20 + + self.clock = pg.time.Clock() + self.y_offset = 0 + self.grabbed = False + self.render_fonts("&N abcDEF789") + + if use_big_surface or "big" in sys.argv: + self.render_surface() + self.display_surface() + self.save_png() + else: + self.display_fonts() + + def get_font_list(self): + """ + Generate a font list using font.get_fonts() for system fonts or + from a path from the command line. + """ + path = "" + if len(sys.argv) > 1 and os.path.exists(sys.argv[1]): + path = os.path.join(sys.argv[1], "") + fonts = [] + if os.path.exists(path): + # this list comprehension could replace the following loop + # fonts = [f in os.listdir(path) if f.endswith('.ttf')] + for font in os.listdir(path): + if font.endswith(".ttf"): + fonts.append(font) + return fonts or pg.font.get_fonts(), path + + def render_fonts(self, text="A display of font &N", **dparams): + """ + Build a list that includes a surface and the running total of their + height for each font in the font list. Store the largest width and + other variables for later use. + """ + font_size = dparams.get("size", 0) or self.font_size + color = dparams.get("color", (255, 255, 255)) + self.back_color = dparams.get("back_color", (0, 0, 0)) + + fonts, path = self.get_font_list() + font_surfaces = [] + total_height = 0 + max_width = 0 + + load_font = pg.font.Font if path else pg.font.SysFont + + # display instructions at the top of the display + font = pg.font.SysFont(pg.font.get_default_font(), font_size) + lines = ( + "Use the scroll wheel or click and drag", + "to scroll up and down", + "Foreign fonts might render incorrectly", + "Here are your {} fonts".format(len(fonts)), + "", + ) + for line in lines: + surf = font.render(line, 1, color, self.back_color) + font_surfaces.append((surf, total_height)) + total_height += surf.get_height() + max_width = max(max_width, surf.get_width()) + + # render all the fonts and store them with the total height + for name in sorted(fonts): + try: + font = load_font(path + name, font_size) + except IOError: + continue + line = text.replace("&N", name) + try: + surf = font.render(line, 1, color, self.back_color) + except pg.error as e: + print(e) + break + + max_width = max(max_width, surf.get_width()) + font_surfaces.append((surf, total_height)) + total_height += surf.get_height() + + # store variables for later usage + self.total_height = total_height + self.max_width = max_width + self.font_surfaces = font_surfaces + self.max_y = total_height - pg.display.get_surface().get_height() + + def display_fonts(self): + """ + Display the visible fonts based on the y_offset value(updated in + handle_events) and the height of the pygame window. + """ + display = pg.display.get_surface() + clock = pg.time.Clock() + center = display.get_width() // 2 + + while True: + # draw visible surfaces + display.fill(self.back_color) + for surface, top in self.font_surfaces: + bottom = top + surface.get_height() + if ( + bottom >= self.y_offset + and top <= self.y_offset + display.get_height() + ): + x = center - surface.get_width() // 2 + display.blit(surface, (x, top - self.y_offset)) + # get input and update the screen + if not self.handle_events(): + break + pg.display.flip() + clock.tick(30) + + def render_surface(self): + """ + Note: this method uses twice the memory and is only called if + big_surface is set to true or big is added to the command line. + + Optionally generates one large buffer to draw all the font surfaces + into. This is necessary to save the display to a png file and may + be useful for testing large surfaces. + """ + + large_surface = pg.surface.Surface( + (self.max_width, self.total_height) + ).convert() + large_surface.fill(self.back_color) + print("scrolling surface created") + + # display the surface size and memory usage + byte_size = large_surface.get_bytesize() + total_size = byte_size * (self.max_width * self.total_height) + print( + "Surface Size = {}x{} @ {}bpp: {:,.3f}mb".format( + self.max_width, self.total_height, byte_size, total_size / 1000000.0 + ) + ) + + y = 0 + center = int(self.max_width / 2) + for surface, top in self.font_surfaces: + w = surface.get_width() + x = center - int(w / 2) + large_surface.blit(surface, (x, y)) + y += surface.get_height() + self.max_y = large_surface.get_height() - pg.display.get_surface().get_height() + self.surface = large_surface + + def display_surface(self, time=10): + """ + Display the large surface created by the render_surface method. Scrolls + based on the y_offset value(set in handle_events) and the height of the + pygame window. + """ + screen = pg.display.get_surface() + + # Create a Rect equal to size of screen. Then we can just change its + # top attribute to draw the desired part of the rendered font surface + # to the display surface + rect = pg.rect.Rect( + 0, + 0, + self.surface.get_width(), + min(self.surface.get_height(), screen.get_height()), + ) + + x = int((screen.get_width() - self.surface.get_width()) / 2) + going = True + while going: + if not self.handle_events(): + going = False + screen.fill(self.back_color) + rect.top = self.y_offset + screen.blit(self.surface, (x, 0), rect) + pg.display.flip() + self.clock.tick(20) + + def save_png(self, name="font_viewer.png"): + pg.image.save(self.surface, name) + file_size = os.path.getsize(name) // 1024 + print("font surface saved to {}\nsize: {:,}Kb".format(name, file_size)) + + def handle_events(self): + """ + This method handles user input. It returns False when it receives + a pygame.QUIT event or the user presses escape. The y_offset is + changed based on mouse and keyboard input. display_fonts() and + display_surface() use the y_offset to scroll display. + """ + events = pg.event.get() + for e in events: + if e.type == pg.QUIT: + return False + elif e.type == pg.KEYDOWN: + if e.key == pg.K_ESCAPE: + return False + elif e.type == pg.MOUSEWHEEL: + self.y_offset += e.y * self.MOUSE_SCROLL_SPEED * -1 + elif e.type == pg.MOUSEBUTTONDOWN: + # enter dragging mode on mouse down + self.grabbed = True + pg.event.set_grab(True) + elif e.type == pg.MOUSEBUTTONUP: + # exit drag mode on mouse up + self.grabbed = False + pg.event.set_grab(False) + + # allow simple accelerated scrolling with the keyboard + keys = pg.key.get_pressed() + if keys[pg.K_UP]: + self.key_held += 1 + self.y_offset -= int(self.KEY_SCROLL_SPEED * (self.key_held // 10)) + elif keys[pg.K_DOWN]: + self.key_held += 1 + self.y_offset += int(self.KEY_SCROLL_SPEED * (self.key_held // 10)) + else: + self.key_held = 20 + + # set the y_offset for scrolling and keep it between 0 and max_y + y = pg.mouse.get_rel()[1] + if y and self.grabbed: + self.y_offset -= y + + self.y_offset = min((max(self.y_offset, 0), self.max_y)) + return True + + +viewer = FontViewer() +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/fonty.py b/.venv/lib/python3.8/site-packages/pygame/examples/fonty.py new file mode 100644 index 0000000..3eed676 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/fonty.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +""" pygame.examples.fonty + +Here we load a .TTF True Type font file, and display it in +a basic pygame window. + +Demonstrating several Font object attributes. + +- basic window, event, and font management. +""" +import pygame as pg + +def main(): + # initialize + pg.init() + resolution = 400, 200 + screen = pg.display.set_mode(resolution) + + ## pg.mouse.set_cursor(*pg.cursors.diamond) + + fg = 250, 240, 230 + bg = 5, 5, 5 + wincolor = 40, 40, 90 + + # fill background + screen.fill(wincolor) + + # load font, prepare values + font = pg.font.Font(None, 80) + text = "Fonty" + size = font.size(text) + + # no AA, no transparancy, normal + ren = font.render(text, 0, fg, bg) + screen.blit(ren, (10, 10)) + + # no AA, transparancy, underline + font.set_underline(1) + ren = font.render(text, 0, fg) + screen.blit(ren, (10, 40 + size[1])) + font.set_underline(0) + + a_sys_font = pg.font.SysFont("Arial", 60) + + # AA, no transparancy, bold + a_sys_font.set_bold(1) + ren = a_sys_font.render(text, 1, fg, bg) + screen.blit(ren, (30 + size[0], 10)) + a_sys_font.set_bold(0) + + # AA, transparancy, italic + a_sys_font.set_italic(1) + ren = a_sys_font.render(text, 1, fg) + screen.blit(ren, (30 + size[0], 40 + size[1])) + a_sys_font.set_italic(0) + + # Get some metrics. + print("Font metrics for 'Fonty': %s" % a_sys_font.metrics(text)) + ch = "\u3060" + msg = "Font metrics for '%s': %s" % (ch, a_sys_font.metrics(ch)) + print(msg) + + ## #some_japanese_unicode = u"\u304b\u3070\u306b" + ##some_japanese_unicode = unicode_('%c%c%c') % (0x304b, 0x3070, 0x306b) + + # AA, transparancy, italic + ##ren = a_sys_font.render(some_japanese_unicode, 1, fg) + ##screen.blit(ren, (30 + size[0], 40 + size[1])) + + # show the surface and await user quit + pg.display.flip() + while True: + # use event.wait to keep from polling 100% cpu + if pg.event.wait().type in (pg.QUIT, pg.KEYDOWN, pg.MOUSEBUTTONDOWN): + break + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/freetype_misc.py b/.venv/lib/python3.8/site-packages/pygame/examples/freetype_misc.py new file mode 100644 index 0000000..d07c422 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/freetype_misc.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +""" pygame.examples.freetype_misc + + +Miscellaneous (or misc) means: + "consisting of a mixture of various things that are not + usually connected with each other" + Adjective + + +All those words you read on computers, magazines, books, and such over the years? +Probably a lot of them were constructed with... + +The FreeType Project: a free, high-quality and portable Font engine. +https://freetype.org + +Next time you're reading something. Think of them. + + +Herein lies a *BOLD* demo consisting of a mixture of various things. + + Not only is it a *BOLD* demo, it's an + italics demo, + a rotated demo, + it's a blend, + and is sized to go nicely with a cup of tea*. + + * also goes well with coffee. + +Enjoy! +""" +import os +import pygame as pg +import pygame.freetype as freetype + + +def run(): + pg.init() + + fontdir = os.path.dirname(os.path.abspath(__file__)) + font = freetype.Font(os.path.join(fontdir, "data", "sans.ttf")) + + screen = pg.display.set_mode((800, 600)) + screen.fill("gray") + + font.underline_adjustment = 0.5 + font.pad = True + font.render_to( + screen, + (32, 32), + "Hello World", + "red3", + "dimgray", + size=64, + style=freetype.STYLE_UNDERLINE | freetype.STYLE_OBLIQUE, + ) + font.pad = False + + font.render_to( + screen, + (32, 128), + "abcdefghijklm", + "dimgray", + "green3", + size=64, + ) + + font.vertical = True + font.render_to(screen, (32, 200), "Vertical?", "blue3", None, size=32) + font.vertical = False + + font.render_to(screen, (64, 190), "Let's spin!", "red3", None, size=48, rotation=55) + + font.render_to( + screen, (160, 290), "All around!", "green3", None, size=48, rotation=-55 + ) + + font.render_to(screen, (250, 220), "and BLEND", (255, 0, 0, 128), None, size=64) + + font.render_to(screen, (265, 237), "or BLAND!", (0, 0xCC, 28, 128), None, size=64) + + # Some pinwheels + font.origin = True + for angle in range(0, 360, 45): + font.render_to(screen, (150, 420), ")", "black", size=48, rotation=angle) + font.vertical = True + for angle in range(15, 375, 30): + font.render_to(screen, (600, 400), "|^*", "orange", size=48, rotation=angle) + font.vertical = False + font.origin = False + + utext = "I \u2665 Unicode" + font.render_to(screen, (298, 320), utext, (0, 0xCC, 0xDD), None, size=64) + + utext = "\u2665" + font.render_to(screen, (480, 32), utext, "gray", "red3", size=148) + + font.render_to( + screen, + (380, 380), + "...yes, this is an SDL surface", + "black", + None, + size=24, + style=freetype.STYLE_STRONG, + ) + + font.origin = True + r = font.render_to( + screen, + (100, 530), + "stretch", + "red3", + None, + size=(24, 24), + style=freetype.STYLE_NORMAL, + ) + font.render_to( + screen, + (100 + r.width, 530), + " VERTICAL", + "red3", + None, + size=(24, 48), + style=freetype.STYLE_NORMAL, + ) + + r = font.render_to( + screen, + (100, 580), + "stretch", + "blue3", + None, + size=(24, 24), + style=freetype.STYLE_NORMAL, + ) + font.render_to( + screen, + (100 + r.width, 580), + " HORIZONTAL", + "blue3", + None, + size=(48, 24), + style=freetype.STYLE_NORMAL, + ) + + pg.display.flip() + + while 1: + if pg.event.wait().type in (pg.QUIT, pg.KEYDOWN, pg.MOUSEBUTTONDOWN): + break + + pg.quit() + + +if __name__ == "__main__": + run() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/glcube.py b/.venv/lib/python3.8/site-packages/pygame/examples/glcube.py new file mode 100644 index 0000000..9d85c24 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/glcube.py @@ -0,0 +1,591 @@ +#!/usr/bin/env python +""" pygame.examples.glcube + +Draw a cube on the screen. + + + +Amazing. + +Every frame we orbit the camera around a small amount +creating the illusion of a spinning object. + +First we setup some points of a multicolored cube. Then we then go through +a semi-unoptimized loop to draw the cube points onto the screen. + +OpenGL does all the hard work for us. :] + + +Keyboard Controls +----------------- + +* ESCAPE key to quit +* f key to toggle fullscreen. + +""" +import math +import ctypes + +import pygame as pg + +try: + import OpenGL.GL as GL + import OpenGL.GLU as GLU +except ImportError: + print("pyopengl missing. The GLCUBE example requires: pyopengl numpy") + raise SystemExit + +try: + from numpy import array, dot, eye, zeros, float32, uint32 +except ImportError: + print("numpy missing. The GLCUBE example requires: pyopengl numpy") + raise SystemExit + + +# do we want to use the 'modern' OpenGL API or the old one? +# This example shows you how to do both. +USE_MODERN_GL = True + +# Some simple data for a colored cube here we have the 3D point position +# and color for each corner. A list of indices describes each face, and a +# list of indices describes each edge. + +CUBE_POINTS = ( + (0.5, -0.5, -0.5), + (0.5, 0.5, -0.5), + (-0.5, 0.5, -0.5), + (-0.5, -0.5, -0.5), + (0.5, -0.5, 0.5), + (0.5, 0.5, 0.5), + (-0.5, -0.5, 0.5), + (-0.5, 0.5, 0.5), +) + +# colors are 0-1 floating values +CUBE_COLORS = ( + (1, 0, 0), + (1, 1, 0), + (0, 1, 0), + (0, 0, 0), + (1, 0, 1), + (1, 1, 1), + (0, 0, 1), + (0, 1, 1), +) + +CUBE_QUAD_VERTS = ( + (0, 1, 2, 3), + (3, 2, 7, 6), + (6, 7, 5, 4), + (4, 5, 1, 0), + (1, 5, 7, 2), + (4, 0, 3, 6), +) + +CUBE_EDGES = ( + (0, 1), + (0, 3), + (0, 4), + (2, 1), + (2, 3), + (2, 7), + (6, 3), + (6, 4), + (6, 7), + (5, 1), + (5, 4), + (5, 7), +) + + +def translate(matrix, x=0.0, y=0.0, z=0.0): + """ + Translate (move) a matrix in the x, y and z axes. + + :param matrix: Matrix to translate. + :param x: direction and magnitude to translate in x axis. Defaults to 0. + :param y: direction and magnitude to translate in y axis. Defaults to 0. + :param z: direction and magnitude to translate in z axis. Defaults to 0. + :return: The translated matrix. + """ + translation_matrix = array( + [ + [1.0, 0.0, 0.0, x], + [0.0, 1.0, 0.0, y], + [0.0, 0.0, 1.0, z], + [0.0, 0.0, 0.0, 1.0], + ], + dtype=matrix.dtype, + ).T + matrix[...] = dot(matrix, translation_matrix) + return matrix + + +def frustum(left, right, bottom, top, znear, zfar): + """ + Build a perspective matrix from the clipping planes, or camera 'frustrum' + volume. + + :param left: left position of the near clipping plane. + :param right: right position of the near clipping plane. + :param bottom: bottom position of the near clipping plane. + :param top: top position of the near clipping plane. + :param znear: z depth of the near clipping plane. + :param zfar: z depth of the far clipping plane. + + :return: A perspective matrix. + """ + perspective_matrix = zeros((4, 4), dtype=float32) + perspective_matrix[0, 0] = +2.0 * znear / (right - left) + perspective_matrix[2, 0] = (right + left) / (right - left) + perspective_matrix[1, 1] = +2.0 * znear / (top - bottom) + perspective_matrix[3, 1] = (top + bottom) / (top - bottom) + perspective_matrix[2, 2] = -(zfar + znear) / (zfar - znear) + perspective_matrix[3, 2] = -2.0 * znear * zfar / (zfar - znear) + perspective_matrix[2, 3] = -1.0 + return perspective_matrix + + +def perspective(fovy, aspect, znear, zfar): + """ + Build a perspective matrix from field of view, aspect ratio and depth + planes. + + :param fovy: the field of view angle in the y axis. + :param aspect: aspect ratio of our view port. + :param znear: z depth of the near clipping plane. + :param zfar: z depth of the far clipping plane. + + :return: A perspective matrix. + """ + h = math.tan(fovy / 360.0 * math.pi) * znear + w = h * aspect + return frustum(-w, w, -h, h, znear, zfar) + + +def rotate(matrix, angle, x, y, z): + """ + Rotate a matrix around an axis. + + :param matrix: The matrix to rotate. + :param angle: The angle to rotate by. + :param x: x of axis to rotate around. + :param y: y of axis to rotate around. + :param z: z of axis to rotate around. + + :return: The rotated matrix + """ + angle = math.pi * angle / 180 + c, s = math.cos(angle), math.sin(angle) + n = math.sqrt(x * x + y * y + z * z) + x, y, z = x / n, y / n, z / n + cx, cy, cz = (1 - c) * x, (1 - c) * y, (1 - c) * z + rotation_matrix = array( + [ + [cx * x + c, cy * x - z * s, cz * x + y * s, 0], + [cx * y + z * s, cy * y + c, cz * y - x * s, 0], + [cx * z - y * s, cy * z + x * s, cz * z + c, 0], + [0, 0, 0, 1], + ], + dtype=matrix.dtype, + ).T + matrix[...] = dot(matrix, rotation_matrix) + return matrix + + +class Rotation: + """ + Data class that stores rotation angles in three axes. + """ + + def __init__(self): + self.theta = 20 + self.phi = 40 + self.psi = 25 + + +def drawcube_old(): + """ + Draw the cube using the old open GL methods pre 3.2 core context. + """ + allpoints = list(zip(CUBE_POINTS, CUBE_COLORS)) + + GL.glBegin(GL.GL_QUADS) + for face in CUBE_QUAD_VERTS: + for vert in face: + pos, color = allpoints[vert] + GL.glColor3fv(color) + GL.glVertex3fv(pos) + GL.glEnd() + + GL.glColor3f(1.0, 1.0, 1.0) + GL.glBegin(GL.GL_LINES) + for line in CUBE_EDGES: + for vert in line: + pos, color = allpoints[vert] + GL.glVertex3fv(pos) + + GL.glEnd() + + +def init_gl_stuff_old(): + """ + Initialise open GL, prior to core context 3.2 + """ + GL.glEnable(GL.GL_DEPTH_TEST) # use our zbuffer + + # setup the camera + GL.glMatrixMode(GL.GL_PROJECTION) + GL.glLoadIdentity() + GLU.gluPerspective(45.0, 640 / 480.0, 0.1, 100.0) # setup lens + GL.glTranslatef(0.0, 0.0, -3.0) # move back + GL.glRotatef(25, 1, 0, 0) # orbit higher + + +def init_gl_modern(display_size): + """ + Initialise open GL in the 'modern' open GL style for open GL versions + greater than 3.1. + + :param display_size: Size of the window/viewport. + """ + + # Create shaders + # -------------------------------------- + vertex_code = """ + + #version 150 + uniform mat4 model; + uniform mat4 view; + uniform mat4 projection; + + uniform vec4 colour_mul; + uniform vec4 colour_add; + + in vec4 vertex_colour; // vertex colour in + in vec3 vertex_position; + + out vec4 vertex_color_out; // vertex colour out + void main() + { + vertex_color_out = (colour_mul * vertex_colour) + colour_add; + gl_Position = projection * view * model * vec4(vertex_position, 1.0); + } + + """ + + fragment_code = """ + #version 150 + in vec4 vertex_color_out; // vertex colour from vertex shader + out vec4 fragColor; + void main() + { + fragColor = vertex_color_out; + } + """ + + program = GL.glCreateProgram() + vertex = GL.glCreateShader(GL.GL_VERTEX_SHADER) + fragment = GL.glCreateShader(GL.GL_FRAGMENT_SHADER) + GL.glShaderSource(vertex, vertex_code) + GL.glCompileShader(vertex) + + # this logs issues the shader compiler finds. + log = GL.glGetShaderInfoLog(vertex) + if isinstance(log, bytes): + log = log.decode() + for line in log.split("\n"): + print(line) + + GL.glAttachShader(program, vertex) + GL.glShaderSource(fragment, fragment_code) + GL.glCompileShader(fragment) + + # this logs issues the shader compiler finds. + log = GL.glGetShaderInfoLog(fragment) + if isinstance(log, bytes): + log = log.decode() + for line in log.split("\n"): + print(line) + + GL.glAttachShader(program, fragment) + GL.glValidateProgram(program) + GL.glLinkProgram(program) + + GL.glDetachShader(program, vertex) + GL.glDetachShader(program, fragment) + GL.glUseProgram(program) + + # Create vertex buffers and shader constants + # ------------------------------------------ + + # Cube Data + vertices = zeros( + 8, [("vertex_position", float32, 3), ("vertex_colour", float32, 4)] + ) + + vertices["vertex_position"] = [ + [1, 1, 1], + [-1, 1, 1], + [-1, -1, 1], + [1, -1, 1], + [1, -1, -1], + [1, 1, -1], + [-1, 1, -1], + [-1, -1, -1], + ] + + vertices["vertex_colour"] = [ + [0, 1, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 1, 0, 1], + [1, 1, 0, 1], + [1, 1, 1, 1], + [1, 0, 1, 1], + [1, 0, 0, 1], + ] + + filled_cube_indices = array( + [ + 0, + 1, + 2, + 0, + 2, + 3, + 0, + 3, + 4, + 0, + 4, + 5, + 0, + 5, + 6, + 0, + 6, + 1, + 1, + 6, + 7, + 1, + 7, + 2, + 7, + 4, + 3, + 7, + 3, + 2, + 4, + 7, + 6, + 4, + 6, + 5, + ], + dtype=uint32, + ) + + outline_cube_indices = array( + [0, 1, 1, 2, 2, 3, 3, 0, 4, 7, 7, 6, 6, 5, 5, 4, 0, 5, 1, 6, 2, 7, 3, 4], + dtype=uint32, + ) + + shader_data = {"buffer": {}, "constants": {}} + + GL.glBindVertexArray(GL.glGenVertexArrays(1)) # Have to do this first + + shader_data["buffer"]["vertices"] = GL.glGenBuffers(1) + GL.glBindBuffer(GL.GL_ARRAY_BUFFER, shader_data["buffer"]["vertices"]) + GL.glBufferData(GL.GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL.GL_DYNAMIC_DRAW) + + stride = vertices.strides[0] + offset = ctypes.c_void_p(0) + + loc = GL.glGetAttribLocation(program, "vertex_position") + GL.glEnableVertexAttribArray(loc) + GL.glVertexAttribPointer(loc, 3, GL.GL_FLOAT, False, stride, offset) + + offset = ctypes.c_void_p(vertices.dtype["vertex_position"].itemsize) + + loc = GL.glGetAttribLocation(program, "vertex_colour") + GL.glEnableVertexAttribArray(loc) + GL.glVertexAttribPointer(loc, 4, GL.GL_FLOAT, False, stride, offset) + + shader_data["buffer"]["filled"] = GL.glGenBuffers(1) + GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["filled"]) + GL.glBufferData( + GL.GL_ELEMENT_ARRAY_BUFFER, + filled_cube_indices.nbytes, + filled_cube_indices, + GL.GL_STATIC_DRAW, + ) + + shader_data["buffer"]["outline"] = GL.glGenBuffers(1) + GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["outline"]) + GL.glBufferData( + GL.GL_ELEMENT_ARRAY_BUFFER, + outline_cube_indices.nbytes, + outline_cube_indices, + GL.GL_STATIC_DRAW, + ) + + shader_data["constants"]["model"] = GL.glGetUniformLocation(program, "model") + GL.glUniformMatrix4fv(shader_data["constants"]["model"], 1, False, eye(4)) + + shader_data["constants"]["view"] = GL.glGetUniformLocation(program, "view") + view = translate(eye(4), z=-6) + GL.glUniformMatrix4fv(shader_data["constants"]["view"], 1, False, view) + + shader_data["constants"]["projection"] = GL.glGetUniformLocation( + program, "projection" + ) + GL.glUniformMatrix4fv(shader_data["constants"]["projection"], 1, False, eye(4)) + + # This colour is multiplied with the base vertex colour in producing + # the final output + shader_data["constants"]["colour_mul"] = GL.glGetUniformLocation( + program, "colour_mul" + ) + GL.glUniform4f(shader_data["constants"]["colour_mul"], 1, 1, 1, 1) + + # This colour is added on to the base vertex colour in producing + # the final output + shader_data["constants"]["colour_add"] = GL.glGetUniformLocation( + program, "colour_add" + ) + GL.glUniform4f(shader_data["constants"]["colour_add"], 0, 0, 0, 0) + + # Set GL drawing data + # ------------------- + GL.glClearColor(0, 0, 0, 0) + GL.glPolygonOffset(1, 1) + GL.glEnable(GL.GL_LINE_SMOOTH) + GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) + GL.glDepthFunc(GL.GL_LESS) + GL.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST) + GL.glLineWidth(1.0) + + projection = perspective(45.0, display_size[0] / float(display_size[1]), 2.0, 100.0) + GL.glUniformMatrix4fv(shader_data["constants"]["projection"], 1, False, projection) + + return shader_data, filled_cube_indices, outline_cube_indices + + +def draw_cube_modern(shader_data, filled_cube_indices, outline_cube_indices, rotation): + """ + Draw a cube in the 'modern' Open GL style, for post 3.1 versions of + open GL. + + :param shader_data: compile vertex & pixel shader data for drawing a cube. + :param filled_cube_indices: the indices to draw the 'filled' cube. + :param outline_cube_indices: the indices to draw the 'outline' cube. + :param rotation: the current rotations to apply. + """ + + GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) + + # Filled cube + GL.glDisable(GL.GL_BLEND) + GL.glEnable(GL.GL_DEPTH_TEST) + GL.glEnable(GL.GL_POLYGON_OFFSET_FILL) + GL.glUniform4f(shader_data["constants"]["colour_mul"], 1, 1, 1, 1) + GL.glUniform4f(shader_data["constants"]["colour_add"], 0, 0, 0, 0.0) + GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["filled"]) + GL.glDrawElements( + GL.GL_TRIANGLES, len(filled_cube_indices), GL.GL_UNSIGNED_INT, None + ) + + # Outlined cube + GL.glDisable(GL.GL_POLYGON_OFFSET_FILL) + GL.glEnable(GL.GL_BLEND) + GL.glUniform4f(shader_data["constants"]["colour_mul"], 0, 0, 0, 0.0) + GL.glUniform4f(shader_data["constants"]["colour_add"], 1, 1, 1, 1.0) + GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, shader_data["buffer"]["outline"]) + GL.glDrawElements(GL.GL_LINES, len(outline_cube_indices), GL.GL_UNSIGNED_INT, None) + + # Rotate cube + # rotation.theta += 1.0 # degrees + rotation.phi += 1.0 # degrees + # rotation.psi += 1.0 # degrees + model = eye(4, dtype=float32) + # rotate(model, rotation.theta, 0, 0, 1) + rotate(model, rotation.phi, 0, 1, 0) + rotate(model, rotation.psi, 1, 0, 0) + GL.glUniformMatrix4fv(shader_data["constants"]["model"], 1, False, model) + + +def main(): + """run the demo""" + + # initialize pygame and setup an opengl display + pg.init() + + gl_version = (3, 0) # GL Version number (Major, Minor) + if USE_MODERN_GL: + gl_version = (3, 2) # GL Version number (Major, Minor) + + # By setting these attributes we can choose which Open GL Profile + # to use, profiles greater than 3.2 use a different rendering path + pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, gl_version[0]) + pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, gl_version[1]) + pg.display.gl_set_attribute( + pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE + ) + + fullscreen = False # start in windowed mode + + display_size = (640, 480) + pg.display.set_mode(display_size, pg.OPENGL | pg.DOUBLEBUF | pg.RESIZABLE) + + if USE_MODERN_GL: + gpu, f_indices, o_indices = init_gl_modern(display_size) + rotation = Rotation() + else: + init_gl_stuff_old() + + going = True + while going: + # check for quit'n events + events = pg.event.get() + for event in events: + if event.type == pg.QUIT or ( + event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE + ): + going = False + + elif event.type == pg.KEYDOWN and event.key == pg.K_f: + if not fullscreen: + print("Changing to FULLSCREEN") + pg.display.set_mode( + (640, 480), pg.OPENGL | pg.DOUBLEBUF | pg.FULLSCREEN + ) + else: + print("Changing to windowed mode") + pg.display.set_mode((640, 480), pg.OPENGL | pg.DOUBLEBUF) + fullscreen = not fullscreen + if gl_version[0] >= 4 or (gl_version[0] == 3 and gl_version[1] >= 2): + gpu, f_indices, o_indices = init_gl_modern(display_size) + rotation = Rotation() + else: + init_gl_stuff_old() + + if USE_MODERN_GL: + draw_cube_modern(gpu, f_indices, o_indices, rotation) + else: + # clear screen and move camera + GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) + # orbit camera around by 1 degree + GL.glRotatef(1, 0, 1, 0) + drawcube_old() + + pg.display.flip() + pg.time.wait(10) + + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/headless_no_windows_needed.py b/.venv/lib/python3.8/site-packages/pygame/examples/headless_no_windows_needed.py new file mode 100644 index 0000000..a74057c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/headless_no_windows_needed.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +""" pygame.examples.headless_no_windows_needed + +How to use pygame with no windowing system, like on headless servers. + +Thumbnail generation with scaling is an example of what you can do with pygame. +NOTE: the pygame scale function uses mmx/sse if available, and can be run + in multiple threads. +""" +useage = """-scale inputimage outputimage new_width new_height +eg. -scale in.png out.png 50 50 + +""" + +import os +import sys + +# set SDL to use the dummy NULL video driver, +# so it doesn't need a windowing system. +os.environ["SDL_VIDEODRIVER"] = "dummy" + +import pygame as pg + +# Some platforms need to init the display for some parts of pg. +pg.display.init() +screen = pg.display.set_mode((1, 1)) + + +def scaleit(fin, fout, w, h): + i = pg.image.load(fin) + + if hasattr(pg.transform, "smoothscale"): + scaled_image = pg.transform.smoothscale(i, (w, h)) + else: + scaled_image = pg.transform.scale(i, (w, h)) + pg.image.save(scaled_image, fout) + + +def main(fin, fout, w, h): + """smoothscale image file named fin as fout with new size (w,h)""" + scaleit(fin, fout, w, h) + + +if __name__ == "__main__": + if "-scale" in sys.argv: + fin, fout, w, h = sys.argv[2:] + w, h = map(int, [w, h]) + main(fin, fout, w, h) + else: + print(useage) diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/joystick.py b/.venv/lib/python3.8/site-packages/pygame/examples/joystick.py new file mode 100644 index 0000000..44cc7b8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/joystick.py @@ -0,0 +1,149 @@ +import pygame + +pygame.init() + +# This is a simple class that will help us print to the screen. +# It has nothing to do with the joysticks, just outputting the +# information. +class TextPrint(object): + def __init__(self): + self.reset() + self.font = pygame.font.Font(None, 20) + + def tprint(self, screen, text): + text_bitmap = self.font.render(text, True, "black") + screen.blit(text_bitmap, (self.x, self.y)) + self.y += self.line_height + + def reset(self): + self.x = 10 + self.y = 10 + self.line_height = 15 + + def indent(self): + self.x += 10 + + def unindent(self): + self.x -= 10 + + +def main(): + # Set the width and height of the screen (width, height), and name the window. + screen = pygame.display.set_mode((500, 700)) + pygame.display.set_caption("Joystick example") + + clock = pygame.time.Clock() + + # Get ready to print. + text_print = TextPrint() + + # This dict can be left as-is, since pygame will generate a + # pygame.JOYDEVICEADDED event for every joystick connected + # at the start of the program. + joysticks = {} + + done = False + while not done: + # Event processing step. + # Possible joystick events: JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN, + # JOYBUTTONUP, JOYHATMOTION, JOYDEVICEADDED, JOYDEVICEREMOVED + for event in pygame.event.get(): + if event.type == pygame.QUIT: + done = True # Flag that we are done so we exit this loop. + + if event.type == pygame.JOYBUTTONDOWN: + print("Joystick button pressed.") + if event.button == 0: + joystick = joysticks[event.instance_id] + if joystick.rumble(0, 0.7, 500): + print( + "Rumble effect played on joystick {}".format( + event.instance_id + ) + ) + + if event.type == pygame.JOYBUTTONUP: + print("Joystick button released.") + + # Handle hotplugging + if event.type == pygame.JOYDEVICEADDED: + # This event will be generated when the program starts for every + # joystick, filling up the list without needing to create them manually. + joy = pygame.joystick.Joystick(event.device_index) + joysticks[joy.get_instance_id()] = joy + print("Joystick {} connencted".format(joy.get_instance_id())) + + if event.type == pygame.JOYDEVICEREMOVED: + del joysticks[event.instance_id] + print("Joystick {} disconnected".format(event.instance_id)) + + # Drawing step + # First, clear the screen to white. Don't put other drawing commands + # above this, or they will be erased with this command. + screen.fill("white") + text_print.reset() + + text_print.tprint(screen, "Number of joysticks: {}".format(len(joysticks))) + text_print.indent() + + # For each joystick: + for joystick in joysticks.values(): + jid = joystick.get_instance_id() + text_print.tprint(screen, "Joystick {}".format(jid)) + text_print.indent() + + # Get the name from the OS for the joystick. + name = joystick.get_name() + text_print.tprint(screen, "Joystick name: {}".format(name)) + + guid = joystick.get_guid() + text_print.tprint(screen, "GUID: {}".format(guid)) + + power_level = joystick.get_power_level() + text_print.tprint(screen, "Joystick's power level: {}".format(power_level)) + + # Usually axis run in pairs, up/down for one, and left/right for + # the other. Triggers count as axes. + axes = joystick.get_numaxes() + text_print.tprint(screen, "Number of axes: {}".format(axes)) + text_print.indent() + + for i in range(axes): + axis = joystick.get_axis(i) + text_print.tprint(screen, "Axis {} value: {:>6.3f}".format(i, axis)) + text_print.unindent() + + buttons = joystick.get_numbuttons() + text_print.tprint(screen, "Number of buttons: {}".format(buttons)) + text_print.indent() + + for i in range(buttons): + button = joystick.get_button(i) + text_print.tprint(screen, "Button {:>2} value: {}".format(i, button)) + text_print.unindent() + + hats = joystick.get_numhats() + text_print.tprint(screen, "Number of hats: {}".format(hats)) + text_print.indent() + + # Hat position. All or nothing for direction, not a float like + # get_axis(). Position is a tuple of int values (x, y). + for i in range(hats): + hat = joystick.get_hat(i) + text_print.tprint(screen, "Hat {} value: {}".format(i, str(hat))) + text_print.unindent() + + text_print.unindent() + + # Go ahead and update the screen with what we've drawn. + pygame.display.flip() + + # Limit to 30 frames per second. + clock.tick(30) + + +if __name__ == "__main__": + main() + # If you forget this line, the program will 'hang' + # on exit if running from IDLE. + pygame.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/liquid.py b/.venv/lib/python3.8/site-packages/pygame/examples/liquid.py new file mode 100644 index 0000000..d55ba9c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/liquid.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +""" pygame.examples.liquid + +This example demonstrates a simplish water effect of an +image. It attempts to create a hardware display surface that +can use pageflipping for faster updates. Note that the colormap +from the loaded GIF image is copied to the colormap for the +display surface. + +This is based on the demo named F2KWarp by Brad Graham of Freedom2000 +done in BlitzBasic. I was just translating the BlitzBasic code to +pygame to compare the results. I didn't bother porting the text and +sound stuff, that's an easy enough challenge for the reader :] +""" + +import pygame as pg +import os +from math import sin +import time + +main_dir = os.path.split(os.path.abspath(__file__))[0] + + +def main(): + # initialize and setup screen + pg.init() + screen = pg.display.set_mode((640, 480), pg.HWSURFACE | pg.DOUBLEBUF) + + # load image and quadruple + imagename = os.path.join(main_dir, "data", "liquid.bmp") + bitmap = pg.image.load(imagename) + bitmap = pg.transform.scale2x(bitmap) + bitmap = pg.transform.scale2x(bitmap) + + # get the image and screen in the same format + if screen.get_bitsize() == 8: + screen.set_palette(bitmap.get_palette()) + else: + bitmap = bitmap.convert() + + # prep some variables + anim = 0.0 + + # mainloop + xblocks = range(0, 640, 20) + yblocks = range(0, 480, 20) + stopevents = pg.QUIT, pg.KEYDOWN, pg.MOUSEBUTTONDOWN + while 1: + for e in pg.event.get(): + if e.type in stopevents: + return + + anim = anim + 0.02 + for x in xblocks: + xpos = (x + (sin(anim + x * 0.01) * 15)) + 20 + for y in yblocks: + ypos = (y + (sin(anim + y * 0.01) * 15)) + 20 + screen.blit(bitmap, (x, y), (xpos, ypos, 20, 20)) + + pg.display.flip() + time.sleep(0.01) + + +if __name__ == "__main__": + main() + pg.quit() + + +"""BTW, here is the code from the BlitzBasic example this was derived +from. i've snipped the sound and text stuff out. +----------------------------------------------------------------- +; Brad@freedom2000.com + +; Load a bmp pic (800x600) and slice it into 1600 squares +Graphics 640,480 +SetBuffer BackBuffer() +bitmap$="f2kwarp.bmp" +pic=LoadAnimImage(bitmap$,20,15,0,1600) + +; use SIN to move all 1600 squares around to give liquid effect +Repeat +f=0:w=w+10:If w=360 Then w=0 +For y=0 To 599 Step 15 +For x = 0 To 799 Step 20 +f=f+1:If f=1600 Then f=0 +DrawBlock pic,(x+(Sin(w+x)*40))/1.7+80,(y+(Sin(w+y)*40))/1.7+60,f +Next:Next:Flip:Cls +Until KeyDown(1) +""" diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/mask.py b/.venv/lib/python3.8/site-packages/pygame/examples/mask.py new file mode 100644 index 0000000..dae3b2b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/mask.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +""" pygame.examples.mask + +A pygame.mask collision detection production. + + + + +Brought + + to + you + by + + the + +pixels + 0000000000000 + and + 111111 + + +This is 32 bits: + 11111111111111111111111111111111 + +There are 32 or 64 bits in a computer 'word'. +Rather than using one word for a pixel, +the mask module represents 32 or 64 pixels in one word. +As you can imagine, this makes things fast, and saves memory. + +Compute intensive things like collision detection, +and computer vision benefit greatly from this. + + +This module can also be run as a stand-alone program, excepting +one or more image file names as command line arguments. +""" + +import sys +import os +import random + +import pygame as pg + + +def maskFromSurface(surface, threshold=127): + return pg.mask.from_surface(surface, threshold) + + +def vadd(x, y): + return [x[0] + y[0], x[1] + y[1]] + + +def vsub(x, y): + return [x[0] - y[0], x[1] - y[1]] + + +def vdot(x, y): + return x[0] * y[0] + x[1] * y[1] + + +class Sprite: + def __init__(self, surface, mask=None): + self.surface = surface + if mask: + self.mask = mask + else: + self.mask = maskFromSurface(self.surface) + self.setPos([0, 0]) + self.setVelocity([0, 0]) + + def setPos(self, pos): + self.pos = [pos[0], pos[1]] + + def setVelocity(self, vel): + self.vel = [vel[0], vel[1]] + + def move(self, dr): + self.pos = vadd(self.pos, dr) + + def kick(self, impulse): + self.vel[0] += impulse[0] + self.vel[1] += impulse[1] + + def collide(self, s): + """Test if the sprites are colliding and + resolve the collision in this case.""" + offset = [int(x) for x in vsub(s.pos, self.pos)] + overlap = self.mask.overlap_area(s.mask, offset) + if overlap == 0: + return + """Calculate collision normal""" + nx = self.mask.overlap_area( + s.mask, (offset[0] + 1, offset[1]) + ) - self.mask.overlap_area(s.mask, (offset[0] - 1, offset[1])) + ny = self.mask.overlap_area( + s.mask, (offset[0], offset[1] + 1) + ) - self.mask.overlap_area(s.mask, (offset[0], offset[1] - 1)) + if nx == 0 and ny == 0: + """One sprite is inside another""" + return + n = [nx, ny] + dv = vsub(s.vel, self.vel) + J = vdot(dv, n) / (2 * vdot(n, n)) + if J > 0: + """Can scale up to 2*J here to get bouncy collisions""" + J *= 1.9 + self.kick([nx * J, ny * J]) + s.kick([-J * nx, -J * ny]) + return + + # """Separate the sprites""" + # c1 = -overlap/vdot(n,n) + # c2 = -c1/2 + # self.move([c2*nx,c2*ny]) + # s.move([(c1+c2)*nx,(c1+c2)*ny]) + + def update(self, dt): + self.pos[0] += dt * self.vel[0] + self.pos[1] += dt * self.vel[1] + + +def main(*args): + """Display multiple images bounce off each other using collision detection + + Positional arguments: + one or more image file names. + + This pg.masks demo will display multiple moving sprites bouncing + off each other. More than one sprite image can be provided. + """ + + if len(args) == 0: + raise ValueError("Require at least one image file name: non given") + print("Press any key to quit") + pg.init() + screen = pg.display.set_mode((640, 480)) + images = [] + masks = [] + for impath in args: + images.append(pg.image.load(impath).convert_alpha()) + masks.append(maskFromSurface(images[-1])) + + numtimes = 10 + import time + + t1 = time.time() + for x in range(numtimes): + unused_mask = maskFromSurface(images[-1]) + t2 = time.time() + + print("python maskFromSurface :%s" % (t2 - t1)) + + t1 = time.time() + for x in range(numtimes): + unused_mask = pg.mask.from_surface(images[-1]) + t2 = time.time() + + print("C pg.mask.from_surface :%s" % (t2 - t1)) + + sprites = [] + for i in range(20): + j = i % len(images) + s = Sprite(images[j], masks[j]) + s.setPos( + ( + random.uniform(0, screen.get_width()), + random.uniform(0, screen.get_height()), + ) + ) + s.setVelocity((random.uniform(-5, 5), random.uniform(-5, 5))) + sprites.append(s) + pg.time.set_timer(pg.USEREVENT, 33) + while 1: + event = pg.event.wait() + if event.type == pg.QUIT: + return + elif event.type == pg.USEREVENT: + + # Do both mechanics and screen update + screen.fill((240, 220, 100)) + for i, sprite in enumerate(sprites): + for j in range(i + 1, len(sprites)): + sprite.collide(sprites[j]) + for s in sprites: + s.update(1) + if s.pos[0] < -s.surface.get_width() - 3: + s.pos[0] = screen.get_width() + elif s.pos[0] > screen.get_width() + 3: + s.pos[0] = -s.surface.get_width() + if s.pos[1] < -s.surface.get_height() - 3: + s.pos[1] = screen.get_height() + elif s.pos[1] > screen.get_height() + 3: + s.pos[1] = -s.surface.get_height() + screen.blit(s.surface, s.pos) + pg.display.update() + elif event.type == pg.KEYDOWN: + return + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: mask.py [ ...]") + print("Let many copies of IMAGE(s) bounce against each other") + print("Press any key to quit") + main_dir = os.path.split(os.path.abspath(__file__))[0] + imagename = os.path.join(main_dir, "data", "chimp.png") + main(imagename) + + else: + main(*sys.argv[1:]) + pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/midi.py b/.venv/lib/python3.8/site-packages/pygame/examples/midi.py new file mode 100644 index 0000000..3e184ef --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/midi.py @@ -0,0 +1,877 @@ +#!/usr/bin/env python +""" pygame.examples.midi + +midi input, and a separate example of midi output. + +By default it runs the output example. + +python -m pygame.examples.midi --output +python -m pygame.examples.midi --input +python -m pygame.examples.midi --input +""" + +import sys +import os + +import pygame as pg +import pygame.midi + +# black and white piano keys use b/w color values directly +BACKGROUNDCOLOR = "slategray" + + +def print_device_info(): + pygame.midi.init() + _print_device_info() + pygame.midi.quit() + + +def _print_device_info(): + for i in range(pygame.midi.get_count()): + r = pygame.midi.get_device_info(i) + (interf, name, input, output, opened) = r + + in_out = "" + if input: + in_out = "(input)" + if output: + in_out = "(output)" + + print( + "%2i: interface :%s:, name :%s:, opened :%s: %s" + % (i, interf, name, opened, in_out) + ) + + +def input_main(device_id=None): + pg.init() + + pygame.midi.init() + + _print_device_info() + + if device_id is None: + input_id = pygame.midi.get_default_input_id() + else: + input_id = device_id + + print("using input_id :%s:" % input_id) + i = pygame.midi.Input(input_id) + + pg.display.set_mode((1, 1)) + + going = True + while going: + events = pygame.event.get() + for e in events: + if e.type in [pg.QUIT]: + going = False + if e.type in [pg.KEYDOWN]: + going = False + if e.type in [pygame.midi.MIDIIN]: + print(e) + + if i.poll(): + midi_events = i.read(10) + # convert them into pygame events. + midi_evs = pygame.midi.midis2events(midi_events, i.device_id) + + for m_e in midi_evs: + pygame.event.post(m_e) + + del i + pygame.midi.quit() + + +def output_main(device_id=None): + """Execute a musical keyboard example for the Church Organ instrument + + This is a piano keyboard example, with a two octave keyboard, starting at + note F3. Left mouse down over a key starts a note, left up stops it. The + notes are also mapped to the computer keyboard keys, assuming an American + English PC keyboard (sorry everyone else, but I don't know if I can map to + absolute key position instead of value.) The white keys are on the second + row, TAB to BACKSLASH, starting with note F3. The black keys map to the top + row, '1' to BACKSPACE, starting with F#3. 'r' is middle C. Close the + window or press ESCAPE to quit the program. Key velocity (note + amplitude) varies vertically on the keyboard image, with minimum velocity + at the top of a key and maximum velocity at bottom. + + Default Midi output, no device_id given, is to the default output device + for the computer. + + """ + + # A note to new pygamers: + # + # All the midi module stuff is in this function. It is unnecessary to + # understand how the keyboard display works to appreciate how midi + # messages are sent. + + # The keyboard is drawn by a Keyboard instance. This instance maps Midi + # notes to musical keyboard keys. A regions surface maps window position + # to (Midi note, velocity) pairs. A key_mapping dictionary does the same + # for computer keyboard keys. Midi sound is controlled with direct method + # calls to a pygame.midi.Output instance. + # + # Things to consider when using pygame.midi: + # + # 1) Initialize the midi module with a to pygame.midi.init(). + # 2) Create a midi.Output instance for the desired output device port. + # 3) Select instruments with set_instrument() method calls. + # 4) Play notes with note_on() and note_off() method calls. + # 5) Call pygame.midi.Quit() when finished. Though the midi module tries + # to ensure that midi is properly shut down, it is best to do it + # explicitly. A try/finally statement is the safest way to do this. + # + + # GRAND_PIANO = 0 + CHURCH_ORGAN = 19 + + instrument = CHURCH_ORGAN + # instrument = GRAND_PIANO + start_note = 53 # F3 (white key note), start_note != 0 + n_notes = 24 # Two octaves (14 white keys) + + key_mapping = make_key_mapping( + [ + pg.K_TAB, + pg.K_1, + pg.K_q, + pg.K_2, + pg.K_w, + pg.K_3, + pg.K_e, + pg.K_r, + pg.K_5, + pg.K_t, + pg.K_6, + pg.K_y, + pg.K_u, + pg.K_8, + pg.K_i, + pg.K_9, + pg.K_o, + pg.K_0, + pg.K_p, + pg.K_LEFTBRACKET, + pg.K_EQUALS, + pg.K_RIGHTBRACKET, + pg.K_BACKSPACE, + pg.K_BACKSLASH, + ], + start_note, + ) + + pg.init() + pygame.midi.init() + + _print_device_info() + + if device_id is None: + port = pygame.midi.get_default_output_id() + else: + port = device_id + + print("using output_id :%s:" % port) + + midi_out = pygame.midi.Output(port, 0) + try: + midi_out.set_instrument(instrument) + keyboard = Keyboard(start_note, n_notes) + + screen = pg.display.set_mode(keyboard.rect.size) + screen.fill(BACKGROUNDCOLOR) + pg.display.flip() + + background = pg.Surface(screen.get_size()) + background.fill(BACKGROUNDCOLOR) + dirty_rects = [] + keyboard.draw(screen, background, dirty_rects) + pg.display.update(dirty_rects) + + regions = pg.Surface(screen.get_size()) # initial color (0,0,0) + keyboard.map_regions(regions) + + pg.event.set_blocked(pg.MOUSEMOTION) + mouse_note = 0 + on_notes = set() + while 1: + e = pg.event.wait() + if e.type == pg.MOUSEBUTTONDOWN: + mouse_note, velocity, __, __ = regions.get_at(e.pos) + if mouse_note and mouse_note not in on_notes: + keyboard.key_down(mouse_note) + midi_out.note_on(mouse_note, velocity) + on_notes.add(mouse_note) + else: + mouse_note = 0 + elif e.type == pg.MOUSEBUTTONUP: + if mouse_note: + midi_out.note_off(mouse_note) + keyboard.key_up(mouse_note) + on_notes.remove(mouse_note) + mouse_note = 0 + elif e.type == pg.QUIT: + break + elif e.type == pg.KEYDOWN: + if e.key == pg.K_ESCAPE: + break + try: + note, velocity = key_mapping[e.key] + except KeyError: + pass + else: + if note not in on_notes: + keyboard.key_down(note) + midi_out.note_on(note, velocity) + on_notes.add(note) + elif e.type == pg.KEYUP: + try: + note, __ = key_mapping[e.key] + except KeyError: + pass + else: + if note in on_notes and note != mouse_note: + keyboard.key_up(note) + midi_out.note_off(note, 0) + on_notes.remove(note) + + dirty_rects = [] + keyboard.draw(screen, background, dirty_rects) + pg.display.update(dirty_rects) + finally: + del midi_out + pygame.midi.quit() + + +def make_key_mapping(keys, start_note): + """Return a dictionary of (note, velocity) by computer keyboard key code""" + mapping = {} + for i, key in enumerate(keys): + mapping[key] = (start_note + i, 127) + return mapping + + +class NullKey(object): + """A dummy key that ignores events passed to it by other keys + + A NullKey instance is the left key instance used by default + for the left most keyboard key. + + """ + + def _right_white_down(self): + pass + + def _right_white_up(self): + pass + + def _right_black_down(self): + pass + + def _right_black_up(self): + pass + + +null_key = NullKey() + + +def key_class(updates, image_strip, image_rects, is_white_key=True): + """Return a keyboard key widget class + + Arguments: + updates - a set into which a key instance adds itself if it needs + redrawing. + image_strip - The surface containing the images of all key states. + image_rects - A list of Rects giving the regions within image_strip that + are relevant to this key class. + is_white_key (default True) - Set false if this is a black key. + + This function automates the creation of a key widget class for the + three basic key types. A key has two basic states, up or down ( + depressed). Corresponding up and down images are drawn for each + of these two states. But to give the illusion of depth, a key + may have shadows cast upon it by the adjacent keys to its right. + These shadows change depending on the up/down state of the key and + its neighbors. So a key may support multiple images and states + depending on the shadows. A key type is determined by the length + of image_rects and the value of is_white. + + """ + + # Naming convention: Variables used by the Key class as part of a + # closure start with 'c_'. + + # State logic and shadows: + # + # A key may cast a shadow upon the key to its left. A black key casts a + # shadow on an adjacent white key. The shadow changes depending of whether + # the black or white key is depressed. A white key casts a shadow on the + # white key to its left if it is up and the left key is down. Therefore + # a keys state, and image it will draw, is determined entirely by its + # itself and the key immediately adjacent to it on the right. A white key + # is always assumed to have an adjacent white key. + # + # There can be up to eight key states, representing all permutations + # of the three fundamental states of self up/down, adjacent white + # right up/down, adjacent black up/down. + # + down_state_none = 0 + down_state_self = 1 + down_state_white = down_state_self << 1 + down_state_self_white = down_state_self | down_state_white + down_state_black = down_state_white << 1 + down_state_self_black = down_state_self | down_state_black + down_state_white_black = down_state_white | down_state_black + down_state_all = down_state_self | down_state_white_black + + # Some values used in the class. + # + c_down_state_initial = down_state_none + c_down_state_rect_initial = image_rects[0] + c_updates = updates + c_image_strip = image_strip + c_width, c_height = image_rects[0].size + + # A key propagates its up/down state change to the adjacent white key on + # the left by calling the adjacent key's _right_black_down or + # _right_white_down method. + # + if is_white_key: + key_color = "white" + else: + key_color = "black" + c_notify_down_method = "_right_%s_down" % key_color + c_notify_up_method = "_right_%s_up" % key_color + + # Images: + # + # A black key only needs two images, for the up and down states. Its + # appearance is unaffected by the adjacent keys to its right, which cast no + # shadows upon it. + # + # A white key with a no adjacent black to its right only needs three + # images, for self up, self down, and both self and adjacent white down. + # + # A white key with both a black and white key to its right needs six + # images: self up, self up and adjacent black down, self down, self and + # adjacent white down, self and adjacent black down, and all three down. + # + # Each 'c_event' dictionary maps the current key state to a new key state, + # along with corresponding image, for the related event. If no redrawing + # is required for the state change then the image rect is simply None. + # + c_event_down = {down_state_none: (down_state_self, image_rects[1])} + c_event_up = {down_state_self: (down_state_none, image_rects[0])} + c_event_right_white_down = { + down_state_none: (down_state_none, None), + down_state_self: (down_state_self, None), + } + c_event_right_white_up = c_event_right_white_down.copy() + c_event_right_black_down = c_event_right_white_down.copy() + c_event_right_black_up = c_event_right_white_down.copy() + if len(image_rects) > 2: + c_event_down[down_state_white] = (down_state_self_white, image_rects[2]) + c_event_up[down_state_self_white] = (down_state_white, image_rects[0]) + c_event_right_white_down[down_state_none] = (down_state_white, None) + c_event_right_white_down[down_state_self] = ( + down_state_self_white, + image_rects[2], + ) + c_event_right_white_up[down_state_white] = (down_state_none, None) + c_event_right_white_up[down_state_self_white] = ( + down_state_self, + image_rects[1], + ) + c_event_right_black_down[down_state_white] = (down_state_white, None) + c_event_right_black_down[down_state_self_white] = (down_state_self_white, None) + c_event_right_black_up[down_state_white] = (down_state_white, None) + c_event_right_black_up[down_state_self_white] = (down_state_self_white, None) + if len(image_rects) > 3: + c_event_down[down_state_black] = (down_state_self_black, image_rects[4]) + c_event_down[down_state_white_black] = (down_state_all, image_rects[5]) + c_event_up[down_state_self_black] = (down_state_black, image_rects[3]) + c_event_up[down_state_all] = (down_state_white_black, image_rects[3]) + c_event_right_white_down[down_state_black] = (down_state_white_black, None) + c_event_right_white_down[down_state_self_black] = ( + down_state_all, + image_rects[5], + ) + c_event_right_white_up[down_state_white_black] = (down_state_black, None) + c_event_right_white_up[down_state_all] = (down_state_self_black, image_rects[4]) + c_event_right_black_down[down_state_none] = (down_state_black, image_rects[3]) + c_event_right_black_down[down_state_self] = ( + down_state_self_black, + image_rects[4], + ) + c_event_right_black_down[down_state_white] = ( + down_state_white_black, + image_rects[3], + ) + c_event_right_black_down[down_state_self_white] = ( + down_state_all, + image_rects[5], + ) + c_event_right_black_up[down_state_black] = (down_state_none, image_rects[0]) + c_event_right_black_up[down_state_self_black] = ( + down_state_self, + image_rects[1], + ) + c_event_right_black_up[down_state_white_black] = ( + down_state_white, + image_rects[0], + ) + c_event_right_black_up[down_state_all] = (down_state_self_white, image_rects[2]) + + class Key(object): + """A key widget, maintains key state and draws the key's image + + Constructor arguments: + ident - A unique key identifier. Any immutable type suitable as a key. + posn - The location of the key on the display surface. + key_left - Optional, the adjacent white key to the left. Changes in + up and down state are propagated to that key. + + A key has an associated position and state. Related to state is the + image drawn. State changes are managed with method calls, one method + per event type. The up and down event methods are public. Other + internal methods are for passing on state changes to the key_left + key instance. + + """ + + is_white = is_white_key + + def __init__(self, ident, posn, key_left=None): + """Return a new Key instance + + The initial state is up, with all adjacent keys to the right also + up. + + """ + if key_left is None: + key_left = null_key + rect = pg.Rect(posn[0], posn[1], c_width, c_height) + self.rect = rect + self._state = c_down_state_initial + self._source_rect = c_down_state_rect_initial + self._ident = ident + self._hash = hash(ident) + self._notify_down = getattr(key_left, c_notify_down_method) + self._notify_up = getattr(key_left, c_notify_up_method) + self._key_left = key_left + self._background_rect = pg.Rect(rect.left, rect.bottom - 10, c_width, 10) + c_updates.add(self) + + def down(self): + """Signal that this key has been depressed (is down)""" + + self._state, source_rect = c_event_down[self._state] + if source_rect is not None: + self._source_rect = source_rect + c_updates.add(self) + self._notify_down() + + def up(self): + """Signal that this key has been released (is up)""" + + self._state, source_rect = c_event_up[self._state] + if source_rect is not None: + self._source_rect = source_rect + c_updates.add(self) + self._notify_up() + + def _right_white_down(self): + """Signal that the adjacent white key has been depressed + + This method is for internal propagation of events between + key instances. + + """ + + self._state, source_rect = c_event_right_white_down[self._state] + if source_rect is not None: + self._source_rect = source_rect + c_updates.add(self) + + def _right_white_up(self): + """Signal that the adjacent white key has been released + + This method is for internal propagation of events between + key instances. + + """ + + self._state, source_rect = c_event_right_white_up[self._state] + if source_rect is not None: + self._source_rect = source_rect + c_updates.add(self) + + def _right_black_down(self): + """Signal that the adjacent black key has been depressed + + This method is for internal propagation of events between + key instances. + + """ + + self._state, source_rect = c_event_right_black_down[self._state] + if source_rect is not None: + self._source_rect = source_rect + c_updates.add(self) + + def _right_black_up(self): + """Signal that the adjacent black key has been released + + This method is for internal propagation of events between + key instances. + + """ + + self._state, source_rect = c_event_right_black_up[self._state] + if source_rect is not None: + self._source_rect = source_rect + c_updates.add(self) + + def __eq__(self, other): + """True if same identifiers""" + + return self._ident == other._ident + + def __hash__(self): + """Return the immutable hash value""" + + return self._hash + + def __str__(self): + """Return the key's identifier and position as a string""" + + return "" % (self._ident, self.rect.top, self.rect.left) + + def draw(self, surf, background, dirty_rects): + """Redraw the key on the surface surf + + The background is redrawn. The altered region is added to the + dirty_rects list. + + """ + + surf.blit(background, self._background_rect, self._background_rect) + surf.blit(c_image_strip, self.rect, self._source_rect) + dirty_rects.append(self.rect) + + return Key + + +def key_images(): + """Return a keyboard keys image strip and a mapping of image locations + + The return tuple is a surface and a dictionary of rects mapped to key + type. + + This function encapsulates the constants relevant to the keyboard image + file. There are five key types. One is the black key. The other four + white keys are determined by the proximity of the black keys. The plain + white key has no black key adjacent to it. A white-left and white-right + key has a black key to the left or right of it respectively. A white-center + key has a black key on both sides. A key may have up to six related + images depending on the state of adjacent keys to its right. + + """ + + my_dir = os.path.split(os.path.abspath(__file__))[0] + strip_file = os.path.join(my_dir, "data", "midikeys.png") + white_key_width = 42 + white_key_height = 160 + black_key_width = 22 + black_key_height = 94 + strip = pg.image.load(strip_file) + names = [ + "black none", + "black self", + "white none", + "white self", + "white self-white", + "white-left none", + "white-left self", + "white-left black", + "white-left self-black", + "white-left self-white", + "white-left all", + "white-center none", + "white-center self", + "white-center black", + "white-center self-black", + "white-center self-white", + "white-center all", + "white-right none", + "white-right self", + "white-right self-white", + ] + rects = {} + for i in range(2): + rects[names[i]] = pg.Rect( + i * white_key_width, 0, black_key_width, black_key_height + ) + for i in range(2, len(names)): + rects[names[i]] = pg.Rect( + i * white_key_width, 0, white_key_width, white_key_height + ) + return strip, rects + + +class Keyboard(object): + """Musical keyboard widget + + Constructor arguments: + start_note: midi note value of the starting note on the keyboard. + n_notes: number of notes (keys) on the keyboard. + + A Keyboard instance draws the musical keyboard and maintains the state of + all the keyboard keys. Individual keys can be in a down (depressed) or + up (released) state. + + """ + + _image_strip, _rects = key_images() + + white_key_width, white_key_height = _rects["white none"].size + black_key_width, black_key_height = _rects["black none"].size + + _updates = set() + + # There are five key classes, representing key shape: + # black key (BlackKey), plain white key (WhiteKey), white key to the left + # of a black key (WhiteKeyLeft), white key between two black keys + # (WhiteKeyCenter), and white key to the right of a black key + # (WhiteKeyRight). + BlackKey = key_class( + _updates, _image_strip, [_rects["black none"], _rects["black self"]], False + ) + WhiteKey = key_class( + _updates, + _image_strip, + [_rects["white none"], _rects["white self"], _rects["white self-white"]], + ) + WhiteKeyLeft = key_class( + _updates, + _image_strip, + [ + _rects["white-left none"], + _rects["white-left self"], + _rects["white-left self-white"], + _rects["white-left black"], + _rects["white-left self-black"], + _rects["white-left all"], + ], + ) + WhiteKeyCenter = key_class( + _updates, + _image_strip, + [ + _rects["white-center none"], + _rects["white-center self"], + _rects["white-center self-white"], + _rects["white-center black"], + _rects["white-center self-black"], + _rects["white-center all"], + ], + ) + WhiteKeyRight = key_class( + _updates, + _image_strip, + [ + _rects["white-right none"], + _rects["white-right self"], + _rects["white-right self-white"], + ], + ) + + def __init__(self, start_note, n_notes): + """Return a new Keyboard instance with n_note keys""" + + self._start_note = start_note + self._end_note = start_note + n_notes - 1 + self._add_keys() + + def _add_keys(self): + """Populate the keyboard with key instances + + Set the _keys and rect attributes. + + """ + + # Keys are entered in a list, where index is Midi note. Since there are + # only 128 possible Midi notes the list length is managable. Unassigned + # note positions should never be accessed, so are set None to ensure + # the bug is quickly detected. + # + key_map = [None] * 128 + + start_note = self._start_note + end_note = self._end_note + black_offset = self.black_key_width // 2 + prev_white_key = None + x = y = 0 + if is_white_key(start_note): + is_prev_white = True + else: + x += black_offset + is_prev_white = False + for note in range(start_note, end_note + 1): + ident = note # For now notes uniquely identify keyboard keys. + if is_white_key(note): + if is_prev_white: + if note == end_note or is_white_key(note + 1): + key = self.WhiteKey(ident, (x, y), prev_white_key) + else: + key = self.WhiteKeyLeft(ident, (x, y), prev_white_key) + else: + if note == end_note or is_white_key(note + 1): + key = self.WhiteKeyRight(ident, (x, y), prev_white_key) + else: + key = self.WhiteKeyCenter(ident, (x, y), prev_white_key) + is_prev_white = True + x += self.white_key_width + prev_white_key = key + else: + key = self.BlackKey(ident, (x - black_offset, y), prev_white_key) + is_prev_white = False + key_map[note] = key + self._keys = key_map + + kb_width = key_map[self._end_note].rect.right + kb_height = self.white_key_height + self.rect = pg.Rect(0, 0, kb_width, kb_height) + + def map_regions(self, regions): + """Draw the key regions onto surface regions. + + Regions must have at least 3 byte pixels. Each pixel of the keyboard + rectangle is set to the color (note, velocity, 0). The regions surface + must be at least as large as (0, 0, self.rect.left, self.rect.bottom) + + """ + + # First draw the white key regions. Then add the overlapping + # black key regions. + # + cutoff = self.black_key_height + black_keys = [] + for note in range(self._start_note, self._end_note + 1): + key = self._keys[note] + if key.is_white: + fill_region(regions, note, key.rect, cutoff) + else: + black_keys.append((note, key)) + for note, key in black_keys: + fill_region(regions, note, key.rect, cutoff) + + def draw(self, surf, background, dirty_rects): + """Redraw all altered keyboard keys""" + + changed_keys = self._updates + while changed_keys: + changed_keys.pop().draw(surf, background, dirty_rects) + + def key_down(self, note): + """Signal a key down event for note""" + + self._keys[note].down() + + def key_up(self, note): + """Signal a key up event for note""" + + self._keys[note].up() + + +def fill_region(regions, note, rect, cutoff): + """Fill the region defined by rect with a (note, velocity, 0) color + + The velocity varies from a small value at the top of the region to + 127 at the bottom. The vertical region 0 to cutoff is split into + three parts, with velocities 42, 84 and 127. Everything below cutoff + has velocity 127. + + """ + + x, y, width, height = rect + if cutoff is None: + cutoff = height + delta_height = cutoff // 3 + regions.fill((note, 42, 0), (x, y, width, delta_height)) + regions.fill((note, 84, 0), (x, y + delta_height, width, delta_height)) + regions.fill( + (note, 127, 0), (x, y + 2 * delta_height, width, height - 2 * delta_height) + ) + + +def is_white_key(note): + """True if note is represented by a white key""" + + key_pattern = [ + True, + False, + True, + True, + False, + True, + False, + True, + True, + False, + True, + False, + ] + return key_pattern[(note - 21) % len(key_pattern)] + + +def usage(): + print("--input [device_id] : Midi message logger") + print("--output [device_id] : Midi piano keyboard") + print("--list : list available midi devices") + + +def main(mode="output", device_id=None): + """Run a Midi example + + Arguments: + mode - if 'output' run a midi keyboard output example + 'input' run a midi event logger input example + 'list' list available midi devices + (default 'output') + device_id - midi device number; if None then use the default midi input or + output device for the system + + """ + + if mode == "input": + input_main(device_id) + elif mode == "output": + output_main(device_id) + elif mode == "list": + print_device_info() + else: + raise ValueError("Unknown mode option '%s'" % mode) + + +if __name__ == "__main__": + + try: + device_id = int(sys.argv[-1]) + except ValueError: + device_id = None + + if "--input" in sys.argv or "-i" in sys.argv: + + input_main(device_id) + + elif "--output" in sys.argv or "-o" in sys.argv: + output_main(device_id) + elif "--list" in sys.argv or "-l" in sys.argv: + print_device_info() + else: + usage() + + pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/moveit.py b/.venv/lib/python3.8/site-packages/pygame/examples/moveit.py new file mode 100644 index 0000000..b902ce5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/moveit.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +""" pygame.examples.moveit + +This is the full and final example from the Pygame Tutorial, +"How Do I Make It Move". It creates 10 objects and animates +them on the screen. + +Note it's a bit scant on error checking, but it's easy to read. :] +Fortunately, this is python, and we needn't wrestle with a pile of +error codes. +""" +import os +import pygame as pg + +main_dir = os.path.split(os.path.abspath(__file__))[0] + +# our game object class +class GameObject: + def __init__(self, image, height, speed): + self.speed = speed + self.image = image + self.pos = image.get_rect().move(0, height) + + def move(self): + self.pos = self.pos.move(self.speed, 0) + if self.pos.right > 600: + self.pos.left = 0 + + +# quick function to load an image +def load_image(name): + path = os.path.join(main_dir, "data", name) + return pg.image.load(path).convert() + + +# here's the full code +def main(): + pg.init() + screen = pg.display.set_mode((640, 480)) + + player = load_image("player1.gif") + background = load_image("liquid.bmp") + + # scale the background image so that it fills the window and + # successfully overwrites the old sprite position. + background = pg.transform.scale2x(background) + background = pg.transform.scale2x(background) + + screen.blit(background, (0, 0)) + + objects = [] + for x in range(10): + o = GameObject(player, x * 40, x) + objects.append(o) + + while 1: + for event in pg.event.get(): + if event.type in (pg.QUIT, pg.KEYDOWN): + return + + for o in objects: + screen.blit(background, o.pos, o.pos) + for o in objects: + o.move() + screen.blit(o.image, o.pos) + + pg.display.update() + + +if __name__ == "__main__": + main() + pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/music_drop_fade.py b/.venv/lib/python3.8/site-packages/pygame/examples/music_drop_fade.py new file mode 100644 index 0000000..9d83546 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/music_drop_fade.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python +""" pygame.examples.music_drop_fade +Fade in and play music from a list while observing several events + +Adds music files to a playlist whenever played by one of the following methods +Music files passed from the commandline are played +Music files and filenames are played when drag and dropped onto the pygame window +Polls the clipboard and plays music files if it finds one there + +Keyboard Controls: +* Press space or enter to pause music playback +* Press up or down to change the music volume +* Press left or right to seek 5 seconds into the track +* Press escape to quit +* Press any other button to skip to the next music file in the list +""" + +import pygame as pg +import os, sys + +VOLUME_CHANGE_AMOUNT = 0.02 # how fast should up and down arrows change the volume? + + +def add_file(filename): + """ + This function will check if filename exists and is a music file + If it is the file will be added to a list of music files(even if already there) + Type checking is by the extension of the file, not by its contents + We can only discover if the file is valid when we mixer.music.load() it later + + It looks in the file directory and its data subdirectory + """ + if filename.rpartition(".")[2].lower() not in music_file_types: + print("{} not added to file list".format(filename)) + print("only these files types are allowed: ", music_file_types) + return False + elif os.path.exists(filename): + music_file_list.append(filename) + elif os.path.exists(os.path.join(main_dir, filename)): + music_file_list.append(os.path.join(main_dir, filename)) + elif os.path.exists(os.path.join(data_dir, filename)): + music_file_list.append(os.path.join(data_dir, filename)) + else: + print("file not found") + return False + print("{} added to file list".format(filename)) + return True + + +def play_file(filename): + """ + This function will call add_file and play it if successful + The music will fade in during the first 4 seconds + set_endevent is used to post a MUSIC_DONE event when the song finishes + The main loop will call play_next() when the MUSIC_DONE event is received + """ + global starting_pos + + if add_file(filename): + try: # we must do this in case the file is not a valid audio file + pg.mixer.music.load(music_file_list[-1]) + except pg.error as e: + print(e) # print description such as 'Not an Ogg Vorbis audio stream' + if filename in music_file_list: + music_file_list.remove(filename) + print("{} removed from file list".format(filename)) + return + pg.mixer.music.play(fade_ms=4000) + pg.mixer.music.set_volume(volume) + + if filename.rpartition(".")[2].lower() in music_can_seek: + print("file supports seeking") + starting_pos = 0 + else: + print("file does not support seeking") + starting_pos = -1 + pg.mixer.music.set_endevent(MUSIC_DONE) + + +def play_next(): + """ + This function will play the next song in music_file_list + It uses pop(0) to get the next song and then appends it to the end of the list + The song will fade in during the first 4 seconds + """ + + global starting_pos + if len(music_file_list) > 1: + nxt = music_file_list.pop(0) + + try: + pg.mixer.music.load(nxt) + except pg.error as e: + print(e) + print("{} removed from file list".format(nxt)) + + music_file_list.append(nxt) + print("starting next song: ", nxt) + else: + nxt = music_file_list[0] + pg.mixer.music.play(fade_ms=4000) + pg.mixer.music.set_volume(volume) + pg.mixer.music.set_endevent(MUSIC_DONE) + + if nxt.rpartition(".")[2].lower() in music_can_seek: + starting_pos = 0 + else: + starting_pos = -1 + + +def draw_text_line(text, y=0): + """ + Draws a line of text onto the display surface + The text will be centered horizontally at the given y postition + The text's height is added to y and returned to the caller + """ + screen = pg.display.get_surface() + surf = font.render(text, 1, (255, 255, 255)) + y += surf.get_height() + x = (screen.get_width() - surf.get_width()) / 2 + screen.blit(surf, (x, y)) + return y + + +def change_music_postion(amount): + """ + Changes current playback postition by amount seconds. + This only works with OGG and MP3 files. + music.get_pos() returns how many milliseconds the song has played, not + the current postion in the file. We must track the starting postion + ourselves. music.set_pos() will set the position in seconds. + """ + global starting_pos + + if starting_pos >= 0: # will be -1 unless play_file() was OGG or MP3 + played_for = pg.mixer.music.get_pos() / 1000.0 + old_pos = starting_pos + played_for + starting_pos = old_pos + amount + pg.mixer.music.play(start=starting_pos) + print("jumped from {} to {}".format(old_pos, starting_pos)) + + +MUSIC_DONE = pg.event.custom_type() # event to be set as mixer.music.set_endevent() +main_dir = os.path.split(os.path.abspath(__file__))[0] +data_dir = os.path.join(main_dir, "data") + +starting_pos = 0 # needed to fast forward and rewind +volume = 0.75 +music_file_list = [] +music_file_types = ("mp3", "ogg", "mid", "mod", "it", "xm", "wav") +music_can_seek = ("mp3", "ogg", "mod", "it", "xm") + + +def main(): + global font # this will be used by the draw_text_line function + global volume, starting_pos + running = True + paused = False + + # we will be polling for key up and key down events + # users should be able to change the volume by holding the up and down arrows + # the change_volume variable will be set by key down events and cleared by key up events + change_volume = 0 + + pg.init() + pg.display.set_mode((640, 480)) + font = pg.font.SysFont("Arial", 24) + clock = pg.time.Clock() + + pg.scrap.init() + pg.SCRAP_TEXT = pg.scrap.get_types()[0] # TODO remove when scrap module is fixed + clipped = pg.scrap.get(pg.SCRAP_TEXT).decode("UTF-8") + # store the current text from the clipboard TODO remove decode + + # add the command line arguments to the music_file_list + for arg in sys.argv[1:]: + add_file(arg) + play_file("house_lo.ogg") # play default music included with pygame + + # draw instructions on screen + y = draw_text_line("Drop music files or path names onto this window", 20) + y = draw_text_line("Copy file names into the clipboard", y) + y = draw_text_line("Or feed them from the command line", y) + y = draw_text_line("If it's music it will play!", y) + y = draw_text_line("SPACE to pause or UP/DOWN to change volume", y) + y = draw_text_line("LEFT and RIGHT will skip around the track", y) + draw_text_line("Other keys will start the next track", y) + + """ + This is the main loop + It will respond to drag and drop, clipboard changes, and key presses + """ + while running: + for ev in pg.event.get(): + if ev.type == pg.QUIT: + running = False + elif ev.type == pg.DROPTEXT: + play_file(ev.text) + elif ev.type == pg.DROPFILE: + play_file(ev.file) + elif ev.type == MUSIC_DONE: + play_next() + elif ev.type == pg.KEYDOWN: + if ev.key == pg.K_ESCAPE: + running = False # exit loop + elif ev.key in (pg.K_SPACE, pg.K_RETURN): + if paused: + pg.mixer.music.unpause() + paused = False + else: + pg.mixer.music.pause() + paused = True + elif ev.key == pg.K_UP: + change_volume = VOLUME_CHANGE_AMOUNT + elif ev.key == pg.K_DOWN: + change_volume = -VOLUME_CHANGE_AMOUNT + elif ev.key == pg.K_RIGHT: + change_music_postion(+5) + elif ev.key == pg.K_LEFT: + change_music_postion(-5) + + else: + play_next() + + elif ev.type == pg.KEYUP: + if ev.key in (pg.K_UP, pg.K_DOWN): + change_volume = 0 + + # is the user holding up or down? + if change_volume: + volume += change_volume + volume = min(max(0, volume), 1) # volume should be between 0 and 1 + pg.mixer.music.set_volume(volume) + print("volume:", volume) + + # TODO remove decode when SDL2 scrap is fixed + new_text = pg.scrap.get(pg.SCRAP_TEXT).decode("UTF-8") + if new_text != clipped: # has the clipboard changed? + clipped = new_text + play_file(clipped) # try to play the file if it has + + pg.display.flip() + clock.tick(9) # keep CPU use down by updating screen less often + + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/pixelarray.py b/.venv/lib/python3.8/site-packages/pygame/examples/pixelarray.py new file mode 100644 index 0000000..9961091 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/pixelarray.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +""" pygame.examples.pixelarray + +PixelArray does array processing of pixels. +Sort of like another array processor called 'numpy' - But for pixels. + + Flip it, + stripe it, + rotate it. + +Controls +-------- + +To see different effects - press a key or click a mouse. +""" +import os +import pygame as pg + + +main_dir = os.path.split(os.path.abspath(__file__))[0] +data_dir = os.path.join(main_dir, "data") + + +def show(image): + screen = pg.display.get_surface() + screen.fill((255, 255, 255)) + screen.blit(image, (0, 0)) + pg.display.flip() + while 1: + event = pg.event.wait() + if event.type == pg.QUIT: + pg.quit() + raise SystemExit + if event.type in [pg.MOUSEBUTTONDOWN, pg.KEYDOWN]: + break + + +def main(): + pg.init() + + pg.display.set_mode((255, 255)) + surface = pg.Surface((255, 255)) + + pg.display.flip() + + # Create the PixelArray. + ar = pg.PixelArray(surface) + + # Do some easy gradient effect. + for y in range(255): + r, g, b = y, y, y + ar[:, y] = (r, g, b) + del ar + show(surface) + + # We have made some gradient effect, now flip it. + ar = pg.PixelArray(surface) + ar[:] = ar[:, ::-1] + del ar + show(surface) + + # Every second column will be made blue + ar = pg.PixelArray(surface) + ar[::2] = (0, 0, 255) + del ar + show(surface) + + # Every second row will be made green + ar = pg.PixelArray(surface) + ar[:, ::2] = (0, 255, 0) + del ar + show(surface) + + # Manipulate the image. Flip it around the y axis. + surface = pg.image.load(os.path.join(data_dir, "arraydemo.bmp")) + ar = pg.PixelArray(surface) + ar[:] = ar[:, ::-1] + del ar + show(surface) + + # Flip the image around the x axis. + ar = pg.PixelArray(surface) + ar[:] = ar[::-1, :] + del ar + show(surface) + + # Every second column will be made white. + ar = pg.PixelArray(surface) + ar[::2] = (255, 255, 255) + del ar + show(surface) + + # Flip the image around both axes, restoring its original layout. + ar = pg.PixelArray(surface) + ar[:] = ar[::-1, ::-1] + del ar + show(surface) + + # Rotate 90 degrees clockwise. + w, h = surface.get_size() + surface2 = pg.Surface((h, w), surface.get_flags(), surface) + ar = pg.PixelArray(surface) + ar2 = pg.PixelArray(surface2) + ar2[...] = ar.transpose()[::-1, :] + del ar, ar2 + show(surface2) + + # Scale it by throwing each second pixel away. + surface = pg.image.load(os.path.join(data_dir, "arraydemo.bmp")) + ar = pg.PixelArray(surface) + sf2 = ar[::2, ::2].make_surface() + del ar + show(sf2) + + # Replace anything looking like the blue color from the text. + ar = pg.PixelArray(surface) + ar.replace((60, 60, 255), (0, 255, 0), 0.06) + del ar + show(surface) + + # Extract anything which might be somewhat black. + surface = pg.image.load(os.path.join(data_dir, "arraydemo.bmp")) + ar = pg.PixelArray(surface) + ar2 = ar.extract((0, 0, 0), 0.07) + sf2 = ar2.surface + del ar, ar2 + show(sf2) + + # Compare two images. + surface = pg.image.load(os.path.join(data_dir, "alien1.gif")) + surface2 = pg.image.load(os.path.join(data_dir, "alien2.gif")) + ar1 = pg.PixelArray(surface) + ar2 = pg.PixelArray(surface2) + ar3 = ar1.compare(ar2, 0.07) + sf3 = ar3.surface + del ar1, ar2, ar3 + show(sf3) + + +if __name__ == "__main__": + main() + pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/playmus.py b/.venv/lib/python3.8/site-packages/pygame/examples/playmus.py new file mode 100644 index 0000000..dac2f64 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/playmus.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +""" pygame.examples.playmus + +A simple music player. + + Use pygame.mixer.music to play an audio file. + +A window is created to handle keyboard events for playback commands. + + +Keyboard Controls +----------------- + +space - play/pause toggle +r - rewind +f - fade out +q - stop + +""" +import sys + +import pygame as pg +import pygame.freetype + + +class Window(object): + """The application's Pygame window + + A Window instance manages the creation of and drawing to a + window. It is a singleton class. Only one instance can exist. + + """ + + instance = None + + def __new__(cls, *args, **kwds): + """Return an open Pygame window""" + + if Window.instance is not None: + return Window.instance + self = object.__new__(cls) + pg.display.init() + self.screen = pg.display.set_mode((600, 400)) + Window.instance = self + return self + + def __init__(self, title): + pg.display.set_caption(title) + self.text_color = (254, 231, 21, 255) + self.background_color = (16, 24, 32, 255) + self.screen.fill(self.background_color) + pg.display.flip() + + pygame.freetype.init() + self.font = pygame.freetype.Font(None, 20) + self.font.origin = True + self.ascender = int(self.font.get_sized_ascender() * 1.5) + self.descender = int(self.font.get_sized_descender() * 1.5) + self.line_height = self.ascender - self.descender + + self.write_lines( + "\nPress 'q' or 'ESCAPE' or close this window to quit\n" + "Press 'SPACE' to play / pause\n" + "Press 'r' to rewind to the beginning (restart)\n" + "Press 'f' to fade music out over 5 seconds\n\n" + "Window will quit automatically when music ends\n", + 0, + ) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + return False + + def close(self): + pg.display.quit() + Window.instance = None + + def write_lines(self, text, line=0): + w, h = self.screen.get_size() + line_height = self.line_height + nlines = h // line_height + if line < 0: + line = nlines + line + for i, text_line in enumerate(text.split("\n"), line): + y = i * line_height + self.ascender + # Clear the line first. + self.screen.fill( + self.background_color, (0, i * line_height, w, line_height) + ) + # Write new text. + self.font.render_to(self.screen, (15, y), text_line, self.text_color) + pg.display.flip() + + +def show_usage_message(): + print("Usage: python playmus.py ") + print(" python -m pygame.examples.playmus ") + + +def main(file_path): + """Play an audio file with pg.mixer.music""" + + with Window(file_path) as win: + win.write_lines("Loading ...", -1) + pg.mixer.init(frequency=44100) + try: + paused = False + pg.mixer.music.load(file_path) + + # Make sure the event loop ticks over at least every 0.5 seconds. + pg.time.set_timer(pg.USEREVENT, 500) + + pg.mixer.music.play() + win.write_lines("Playing ...\n", -1) + + while pg.mixer.music.get_busy() or paused: + e = pg.event.wait() + if e.type == pg.KEYDOWN: + key = e.key + if key == pg.K_SPACE: + if paused: + pg.mixer.music.unpause() + paused = False + win.write_lines("Playing ...\n", -1) + else: + pg.mixer.music.pause() + paused = True + win.write_lines("Paused ...\n", -1) + elif key == pg.K_r: + if file_path[-3:].lower() in ("ogg", "mp3", "mod"): + status = "Rewound." + pg.mixer.music.rewind() + else: + status = "Restarted." + pg.mixer.music.play() + if paused: + pg.mixer.music.pause() + win.write_lines(status, -1) + elif key == pg.K_f: + win.write_lines("Fading out ...\n", -1) + pg.mixer.music.fadeout(5000) + # when finished get_busy() will return False. + elif key in [pg.K_q, pg.K_ESCAPE]: + paused = False + pg.mixer.music.stop() + # get_busy() will now return False. + elif e.type == pg.QUIT: + paused = False + pg.mixer.music.stop() + # get_busy() will now return False. + pg.time.set_timer(pg.USEREVENT, 0) + finally: + pg.mixer.quit() + pg.quit() + + +if __name__ == "__main__": + # Check the only command line argument, a file path + if len(sys.argv) != 2: + show_usage_message() + else: + main(sys.argv[1]) diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/prevent_display_stretching.py b/.venv/lib/python3.8/site-packages/pygame/examples/prevent_display_stretching.py new file mode 100644 index 0000000..363df81 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/prevent_display_stretching.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +""" pygame.examples.prevent_display_stretching + +Prevent display stretching on Windows. + +On some computers, the display environment can be configured to stretch +all windows so that they will not appear too small on the screen for +the user. This configuration is especially common on high-DPI displays. +pygame graphics appear distorted when automatically stretched by the +display environment. This script demonstrates a technique for preventing +this stretching and distortion. + +Limitations: +This script makes an API call that is only available on Windows (versions +Vista and newer). + +""" + +# Ensure that the computer is running Windows Vista or newer +import os +import sys + +# game constants +TEXTCOLOR = "green" +BACKGROUNDCOLOR = "black" +AXISCOLOR = "white" + +if os.name != "nt" or sys.getwindowsversion()[0] < 6: + raise NotImplementedError("this script requires Windows Vista or newer") + +import pygame as pg + +import ctypes + +# Determine whether or not the user would like to prevent stretching +if os.path.basename(sys.executable) == "pythonw.exe": + selection = "y" +else: + selection = None + while selection not in ("y", "n"): + selection = input("Prevent stretching? (y/n): ").strip().lower() + +if selection == "y": + msg = "Stretching is prevented." +else: + msg = "Stretching is not prevented." + +# Prevent stretching +if selection == "y": + user32 = ctypes.windll.user32 + user32.SetProcessDPIAware() + +# Show screen +pg.display.init() +RESOLUTION = (350, 350) +screen = pg.display.set_mode(RESOLUTION) + +# Render message onto a surface +pg.font.init() +font = pg.font.Font(None, 36) +msg_surf = font.render(msg, 1, TEXTCOLOR) +res_surf = font.render("Intended resolution: %ix%i" % RESOLUTION, 1, TEXTCOLOR) + +# Control loop +running = True +clock = pg.time.Clock() +counter = 0 +while running: + + for event in pg.event.get(): + if event.type == pg.QUIT: + running = False + + screen.fill(BACKGROUNDCOLOR) + + # Draw lines which will be blurry if the window is stretched + # or clear if the window is not stretched. + pg.draw.line(screen, AXISCOLOR, (0, counter), (RESOLUTION[0] - 1, counter)) + pg.draw.line(screen, AXISCOLOR, (counter, 0), (counter, RESOLUTION[1] - 1)) + + # Blit message onto screen surface + msg_blit_rect = screen.blit(msg_surf, (0, 0)) + screen.blit(res_surf, (0, msg_blit_rect.bottom)) + + clock.tick(10) + + pg.display.flip() + + counter += 1 + if counter == RESOLUTION[0]: + counter = 0 + +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/resizing_new.py b/.venv/lib/python3.8/site-packages/pygame/examples/resizing_new.py new file mode 100644 index 0000000..cda01f2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/resizing_new.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +import pygame as pg + +pg.init() + +RES = (160, 120) +FPS = 30 +clock = pg.time.Clock() + +screen = pg.display.set_mode(RES, pg.RESIZABLE) +pg.display._set_autoresize(False) + +# MAIN LOOP + +done = False + +i = 0 +j = 0 + +while not done: + for event in pg.event.get(): + if event.type == pg.KEYDOWN and event.key == pg.K_q: + done = True + if event.type == pg.QUIT: + done = True + # if event.type==pg.WINDOWRESIZED: + # screen=pg.display.get_surface() + if event.type == pg.VIDEORESIZE: + screen = pg.display.get_surface() + i += 1 + i = i % screen.get_width() + j += i % 2 + j = j % screen.get_height() + + screen.fill((255, 0, 255)) + pg.draw.circle(screen, (0, 0, 0), (100, 100), 20) + pg.draw.circle(screen, (0, 0, 200), (0, 0), 10) + pg.draw.circle(screen, (200, 0, 0), (160, 120), 30) + pg.draw.line(screen, (250, 250, 0), (0, 120), (160, 0)) + pg.draw.circle(screen, (255, 255, 255), (i, j), 5) + + pg.display.flip() + clock.tick(FPS) +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/scaletest.py b/.venv/lib/python3.8/site-packages/pygame/examples/scaletest.py new file mode 100644 index 0000000..6d7b964 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/scaletest.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +""" pygame.examples.scaletest + +Shows an interactive image scaler. + +""" +import sys +import time +import pygame as pg + + +def main(imagefile, convert_alpha=False, run_speed_test=False): + """show an interactive image scaler + + Args: + imagefile - name of source image (required) + convert_alpha - use convert_alpha() on the surf (default False) + run_speed_test - (default False) + """ + + # initialize display + pg.display.init() + # load background image + background = pg.image.load(imagefile) + + if run_speed_test: + if convert_alpha: + # convert_alpha() requires the display mode to be set + pg.display.set_mode((1, 1)) + background = background.convert_alpha() + + SpeedTest(background) + return + + # start fullscreen mode + screen = pg.display.set_mode((1024, 768), pg.FULLSCREEN) + if convert_alpha: + background = background.convert_alpha() + + # turn off the mouse pointer + pg.mouse.set_visible(0) + # main loop + bRunning = True + bUp = False + bDown = False + bLeft = False + bRight = False + cursize = [background.get_width(), background.get_height()] + while bRunning: + image = pg.transform.smoothscale(background, cursize) + imgpos = image.get_rect(centerx=512, centery=384) + screen.fill((255, 255, 255)) + screen.blit(image, imgpos) + pg.display.flip() + for event in pg.event.get(): + if event.type == pg.QUIT or ( + event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE + ): + bRunning = False + if event.type == pg.KEYDOWN: + if event.key == pg.K_UP: + bUp = True + if event.key == pg.K_DOWN: + bDown = True + if event.key == pg.K_LEFT: + bLeft = True + if event.key == pg.K_RIGHT: + bRight = True + if event.type == pg.KEYUP: + if event.key == pg.K_UP: + bUp = False + if event.key == pg.K_DOWN: + bDown = False + if event.key == pg.K_LEFT: + bLeft = False + if event.key == pg.K_RIGHT: + bRight = False + if bUp: + cursize[1] -= 2 + if cursize[1] < 1: + cursize[1] = 1 + if bDown: + cursize[1] += 2 + if bLeft: + cursize[0] -= 2 + if cursize[0] < 1: + cursize[0] = 1 + if bRight: + cursize[0] += 2 + pg.quit() + + +def SpeedTest(image): + print("\nImage Scaling Speed Test - Image Size %s\n" % str(image.get_size())) + + imgsize = [image.get_width(), image.get_height()] + duration = 0.0 + for i in range(128): + shrinkx = (imgsize[0] * i) // 128 + shrinky = (imgsize[1] * i) // 128 + start = time.time() + tempimg = pg.transform.smoothscale(image, (shrinkx, shrinky)) + duration += time.time() - start + del tempimg + + print( + "Average transform.smoothscale shrink time: %.4f ms." % (duration / 128 * 1000) + ) + + duration = 0.0 + for i in range(128): + expandx = (imgsize[0] * (i + 129)) // 128 + expandy = (imgsize[1] * (i + 129)) // 128 + start = time.time() + tempimg = pg.transform.smoothscale(image, (expandx, expandy)) + duration += time.time() - start + del tempimg + + print( + "Average transform.smoothscale expand time: %.4f ms." % (duration / 128 * 1000) + ) + + duration = 0.0 + for i in range(128): + shrinkx = (imgsize[0] * i) // 128 + shrinky = (imgsize[1] * i) // 128 + start = time.time() + tempimg = pg.transform.scale(image, (shrinkx, shrinky)) + duration += time.time() - start + del tempimg + + print("Average transform.scale shrink time: %.4f ms." % (duration / 128 * 1000)) + + duration = 0.0 + for i in range(128): + expandx = (imgsize[0] * (i + 129)) // 128 + expandy = (imgsize[1] * (i + 129)) // 128 + start = time.time() + tempimg = pg.transform.scale(image, (expandx, expandy)) + duration += time.time() - start + del tempimg + + print("Average transform.scale expand time: %.4f ms." % (duration / 128 * 1000)) + + +if __name__ == "__main__": + # check input parameters + if len(sys.argv) < 2: + print("\nUsage: %s imagefile [-t] [-convert_alpha]" % sys.argv[0]) + print(" imagefile image filename (required)") + print(" -t run speed test") + print(" -convert_alpha use convert_alpha() on the image's " "surface\n") + else: + main( + sys.argv[1], + convert_alpha="-convert_alpha" in sys.argv, + run_speed_test="-t" in sys.argv, + ) diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/scrap_clipboard.py b/.venv/lib/python3.8/site-packages/pygame/examples/scrap_clipboard.py new file mode 100644 index 0000000..5978a42 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/scrap_clipboard.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +""" pygame.examples.scrap_clipboard + +Demonstrates the clipboard capabilities of pygame. + +Copy/paste! + + +Keyboard Controls +----------------- + +g - get and print types in clipboard. If, image blit to screen. +p - place some text into clipboard +a - print types available in the clipboard +i - put image into the clipboard +""" +import os + +import pygame as pg +import pygame.scrap as scrap + +from io import BytesIO + + +def usage(): + print("Press the 'g' key to get all of the current clipboard data") + print("Press the 'p' key to put a string into the clipboard") + print("Press the 'a' key to get a list of the currently available types") + print("Press the 'i' key to put an image into the clipboard") + + +main_dir = os.path.split(os.path.abspath(__file__))[0] + +pg.init() +screen = pg.display.set_mode((200, 200)) +c = pg.time.Clock() +going = True + +# Initialize the scrap module and use the clipboard mode. +scrap.init() +scrap.set_mode(pg.SCRAP_CLIPBOARD) + +usage() + +while going: + for e in pg.event.get(): + if e.type == pg.QUIT or (e.type == pg.KEYDOWN and e.key == pg.K_ESCAPE): + going = False + + elif e.type == pg.KEYDOWN and e.key == pg.K_g: + # This means to look for data. + print("Getting the different clipboard data..") + for t in scrap.get_types(): + r = scrap.get(t) + if r and len(r) > 500: + print("Type %s : (large %i byte buffer)" % (t, len(r))) + elif r is None: + print("Type %s : None" % (t,)) + else: + print("Type %s : '%s'" % (t, r.decode("ascii", "ignore"))) + if "image" in t: + namehint = t.split("/")[1] + if namehint in ["bmp", "png", "jpg"]: + f = BytesIO(r) + loaded_surf = pg.image.load(f, "." + namehint) + screen.blit(loaded_surf, (0, 0)) + + elif e.type == pg.KEYDOWN and e.key == pg.K_p: + # Place some text into the selection. + print("Placing clipboard text.") + scrap.put(pg.SCRAP_TEXT, b"Hello. This is a message from scrap.") + + elif e.type == pg.KEYDOWN and e.key == pg.K_a: + # Get all available types. + print("Getting the available types from the clipboard.") + types = scrap.get_types() + print(types) + if len(types) > 0: + print("Contains %s: %s" % (types[0], scrap.contains(types[0]))) + print("Contains _INVALID_: ", scrap.contains("_INVALID_")) + + elif e.type == pg.KEYDOWN and e.key == pg.K_i: + print("Putting image into the clipboard.") + scrap.set_mode(pg.SCRAP_CLIPBOARD) + fp = open(os.path.join(main_dir, "data", "liquid.bmp"), "rb") + buf = fp.read() + scrap.put("image/bmp", buf) + fp.close() + + elif e.type in (pg.KEYDOWN, pg.MOUSEBUTTONDOWN): + usage() + pg.display.flip() + c.tick(40) +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/scroll.py b/.venv/lib/python3.8/site-packages/pygame/examples/scroll.py new file mode 100644 index 0000000..48b7417 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/scroll.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +""" pygame.examples.scroll + +An zoomed image viewer that demonstrates Surface.scroll + +This example shows a scrollable image that has a zoom factor of eight. +It uses the Surface.scroll function to shift the image on the display +surface. A clip rectangle protects a margin area. If called as a function, +the example accepts an optional image file path. If run as a program +it takes an optional file path command line argument. If no file +is provided a default image file is used. + +When running click on a black triangle to move one pixel in the direction +the triangle points. Or use the arrow keys. Close the window or press ESC +to quit. +""" +import sys +import os + +import pygame as pg +from pygame.transform import scale + +main_dir = os.path.dirname(os.path.abspath(__file__)) + +# game constants +DIR_UP = 1 +DIR_DOWN = 2 +DIR_LEFT = 3 +DIR_RIGHT = 4 + +zoom_factor = 8 + + +def draw_arrow(surf, color, posn, direction): + x, y = posn + if direction == DIR_UP: + pointlist = ((x - 29, y + 30), (x + 30, y + 30), (x + 1, y - 29), (x, y - 29)) + elif direction == DIR_DOWN: + pointlist = ((x - 29, y - 29), (x + 30, y - 29), (x + 1, y + 30), (x, y + 30)) + elif direction == DIR_LEFT: + pointlist = ((x + 30, y - 29), (x + 30, y + 30), (x - 29, y + 1), (x - 29, y)) + else: + pointlist = ((x - 29, y - 29), (x - 29, y + 30), (x + 30, y + 1), (x + 30, y)) + pg.draw.polygon(surf, color, pointlist) + + +def add_arrow_button(screen, regions, posn, direction): + draw_arrow(screen, "black", posn, direction) + draw_arrow(regions, (direction, 0, 0), posn, direction) + + +def scroll_view(screen, image, direction, view_rect): + src_rect = None + zoom_view_rect = screen.get_clip() + image_w, image_h = image.get_size() + if direction == DIR_UP: + if view_rect.top > 0: + screen.scroll(dy=zoom_factor) + view_rect.move_ip(0, -1) + src_rect = view_rect.copy() + src_rect.h = 1 + dst_rect = zoom_view_rect.copy() + dst_rect.h = zoom_factor + elif direction == DIR_DOWN: + if view_rect.bottom < image_h: + screen.scroll(dy=-zoom_factor) + view_rect.move_ip(0, 1) + src_rect = view_rect.copy() + src_rect.h = 1 + src_rect.bottom = view_rect.bottom + dst_rect = zoom_view_rect.copy() + dst_rect.h = zoom_factor + dst_rect.bottom = zoom_view_rect.bottom + elif direction == DIR_LEFT: + if view_rect.left > 0: + screen.scroll(dx=zoom_factor) + view_rect.move_ip(-1, 0) + src_rect = view_rect.copy() + src_rect.w = 1 + dst_rect = zoom_view_rect.copy() + dst_rect.w = zoom_factor + elif direction == DIR_RIGHT: + if view_rect.right < image_w: + screen.scroll(dx=-zoom_factor) + view_rect.move_ip(1, 0) + src_rect = view_rect.copy() + src_rect.w = 1 + src_rect.right = view_rect.right + dst_rect = zoom_view_rect.copy() + dst_rect.w = zoom_factor + dst_rect.right = zoom_view_rect.right + if src_rect is not None: + scale(image.subsurface(src_rect), dst_rect.size, screen.subsurface(dst_rect)) + pg.display.update(zoom_view_rect) + + +def main(image_file=None): + if image_file is None: + image_file = os.path.join(main_dir, "data", "arraydemo.bmp") + margin = 80 + view_size = (30, 20) + zoom_view_size = (view_size[0] * zoom_factor, view_size[1] * zoom_factor) + win_size = (zoom_view_size[0] + 2 * margin, zoom_view_size[1] + 2 * margin) + background_color = pg.Color("beige") + + pg.init() + + # set up key repeating so we can hold down the key to scroll. + old_k_delay, old_k_interval = pg.key.get_repeat() + pg.key.set_repeat(500, 30) + + try: + screen = pg.display.set_mode(win_size) + screen.fill(background_color) + pg.display.flip() + + image = pg.image.load(image_file).convert() + image_w, image_h = image.get_size() + + if image_w < view_size[0] or image_h < view_size[1]: + print("The source image is too small for this example.") + print("A %i by %i or larger image is required." % zoom_view_size) + return + + regions = pg.Surface(win_size, 0, 24) + add_arrow_button(screen, regions, (40, win_size[1] // 2), DIR_LEFT) + add_arrow_button( + screen, regions, (win_size[0] - 40, win_size[1] // 2), DIR_RIGHT + ) + add_arrow_button(screen, regions, (win_size[0] // 2, 40), DIR_UP) + add_arrow_button( + screen, regions, (win_size[0] // 2, win_size[1] - 40), DIR_DOWN + ) + pg.display.flip() + + screen.set_clip((margin, margin, zoom_view_size[0], zoom_view_size[1])) + + view_rect = pg.Rect(0, 0, view_size[0], view_size[1]) + + scale( + image.subsurface(view_rect), + zoom_view_size, + screen.subsurface(screen.get_clip()), + ) + pg.display.flip() + + # the direction we will scroll in. + direction = None + + clock = pg.time.Clock() + clock.tick() + + going = True + while going: + # wait for events before doing anything. + # events = [pg.event.wait()] + pg.event.get() + events = pg.event.get() + + for e in events: + if e.type == pg.KEYDOWN: + if e.key == pg.K_ESCAPE: + going = False + elif e.key == pg.K_DOWN: + scroll_view(screen, image, DIR_DOWN, view_rect) + elif e.key == pg.K_UP: + scroll_view(screen, image, DIR_UP, view_rect) + elif e.key == pg.K_LEFT: + scroll_view(screen, image, DIR_LEFT, view_rect) + elif e.key == pg.K_RIGHT: + scroll_view(screen, image, DIR_RIGHT, view_rect) + elif e.type == pg.QUIT: + going = False + elif e.type == pg.MOUSEBUTTONDOWN: + direction = regions.get_at(e.pos)[0] + elif e.type == pg.MOUSEBUTTONUP: + direction = None + + if direction: + scroll_view(screen, image, direction, view_rect) + clock.tick(30) + + finally: + pg.key.set_repeat(old_k_delay, old_k_interval) + pg.quit() + + +if __name__ == "__main__": + if len(sys.argv) > 1: + image_file = sys.argv[1] + else: + image_file = None + main(image_file) diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/setmodescale.py b/.venv/lib/python3.8/site-packages/pygame/examples/setmodescale.py new file mode 100644 index 0000000..3f427d2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/setmodescale.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +""" pygame.examples.setmodescale + +On high resolution displays(4k, 1080p) and tiny graphics games (640x480) +show up very small so that they are unplayable. SCALED scales up the window +for you. The game thinks it's a 640x480 window, but really it can be bigger. +Mouse events are scaled for you, so your game doesn't need to do it. + +Passing SCALED to pygame.display.set_mode means the resolution depends +on desktop size and the graphics are scaled. +""" + +import pygame as pg + +pg.init() + +RES = (160, 120) +FPS = 30 +clock = pg.time.Clock() + +print("desktops", pg.display.get_desktop_sizes()) +screen = pg.display.set_mode(RES, pg.SCALED | pg.RESIZABLE) + +# MAIN LOOP + +done = False + +i = 0 +j = 0 + +r_name, r_flags = pg.display._get_renderer_info() +print("renderer:", r_name, "flags:", bin(r_flags)) +for flag, name in [ + (1, "software"), + (2, "accelerated"), + (4, "VSync"), + (8, "render to texture"), +]: + if flag & r_flags: + print(name) + +while not done: + for event in pg.event.get(): + if event.type == pg.KEYDOWN and event.key == pg.K_q: + done = True + if event.type == pg.QUIT: + done = True + if event.type == pg.KEYDOWN and event.key == pg.K_f: + pg.display.toggle_fullscreen() + if event.type == pg.VIDEORESIZE: + pg.display._resize_event(event) + + i += 1 + i = i % screen.get_width() + j += i % 2 + j = j % screen.get_height() + + screen.fill((255, 0, 255)) + pg.draw.circle(screen, (0, 0, 0), (100, 100), 20) + pg.draw.circle(screen, (0, 0, 200), (0, 0), 10) + pg.draw.circle(screen, (200, 0, 0), (160, 120), 30) + pg.draw.line(screen, (250, 250, 0), (0, 120), (160, 0)) + pg.draw.circle(screen, (255, 255, 255), (i, j), 5) + + pg.display.flip() + clock.tick(FPS) +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/sound.py b/.venv/lib/python3.8/site-packages/pygame/examples/sound.py new file mode 100644 index 0000000..c5a23b9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/sound.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +""" pygame.examples.sound + +Playing a soundfile and waiting for it to finish. You'll need the +pygame.mixer module for this to work. Note how in this simple example +we don't even bother loading all of the pygame package. +Just pick the mixer for sound and time for the delay function. + +Optional command line argument: audio file name +""" +import os +import sys +import pygame as pg + +main_dir = os.path.split(os.path.abspath(__file__))[0] + + +def main(file_path=None): + """Play an audio file as a buffered sound sample + + :param str file_path: audio file (default data/secosmic_low.wav) + """ + # choose a desired audio format + pg.mixer.init(11025) # raises exception on fail + + # load the sound + sound = pg.mixer.Sound(file_path) + + # start playing + print("Playing Sound...") + channel = sound.play() + + # poll until finished + while channel.get_busy(): # still playing + print(" ...still going...") + pg.time.wait(1000) + print("...Finished") + pg.quit() + + +if __name__ == "__main__": + if len(sys.argv) > 1: + main(sys.argv[1]) + else: + main(os.path.join(main_dir, "data", "secosmic_lo.wav")) diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/sound_array_demos.py b/.venv/lib/python3.8/site-packages/pygame/examples/sound_array_demos.py new file mode 100644 index 0000000..1a2b49d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/sound_array_demos.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +""" pygame.examples.sound_array_demos + +Creates an echo effect on any Sound object. + +Uses sndarray and numpy to create offset faded copies of the +original sound. Currently it just uses hardcoded values for the +number of echos and the delay. Easy for you to recreate as +needed. + +version 2. changes: +- Should work with different sample rates now. +- put into a function. +- Uses numpy by default, but falls back on Numeric. +""" +import os +import pygame as pg +from numpy import zeros, int32, int16 +import time + + +# pg.mixer.init(44100, -16, 0) +pg.mixer.init() +# pg.mixer.init(11025, -16, 0) +# pg.mixer.init(11025) + + +def make_echo(sound, samples_per_second, mydebug=True): + """returns a sound which is echoed of the last one.""" + + echo_length = 3.5 + + a1 = pg.sndarray.array(sound) + if mydebug: + print("SHAPE1: %s" % (a1.shape,)) + + length = a1.shape[0] + + # myarr = zeros(length+12000) + myarr = zeros(a1.shape, int32) + + if len(a1.shape) > 1: + # mult = a1.shape[1] + size = (a1.shape[0] + int(echo_length * a1.shape[0]), a1.shape[1]) + # size = (a1.shape[0] + int(a1.shape[0] + (echo_length * 3000)), a1.shape[1]) + else: + # mult = 1 + size = (a1.shape[0] + int(echo_length * a1.shape[0]),) + # size = (a1.shape[0] + int(a1.shape[0] + (echo_length * 3000)),) + + if mydebug: + print(int(echo_length * a1.shape[0])) + myarr = zeros(size, int32) + + if mydebug: + print("size %s" % (size,)) + print(myarr.shape) + myarr[:length] = a1 + # print (myarr[3000:length+3000]) + # print (a1 >> 1) + # print ("a1.shape %s" % (a1.shape,)) + # c = myarr[3000:length+(3000*mult)] + # print ("c.shape %s" % (c.shape,)) + + incr = int(samples_per_second / echo_length) + gap = length + + myarr[incr : gap + incr] += a1 >> 1 + myarr[incr * 2 : gap + (incr * 2)] += a1 >> 2 + myarr[incr * 3 : gap + (incr * 3)] += a1 >> 3 + myarr[incr * 4 : gap + (incr * 4)] += a1 >> 4 + + if mydebug: + print("SHAPE2: %s" % (myarr.shape,)) + + sound2 = pg.sndarray.make_sound(myarr.astype(int16)) + + return sound2 + + +def slow_down_sound(sound, rate): + """returns a sound which is a slowed down version of the original. + rate - at which the sound should be slowed down. eg. 0.5 would be half speed. + """ + + raise NotImplementedError() + # grow_rate = 1 / rate + # make it 1/rate times longer. + # a1 = pg.sndarray.array(sound) + # surf = pg.surfarray.make_surface(a1) + # print (a1.shape[0] * grow_rate) + # scaled_surf = pg.transform.scale(surf, (int(a1.shape[0] * grow_rate), a1.shape[1])) + # print (scaled_surf) + # print (surf) + + # a2 = a1 * rate + # print (a1.shape) + # print (a2.shape) + # print (a2) + # sound2 = pg.sndarray.make_sound(a2.astype(int16)) + # return sound2 + + +def sound_from_pos(sound, start_pos, samples_per_second=None, inplace=1): + """returns a sound which begins at the start_pos. + start_pos - in seconds from the begining. + samples_per_second - + """ + + # see if we want to reuse the sound data or not. + if inplace: + a1 = pg.sndarray.samples(sound) + else: + a1 = pg.sndarray.array(sound) + + # see if samples per second has been given. If not, query the pg.mixer. + # eg. it might be set to 22050 + if samples_per_second is None: + samples_per_second = pg.mixer.get_init()[0] + + # figure out the start position in terms of samples. + start_pos_in_samples = int(start_pos * samples_per_second) + + # cut the beginning off the sound at the start position. + a2 = a1[start_pos_in_samples:] + + # make the Sound instance from the array. + sound2 = pg.sndarray.make_sound(a2) + + return sound2 + + +def main(): + """play various sndarray effects""" + + main_dir = os.path.split(os.path.abspath(__file__))[0] + print("mixer.get_init %s" % (pg.mixer.get_init(),)) + + samples_per_second = pg.mixer.get_init()[0] + + print(("-" * 30) + "\n") + print("loading sound") + sound = pg.mixer.Sound(os.path.join(main_dir, "data", "car_door.wav")) + + print("-" * 30) + print("start positions") + print("-" * 30) + + start_pos = 0.1 + sound2 = sound_from_pos(sound, start_pos, samples_per_second) + + print("sound.get_length %s" % (sound.get_length(),)) + print("sound2.get_length %s" % (sound2.get_length(),)) + sound2.play() + while pg.mixer.get_busy(): + pg.time.wait(200) + + print("waiting 2 seconds") + pg.time.wait(2000) + print("playing original sound") + + sound.play() + while pg.mixer.get_busy(): + pg.time.wait(200) + + print("waiting 2 seconds") + pg.time.wait(2000) + + # if 0: + # #TODO: this is broken. + # print (("-" * 30) + "\n") + # print ("Slow down the original sound.") + # rate = 0.2 + # slowed_sound = slow_down_sound(sound, rate) + # slowed_sound.play() + # while pg.mixer.get_busy(): + # pg.time.wait(200) + + print("-" * 30) + print("echoing") + print("-" * 30) + + t1 = time.time() + sound2 = make_echo(sound, samples_per_second) + print("time to make echo %i" % (time.time() - t1,)) + + print("original sound") + sound.play() + while pg.mixer.get_busy(): + pg.time.wait(200) + + print("echoed sound") + sound2.play() + while pg.mixer.get_busy(): + pg.time.wait(200) + + sound = pg.mixer.Sound(os.path.join(main_dir, "data", "secosmic_lo.wav")) + + t1 = time.time() + sound3 = make_echo(sound, samples_per_second) + print("time to make echo %i" % (time.time() - t1,)) + + print("original sound") + sound.play() + while pg.mixer.get_busy(): + pg.time.wait(200) + + print("echoed sound") + sound3.play() + while pg.mixer.get_busy(): + pg.time.wait(200) + + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/sprite_texture.py b/.venv/lib/python3.8/site-packages/pygame/examples/sprite_texture.py new file mode 100644 index 0000000..79d8cf5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/sprite_texture.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +""" pygame.examples.sprite_texture + +Experimental! Uses APIs which may disapear in the next release (_sdl2 is private). + + +Hardware accelerated Image objects with pygame.sprite. + +_sdl2.video.Image is a backwards compatible way with to use Texture with +pygame.sprite groups. +""" +import os +import pygame as pg + +if pg.get_sdl_version()[0] < 2: + raise SystemExit("This example requires pygame 2 and SDL2.") +from pygame._sdl2 import Window, Texture, Image, Renderer + + +data_dir = os.path.join(os.path.split(os.path.abspath(__file__))[0], "data") + + +def load_img(file): + return pg.image.load(os.path.join(data_dir, file)) + + +pg.display.init() +pg.key.set_repeat(10, 10) + +win = Window("asdf", resizable=True) +renderer = Renderer(win) +tex = Texture.from_surface(renderer, load_img("alien1.gif")) + + +class Something(pg.sprite.Sprite): + def __init__(self, img): + pg.sprite.Sprite.__init__(self) + + self.rect = img.get_rect() + self.image = img + + self.rect.w *= 5 + self.rect.h *= 5 + + img.origin = self.rect.w / 2, self.rect.h / 2 + + +sprite = Something(Image(tex, (0, 0, tex.width / 2, tex.height / 2))) +sprite.rect.x = 250 +sprite.rect.y = 50 + +# sprite2 = Something(Image(sprite.image)) +sprite2 = Something(Image(tex)) +sprite2.rect.x = 250 +sprite2.rect.y = 250 +sprite2.rect.w /= 2 +sprite2.rect.h /= 2 + +group = pg.sprite.Group() +group.add(sprite2) +group.add(sprite) + +import math + +t = 0 +running = True +clock = pg.time.Clock() +renderer.draw_color = (255, 0, 0, 255) + +while running: + for event in pg.event.get(): + if event.type == pg.QUIT: + running = False + elif event.type == pg.KEYDOWN: + if event.key == pg.K_ESCAPE: + running = False + elif event.key == pg.K_LEFT: + sprite.rect.x -= 5 + elif event.key == pg.K_RIGHT: + sprite.rect.x += 5 + elif event.key == pg.K_DOWN: + sprite.rect.y += 5 + elif event.key == pg.K_UP: + sprite.rect.y -= 5 + + renderer.clear() + t += 1 + + img = sprite.image + img.angle += 1 + img.flipX = t % 50 < 25 + img.flipY = t % 100 < 50 + img.color[0] = int(255.0 * (0.5 + math.sin(0.5 * t + 10.0) / 2.0)) + img.alpha = int(255.0 * (0.5 + math.sin(0.1 * t) / 2.0)) + # img.draw(dstrect=(x, y, 5 * img.srcrect['w'], 5 * img.srcrect['h'])) + + group.draw(renderer) + + renderer.present() + + clock.tick(60) + win.title = str("FPS: {}".format(clock.get_fps())) + +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/stars.py b/.venv/lib/python3.8/site-packages/pygame/examples/stars.py new file mode 100644 index 0000000..1bd2ac6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/stars.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +""" pg.examples.stars + + We are all in the gutter, + but some of us are looking at the stars. + -- Oscar Wilde + +A simple starfield example. Note you can move the 'center' of +the starfield by leftclicking in the window. This example show +the basics of creating a window, simple pixel plotting, and input +event management. +""" +import random +import math +import pygame as pg + +# constants +WINSIZE = [640, 480] +WINCENTER = [320, 240] +NUMSTARS = 150 + + +def init_star(): + "creates new star values" + dir = random.randrange(100000) + velmult = random.random() * 0.6 + 0.4 + vel = [math.sin(dir) * velmult, math.cos(dir) * velmult] + return vel, WINCENTER[:] + + +def initialize_stars(): + "creates a new starfield" + stars = [] + for x in range(NUMSTARS): + star = init_star() + vel, pos = star + steps = random.randint(0, WINCENTER[0]) + pos[0] = pos[0] + (vel[0] * steps) + pos[1] = pos[1] + (vel[1] * steps) + vel[0] = vel[0] * (steps * 0.09) + vel[1] = vel[1] * (steps * 0.09) + stars.append(star) + move_stars(stars) + return stars + + +def draw_stars(surface, stars, color): + "used to draw (and clear) the stars" + for vel, pos in stars: + pos = (int(pos[0]), int(pos[1])) + surface.set_at(pos, color) + + +def move_stars(stars): + "animate the star values" + for vel, pos in stars: + pos[0] = pos[0] + vel[0] + pos[1] = pos[1] + vel[1] + if not 0 <= pos[0] <= WINSIZE[0] or not 0 <= pos[1] <= WINSIZE[1]: + vel[:], pos[:] = init_star() + else: + vel[0] = vel[0] * 1.05 + vel[1] = vel[1] * 1.05 + + +def main(): + "This is the starfield code" + # create our starfield + random.seed() + stars = initialize_stars() + clock = pg.time.Clock() + # initialize and prepare screen + pg.init() + screen = pg.display.set_mode(WINSIZE) + pg.display.set_caption("pygame Stars Example") + white = 255, 240, 200 + black = 20, 20, 40 + screen.fill(black) + + # main game loop + done = 0 + while not done: + draw_stars(screen, stars, black) + move_stars(stars) + draw_stars(screen, stars, white) + pg.display.update() + for e in pg.event.get(): + if e.type == pg.QUIT or (e.type == pg.KEYUP and e.key == pg.K_ESCAPE): + done = 1 + break + elif e.type == pg.MOUSEBUTTONDOWN and e.button == 1: + WINCENTER[:] = list(e.pos) + clock.tick(50) + pg.quit() + + +# if python says run, then we should run +if __name__ == "__main__": + main() + + # I prefer the time of insects to the time of stars. + # + # -- WisÅ‚awa Szymborska diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/testsprite.py b/.venv/lib/python3.8/site-packages/pygame/examples/testsprite.py new file mode 100644 index 0000000..825fb9e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/testsprite.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python +""" pg.examples.testsprite + +Like the testsprite.c that comes with libsdl, this pygame version shows +lots of sprites moving around. + +It is an abomination of ugly code, and mostly used for testing. + + +See pg.examples.aliens for some prettyier code. +""" +import sys +import os + +from random import randint +from time import time + +import pygame as pg + + +if "-psyco" in sys.argv: + try: + import psyco + + psyco.full() + except Exception: + print("No psyco for you! psyco failed to import and run.") + +main_dir = os.path.split(os.path.abspath(__file__))[0] +data_dir = os.path.join(main_dir, "data") + + +# use this to use update rects or not. +# If the screen is mostly full, then update rects are not useful. +update_rects = True +if "-update_rects" in sys.argv: + update_rects = True +if "-noupdate_rects" in sys.argv: + update_rects = False + +use_static = False +if "-static" in sys.argv: + use_static = True + + +use_layered_dirty = False +if "-layered_dirty" in sys.argv: + update_rects = True + use_layered_dirty = True + + +flags = 0 +if "-flip" in sys.argv: + flags ^= pg.DOUBLEBUF + +if "-fullscreen" in sys.argv: + flags ^= pg.FULLSCREEN + +if "-sw" in sys.argv: + flags ^= pg.SWSURFACE + +use_rle = True + +if "-hw" in sys.argv: + flags ^= pg.HWSURFACE + use_rle = False + +if "-scaled" in sys.argv: + flags ^= pg.SCALED + +screen_dims = [640, 480] + +if "-height" in sys.argv: + i = sys.argv.index("-height") + screen_dims[1] = int(sys.argv[i + 1]) + +if "-width" in sys.argv: + i = sys.argv.index("-width") + screen_dims[0] = int(sys.argv[i + 1]) + +if "-alpha" in sys.argv: + use_alpha = True +else: + use_alpha = False + +print(screen_dims) + + +##class Thingy(pg.sprite.Sprite): +## images = None +## def __init__(self): +## pg.sprite.Sprite.__init__(self) +## self.image = Thingy.images[0] +## self.rect = self.image.get_rect() +## self.rect.x = randint(0, screen_dims[0]) +## self.rect.y = randint(0, screen_dims[1]) +## #self.vel = [randint(-10, 10), randint(-10, 10)] +## self.vel = [randint(-1, 1), randint(-1, 1)] +## +## def move(self): +## for i in [0, 1]: +## nv = self.rect[i] + self.vel[i] +## if nv >= screen_dims[i] or nv < 0: +## self.vel[i] = -self.vel[i] +## nv = self.rect[i] + self.vel[i] +## self.rect[i] = nv + + +class Thingy(pg.sprite.DirtySprite): + images = None + + def __init__(self): + ## pg.sprite.Sprite.__init__(self) + pg.sprite.DirtySprite.__init__(self) + self.image = Thingy.images[0] + self.rect = self.image.get_rect() + self.rect.x = randint(0, screen_dims[0]) + self.rect.y = randint(0, screen_dims[1]) + # self.vel = [randint(-10, 10), randint(-10, 10)] + self.vel = [randint(-1, 1), randint(-1, 1)] + self.dirty = 2 + + def update(self): + for i in [0, 1]: + nv = self.rect[i] + self.vel[i] + if nv >= screen_dims[i] or nv < 0: + self.vel[i] = -self.vel[i] + nv = self.rect[i] + self.vel[i] + self.rect[i] = nv + + +class Static(pg.sprite.DirtySprite): + images = None + + def __init__(self): + pg.sprite.DirtySprite.__init__(self) + self.image = Static.images[0] + self.rect = self.image.get_rect() + self.rect.x = randint(0, 3 * screen_dims[0] / 4) + self.rect.y = randint(0, 3 * screen_dims[1] / 4) + + +def main( + update_rects=True, + use_static=False, + use_layered_dirty=False, + screen_dims=[640, 480], + use_alpha=False, + flags=0, +): + """Show lots of sprites moving around + + Optional keyword arguments: + update_rects - use the RenderUpdate sprite group class (default True) + use_static - include non-moving images (default False) + use_layered_dirty - Use the FastRenderGroup sprite group (default False) + screen_dims - Pygame window dimensions (default [640, 480]) + use_alpha - use alpha blending (default False) + flags - additional display mode flags (default no additional flags) + + """ + + if use_layered_dirty: + update_rects = True + + pg.init() # needed to initialise time module for get_ticks() + pg.display.init() + + # if "-fast" in sys.argv: + + screen = pg.display.set_mode(screen_dims, flags, vsync="-vsync" in sys.argv) + + # this is mainly for GP2X, so it can quit. + pg.joystick.init() + num_joysticks = pg.joystick.get_count() + if num_joysticks > 0: + stick = pg.joystick.Joystick(0) + stick.init() # now we will receive events for the joystick + + screen.fill([0, 0, 0]) + pg.display.flip() + sprite_surface = pg.image.load(os.path.join(data_dir, "asprite.bmp")) + sprite_surface2 = pg.image.load(os.path.join(data_dir, "static.png")) + + if use_rle: + sprite_surface.set_colorkey([0xFF, 0xFF, 0xFF], pg.SRCCOLORKEY | pg.RLEACCEL) + sprite_surface2.set_colorkey([0xFF, 0xFF, 0xFF], pg.SRCCOLORKEY | pg.RLEACCEL) + else: + sprite_surface.set_colorkey([0xFF, 0xFF, 0xFF], pg.SRCCOLORKEY) + sprite_surface2.set_colorkey([0xFF, 0xFF, 0xFF], pg.SRCCOLORKEY) + + if use_alpha: + sprite_surface = sprite_surface.convert_alpha() + sprite_surface2 = sprite_surface2.convert_alpha() + else: + sprite_surface = sprite_surface.convert() + sprite_surface2 = sprite_surface2.convert() + + Thingy.images = [sprite_surface] + if use_static: + Static.images = [sprite_surface2] + + if len(sys.argv) > 1: + try: + numsprites = int(sys.argv[-1]) + except Exception: + numsprites = 100 + else: + numsprites = 100 + sprites = None + if use_layered_dirty: + ## sprites = pg.sprite.FastRenderGroup() + sprites = pg.sprite.LayeredDirty() + else: + if update_rects: + sprites = pg.sprite.RenderUpdates() + else: + sprites = pg.sprite.Group() + + for i in range(0, numsprites): + if use_static and i % 2 == 0: + sprites.add(Static()) + sprites.add(Thingy()) + + frames = 0 + start = time() + + background = pg.Surface(screen.get_size()) + background = background.convert() + background.fill([0, 0, 0]) + + going = True + while going: + if not update_rects: + screen.fill([0, 0, 0]) + + ## for sprite in sprites: + ## sprite.move() + + if update_rects: + sprites.clear(screen, background) + sprites.update() + + rects = sprites.draw(screen) + if update_rects: + pg.display.update(rects) + else: + pg.display.flip() + + for event in pg.event.get(): + if event.type in [pg.QUIT, pg.KEYDOWN, pg.QUIT, pg.JOYBUTTONDOWN]: + going = False + + frames += 1 + end = time() + print("FPS: %f" % (frames / ((end - start)))) + pg.quit() + + +if __name__ == "__main__": + main(update_rects, use_static, use_layered_dirty, screen_dims, use_alpha, flags) diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/textinput.py b/.venv/lib/python3.8/site-packages/pygame/examples/textinput.py new file mode 100644 index 0000000..4e241d1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/textinput.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +""" pg.examples.textinput + +A little "console" where you can write in text. + +Shows how to use the TEXTEDITING and TEXTINPUT events. +""" +import sys +import pygame as pg +import pygame.freetype as freetype + +# Version check +if pg.get_sdl_version() < (2, 0, 0): + raise Exception("This example requires pygame 2.") + +###CONSTS +# Set to true or add 'showevent' in argv to see IME and KEYDOWN events +PRINT_EVENT = False +# frames per second, the general speed of the program +FPS = 50 +# size of window +WINDOWWIDTH, WINDOWHEIGHT = 640, 480 +BGCOLOR = (0, 0, 0) + +# position of chatlist and chatbox +CHATLIST_POS = pg.Rect(0, 20, WINDOWWIDTH, 400) +CHATBOX_POS = pg.Rect(0, 440, WINDOWWIDTH, 40) +CHATLIST_MAXSIZE = 20 + +TEXTCOLOR = (0, 255, 0) + +# Add fontname for each language, otherwise some text can't be correctly displayed. +FONTNAMES = [ + "notosanscjktcregular", + "notosansmonocjktcregular", + "notosansregular,", + "microsoftjhengheimicrosoftjhengheiuilight", + "microsoftyaheimicrosoftyaheiuilight", + "msgothicmsuigothicmspgothic", + "msmincho", + "Arial", +] + +# Initalize +pg.init() +Screen = pg.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) +pg.display.set_caption("TextInput example") +FPSClock = pg.time.Clock() + +# Freetype +# "The font name can be a comma separated list of font names to search for." +FONTNAMES = ",".join(str(x) for x in FONTNAMES) +Font = freetype.SysFont(FONTNAMES, 24) +FontSmall = freetype.SysFont(FONTNAMES, 16) +print("Using font: " + Font.name) + +# Main loop process +def main(): + global BGCOLOR, PRINT_EVENT, CHATBOX_POS, CHATLIST_POS, CHATLIST_MAXSIZE + global FPSClock, Font, Screen + + """ + https://wiki.libsdl.org/SDL_HINT_IME_INTERNAL_EDITING + https://wiki.libsdl.org/Tutorials/TextInput + Candidate list not showing due to SDL2 problem ;w; + """ + pg.key.start_text_input() + input_rect = pg.Rect(80, 80, 320, 40) + pg.key.set_text_input_rect(input_rect) + + _IMEEditing = False + _IMEText = "" + _IMETextPos = 0 + _IMEEditingText = "" + _IMEEditingPos = 0 + ChatList = [] + + while True: + for event in pg.event.get(): + if event.type == pg.QUIT: + pg.quit() + return + + elif event.type == pg.KEYDOWN: + if PRINT_EVENT: + print(event) + + if _IMEEditing: + if len(_IMEEditingText) == 0: + _IMEEditing = False + continue + + if event.key == pg.K_BACKSPACE: + if len(_IMEText) > 0 and _IMETextPos > 0: + _IMEText = ( + _IMEText[0 : _IMETextPos - 1] + _IMEText[_IMETextPos:] + ) + _IMETextPos = max(0, _IMETextPos - 1) + + elif event.key == pg.K_DELETE: + _IMEText = _IMEText[0:_IMETextPos] + _IMEText[_IMETextPos + 1 :] + elif event.key == pg.K_LEFT: + _IMETextPos = max(0, _IMETextPos - 1) + elif event.key == pg.K_RIGHT: + _IMETextPos = min(len(_IMEText), _IMETextPos + 1) + # Handle ENTER key + elif event.key in [pg.K_RETURN, pg.K_KP_ENTER]: + # Block if we have no text to append + if len(_IMEText) == 0: + continue + + # Append chat list + ChatList.append(_IMEText) + if len(ChatList) > CHATLIST_MAXSIZE: + ChatList.pop(0) + _IMEText = "" + _IMETextPos = 0 + + elif event.type == pg.TEXTEDITING: + if PRINT_EVENT: + print(event) + _IMEEditing = True + _IMEEditingText = event.text + _IMEEditingPos = event.start + + elif event.type == pg.TEXTINPUT: + if PRINT_EVENT: + print(event) + _IMEEditing = False + _IMEEditingText = "" + _IMEText = _IMEText[0:_IMETextPos] + event.text + _IMEText[_IMETextPos:] + _IMETextPos += len(event.text) + + # Screen updates + Screen.fill(BGCOLOR) + + # Chat List updates + chat_height = CHATLIST_POS.height / CHATLIST_MAXSIZE + for i, chat in enumerate(ChatList): + FontSmall.render_to( + Screen, + (CHATLIST_POS.x, CHATLIST_POS.y + i * chat_height), + chat, + TEXTCOLOR, + ) + + # Chat box updates + start_pos = CHATBOX_POS.copy() + ime_textL = ">" + _IMEText[0:_IMETextPos] + ime_textM = ( + _IMEEditingText[0:_IMEEditingPos] + "|" + _IMEEditingText[_IMEEditingPos:] + ) + ime_textR = _IMEText[_IMETextPos:] + + rect_textL = Font.render_to(Screen, start_pos, ime_textL, TEXTCOLOR) + start_pos.x += rect_textL.width + + # Editing texts should be underlined + rect_textM = Font.render_to( + Screen, start_pos, ime_textM, TEXTCOLOR, None, freetype.STYLE_UNDERLINE + ) + start_pos.x += rect_textM.width + Font.render_to(Screen, start_pos, ime_textR, TEXTCOLOR) + + pg.display.update() + + FPSClock.tick(FPS) + + +if __name__ == "__main__": + if "showevent" in sys.argv: + PRINT_EVENT = True + + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/vgrade.py b/.venv/lib/python3.8/site-packages/pygame/examples/vgrade.py new file mode 100644 index 0000000..9618c45 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/vgrade.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +""" pg.examples.vgrade + +This example demonstrates creating an image with numpy +python, and displaying that through SDL. You can look at the +method of importing numpy and pg.surfarray. This method +will fail 'gracefully' if it is not available. +I've tried mixing in a lot of comments where the code might +not be self explanatory, nonetheless it may still seem a bit +strange. Learning to use numpy for images like this takes a +bit of learning, but the payoff is extremely fast image +manipulation in python. + +For Pygame 1.9.2 and up, this example also showcases a new feature +of surfarray.blit_surface: array broadcasting. If a source array +has either a width or height of 1, the array is repeatedly blitted +to the surface along that dimension to fill the surface. In fact, +a (1, 1) or (1, 1, 3) array results in a simple surface color fill. + +Just so you know how this breaks down. For each sampling of +time, 30% goes to each creating the gradient and blitting the +array. The final 40% goes to flipping/updating the display surface + +The window will have no border decorations. + +The code also demonstrates use of the timer events. +""" + + +import os +import pygame as pg + +try: + import numpy as np + import numpy.random as np_random +except ImportError: + raise SystemExit("This example requires numpy and the pygame surfarray module") + +timer = 0 + + +def stopwatch(message=None): + "simple routine to time python code" + global timer + if not message: + timer = pg.time.get_ticks() + return + now = pg.time.get_ticks() + runtime = (now - timer) / 1000.0 + 0.001 + print("%s %s %s" % (message, runtime, ("seconds\t(%.2ffps)" % (1.0 / runtime)))) + timer = now + + +def VertGradientColumn(surf, topcolor, bottomcolor): + "creates a new 3d vertical gradient array" + topcolor = np.array(topcolor, copy=False) + bottomcolor = np.array(bottomcolor, copy=False) + diff = bottomcolor - topcolor + width, height = surf.get_size() + # create array from 0.0 to 1.0 triplets + column = np.arange(height, dtype="float") / height + column = np.repeat(column[:, np.newaxis], [3], 1) + # create a single column of gradient + column = topcolor + (diff * column).astype("int") + # make the column a 3d image column by adding X + column = column.astype("uint8")[np.newaxis, :, :] + # 3d array into 2d array + return pg.surfarray.map_array(surf, column) + + +def DisplayGradient(surf): + "choose random colors and show them" + stopwatch() + colors = np_random.randint(0, 255, (2, 3)) + column = VertGradientColumn(surf, colors[0], colors[1]) + pg.surfarray.blit_array(surf, column) + pg.display.flip() + stopwatch("Gradient:") + + +def main(): + pg.init() + pg.mixer.quit() # remove ALSA underflow messages for Debian squeeze + size = 600, 400 + os.environ["SDL_VIDEO_CENTERED"] = "1" + screen = pg.display.set_mode(size, pg.NOFRAME, 0) + + pg.event.set_blocked(pg.MOUSEMOTION) # keep our queue cleaner + pg.time.set_timer(pg.USEREVENT, 500) + + while 1: + event = pg.event.wait() + if event.type in (pg.QUIT, pg.KEYDOWN, pg.MOUSEBUTTONDOWN): + break + elif event.type == pg.USEREVENT: + DisplayGradient(screen) + + pg.quit() + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.8/site-packages/pygame/examples/video.py b/.venv/lib/python3.8/site-packages/pygame/examples/video.py new file mode 100644 index 0000000..3d4b9f1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/examples/video.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python +""" pg.examples.video + +Experimental! + +* dialog message boxes with messagebox. +* multiple windows with Window +* driver selection +* Renderer, Texture, and Image classes +* Drawing lines, rects, and such onto Renderers. +""" +import os +import pygame as pg + +if pg.get_sdl_version()[0] < 2: + raise SystemExit( + "This example requires pygame 2 and SDL2. _sdl2 is experimental and will change." + ) +from pygame._sdl2 import Window, Texture, Image, Renderer, get_drivers, messagebox + +data_dir = os.path.join(os.path.split(os.path.abspath(__file__))[0], "data") + + +def load_img(file): + return pg.image.load(os.path.join(data_dir, file)) + + +pg.display.init() +pg.key.set_repeat(1000, 10) + +for driver in get_drivers(): + print(driver) + +import random + +answer = messagebox( + "I will open two windows! Continue?", + "Hello!", + info=True, + buttons=("Yes", "No", "Chance"), + return_button=0, + escape_button=1, +) +if answer == 1 or (answer == 2 and random.random() < 0.5): + import sys + + sys.exit(0) + +win = Window("asdf", resizable=True) +renderer = Renderer(win) +tex = Texture.from_surface(renderer, load_img("alien1.gif")) + +running = True + +x, y = 250, 50 +clock = pg.time.Clock() + +backgrounds = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)] +bg_index = 0 + +renderer.draw_color = backgrounds[bg_index] + +win2 = Window("2nd window", size=(256, 256), always_on_top=True) +win2.opacity = 0.5 +win2.set_icon(load_img("bomb.gif")) +renderer2 = Renderer(win2) +tex2 = Texture.from_surface(renderer2, load_img("asprite.bmp")) +renderer2.clear() +tex2.draw() +renderer2.present() +del tex2 + +full = 0 + +tex = Image(tex) + + +surf = pg.Surface((64, 64)) +streamtex = Texture(renderer, (64, 64), streaming=True) +tex_update_interval = 1000 +next_tex_update = pg.time.get_ticks() + + +while running: + for event in pg.event.get(): + if event.type == pg.QUIT: + running = False + elif getattr(event, "window", None) == win2: + if ( + event.type == pg.KEYDOWN + and event.key == pg.K_ESCAPE + or event.type == pg.WINDOWCLOSE + ): + win2.destroy() + elif event.type == pg.KEYDOWN: + if event.key == pg.K_ESCAPE: + running = False + elif event.key == pg.K_LEFT: + x -= 5 + elif event.key == pg.K_RIGHT: + x += 5 + elif event.key == pg.K_DOWN: + y += 5 + elif event.key == pg.K_UP: + y -= 5 + elif event.key == pg.K_f: + if full == 0: + win.set_fullscreen(True) + full = 1 + else: + win.set_windowed() + full = 0 + elif event.key == pg.K_s: + readsurf = renderer.to_surface() + pg.image.save(readsurf, "test.png") + + elif event.key == pg.K_SPACE: + bg_index = (bg_index + 1) % len(backgrounds) + renderer.draw_color = backgrounds[bg_index] + + renderer.clear() + + # update texture + curtime = pg.time.get_ticks() + if curtime >= next_tex_update: + for x_ in range(streamtex.width // 4): + for y_ in range(streamtex.height // 4): + newcol = ( + random.randint(0, 255), + random.randint(0, 255), + random.randint(0, 255), + 255, + ) + area = (4 * x_, 4 * y_, 4, 4) + surf.fill(newcol, area) + streamtex.update(surf) + next_tex_update = curtime + tex_update_interval + streamtex.draw(dstrect=pg.Rect(64, 128, 64, 64)) + + tex.draw(dstrect=(x, y)) + + # TODO: should these be? + # - line instead of draw_line + # - point instead of draw_point + # - rect(rect, width=1)->draw 1 pixel, instead of draw_rect + # - rect(rect, width=0)->filled ? , instead of fill_rect + # + # TODO: should these work with pg.draw.line(renderer, ...) functions? + renderer.draw_color = (255, 255, 255, 255) + renderer.draw_line((0, 0), (64, 64)) + renderer.draw_line((64, 64), (128, 0)) + renderer.draw_point((72, 32)) + renderer.draw_rect(pg.Rect(0, 64, 64, 64)) + renderer.fill_rect(pg.Rect(0, 128, 64, 64)) + renderer.draw_color = backgrounds[bg_index] + + renderer.present() + + clock.tick(60) + win.title = str("FPS: {}".format(clock.get_fps())) + +pg.quit() diff --git a/.venv/lib/python3.8/site-packages/pygame/fastevent.py b/.venv/lib/python3.8/site-packages/pygame/fastevent.py new file mode 100644 index 0000000..e102fc4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/fastevent.py @@ -0,0 +1,88 @@ +""" +A compatibility shim for pygame.fastevent based on pygame.event. +This module was deprecated in pygame 2.2, and is scheduled for removal in a +future pygame version. If you are using pygame.fastevent, please migrate to +using regular pygame.event module +""" + +import pygame.event +import pygame.display +from pygame import error, register_quit +from pygame.event import Event + +_ft_init = False + + +def _ft_init_check(): + """ + Raises error if module is not init + """ + if not _ft_init: + raise error("fastevent system not initialized") + + +def _quit_hook(): + """ + Hook that gets run to quit module + """ + global _ft_init + _ft_init = False + + +def init(): + """init() -> None + initialize pygame.fastevent + """ + global _ft_init + if not pygame.display.get_init(): + raise error("video system not initialized") + + register_quit(_quit_hook) + _ft_init = True + + +def get_init(): + """get_init() -> bool + returns True if the fastevent module is currently initialized + """ + return _ft_init + + +def pump(): + """pump() -> None + internally process pygame event handlers + """ + _ft_init_check() + pygame.event.pump() + + +def wait(): + """wait() -> Event + wait for an event + """ + _ft_init_check() + return pygame.event.wait() + + +def poll(): + """poll() -> Event + get an available event + """ + _ft_init_check() + return pygame.event.poll() + + +def get(): + """get() -> list of Events + get all events from the queue + """ + _ft_init_check() + return pygame.event.get() + + +def post(event: Event): + """post(Event) -> None + place an event on the queue + """ + _ft_init_check() + pygame.event.post(event) diff --git a/.venv/lib/python3.8/site-packages/pygame/fastevent.pyi b/.venv/lib/python3.8/site-packages/pygame/fastevent.pyi new file mode 100644 index 0000000..9ef2e26 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/fastevent.pyi @@ -0,0 +1,11 @@ +from typing import List + +from pygame.event import Event + +def init() -> None: ... +def get_init() -> bool: ... +def pump() -> None: ... +def wait() -> Event: ... +def pool() -> Event: ... +def get() -> List[Event]: ... +def post(event: Event) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/font.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/font.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..89471a6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/font.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/font.pyi b/.venv/lib/python3.8/site-packages/pygame/font.pyi new file mode 100644 index 0000000..9002ed7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/font.pyi @@ -0,0 +1,50 @@ +from typing import Hashable, Iterable, List, Optional, Tuple, Union + +from pygame.surface import Surface + +from ._common import _ColorValue, _FileArg + +def init() -> None: ... +def quit() -> None: ... +def get_init() -> bool: ... +def get_default_font() -> str: ... +def get_fonts() -> List[str]: ... +def match_font( + name: Union[str, bytes, Iterable[Union[str, bytes]]], + bold: Hashable = False, + italic: Hashable = False, +) -> str: ... +def SysFont( + name: Union[str, bytes, Iterable[Union[str, bytes]]], + size: int, + bold: Hashable = False, + italic: Hashable = False, +) -> Font: ... + +class Font(object): + + bold: bool + italic: bool + underline: bool + def __init__(self, name: Optional[_FileArg], size: int) -> None: ... + def render( + self, + text: Union[str, bytes], + antialias: bool, + color: _ColorValue, + background: Optional[_ColorValue] = None, + ) -> Surface: ... + def size(self, text: Union[str, bytes]) -> Tuple[int, int]: ... + def set_underline(self, value: bool) -> None: ... + def get_underline(self) -> bool: ... + def set_bold(self, value: bool) -> None: ... + def get_bold(self) -> bool: ... + def set_italic(self, value: bool) -> None: ... + def metrics( + self, text: Union[str, bytes] + ) -> List[Tuple[int, int, int, int, int]]: ... + def get_italic(self) -> bool: ... + def get_linesize(self) -> int: ... + def get_height(self) -> int: ... + def get_ascent(self) -> int: ... + def get_descent(self) -> int: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/freesansbold.ttf b/.venv/lib/python3.8/site-packages/pygame/freesansbold.ttf new file mode 100644 index 0000000..a98562f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/freesansbold.ttf differ diff --git a/.venv/lib/python3.8/site-packages/pygame/freetype.py b/.venv/lib/python3.8/site-packages/pygame/freetype.py new file mode 100644 index 0000000..c1b5f59 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/freetype.py @@ -0,0 +1,78 @@ +"""Enhanced Pygame module for loading and rendering computer fonts""" + +from pygame._freetype import ( + Font, + STYLE_NORMAL, + STYLE_OBLIQUE, + STYLE_STRONG, + STYLE_UNDERLINE, + STYLE_WIDE, + STYLE_DEFAULT, + init, + quit, + get_init, + was_init, + get_cache_size, + get_default_font, + get_default_resolution, + get_error, + get_version, + set_default_resolution, +) +from pygame.sysfont import match_font, get_fonts, SysFont as _SysFont + +__all__ = [ + "Font", + "STYLE_NORMAL", + "STYLE_OBLIQUE", + "STYLE_STRONG", + "STYLE_UNDERLINE", + "STYLE_WIDE", + "STYLE_DEFAULT", + "init", + "quit", + "get_init", + "was_init", + "get_cache_size", + "get_default_font", + "get_default_resolution", + "get_error", + "get_version", + "set_default_resolution", + "match_font", + "get_fonts", +] + + +def SysFont(name, size, bold=0, italic=0, constructor=None): + """pygame.ftfont.SysFont(name, size, bold=False, italic=False, constructor=None) -> Font + Create a pygame Font from system font resources. + + This will search the system fonts for the given font + name. You can also enable bold or italic styles, and + the appropriate system font will be selected if available. + + This will always return a valid Font object, and will + fallback on the builtin pygame font if the given font + is not found. + + Name can also be an iterable of font names, a string of + comma-separated font names, or a bytes of comma-separated + font names, in which case the set of names will be searched + in order. Pygame uses a small set of common font aliases. If the + specific font you ask for is not available, a reasonable + alternative may be used. + + If optional constructor is provided, it must be a function with + signature constructor(fontpath, size, bold, italic) which returns + a Font instance. If None, a pygame.freetype.Font object is created. + """ + if constructor is None: + + def constructor(fontpath, size, bold, italic): + font = Font(fontpath, size) + font.strong = bold + font.oblique = italic + return font + + return _SysFont(name, size, bold, italic, constructor) diff --git a/.venv/lib/python3.8/site-packages/pygame/freetype.pyi b/.venv/lib/python3.8/site-packages/pygame/freetype.pyi new file mode 100644 index 0000000..dd77f55 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/freetype.pyi @@ -0,0 +1,122 @@ +from typing import Any, Iterable, List, Optional, Text, Tuple, Union + +from pygame.color import Color +from pygame.font import Font +from pygame.rect import Rect +from pygame.surface import Surface + +from ._common import _ColorValue, _FileArg, _CanBeRect + +def get_error() -> str: ... +def get_version() -> Tuple[int, int, int]: ... +def init(cache_size: int = 64, resolution: int = 72) -> None: ... +def quit() -> None: ... +def get_init() -> bool: ... +def was_init() -> bool: ... +def get_cache_size() -> int: ... +def get_default_resolution() -> int: ... +def set_default_resolution(resolution: int) -> None: ... +def SysFont( + name: Union[str, bytes, Iterable[Union[str, bytes]]], + size: int, + bold: int = False, + italic: int = False, +) -> Font: ... +def get_default_font() -> str: ... + +STYLE_NORMAL: int +STYLE_UNDERLINE: int +STYLE_OBLIQUE: int +STYLE_STRONG: int +STYLE_WIDE: int +STYLE_DEFAULT: int + +class Font: + name: str + path: Text + size: Union[float, Tuple[float, float]] + height: int + ascender: int + descender: int + style: int + underline: bool + strong: bool + oblique: bool + wide: bool + strength: float + underline_adjustment: float + fixed_width: bool + fixed_sizes: int + scalable: bool + use_bitmap_strikes: bool + antialiased: bool + kerning: bool + vertical: bool + rotation: int + fgcolor: Color + bgcolor: Color + origin: bool + pad: bool + ucs4: bool + resolution: int + def __init__( + self, + file: Optional[_FileArg], + size: float = 0, + font_index: int = 0, + resolution: int = 0, + ucs4: int = False, + ) -> None: ... + def get_rect( + self, + text: str, + style: int = STYLE_DEFAULT, + rotation: int = 0, + size: float = 0, + ) -> Rect: ... + def get_metrics( + self, text: str, size: float = 0 + ) -> List[Tuple[int, int, int, int, float, float]]: ... + def get_sized_ascender(self, size: float) -> int: ... + def get_sized_descender(self, size: float) -> int: ... + def get_sized_height(self, size: float) -> int: ... + def get_sized_glyph_height(self, size: float) -> int: ... + def get_sizes(self) -> List[Tuple[int, int, int, float, float]]: ... + def render( + self, + text: str, + fgcolor: Optional[_ColorValue] = None, + bgcolor: Optional[_ColorValue] = None, + style: int = STYLE_DEFAULT, + rotation: int = 0, + size: float = 0, + ) -> Tuple[Surface, Rect]: ... + def render_to( + self, + surf: Surface, + dest: _CanBeRect, + text: str, + fgcolor: Optional[_ColorValue] = None, + bgcolor: Optional[_ColorValue] = None, + style: int = STYLE_DEFAULT, + rotation: int = 0, + size: float = 0, + ) -> Rect: ... + def render_raw( + self, + text: str, + style: int = STYLE_DEFAULT, + rotation: int = 0, + size: float = 0, + invert: bool = False, + ) -> Tuple[bytes, Tuple[int, int]]: ... + def render_raw_to( + self, + array: Any, + text: str, + dest: Optional[_CanBeRect] = None, + style: int = STYLE_DEFAULT, + rotation: int = 0, + size: float = 0, + invert: bool = False, + ) -> Rect: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/ftfont.py b/.venv/lib/python3.8/site-packages/pygame/ftfont.py new file mode 100644 index 0000000..e648ede --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/ftfont.py @@ -0,0 +1,203 @@ +"""pygame module for loading and rendering fonts (freetype alternative)""" + +__all__ = [ + "Font", + "init", + "quit", + "get_default_font", + "get_init", + "SysFont", + "match_font", + "get_fonts", +] + +from pygame._freetype import init, Font as _Font, get_default_resolution +from pygame._freetype import quit, get_default_font, get_init as _get_init +from pygame._freetype import __PYGAMEinit__ +from pygame.sysfont import match_font, get_fonts, SysFont as _SysFont +from pygame import encode_file_path + + +class Font(_Font): + """Font(filename, size) -> Font + Font(object, size) -> Font + create a new Font object from a file (freetype alternative) + + This Font type differs from font.Font in that it can render glyphs + for Unicode code points in the supplementary planes (> 0xFFFF). + """ + + __encode_file_path = staticmethod(encode_file_path) + __get_default_resolution = staticmethod(get_default_resolution) + __default_font = encode_file_path(get_default_font()) + + __unull = "\x00" + __bnull = b"\x00" + + def __init__(self, file, size=-1): + size = max(size, 1) + if isinstance(file, str): + try: + bfile = self.__encode_file_path(file, ValueError) + except ValueError: + bfile = "" + else: + bfile = file + if isinstance(bfile, bytes) and bfile == self.__default_font: + file = None + if file is None: + resolution = int(self.__get_default_resolution() * 0.6875) + if resolution == 0: + resolution = 1 + else: + resolution = 0 + super(Font, self).__init__(file, size=size, resolution=resolution) + self.strength = 1.0 / 12.0 + self.kerning = False + self.origin = True + self.pad = True + self.ucs4 = True + self.underline_adjustment = 1.0 + + def render(self, text, antialias, color, background=None): + """render(text, antialias, color, background=None) -> Surface + draw text on a new Surface""" + + if text is None: + text = "" + if isinstance(text, str) and self.__unull in text: + raise ValueError("A null character was found in the text") + if isinstance(text, bytes) and self.__bnull in text: + raise ValueError("A null character was found in the text") + save_antialiased = ( + self.antialiased # pylint: disable = access-member-before-definition + ) + self.antialiased = bool(antialias) + try: + s, _ = super(Font, self).render(text, color, background) + return s + finally: + self.antialiased = save_antialiased + + def set_bold(self, value): + """set_bold(bool) -> None + enable fake rendering of bold text""" + + self.wide = bool(value) + + def get_bold(self): + """get_bold() -> bool + check if text will be rendered bold""" + + return self.wide + + bold = property(get_bold, set_bold) + + def set_italic(self, value): + """set_italic(bool) -> None + enable fake rendering of italic text""" + + self.oblique = bool(value) + + def get_italic(self): + """get_italic() -> bool + check if the text will be rendered italic""" + + return self.oblique + + italic = property(get_italic, set_italic) + + def set_underline(self, value): + """set_underline(bool) -> None + control if text is rendered with an underline""" + + self.underline = bool(value) + + def get_underline(self): + """set_bold(bool) -> None + enable fake rendering of bold text""" + + return self.underline + + def metrics(self, text): + """metrics(text) -> list + Gets the metrics for each character in the passed string.""" + + return self.get_metrics(text) + + def get_ascent(self): + """get_ascent() -> int + get the ascent of the font""" + + return self.get_sized_ascender() + + def get_descent(self): + """get_descent() -> int + get the descent of the font""" + + return self.get_sized_descender() + + def get_height(self): + """get_height() -> int + get the height of the font""" + + return self.get_sized_ascender() - self.get_sized_descender() + 1 + + def get_linesize(self): + """get_linesize() -> int + get the line space of the font text""" + + return self.get_sized_height() + + def size(self, text): + """size(text) -> (width, height) + determine the amount of space needed to render text""" + + return self.get_rect(text).size + + +FontType = Font + + +def get_init(): + """get_init() -> bool + true if the font module is initialized""" + + return _get_init() + + +def SysFont(name, size, bold=0, italic=0, constructor=None): + """pygame.ftfont.SysFont(name, size, bold=False, italic=False, constructor=None) -> Font + Create a pygame Font from system font resources. + + This will search the system fonts for the given font + name. You can also enable bold or italic styles, and + the appropriate system font will be selected if available. + + This will always return a valid Font object, and will + fallback on the builtin pygame font if the given font + is not found. + + Name can also be an iterable of font names, a string of + comma-separated font names, or a bytes of comma-separated + font names, in which case the set of names will be searched + in order. Pygame uses a small set of common font aliases. If the + specific font you ask for is not available, a reasonable + alternative may be used. + + If optional constructor is provided, it must be a function with + signature constructor(fontpath, size, bold, italic) which returns + a Font instance. If None, a pygame.ftfont.Font object is created. + """ + if constructor is None: + + def constructor(fontpath, size, bold, italic): + font = Font(fontpath, size) + font.set_bold(bold) + font.set_italic(italic) + return font + + return _SysFont(name, size, bold, italic, constructor) + + +del _Font, get_default_resolution, encode_file_path diff --git a/.venv/lib/python3.8/site-packages/pygame/gfxdraw.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/gfxdraw.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..ae6fc6f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/gfxdraw.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/gfxdraw.pyi b/.venv/lib/python3.8/site-packages/pygame/gfxdraw.pyi new file mode 100644 index 0000000..7da2d0f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/gfxdraw.pyi @@ -0,0 +1,91 @@ +from typing import Sequence + +from pygame.surface import Surface + +from ._common import _ColorValue, _Coordinate, _RectValue + +def pixel(surface: Surface, x: int, y: int, color: _ColorValue) -> None: ... +def hline(surface: Surface, x1: int, x2: int, y: int, color: _ColorValue) -> None: ... +def vline(surface: Surface, x: int, y1: int, y2: int, color: _ColorValue) -> None: ... +def line( + surface: Surface, x1: int, y1: int, x2: int, y2: int, color: _ColorValue +) -> None: ... +def rectangle(surface: Surface, rect: _RectValue, color: _ColorValue) -> None: ... +def box(surface: Surface, rect: _RectValue, color: _ColorValue) -> None: ... +def circle(surface: Surface, x: int, y: int, r: int, color: _ColorValue) -> None: ... +def aacircle(surface: Surface, x: int, y: int, r: int, color: _ColorValue) -> None: ... +def filled_circle( + surface: Surface, x: int, y: int, r: int, color: _ColorValue +) -> None: ... +def ellipse( + surface: Surface, x: int, y: int, rx: int, ry: int, color: _ColorValue +) -> None: ... +def aaellipse( + surface: Surface, x: int, y: int, rx: int, ry: int, color: _ColorValue +) -> None: ... +def filled_ellipse( + surface: Surface, x: int, y: int, rx: int, ry: int, color: _ColorValue +) -> None: ... +def arc( + surface: Surface, + x: int, + y: int, + r: int, + start_angle: int, + atp_angle: int, + color: _ColorValue, +) -> None: ... +def pie( + surface: Surface, + x: int, + y: int, + r: int, + start_angle: int, + atp_angle: int, + color: _ColorValue, +) -> None: ... +def trigon( + surface: Surface, + x1: int, + y1: int, + x2: int, + y2: int, + x3: int, + y3: int, + color: _ColorValue, +) -> None: ... +def aatrigon( + surface: Surface, + x1: int, + y1: int, + x2: int, + y2: int, + x3: int, + y3: int, + color: _ColorValue, +) -> None: ... +def filled_trigon( + surface: Surface, + x1: int, + y1: int, + x2: int, + y2: int, + x3: int, + y3: int, + color: _ColorValue, +) -> None: ... +def polygon( + surface: Surface, points: Sequence[_Coordinate], color: _ColorValue +) -> None: ... +def aapolygon( + surface: Surface, points: Sequence[_Coordinate], color: _ColorValue +) -> None: ... +def filled_polygon( + surface: Surface, points: Sequence[_Coordinate], color: _ColorValue +) -> None: ... +def textured_polygon( + surface: Surface, points: Sequence[_Coordinate], texture: Surface, tx: int, ty: int +) -> None: ... +def bezier( + surface: Surface, points: Sequence[_Coordinate], steps: int, color: _ColorValue +) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/image.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/image.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..17d2923 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/image.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/image.pyi b/.venv/lib/python3.8/site-packages/pygame/image.pyi new file mode 100644 index 0000000..b99a711 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/image.pyi @@ -0,0 +1,35 @@ +from typing import List, Literal, Tuple, Union + +from pygame.bufferproxy import BufferProxy +from pygame.surface import Surface + +from ._common import _FileArg + +_BufferStyle = Union[BufferProxy, bytes, bytearray, memoryview] +_to_string_format = Literal[ + "P", "RGB", "RGBX", "RGBA", "ARGB", "RGBA_PREMULT", "ARGB_PREMULT" +] +_from_buffer_format = Literal["P", "RGB", "BGR", "RGBX", "RGBA", "ARGB"] +_from_string_format = Literal["P", "RGB", "RGBX", "RGBA", "ARGB"] + +def load(filename: _FileArg, namehint: str = "") -> Surface: ... +def save(surface: Surface, filename: _FileArg, namehint: str = "") -> None: ... +def get_sdl_image_version() -> Union[None, Tuple[int, int, int]]: ... +def get_extended() -> bool: ... +def tostring( + surface: Surface, format: _to_string_format, flipped: bool = False +) -> str: ... +def fromstring( + string: str, + size: Union[List[int], Tuple[int, int]], + format: _from_string_format, + flipped: bool = False, +) -> Surface: ... +def frombuffer( + bytes: _BufferStyle, + size: Union[List[int], Tuple[int, int]], + format: _from_buffer_format, +) -> Surface: ... +def load_basic(filename: _FileArg) -> Surface: ... +def load_extended(filename: _FileArg, namehint: str = "") -> Surface: ... +def save_extended(surface: Surface, filename: _FileArg, namehint: str = "") -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/imageext.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/imageext.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..19cdd2e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/imageext.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/joystick.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/joystick.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..7bddf03 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/joystick.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/joystick.pyi b/.venv/lib/python3.8/site-packages/pygame/joystick.pyi new file mode 100644 index 0000000..9ca8532 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/joystick.pyi @@ -0,0 +1,29 @@ +from typing import Tuple + +def init() -> None: ... +def quit() -> None: ... +def get_init() -> bool: ... +def get_count() -> int: ... + +class Joystick(object): + def __init__(self, id: int) -> None: ... + def init(self) -> None: ... + def quit(self) -> None: ... + def get_init(self) -> bool: ... + def get_id(self) -> int: ... + def get_instance_id(self) -> int: ... + def get_guid(self) -> str: ... + def get_power_level(self) -> str: ... + def get_name(self) -> str: ... + def get_numaxes(self) -> int: ... + def get_axis(self, axis_number: int) -> float: ... + def get_numballs(self) -> int: ... + def get_ball(self, ball_number: int) -> Tuple[float, float]: ... + def get_numbuttons(self) -> int: ... + def get_button(self, button: int) -> bool: ... + def get_numhats(self) -> int: ... + def get_hat(self, hat_number: int) -> Tuple[float, float]: ... + def rumble( + self, low_frequency: float, high_frequency: float, duration: int + ) -> bool: ... + def stop_rumble(self) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/key.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/key.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..b02a083 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/key.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/key.pyi b/.venv/lib/python3.8/site-packages/pygame/key.pyi new file mode 100644 index 0000000..d93beec --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/key.pyi @@ -0,0 +1,15 @@ +from typing import Sequence, Tuple + +from ._common import _RectValue + +def get_focused() -> bool: ... +def get_pressed() -> Sequence[bool]: ... +def get_mods() -> int: ... +def set_mods(mods: int) -> None: ... +def set_repeat(delay: int = 0, interval: int = 0) -> None: ... +def get_repeat() -> Tuple[int, int]: ... +def name(key: int) -> str: ... +def key_code(name: str) -> int: ... +def start_text_input() -> None: ... +def stop_text_input() -> None: ... +def set_text_input_rect(rect: _RectValue) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/locals.py b/.venv/lib/python3.8/site-packages/pygame/locals.py new file mode 100644 index 0000000..66e6c24 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/locals.py @@ -0,0 +1,576 @@ +# pygame - Python Game Library +# Copyright (C) 2000-2003 Pete Shinners +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Pete Shinners +# pete@shinners.org + + +"""Set of functions from PyGame that are handy to have in +the local namespace for your module""" + +from pygame.constants import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +from pygame.rect import Rect +from pygame import color + +Color = color.Color + + +__all__ = [ + "Rect", + "Color", + "ACTIVEEVENT", + "ANYFORMAT", + "APPACTIVE", + "APPFOCUSMOUSE", + "APPINPUTFOCUS", + "ASYNCBLIT", + "AUDIODEVICEADDED", + "AUDIODEVICEREMOVED", + "AUDIO_ALLOW_ANY_CHANGE", + "AUDIO_ALLOW_CHANNELS_CHANGE", + "AUDIO_ALLOW_FORMAT_CHANGE", + "AUDIO_ALLOW_FREQUENCY_CHANGE", + "AUDIO_S16", + "AUDIO_S16LSB", + "AUDIO_S16MSB", + "AUDIO_S16SYS", + "AUDIO_S8", + "AUDIO_U16", + "AUDIO_U16LSB", + "AUDIO_U16MSB", + "AUDIO_U16SYS", + "AUDIO_U8", + "BIG_ENDIAN", + "BLENDMODE_ADD", + "BLENDMODE_BLEND", + "BLENDMODE_MOD", + "BLENDMODE_NONE", + "BLEND_ADD", + "BLEND_MAX", + "BLEND_MIN", + "BLEND_MULT", + "BLEND_PREMULTIPLIED", + "BLEND_ALPHA_SDL2", + "BLEND_RGBA_ADD", + "BLEND_RGBA_MAX", + "BLEND_RGBA_MIN", + "BLEND_RGBA_MULT", + "BLEND_RGBA_SUB", + "BLEND_RGB_ADD", + "BLEND_RGB_MAX", + "BLEND_RGB_MIN", + "BLEND_RGB_MULT", + "BLEND_RGB_SUB", + "BLEND_SUB", + "BUTTON_LEFT", + "BUTTON_MIDDLE", + "BUTTON_RIGHT", + "BUTTON_WHEELDOWN", + "BUTTON_WHEELUP", + "BUTTON_X1", + "BUTTON_X2", + "CONTROLLERAXISMOTION", + "CONTROLLERBUTTONDOWN", + "CONTROLLERBUTTONUP", + "CONTROLLERDEVICEADDED", + "CONTROLLERDEVICEREMAPPED", + "CONTROLLERDEVICEREMOVED", + "CONTROLLERTOUCHPADDOWN", + "CONTROLLERTOUCHPADMOTION", + "CONTROLLERTOUCHPADUP", + "CONTROLLER_AXIS_INVALID", + "CONTROLLER_AXIS_LEFTX", + "CONTROLLER_AXIS_LEFTY", + "CONTROLLER_AXIS_MAX", + "CONTROLLER_AXIS_RIGHTX", + "CONTROLLER_AXIS_RIGHTY", + "CONTROLLER_AXIS_TRIGGERLEFT", + "CONTROLLER_AXIS_TRIGGERRIGHT", + "CONTROLLER_BUTTON_A", + "CONTROLLER_BUTTON_B", + "CONTROLLER_BUTTON_BACK", + "CONTROLLER_BUTTON_DPAD_DOWN", + "CONTROLLER_BUTTON_DPAD_LEFT", + "CONTROLLER_BUTTON_DPAD_RIGHT", + "CONTROLLER_BUTTON_DPAD_UP", + "CONTROLLER_BUTTON_GUIDE", + "CONTROLLER_BUTTON_INVALID", + "CONTROLLER_BUTTON_LEFTSHOULDER", + "CONTROLLER_BUTTON_LEFTSTICK", + "CONTROLLER_BUTTON_MAX", + "CONTROLLER_BUTTON_RIGHTSHOULDER", + "CONTROLLER_BUTTON_RIGHTSTICK", + "CONTROLLER_BUTTON_START", + "CONTROLLER_BUTTON_X", + "CONTROLLER_BUTTON_Y", + "DOUBLEBUF", + "DROPBEGIN", + "DROPCOMPLETE", + "DROPFILE", + "DROPTEXT", + "FINGERDOWN", + "FINGERMOTION", + "FINGERUP", + "FULLSCREEN", + "GL_ACCELERATED_VISUAL", + "GL_ACCUM_ALPHA_SIZE", + "GL_ACCUM_BLUE_SIZE", + "GL_ACCUM_GREEN_SIZE", + "GL_ACCUM_RED_SIZE", + "GL_ALPHA_SIZE", + "GL_BLUE_SIZE", + "GL_BUFFER_SIZE", + "GL_CONTEXT_DEBUG_FLAG", + "GL_CONTEXT_FLAGS", + "GL_CONTEXT_FORWARD_COMPATIBLE_FLAG", + "GL_CONTEXT_MAJOR_VERSION", + "GL_CONTEXT_MINOR_VERSION", + "GL_CONTEXT_PROFILE_COMPATIBILITY", + "GL_CONTEXT_PROFILE_CORE", + "GL_CONTEXT_PROFILE_ES", + "GL_CONTEXT_PROFILE_MASK", + "GL_CONTEXT_RELEASE_BEHAVIOR", + "GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH", + "GL_CONTEXT_RELEASE_BEHAVIOR_NONE", + "GL_CONTEXT_RESET_ISOLATION_FLAG", + "GL_CONTEXT_ROBUST_ACCESS_FLAG", + "GL_DEPTH_SIZE", + "GL_DOUBLEBUFFER", + "GL_FRAMEBUFFER_SRGB_CAPABLE", + "GL_GREEN_SIZE", + "GL_MULTISAMPLEBUFFERS", + "GL_MULTISAMPLESAMPLES", + "GL_RED_SIZE", + "GL_SHARE_WITH_CURRENT_CONTEXT", + "GL_STENCIL_SIZE", + "GL_STEREO", + "GL_SWAP_CONTROL", + "HAT_CENTERED", + "HAT_DOWN", + "HAT_LEFT", + "HAT_LEFTDOWN", + "HAT_LEFTUP", + "HAT_RIGHT", + "HAT_RIGHTDOWN", + "HAT_RIGHTUP", + "HAT_UP", + "HIDDEN", + "HWACCEL", + "HWPALETTE", + "HWSURFACE", + "JOYAXISMOTION", + "JOYBALLMOTION", + "JOYBUTTONDOWN", + "JOYBUTTONUP", + "JOYHATMOTION", + "JOYDEVICEADDED", + "JOYDEVICEREMOVED", + "KEYDOWN", + "KEYUP", + "KMOD_ALT", + "KMOD_CAPS", + "KMOD_CTRL", + "KMOD_GUI", + "KMOD_LALT", + "KMOD_LCTRL", + "KMOD_LGUI", + "KMOD_LMETA", + "KMOD_LSHIFT", + "KMOD_META", + "KMOD_MODE", + "KMOD_NONE", + "KMOD_NUM", + "KMOD_RALT", + "KMOD_RCTRL", + "KMOD_RGUI", + "KMOD_RMETA", + "KMOD_RSHIFT", + "KMOD_SHIFT", + "KSCAN_0", + "KSCAN_1", + "KSCAN_2", + "KSCAN_3", + "KSCAN_4", + "KSCAN_5", + "KSCAN_6", + "KSCAN_7", + "KSCAN_8", + "KSCAN_9", + "KSCAN_A", + "KSCAN_APOSTROPHE", + "KSCAN_B", + "KSCAN_BACKSLASH", + "KSCAN_BACKSPACE", + "KSCAN_BREAK", + "KSCAN_C", + "KSCAN_CAPSLOCK", + "KSCAN_CLEAR", + "KSCAN_COMMA", + "KSCAN_CURRENCYSUBUNIT", + "KSCAN_CURRENCYUNIT", + "KSCAN_D", + "KSCAN_DELETE", + "KSCAN_DOWN", + "KSCAN_E", + "KSCAN_END", + "KSCAN_EQUALS", + "KSCAN_ESCAPE", + "KSCAN_EURO", + "KSCAN_F", + "KSCAN_F1", + "KSCAN_F10", + "KSCAN_F11", + "KSCAN_F12", + "KSCAN_F13", + "KSCAN_F14", + "KSCAN_F15", + "KSCAN_F2", + "KSCAN_F3", + "KSCAN_F4", + "KSCAN_F5", + "KSCAN_F6", + "KSCAN_F7", + "KSCAN_F8", + "KSCAN_F9", + "KSCAN_G", + "KSCAN_GRAVE", + "KSCAN_H", + "KSCAN_HELP", + "KSCAN_HOME", + "KSCAN_I", + "KSCAN_INSERT", + "KSCAN_INTERNATIONAL1", + "KSCAN_INTERNATIONAL2", + "KSCAN_INTERNATIONAL3", + "KSCAN_INTERNATIONAL4", + "KSCAN_INTERNATIONAL5", + "KSCAN_INTERNATIONAL6", + "KSCAN_INTERNATIONAL7", + "KSCAN_INTERNATIONAL8", + "KSCAN_INTERNATIONAL9", + "KSCAN_J", + "KSCAN_K", + "KSCAN_KP0", + "KSCAN_KP1", + "KSCAN_KP2", + "KSCAN_KP3", + "KSCAN_KP4", + "KSCAN_KP5", + "KSCAN_KP6", + "KSCAN_KP7", + "KSCAN_KP8", + "KSCAN_KP9", + "KSCAN_KP_0", + "KSCAN_KP_1", + "KSCAN_KP_2", + "KSCAN_KP_3", + "KSCAN_KP_4", + "KSCAN_KP_5", + "KSCAN_KP_6", + "KSCAN_KP_7", + "KSCAN_KP_8", + "KSCAN_KP_9", + "KSCAN_KP_DIVIDE", + "KSCAN_KP_ENTER", + "KSCAN_KP_EQUALS", + "KSCAN_KP_MINUS", + "KSCAN_KP_MULTIPLY", + "KSCAN_KP_PERIOD", + "KSCAN_KP_PLUS", + "KSCAN_L", + "KSCAN_LALT", + "KSCAN_LANG1", + "KSCAN_LANG2", + "KSCAN_LANG3", + "KSCAN_LANG4", + "KSCAN_LANG5", + "KSCAN_LANG6", + "KSCAN_LANG7", + "KSCAN_LANG8", + "KSCAN_LANG9", + "KSCAN_LCTRL", + "KSCAN_LEFT", + "KSCAN_LEFTBRACKET", + "KSCAN_LGUI", + "KSCAN_LMETA", + "KSCAN_LSHIFT", + "KSCAN_LSUPER", + "KSCAN_M", + "KSCAN_MENU", + "KSCAN_MINUS", + "KSCAN_MODE", + "KSCAN_N", + "KSCAN_NONUSBACKSLASH", + "KSCAN_NONUSHASH", + "KSCAN_NUMLOCK", + "KSCAN_NUMLOCKCLEAR", + "KSCAN_O", + "KSCAN_P", + "KSCAN_PAGEDOWN", + "KSCAN_PAGEUP", + "KSCAN_PAUSE", + "KSCAN_PERIOD", + "KSCAN_POWER", + "KSCAN_PRINT", + "KSCAN_PRINTSCREEN", + "KSCAN_Q", + "KSCAN_R", + "KSCAN_RALT", + "KSCAN_RCTRL", + "KSCAN_RETURN", + "KSCAN_RGUI", + "KSCAN_RIGHT", + "KSCAN_RIGHTBRACKET", + "KSCAN_RMETA", + "KSCAN_RSHIFT", + "KSCAN_RSUPER", + "KSCAN_S", + "KSCAN_SCROLLLOCK", + "KSCAN_SCROLLOCK", + "KSCAN_SEMICOLON", + "KSCAN_SLASH", + "KSCAN_SPACE", + "KSCAN_SYSREQ", + "KSCAN_T", + "KSCAN_TAB", + "KSCAN_U", + "KSCAN_UNKNOWN", + "KSCAN_UP", + "KSCAN_V", + "KSCAN_W", + "KSCAN_X", + "KSCAN_Y", + "KSCAN_Z", + "K_0", + "K_1", + "K_2", + "K_3", + "K_4", + "K_5", + "K_6", + "K_7", + "K_8", + "K_9", + "K_AC_BACK", + "K_AMPERSAND", + "K_ASTERISK", + "K_AT", + "K_BACKQUOTE", + "K_BACKSLASH", + "K_BACKSPACE", + "K_BREAK", + "K_CAPSLOCK", + "K_CARET", + "K_CLEAR", + "K_COLON", + "K_COMMA", + "K_CURRENCYSUBUNIT", + "K_CURRENCYUNIT", + "K_DELETE", + "K_DOLLAR", + "K_DOWN", + "K_END", + "K_EQUALS", + "K_ESCAPE", + "K_EURO", + "K_EXCLAIM", + "K_F1", + "K_F10", + "K_F11", + "K_F12", + "K_F13", + "K_F14", + "K_F15", + "K_F2", + "K_F3", + "K_F4", + "K_F5", + "K_F6", + "K_F7", + "K_F8", + "K_F9", + "K_GREATER", + "K_HASH", + "K_HELP", + "K_HOME", + "K_INSERT", + "K_KP0", + "K_KP1", + "K_KP2", + "K_KP3", + "K_KP4", + "K_KP5", + "K_KP6", + "K_KP7", + "K_KP8", + "K_KP9", + "K_KP_0", + "K_KP_1", + "K_KP_2", + "K_KP_3", + "K_KP_4", + "K_KP_5", + "K_KP_6", + "K_KP_7", + "K_KP_8", + "K_KP_9", + "K_KP_DIVIDE", + "K_KP_ENTER", + "K_KP_EQUALS", + "K_KP_MINUS", + "K_KP_MULTIPLY", + "K_KP_PERIOD", + "K_KP_PLUS", + "K_LALT", + "K_LCTRL", + "K_LEFT", + "K_LEFTBRACKET", + "K_LEFTPAREN", + "K_LESS", + "K_LGUI", + "K_LMETA", + "K_LSHIFT", + "K_LSUPER", + "K_MENU", + "K_MINUS", + "K_MODE", + "K_NUMLOCK", + "K_NUMLOCKCLEAR", + "K_PAGEDOWN", + "K_PAGEUP", + "K_PAUSE", + "K_PERCENT", + "K_PERIOD", + "K_PLUS", + "K_POWER", + "K_PRINT", + "K_PRINTSCREEN", + "K_QUESTION", + "K_QUOTE", + "K_QUOTEDBL", + "K_RALT", + "K_RCTRL", + "K_RETURN", + "K_RGUI", + "K_RIGHT", + "K_RIGHTBRACKET", + "K_RIGHTPAREN", + "K_RMETA", + "K_RSHIFT", + "K_RSUPER", + "K_SCROLLLOCK", + "K_SCROLLOCK", + "K_SEMICOLON", + "K_SLASH", + "K_SPACE", + "K_SYSREQ", + "K_TAB", + "K_UNDERSCORE", + "K_UNKNOWN", + "K_UP", + "K_a", + "K_b", + "K_c", + "K_d", + "K_e", + "K_f", + "K_g", + "K_h", + "K_i", + "K_j", + "K_k", + "K_l", + "K_m", + "K_n", + "K_o", + "K_p", + "K_q", + "K_r", + "K_s", + "K_t", + "K_u", + "K_v", + "K_w", + "K_x", + "K_y", + "K_z", + "LIL_ENDIAN", + "MIDIIN", + "MIDIOUT", + "MOUSEBUTTONDOWN", + "MOUSEBUTTONUP", + "MOUSEMOTION", + "MOUSEWHEEL", + "MULTIGESTURE", + "NOEVENT", + "NOFRAME", + "NUMEVENTS", + "OPENGL", + "OPENGLBLIT", + "PREALLOC", + "QUIT", + "RESIZABLE", + "RLEACCEL", + "RLEACCELOK", + "SCALED", + "SCRAP_BMP", + "SCRAP_CLIPBOARD", + "SCRAP_PBM", + "SCRAP_PPM", + "SCRAP_SELECTION", + "SCRAP_TEXT", + "SHOWN", + "SRCALPHA", + "SRCCOLORKEY", + "SWSURFACE", + "SYSTEM_CURSOR_ARROW", + "SYSTEM_CURSOR_CROSSHAIR", + "SYSTEM_CURSOR_HAND", + "SYSTEM_CURSOR_IBEAM", + "SYSTEM_CURSOR_NO", + "SYSTEM_CURSOR_SIZEALL", + "SYSTEM_CURSOR_SIZENESW", + "SYSTEM_CURSOR_SIZENS", + "SYSTEM_CURSOR_SIZENWSE", + "SYSTEM_CURSOR_SIZEWE", + "SYSTEM_CURSOR_WAIT", + "SYSTEM_CURSOR_WAITARROW", + "SYSWMEVENT", + "TEXTEDITING", + "TEXTINPUT", + "TIMER_RESOLUTION", + "USEREVENT", + "USEREVENT_DROPFILE", + "VIDEOEXPOSE", + "VIDEORESIZE", + "WINDOWSHOWN", + "WINDOWHIDDEN", + "WINDOWEXPOSED", + "WINDOWMOVED", + "WINDOWRESIZED", + "WINDOWSIZECHANGED", + "WINDOWMINIMIZED", + "WINDOWMAXIMIZED", + "WINDOWRESTORED", + "WINDOWENTER", + "WINDOWLEAVE", + "WINDOWFOCUSGAINED", + "WINDOWFOCUSLOST", + "WINDOWCLOSE", + "WINDOWTAKEFOCUS", + "WINDOWHITTEST", +] diff --git a/.venv/lib/python3.8/site-packages/pygame/macosx.py b/.venv/lib/python3.8/site-packages/pygame/macosx.py new file mode 100644 index 0000000..fca8e21 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/macosx.py @@ -0,0 +1,15 @@ +import platform +import os +import sys +from pygame.pkgdata import getResource +from pygame import sdlmain_osx + +__all__ = ["Video_AutoInit"] + + +def Video_AutoInit(): + """Called from the base.c just before display module is initialized.""" + if "Darwin" in platform.platform(): + if (os.getcwd() == "/") and len(sys.argv) > 1: + os.chdir(os.path.dirname(sys.argv[0])) + return True diff --git a/.venv/lib/python3.8/site-packages/pygame/mask.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/mask.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..2ca1d82 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/mask.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/mask.pyi b/.venv/lib/python3.8/site-packages/pygame/mask.pyi new file mode 100644 index 0000000..726d3bf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/mask.pyi @@ -0,0 +1,58 @@ +from typing import Any, List, Optional, Sequence, Tuple, TypeVar, Union + +from pygame.rect import Rect +from pygame.surface import Surface + +from ._common import _ColorValue, _Coordinate, _RectValue + +_Offset = TypeVar("_Offset", Tuple[int, int], Sequence[int]) + +def from_surface(surface: Surface, threshold: int = 127) -> Mask: ... +def from_threshold( + surface: Surface, + color: _ColorValue, + threshold: _ColorValue = (0, 0, 0, 255), + other_surface: Optional[Surface] = None, + palette_colors: int = 1, +) -> Mask: ... + +class Mask: + def __init__(self, size: _Coordinate, fill: bool = False) -> None: ... + def copy(self) -> Mask: ... + def get_size(self) -> Tuple[int, int]: ... + def get_rect(self, **kwargs: Any) -> Rect: ... # Dict type needs to be completed + def get_at(self, pos: _Coordinate) -> int: ... + def set_at(self, pos: _Coordinate, value: int = 1) -> None: ... + def overlap(self, other: Mask, offset: _Offset) -> Union[Tuple[int, int], None]: ... + def overlap_area(self, other: Mask, offset: _Coordinate) -> int: ... + def overlap_mask(self, other: Mask, offset: _Coordinate) -> Mask: ... + def fill(self) -> None: ... + def clear(self) -> None: ... + def invert(self) -> None: ... + def scale(self, size: _Coordinate) -> Mask: ... + def draw(self, other: Mask, offset: _Coordinate) -> None: ... + def erase(self, other: Mask, offset: _Coordinate) -> None: ... + def count(self) -> int: ... + def centroid(self) -> Tuple[int, int]: ... + def angle(self) -> float: ... + def outline(self, every: int = 1) -> List[Tuple[int, int]]: ... + def convolve( + self, + other: Mask, + output: Optional[Mask] = None, + offset: _Coordinate = (0, 0), + ) -> Mask: ... + def connected_component( + self, pos: Union[List[int], Tuple[int, int]] = ... + ) -> Mask: ... + def connected_components(self, minimum: int = 0) -> List[Mask]: ... + def get_bounding_rects(self) -> Rect: ... + def to_surface( + self, + surface: Optional[Surface] = None, + setsurface: Optional[Surface] = None, + unsetsurface: Optional[Surface] = None, + setcolor: _ColorValue = (255, 255, 255, 255), + unsetcolor: _ColorValue = (0, 0, 0, 255), + dest: Union[_RectValue, _Coordinate] = (0, 0), + ) -> Surface: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/math.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/math.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..af94c9d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/math.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/math.pyi b/.venv/lib/python3.8/site-packages/pygame/math.pyi new file mode 100644 index 0000000..594acf2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/math.pyi @@ -0,0 +1,281 @@ +from typing import List, Sequence, Tuple, Union, overload + +class _VectorElementwiseProxy2: + def __add__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __radd__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __sub__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __rsub__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __mul__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __rmul__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __truediv__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __rtruediv__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __floordiv__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __rfloordiv__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __mod__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __rmod__( + self, other: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __pow__( + self, power: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + def __rpow__( + self, power: Union[float, Vector2, _VectorElementwiseProxy2] + ) -> Vector2: ... + +class _VectorElementwiseProxy3: + def __add__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __radd__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __sub__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __rsub__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __mul__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __rmul__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __truediv__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __rtruediv__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __floordiv__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __rfloordiv__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __mod__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __rmod__( + self, other: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __pow__( + self, power: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + def __rpow__( + self, power: Union[float, Vector3, _VectorElementwiseProxy3] + ) -> Vector3: ... + +class Vector2: + x: float + y: float + xx: Vector2 + xy: Vector2 + yx: Vector2 + yy: Vector2 + __hash__: None # type: ignore + @overload + def __init__( + self, + x: Union[float, Tuple[float, float], List[float], Vector2] = 0, + ) -> None: ... + @overload + def __init__(self, x: float, y: float) -> None: ... + def __setitem__(self, key: int, value: float) -> None: ... + @overload + def __getitem__(self, i: int) -> float: ... + @overload + def __getitem__(self, s: slice) -> List[float]: ... + def __add__(self, other: Vector2) -> Vector2: ... + def __sub__(self, other: Vector2) -> Vector2: ... + @overload + def __mul__(self, other: Vector2) -> float: ... + @overload + def __mul__(self, other: float) -> Vector2: ... + def __rmul__(self, other: float) -> Vector2: ... + def __truediv__(self, other: float) -> Vector2: ... + def __floordiv__(self, other: float) -> Vector2: ... + def __neg__(self) -> Vector2: ... + def __pos__(self) -> Vector2: ... + def __bool__(self) -> bool: ... + def __iadd__(self, other: Vector2) -> Vector2: ... + def __isub__(self, other: Vector2) -> Vector2: ... + @overload + def __imul__(self, other: Vector2) -> float: ... + @overload + def __imul__(self, other: float) -> Vector2: ... + def dot(self, other: Vector2) -> float: ... + def cross(self, other: Vector2) -> Vector2: ... + def magnitude(self) -> float: ... + def magnitude_squared(self) -> float: ... + def length(self) -> float: ... + def length_squared(self) -> float: ... + def normalize(self) -> Vector2: ... + def normalize_ip(self) -> None: ... + def is_normalized(self) -> bool: ... + def scale_to_length(self, value: float) -> None: ... + def reflect(self, other: Vector2) -> Vector2: ... + def reflect_ip(self, other: Vector2) -> None: ... + def distance_to(self, other: Union[Vector2, Sequence[float]]) -> float: ... + def distance_squared_to(self, other: Vector2) -> float: ... + def lerp(self, other: Vector2, value: float) -> Vector2: ... + def slerp(self, other: Vector2, value: float) -> Vector2: ... + def elementwise(self) -> _VectorElementwiseProxy2: ... + def rotate(self, angle: float) -> Vector2: ... + def rotate_rad(self, angle: float) -> Vector2: ... + def rotate_ip(self, angle: float) -> None: ... + def rotate_rad_ip(self, angle: float) -> None: ... + def rotate_ip_rad(self, angle: float) -> None: ... + def angle_to(self, other: Vector2) -> float: ... + def as_polar(self) -> Tuple[float, float]: ... + def from_polar( + self, polar_value: Union[List[float], Tuple[float, float]] + ) -> None: ... + def copy(self) -> Vector2: ... + def update( + self, + x: Union[float, Vector2, Tuple[float, float], List[float]] = 0, + y: float = 0, + ) -> None: ... + +class Vector3: + x: float + y: float + z: float + xx: Vector2 + xy: Vector2 + xz: Vector2 + yx: Vector2 + yy: Vector2 + yz: Vector2 + zx: Vector2 + zy: Vector2 + zz: Vector2 + xxx: Vector3 + xxy: Vector3 + xxz: Vector3 + xyx: Vector3 + xyy: Vector3 + xyz: Vector3 + xzx: Vector3 + xzy: Vector3 + xzz: Vector3 + yxx: Vector3 + yxy: Vector3 + yxz: Vector3 + yyx: Vector3 + yyy: Vector3 + yyz: Vector3 + yzx: Vector3 + yzy: Vector3 + yzz: Vector3 + zxx: Vector3 + zxy: Vector3 + zxz: Vector3 + zyx: Vector3 + zyy: Vector3 + zyz: Vector3 + zzx: Vector3 + zzy: Vector3 + zzz: Vector3 + __hash__: None # type: ignore + @overload + def __init__( + self, + xyz: Union[float, Tuple[float, float, float], List[float], Vector3] = 0, + ) -> None: ... + @overload + def __init__(self, x: float, y: float, z: float) -> None: ... + def __setitem__(self, key: int, value: float) -> None: ... + @overload + def __getitem__(self, i: int) -> float: ... + @overload + def __getitem__(self, s: slice) -> List[float]: ... + def __add__(self, other: Vector3) -> Vector3: ... + def __sub__(self, other: Vector3) -> Vector3: ... + @overload + def __mul__(self, other: Vector3) -> float: ... + @overload + def __mul__(self, other: float) -> Vector3: ... + def __rmul__(self, other: float) -> Vector3: ... + def __truediv__(self, other: float) -> Vector3: ... + def __floordiv__(self, other: float) -> Vector3: ... + def __neg__(self) -> Vector3: ... + def __pos__(self) -> Vector3: ... + def __bool__(self) -> bool: ... + def __iadd__(self, other: Vector3) -> Vector3: ... + def __isub__(self, other: Vector3) -> Vector3: ... + @overload + def __imul__(self, other: Vector3) -> float: ... + @overload + def __imul__(self, other: float) -> Vector3: ... + def dot(self, other: Vector3) -> float: ... + def cross(self, other: Vector3) -> Vector3: ... + def magnitude(self) -> float: ... + def magnitude_squared(self) -> float: ... + def length(self) -> float: ... + def length_squared(self) -> float: ... + def normalize(self) -> Vector3: ... + def normalize_ip(self) -> None: ... + def is_normalized(self) -> bool: ... + def scale_to_length(self, value: float) -> None: ... + def reflect(self, other: Vector3) -> Vector3: ... + def reflect_ip(self, other: Vector3) -> None: ... + def distance_to(self, other: Vector3) -> float: ... + def distance_squared_to(self, other: Vector3) -> float: ... + def lerp(self, other: Vector3, value: float) -> Vector3: ... + def slerp(self, other: Vector3, value: float) -> Vector3: ... + def elementwise(self) -> _VectorElementwiseProxy3: ... + def rotate(self, angle: float, axis: Vector3) -> Vector3: ... + def rotate_rad(self, angle: float, axis: Vector3) -> Vector3: ... + def rotate_ip(self, angle: float, axis: Vector3) -> None: ... + def rotate_rad_ip(self, angle: float, axis: Vector3) -> None: ... + def rotate_ip_rad(self, angle: float, axis: Vector3) -> None: ... + def rotate_x(self, angle: float) -> Vector3: ... + def rotate_x_rad(self, angle: float) -> Vector3: ... + def rotate_x_ip(self, angle: float) -> None: ... + def rotate_x_rad_ip(self, angle: float) -> None: ... + def rotate_x_ip_rad(self, angle: float) -> None: ... + def rotate_y(self, angle: float) -> Vector3: ... + def rotate_y_rad(self, angle: float) -> Vector3: ... + def rotate_y_ip(self, angle: float) -> None: ... + def rotate_y_rad_ip(self, angle: float) -> None: ... + def rotate_y_ip_rad(self, angle: float) -> None: ... + def rotate_z(self, angle: float) -> Vector3: ... + def rotate_z_rad(self, angle: float) -> Vector3: ... + def rotate_z_ip(self, angle: float) -> None: ... + def rotate_z_rad_ip(self, angle: float) -> None: ... + def rotate_z_ip_rad(self, angle: float) -> None: ... + def angle_to(self, other: Vector3) -> float: ... + def as_spherical(self) -> Tuple[float, float, float]: ... + def from_spherical(self, spherical: Tuple[float, float, float]) -> None: ... + def copy(self) -> Vector3: ... + @overload + def update( + self, + xyz: Union[float, Tuple[float, float, float], List[float], Vector3] = 0, + ) -> None: ... + @overload + def update(self, x: int, y: int, z: int) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/midi.py b/.venv/lib/python3.8/site-packages/pygame/midi.py new file mode 100644 index 0000000..bcf8d6b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/midi.py @@ -0,0 +1,718 @@ +"""pygame.midi +pygame module for interacting with midi input and output. + +The midi module can send output to midi devices, and get input +from midi devices. It can also list midi devices on the system. + +Including real midi devices, and virtual ones. + +It uses the portmidi library. Is portable to which ever platforms +portmidi supports (currently windows, OSX, and linux). + +This uses pyportmidi for now, but may use its own bindings at some +point in the future. The pyportmidi bindings are included with pygame. + +New in pygame 1.9.0. +""" + +# TODO: finish writing tests. +# - likely as interactive tests... so you'd need to plug in +# a midi device. +# TODO: create a background thread version for input threads. +# - that can automatically inject input into the event queue +# once the input object is running. Like joysticks. + +import math +import atexit + +import pygame +import pygame.locals + +import pygame.pypm as _pypm + +# For backward compatibility. +MIDIIN = pygame.locals.MIDIIN +MIDIOUT = pygame.locals.MIDIOUT + +__all__ = [ + "Input", + "MIDIIN", + "MIDIOUT", + "MidiException", + "Output", + "get_count", + "get_default_input_id", + "get_default_output_id", + "get_device_info", + "init", + "midis2events", + "quit", + "get_init", + "time", + "frequency_to_midi", + "midi_to_frequency", + "midi_to_ansi_note", +] + +__theclasses__ = ["Input", "Output"] + + +def _module_init(state=None): + # this is a sneaky dodge to store module level state in a non-public + # function. Helps us dodge using globals. + if state is not None: + _module_init.value = state + return state + + try: + _module_init.value + except AttributeError: + return False + else: + return _module_init.value + + +def init(): + """initialize the midi module + pygame.midi.init(): return None + + Call the initialisation function before using the midi module. + + It is safe to call this more than once. + """ + if not _module_init(): + _pypm.Initialize() + _module_init(True) + atexit.register(quit) + + +def quit(): # pylint: disable=redefined-builtin + """uninitialize the midi module + pygame.midi.quit(): return None + + + Called automatically atexit if you don't call it. + + It is safe to call this function more than once. + """ + if _module_init(): + # TODO: find all Input and Output classes and close them first? + _pypm.Terminate() + _module_init(False) + + +def get_init(): + """returns True if the midi module is currently initialized + pygame.midi.get_init(): return bool + + Returns True if the pygame.midi module is currently initialized. + + New in pygame 1.9.5. + """ + return _module_init() + + +def _check_init(): + if not _module_init(): + raise RuntimeError("pygame.midi not initialised.") + + +def get_count(): + """gets the number of devices. + pygame.midi.get_count(): return num_devices + + + Device ids range from 0 to get_count() -1 + """ + _check_init() + return _pypm.CountDevices() + + +def get_default_input_id(): + """gets default input device number + pygame.midi.get_default_input_id(): return default_id + + + Return the default device ID or -1 if there are no devices. + The result can be passed to the Input()/Output() class. + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PM_RECOMMENDED_INPUT_DEVICE=1 + + The user should first determine the available device ID by using + the supplied application "testin" or "testout". + + In general, the registry is a better place for this kind of info, + and with USB devices that can come and go, using integers is not + very reliable for device identification. Under Windows, if + PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is + *NOT* found in the environment, then the default device is obtained + by looking for a string in the registry under: + HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device + and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device + for a string. The number of the first device with a substring that + matches the string exactly is returned. For example, if the string + in the registry is "USB", and device 1 is named + "In USB MidiSport 1x1", then that will be the default + input because it contains the string "USB". + + In addition to the name, get_device_info() returns "interf", which + is the interface name. (The "interface" is the underlying software + system or API used by PortMidi to access devices. Examples are + MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.) + At present, the only Win32 interface is "MMSystem", the only Linux + interface is "ALSA", and the only Max OS X interface is "CoreMIDI". + To specify both the interface and the device name in the registry, + separate the two with a comma and a space, e.g.: + MMSystem, In USB MidiSport 1x1 + In this case, the string before the comma must be a substring of + the "interf" string, and the string after the space must be a + substring of the "name" name string in order to match the device. + + Note: in the current release, the default is simply the first device + (the input or output device with the lowest PmDeviceID). + """ + _check_init() + return _pypm.GetDefaultInputDeviceID() + + +def get_default_output_id(): + """gets default output device number + pygame.midi.get_default_output_id(): return default_id + + + Return the default device ID or -1 if there are no devices. + The result can be passed to the Input()/Output() class. + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PM_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ID by using + the supplied application "testin" or "testout". + + In general, the registry is a better place for this kind of info, + and with USB devices that can come and go, using integers is not + very reliable for device identification. Under Windows, if + PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is + *NOT* found in the environment, then the default device is obtained + by looking for a string in the registry under: + HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device + and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device + for a string. The number of the first device with a substring that + matches the string exactly is returned. For example, if the string + in the registry is "USB", and device 1 is named + "In USB MidiSport 1x1", then that will be the default + input because it contains the string "USB". + + In addition to the name, get_device_info() returns "interf", which + is the interface name. (The "interface" is the underlying software + system or API used by PortMidi to access devices. Examples are + MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.) + At present, the only Win32 interface is "MMSystem", the only Linux + interface is "ALSA", and the only Max OS X interface is "CoreMIDI". + To specify both the interface and the device name in the registry, + separate the two with a comma and a space, e.g.: + MMSystem, In USB MidiSport 1x1 + In this case, the string before the comma must be a substring of + the "interf" string, and the string after the space must be a + substring of the "name" name string in order to match the device. + + Note: in the current release, the default is simply the first device + (the input or output device with the lowest PmDeviceID). + """ + _check_init() + return _pypm.GetDefaultOutputDeviceID() + + +def get_device_info(an_id): + """returns information about a midi device + pygame.midi.get_device_info(an_id): return (interf, name, + input, output, + opened) + + interf - a text string describing the device interface, eg 'ALSA'. + name - a text string for the name of the device, eg 'Midi Through Port-0' + input - 0, or 1 if the device is an input device. + output - 0, or 1 if the device is an output device. + opened - 0, or 1 if the device is opened. + + If the id is out of range, the function returns None. + """ + _check_init() + return _pypm.GetDeviceInfo(an_id) + + +class Input(object): + """Input is used to get midi input from midi devices. + Input(device_id) + Input(device_id, buffer_size) + + buffer_size - the number of input events to be buffered waiting to + be read using Input.read() + """ + + def __init__(self, device_id, buffer_size=4096): + """ + The buffer_size specifies the number of input events to be buffered + waiting to be read using Input.read(). + """ + _check_init() + + if device_id == -1: + raise MidiException( + "Device id is -1, not a valid output id. " + "-1 usually means there were no default " + "Output devices." + ) + + try: + result = get_device_info(device_id) + except TypeError: + raise TypeError("an integer is required") + except OverflowError: + raise OverflowError("long int too large to convert to int") + + # and now some nasty looking error checking, to provide nice error + # messages to the kind, lovely, midi using people of wherever. + if result: + _, _, is_input, is_output, _ = result + if is_input: + try: + self._input = _pypm.Input(device_id, buffer_size) + except TypeError: + raise TypeError("an integer is required") + self.device_id = device_id + + elif is_output: + raise MidiException( + "Device id given is not a valid" " input id, it is an output id." + ) + else: + raise MidiException("Device id given is not a valid input id.") + else: + raise MidiException("Device id invalid, out of range.") + + def _check_open(self): + if self._input is None: + raise MidiException("midi not open.") + + def close(self): + """closes a midi stream, flushing any pending buffers. + Input.close(): return None + + PortMidi attempts to close open streams when the application + exits -- this is particularly difficult under Windows. + """ + _check_init() + if self._input is not None: + self._input.Close() + self._input = None + + def read(self, num_events): + """reads num_events midi events from the buffer. + Input.read(num_events): return midi_event_list + + Reads from the Input buffer and gives back midi events. + [[[status,data1,data2,data3],timestamp], + [[status,data1,data2,data3],timestamp],...] + """ + _check_init() + self._check_open() + return self._input.Read(num_events) + + def poll(self): + """returns true if there's data, or false if not. + Input.poll(): return Bool + + raises a MidiException on error. + """ + _check_init() + self._check_open() + + result = self._input.Poll() + if result == _pypm.TRUE: + return True + + if result == _pypm.FALSE: + return False + + err_text = _pypm.GetErrorText(result) + raise MidiException((result, err_text)) + + +class Output(object): + """Output is used to send midi to an output device + Output(device_id) + Output(device_id, latency = 0) + Output(device_id, buffer_size = 4096) + Output(device_id, latency, buffer_size) + + The buffer_size specifies the number of output events to be + buffered waiting for output. (In some cases -- see below -- + PortMidi does not buffer output at all and merely passes data + to a lower-level API, in which case buffersize is ignored.) + + latency is the delay in milliseconds applied to timestamps to determine + when the output should actually occur. (If latency is < 0, 0 is + assumed.) + + If latency is zero, timestamps are ignored and all output is delivered + immediately. If latency is greater than zero, output is delayed until + the message timestamp plus the latency. (NOTE: time is measured + relative to the time source indicated by time_proc. Timestamps are + absolute, not relative delays or offsets.) In some cases, PortMidi + can obtain better timing than your application by passing timestamps + along to the device driver or hardware. Latency may also help you + to synchronize midi data to audio data by matching midi latency to + the audio buffer latency. + + """ + + def __init__(self, device_id, latency=0, buffer_size=256): + """Output(device_id) + Output(device_id, latency = 0) + Output(device_id, buffer_size = 4096) + Output(device_id, latency, buffer_size) + + The buffer_size specifies the number of output events to be + buffered waiting for output. (In some cases -- see below -- + PortMidi does not buffer output at all and merely passes data + to a lower-level API, in which case buffersize is ignored.) + + latency is the delay in milliseconds applied to timestamps to determine + when the output should actually occur. (If latency is < 0, 0 is + assumed.) + + If latency is zero, timestamps are ignored and all output is delivered + immediately. If latency is greater than zero, output is delayed until + the message timestamp plus the latency. (NOTE: time is measured + relative to the time source indicated by time_proc. Timestamps are + absolute, not relative delays or offsets.) In some cases, PortMidi + can obtain better timing than your application by passing timestamps + along to the device driver or hardware. Latency may also help you + to synchronize midi data to audio data by matching midi latency to + the audio buffer latency. + """ + + _check_init() + self._aborted = 0 + + if device_id == -1: + raise MidiException( + "Device id is -1, not a valid output id." + " -1 usually means there were no default " + "Output devices." + ) + + try: + result = get_device_info(device_id) + except TypeError: + raise TypeError("an integer is required") + except OverflowError: + raise OverflowError("long int too large to convert to int") + + # and now some nasty looking error checking, to provide nice error + # messages to the kind, lovely, midi using people of wherever. + if result: + _, _, is_input, is_output, _ = result + if is_output: + try: + self._output = _pypm.Output(device_id, latency, buffer_size) + except TypeError: + raise TypeError("an integer is required") + self.device_id = device_id + + elif is_input: + raise MidiException( + "Device id given is not a valid output " "id, it is an input id." + ) + else: + raise MidiException("Device id given is not a" " valid output id.") + else: + raise MidiException("Device id invalid, out of range.") + + def _check_open(self): + if self._output is None: + raise MidiException("midi not open.") + + if self._aborted: + raise MidiException("midi aborted.") + + def close(self): + """closes a midi stream, flushing any pending buffers. + Output.close(): return None + + PortMidi attempts to close open streams when the application + exits -- this is particularly difficult under Windows. + """ + _check_init() + if self._output is not None: + self._output.Close() + self._output = None + + def abort(self): + """terminates outgoing messages immediately + Output.abort(): return None + + The caller should immediately close the output port; + this call may result in transmission of a partial midi message. + There is no abort for Midi input because the user can simply + ignore messages in the buffer and close an input device at + any time. + """ + + _check_init() + if self._output: + self._output.Abort() + self._aborted = 1 + + def write(self, data): + """writes a list of midi data to the Output + Output.write(data) + + writes series of MIDI information in the form of a list: + write([[[status <,data1><,data2><,data3>],timestamp], + [[status <,data1><,data2><,data3>],timestamp],...]) + fields are optional + example: choose program change 1 at time 20000 and + send note 65 with velocity 100 500 ms later. + write([[[0xc0,0,0],20000],[[0x90,60,100],20500]]) + notes: + 1. timestamps will be ignored if latency = 0. + 2. To get a note to play immediately, send MIDI info with + timestamp read from function Time. + 3. understanding optional data fields: + write([[[0xc0,0,0],20000]]) is equivalent to + write([[[0xc0],20000]]) + + Can send up to 1024 elements in your data list, otherwise an + IndexError exception is raised. + """ + _check_init() + self._check_open() + + self._output.Write(data) + + def write_short(self, status, data1=0, data2=0): + """write_short(status <, data1><, data2>) + Output.write_short(status) + Output.write_short(status, data1 = 0, data2 = 0) + + output MIDI information of 3 bytes or less. + data fields are optional + status byte could be: + 0xc0 = program change + 0x90 = note on + etc. + data bytes are optional and assumed 0 if omitted + example: note 65 on with velocity 100 + write_short(0x90,65,100) + """ + _check_init() + self._check_open() + self._output.WriteShort(status, data1, data2) + + def write_sys_ex(self, when, msg): + """writes a timestamped system-exclusive midi message. + Output.write_sys_ex(when, msg) + + msg - can be a *list* or a *string* + when - a timestamp in miliseconds + example: + (assuming o is an onput MIDI stream) + o.write_sys_ex(0,'\\xF0\\x7D\\x10\\x11\\x12\\x13\\xF7') + is equivalent to + o.write_sys_ex(pygame.midi.time(), + [0xF0,0x7D,0x10,0x11,0x12,0x13,0xF7]) + """ + _check_init() + self._check_open() + self._output.WriteSysEx(when, msg) + + def note_on(self, note, velocity, channel=0): + """turns a midi note on. Note must be off. + Output.note_on(note, velocity, channel=0) + + note is an integer from 0 to 127 + velocity is an integer from 0 to 127 + channel is an integer from 0 to 15 + + Turn a note on in the output stream. The note must already + be off for this to work correctly. + """ + if not 0 <= channel <= 15: + raise ValueError("Channel not between 0 and 15.") + + self.write_short(0x90 + channel, note, velocity) + + def note_off(self, note, velocity=0, channel=0): + """turns a midi note off. Note must be on. + Output.note_off(note, velocity=0, channel=0) + + note is an integer from 0 to 127 + velocity is an integer from 0 to 127 (release velocity) + channel is an integer from 0 to 15 + + Turn a note off in the output stream. The note must already + be on for this to work correctly. + """ + if not 0 <= channel <= 15: + raise ValueError("Channel not between 0 and 15.") + + self.write_short(0x80 + channel, note, velocity) + + def set_instrument(self, instrument_id, channel=0): + """select an instrument for a channel, with a value between 0 and 127 + Output.set_instrument(instrument_id, channel=0) + + Also called "patch change" or "program change". + """ + if not 0 <= instrument_id <= 127: + raise ValueError(f"Undefined instrument id: {instrument_id}") + + if not 0 <= channel <= 15: + raise ValueError("Channel not between 0 and 15.") + + self.write_short(0xC0 + channel, instrument_id) + + def pitch_bend(self, value=0, channel=0): + """modify the pitch of a channel. + Output.pitch_bend(value=0, channel=0) + + Adjust the pitch of a channel. The value is a signed integer + from -8192 to +8191. For example, 0 means "no change", +4096 is + typically a semitone higher, and -8192 is 1 whole tone lower (though + the musical range corresponding to the pitch bend range can also be + changed in some synthesizers). + + If no value is given, the pitch bend is returned to "no change". + """ + if not 0 <= channel <= 15: + raise ValueError("Channel not between 0 and 15.") + + if not -8192 <= value <= 8191: + raise ValueError( + f"Pitch bend value must be between -8192 and +8191, not {value}." + ) + + # "The 14 bit value of the pitch bend is defined so that a value of + # 0x2000 is the center corresponding to the normal pitch of the note + # (no pitch change)." so value=0 should send 0x2000 + value = value + 0x2000 + lsb = value & 0x7F # keep least 7 bits + msb = value >> 7 + self.write_short(0xE0 + channel, lsb, msb) + + +# MIDI commands +# +# 0x80 Note Off (note_off) +# 0x90 Note On (note_on) +# 0xA0 Aftertouch +# 0xB0 Continuous controller +# 0xC0 Patch change (set_instrument?) +# 0xD0 Channel Pressure +# 0xE0 Pitch bend +# 0xF0 (non-musical commands) + + +def time(): + """returns the current time in ms of the PortMidi timer + pygame.midi.time(): return time + + The time is reset to 0, when the module is inited. + """ + _check_init() + return _pypm.Time() + + +def midis2events(midis, device_id): + """converts midi events to pygame events + pygame.midi.midis2events(midis, device_id): return [Event, ...] + + Takes a sequence of midi events and returns list of pygame events. + """ + evs = [] + for midi in midis: + ((status, data1, data2, data3), timestamp) = midi + + event = pygame.event.Event( + MIDIIN, + status=status, + data1=data1, + data2=data2, + data3=data3, + timestamp=timestamp, + vice_id=device_id, + ) + evs.append(event) + + return evs + + +class MidiException(Exception): + """exception that pygame.midi functions and classes can raise + MidiException(errno) + """ + + def __init__(self, value): + super(MidiException, self).__init__(value) + self.parameter = value + + def __str__(self): + return repr(self.parameter) + + +def frequency_to_midi(frequency): + """converts a frequency into a MIDI note. + + Rounds to the closest midi note. + + ::Examples:: + + >>> frequency_to_midi(27.5) + 21 + >>> frequency_to_midi(36.7) + 26 + >>> frequency_to_midi(4186.0) + 108 + """ + return int(round(69 + (12 * math.log(frequency / 440.0)) / math.log(2))) + + +def midi_to_frequency(midi_note): + """Converts a midi note to a frequency. + + ::Examples:: + + >>> midi_to_frequency(21) + 27.5 + >>> midi_to_frequency(26) + 36.7 + >>> midi_to_frequency(108) + 4186.0 + """ + return round(440.0 * 2 ** ((midi_note - 69) * (1.0 / 12.0)), 1) + + +def midi_to_ansi_note(midi_note): + """returns the Ansi Note name for a midi number. + + ::Examples:: + + >>> midi_to_ansi_note(21) + 'A0' + >>> midi_to_ansi_note(102) + 'F#7' + >>> midi_to_ansi_note(108) + 'C8' + """ + notes = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"] + num_notes = 12 + note_name = notes[int(((midi_note - 21) % num_notes))] + note_number = (midi_note - 12) // num_notes + return f"{note_name}{note_number}" diff --git a/.venv/lib/python3.8/site-packages/pygame/midi.pyi b/.venv/lib/python3.8/site-packages/pygame/midi.pyi new file mode 100644 index 0000000..90cd4a6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/midi.pyi @@ -0,0 +1,49 @@ +from typing import List, Sequence, Tuple, Union + +from pygame.event import Event + +MIDIIN: int +MIDIOUT: int + +class MidiException(Exception): + def __init__(self, errno: str) -> None: ... + +def init() -> None: ... +def quit() -> None: ... +def get_init() -> bool: ... +def get_count() -> int: ... +def get_default_input_id() -> int: ... +def get_default_output_id() -> int: ... +def get_device_info(an_id: int) -> Tuple[str, str, int, int, int]: ... +def midis2events( + midi_events: Sequence[Sequence[Union[Sequence[int], int]]], device_id: int +) -> List[Event]: ... +def time() -> int: ... +def frequency_to_midi(frequency: float) -> int: ... +def midi_to_frequency(midi_note: int) -> float: ... +def midi_to_ansi_note(midi_note: int) -> str: ... + +class Input: + device_id: int + def __init__(self, device_id: int, buffer_size: int = 4096) -> None: ... + def close(self) -> None: ... + def pool(self) -> bool: ... + def read(self, num_events: int) -> List[List[Union[List[int], int]]]: ... + +class Output: + device_id: int + def __init__( + self, + device_id: int, + latency: int = 0, + buffersize: int = 4096, + ) -> None: ... + def abort(self) -> None: ... + def close(self) -> None: ... + def note_off(self, note: int, velocity: int = 0, channel: int = 0) -> None: ... + def note_on(self, note: int, velocity: int = 0, channel: int = 0) -> None: ... + def set_instrument(self, instrument_id: int, channel: int = 0) -> None: ... + def pitch_bend(self, value: int = 0, channel: int = 0) -> None: ... + def write(self, data: List[List[Union[List[int], int]]]) -> None: ... + def write_short(self, status: int, data1: int = 0, data2: int = 0) -> None: ... + def write_sys_ex(self, msg: Union[List[int], str], when: int) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/mixer.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/mixer.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..12cfb51 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/mixer.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/mixer.pyi b/.venv/lib/python3.8/site-packages/pygame/mixer.pyi new file mode 100644 index 0000000..52681ad --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/mixer.pyi @@ -0,0 +1,86 @@ +from typing import Any, Optional, Tuple, Union, overload + +import numpy + +from pygame.event import Event + +from . import music as music +from ._common import _FileArg + +def init( + frequency: int = 44100, + size: int = -16, + channels: int = 2, + buffer: int = 512, + devicename: Optional[str] = None, + allowedchanges: int = 5, +) -> None: ... +def pre_init( + frequency: int = 44100, + size: int = -16, + channels: int = 2, + buffer: int = 512, + devicename: Optional[str] = None, + allowedchanges: int = 5, +) -> None: ... +def quit() -> None: ... +def get_init() -> Tuple[int, int, int]: ... +def stop() -> None: ... +def pause() -> None: ... +def unpause() -> None: ... +def fadeout(time: int) -> None: ... +def set_num_channels(count: int) -> None: ... +def get_num_channels() -> int: ... +def set_reserved(count: int) -> int: ... +def find_channel(force: bool = False) -> Channel: ... +def get_busy() -> bool: ... +def get_sdl_mixer_version(linked: bool = True) -> Tuple[int, int, int]: ... + +class Sound: + @overload + def __init__(self, file: _FileArg) -> None: ... + @overload + def __init__( + self, buffer: Any + ) -> None: ... # Buffer protocol is still not implemented in typing + @overload + def __init__( + self, array: numpy.ndarray + ) -> None: ... # Buffer protocol is still not implemented in typing + def play( + self, + loops: int = 0, + maxtime: int = 0, + fade_ms: int = 0, + ) -> Channel: ... + def stop(self) -> None: ... + def fadeout(self, time: int) -> None: ... + def set_volume(self, value: float) -> None: ... + def get_volume(self) -> float: ... + def get_num_channels(self) -> int: ... + def get_length(self) -> float: ... + def get_raw(self) -> bytes: ... + +class Channel: + def __init__(self, id: int) -> None: ... + def play( + self, + sound: Sound, + loops: int = 0, + maxtime: int = 0, + fade_ms: int = 0, + ) -> None: ... + def stop(self) -> None: ... + def pause(self) -> None: ... + def unpause(self) -> None: ... + def fadeout(self, time: int) -> None: ... + @overload + def set_volume(self, value: float) -> None: ... + @overload + def set_volume(self, left: float, right: float) -> None: ... + def get_volume(self) -> float: ... + def get_busy(self) -> bool: ... + def get_sound(self) -> Sound: ... + def get_queue(self) -> Sound: ... + def set_endevent(self, type: Union[int, Event] = 0) -> None: ... + def get_endevent(self) -> int: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/mixer_music.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/mixer_music.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..3cea40c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/mixer_music.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/mouse.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/mouse.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..abe2a54 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/mouse.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/mouse.pyi b/.venv/lib/python3.8/site-packages/pygame/mouse.pyi new file mode 100644 index 0000000..84b77ed --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/mouse.pyi @@ -0,0 +1,34 @@ +from typing import List, Sequence, Tuple, Union, overload + +from pygame.cursors import Cursor +from pygame.surface import Surface + +def get_pressed( + num_buttons: int = 3, +) -> Union[Tuple[bool, bool, bool], Tuple[bool, bool, bool, bool, bool]]: ... +def get_pos() -> Tuple[int, int]: ... +def get_rel() -> Tuple[int, int]: ... +@overload +def set_pos(pos: Union[List[float], Tuple[float, float]]) -> None: ... +@overload +def set_pos(x: float, y: float) -> None: ... +def set_visible(value: bool) -> int: ... +def get_visible() -> bool: ... +def get_focused() -> bool: ... +@overload +def set_cursor(cursor: Cursor) -> None: ... +@overload +def set_cursor(constant: int) -> None: ... +@overload +def set_cursor( + size: Union[Tuple[int, int], List[int]], + hotspot: Union[Tuple[int, int], List[int]], + xormasks: Sequence[int], + andmasks: Sequence[int], +) -> None: ... +@overload +def set_cursor( + hotspot: Union[Tuple[int, int], List[int]], surface: Surface +) -> None: ... +def get_cursor() -> Cursor: ... +def set_system_cursor(cursor: int) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/music.pyi b/.venv/lib/python3.8/site-packages/pygame/music.pyi new file mode 100644 index 0000000..91a6f55 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/music.pyi @@ -0,0 +1,20 @@ +from typing import Optional + +from ._common import _FileArg + +def load(filename: _FileArg, namehint: Optional[str] = "") -> None: ... +def unload() -> None: ... +def play(loops: int = 0, start: float = 0.0, fade_ms: int = 0) -> None: ... +def rewind() -> None: ... +def stop() -> None: ... +def pause() -> None: ... +def unpause() -> None: ... +def fadeout(time: int) -> None: ... +def set_volume(volume: float) -> None: ... +def get_volume() -> float: ... +def get_busy() -> bool: ... +def set_pos(pos: float) -> None: ... +def get_pos() -> int: ... +def queue(filename: _FileArg, namehint: str = "", loops: int = 0) -> None: ... +def set_endevent(event_type: int) -> None: ... +def get_endevent() -> int: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/newbuffer.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/newbuffer.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..394121d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/newbuffer.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/pixelarray.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/pixelarray.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..fa5c586 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/pixelarray.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/pixelarray.pyi b/.venv/lib/python3.8/site-packages/pygame/pixelarray.pyi new file mode 100644 index 0000000..f5e61dd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/pixelarray.pyi @@ -0,0 +1,35 @@ +from typing import Sequence, Tuple + +from pygame.surface import Surface + +from ._common import _ColorValue + +class PixelArray: + surface: Surface + itemsize: int + ndim: int + shape: Tuple[int, ...] + strides: Tuple[int, ...] + def __init__(self, surface: Surface) -> None: ... + def make_surface(self) -> Surface: ... + def replace( + self, + color: _ColorValue, + repcolor: _ColorValue, + distance: float = 0, + weights: Sequence[float] = (0.299, 0.587, 0.114), + ) -> None: ... + def extract( + self, + color: _ColorValue, + distance: float = 0, + weights: Sequence[float] = (0.299, 0.587, 0.114), + ) -> PixelArray: ... + def compare( + self, + array: PixelArray, + distance: float = 0, + weights: Sequence[float] = (0.299, 0.587, 0.114), + ) -> PixelArray: ... + def transpose(self) -> PixelArray: ... + def close(self) -> PixelArray: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/pixelcopy.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/pixelcopy.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..f5dae97 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/pixelcopy.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/pixelcopy.pyi b/.venv/lib/python3.8/site-packages/pygame/pixelcopy.pyi new file mode 100644 index 0000000..3734ddf --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/pixelcopy.pyi @@ -0,0 +1,19 @@ +import numpy +from typing_extensions import Literal + +from pygame.surface import Surface + +_kind = Literal["P", "p", "R", "r", "G", "g", "B", "b", "A", "a", "C", "c"] + +def surface_to_array( + array: numpy.ndarray, + surface: Surface, + kind: _kind = "P", + opaque: int = 255, + clear: int = 0, +) -> None: ... +def array_to_surface(surface: Surface, array: numpy.ndarray) -> None: ... +def map_to_array( + array1: numpy.ndarray, array2: numpy.ndarray, surface: Surface +) -> None: ... +def make_surface(array: numpy.ndarray) -> Surface: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/pkgdata.py b/.venv/lib/python3.8/site-packages/pygame/pkgdata.py new file mode 100644 index 0000000..1d89028 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/pkgdata.py @@ -0,0 +1,79 @@ +""" +pkgdata is a simple, extensible way for a package to acquire data file +resources. + +The getResource function is equivalent to the standard idioms, such as +the following minimal implementation: + + import sys, os + + def getResource(identifier, pkgname=__name__): + pkgpath = os.path.dirname(sys.modules[pkgname].__file__) + path = os.path.join(pkgpath, identifier) + return file(os.path.normpath(path), mode='rb') + +When a __loader__ is present on the module given by __name__, it will defer +getResource to its get_data implementation and return it as a file-like +object (such as StringIO). +""" + +__all__ = ["getResource"] +import sys +import os + +from io import BytesIO + +try: + from pkg_resources import resource_stream, resource_exists +except ImportError: + + def resource_exists(_package_or_requirement, _resource_name): + """ + A stub for when we fail to import this function. + + :return: Always returns False + """ + return False + + def resource_stream(_package_of_requirement, _resource_name): + """ + A stub for when we fail to import this function. + + Always raises a NotImplementedError when called. + """ + raise NotImplementedError + + +def getResource(identifier, pkgname=__name__): + """ + Acquire a readable object for a given package name and identifier. + An IOError will be raised if the resource can not be found. + + For example: + mydata = getResource('mypkgdata.jpg').read() + + Note that the package name must be fully qualified, if given, such + that it would be found in sys.modules. + + In some cases, getResource will return a real file object. In that + case, it may be useful to use its name attribute to get the path + rather than use it as a file-like object. For example, you may + be handing data off to a C API. + """ + + # When pyinstaller (or similar tools) are used, resource_exists may raise + # NotImplemented error + try: + if resource_exists(pkgname, identifier): + return resource_stream(pkgname, identifier) + except NotImplementedError: + pass + + mod = sys.modules[pkgname] + path_to_file = getattr(mod, "__file__", None) + if path_to_file is None: + raise IOError(f"{repr(mod)} has no __file__!") + path = os.path.join(os.path.dirname(path_to_file), identifier) + + # pylint: disable=consider-using-with + return open(os.path.normpath(path), "rb") diff --git a/.venv/lib/python3.8/site-packages/pygame/py.typed b/.venv/lib/python3.8/site-packages/pygame/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.8/site-packages/pygame/pygame.ico b/.venv/lib/python3.8/site-packages/pygame/pygame.ico new file mode 100644 index 0000000..06f699e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/pygame.ico differ diff --git a/.venv/lib/python3.8/site-packages/pygame/pygame_icon.bmp b/.venv/lib/python3.8/site-packages/pygame/pygame_icon.bmp new file mode 100644 index 0000000..74aea77 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/pygame_icon.bmp differ diff --git a/.venv/lib/python3.8/site-packages/pygame/pygame_icon.icns b/.venv/lib/python3.8/site-packages/pygame/pygame_icon.icns new file mode 100644 index 0000000..44a67bb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/pygame_icon.icns differ diff --git a/.venv/lib/python3.8/site-packages/pygame/pygame_icon_mac.bmp b/.venv/lib/python3.8/site-packages/pygame/pygame_icon_mac.bmp new file mode 100644 index 0000000..7b58bb1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/pygame_icon_mac.bmp differ diff --git a/.venv/lib/python3.8/site-packages/pygame/pypm.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/pypm.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..5d7c5aa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/pypm.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/rect.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/rect.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..873e614 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/rect.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/rect.pyi b/.venv/lib/python3.8/site-packages/pygame/rect.pyi new file mode 100644 index 0000000..c2d662c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/rect.pyi @@ -0,0 +1,229 @@ +from typing import Dict, List, Sequence, Tuple, TypeVar, Union, overload + +from pygame.math import Vector2 +from ._common import _Coordinate, _CanBeRect + +_K = TypeVar("_K") +_V = TypeVar("_V") + +class Rect(object): + x: int + y: int + top: int + left: int + bottom: int + right: int + topleft: Tuple[int, int] + bottomleft: Tuple[int, int] + topright: Tuple[int, int] + bottomright: Tuple[int, int] + midtop: Tuple[int, int] + midleft: Tuple[int, int] + midbottom: Tuple[int, int] + midright: Tuple[int, int] + center: Tuple[int, int] + centerx: int + centery: int + size: Tuple[int, int] + width: int + height: int + w: int + h: int + __hash__: None # type: ignore + @overload + def __init__( + self, left: float, top: float, width: float, height: float + ) -> None: ... + @overload + def __init__( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> None: ... + @overload + def __init__( + self, + left_top_width_height: Union[ + Rect, Tuple[float, float, float, float], List[float] + ], + ) -> None: ... + @overload + def __getitem__(self, i: int) -> int: ... + @overload + def __getitem__(self, s: slice) -> List[int]: ... + def copy(self) -> Rect: ... + @overload + def move(self, x: float, y: float) -> Rect: ... + @overload + def move(self, move_by: _Coordinate) -> Rect: ... + @overload + def move_ip(self, x: float, y: float) -> None: ... + @overload + def move_ip(self, move_by: _Coordinate) -> None: ... + @overload + def inflate(self, x: float, y: float) -> Rect: ... + @overload + def inflate(self, inflate_by: _Coordinate) -> Rect: ... + @overload + def inflate_ip(self, x: float, y: float) -> None: ... + @overload + def inflate_ip(self, inflate_by: _Coordinate) -> None: ... + @overload + def update(self, left: float, top: float, width: float, height: float) -> None: ... + @overload + def update( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> None: ... + @overload + def update( + self, + left_top_width_height: Union[ + Rect, Tuple[float, float, float, float], List[float] + ], + ) -> None: ... + @overload + def clamp(self, rect: Union[_CanBeRect, Rect]) -> Rect: ... + @overload + def clamp( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> Rect: ... + @overload + def clamp(self, left: float, top: float, width: float, height: float) -> Rect: ... + @overload + def clamp_ip(self, rect: Union[_CanBeRect, Rect]) -> None: ... + @overload + def clamp_ip( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> None: ... + @overload + def clamp_ip( + self, left: float, top: float, width: float, height: float + ) -> None: ... + @overload + def clip(self, rect: Union[_CanBeRect, Rect]) -> Rect: ... + @overload + def clip( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> Rect: ... + @overload + def clip(self, left: float, top: float, width: float, height: float) -> Rect: ... + @overload + def clipline( + self, x1: float, x2: float, x3: float, x4: float + ) -> Union[Tuple[Tuple[int, int], Tuple[int, int]], Tuple[()]]: ... + @overload + def clipline( + self, first_coordinate: _Coordinate, second_coordinate: _Coordinate + ) -> Union[Tuple[Tuple[int, int], Tuple[int, int]], Tuple[()]]: ... + @overload + def clipline( + self, values: Union[Tuple[float, float, float, float], List[float]] + ) -> Union[Tuple[Tuple[int, int], Tuple[int, int]], Tuple[()]]: ... + @overload + def clipline( + self, coordinates: Union[Tuple[_Coordinate, _Coordinate], List[_Coordinate]] + ) -> Union[Tuple[Tuple[int, int], Tuple[int, int]], Tuple[()]]: ... + @overload + def union(self, rect: Union[_CanBeRect, Rect]) -> Rect: ... + @overload + def union( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> Rect: ... + @overload + def union(self, left: float, top: float, width: float, height: float) -> Rect: ... + @overload + def union_ip(self, rect: Union[_CanBeRect, Rect]) -> None: ... + @overload + def union_ip( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> None: ... + @overload + def union_ip( + self, left: float, top: float, width: float, height: float + ) -> None: ... + def unionall(self, rect: Sequence[Union[_CanBeRect, Rect]]) -> Rect: ... + def unionall_ip(self, rect_sequence: Sequence[Union[_CanBeRect, Rect]]) -> None: ... + @overload + def fit(self, rect: Union[_CanBeRect, Rect]) -> Rect: ... + @overload + def fit( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> Rect: ... + @overload + def fit(self, left: float, top: float, width: float, height: float) -> Rect: ... + def normalize(self) -> None: ... + @overload + def __contains__(self, rect: Union[_CanBeRect, Rect, int]) -> bool: ... + @overload + def __contains__( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> bool: ... + @overload + def __contains__(self, left: float, top: float, width: float, height: float) -> bool: ... + @overload + def contains(self, rect: Union[_CanBeRect, Rect]) -> bool: ... + @overload + def contains( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> bool: ... + @overload + def contains( + self, left: float, top: float, width: float, height: float + ) -> bool: ... + @overload + def collidepoint(self, x: float, y: float) -> bool: ... + @overload + def collidepoint(self, x_y: Union[List[float], Tuple[float, float]]) -> bool: ... + @overload + def colliderect(self, rect: Union[_CanBeRect, Rect]) -> bool: ... + @overload + def colliderect( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> bool: ... + @overload + def colliderect( + self, left: float, top: float, width: float, height: float + ) -> bool: ... + def collidelist(self, rect_list: Sequence[Union[Rect, _CanBeRect]]) -> int: ... + def collidelistall( + self, rect_list: Sequence[Union[Rect, _CanBeRect]] + ) -> List[int]: ... + # Also undocumented: the dict collision methods take a 'values' argument + # that defaults to False. If it is False, the keys in rect_dict must be + # Rect-like; otherwise, the values must be Rects. + @overload + def collidedict( + self, rect_dict: Dict[_CanBeRect, _V], values: bool = ... + ) -> Tuple[_CanBeRect, _V]: ... + @overload + def collidedict( + self, rect_dict: Dict[_K, "Rect"], values: bool + ) -> Tuple[_K, "Rect"]: ... + @overload + def collidedictall( + self, rect_dict: Dict[_CanBeRect, _V], values: bool = ... + ) -> List[Tuple[_CanBeRect, _V]]: ... + @overload + def collidedictall( + self, rect_dict: Dict[_K, "Rect"], values: bool + ) -> List[Tuple[_K, "Rect"]]: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/rwobject.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/rwobject.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..3ad6b8e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/rwobject.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/scrap.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/scrap.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..b57a627 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/scrap.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/scrap.pyi b/.venv/lib/python3.8/site-packages/pygame/scrap.pyi new file mode 100644 index 0000000..7905ce6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/scrap.pyi @@ -0,0 +1,10 @@ +from typing import AnyStr, List + +def init() -> None: ... +def get_init() -> bool: ... +def get(data_type: str) -> AnyStr: ... +def get_types() -> List[str]: ... +def put(data_type: str, data: AnyStr) -> None: ... +def contains(data_type: str) -> bool: ... +def lost() -> bool: ... +def set_mode(mode: int) -> None: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/sndarray.py b/.venv/lib/python3.8/site-packages/pygame/sndarray.py new file mode 100644 index 0000000..99ac4c7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/sndarray.py @@ -0,0 +1,139 @@ +## pygame - Python Game Library +## Copyright (C) 2008 Marcus von Appen +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Library General Public +## License as published by the Free Software Foundation; either +## version 2 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Library General Public License for more details. +## +## You should have received a copy of the GNU Library General Public +## License along with this library; if not, write to the Free +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## +## Marcus von Appen +## mva@sysfault.org + +"""pygame module for accessing sound sample data + +Functions to convert between NumPy arrays and Sound objects. This module +will only be functional when pygame can use the external NumPy package. +If NumPy can't be imported, surfarray becomes a MissingModule object. + +Sound data is made of thousands of samples per second, and each sample +is the amplitude of the wave at a particular moment in time. For +example, in 22-kHz format, element number 5 of the array is the +amplitude of the wave after 5/22000 seconds. + +Each sample is an 8-bit or 16-bit integer, depending on the data format. +A stereo sound file has two values per sample, while a mono sound file +only has one. + +Sounds with 16-bit data will be treated as unsigned integers, +if the sound sample type requests this. +""" + +from pygame import mixer +import numpy + +import warnings + + +__all__ = [ + "array", + "samples", + "make_sound", + "use_arraytype", + "get_arraytype", + "get_arraytypes", +] + + +def array(sound): + """pygame.sndarray.array(Sound): return array + + Copy Sound samples into an array. + + Creates a new array for the sound data and copies the samples. The + array will always be in the format returned from + pygame.mixer.get_init(). + """ + + return numpy.array(sound, copy=True) + + +def samples(sound): + """pygame.sndarray.samples(Sound): return array + + Reference Sound samples into an array. + + Creates a new array that directly references the samples in a Sound + object. Modifying the array will change the Sound. The array will + always be in the format returned from pygame.mixer.get_init(). + """ + + return numpy.array(sound, copy=False) + + +def make_sound(array): + """pygame.sndarray.make_sound(array): return Sound + + Convert an array into a Sound object. + + Create a new playable Sound object from an array. The mixer module + must be initialized and the array format must be similar to the mixer + audio format. + """ + + return mixer.Sound(array=array) + + +def use_arraytype(arraytype): + """pygame.sndarray.use_arraytype(arraytype): return None + + DEPRECATED - only numpy arrays are now supported. + """ + warnings.warn( + DeprecationWarning( + "only numpy arrays are now supported, " + "this function will be removed in a " + "future version of the module" + ) + ) + arraytype = arraytype.lower() + if arraytype != "numpy": + raise ValueError("invalid array type") + + +def get_arraytype(): + """pygame.sndarray.get_arraytype(): return str + + DEPRECATED - only numpy arrays are now supported. + """ + warnings.warn( + DeprecationWarning( + "only numpy arrays are now supported, " + "this function will be removed in a " + "future version of the module" + ) + ) + return "numpy" + + +def get_arraytypes(): + """pygame.sndarray.get_arraytypes(): return tuple + + DEPRECATED - only numpy arrays are now supported. + """ + warnings.warn( + DeprecationWarning( + "only numpy arrays are now supported, " + "this function will be removed in a " + "future version of the module" + ) + ) + return ("numpy",) diff --git a/.venv/lib/python3.8/site-packages/pygame/sndarray.pyi b/.venv/lib/python3.8/site-packages/pygame/sndarray.pyi new file mode 100644 index 0000000..8b0dd65 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/sndarray.pyi @@ -0,0 +1,12 @@ +from typing import Tuple + +import numpy + +from pygame.mixer import Sound + +def array(sound: Sound) -> numpy.ndarray: ... +def samples(sound: Sound) -> numpy.ndarray: ... +def make_sound(array: numpy.ndarray) -> Sound: ... +def use_arraytype(arraytype: str) -> Sound: ... +def get_arraytype() -> str: ... +def get_arraytypes() -> Tuple[str]: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/sprite.py b/.venv/lib/python3.8/site-packages/pygame/sprite.py new file mode 100644 index 0000000..553ccfb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/sprite.py @@ -0,0 +1,1782 @@ +# pygame - Python Game Library +# Copyright (C) 2000-2003, 2007 Pete Shinners +# (C) 2004 Joe Wreschnig +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Pete Shinners +# pete@shinners.org + +"""pygame module with basic game object classes + +This module contains several simple classes to be used within games. There +are the main Sprite class and several Group classes that contain Sprites. +The use of these classes is entirely optional when using Pygame. The classes +are fairly lightweight and only provide a starting place for the code +that is common to most games. + +The Sprite class is intended to be used as a base class for the different +types of objects in the game. There is also a base Group class that simply +stores sprites. A game could create new types of Group classes that operate +on specially customized Sprite instances they contain. + +The basic Sprite class can draw the Sprites it contains to a Surface. The +Group.draw() method requires that each Sprite have a Surface.image attribute +and a Surface.rect. The Group.clear() method requires these same attributes +and can be used to erase all the Sprites with background. There are also +more advanced Groups: pygame.sprite.RenderUpdates() and +pygame.sprite.OrderedUpdates(). + +Lastly, this module contains several collision functions. These help find +sprites inside multiple groups that have intersecting bounding rectangles. +To find the collisions, the Sprites are required to have a Surface.rect +attribute assigned. + +The groups are designed for high efficiency in removing and adding Sprites +to them. They also allow cheap testing to see if a Sprite already exists in +a Group. A given Sprite can exist in any number of groups. A game could use +some groups to control object rendering, and a completely separate set of +groups to control interaction or player movement. Instead of adding type +attributes or bools to a derived Sprite class, consider keeping the +Sprites inside organized Groups. This will allow for easier lookup later +in the game. + +Sprites and Groups manage their relationships with the add() and remove() +methods. These methods can accept a single or multiple group arguments for +membership. The default initializers for these classes also take a +single group or list of groups as arguments for initial membership. It is safe +to repeatedly add and remove the same Sprite from a Group. + +While it is possible to design sprite and group classes that don't derive +from the Sprite and AbstractGroup classes below, it is strongly recommended +that you extend those when you create a new Sprite or Group class. + +Sprites are not thread safe, so lock them yourself if using threads. + +""" + +# TODO: a group that holds only the 'n' most recent elements. +# sort of like the GroupSingle class, but holding more +# than one sprite +# +# drawing groups that can 'automatically' store the area +# underneath so they can "clear" without needing a background +# function. obviously a little slower than normal, but nice +# to use in many situations. (also remember it must "clear" +# in the reverse order that it draws :]) +# +# the drawing groups should also be able to take a background +# function, instead of just a background surface. the function +# would take a surface and a rectangle on that surface to erase. +# +# perhaps more types of collision functions? the current two +# should handle just about every need, but perhaps more optimized +# specific ones that aren't quite so general but fit into common +# specialized cases. + +from operator import truth +from warnings import warn + +import pygame + +from pygame.rect import Rect +from pygame.time import get_ticks +from pygame.mask import from_surface + + +class Sprite(object): + """simple base class for visible game objects + + pygame.sprite.Sprite(*groups): return Sprite + + The base class for visible game objects. Derived classes will want to + override the Sprite.update() method and assign Sprite.image and Sprite.rect + attributes. The initializer can accept any number of Group instances that + the Sprite will become a member of. + + When subclassing the Sprite class, be sure to call the base initializer + before adding the Sprite to Groups. + + """ + + def __init__(self, *groups): + self.__g = {} # The groups the sprite is in + if groups: + self.add(*groups) + + def add(self, *groups): + """add the sprite to groups + + Sprite.add(*groups): return None + + Any number of Group instances can be passed as arguments. The + Sprite will be added to the Groups it is not already a member of. + + """ + has = self.__g.__contains__ + for group in groups: + if hasattr(group, "_spritegroup"): + if not has(group): + group.add_internal(self) + self.add_internal(group) + else: + self.add(*group) + + def remove(self, *groups): + """remove the sprite from groups + + Sprite.remove(*groups): return None + + Any number of Group instances can be passed as arguments. The Sprite + will be removed from the Groups it is currently a member of. + + """ + has = self.__g.__contains__ + for group in groups: + if hasattr(group, "_spritegroup"): + if has(group): + group.remove_internal(self) + self.remove_internal(group) + else: + self.remove(*group) + + def add_internal(self, group): + """ + For adding this sprite to a group internally. + + :param group: The group we are adding to. + """ + self.__g[group] = 0 + + def remove_internal(self, group): + """ + For removing this sprite from a group internally. + + :param group: The group we are removing from. + """ + del self.__g[group] + + def update(self, *args, **kwargs): + """method to control sprite behavior + + Sprite.update(*args, **kwargs): + + The default implementation of this method does nothing; it's just a + convenient "hook" that you can override. This method is called by + Group.update() with whatever arguments you give it. + + There is no need to use this method if not using the convenience + method by the same name in the Group class. + + """ + + def kill(self): + """remove the Sprite from all Groups + + Sprite.kill(): return None + + The Sprite is removed from all the Groups that contain it. This won't + change anything about the state of the Sprite. It is possible to + continue to use the Sprite after this method has been called, including + adding it to Groups. + + """ + for group in self.__g: + group.remove_internal(self) + self.__g.clear() + + def groups(self): + """list of Groups that contain this Sprite + + Sprite.groups(): return group_list + + Returns a list of all the Groups that contain this Sprite. + + """ + return list(self.__g) + + def alive(self): + """does the sprite belong to any groups + + Sprite.alive(): return bool + + Returns True when the Sprite belongs to one or more Groups. + """ + return truth(self.__g) + + def __repr__(self): + return f"<{self.__class__.__name__} Sprite(in {len(self.__g)} groups)>" + + @property + def layer(self): + """ + Dynamic, read only property for protected _layer attribute. + This will get the _layer variable if it exists. + + If you try to get it before it is set it will raise an attribute error. + + Layer property can only be set before the sprite is added to a group, + after that it is read only and a sprite's layer in a group should be + set via the group's change_layer() method. + + :return: layer as an int, or raise AttributeError. + """ + return getattr(self, "_layer") + + @layer.setter + def layer(self, value): + if not self.alive(): + setattr(self, "_layer", value) + else: + raise AttributeError( + "Can't set layer directly after " + "adding to group. Use " + "group.change_layer(sprite, new_layer) " + "instead." + ) + + +class DirtySprite(Sprite): + """a more featureful subclass of Sprite with more attributes + + pygame.sprite.DirtySprite(*groups): return DirtySprite + + Extra DirtySprite attributes with their default values: + + dirty = 1 + If set to 1, it is repainted and then set to 0 again. + If set to 2, it is always dirty (repainted each frame; + flag is not reset). + If set to 0, it is not dirty and therefore not repainted again. + + blendmode = 0 + It's the special_flags argument of Surface.blit; see the blendmodes in + the Surface.blit documentation + + source_rect = None + This is the source rect to use. Remember that it is relative to the top + left corner (0, 0) of self.image. + + visible = 1 + Normally this is 1. If set to 0, it will not be repainted. (If you + change visible to 1, you must set dirty to 1 for it to be erased from + the screen.) + + _layer = 0 + 0 is the default value but this is able to be set differently + when subclassing. + + """ + + def __init__(self, *groups): + + self.dirty = 1 + + # referred to as special_flags in the documentation of Surface.blit + self.blendmode = 0 + self._visible = 1 + + # Default 0 unless initialized differently. + self._layer = getattr(self, "_layer", 0) + self.source_rect = None + Sprite.__init__(self, *groups) + + def _set_visible(self, val): + """set the visible value (0 or 1) and makes the sprite dirty""" + self._visible = val + if self.dirty < 2: + self.dirty = 1 + + def _get_visible(self): + """return the visible value of that sprite""" + return self._visible + + @property + def visible(self): + """ + You can make this sprite disappear without removing it from the group + assign 0 for invisible and 1 for visible + """ + return self._get_visible() + + @visible.setter + def visible(self, value): + self._set_visible(value) + + @property + def layer(self): + """ + Layer property can only be set before the sprite is added to a group, + after that it is read only and a sprite's layer in a group should be + set via the group's change_layer() method. + + Overwrites dynamic property from sprite class for speed. + """ + return self._layer + + @layer.setter + def layer(self, value): + if not self.alive(): + self._layer = value + else: + raise AttributeError( + "Can't set layer directly after " + "adding to group. Use " + "group.change_layer(sprite, new_layer) " + "instead." + ) + + def __repr__(self): + return ( + f"<{self.__class__.__name__} DirtySprite(in {len(self.groups())} groups)>" + ) + + +class AbstractGroup(object): + """base class for containers of sprites + + AbstractGroup does everything needed to behave as a normal group. You can + easily subclass a new group class from this or the other groups below if + you want to add more features. + + Any AbstractGroup-derived sprite groups act like sequences and support + iteration, len, and so on. + + """ + + # dummy val to identify sprite groups, and avoid infinite recursion + _spritegroup = True + + def __init__(self): + self.spritedict = {} + self.lostsprites = [] + + def sprites(self): + """get a list of sprites in the group + + Group.sprite(): return list + + Returns an object that can be looped over with a 'for' loop. (For now, + it is always a list, but this could change in a future version of + pygame.) Alternatively, you can get the same information by iterating + directly over the sprite group, e.g. 'for sprite in group'. + + """ + return list(self.spritedict) + + def add_internal( + self, + sprite, + layer=None, # noqa pylint: disable=unused-argument; supporting legacy derived classes that override in non-pythonic way + ): + """ + For adding a sprite to this group internally. + + :param sprite: The sprite we are adding. + :param layer: the layer to add to, if the group type supports layers + """ + self.spritedict[sprite] = None + + def remove_internal(self, sprite): + """ + For removing a sprite from this group internally. + + :param sprite: The sprite we are removing. + """ + lost_rect = self.spritedict[sprite] + if lost_rect: + self.lostsprites.append(lost_rect) + del self.spritedict[sprite] + + def has_internal(self, sprite): + """ + For checking if a sprite is in this group internally. + + :param sprite: The sprite we are checking. + """ + return sprite in self.spritedict + + def copy(self): + """copy a group with all the same sprites + + Group.copy(): return Group + + Returns a copy of the group that is an instance of the same class + and has the same sprites in it. + + """ + return self.__class__( # noqa pylint: disable=too-many-function-args + self.sprites() # Needed because copy() won't work on AbstractGroup + ) + + def __iter__(self): + return iter(self.sprites()) + + def __contains__(self, sprite): + return self.has(sprite) + + def add(self, *sprites): + """add sprite(s) to group + + Group.add(sprite, list, group, ...): return None + + Adds a sprite or sequence of sprites to a group. + + """ + for sprite in sprites: + # It's possible that some sprite is also an iterator. + # If this is the case, we should add the sprite itself, + # and not the iterator object. + if isinstance(sprite, Sprite): + if not self.has_internal(sprite): + self.add_internal(sprite) + sprite.add_internal(self) + else: + try: + # See if sprite is an iterator, like a list or sprite + # group. + self.add(*sprite) + except (TypeError, AttributeError): + # Not iterable. This is probably a sprite that is not an + # instance of the Sprite class or is not an instance of a + # subclass of the Sprite class. Alternately, it could be an + # old-style sprite group. + if hasattr(sprite, "_spritegroup"): + for spr in sprite.sprites(): + if not self.has_internal(spr): + self.add_internal(spr) + spr.add_internal(self) + elif not self.has_internal(sprite): + self.add_internal(sprite) + sprite.add_internal(self) + + def remove(self, *sprites): + """remove sprite(s) from group + + Group.remove(sprite, list, or group, ...): return None + + Removes a sprite or sequence of sprites from a group. + + """ + # This function behaves essentially the same as Group.add. It first + # tries to handle each argument as an instance of the Sprite class. If + # that fails, then it tries to handle the argument as an iterable + # object. If that fails, then it tries to handle the argument as an + # old-style sprite group. Lastly, if that fails, it assumes that the + # normal Sprite methods should be used. + for sprite in sprites: + if isinstance(sprite, Sprite): + if self.has_internal(sprite): + self.remove_internal(sprite) + sprite.remove_internal(self) + else: + try: + self.remove(*sprite) + except (TypeError, AttributeError): + if hasattr(sprite, "_spritegroup"): + for spr in sprite.sprites(): + if self.has_internal(spr): + self.remove_internal(spr) + spr.remove_internal(self) + elif self.has_internal(sprite): + self.remove_internal(sprite) + sprite.remove_internal(self) + + def has(self, *sprites): + """ask if group has a sprite or sprites + + Group.has(sprite or group, ...): return bool + + Returns True if the given sprite or sprites are contained in the + group. Alternatively, you can get the same information using the + 'in' operator, e.g. 'sprite in group', 'subgroup in group'. + + """ + if not sprites: + return False # return False if no sprites passed in + + for sprite in sprites: + if isinstance(sprite, Sprite): + # Check for Sprite instance's membership in this group + if not self.has_internal(sprite): + return False + else: + try: + if not self.has(*sprite): + return False + except (TypeError, AttributeError): + if hasattr(sprite, "_spritegroup"): + for spr in sprite.sprites(): + if not self.has_internal(spr): + return False + else: + if not self.has_internal(sprite): + return False + + return True + + def update(self, *args, **kwargs): + """call the update method of every member sprite + + Group.update(*args, **kwargs): return None + + Calls the update method of every member sprite. All arguments that + were passed to this method are passed to the Sprite update function. + + """ + for sprite in self.sprites(): + sprite.update(*args, **kwargs) + + def draw(self, surface): + """draw all sprites onto the surface + + Group.draw(surface): return Rect_list + + Draws all of the member sprites onto the given surface. + + """ + sprites = self.sprites() + if hasattr(surface, "blits"): + self.spritedict.update( + zip(sprites, surface.blits((spr.image, spr.rect) for spr in sprites)) + ) + else: + for spr in sprites: + self.spritedict[spr] = surface.blit(spr.image, spr.rect) + self.lostsprites = [] + dirty = self.lostsprites + + return dirty + + def clear(self, surface, bgd): + """erase the previous position of all sprites + + Group.clear(surface, bgd): return None + + Clears the area under every drawn sprite in the group. The bgd + argument should be Surface which is the same dimensions as the + screen surface. The bgd could also be a function which accepts + the given surface and the area to be cleared as arguments. + + """ + if callable(bgd): + for lost_clear_rect in self.lostsprites: + bgd(surface, lost_clear_rect) + for clear_rect in self.spritedict.values(): + if clear_rect: + bgd(surface, clear_rect) + else: + surface_blit = surface.blit + for lost_clear_rect in self.lostsprites: + surface_blit(bgd, lost_clear_rect, lost_clear_rect) + for clear_rect in self.spritedict.values(): + if clear_rect: + surface_blit(bgd, clear_rect, clear_rect) + + def empty(self): + """remove all sprites + + Group.empty(): return None + + Removes all the sprites from the group. + + """ + for sprite in self.sprites(): + self.remove_internal(sprite) + sprite.remove_internal(self) + + def __nonzero__(self): + return truth(self.sprites()) + + __bool__ = __nonzero__ + + def __len__(self): + """return number of sprites in group + + Group.len(group): return int + + Returns the number of sprites contained in the group. + + """ + return len(self.sprites()) + + def __repr__(self): + return f"<{self.__class__.__name__}({len(self)} sprites)>" + + +class Group(AbstractGroup): + """container class for many Sprites + + pygame.sprite.Group(*sprites): return Group + + A simple container for Sprite objects. This class can be subclassed to + create containers with more specific behaviors. The constructor takes any + number of Sprite arguments to add to the Group. The group supports the + following standard Python operations: + + in test if a Sprite is contained + len the number of Sprites contained + bool test if any Sprites are contained + iter iterate through all the Sprites + + The Sprites in the Group are not ordered, so the Sprites are drawn and + iterated over in no particular order. + + """ + + def __init__(self, *sprites): + AbstractGroup.__init__(self) + self.add(*sprites) + + +RenderPlain = Group +RenderClear = Group + + +class RenderUpdates(Group): + """Group class that tracks dirty updates + + pygame.sprite.RenderUpdates(*sprites): return RenderUpdates + + This class is derived from pygame.sprite.Group(). It has an enhanced draw + method that tracks the changed areas of the screen. + + """ + + def draw(self, surface): + surface_blit = surface.blit + dirty = self.lostsprites + self.lostsprites = [] + dirty_append = dirty.append + for sprite in self.sprites(): + old_rect = self.spritedict[sprite] + new_rect = surface_blit(sprite.image, sprite.rect) + if old_rect: + if new_rect.colliderect(old_rect): + dirty_append(new_rect.union(old_rect)) + else: + dirty_append(new_rect) + dirty_append(old_rect) + else: + dirty_append(new_rect) + self.spritedict[sprite] = new_rect + return dirty + + +class OrderedUpdates(RenderUpdates): + """RenderUpdates class that draws Sprites in order of addition + + pygame.sprite.OrderedUpdates(*spites): return OrderedUpdates + + This class derives from pygame.sprite.RenderUpdates(). It maintains + the order in which the Sprites were added to the Group for rendering. + This makes adding and removing Sprites from the Group a little + slower than regular Groups. + + """ + + def __init__(self, *sprites): + self._spritelist = [] + RenderUpdates.__init__(self, *sprites) + + def sprites(self): + return list(self._spritelist) + + def add_internal(self, sprite, layer=None): + RenderUpdates.add_internal(self, sprite) + self._spritelist.append(sprite) + + def remove_internal(self, sprite): + RenderUpdates.remove_internal(self, sprite) + self._spritelist.remove(sprite) + + +class LayeredUpdates(AbstractGroup): + """LayeredUpdates Group handles layers, which are drawn like OrderedUpdates + + pygame.sprite.LayeredUpdates(*spites, **kwargs): return LayeredUpdates + + This group is fully compatible with pygame.sprite.Sprite. + New in pygame 1.8.0 + + """ + + _init_rect = Rect(0, 0, 0, 0) + + def __init__(self, *sprites, **kwargs): + """initialize an instance of LayeredUpdates with the given attributes + + You can set the default layer through kwargs using 'default_layer' + and an integer for the layer. The default layer is 0. + + If the sprite you add has an attribute _layer, then that layer will be + used. If **kwarg contains 'layer', then the passed sprites will be + added to that layer (overriding the sprite._layer attribute). If + neither the sprite nor **kwarg has a 'layer', then the default layer is + used to add the sprites. + + """ + self._spritelayers = {} + self._spritelist = [] + AbstractGroup.__init__(self) + self._default_layer = kwargs.get("default_layer", 0) + + self.add(*sprites, **kwargs) + + def add_internal(self, sprite, layer=None): + """Do not use this method directly. + + It is used by the group to add a sprite internally. + + """ + self.spritedict[sprite] = self._init_rect + + if layer is None: + try: + layer = sprite.layer + except AttributeError: + layer = self._default_layer + setattr(sprite, "_layer", layer) + elif hasattr(sprite, "_layer"): + setattr(sprite, "_layer", layer) + + sprites = self._spritelist # speedup + sprites_layers = self._spritelayers + sprites_layers[sprite] = layer + + # add the sprite at the right position + # bisect algorithmus + leng = len(sprites) + low = mid = 0 + high = leng - 1 + while low <= high: + mid = low + (high - low) // 2 + if sprites_layers[sprites[mid]] <= layer: + low = mid + 1 + else: + high = mid - 1 + # linear search to find final position + while mid < leng and sprites_layers[sprites[mid]] <= layer: + mid += 1 + sprites.insert(mid, sprite) + + def add(self, *sprites, **kwargs): + """add a sprite or sequence of sprites to a group + + LayeredUpdates.add(*sprites, **kwargs): return None + + If the sprite you add has an attribute _layer, then that layer will be + used. If **kwarg contains 'layer', then the passed sprites will be + added to that layer (overriding the sprite._layer attribute). If + neither the sprite nor **kwarg has a 'layer', then the default layer is + used to add the sprites. + + """ + + if not sprites: + return + layer = kwargs["layer"] if "layer" in kwargs else None + for sprite in sprites: + # It's possible that some sprite is also an iterator. + # If this is the case, we should add the sprite itself, + # and not the iterator object. + if isinstance(sprite, Sprite): + if not self.has_internal(sprite): + self.add_internal(sprite, layer) + sprite.add_internal(self) + else: + try: + # See if sprite is an iterator, like a list or sprite + # group. + self.add(*sprite, **kwargs) + except (TypeError, AttributeError): + # Not iterable. This is probably a sprite that is not an + # instance of the Sprite class or is not an instance of a + # subclass of the Sprite class. Alternately, it could be an + # old-style sprite group. + if hasattr(sprite, "_spritegroup"): + for spr in sprite.sprites(): + if not self.has_internal(spr): + self.add_internal(spr, layer) + spr.add_internal(self) + elif not self.has_internal(sprite): + self.add_internal(sprite, layer) + sprite.add_internal(self) + + def remove_internal(self, sprite): + """Do not use this method directly. + + The group uses it to add a sprite. + + """ + self._spritelist.remove(sprite) + # these dirty rects are suboptimal for one frame + old_rect = self.spritedict[sprite] + if old_rect is not self._init_rect: + self.lostsprites.append(old_rect) # dirty rect + if hasattr(sprite, "rect"): + self.lostsprites.append(sprite.rect) # dirty rect + + del self.spritedict[sprite] + del self._spritelayers[sprite] + + def sprites(self): + """return a ordered list of sprites (first back, last top). + + LayeredUpdates.sprites(): return sprites + + """ + return list(self._spritelist) + + def draw(self, surface): + """draw all sprites in the right order onto the passed surface + + LayeredUpdates.draw(surface): return Rect_list + + """ + spritedict = self.spritedict + surface_blit = surface.blit + dirty = self.lostsprites + self.lostsprites = [] + dirty_append = dirty.append + init_rect = self._init_rect + for spr in self.sprites(): + rec = spritedict[spr] + newrect = surface_blit(spr.image, spr.rect) + if rec is init_rect: + dirty_append(newrect) + else: + if newrect.colliderect(rec): + dirty_append(newrect.union(rec)) + else: + dirty_append(newrect) + dirty_append(rec) + spritedict[spr] = newrect + return dirty + + def get_sprites_at(self, pos): + """return a list with all sprites at that position + + LayeredUpdates.get_sprites_at(pos): return colliding_sprites + + Bottom sprites are listed first; the top ones are listed last. + + """ + _sprites = self._spritelist + rect = Rect(pos, (1, 1)) + colliding_idx = rect.collidelistall(_sprites) + return [_sprites[i] for i in colliding_idx] + + def get_sprite(self, idx): + """return the sprite at the index idx from the groups sprites + + LayeredUpdates.get_sprite(idx): return sprite + + Raises IndexOutOfBounds if the idx is not within range. + + """ + return self._spritelist[idx] + + def remove_sprites_of_layer(self, layer_nr): + """remove all sprites from a layer and return them as a list + + LayeredUpdates.remove_sprites_of_layer(layer_nr): return sprites + + """ + sprites = self.get_sprites_from_layer(layer_nr) + self.remove(*sprites) + return sprites + + # layer methods + def layers(self): + """return a list of unique defined layers defined. + + LayeredUpdates.layers(): return layers + + """ + return sorted(set(self._spritelayers.values())) + + def change_layer(self, sprite, new_layer): + """change the layer of the sprite + + LayeredUpdates.change_layer(sprite, new_layer): return None + + The sprite must have been added to the renderer already. This is not + checked. + + """ + sprites = self._spritelist # speedup + sprites_layers = self._spritelayers # speedup + + sprites.remove(sprite) + sprites_layers.pop(sprite) + + # add the sprite at the right position + # bisect algorithmus + leng = len(sprites) + low = mid = 0 + high = leng - 1 + while low <= high: + mid = low + (high - low) // 2 + if sprites_layers[sprites[mid]] <= new_layer: + low = mid + 1 + else: + high = mid - 1 + # linear search to find final position + while mid < leng and sprites_layers[sprites[mid]] <= new_layer: + mid += 1 + sprites.insert(mid, sprite) + if hasattr(sprite, "_layer"): + setattr(sprite, "_layer", new_layer) + + # add layer info + sprites_layers[sprite] = new_layer + + def get_layer_of_sprite(self, sprite): + """return the layer that sprite is currently in + + If the sprite is not found, then it will return the default layer. + + """ + return self._spritelayers.get(sprite, self._default_layer) + + def get_top_layer(self): + """return the top layer + + LayeredUpdates.get_top_layer(): return layer + + """ + return self._spritelayers[self._spritelist[-1]] + + def get_bottom_layer(self): + """return the bottom layer + + LayeredUpdates.get_bottom_layer(): return layer + + """ + return self._spritelayers[self._spritelist[0]] + + def move_to_front(self, sprite): + """bring the sprite to front layer + + LayeredUpdates.move_to_front(sprite): return None + + Brings the sprite to front by changing the sprite layer to the top-most + layer. The sprite is added at the end of the list of sprites in that + top-most layer. + + """ + self.change_layer(sprite, self.get_top_layer()) + + def move_to_back(self, sprite): + """move the sprite to the bottom layer + + LayeredUpdates.move_to_back(sprite): return None + + Moves the sprite to the bottom layer by moving it to a new layer below + the current bottom layer. + + """ + self.change_layer(sprite, self.get_bottom_layer() - 1) + + def get_top_sprite(self): + """return the topmost sprite + + LayeredUpdates.get_top_sprite(): return Sprite + + """ + return self._spritelist[-1] + + def get_sprites_from_layer(self, layer): + """return all sprites from a layer ordered as they where added + + LayeredUpdates.get_sprites_from_layer(layer): return sprites + + Returns all sprites from a layer. The sprites are ordered in the + sequence that they where added. (The sprites are not removed from the + layer. + + """ + sprites = [] + sprites_append = sprites.append + sprite_layers = self._spritelayers + for spr in self._spritelist: + if sprite_layers[spr] == layer: + sprites_append(spr) + elif sprite_layers[spr] > layer: + # break after because no other will + # follow with same layer + break + return sprites + + def switch_layer(self, layer1_nr, layer2_nr): + """switch the sprites from layer1_nr to layer2_nr + + LayeredUpdates.switch_layer(layer1_nr, layer2_nr): return None + + The layers number must exist. This method does not check for the + existence of the given layers. + + """ + sprites1 = self.remove_sprites_of_layer(layer1_nr) + for spr in self.get_sprites_from_layer(layer2_nr): + self.change_layer(spr, layer1_nr) + self.add(layer=layer2_nr, *sprites1) + + +class LayeredDirty(LayeredUpdates): + """LayeredDirty Group is for DirtySprites; subclasses LayeredUpdates + + pygame.sprite.LayeredDirty(*spites, **kwargs): return LayeredDirty + + This group requires pygame.sprite.DirtySprite or any sprite that + has the following attributes: + image, rect, dirty, visible, blendmode (see doc of DirtySprite). + + It uses the dirty flag technique and is therefore faster than + pygame.sprite.RenderUpdates if you have many static sprites. It + also switches automatically between dirty rect updating and full + screen drawing, so you do no have to worry which would be faster. + + As with the pygame.sprite.Group, you can specify some additional attributes + through kwargs: + _use_update: True/False (default is False) + _default_layer: default layer where the sprites without a layer are + added + _time_threshold: threshold time for switching between dirty rect mode + and fullscreen mode; defaults to updating at 80 frames per second, + which is equal to 1000.0 / 80.0 + + New in pygame 1.8.0 + + """ + + def __init__(self, *sprites, **kwargs): + """initialize group. + + pygame.sprite.LayeredDirty(*spites, **kwargs): return LayeredDirty + + You can specify some additional attributes through kwargs: + _use_update: True/False (default is False) + _default_layer: default layer where the sprites without a layer are + added + _time_threshold: threshold time for switching between dirty rect + mode and fullscreen mode; defaults to updating at 80 frames per + second, which is equal to 1000.0 / 80.0 + + """ + LayeredUpdates.__init__(self, *sprites, **kwargs) + self._clip = None + + self._use_update = False + + self._time_threshold = 1000.0 / 80.0 # 1000.0 / fps + + self._bgd = None + for key, val in kwargs.items(): + if key in ["_use_update", "_time_threshold", "_default_layer"] and hasattr( + self, key + ): + setattr(self, key, val) + + def add_internal(self, sprite, layer=None): + """Do not use this method directly. + + It is used by the group to add a sprite internally. + + """ + # check if all needed attributes are set + if not hasattr(sprite, "dirty"): + raise AttributeError() + if not hasattr(sprite, "visible"): + raise AttributeError() + if not hasattr(sprite, "blendmode"): + raise AttributeError() + + if not isinstance(sprite, DirtySprite): + raise TypeError() + + if sprite.dirty == 0: # set it dirty if it is not + sprite.dirty = 1 + + LayeredUpdates.add_internal(self, sprite, layer) + + def draw( + self, surface, bgd=None + ): # noqa pylint: disable=arguments-differ; unable to change public interface + """draw all sprites in the right order onto the given surface + + LayeredDirty.draw(surface, bgd=None): return Rect_list + + You can pass the background too. If a self.bgd is already set to some + value that is not None, then the bgd argument has no effect. + + """ + # functions and classes assigned locally to speed up loops + orig_clip = surface.get_clip() + latest_clip = self._clip + if latest_clip is None: + latest_clip = orig_clip + + local_sprites = self._spritelist + local_old_rect = self.spritedict + local_update = self.lostsprites + rect_type = Rect + + surf_blit_func = surface.blit + if bgd is not None: + self._bgd = bgd + local_bgd = self._bgd + + surface.set_clip(latest_clip) + # ------- + # 0. decide whether to render with update or flip + start_time = get_ticks() + if self._use_update: # dirty rects mode + # 1. find dirty area on screen and put the rects into + # self.lostsprites still not happy with that part + self._find_dirty_area( + latest_clip, + local_old_rect, + rect_type, + local_sprites, + local_update, + local_update.append, + self._init_rect, + ) + # can it be done better? because that is an O(n**2) algorithm in + # worst case + + # clear using background + if local_bgd is not None: + for rec in local_update: + surf_blit_func(local_bgd, rec, rec) + + # 2. draw + self._draw_dirty_internal( + local_old_rect, rect_type, local_sprites, surf_blit_func, local_update + ) + local_ret = list(local_update) + else: # flip, full screen mode + if local_bgd is not None: + surf_blit_func(local_bgd, (0, 0)) + for spr in local_sprites: + if spr.visible: + local_old_rect[spr] = surf_blit_func( + spr.image, spr.rect, spr.source_rect, spr.blendmode + ) + # return only the part of the screen changed + local_ret = [rect_type(latest_clip)] + + # timing for switching modes + # How may a good threshold be found? It depends on the hardware. + end_time = get_ticks() + if end_time - start_time > self._time_threshold: + self._use_update = False + else: + self._use_update = True + + # emtpy dirty rects list + local_update[:] = [] + + # ------- + # restore original clip + surface.set_clip(orig_clip) + return local_ret + + @staticmethod + def _draw_dirty_internal(_old_rect, _rect, _sprites, _surf_blit, _update): + for spr in _sprites: + if spr.dirty < 1 and spr.visible: + # sprite not dirty; blit only the intersecting part + if spr.source_rect is not None: + # For possible future speed up, source_rect's data + # can be pre-fetched outside of this loop. + _spr_rect = _rect(spr.rect.topleft, spr.source_rect.size) + rect_offset_x = spr.source_rect[0] - _spr_rect[0] + rect_offset_y = spr.source_rect[1] - _spr_rect[1] + else: + _spr_rect = spr.rect + rect_offset_x = -_spr_rect[0] + rect_offset_y = -_spr_rect[1] + + _spr_rect_clip = _spr_rect.clip + + for idx in _spr_rect.collidelistall(_update): + # clip + clip = _spr_rect_clip(_update[idx]) + _surf_blit( + spr.image, + clip, + ( + clip[0] + rect_offset_x, + clip[1] + rect_offset_y, + clip[2], + clip[3], + ), + spr.blendmode, + ) + else: # dirty sprite + if spr.visible: + _old_rect[spr] = _surf_blit( + spr.image, spr.rect, spr.source_rect, spr.blendmode + ) + if spr.dirty == 1: + spr.dirty = 0 + + @staticmethod + def _find_dirty_area( + _clip, _old_rect, _rect, _sprites, _update, _update_append, init_rect + ): + for spr in _sprites: + if spr.dirty > 0: + # chose the right rect + if spr.source_rect: + _union_rect = _rect(spr.rect.topleft, spr.source_rect.size) + else: + _union_rect = _rect(spr.rect) + + _union_rect_collidelist = _union_rect.collidelist + _union_rect_union_ip = _union_rect.union_ip + i = _union_rect_collidelist(_update) + while i > -1: + _union_rect_union_ip(_update[i]) + del _update[i] + i = _union_rect_collidelist(_update) + _update_append(_union_rect.clip(_clip)) + + if _old_rect[spr] is not init_rect: + _union_rect = _rect(_old_rect[spr]) + _union_rect_collidelist = _union_rect.collidelist + _union_rect_union_ip = _union_rect.union_ip + i = _union_rect_collidelist(_update) + while i > -1: + _union_rect_union_ip(_update[i]) + del _update[i] + i = _union_rect_collidelist(_update) + _update_append(_union_rect.clip(_clip)) + + def clear(self, surface, bgd): + """use to set background + + Group.clear(surface, bgd): return None + + """ + self._bgd = bgd + + def repaint_rect(self, screen_rect): + """repaint the given area + + LayeredDirty.repaint_rect(screen_rect): return None + + screen_rect is in screen coordinates. + + """ + if self._clip: + self.lostsprites.append(screen_rect.clip(self._clip)) + else: + self.lostsprites.append(Rect(screen_rect)) + + def set_clip(self, screen_rect=None): + """clip the area where to draw; pass None (default) to reset the clip + + LayeredDirty.set_clip(screen_rect=None): return None + + """ + if screen_rect is None: + self._clip = pygame.display.get_surface().get_rect() + else: + self._clip = screen_rect + self._use_update = False + + def get_clip(self): + """get the area where drawing will occur + + LayeredDirty.get_clip(): return Rect + + """ + return self._clip + + def change_layer(self, sprite, new_layer): + """change the layer of the sprite + + LayeredUpdates.change_layer(sprite, new_layer): return None + + The sprite must have been added to the renderer already. This is not + checked. + + """ + LayeredUpdates.change_layer(self, sprite, new_layer) + if sprite.dirty == 0: + sprite.dirty = 1 + + def set_timing_treshold(self, time_ms): + """set the threshold in milliseconds + + set_timing_treshold(time_ms): return None + + Defaults to 1000.0 / 80.0. This means that the screen will be painted + using the flip method rather than the update method if the update + method is taking so long to update the screen that the frame rate falls + below 80 frames per second. + + Raises TypeError if time_ms is not int or float. + + """ + warn( + "This function will be removed, use set_timing_threshold function instead", + DeprecationWarning, + ) + self.set_timing_threshold(time_ms) + + def set_timing_threshold(self, time_ms): + """set the threshold in milliseconds + + set_timing_threshold(time_ms): return None + + Defaults to 1000.0 / 80.0. This means that the screen will be painted + using the flip method rather than the update method if the update + method is taking so long to update the screen that the frame rate falls + below 80 frames per second. + + Raises TypeError if time_ms is not int or float. + + """ + if isinstance(time_ms, (int, float)): + self._time_threshold = time_ms + else: + raise TypeError( + f"Expected numeric value, got {time_ms.__class__.__name__} instead" + ) + + +class GroupSingle(AbstractGroup): + """A group container that holds a single most recent item. + + This class works just like a regular group, but it only keeps a single + sprite in the group. Whatever sprite has been added to the group last will + be the only sprite in the group. + + You can access its one sprite as the .sprite attribute. Assigning to this + attribute will properly remove the old sprite and then add the new one. + + """ + + def __init__(self, sprite=None): + AbstractGroup.__init__(self) + self.__sprite = None + if sprite is not None: + self.add(sprite) + + def copy(self): + return GroupSingle(self.__sprite) + + def sprites(self): + if self.__sprite is not None: + return [self.__sprite] + return [] + + def add_internal(self, sprite, _=None): + if self.__sprite is not None: + self.__sprite.remove_internal(self) + self.remove_internal(self.__sprite) + self.__sprite = sprite + + def __nonzero__(self): + return self.__sprite is not None + + __bool__ = __nonzero__ + + def _get_sprite(self): + return self.__sprite + + def _set_sprite(self, sprite): + self.add_internal(sprite) + sprite.add_internal(self) + return sprite + + @property + def sprite(self): + """ + Property for the single sprite contained in this group + + :return: The sprite. + """ + return self._get_sprite() + + @sprite.setter + def sprite(self, sprite_to_set): + self._set_sprite(sprite_to_set) + + def remove_internal(self, sprite): + if sprite is self.__sprite: + self.__sprite = None + if sprite in self.spritedict: + AbstractGroup.remove_internal(self, sprite) + + def has_internal(self, sprite): + return self.__sprite is sprite + + # Optimizations... + def __contains__(self, sprite): + return self.__sprite is sprite + + +# Some different collision detection functions that could be used. +def collide_rect(left, right): + """collision detection between two sprites, using rects. + + pygame.sprite.collide_rect(left, right): return bool + + Tests for collision between two sprites. Uses the pygame.Rect colliderect + function to calculate the collision. It is intended to be passed as a + collided callback function to the *collide functions. Sprites must have + "rect" attributes. + + New in pygame 1.8.0 + + """ + return left.rect.colliderect(right.rect) + + +class collide_rect_ratio: # noqa pylint: disable=invalid-name; this is a function-like class + """A callable class that checks for collisions using scaled rects + + The class checks for collisions between two sprites using a scaled version + of the sprites' rects. Is created with a ratio; the instance is then + intended to be passed as a collided callback function to the *collide + functions. + + New in pygame 1.8.1 + + """ + + def __init__(self, ratio): + """create a new collide_rect_ratio callable + + Ratio is expected to be a floating point value used to scale + the underlying sprite rect before checking for collisions. + + """ + self.ratio = ratio + + def __repr__(self): + """ + Turn the class into a string. + """ + # pylint: disable=consider-using-f-string + return "<{klass} @{id:x} {attrs}>".format( + klass=self.__class__.__name__, + id=id(self) & 0xFFFFFF, + attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()), + ) + + def __call__(self, left, right): + """detect collision between two sprites using scaled rects + + pygame.sprite.collide_rect_ratio(ratio)(left, right): return bool + + Tests for collision between two sprites. Uses the pygame.Rect + colliderect function to calculate the collision after scaling the rects + by the stored ratio. Sprites must have "rect" attributes. + + """ + + ratio = self.ratio + + leftrect = left.rect + width = leftrect.width + height = leftrect.height + leftrect = leftrect.inflate(width * ratio - width, height * ratio - height) + + rightrect = right.rect + width = rightrect.width + height = rightrect.height + rightrect = rightrect.inflate(width * ratio - width, height * ratio - height) + + return leftrect.colliderect(rightrect) + + +def collide_circle(left, right): + """detect collision between two sprites using circles + + pygame.sprite.collide_circle(left, right): return bool + + Tests for collision between two sprites by testing whether two circles + centered on the sprites overlap. If the sprites have a "radius" attribute, + then that radius is used to create the circle; otherwise, a circle is + created that is big enough to completely enclose the sprite's rect as + given by the "rect" attribute. This function is intended to be passed as + a collided callback function to the *collide functions. Sprites must have a + "rect" and an optional "radius" attribute. + + New in pygame 1.8.0 + + """ + + xdistance = left.rect.centerx - right.rect.centerx + ydistance = left.rect.centery - right.rect.centery + distancesquared = xdistance ** 2 + ydistance ** 2 + + try: + leftradius = left.radius + except AttributeError: + leftrect = left.rect + # approximating the radius of a square by using half of the diagonal, + # might give false positives (especially if its a long small rect) + leftradius = 0.5 * ((leftrect.width ** 2 + leftrect.height ** 2) ** 0.5) + # store the radius on the sprite for next time + left.radius = leftradius + + try: + rightradius = right.radius + except AttributeError: + rightrect = right.rect + # approximating the radius of a square by using half of the diagonal + # might give false positives (especially if its a long small rect) + rightradius = 0.5 * ((rightrect.width ** 2 + rightrect.height ** 2) ** 0.5) + # store the radius on the sprite for next time + right.radius = rightradius + return distancesquared <= (leftradius + rightradius) ** 2 + + +class collide_circle_ratio( + object +): # noqa pylint: disable=invalid-name; this is a function-like class + """detect collision between two sprites using scaled circles + + This callable class checks for collisions between two sprites using a + scaled version of a sprite's radius. It is created with a ratio as the + argument to the constructor. The instance is then intended to be passed as + a collided callback function to the *collide functions. + + New in pygame 1.8.1 + + """ + + def __init__(self, ratio): + """creates a new collide_circle_ratio callable instance + + The given ratio is expected to be a floating point value used to scale + the underlying sprite radius before checking for collisions. + + When the ratio is ratio=1.0, then it behaves exactly like the + collide_circle method. + + """ + self.ratio = ratio + + def __repr__(self): + """ + Turn the class into a string. + """ + # pylint: disable=consider-using-f-string + return "<{klass} @{id:x} {attrs}>".format( + klass=self.__class__.__name__, + id=id(self) & 0xFFFFFF, + attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()), + ) + + def __call__(self, left, right): + """detect collision between two sprites using scaled circles + + pygame.sprite.collide_circle_radio(ratio)(left, right): return bool + + Tests for collision between two sprites by testing whether two circles + centered on the sprites overlap after scaling the circle's radius by + the stored ratio. If the sprites have a "radius" attribute, that is + used to create the circle; otherwise, a circle is created that is big + enough to completely enclose the sprite's rect as given by the "rect" + attribute. Intended to be passed as a collided callback function to the + *collide functions. Sprites must have a "rect" and an optional "radius" + attribute. + + """ + + ratio = self.ratio + xdistance = left.rect.centerx - right.rect.centerx + ydistance = left.rect.centery - right.rect.centery + distancesquared = xdistance ** 2 + ydistance ** 2 + + try: + leftradius = left.radius + except AttributeError: + leftrect = left.rect + leftradius = 0.5 * ((leftrect.width ** 2 + leftrect.height ** 2) ** 0.5) + # store the radius on the sprite for next time + left.radius = leftradius + leftradius *= ratio + + try: + rightradius = right.radius + except AttributeError: + rightrect = right.rect + rightradius = 0.5 * ((rightrect.width ** 2 + rightrect.height ** 2) ** 0.5) + # store the radius on the sprite for next time + right.radius = rightradius + rightradius *= ratio + + return distancesquared <= (leftradius + rightradius) ** 2 + + +def collide_mask(left, right): + """collision detection between two sprites, using masks. + + pygame.sprite.collide_mask(SpriteLeft, SpriteRight): bool + + Tests for collision between two sprites by testing if their bitmasks + overlap. If the sprites have a "mask" attribute, that is used as the mask; + otherwise, a mask is created from the sprite image. Intended to be passed + as a collided callback function to the *collide functions. Sprites must + have a "rect" and an optional "mask" attribute. + + New in pygame 1.8.0 + + """ + xoffset = right.rect[0] - left.rect[0] + yoffset = right.rect[1] - left.rect[1] + try: + leftmask = left.mask + except AttributeError: + leftmask = from_surface(left.image) + try: + rightmask = right.mask + except AttributeError: + rightmask = from_surface(right.image) + return leftmask.overlap(rightmask, (xoffset, yoffset)) + + +def spritecollide(sprite, group, dokill, collided=None): + """find Sprites in a Group that intersect another Sprite + + pygame.sprite.spritecollide(sprite, group, dokill, collided=None): + return Sprite_list + + Return a list containing all Sprites in a Group that intersect with another + Sprite. Intersection is determined by comparing the Sprite.rect attribute + of each Sprite. + + The dokill argument is a bool. If set to True, all Sprites that collide + will be removed from the Group. + + The collided argument is a callback function used to calculate if two + sprites are colliding. it should take two sprites as values, and return a + bool value indicating if they are colliding. If collided is not passed, all + sprites must have a "rect" value, which is a rectangle of the sprite area, + which will be used to calculate the collision. + + """ + # pull the default collision function in as a local variable outside + # the loop as this makes the loop run faster + default_sprite_collide_func = sprite.rect.colliderect + + if dokill: + + crashed = [] + append = crashed.append + + for group_sprite in group.sprites(): + if collided: + if collided(sprite, group_sprite): + group_sprite.kill() + append(group_sprite) + else: + if default_sprite_collide_func(group_sprite.rect): + group_sprite.kill() + append(group_sprite) + + return crashed + + if collided: + return [ + group_sprite for group_sprite in group if collided(sprite, group_sprite) + ] + + return [ + group_sprite + for group_sprite in group + if default_sprite_collide_func(group_sprite.rect) + ] + + +def groupcollide(groupa, groupb, dokilla, dokillb, collided=None): + """detect collision between a group and another group + + pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb): + return dict + + Given two groups, this will find the intersections between all sprites in + each group. It returns a dictionary of all sprites in the first group that + collide. The value for each item in the dictionary is a list of the sprites + in the second group it collides with. The two dokill arguments control if + the sprites from either group will be automatically removed from all + groups. Collided is a callback function used to calculate if two sprites + are colliding. it should take two sprites as values, and return a bool + value indicating if they are colliding. If collided is not passed, all + sprites must have a "rect" value, which is a rectangle of the sprite area + that will be used to calculate the collision. + + """ + crashed = {} + # pull the collision function in as a local variable outside + # the loop as this makes the loop run faster + sprite_collide_func = spritecollide + if dokilla: + for group_a_sprite in groupa.sprites(): + collision = sprite_collide_func(group_a_sprite, groupb, dokillb, collided) + if collision: + crashed[group_a_sprite] = collision + group_a_sprite.kill() + else: + for group_a_sprite in groupa: + collision = sprite_collide_func(group_a_sprite, groupb, dokillb, collided) + if collision: + crashed[group_a_sprite] = collision + return crashed + + +def spritecollideany(sprite, group, collided=None): + """finds any sprites in a group that collide with the given sprite + + pygame.sprite.spritecollideany(sprite, group): return sprite + + Given a sprite and a group of sprites, this will return return any single + sprite that collides with with the given sprite. If there are no + collisions, then this returns None. + + If you don't need all the features of the spritecollide function, this + function will be a bit quicker. + + Collided is a callback function used to calculate if two sprites are + colliding. It should take two sprites as values and return a bool value + indicating if they are colliding. If collided is not passed, then all + sprites must have a "rect" value, which is a rectangle of the sprite area, + which will be used to calculate the collision. + + + """ + # pull the default collision function in as a local variable outside + # the loop as this makes the loop run faster + default_sprite_collide_func = sprite.rect.colliderect + + if collided is not None: + for group_sprite in group: + if collided(sprite, group_sprite): + return group_sprite + else: + # Special case old behaviour for speed. + for group_sprite in group: + if default_sprite_collide_func(group_sprite.rect): + return group_sprite + return None diff --git a/.venv/lib/python3.8/site-packages/pygame/sprite.pyi b/.venv/lib/python3.8/site-packages/pygame/sprite.pyi new file mode 100644 index 0000000..4e2f0fd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/sprite.pyi @@ -0,0 +1,152 @@ +from typing import ( + Any, + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Sequence, + SupportsFloat, + Tuple, + Union, +) + +from pygame.rect import Rect +from pygame.surface import Surface + +from ._common import _CanBeRect + +# Some functions violate Liskov substitution principle so mypy will throw errors for this file, but this are the +# best type hints I could do + +class Sprite: + image: Optional[Surface] = None + rect: Optional[Rect] = None + def __init__(self, *groups: AbstractGroup) -> None: ... + def update(self, *args: Any, **kwargs: Any) -> None: ... + def add(self, *groups: AbstractGroup) -> None: ... + def remove(self, *groups: AbstractGroup) -> None: ... + def kill(self) -> None: ... + def alive(self) -> bool: ... + def groups(self) -> List[AbstractGroup]: ... + +class DirtySprite(Sprite): + dirty: int + blendmode: int + source_rect: Rect + visible: int + _layer: int + def _set_visible(self, value: int) -> None: ... + def _get_visible(self) -> int: ... + +class AbstractGroup: + spritedict: Dict[Sprite, Rect] + lostsprites: List[int] # I think + def __init__(self) -> None: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[Sprite]: ... + def copy(self) -> AbstractGroup: ... + def sprites(self) -> List[Sprite]: ... + def add( + self, + *sprites: Union[Sprite, AbstractGroup, Iterable[Union[Sprite, AbstractGroup]]] + ) -> None: ... + def remove(self, *sprites: Sprite) -> None: ... + def has(self, *sprites: Sprite) -> bool: ... + def update(self, *args: Any, **kwargs: Any) -> None: ... + def draw(self, surface: Surface) -> List[Rect]: ... + def clear(self, surface_dest: Surface, background: Surface) -> None: ... + def empty(self) -> None: ... + +class Group(AbstractGroup): + def __init__(self, *sprites: Union[Sprite, Sequence[Sprite]]) -> None: ... + def copy(self) -> Group: ... + +class RenderPlain(Group): + def copy(self) -> RenderPlain: ... + +class RenderClear(Group): + def copy(self) -> RenderClear: ... + +class RenderUpdates(Group): + def copy(self) -> RenderUpdates: ... + def draw(self, surface: Surface) -> List[Rect]: ... + +class OrderedUpdates(RenderUpdates): + def copy(self) -> OrderedUpdates: ... + +class LayeredUpdates(AbstractGroup): + def __init__(self, *sprites: Sprite, **kwargs: Any) -> None: ... + def copy(self) -> LayeredUpdates: ... + def add(self, *sprites: Sprite, **kwargs: Any) -> None: ... + def draw(self, surface: Surface) -> List[Rect]: ... + def get_sprites_at( + self, pos: Union[Tuple[int, int], List[int]] + ) -> List[Sprite]: ... + def get_sprite(self, idx: int) -> Sprite: ... + def remove_sprites_of_layer(self, layer_nr: int) -> List[Sprite]: ... + def layers(self) -> List[int]: ... + def change_layer(self, sprite: Sprite, new_layer: int) -> None: ... + def get_layer_of_sprite(self, sprite: Sprite) -> int: ... + def get_top_layer(self) -> int: ... + def get_bottom_layer(self) -> int: ... + def move_to_front(self, sprite: Sprite) -> None: ... + def move_to_back(self, sprite: Sprite) -> None: ... + def get_top_sprite(self) -> Sprite: ... + def get_sprites_from_layer(self, layer: int) -> List[Sprite]: ... + def switch_layer(self, layer1_nr: int, layer2_nr: int) -> None: ... + +class LayeredDirty(LayeredUpdates): + def __init__(self, *sprites: DirtySprite, **kwargs: Any) -> None: ... + def copy(self) -> LayeredDirty: ... + def draw(self, surface: Surface, bgd: Optional[Surface] = None) -> List[Rect]: ... + def clear(self, surface: Surface, bgd: Surface) -> None: ... + def repaint_rect(self, screen_rect: _CanBeRect) -> None: ... + def set_clip(self, screen_rect: Optional[_CanBeRect] = None) -> None: ... + def get_clip(self) -> Rect: ... + def set_timing_treshold( + self, time_ms: SupportsFloat + ) -> None: ... # This actually accept any value + def set_timing_threshold( + self, time_ms: SupportsFloat + ) -> None: ... # This actually accept any value + +class GroupSingle(AbstractGroup): + sprite: Sprite + def __init__(self, sprite: Optional[Sprite] = None) -> None: ... + def copy(self) -> GroupSingle: ... + +def spritecollide( + sprite: Sprite, + group: AbstractGroup, + dokill: bool, + collided: Optional[Callable[[Sprite, Sprite], bool]] = None, +) -> List[Sprite]: ... +def collide_rect(left: Sprite, right: Sprite) -> bool: ... + +class collide_rect_ratio: + ratio: float + def __init__(self, ratio: float) -> None: ... + def __call__(self, left: Sprite, right: Sprite) -> bool: ... + +def collide_circle(left: Sprite, right: Sprite) -> bool: ... + +class collide_circle_ratio: + ratio: float + def __init__(self, ratio: float) -> None: ... + def __call__(self, left: Sprite, right: Sprite) -> bool: ... + +def collide_mask(sprite1: Sprite, sprite2: Sprite) -> Tuple[int, int]: ... +def groupcollide( + group1: AbstractGroup, + group2: AbstractGroup, + dokill: bool, + dokill2: bool, + collided: Optional[Callable[[Sprite, Sprite], bool]] = None, +) -> Dict[Sprite, Sprite]: ... +def spritecollideany( + sprite: Sprite, + group: AbstractGroup, + collided: Optional[Callable[[Sprite, Sprite], bool]] = None, +) -> Sprite: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/surface.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/surface.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..cd7fe09 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/surface.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/surface.pyi b/.venv/lib/python3.8/site-packages/pygame/surface.pyi new file mode 100644 index 0000000..7b086a5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/surface.pyi @@ -0,0 +1,121 @@ +from typing import Any, List, Optional, Sequence, Text, Tuple, Union, overload + +from pygame.bufferproxy import BufferProxy +from pygame.math import Vector2 +from pygame.rect import Rect + +from ._common import _CanBeRect, _ColorValue, _Coordinate, _RgbaOutput + +class Surface(object): + _pixels_address: int + @overload + def __init__( + self, + size: _Coordinate, + flags: int = ..., + depth: int = ..., + masks: Optional[_ColorValue] = ..., + ) -> None: ... + @overload + def __init__( + self, + size: _Coordinate, + flags: int = ..., + surface: Surface = ..., + ) -> None: ... + def blit( + self, + source: Surface, + dest: Union[_Coordinate, _CanBeRect], + area: Optional[_CanBeRect] = ..., + special_flags: int = ..., + ) -> Rect: ... + def blits( + self, + blit_sequence: Sequence[ + Union[ + Tuple[Surface, Union[_Coordinate, _CanBeRect]], + Tuple[Surface, Union[_Coordinate, _CanBeRect], Union[_CanBeRect, int]], + Tuple[Surface, Union[_Coordinate, _CanBeRect], _CanBeRect, int], + ] + ], + doreturn: Union[int, bool] = 1, + ) -> Union[List[Rect], None]: ... + @overload + def convert(self, surface: Surface) -> Surface: ... + @overload + def convert(self, depth: int, flags: int = ...) -> Surface: ... + @overload + def convert(self, masks: _ColorValue, flags: int = ...) -> Surface: ... + @overload + def convert(self) -> Surface: ... + @overload + def convert_alpha(self, surface: Surface) -> Surface: ... + @overload + def convert_alpha(self) -> Surface: ... + def copy(self) -> Surface: ... + def fill( + self, + color: _ColorValue, + rect: Optional[_CanBeRect] = ..., + special_flags: int = ..., + ) -> Rect: ... + def scroll(self, dx: int = ..., dy: int = ...) -> None: ... + @overload + def set_colorkey(self, color: _ColorValue, flags: int = ...) -> None: ... + @overload + def set_colorkey(self, color: None) -> None: ... + def get_colorkey(self) -> Optional[_RgbaOutput]: ... + @overload + def set_alpha(self, value: int, flags: int = ...) -> None: ... + @overload + def set_alpha(self, value: None) -> None: ... + def get_alpha(self) -> Optional[int]: ... + def lock(self) -> None: ... + def unlock(self) -> None: ... + def mustlock(self) -> bool: ... + def get_locked(self) -> bool: ... + def get_locks(self) -> Tuple[Any, ...]: ... + def get_at(self, x_y: Sequence[int]) -> _RgbaOutput: ... + def set_at(self, x_y: Sequence[int], color: _ColorValue) -> None: ... + def get_at_mapped(self, x_y: Sequence[int]) -> int: ... + def get_palette(self) -> List[_RgbaOutput]: ... + def get_palette_at(self, index: int) -> _RgbaOutput: ... + def set_palette(self, palette: List[_ColorValue]) -> None: ... + def set_palette_at(self, index: int, color: _ColorValue) -> None: ... + def map_rgb(self, color: _ColorValue) -> int: ... + def unmap_rgb(self, mapped_int: int) -> _RgbaOutput: ... + def set_clip(self, rect: Optional[_CanBeRect]) -> None: ... + def get_clip(self) -> Rect: ... + @overload + def subsurface(self, rect: Union[_CanBeRect, Rect]) -> Surface: ... + @overload + def subsurface( + self, + left_top: Union[List[float], Tuple[float, float], Vector2], + width_height: Union[List[float], Tuple[float, float], Vector2], + ) -> Surface: ... + @overload + def subsurface( + self, left: float, top: float, width: float, height: float + ) -> Surface: ... + def get_parent(self) -> Surface: ... + def get_abs_parent(self) -> Surface: ... + def get_offset(self) -> Tuple[int, int]: ... + def get_abs_offset(self) -> Tuple[int, int]: ... + def get_size(self) -> Tuple[int, int]: ... + def get_width(self) -> int: ... + def get_height(self) -> int: ... + def get_rect(self, **kwargs: Any) -> Rect: ... + def get_bitsize(self) -> int: ... + def get_bytesize(self) -> int: ... + def get_flags(self) -> int: ... + def get_pitch(self) -> int: ... + def get_masks(self) -> _RgbaOutput: ... + def set_masks(self, color: _ColorValue) -> None: ... + def get_shifts(self) -> _RgbaOutput: ... + def set_shifts(self, color: _ColorValue) -> None: ... + def get_losses(self) -> _RgbaOutput: ... + def get_bounding_rect(self, min_alpha: int = ...) -> Rect: ... + def get_view(self, kind: Text = ...) -> BufferProxy: ... + def get_buffer(self) -> BufferProxy: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/surfarray.py b/.venv/lib/python3.8/site-packages/pygame/surfarray.py new file mode 100644 index 0000000..70653fd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/surfarray.py @@ -0,0 +1,447 @@ +## pygame - Python Game Library +## Copyright (C) 2007 Marcus von Appen +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Library General Public +## License as published by the Free Software Foundation; either +## version 2 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Library General Public License for more details. +## +## You should have received a copy of the GNU Library General Public +## License along with this library; if not, write to the Free +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## +## Marcus von Appen +## mva@sysfault.org + +"""pygame module for accessing surface pixel data using array interfaces + +Functions to convert between NumPy arrays and Surface objects. This module +will only be functional when pygame can use the external NumPy package. +If NumPy can't be imported, surfarray becomes a MissingModule object. + +Every pixel is stored as a single integer value to represent the red, +green, and blue colors. The 8bit images use a value that looks into a +colormap. Pixels with higher depth use a bit packing process to place +three or four values into a single number. + +The arrays are indexed by the X axis first, followed by the Y +axis. Arrays that treat the pixels as a single integer are referred to +as 2D arrays. This module can also separate the red, green, and blue +color values into separate indices. These types of arrays are referred +to as 3D arrays, and the last index is 0 for red, 1 for green, and 2 for +blue. +""" + + +from pygame.pixelcopy import ( + array_to_surface, + surface_to_array, + map_array as pix_map_array, + make_surface as pix_make_surface, +) +import numpy +from numpy import ( + array as numpy_array, + empty as numpy_empty, + uint32 as numpy_uint32, + ndarray as numpy_ndarray, +) + +import warnings # will be removed in the future + + +# float96 not available on all numpy versions. +numpy_floats = [] +for type_name in "float32 float64 float96".split(): + if hasattr(numpy, type_name): + numpy_floats.append(getattr(numpy, type_name)) +# Added below due to deprecation of numpy.float. See issue #2814 +numpy_floats.append(float) + +# Pixel sizes corresponding to NumPy supported integer sizes, and therefore +# permissible for 2D reference arrays. +_pixel2d_bitdepths = {8, 16, 32} + + +__all__ = [ + "array2d", + "array3d", + "array_alpha", + "array_blue", + "array_colorkey", + "array_green", + "array_red", + "array_to_surface", + "blit_array", + "get_arraytype", + "get_arraytypes", + "make_surface", + "map_array", + "pixels2d", + "pixels3d", + "pixels_alpha", + "pixels_blue", + "pixels_green", + "pixels_red", + "surface_to_array", + "use_arraytype", +] + + +def blit_array(surface, array): + """pygame.surfarray.blit_array(Surface, array): return None + + Blit directly from a array values. + + Directly copy values from an array into a Surface. This is faster than + converting the array into a Surface and blitting. The array must be the + same dimensions as the Surface and will completely replace all pixel + values. Only integer, ascii character and record arrays are accepted. + + This function will temporarily lock the Surface as the new values are + copied. + """ + if isinstance(array, numpy_ndarray) and array.dtype in numpy_floats: + array = array.round(0).astype(numpy_uint32) + return array_to_surface(surface, array) + + +def make_surface(array): + """pygame.surfarray.make_surface (array): return Surface + + Copy an array to a new surface. + + Create a new Surface that best resembles the data and format on the + array. The array can be 2D or 3D with any sized integer values. + """ + if isinstance(array, numpy_ndarray) and array.dtype in numpy_floats: + array = array.round(0).astype(numpy_uint32) + return pix_make_surface(array) + + +def array2d(surface): + """pygame.surfarray.array2d(Surface): return array + + copy pixels into a 2d array + + Copy the pixels from a Surface into a 2D array. The bit depth of the + surface will control the size of the integer values, and will work + for any type of pixel format. + + This function will temporarily lock the Surface as pixels are copied + (see the Surface.lock - lock the Surface memory for pixel access + method). + """ + bpp = surface.get_bytesize() + try: + dtype = (numpy.uint8, numpy.uint16, numpy.int32, numpy.int32)[bpp - 1] + except IndexError: + raise ValueError(f"unsupported bit depth {bpp * 8} for 2D array") + size = surface.get_size() + array = numpy.empty(size, dtype) + surface_to_array(array, surface) + return array + + +def pixels2d(surface): + """pygame.surfarray.pixels2d(Surface): return array + + reference pixels into a 2d array + + Create a new 2D array that directly references the pixel values in a + Surface. Any changes to the array will affect the pixels in the + Surface. This is a fast operation since no data is copied. + + Pixels from a 24-bit Surface cannot be referenced, but all other + Surface bit depths can. + + The Surface this references will remain locked for the lifetime of + the array (see the Surface.lock - lock the Surface memory for pixel + access method). + """ + if surface.get_bitsize() not in _pixel2d_bitdepths: + raise ValueError("unsupport bit depth for 2D reference array") + try: + return numpy_array(surface.get_view("2"), copy=False) + except (ValueError, TypeError): + raise ValueError( + f"bit depth {surface.get_bitsize()} unsupported for 2D reference array" + ) + + +def array3d(surface): + """pygame.surfarray.array3d(Surface): return array + + copy pixels into a 3d array + + Copy the pixels from a Surface into a 3D array. The bit depth of the + surface will control the size of the integer values, and will work + for any type of pixel format. + + This function will temporarily lock the Surface as pixels are copied + (see the Surface.lock - lock the Surface memory for pixel access + method). + """ + width, height = surface.get_size() + array = numpy.empty((width, height, 3), numpy.uint8) + surface_to_array(array, surface) + return array + + +def pixels3d(surface): + """pygame.surfarray.pixels3d(Surface): return array + + reference pixels into a 3d array + + Create a new 3D array that directly references the pixel values in a + Surface. Any changes to the array will affect the pixels in the + Surface. This is a fast operation since no data is copied. + + This will only work on Surfaces that have 24-bit or 32-bit + formats. Lower pixel formats cannot be referenced. + + The Surface this references will remain locked for the lifetime of + the array (see the Surface.lock - lock the Surface memory for pixel + access method). + """ + return numpy_array(surface.get_view("3"), copy=False) + + +def array_alpha(surface): + """pygame.surfarray.array_alpha(Surface): return array + + copy pixel alphas into a 2d array + + Copy the pixel alpha values (degree of transparency) from a Surface + into a 2D array. This will work for any type of Surface + format. Surfaces without a pixel alpha will return an array with all + opaque values. + + This function will temporarily lock the Surface as pixels are copied + (see the Surface.lock - lock the Surface memory for pixel access + method). + """ + size = surface.get_size() + array = numpy.empty(size, numpy.uint8) + surface_to_array(array, surface, "A") + return array + + +def pixels_alpha(surface): + """pygame.surfarray.pixels_alpha(Surface): return array + + reference pixel alphas into a 2d array + + Create a new 2D array that directly references the alpha values + (degree of transparency) in a Surface. Any changes to the array will + affect the pixels in the Surface. This is a fast operation since no + data is copied. + + This can only work on 32-bit Surfaces with a per-pixel alpha value. + + The Surface this array references will remain locked for the + lifetime of the array. + """ + return numpy.array(surface.get_view("A"), copy=False) + + +def pixels_red(surface): + """pygame.surfarray.pixels_red(Surface): return array + + Reference pixel red into a 2d array. + + Create a new 2D array that directly references the red values + in a Surface. Any changes to the array will affect the pixels + in the Surface. This is a fast operation since no data is copied. + + This can only work on 24-bit or 32-bit Surfaces. + + The Surface this array references will remain locked for the + lifetime of the array. + """ + return numpy.array(surface.get_view("R"), copy=False) + + +def array_red(surface): + """pygame.surfarray.array_red(Surface): return array + + copy pixel red into a 2d array + + Copy the pixel red values from a Surface into a 2D array. This will work + for any type of Surface format. + + This function will temporarily lock the Surface as pixels are copied + (see the Surface.lock - lock the Surface memory for pixel access + method). + """ + size = surface.get_size() + array = numpy.empty(size, numpy.uint8) + surface_to_array(array, surface, "R") + return array + + +def pixels_green(surface): + """pygame.surfarray.pixels_green(Surface): return array + + Reference pixel green into a 2d array. + + Create a new 2D array that directly references the green values + in a Surface. Any changes to the array will affect the pixels + in the Surface. This is a fast operation since no data is copied. + + This can only work on 24-bit or 32-bit Surfaces. + + The Surface this array references will remain locked for the + lifetime of the array. + """ + return numpy.array(surface.get_view("G"), copy=False) + + +def array_green(surface): + """pygame.surfarray.array_green(Surface): return array + + copy pixel green into a 2d array + + Copy the pixel green values from a Surface into a 2D array. This will work + for any type of Surface format. + + This function will temporarily lock the Surface as pixels are copied + (see the Surface.lock - lock the Surface memory for pixel access + method). + """ + size = surface.get_size() + array = numpy.empty(size, numpy.uint8) + surface_to_array(array, surface, "G") + return array + + +def pixels_blue(surface): + """pygame.surfarray.pixels_blue(Surface): return array + + Reference pixel blue into a 2d array. + + Create a new 2D array that directly references the blue values + in a Surface. Any changes to the array will affect the pixels + in the Surface. This is a fast operation since no data is copied. + + This can only work on 24-bit or 32-bit Surfaces. + + The Surface this array references will remain locked for the + lifetime of the array. + """ + return numpy.array(surface.get_view("B"), copy=False) + + +def array_blue(surface): + """pygame.surfarray.array_blue(Surface): return array + + copy pixel blue into a 2d array + + Copy the pixel blue values from a Surface into a 2D array. This will work + for any type of Surface format. + + This function will temporarily lock the Surface as pixels are copied + (see the Surface.lock - lock the Surface memory for pixel access + method). + """ + size = surface.get_size() + array = numpy.empty(size, numpy.uint8) + surface_to_array(array, surface, "B") + return array + + +def array_colorkey(surface): + """pygame.surfarray.array_colorkey(Surface): return array + + copy the colorkey values into a 2d array + + Create a new array with the colorkey transparency value from each + pixel. If the pixel matches the colorkey it will be fully + tranparent; otherwise it will be fully opaque. + + This will work on any type of Surface format. If the image has no + colorkey a solid opaque array will be returned. + + This function will temporarily lock the Surface as pixels are + copied. + """ + size = surface.get_size() + array = numpy.empty(size, numpy.uint8) + surface_to_array(array, surface, "C") + return array + + +def map_array(surface, array): + """pygame.surfarray.map_array(Surface, array3d): return array2d + + map a 3d array into a 2d array + + Convert a 3D array into a 2D array. This will use the given Surface + format to control the conversion. + + Note: arrays do not need to be 3D, as long as the minor axis has + three elements giving the component colours, any array shape can be + used (for example, a single colour can be mapped, or an array of + colours). The array shape is limited to eleven dimensions maximum, + including the three element minor axis. + """ + if array.ndim == 0: + raise ValueError("array must have at least 1 dimension") + shape = array.shape + if shape[-1] != 3: + raise ValueError("array must be a 3d array of 3-value color data") + target = numpy_empty(shape[:-1], numpy.int32) + pix_map_array(target, array, surface) + return target + + +def use_arraytype(arraytype): + """pygame.surfarray.use_arraytype(arraytype): return None + + DEPRECATED - only numpy arrays are now supported. + """ + warnings.warn( + DeprecationWarning( + "only numpy arrays are now supported, " + "this function will be removed in a " + "future version of the module" + ) + ) + arraytype = arraytype.lower() + if arraytype != "numpy": + raise ValueError("invalid array type") + + +def get_arraytype(): + """pygame.surfarray.get_arraytype(): return str + + DEPRECATED - only numpy arrays are now supported. + """ + warnings.warn( + DeprecationWarning( + "only numpy arrays are now supported, " + "this function will be removed in a " + "future version of the module" + ) + ) + return "numpy" + + +def get_arraytypes(): + """pygame.surfarray.get_arraytypes(): return tuple + + DEPRECATED - only numpy arrays are now supported. + """ + warnings.warn( + DeprecationWarning( + "only numpy arrays are now supported, " + "this function will be removed in a " + "future version of the module" + ) + ) + return ("numpy",) diff --git a/.venv/lib/python3.8/site-packages/pygame/surfarray.pyi b/.venv/lib/python3.8/site-packages/pygame/surfarray.pyi new file mode 100644 index 0000000..33d7dbb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/surfarray.pyi @@ -0,0 +1,25 @@ +from typing import Tuple + +import numpy + +from pygame.surface import Surface + +def array2d(surface: Surface) -> numpy.ndarray: ... +def pixels2d(surface: Surface) -> numpy.ndarray: ... +def array3d(surface: Surface) -> numpy.ndarray: ... +def pixels3d(surface: Surface) -> numpy.ndarray: ... +def array_alpha(surface: Surface) -> numpy.ndarray: ... +def pixels_alpha(surface: Surface) -> numpy.ndarray: ... +def array_red(surface: Surface) -> numpy.ndarray: ... +def pixels_red(surface: Surface) -> numpy.ndarray: ... +def array_green(surface: Surface) -> numpy.ndarray: ... +def pixels_green(surface: Surface) -> numpy.ndarray: ... +def array_blue(surface: Surface) -> numpy.ndarray: ... +def pixels_blue(surface: Surface) -> numpy.ndarray: ... +def array_colorkey(surface: Surface) -> numpy.ndarray: ... +def make_surface(array: numpy.ndarray) -> Surface: ... +def blit_array(surface: Surface, array: numpy.ndarray) -> None: ... +def map_array(surface: Surface, array3d: numpy.ndarray) -> numpy.ndarray: ... +def use_arraytype(arraytype: str) -> None: ... +def get_arraytype() -> str: ... +def get_arraytypes() -> Tuple[str]: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/surflock.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/surflock.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..92391ff Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/surflock.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/sysfont.py b/.venv/lib/python3.8/site-packages/pygame/sysfont.py new file mode 100644 index 0000000..588a5b5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/sysfont.py @@ -0,0 +1,529 @@ +# coding: ascii +# pygame - Python Game Library +# Copyright (C) 2000-2003 Pete Shinners +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Pete Shinners +# pete@shinners.org +"""sysfont, used in the font module to find system fonts""" + +import os +import sys +from os.path import basename, dirname, exists, join, splitext + +from pygame.font import Font + + +OpenType_extensions = frozenset((".ttf", ".ttc", ".otf")) +Sysfonts = {} +Sysalias = {} + +# Python 3 compatibility + + +def toascii(raw): + """convert bytes to ASCII-only string""" + return raw.decode("ascii", "ignore") + + +if os.name == "nt": + import winreg as _winreg +else: + import subprocess + + +def _simplename(name): + """create simple version of the font name""" + # return alphanumeric characters of a string (converted to lowercase) + return "".join(c.lower() for c in name if c.isalnum()) + + +def _addfont(name, bold, italic, font, fontdict): + """insert a font and style into the font dictionary""" + if name not in fontdict: + fontdict[name] = {} + fontdict[name][bold, italic] = font + + +def initsysfonts_win32(): + """initialize fonts dictionary on Windows""" + + fontdir = join(os.environ.get("WINDIR", "C:\\Windows"), "Fonts") + + fonts = {} + + # add fonts entered in the registry + + # find valid registry keys containing font information. + # http://docs.python.org/lib/module-sys.html + # 0 (VER_PLATFORM_WIN32s) Win32s on Windows 3.1 + # 1 (VER_PLATFORM_WIN32_WINDOWS) Windows 95/98/ME + # 2 (VER_PLATFORM_WIN32_NT) Windows NT/2000/XP + # 3 (VER_PLATFORM_WIN32_CE) Windows CE + if sys.getwindowsversion()[0] == 1: + key_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts" + else: + key_name = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" + key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, key_name) + + for i in range(_winreg.QueryInfoKey(key)[1]): + try: + # name is the font's name e.g. Times New Roman (TrueType) + # font is the font's filename e.g. times.ttf + name, font = _winreg.EnumValue(key, i)[0:2] + except EnvironmentError: + break + + # try to handle windows unicode strings for file names with + # international characters + + # here are two documents with some information about it: + # http://www.python.org/peps/pep-0277.html + # https://www.microsoft.com/technet/archive/interopmigration/linux/mvc/lintowin.mspx#ECAA + try: + font = str(font) + except UnicodeEncodeError: + # MBCS is the windows encoding for unicode file names. + try: + font = font.encode("MBCS") + except UnicodeEncodeError: + # no success with str or MBCS encoding... skip this font. + continue + + if splitext(font)[1].lower() not in OpenType_extensions: + continue + if not dirname(font): + font = join(fontdir, font) + + # Some are named A & B, both names should be processed separately + # Ex: the main Cambria file is marked as "Cambria & Cambria Math" + for name in name.split("&"): + _parse_font_entry_win(name, font, fonts) + + return fonts + + +def _parse_font_entry_win(name, font, fonts): + """ + Parse out a simpler name and the font style from the initial file name. + + :param name: The font name + :param font: The font file path + :param fonts: The pygame font dictionary + + :return: Tuple of (bold, italic, name) + """ + true_type_suffix = "(TrueType)" + mods = ("demibold", "narrow", "light", "unicode", "bt", "mt") + if name.endswith(true_type_suffix): + name = name.rstrip(true_type_suffix).rstrip() + name = name.lower().split() + bold = italic = False + for mod in mods: + if mod in name: + name.remove(mod) + if "bold" in name: + name.remove("bold") + bold = True + if "italic" in name: + name.remove("italic") + italic = True + name = "".join(name) + name = _simplename(name) + + _addfont(name, bold, italic, font, fonts) + + +def _parse_font_entry_darwin(name, filepath, fonts): + """ + Parses a font entry for macOS + + :param name: The filepath without extensions or directories + :param filepath: The full path to the font + :param fonts: The pygame font dictionary to add the parsed font data to. + """ + + name = _simplename(name) + + mods = ("regular",) + + for mod in mods: + if mod in name: + name = name.replace(mod, "") + + bold = italic = False + if "bold" in name: + name = name.replace("bold", "") + bold = True + if "italic" in name: + name = name.replace("italic", "") + italic = True + + _addfont(name, bold, italic, filepath, fonts) + + +def _font_finder_darwin(): + locations = [ + "/Library/Fonts", + "/Network/Library/Fonts", + "/System/Library/Fonts", + "/System/Library/Fonts/Supplemental", + ] + + username = os.getenv("USER") + if username: + locations.append("/Users/" + username + "/Library/Fonts") + + strange_root = "/System/Library/Assets/com_apple_MobileAsset_Font3" + if exists(strange_root): + strange_locations = os.listdir(strange_root) + for loc in strange_locations: + locations.append(strange_root + "/" + loc + "/AssetData") + + fonts = {} + + for location in locations: + if not exists(location): + continue + + files = os.listdir(location) + for file in files: + name, extension = splitext(file) + if extension in OpenType_extensions: + _parse_font_entry_darwin(name, join(location, file), fonts) + + return fonts + + +def initsysfonts_darwin(): + """Read the fonts on MacOS, and OS X.""" + # if the X11 binary exists... try and use that. + # Not likely to be there on pre 10.4.x ... or MacOS 10.10+ + if exists("/usr/X11/bin/fc-list"): + fonts = initsysfonts_unix("/usr/X11/bin/fc-list") + # This fc-list path will work with the X11 from the OS X 10.3 installation + # disc + elif exists("/usr/X11R6/bin/fc-list"): + fonts = initsysfonts_unix("/usr/X11R6/bin/fc-list") + else: + # eventually this should probably be the preferred solution + fonts = _font_finder_darwin() + + return fonts + + +# read the fonts on unix +def initsysfonts_unix(path="fc-list"): + """use the fc-list from fontconfig to get a list of fonts""" + fonts = {} + + try: + # pylint: disable=consider-using-with + # subprocess.Popen is not a context manager in all of + # pygame's supported python versions. + + # note, we capture stderr so if fc-list isn't there to stop stderr + # printing. + flout, _ = subprocess.Popen( + f"{path} : file family style", + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + ).communicate() + except (OSError, ValueError): + return fonts + + entries = toascii(flout) + try: + for entry in entries.split("\n"): + + try: + _parse_font_entry_unix(entry, fonts) + except ValueError: + # try the next one. + pass + + except ValueError: + pass + + return fonts + + +def _parse_font_entry_unix(entry, fonts): + """ + Parses an entry in the unix font data to add to the pygame font + dictionary. + + :param entry: A entry from the unix font list. + :param fonts: The pygame font dictionary to add the parsed font data to. + + """ + filename, family, style = entry.split(":", 2) + if splitext(filename)[1].lower() in OpenType_extensions: + bold = "Bold" in style + italic = "Italic" in style + oblique = "Oblique" in style + for name in family.strip().split(","): + if name: + break + else: + name = splitext(basename(filename))[0] + + _addfont(_simplename(name), bold, italic or oblique, filename, fonts) + + +def create_aliases(): + """Map common fonts that are absent from the system to similar fonts + that are installed in the system + """ + alias_groups = ( + ( + "monospace", + "misc-fixed", + "courier", + "couriernew", + "console", + "fixed", + "mono", + "freemono", + "bitstreamverasansmono", + "verasansmono", + "monotype", + "lucidaconsole", + "consolas", + "dejavusansmono", + "liberationmono", + ), + ( + "sans", + "arial", + "helvetica", + "swiss", + "freesans", + "bitstreamverasans", + "verasans", + "verdana", + "tahoma", + "calibri", + "gillsans", + "segoeui", + "trebuchetms", + "ubuntu", + "dejavusans", + "liberationsans", + ), + ( + "serif", + "times", + "freeserif", + "bitstreamveraserif", + "roman", + "timesroman", + "timesnewroman", + "dutch", + "veraserif", + "georgia", + "cambria", + "constantia", + "dejavuserif", + "liberationserif", + ), + ("wingdings", "wingbats"), + ("comicsansms", "comicsans"), + ) + for alias_set in alias_groups: + for name in alias_set: + if name in Sysfonts: + found = Sysfonts[name] + break + else: + continue + for name in alias_set: + if name not in Sysfonts: + Sysalias[name] = found + + +def initsysfonts(): + """ + Initialise the sysfont module, called once. Locates the installed fonts + and creates some aliases for common font categories. + + Has different initialisation functions for different platforms. + """ + if sys.platform == "win32": + fonts = initsysfonts_win32() + elif sys.platform == "darwin": + fonts = initsysfonts_darwin() + else: + fonts = initsysfonts_unix() + Sysfonts.update(fonts) + create_aliases() + if not Sysfonts: # dummy so we don't try to reinit + Sysfonts[None] = None + + +def font_constructor(fontpath, size, bold, italic): + """ + pygame.font specific declarations + + :param fontpath: path to a font. + :param size: size of a font. + :param bold: bold style, True or False. + :param italic: italic style, True or False. + + :return: A font.Font object. + """ + + font = Font(fontpath, size) + if bold: + font.set_bold(True) + if italic: + font.set_italic(True) + + return font + + +# the exported functions + + +def SysFont(name, size, bold=False, italic=False, constructor=None): + """pygame.font.SysFont(name, size, bold=False, italic=False, constructor=None) -> Font + Create a pygame Font from system font resources. + + This will search the system fonts for the given font + name. You can also enable bold or italic styles, and + the appropriate system font will be selected if available. + + This will always return a valid Font object, and will + fallback on the builtin pygame font if the given font + is not found. + + Name can also be an iterable of font names, a string of + comma-separated font names, or a bytes of comma-separated + font names, in which case the set of names will be searched + in order. Pygame uses a small set of common font aliases. If the + specific font you ask for is not available, a reasonable + alternative may be used. + + If optional constructor is provided, it must be a function with + signature constructor(fontpath, size, bold, italic) which returns + a Font instance. If None, a pygame.font.Font object is created. + """ + if constructor is None: + constructor = font_constructor + + if not Sysfonts: + initsysfonts() + + gotbold = gotitalic = False + fontname = None + if name: + if isinstance(name, (str, bytes)): + name = name.split(b"," if isinstance(name, bytes) else ",") + for single_name in name: + if isinstance(single_name, bytes): + single_name = single_name.decode() + + single_name = _simplename(single_name) + styles = Sysfonts.get(single_name) + if not styles: + styles = Sysalias.get(single_name) + if styles: + plainname = styles.get((False, False)) + fontname = styles.get((bold, italic)) + if not (fontname or plainname): + # Neither requested style, nor plain font exists, so + # return a font with the name requested, but an + # arbitrary style. + (style, fontname) = list(styles.items())[0] + # Attempt to style it as requested. This can't + # unbold or unitalicize anything, but it can + # fake bold and/or fake italicize. + if bold and style[0]: + gotbold = True + if italic and style[1]: + gotitalic = True + elif not fontname: + fontname = plainname + elif plainname != fontname: + gotbold = bold + gotitalic = italic + if fontname: + break + + set_bold = set_italic = False + if bold and not gotbold: + set_bold = True + if italic and not gotitalic: + set_italic = True + + return constructor(fontname, size, set_bold, set_italic) + + +def get_fonts(): + """pygame.font.get_fonts() -> list + get a list of system font names + + Returns the list of all found system fonts. Note that + the names of the fonts will be all lowercase with spaces + removed. This is how pygame internally stores the font + names for matching. + """ + if not Sysfonts: + initsysfonts() + return list(Sysfonts) + + +def match_font(name, bold=0, italic=0): + """pygame.font.match_font(name, bold=0, italic=0) -> name + find the filename for the named system font + + This performs the same font search as the SysFont() + function, only it returns the path to the TTF file + that would be loaded. The font name can also be an + iterable of font names or a string/bytes of comma-separated + font names to try. + + If no match is found, None is returned. + """ + if not Sysfonts: + initsysfonts() + + fontname = None + if isinstance(name, (str, bytes)): + name = name.split(b"," if isinstance(name, bytes) else ",") + + for single_name in name: + if isinstance(single_name, bytes): + single_name = single_name.decode() + + single_name = _simplename(single_name) + styles = Sysfonts.get(single_name) + if not styles: + styles = Sysalias.get(single_name) + if styles: + while not fontname: + fontname = styles.get((bold, italic)) + if italic: + italic = 0 + elif bold: + bold = 0 + elif not fontname: + fontname = list(styles.values())[0] + if fontname: + break + return fontname diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/__init__.py new file mode 100644 index 0000000..dd26586 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/__init__.py @@ -0,0 +1,40 @@ +"""Pygame unit test suite package + +Exports function run() + +A quick way to run the test suite package from the command line +is by importing the go submodule: + +python -m "import pygame.tests" [] + +Command line option --help displays a usage message. Available options +correspond to the pygame.tests.run arguments. + +The xxxx_test submodules of the tests package are unit test suites for +individual parts of Pygame. Each can also be run as a main program. This is +useful if the test, such as cdrom_test, is interactive. + +For Pygame development the test suite can be run from a Pygame distribution +root directory using run_tests.py. Alternately, test/__main__.py can be run +directly. + +""" + +if __name__ == "pygame.tests": + from pygame.tests.test_utils.run_tests import run +elif __name__ == "__main__": + import os + import sys + + pkg_dir = os.path.split(os.path.abspath(__file__))[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) + + if is_pygame_pkg: + import pygame.tests.__main__ + else: + import test.__main__ +else: + from test.test_utils.run_tests import run diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__main__.py b/.venv/lib/python3.8/site-packages/pygame/tests/__main__.py new file mode 100644 index 0000000..abdd92e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/__main__.py @@ -0,0 +1,144 @@ +"""Load and run the Pygame test suite + +python -c "import pygame.tests.go" [] + +or + +python test/go.py [] + +Command line option --help displays a command line usage message. + +run_tests.py in the main distribution directory is an alternative to test.go + +""" + +import sys + +if __name__ == "__main__": + import os + + pkg_dir = os.path.split(os.path.abspath(__file__))[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +if is_pygame_pkg: + from pygame.tests.test_utils.run_tests import run_and_exit + from pygame.tests.test_utils.test_runner import opt_parser +else: + from test.test_utils.run_tests import run_and_exit + from test.test_utils.test_runner import opt_parser + +if is_pygame_pkg: + test_pkg_name = "pygame.tests" +else: + test_pkg_name = "test" +program_name = sys.argv[0] +if program_name == "-c": + program_name = 'python -c "import %s.go"' % test_pkg_name + +########################################################################### +# Set additional command line options +# +# Defined in test_runner.py as it shares options, added to here + +opt_parser.set_usage( + """ + +Runs all or some of the %(pkg)s.xxxx_test tests. + +$ %(exec)s sprite threads -sd + +Runs the sprite and threads module tests isolated in subprocesses, dumping +all failing tests info in the form of a dict. + +""" + % {"pkg": test_pkg_name, "exec": program_name} +) + +opt_parser.add_option( + "-d", "--dump", action="store_true", help="dump results as dict ready to eval" +) + +opt_parser.add_option("-F", "--file", help="dump results to a file") + +opt_parser.add_option( + "-m", + "--multi_thread", + metavar="THREADS", + type="int", + help="run subprocessed tests in x THREADS", +) + +opt_parser.add_option( + "-t", + "--time_out", + metavar="SECONDS", + type="int", + help="kill stalled subprocessed tests after SECONDS", +) + +opt_parser.add_option( + "-f", "--fake", metavar="DIR", help="run fake tests in run_tests__tests/$DIR" +) + +opt_parser.add_option( + "-p", + "--python", + metavar="PYTHON", + help="path to python excutable to run subproccesed tests\n" + "default (sys.executable): %s" % sys.executable, +) + +opt_parser.add_option( + "-I", + "--interactive", + action="store_true", + help="include tests requiring user input", +) + +opt_parser.add_option("-S", "--seed", type="int", help="Randomisation seed") + +########################################################################### +# Set run() keyword arguements according to command line arguemnts. +# args will be the test module list, passed as positional argumemts. + +options, args = opt_parser.parse_args() +kwds = {} +if options.incomplete: + kwds["incomplete"] = True +if options.usesubprocess: + kwds["usesubprocess"] = True +else: + kwds["usesubprocess"] = False +if options.dump: + kwds["dump"] = True +if options.file: + kwds["file"] = options.file +if options.exclude: + kwds["exclude"] = options.exclude +if options.unbuffered: + kwds["unbuffered"] = True +if options.randomize: + kwds["randomize"] = True +if options.seed is not None: + kwds["seed"] = options.seed +if options.multi_thread is not None: + kwds["multi_thread"] = options.multi_thread +if options.time_out is not None: + kwds["time_out"] = options.time_out +if options.fake: + kwds["fake"] = options.fake +if options.python: + kwds["python"] = options.python +if options.interactive: + kwds["interactive"] = True +kwds["verbosity"] = options.verbosity if options.verbosity is not None else 1 + + +########################################################################### +# Run the test suite. +run_and_exit(*args, **kwds) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d966f2a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..6162359 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/base_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/base_test.cpython-38.pyc new file mode 100644 index 0000000..1167821 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/base_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/blit_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/blit_test.cpython-38.pyc new file mode 100644 index 0000000..58164d3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/blit_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/bufferproxy_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/bufferproxy_test.cpython-38.pyc new file mode 100644 index 0000000..139f6dd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/bufferproxy_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/camera_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/camera_test.cpython-38.pyc new file mode 100644 index 0000000..71cd088 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/camera_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/color_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/color_test.cpython-38.pyc new file mode 100644 index 0000000..7564e93 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/color_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/constants_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/constants_test.cpython-38.pyc new file mode 100644 index 0000000..c81c115 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/constants_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/controller_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/controller_test.cpython-38.pyc new file mode 100644 index 0000000..efeb96b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/controller_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/cursors_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/cursors_test.cpython-38.pyc new file mode 100644 index 0000000..f48073a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/cursors_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/display_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/display_test.cpython-38.pyc new file mode 100644 index 0000000..1e435d5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/display_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/docs_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/docs_test.cpython-38.pyc new file mode 100644 index 0000000..02aba08 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/docs_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/draw_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/draw_test.cpython-38.pyc new file mode 100644 index 0000000..c9ee1cf Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/draw_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/event_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/event_test.cpython-38.pyc new file mode 100644 index 0000000..be593fd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/event_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/font_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/font_test.cpython-38.pyc new file mode 100644 index 0000000..75ab3e7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/font_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/freetype_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/freetype_tags.cpython-38.pyc new file mode 100644 index 0000000..2eb1f8f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/freetype_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/freetype_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/freetype_test.cpython-38.pyc new file mode 100644 index 0000000..a042f80 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/freetype_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/ftfont_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/ftfont_tags.cpython-38.pyc new file mode 100644 index 0000000..8eb3136 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/ftfont_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/ftfont_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/ftfont_test.cpython-38.pyc new file mode 100644 index 0000000..5933c3a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/ftfont_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/gfxdraw_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/gfxdraw_test.cpython-38.pyc new file mode 100644 index 0000000..48db60a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/gfxdraw_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image__save_gl_surface_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image__save_gl_surface_test.cpython-38.pyc new file mode 100644 index 0000000..d3bda2e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image__save_gl_surface_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image_tags.cpython-38.pyc new file mode 100644 index 0000000..a3108d6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image_test.cpython-38.pyc new file mode 100644 index 0000000..1b42b13 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/image_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/imageext_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/imageext_tags.cpython-38.pyc new file mode 100644 index 0000000..767d24f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/imageext_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/imageext_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/imageext_test.cpython-38.pyc new file mode 100644 index 0000000..8c7da29 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/imageext_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/joystick_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/joystick_test.cpython-38.pyc new file mode 100644 index 0000000..a3222e1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/joystick_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/key_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/key_test.cpython-38.pyc new file mode 100644 index 0000000..0964b4b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/key_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mask_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mask_test.cpython-38.pyc new file mode 100644 index 0000000..22e8e37 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mask_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/math_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/math_test.cpython-38.pyc new file mode 100644 index 0000000..a41adc3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/math_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/midi_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/midi_test.cpython-38.pyc new file mode 100644 index 0000000..95380b3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/midi_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_music_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_music_tags.cpython-38.pyc new file mode 100644 index 0000000..8707eec Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_music_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_music_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_music_test.cpython-38.pyc new file mode 100644 index 0000000..9c218b6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_music_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_tags.cpython-38.pyc new file mode 100644 index 0000000..5f34105 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_test.cpython-38.pyc new file mode 100644 index 0000000..7760ec9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mixer_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mouse_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mouse_test.cpython-38.pyc new file mode 100644 index 0000000..65306ed Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/mouse_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/pixelarray_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/pixelarray_test.cpython-38.pyc new file mode 100644 index 0000000..1daa41d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/pixelarray_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/pixelcopy_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/pixelcopy_test.cpython-38.pyc new file mode 100644 index 0000000..f712d6b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/pixelcopy_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/rect_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/rect_test.cpython-38.pyc new file mode 100644 index 0000000..fcfa45d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/rect_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/rwobject_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/rwobject_test.cpython-38.pyc new file mode 100644 index 0000000..fc06a12 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/rwobject_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/scrap_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/scrap_tags.cpython-38.pyc new file mode 100644 index 0000000..2bee7a7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/scrap_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/scrap_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/scrap_test.cpython-38.pyc new file mode 100644 index 0000000..9e7be04 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/scrap_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sndarray_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sndarray_tags.cpython-38.pyc new file mode 100644 index 0000000..214f6f4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sndarray_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sndarray_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sndarray_test.cpython-38.pyc new file mode 100644 index 0000000..a9997a4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sndarray_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sprite_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sprite_test.cpython-38.pyc new file mode 100644 index 0000000..57437bd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sprite_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surface_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surface_test.cpython-38.pyc new file mode 100644 index 0000000..cacffdc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surface_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surfarray_tags.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surfarray_tags.cpython-38.pyc new file mode 100644 index 0000000..f8ea30e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surfarray_tags.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surfarray_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surfarray_test.cpython-38.pyc new file mode 100644 index 0000000..4a69de2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surfarray_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surflock_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surflock_test.cpython-38.pyc new file mode 100644 index 0000000..009d5b8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/surflock_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sysfont_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sysfont_test.cpython-38.pyc new file mode 100644 index 0000000..6dc0c56 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/sysfont_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/test_test_.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/test_test_.cpython-38.pyc new file mode 100644 index 0000000..8fc4a15 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/test_test_.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/threads_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/threads_test.cpython-38.pyc new file mode 100644 index 0000000..b7d437c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/threads_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/time_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/time_test.cpython-38.pyc new file mode 100644 index 0000000..870901c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/time_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/touch_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/touch_test.cpython-38.pyc new file mode 100644 index 0000000..7743d08 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/touch_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/transform_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/transform_test.cpython-38.pyc new file mode 100644 index 0000000..6f9db59 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/transform_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/version_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/version_test.cpython-38.pyc new file mode 100644 index 0000000..0ab33f1 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/version_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/video_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/video_test.cpython-38.pyc new file mode 100644 index 0000000..e863bb8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/__pycache__/video_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/base_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/base_test.py new file mode 100644 index 0000000..7b7d64a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/base_test.py @@ -0,0 +1,635 @@ +# -*- coding: utf8 -*- + +import sys +import unittest + +import platform + +IS_PYPY = "PyPy" == platform.python_implementation() + +try: + from pygame.tests.test_utils import arrinter +except NameError: + pass +import pygame + + +quit_count = 0 + + +def quit_hook(): + global quit_count + quit_count += 1 + + +class BaseModuleTest(unittest.TestCase): + def tearDown(self): + # Clean up after each test method. + pygame.quit() + + def test_get_sdl_byteorder(self): + """Ensure the SDL byte order is valid""" + byte_order = pygame.get_sdl_byteorder() + expected_options = (pygame.LIL_ENDIAN, pygame.BIG_ENDIAN) + + self.assertIn(byte_order, expected_options) + + def test_get_sdl_version(self): + """Ensure the SDL version is valid""" + self.assertEqual(len(pygame.get_sdl_version()), 3) + + class ExporterBase(object): + def __init__(self, shape, typechar, itemsize): + import ctypes + + ndim = len(shape) + self.ndim = ndim + self.shape = tuple(shape) + array_len = 1 + for d in shape: + array_len *= d + self.size = itemsize * array_len + self.parent = ctypes.create_string_buffer(self.size) + self.itemsize = itemsize + strides = [itemsize] * ndim + for i in range(ndim - 1, 0, -1): + strides[i - 1] = strides[i] * shape[i] + self.strides = tuple(strides) + self.data = ctypes.addressof(self.parent), False + if self.itemsize == 1: + byteorder = "|" + elif sys.byteorder == "big": + byteorder = ">" + else: + byteorder = "<" + self.typestr = byteorder + typechar + str(self.itemsize) + + def assertSame(self, proxy, obj): + self.assertEqual(proxy.length, obj.size) + iface = proxy.__array_interface__ + self.assertEqual(iface["typestr"], obj.typestr) + self.assertEqual(iface["shape"], obj.shape) + self.assertEqual(iface["strides"], obj.strides) + self.assertEqual(iface["data"], obj.data) + + def test_PgObject_GetBuffer_array_interface(self): + from pygame.bufferproxy import BufferProxy + + class Exporter(self.ExporterBase): + def get__array_interface__(self): + return { + "version": 3, + "typestr": self.typestr, + "shape": self.shape, + "strides": self.strides, + "data": self.data, + } + + __array_interface__ = property(get__array_interface__) + # Should be ignored by PgObject_GetBuffer + __array_struct__ = property(lambda self: None) + + _shape = [2, 3, 5, 7, 11] # Some prime numbers + for ndim in range(1, len(_shape)): + o = Exporter(_shape[0:ndim], "i", 2) + v = BufferProxy(o) + self.assertSame(v, o) + ndim = 2 + shape = _shape[0:ndim] + for typechar in ("i", "u"): + for itemsize in (1, 2, 4, 8): + o = Exporter(shape, typechar, itemsize) + v = BufferProxy(o) + self.assertSame(v, o) + for itemsize in (4, 8): + o = Exporter(shape, "f", itemsize) + v = BufferProxy(o) + self.assertSame(v, o) + + # Is the dict received from an exporting object properly released? + # The dict should be freed before PgObject_GetBuffer returns. + # When the BufferProxy v's length property is referenced, v calls + # PgObject_GetBuffer, which in turn references Exporter2 o's + # __array_interface__ property. The Exporter2 instance o returns a + # dict subclass for which it keeps both a regular reference and a + # weak reference. The regular reference should be the only + # remaining reference when PgObject_GetBuffer returns. This is + # verified by first checking the weak reference both before and + # after the regular reference held by o is removed. + + import weakref, gc + + class NoDictError(RuntimeError): + pass + + class WRDict(dict): + """Weak referenceable dict""" + + pass + + class Exporter2(Exporter): + def get__array_interface__2(self): + self.d = WRDict(Exporter.get__array_interface__(self)) + self.dict_ref = weakref.ref(self.d) + return self.d + + __array_interface__ = property(get__array_interface__2) + + def free_dict(self): + self.d = None + + def is_dict_alive(self): + try: + return self.dict_ref() is not None + except AttributeError: + raise NoDictError("__array_interface__ is unread") + + o = Exporter2((2, 4), "u", 4) + v = BufferProxy(o) + self.assertRaises(NoDictError, o.is_dict_alive) + length = v.length + self.assertTrue(o.is_dict_alive()) + o.free_dict() + gc.collect() + self.assertFalse(o.is_dict_alive()) + + def test_GetView_array_struct(self): + from pygame.bufferproxy import BufferProxy + + class Exporter(self.ExporterBase): + def __init__(self, shape, typechar, itemsize): + super(Exporter, self).__init__(shape, typechar, itemsize) + self.view = BufferProxy(self.__dict__) + + def get__array_struct__(self): + return self.view.__array_struct__ + + __array_struct__ = property(get__array_struct__) + # Should not cause PgObject_GetBuffer to fail + __array_interface__ = property(lambda self: None) + + _shape = [2, 3, 5, 7, 11] # Some prime numbers + for ndim in range(1, len(_shape)): + o = Exporter(_shape[0:ndim], "i", 2) + v = BufferProxy(o) + self.assertSame(v, o) + ndim = 2 + shape = _shape[0:ndim] + for typechar in ("i", "u"): + for itemsize in (1, 2, 4, 8): + o = Exporter(shape, typechar, itemsize) + v = BufferProxy(o) + self.assertSame(v, o) + for itemsize in (4, 8): + o = Exporter(shape, "f", itemsize) + v = BufferProxy(o) + self.assertSame(v, o) + + # Check returned cobject/capsule reference count + try: + from sys import getrefcount + except ImportError: + # PyPy: no reference counting + pass + else: + o = Exporter(shape, typechar, itemsize) + self.assertEqual(getrefcount(o.__array_struct__), 1) + + if pygame.HAVE_NEWBUF: + from pygame.tests.test_utils import buftools + + def NEWBUF_assertSame(self, proxy, exp): + buftools = self.buftools + Importer = buftools.Importer + self.assertEqual(proxy.length, exp.len) + imp = Importer(proxy, buftools.PyBUF_RECORDS_RO) + self.assertEqual(imp.readonly, exp.readonly) + self.assertEqual(imp.format, exp.format) + self.assertEqual(imp.itemsize, exp.itemsize) + self.assertEqual(imp.ndim, exp.ndim) + self.assertEqual(imp.shape, exp.shape) + self.assertEqual(imp.strides, exp.strides) + self.assertTrue(imp.suboffsets is None) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + @unittest.skipIf(IS_PYPY, "pypy no likey") + def test_newbuf(self): + from pygame.bufferproxy import BufferProxy + + Exporter = self.buftools.Exporter + _shape = [2, 3, 5, 7, 11] # Some prime numbers + for ndim in range(1, len(_shape)): + o = Exporter(_shape[0:ndim], "=h") + v = BufferProxy(o) + self.NEWBUF_assertSame(v, o) + ndim = 2 + shape = _shape[0:ndim] + for format in [ + "b", + "B", + "=h", + "=H", + "=i", + "=I", + "=q", + "=Q", + "f", + "d", + "1h", + "=1h", + "x", + "1x", + "2x", + "3x", + "4x", + "5x", + "6x", + "7x", + "8x", + "9x", + ]: + o = Exporter(shape, format) + v = BufferProxy(o) + self.NEWBUF_assertSame(v, o) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_bad_format(self): + from pygame.bufferproxy import BufferProxy + from pygame.newbuffer import BufferMixin + from ctypes import create_string_buffer, addressof + + buftools = self.buftools + Exporter = buftools.Exporter + Importer = buftools.Importer + PyBUF_FORMAT = buftools.PyBUF_FORMAT + + for format in [ + "", + "=", + "1", + " ", + "2h", + "=2h", + "0x", + "11x", + "=!", + "h ", + " h", + "hh", + "?", + ]: + exp = Exporter((1,), format, itemsize=2) + b = BufferProxy(exp) + self.assertRaises(ValueError, Importer, b, PyBUF_FORMAT) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + @unittest.skipIf(IS_PYPY, "fails on pypy") + def test_PgDict_AsBuffer_PyBUF_flags(self): + from pygame.bufferproxy import BufferProxy + + is_lil_endian = pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN + fsys, frev = ("<", ">") if is_lil_endian else (">", "<") + buftools = self.buftools + Importer = buftools.Importer + a = BufferProxy( + {"typestr": "|u4", "shape": (10, 2), "data": (9, False)} + ) # 9? No data accesses. + b = Importer(a, buftools.PyBUF_SIMPLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 4) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, 9) + b = Importer(a, buftools.PyBUF_WRITABLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 4) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, 9) + b = Importer(a, buftools.PyBUF_ND) + self.assertEqual(b.ndim, 2) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 4) + self.assertEqual(b.shape, (10, 2)) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, 9) + a = BufferProxy( + { + "typestr": fsys + "i2", + "shape": (5, 10), + "strides": (24, 2), + "data": (42, False), + } + ) # 42? No data accesses. + b = Importer(a, buftools.PyBUF_STRIDES) + self.assertEqual(b.ndim, 2) + self.assertTrue(b.format is None) + self.assertEqual(b.len, 100) + self.assertEqual(b.itemsize, 2) + self.assertEqual(b.shape, (5, 10)) + self.assertEqual(b.strides, (24, 2)) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, 42) + b = Importer(a, buftools.PyBUF_FULL_RO) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, "=h") + self.assertEqual(b.len, 100) + self.assertEqual(b.itemsize, 2) + self.assertEqual(b.shape, (5, 10)) + self.assertEqual(b.strides, (24, 2)) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, 42) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_CONTIG) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_CONTIG) + a = BufferProxy( + { + "typestr": frev + "i2", + "shape": (3, 5, 10), + "strides": (120, 24, 2), + "data": (1000000, True), + } + ) # 1000000? No data accesses. + b = Importer(a, buftools.PyBUF_FULL_RO) + self.assertEqual(b.ndim, 3) + self.assertEqual(b.format, frev + "h") + self.assertEqual(b.len, 300) + self.assertEqual(b.itemsize, 2) + self.assertEqual(b.shape, (3, 5, 10)) + self.assertEqual(b.strides, (120, 24, 2)) + self.assertTrue(b.suboffsets is None) + self.assertTrue(b.readonly) + self.assertEqual(b.buf, 1000000) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_FULL) + + @unittest.skipIf(IS_PYPY or (not pygame.HAVE_NEWBUF), "newbuf with ctypes") + def test_PgObject_AsBuffer_PyBUF_flags(self): + from pygame.bufferproxy import BufferProxy + import ctypes + + is_lil_endian = pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN + fsys, frev = ("<", ">") if is_lil_endian else (">", "<") + buftools = self.buftools + Importer = buftools.Importer + e = arrinter.Exporter( + (10, 2), typekind="f", itemsize=ctypes.sizeof(ctypes.c_double) + ) + a = BufferProxy(e) + b = Importer(a, buftools.PyBUF_SIMPLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, e.len) + self.assertEqual(b.itemsize, e.itemsize) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, e.data) + b = Importer(a, buftools.PyBUF_WRITABLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, e.len) + self.assertEqual(b.itemsize, e.itemsize) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, e.data) + b = Importer(a, buftools.PyBUF_ND) + self.assertEqual(b.ndim, e.nd) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, e.itemsize) + self.assertEqual(b.shape, e.shape) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, e.data) + e = arrinter.Exporter((5, 10), typekind="i", itemsize=2, strides=(24, 2)) + a = BufferProxy(e) + b = Importer(a, buftools.PyBUF_STRIDES) + self.assertEqual(b.ndim, e.nd) + self.assertTrue(b.format is None) + self.assertEqual(b.len, e.len) + self.assertEqual(b.itemsize, e.itemsize) + self.assertEqual(b.shape, e.shape) + self.assertEqual(b.strides, e.strides) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, e.data) + b = Importer(a, buftools.PyBUF_FULL_RO) + self.assertEqual(b.ndim, e.nd) + self.assertEqual(b.format, "=h") + self.assertEqual(b.len, e.len) + self.assertEqual(b.itemsize, e.itemsize) + self.assertEqual(b.shape, e.shape) + self.assertEqual(b.strides, e.strides) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, e.data) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_CONTIG) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_CONTIG) + e = arrinter.Exporter( + (3, 5, 10), + typekind="i", + itemsize=2, + strides=(120, 24, 2), + flags=arrinter.PAI_ALIGNED, + ) + a = BufferProxy(e) + b = Importer(a, buftools.PyBUF_FULL_RO) + self.assertEqual(b.ndim, e.nd) + self.assertEqual(b.format, frev + "h") + self.assertEqual(b.len, e.len) + self.assertEqual(b.itemsize, e.itemsize) + self.assertEqual(b.shape, e.shape) + self.assertEqual(b.strides, e.strides) + self.assertTrue(b.suboffsets is None) + self.assertTrue(b.readonly) + self.assertEqual(b.buf, e.data) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_FULL) + + def test_PgObject_GetBuffer_exception(self): + # For consistency with surfarray + from pygame.bufferproxy import BufferProxy + + bp = BufferProxy(1) + self.assertRaises(ValueError, getattr, bp, "length") + + def not_init_assertions(self): + self.assertFalse(pygame.get_init(), "pygame shouldn't be initialized") + self.assertFalse(pygame.display.get_init(), "display shouldn't be initialized") + + if "pygame.mixer" in sys.modules: + self.assertFalse(pygame.mixer.get_init(), "mixer shouldn't be initialized") + + if "pygame.font" in sys.modules: + self.assertFalse(pygame.font.get_init(), "init shouldn't be initialized") + + ## !!! TODO : Remove when scrap works for OS X + import platform + + if platform.system().startswith("Darwin"): + return + + try: + self.assertRaises(pygame.error, pygame.scrap.get) + except NotImplementedError: + # Scrap is optional. + pass + + # pygame.cdrom + # pygame.joystick + + def init_assertions(self): + self.assertTrue(pygame.get_init()) + self.assertTrue(pygame.display.get_init()) + + if "pygame.mixer" in sys.modules: + self.assertTrue(pygame.mixer.get_init()) + + if "pygame.font" in sys.modules: + self.assertTrue(pygame.font.get_init()) + + def test_quit__and_init(self): + # __doc__ (as of 2008-06-25) for pygame.base.quit: + + # pygame.quit(): return None + # uninitialize all pygame modules + + # Make sure everything is not init + self.not_init_assertions() + + # Initiate it + pygame.init() + + # Check + self.init_assertions() + + # Quit + pygame.quit() + + # All modules have quit + self.not_init_assertions() + + def test_register_quit(self): + """Ensure that a registered function is called on quit()""" + self.assertEqual(quit_count, 0) + + pygame.init() + pygame.register_quit(quit_hook) + pygame.quit() + + self.assertEqual(quit_count, 1) + + def test_get_error(self): + + # __doc__ (as of 2008-08-02) for pygame.base.get_error: + + # pygame.get_error(): return errorstr + # get the current error message + # + # SDL maintains an internal error message. This message will usually + # be given to you when pygame.error is raised. You will rarely need to + # call this function. + # + + # The first error could be all sorts of nonsense or empty. + e = pygame.get_error() + pygame.set_error("hi") + self.assertEqual(pygame.get_error(), "hi") + pygame.set_error("") + self.assertEqual(pygame.get_error(), "") + + def test_set_error(self): + + # The first error could be all sorts of nonsense or empty. + e = pygame.get_error() + pygame.set_error("hi") + self.assertEqual(pygame.get_error(), "hi") + pygame.set_error("") + self.assertEqual(pygame.get_error(), "") + + def test_unicode_error(self): + pygame.set_error("你好") + self.assertEqual("你好", pygame.get_error()) + + def test_init(self): + """Ensures init() works properly.""" + # Make sure nothing initialized. + self.not_init_assertions() + + # display and joystick must init, at minimum + expected_min_passes = 2 + + # All modules should pass. + expected_fails = 0 + + passes, fails = pygame.init() + + self.init_assertions() + self.assertGreaterEqual(passes, expected_min_passes) + self.assertEqual(fails, expected_fails) + + def test_get_init(self): + # Test if get_init() gets the init state. + self.assertFalse(pygame.get_init()) + + def test_get_init__after_init(self): + # Test if get_init() gets the init state after pygame.init() called. + pygame.init() + + self.assertTrue(pygame.get_init()) + + def test_get_init__after_quit(self): + # Test if get_init() gets the init state after pygame.quit() called. + pygame.init() + pygame.quit() + + self.assertFalse(pygame.get_init()) + + def todo_test_segfault(self): + + # __doc__ (as of 2008-08-02) for pygame.base.segfault: + + # crash + + self.fail() + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/blit_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/blit_test.py new file mode 100644 index 0000000..906e7c4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/blit_test.py @@ -0,0 +1,155 @@ +import unittest + +import pygame +from pygame.locals import * + + +class BlitTest(unittest.TestCase): + def test_SRCALPHA(self): + """SRCALPHA tests.""" + # blend(s, 0, d) = d + s = pygame.Surface((1, 1), SRCALPHA, 32) + s.fill((255, 255, 255, 0)) + + d = pygame.Surface((1, 1), SRCALPHA, 32) + d.fill((0, 0, 255, 255)) + + s.blit(d, (0, 0)) + self.assertEqual(s.get_at((0, 0)), d.get_at((0, 0))) + + # blend(s, 255, d) = s + s = pygame.Surface((1, 1), SRCALPHA, 32) + s.fill((123, 0, 0, 255)) + s1 = pygame.Surface((1, 1), SRCALPHA, 32) + s1.fill((123, 0, 0, 255)) + d = pygame.Surface((1, 1), SRCALPHA, 32) + d.fill((10, 0, 0, 0)) + s.blit(d, (0, 0)) + self.assertEqual(s.get_at((0, 0)), s1.get_at((0, 0))) + + # TODO: these should be true too. + # blend(0, sA, 0) = 0 + # blend(255, sA, 255) = 255 + # blend(s, sA, d) <= 255 + + def test_BLEND(self): + """BLEND_ tests.""" + + # test that it doesn't overflow, and that it is saturated. + s = pygame.Surface((1, 1), SRCALPHA, 32) + s.fill((255, 255, 255, 0)) + + d = pygame.Surface((1, 1), SRCALPHA, 32) + d.fill((0, 0, 255, 255)) + + s.blit(d, (0, 0), None, BLEND_ADD) + + # print "d %s" % (d.get_at((0,0)),) + # print s.get_at((0,0)) + # self.assertEqual(s.get_at((0,0))[2], 255 ) + # self.assertEqual(s.get_at((0,0))[3], 0 ) + + s.blit(d, (0, 0), None, BLEND_RGBA_ADD) + # print s.get_at((0,0)) + self.assertEqual(s.get_at((0, 0))[3], 255) + + # test adding works. + s.fill((20, 255, 255, 0)) + d.fill((10, 0, 255, 255)) + s.blit(d, (0, 0), None, BLEND_ADD) + self.assertEqual(s.get_at((0, 0))[2], 255) + + # test subbing works. + s.fill((20, 255, 255, 0)) + d.fill((10, 0, 255, 255)) + s.blit(d, (0, 0), None, BLEND_SUB) + self.assertEqual(s.get_at((0, 0))[0], 10) + + # no overflow in sub blend. + s.fill((20, 255, 255, 0)) + d.fill((30, 0, 255, 255)) + s.blit(d, (0, 0), None, BLEND_SUB) + self.assertEqual(s.get_at((0, 0))[0], 0) + + def make_blit_list(self, num_surfs): + + blit_list = [] + for i in range(num_surfs): + dest = (i * 10, 0) + surf = pygame.Surface((10, 10), SRCALPHA, 32) + color = (i * 1, i * 1, i * 1) + surf.fill(color) + blit_list.append((surf, dest)) + return blit_list + + def test_blits(self): + + NUM_SURFS = 255 + PRINT_TIMING = 0 + dst = pygame.Surface((NUM_SURFS * 10, 10), SRCALPHA, 32) + dst.fill((230, 230, 230)) + blit_list = self.make_blit_list(NUM_SURFS) + + def blits(blit_list): + for surface, dest in blit_list: + dst.blit(surface, dest) + + from time import time + + t0 = time() + results = blits(blit_list) + t1 = time() + if PRINT_TIMING: + print("python blits: %s" % (t1 - t0)) + + dst.fill((230, 230, 230)) + t0 = time() + results = dst.blits(blit_list) + t1 = time() + if PRINT_TIMING: + print("Surface.blits :%s" % (t1 - t0)) + + # check if we blit all the different colors in the correct spots. + for i in range(NUM_SURFS): + color = (i * 1, i * 1, i * 1) + self.assertEqual(dst.get_at((i * 10, 0)), color) + self.assertEqual(dst.get_at(((i * 10) + 5, 5)), color) + + self.assertEqual(len(results), NUM_SURFS) + + t0 = time() + results = dst.blits(blit_list, doreturn=0) + t1 = time() + if PRINT_TIMING: + print("Surface.blits doreturn=0: %s" % (t1 - t0)) + self.assertEqual(results, None) + + t0 = time() + results = dst.blits(((surf, dest) for surf, dest in blit_list)) + t1 = time() + if PRINT_TIMING: + print("Surface.blits generator: %s" % (t1 - t0)) + + def test_blits_not_sequence(self): + dst = pygame.Surface((100, 10), SRCALPHA, 32) + self.assertRaises(ValueError, dst.blits, None) + + def test_blits_wrong_length(self): + dst = pygame.Surface((100, 10), SRCALPHA, 32) + self.assertRaises( + ValueError, dst.blits, [pygame.Surface((10, 10), SRCALPHA, 32)] + ) + + def test_blits_bad_surf_args(self): + dst = pygame.Surface((100, 10), SRCALPHA, 32) + self.assertRaises(TypeError, dst.blits, [(None, None)]) + + def test_blits_bad_dest(self): + dst = pygame.Surface((100, 10), SRCALPHA, 32) + self.assertRaises( + TypeError, dst.blits, [(pygame.Surface((10, 10), SRCALPHA, 32), None)] + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/bufferproxy_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/bufferproxy_test.py new file mode 100644 index 0000000..3d9c0b3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/bufferproxy_test.py @@ -0,0 +1,507 @@ +import re +import weakref +import gc +import ctypes +import unittest + +import pygame +from pygame.bufferproxy import BufferProxy + + +try: + BufferError +except NameError: + from pygame import BufferError + + +class BufferProxyTest(unittest.TestCase): + view_keywords = { + "shape": (5, 4, 3), + "typestr": "|u1", + "data": (0, True), + "strides": (4, 20, 1), + } + + def test_module_name(self): + self.assertEqual(pygame.bufferproxy.__name__, "pygame.bufferproxy") + + def test_class_name(self): + self.assertEqual(BufferProxy.__name__, "BufferProxy") + + def test___array_struct___property(self): + kwds = self.view_keywords + v = BufferProxy(kwds) + d = pygame.get_array_interface(v) + self.assertEqual(len(d), 5) + self.assertEqual(d["version"], 3) + self.assertEqual(d["shape"], kwds["shape"]) + self.assertEqual(d["typestr"], kwds["typestr"]) + self.assertEqual(d["data"], kwds["data"]) + self.assertEqual(d["strides"], kwds["strides"]) + + def test___array_interface___property(self): + kwds = self.view_keywords + v = BufferProxy(kwds) + d = v.__array_interface__ + self.assertEqual(len(d), 5) + self.assertEqual(d["version"], 3) + self.assertEqual(d["shape"], kwds["shape"]) + self.assertEqual(d["typestr"], kwds["typestr"]) + self.assertEqual(d["data"], kwds["data"]) + self.assertEqual(d["strides"], kwds["strides"]) + + def test_parent_property(self): + kwds = dict(self.view_keywords) + p = [] + kwds["parent"] = p + v = BufferProxy(kwds) + + self.assertIs(v.parent, p) + + def test_before(self): + def callback(parent): + success.append(parent is p) + + class MyException(Exception): + pass + + def raise_exception(parent): + raise MyException("Just a test.") + + kwds = dict(self.view_keywords) + p = [] + kwds["parent"] = p + + # For array interface + success = [] + kwds["before"] = callback + v = BufferProxy(kwds) + self.assertEqual(len(success), 0) + d = v.__array_interface__ + self.assertEqual(len(success), 1) + self.assertTrue(success[0]) + d = v.__array_interface__ + self.assertEqual(len(success), 1) + d = v = None + gc.collect() + self.assertEqual(len(success), 1) + + # For array struct + success = [] + kwds["before"] = callback + v = BufferProxy(kwds) + self.assertEqual(len(success), 0) + c = v.__array_struct__ + self.assertEqual(len(success), 1) + self.assertTrue(success[0]) + c = v.__array_struct__ + self.assertEqual(len(success), 1) + c = v = None + gc.collect() + self.assertEqual(len(success), 1) + + # Callback raises an exception + kwds["before"] = raise_exception + v = BufferProxy(kwds) + self.assertRaises(MyException, lambda: v.__array_struct__) + + def test_after(self): + def callback(parent): + success.append(parent is p) + + kwds = dict(self.view_keywords) + p = [] + kwds["parent"] = p + + # For array interface + success = [] + kwds["after"] = callback + v = BufferProxy(kwds) + self.assertEqual(len(success), 0) + d = v.__array_interface__ + self.assertEqual(len(success), 0) + d = v.__array_interface__ + self.assertEqual(len(success), 0) + d = v = None + gc.collect() + self.assertEqual(len(success), 1) + self.assertTrue(success[0]) + + # For array struct + success = [] + kwds["after"] = callback + v = BufferProxy(kwds) + self.assertEqual(len(success), 0) + c = v.__array_struct__ + self.assertEqual(len(success), 0) + c = v.__array_struct__ + self.assertEqual(len(success), 0) + c = v = None + gc.collect() + self.assertEqual(len(success), 1) + self.assertTrue(success[0]) + + def test_attribute(self): + v = BufferProxy(self.view_keywords) + self.assertRaises(AttributeError, getattr, v, "undefined") + v.undefined = 12 + self.assertEqual(v.undefined, 12) + del v.undefined + self.assertRaises(AttributeError, getattr, v, "undefined") + + def test_weakref(self): + v = BufferProxy(self.view_keywords) + weak_v = weakref.ref(v) + + self.assertIs(weak_v(), v) + + v = None + gc.collect() + + self.assertIsNone(weak_v()) + + def test_gc(self): + """refcount agnostic check that contained objects are freed""" + + def before_callback(parent): + return r[0] + + def after_callback(parent): + return r[1] + + class Obj(object): + pass + + p = Obj() + a = Obj() + r = [Obj(), Obj()] + weak_p = weakref.ref(p) + weak_a = weakref.ref(a) + weak_r0 = weakref.ref(r[0]) + weak_r1 = weakref.ref(r[1]) + weak_before = weakref.ref(before_callback) + weak_after = weakref.ref(after_callback) + kwds = dict(self.view_keywords) + kwds["parent"] = p + kwds["before"] = before_callback + kwds["after"] = after_callback + v = BufferProxy(kwds) + v.some_attribute = a + weak_v = weakref.ref(v) + kwds = p = a = before_callback = after_callback = None + gc.collect() + self.assertTrue(weak_p() is not None) + self.assertTrue(weak_a() is not None) + self.assertTrue(weak_before() is not None) + self.assertTrue(weak_after() is not None) + v = None + [gc.collect() for x in range(4)] + self.assertTrue(weak_v() is None) + self.assertTrue(weak_p() is None) + self.assertTrue(weak_a() is None) + self.assertTrue(weak_before() is None) + self.assertTrue(weak_after() is None) + self.assertTrue(weak_r0() is not None) + self.assertTrue(weak_r1() is not None) + r = None + gc.collect() + self.assertTrue(weak_r0() is None) + self.assertTrue(weak_r1() is None) + + # Cycle removal + kwds = dict(self.view_keywords) + kwds["parent"] = [] + v = BufferProxy(kwds) + v.some_attribute = v + tracked = True + for o in gc.get_objects(): + if o is v: + break + else: + tracked = False + self.assertTrue(tracked) + kwds["parent"].append(v) + kwds = None + gc.collect() + n1 = len(gc.garbage) + v = None + gc.collect() + n2 = len(gc.garbage) + self.assertEqual(n2, n1) + + def test_c_api(self): + api = pygame.bufferproxy._PYGAME_C_API + api_type = type(pygame.base._PYGAME_C_API) + + self.assertIsInstance(api, api_type) + + def test_repr(self): + v = BufferProxy(self.view_keywords) + cname = BufferProxy.__name__ + oname, ovalue = re.findall(r"<([^)]+)\(([^)]+)\)>", repr(v))[0] + self.assertEqual(oname, cname) + self.assertEqual(v.length, int(ovalue)) + + def test_subclassing(self): + class MyBufferProxy(BufferProxy): + def __repr__(self): + return "*%s*" % (BufferProxy.__repr__(self),) + + kwds = dict(self.view_keywords) + kwds["parent"] = 0 + v = MyBufferProxy(kwds) + self.assertEqual(v.parent, 0) + r = repr(v) + self.assertEqual(r[:2], "*<") + self.assertEqual(r[-2:], ">*") + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def NEWBUF_test_newbuf(self): + from ctypes import string_at + + from pygame.tests.test_utils import buftools + + Exporter = buftools.Exporter + Importer = buftools.Importer + exp = Exporter((10,), "B", readonly=True) + b = BufferProxy(exp) + self.assertEqual(b.length, exp.len) + self.assertEqual(b.raw, string_at(exp.buf, exp.len)) + d = b.__array_interface__ + try: + self.assertEqual(d["typestr"], "|u1") + self.assertEqual(d["shape"], exp.shape) + self.assertEqual(d["strides"], exp.strides) + self.assertEqual(d["data"], (exp.buf, True)) + finally: + d = None + exp = Exporter((3,), "=h") + b = BufferProxy(exp) + self.assertEqual(b.length, exp.len) + self.assertEqual(b.raw, string_at(exp.buf, exp.len)) + d = b.__array_interface__ + try: + lil_endian = pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN + f = "{}i{}".format("<" if lil_endian else ">", exp.itemsize) + self.assertEqual(d["typestr"], f) + self.assertEqual(d["shape"], exp.shape) + self.assertEqual(d["strides"], exp.strides) + self.assertEqual(d["data"], (exp.buf, False)) + finally: + d = None + + exp = Exporter((10, 2), "=i") + b = BufferProxy(exp) + imp = Importer(b, buftools.PyBUF_RECORDS) + self.assertTrue(imp.obj is b) + self.assertEqual(imp.buf, exp.buf) + self.assertEqual(imp.ndim, exp.ndim) + self.assertEqual(imp.format, exp.format) + self.assertEqual(imp.readonly, exp.readonly) + self.assertEqual(imp.itemsize, exp.itemsize) + self.assertEqual(imp.len, exp.len) + self.assertEqual(imp.shape, exp.shape) + self.assertEqual(imp.strides, exp.strides) + self.assertTrue(imp.suboffsets is None) + + d = { + "typestr": "|u1", + "shape": (10,), + "strides": (1,), + "data": (9, True), + } # 9? Will not reading the data anyway. + b = BufferProxy(d) + imp = Importer(b, buftools.PyBUF_SIMPLE) + self.assertTrue(imp.obj is b) + self.assertEqual(imp.buf, 9) + self.assertEqual(imp.len, 10) + self.assertEqual(imp.format, None) + self.assertEqual(imp.itemsize, 1) + self.assertEqual(imp.ndim, 0) + self.assertTrue(imp.readonly) + self.assertTrue(imp.shape is None) + self.assertTrue(imp.strides is None) + self.assertTrue(imp.suboffsets is None) + + try: + pygame.bufferproxy.get_segcount + except AttributeError: + pass + else: + + def test_oldbuf_arg(self): + self.OLDBUF_test_oldbuf_arg() + + def OLDBUF_test_oldbuf_arg(self): + from pygame.bufferproxy import get_segcount, get_read_buffer, get_write_buffer + + content = b"\x01\x00\x00\x02" * 12 + memory = ctypes.create_string_buffer(content) + memaddr = ctypes.addressof(memory) + + def raise_exception(o): + raise ValueError("An exception") + + bf = BufferProxy( + { + "shape": (len(content),), + "typestr": "|u1", + "data": (memaddr, False), + "strides": (1,), + } + ) + seglen, segaddr = get_read_buffer(bf, 0) + self.assertEqual(segaddr, 0) + self.assertEqual(seglen, 0) + seglen, segaddr = get_write_buffer(bf, 0) + self.assertEqual(segaddr, 0) + self.assertEqual(seglen, 0) + segcount, buflen = get_segcount(bf) + self.assertEqual(segcount, 1) + self.assertEqual(buflen, len(content)) + seglen, segaddr = get_read_buffer(bf, 0) + self.assertEqual(segaddr, memaddr) + self.assertEqual(seglen, len(content)) + seglen, segaddr = get_write_buffer(bf, 0) + self.assertEqual(segaddr, memaddr) + self.assertEqual(seglen, len(content)) + + bf = BufferProxy( + { + "shape": (len(content),), + "typestr": "|u1", + "data": (memaddr, True), + "strides": (1,), + } + ) + segcount, buflen = get_segcount(bf) + self.assertEqual(segcount, 1) + self.assertEqual(buflen, len(content)) + seglen, segaddr = get_read_buffer(bf, 0) + self.assertEqual(segaddr, memaddr) + self.assertEqual(seglen, len(content)) + self.assertRaises(ValueError, get_write_buffer, bf, 0) + + bf = BufferProxy( + { + "shape": (len(content),), + "typestr": "|u1", + "data": (memaddr, True), + "strides": (1,), + "before": raise_exception, + } + ) + segcount, buflen = get_segcount(bf) + self.assertEqual(segcount, 0) + self.assertEqual(buflen, 0) + + bf = BufferProxy( + { + "shape": (3, 4), + "typestr": "|u4", + "data": (memaddr, True), + "strides": (12, 4), + } + ) + segcount, buflen = get_segcount(bf) + self.assertEqual(segcount, 3 * 4) + self.assertEqual(buflen, 3 * 4 * 4) + for i in range(0, 4): + seglen, segaddr = get_read_buffer(bf, i) + self.assertEqual(segaddr, memaddr + i * 4) + self.assertEqual(seglen, 4) + + +class BufferProxyLegacyTest(unittest.TestCase): + content = b"\x01\x00\x00\x02" * 12 + buffer = ctypes.create_string_buffer(content) + data = (ctypes.addressof(buffer), True) + + def test_length(self): + + # __doc__ (as of 2008-08-02) for pygame.bufferproxy.BufferProxy.length: + + # The size of the buffer data in bytes. + bf = BufferProxy( + {"shape": (3, 4), "typestr": "|u4", "data": self.data, "strides": (12, 4)} + ) + self.assertEqual(bf.length, len(self.content)) + bf = BufferProxy( + {"shape": (3, 3), "typestr": "|u4", "data": self.data, "strides": (12, 4)} + ) + self.assertEqual(bf.length, 3 * 3 * 4) + + def test_raw(self): + + # __doc__ (as of 2008-08-02) for pygame.bufferproxy.BufferProxy.raw: + + # The raw buffer data as string. The string may contain NUL bytes. + + bf = BufferProxy( + {"shape": (len(self.content),), "typestr": "|u1", "data": self.data} + ) + self.assertEqual(bf.raw, self.content) + bf = BufferProxy( + {"shape": (3, 4), "typestr": "|u4", "data": self.data, "strides": (4, 12)} + ) + self.assertEqual(bf.raw, self.content) + bf = BufferProxy( + {"shape": (3, 4), "typestr": "|u1", "data": self.data, "strides": (16, 4)} + ) + self.assertRaises(ValueError, getattr, bf, "raw") + + def test_write(self): + + # __doc__ (as of 2008-08-02) for pygame.bufferproxy.BufferProxy.write: + + # B.write (bufferproxy, buffer, offset) -> None + # + # Writes raw data to the bufferproxy. + # + # Writes the raw data from buffer to the BufferProxy object, starting + # at the specified offset within the BufferProxy. + # If the length of the passed buffer exceeds the length of the + # BufferProxy (reduced by the offset), an IndexError will be raised. + from ctypes import c_byte, sizeof, addressof, string_at, memset + + nullbyte = "\x00".encode("latin_1") + Buf = c_byte * 10 + data_buf = Buf(*range(1, 3 * sizeof(Buf) + 1, 3)) + data = string_at(data_buf, sizeof(data_buf)) + buf = Buf() + bp = BufferProxy( + {"typestr": "|u1", "shape": (sizeof(buf),), "data": (addressof(buf), False)} + ) + try: + self.assertEqual(bp.raw, nullbyte * sizeof(Buf)) + bp.write(data) + self.assertEqual(bp.raw, data) + memset(buf, 0, sizeof(buf)) + bp.write(data[:3], 2) + raw = bp.raw + self.assertEqual(raw[:2], nullbyte * 2) + self.assertEqual(raw[2:5], data[:3]) + self.assertEqual(raw[5:], nullbyte * (sizeof(Buf) - 5)) + bp.write(data[:3], bp.length - 3) + raw = bp.raw + self.assertEqual(raw[-3:], data[:3]) + self.assertRaises(IndexError, bp.write, data, 1) + self.assertRaises(IndexError, bp.write, data[:5], -1) + self.assertRaises(IndexError, bp.write, data[:5], bp.length) + self.assertRaises(TypeError, bp.write, 12) + bp = BufferProxy( + { + "typestr": "|u1", + "shape": (sizeof(buf),), + "data": (addressof(buf), True), + } + ) + self.assertRaises(pygame.BufferError, bp.write, "123".encode("latin_1")) + finally: + # Make sure bp is garbage collected before buf + bp = None + gc.collect() + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/camera_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/camera_test.py new file mode 100644 index 0000000..79cf0f9 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/camera_test.py @@ -0,0 +1,5 @@ +import unittest + + +class CameraModuleTest(unittest.TestCase): + pass diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/color_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/color_test.py new file mode 100644 index 0000000..16eefd1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/color_test.py @@ -0,0 +1,1302 @@ +import unittest +import math +import operator +import platform + +import pygame +from pygame.colordict import THECOLORS + + +IS_PYPY = "PyPy" == platform.python_implementation() +################################### CONSTANTS ################################## + +rgba_vals = [0, 1, 62, 63, 126, 127, 255] + +rgba_combinations = [ + (r, g, b, a) + for r in rgba_vals + for g in rgba_vals + for b in rgba_vals + for a in rgba_vals +] + +################################################################################ + + +def rgba_combos_Color_generator(): + for rgba in rgba_combinations: + yield pygame.Color(*rgba) + + +# Python gamma correct +def gamma_correct(rgba_0_255, gamma): + corrected = round(255.0 * math.pow(rgba_0_255 / 255.0, gamma)) + return max(min(int(corrected), 255), 0) + + +################################################################################ + +# TODO: add tests for +# correct_gamma() -- test against statically defined verified correct values +# coerce () -- ?? + + +def _assignr(x, y): + x.r = y + + +def _assigng(x, y): + x.g = y + + +def _assignb(x, y): + x.b = y + + +def _assigna(x, y): + x.a = y + + +def _assign_item(x, p, y): + x[p] = y + + +class ColorTypeTest(unittest.TestCase): + def test_new(self): + c = pygame.Color.__new__(pygame.Color) + self.assertEqual(c, pygame.Color(0, 0, 0, 255)) + self.assertEqual(len(c), 4) + + def test_init(self): + c = pygame.Color(10, 20, 30, 200) + self.assertEqual(c, (10, 20, 30, 200)) + c.set_length(3) + self.assertEqual(len(c), 3) + c.__init__(100, 110, 120, 128) + self.assertEqual(len(c), 4) + self.assertEqual(c, (100, 110, 120, 128)) + + def test_invalid_html_hex_codes(self): + # This was a problem with the way 2 digit hex numbers were + # calculated. The test_hex_digits test is related to the fix. + Color = pygame.color.Color + self.assertRaises(ValueError, lambda: Color("# f000000")) + self.assertRaises(ValueError, lambda: Color("#f 000000")) + self.assertRaises(ValueError, lambda: Color("#-f000000")) + + def test_hex_digits(self): + # This is an implementation specific test. + # Two digit hex numbers are calculated using table lookups + # for the upper and lower digits. + Color = pygame.color.Color + self.assertEqual(Color("#00000000").r, 0x00) + self.assertEqual(Color("#10000000").r, 0x10) + self.assertEqual(Color("#20000000").r, 0x20) + self.assertEqual(Color("#30000000").r, 0x30) + self.assertEqual(Color("#40000000").r, 0x40) + self.assertEqual(Color("#50000000").r, 0x50) + self.assertEqual(Color("#60000000").r, 0x60) + self.assertEqual(Color("#70000000").r, 0x70) + self.assertEqual(Color("#80000000").r, 0x80) + self.assertEqual(Color("#90000000").r, 0x90) + self.assertEqual(Color("#A0000000").r, 0xA0) + self.assertEqual(Color("#B0000000").r, 0xB0) + self.assertEqual(Color("#C0000000").r, 0xC0) + self.assertEqual(Color("#D0000000").r, 0xD0) + self.assertEqual(Color("#E0000000").r, 0xE0) + self.assertEqual(Color("#F0000000").r, 0xF0) + self.assertEqual(Color("#01000000").r, 0x01) + self.assertEqual(Color("#02000000").r, 0x02) + self.assertEqual(Color("#03000000").r, 0x03) + self.assertEqual(Color("#04000000").r, 0x04) + self.assertEqual(Color("#05000000").r, 0x05) + self.assertEqual(Color("#06000000").r, 0x06) + self.assertEqual(Color("#07000000").r, 0x07) + self.assertEqual(Color("#08000000").r, 0x08) + self.assertEqual(Color("#09000000").r, 0x09) + self.assertEqual(Color("#0A000000").r, 0x0A) + self.assertEqual(Color("#0B000000").r, 0x0B) + self.assertEqual(Color("#0C000000").r, 0x0C) + self.assertEqual(Color("#0D000000").r, 0x0D) + self.assertEqual(Color("#0E000000").r, 0x0E) + self.assertEqual(Color("#0F000000").r, 0x0F) + + def test_comparison(self): + Color = pygame.color.Color + + # Check valid comparisons + self.assertTrue(Color(255, 0, 0, 0) == Color(255, 0, 0, 0)) + self.assertTrue(Color(0, 255, 0, 0) == Color(0, 255, 0, 0)) + self.assertTrue(Color(0, 0, 255, 0) == Color(0, 0, 255, 0)) + self.assertTrue(Color(0, 0, 0, 255) == Color(0, 0, 0, 255)) + self.assertFalse(Color(0, 0, 0, 0) == Color(255, 0, 0, 0)) + self.assertFalse(Color(0, 0, 0, 0) == Color(0, 255, 0, 0)) + self.assertFalse(Color(0, 0, 0, 0) == Color(0, 0, 255, 0)) + self.assertFalse(Color(0, 0, 0, 0) == Color(0, 0, 0, 255)) + self.assertTrue(Color(0, 0, 0, 0) != Color(255, 0, 0, 0)) + self.assertTrue(Color(0, 0, 0, 0) != Color(0, 255, 0, 0)) + self.assertTrue(Color(0, 0, 0, 0) != Color(0, 0, 255, 0)) + self.assertTrue(Color(0, 0, 0, 0) != Color(0, 0, 0, 255)) + self.assertFalse(Color(255, 0, 0, 0) != Color(255, 0, 0, 0)) + self.assertFalse(Color(0, 255, 0, 0) != Color(0, 255, 0, 0)) + self.assertFalse(Color(0, 0, 255, 0) != Color(0, 0, 255, 0)) + self.assertFalse(Color(0, 0, 0, 255) != Color(0, 0, 0, 255)) + + self.assertTrue(Color(255, 0, 0, 0) == (255, 0, 0, 0)) + self.assertTrue(Color(0, 255, 0, 0) == (0, 255, 0, 0)) + self.assertTrue(Color(0, 0, 255, 0) == (0, 0, 255, 0)) + self.assertTrue(Color(0, 0, 0, 255) == (0, 0, 0, 255)) + self.assertFalse(Color(0, 0, 0, 0) == (255, 0, 0, 0)) + self.assertFalse(Color(0, 0, 0, 0) == (0, 255, 0, 0)) + self.assertFalse(Color(0, 0, 0, 0) == (0, 0, 255, 0)) + self.assertFalse(Color(0, 0, 0, 0) == (0, 0, 0, 255)) + self.assertTrue(Color(0, 0, 0, 0) != (255, 0, 0, 0)) + self.assertTrue(Color(0, 0, 0, 0) != (0, 255, 0, 0)) + self.assertTrue(Color(0, 0, 0, 0) != (0, 0, 255, 0)) + self.assertTrue(Color(0, 0, 0, 0) != (0, 0, 0, 255)) + self.assertFalse(Color(255, 0, 0, 0) != (255, 0, 0, 0)) + self.assertFalse(Color(0, 255, 0, 0) != (0, 255, 0, 0)) + self.assertFalse(Color(0, 0, 255, 0) != (0, 0, 255, 0)) + self.assertFalse(Color(0, 0, 0, 255) != (0, 0, 0, 255)) + + self.assertTrue((255, 0, 0, 0) == Color(255, 0, 0, 0)) + self.assertTrue((0, 255, 0, 0) == Color(0, 255, 0, 0)) + self.assertTrue((0, 0, 255, 0) == Color(0, 0, 255, 0)) + self.assertTrue((0, 0, 0, 255) == Color(0, 0, 0, 255)) + self.assertFalse((0, 0, 0, 0) == Color(255, 0, 0, 0)) + self.assertFalse((0, 0, 0, 0) == Color(0, 255, 0, 0)) + self.assertFalse((0, 0, 0, 0) == Color(0, 0, 255, 0)) + self.assertFalse((0, 0, 0, 0) == Color(0, 0, 0, 255)) + self.assertTrue((0, 0, 0, 0) != Color(255, 0, 0, 0)) + self.assertTrue((0, 0, 0, 0) != Color(0, 255, 0, 0)) + self.assertTrue((0, 0, 0, 0) != Color(0, 0, 255, 0)) + self.assertTrue((0, 0, 0, 0) != Color(0, 0, 0, 255)) + self.assertFalse((255, 0, 0, 0) != Color(255, 0, 0, 0)) + self.assertFalse((0, 255, 0, 0) != Color(0, 255, 0, 0)) + self.assertFalse((0, 0, 255, 0) != Color(0, 0, 255, 0)) + self.assertFalse((0, 0, 0, 255) != Color(0, 0, 0, 255)) + + class TupleSubclass(tuple): + pass + + self.assertTrue(Color(255, 0, 0, 0) == TupleSubclass((255, 0, 0, 0))) + self.assertTrue(TupleSubclass((255, 0, 0, 0)) == Color(255, 0, 0, 0)) + self.assertFalse(Color(255, 0, 0, 0) != TupleSubclass((255, 0, 0, 0))) + self.assertFalse(TupleSubclass((255, 0, 0, 0)) != Color(255, 0, 0, 0)) + + # These are not supported so will be unequal. + self.assertFalse(Color(255, 0, 0, 0) == "#ff000000") + self.assertTrue(Color(255, 0, 0, 0) != "#ff000000") + + self.assertFalse("#ff000000" == Color(255, 0, 0, 0)) + self.assertTrue("#ff000000" != Color(255, 0, 0, 0)) + + self.assertFalse(Color(255, 0, 0, 0) == 0xFF000000) + self.assertTrue(Color(255, 0, 0, 0) != 0xFF000000) + + self.assertFalse(0xFF000000 == Color(255, 0, 0, 0)) + self.assertTrue(0xFF000000 != Color(255, 0, 0, 0)) + + self.assertFalse(Color(255, 0, 0, 0) == [255, 0, 0, 0]) + self.assertTrue(Color(255, 0, 0, 0) != [255, 0, 0, 0]) + + self.assertFalse([255, 0, 0, 0] == Color(255, 0, 0, 0)) + self.assertTrue([255, 0, 0, 0] != Color(255, 0, 0, 0)) + + # Comparison is not implemented for invalid color values. + class Test(object): + def __eq__(self, other): + return -1 + + def __ne__(self, other): + return -2 + + class TestTuple(tuple): + def __eq__(self, other): + return -1 + + def __ne__(self, other): + return -2 + + t = Test() + t_tuple = TestTuple(("a", 0, 0, 0)) + black = Color("black") + self.assertEqual(black == t, -1) + self.assertEqual(t == black, -1) + self.assertEqual(black != t, -2) + self.assertEqual(t != black, -2) + self.assertEqual(black == t_tuple, -1) + self.assertEqual(black != t_tuple, -2) + self.assertEqual(t_tuple == black, -1) + self.assertEqual(t_tuple != black, -2) + + def test_ignore_whitespace(self): + self.assertEqual(pygame.color.Color("red"), pygame.color.Color(" r e d ")) + + def test_slice(self): + # """|tags: python3_ignore|""" + + # slicing a color gives you back a tuple. + # do all sorts of slice combinations. + c = pygame.Color(1, 2, 3, 4) + + self.assertEqual((1, 2, 3, 4), c[:]) + self.assertEqual((1, 2, 3), c[:-1]) + + self.assertEqual((), c[:-5]) + + self.assertEqual((1, 2, 3, 4), c[:4]) + self.assertEqual((1, 2, 3, 4), c[:5]) + self.assertEqual((1, 2), c[:2]) + self.assertEqual((1,), c[:1]) + self.assertEqual((), c[:0]) + + self.assertEqual((2,), c[1:-2]) + self.assertEqual((3, 4), c[-2:]) + self.assertEqual((4,), c[-1:]) + + # NOTE: assigning to a slice is currently unsupported. + + def test_unpack(self): + # should be able to unpack to r,g,b,a and r,g,b + c = pygame.Color(1, 2, 3, 4) + r, g, b, a = c + self.assertEqual((1, 2, 3, 4), (r, g, b, a)) + self.assertEqual(c, (r, g, b, a)) + + c.set_length(3) + r, g, b = c + self.assertEqual((1, 2, 3), (r, g, b)) + + def test_length(self): + # should be able to unpack to r,g,b,a and r,g,b + c = pygame.Color(1, 2, 3, 4) + self.assertEqual(len(c), 4) + + c.set_length(3) + self.assertEqual(len(c), 3) + + # it keeps the old alpha anyway... + self.assertEqual(c.a, 4) + + # however you can't get the alpha in this way: + self.assertRaises(IndexError, lambda x: c[x], 4) + + c.set_length(4) + self.assertEqual(len(c), 4) + self.assertEqual(len(c), 4) + + self.assertRaises(ValueError, c.set_length, 5) + self.assertRaises(ValueError, c.set_length, -1) + self.assertRaises(ValueError, c.set_length, 0) + self.assertRaises(ValueError, c.set_length, pow(2, 33)) + + def test_case_insensitivity_of_string_args(self): + self.assertEqual(pygame.color.Color("red"), pygame.color.Color("Red")) + + def test_color(self): + """Ensures Color objects can be created.""" + color = pygame.Color(0, 0, 0, 0) + + self.assertIsInstance(color, pygame.Color) + + def test_color__rgba_int_args(self): + """Ensures Color objects can be created using ints.""" + color = pygame.Color(10, 20, 30, 40) + + self.assertEqual(color.r, 10) + self.assertEqual(color.g, 20) + self.assertEqual(color.b, 30) + self.assertEqual(color.a, 40) + + def test_color__rgba_int_args_without_alpha(self): + """Ensures Color objects can be created without providing alpha.""" + color = pygame.Color(10, 20, 30) + + self.assertEqual(color.r, 10) + self.assertEqual(color.g, 20) + self.assertEqual(color.b, 30) + self.assertEqual(color.a, 255) + + def test_color__rgba_int_args_invalid_value(self): + """Ensures invalid values are detected when creating Color objects.""" + self.assertRaises(ValueError, pygame.Color, 257, 10, 105, 44) + self.assertRaises(ValueError, pygame.Color, 10, 257, 105, 44) + self.assertRaises(ValueError, pygame.Color, 10, 105, 257, 44) + self.assertRaises(ValueError, pygame.Color, 10, 105, 44, 257) + + def test_color__rgba_int_args_invalid_value_without_alpha(self): + """Ensures invalid values are detected when creating Color objects + without providing an alpha. + """ + self.assertRaises(ValueError, pygame.Color, 256, 10, 105) + self.assertRaises(ValueError, pygame.Color, 10, 256, 105) + self.assertRaises(ValueError, pygame.Color, 10, 105, 256) + + def test_color__color_object_arg(self): + """Ensures Color objects can be created using Color objects.""" + color_args = (10, 20, 30, 40) + color_obj = pygame.Color(*color_args) + + new_color_obj = pygame.Color(color_obj) + + self.assertIsInstance(new_color_obj, pygame.Color) + self.assertEqual(new_color_obj, color_obj) + self.assertEqual(new_color_obj.r, color_args[0]) + self.assertEqual(new_color_obj.g, color_args[1]) + self.assertEqual(new_color_obj.b, color_args[2]) + self.assertEqual(new_color_obj.a, color_args[3]) + + def test_color__name_str_arg(self): + """Ensures Color objects can be created using str names.""" + for name in ("aquamarine3", "AQUAMARINE3", "AqUAmArIne3"): + color = pygame.Color(name) + + self.assertEqual(color.r, 102) + self.assertEqual(color.g, 205) + self.assertEqual(color.b, 170) + self.assertEqual(color.a, 255) + + def test_color__name_str_arg_from_colordict(self): + """Ensures Color objects can be created using str names + from the THECOLORS dict.""" + for name, values in THECOLORS.items(): + color = pygame.Color(name) + + self.assertEqual(color.r, values[0]) + self.assertEqual(color.g, values[1]) + self.assertEqual(color.b, values[2]) + self.assertEqual(color.a, values[3]) + + def test_color__html_str_arg(self): + """Ensures Color objects can be created using html strings.""" + # See test_webstyle() for related tests. + color = pygame.Color("#a1B2c3D4") + + self.assertEqual(color.r, 0xA1) + self.assertEqual(color.g, 0xB2) + self.assertEqual(color.b, 0xC3) + self.assertEqual(color.a, 0xD4) + + def test_color__hex_str_arg(self): + """Ensures Color objects can be created using hex strings.""" + # See test_webstyle() for related tests. + color = pygame.Color("0x1a2B3c4D") + + self.assertEqual(color.r, 0x1A) + self.assertEqual(color.g, 0x2B) + self.assertEqual(color.b, 0x3C) + self.assertEqual(color.a, 0x4D) + + def test_color__int_arg(self): + """Ensures Color objects can be created using one int value.""" + for value in (0x0, 0xFFFFFFFF, 0xAABBCCDD): + color = pygame.Color(value) + + self.assertEqual(color.r, (value >> 24) & 0xFF) + self.assertEqual(color.g, (value >> 16) & 0xFF) + self.assertEqual(color.b, (value >> 8) & 0xFF) + self.assertEqual(color.a, value & 0xFF) + + def test_color__int_arg_invalid(self): + """Ensures invalid int values are detected when creating Color objects.""" + with self.assertRaises(ValueError): + color = pygame.Color(0x1FFFFFFFF) + + def test_color__sequence_arg(self): + """Ensures Color objects can be created using tuples/lists.""" + color_values = (33, 44, 55, 66) + for seq_type in (tuple, list): + color = pygame.Color(seq_type(color_values)) + + self.assertEqual(color.r, color_values[0]) + self.assertEqual(color.g, color_values[1]) + self.assertEqual(color.b, color_values[2]) + self.assertEqual(color.a, color_values[3]) + + def test_color__sequence_arg_without_alpha(self): + """Ensures Color objects can be created using tuples/lists + without providing an alpha value. + """ + color_values = (33, 44, 55) + for seq_type in (tuple, list): + color = pygame.Color(seq_type(color_values)) + + self.assertEqual(color.r, color_values[0]) + self.assertEqual(color.g, color_values[1]) + self.assertEqual(color.b, color_values[2]) + self.assertEqual(color.a, 255) + + def test_color__sequence_arg_invalid_value(self): + """Ensures invalid sequences are detected when creating Color objects.""" + cls = pygame.Color + for seq_type in (tuple, list): + self.assertRaises(ValueError, cls, seq_type((256, 90, 80, 70))) + self.assertRaises(ValueError, cls, seq_type((100, 256, 80, 70))) + self.assertRaises(ValueError, cls, seq_type((100, 90, 256, 70))) + self.assertRaises(ValueError, cls, seq_type((100, 90, 80, 256))) + + def test_color__sequence_arg_invalid_value_without_alpha(self): + """Ensures invalid sequences are detected when creating Color objects + without providing an alpha. + """ + cls = pygame.Color + for seq_type in (tuple, list): + self.assertRaises(ValueError, cls, seq_type((256, 90, 80))) + self.assertRaises(ValueError, cls, seq_type((100, 256, 80))) + self.assertRaises(ValueError, cls, seq_type((100, 90, 256))) + + def test_color__sequence_arg_invalid_format(self): + """Ensures invalid sequences are detected when creating Color objects + with the wrong number of values. + """ + cls = pygame.Color + for seq_type in (tuple, list): + self.assertRaises(ValueError, cls, seq_type((100,))) + self.assertRaises(ValueError, cls, seq_type((100, 90))) + self.assertRaises(ValueError, cls, seq_type((100, 90, 80, 70, 60))) + + def test_rgba(self): + c = pygame.Color(0) + self.assertEqual(c.r, 0) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 0) + self.assertEqual(c.a, 0) + + # Test simple assignments + c.r = 123 + self.assertEqual(c.r, 123) + self.assertRaises(ValueError, _assignr, c, 537) + self.assertEqual(c.r, 123) + self.assertRaises(ValueError, _assignr, c, -3) + self.assertEqual(c.r, 123) + + c.g = 55 + self.assertEqual(c.g, 55) + self.assertRaises(ValueError, _assigng, c, 348) + self.assertEqual(c.g, 55) + self.assertRaises(ValueError, _assigng, c, -44) + self.assertEqual(c.g, 55) + + c.b = 77 + self.assertEqual(c.b, 77) + self.assertRaises(ValueError, _assignb, c, 256) + self.assertEqual(c.b, 77) + self.assertRaises(ValueError, _assignb, c, -12) + self.assertEqual(c.b, 77) + + c.a = 255 + self.assertEqual(c.a, 255) + self.assertRaises(ValueError, _assigna, c, 312) + self.assertEqual(c.a, 255) + self.assertRaises(ValueError, _assigna, c, -10) + self.assertEqual(c.a, 255) + + def test_repr(self): + c = pygame.Color(68, 38, 26, 69) + t = "(68, 38, 26, 69)" + self.assertEqual(repr(c), t) + + def test_add(self): + c1 = pygame.Color(0) + self.assertEqual(c1.r, 0) + self.assertEqual(c1.g, 0) + self.assertEqual(c1.b, 0) + self.assertEqual(c1.a, 0) + + c2 = pygame.Color(20, 33, 82, 193) + self.assertEqual(c2.r, 20) + self.assertEqual(c2.g, 33) + self.assertEqual(c2.b, 82) + self.assertEqual(c2.a, 193) + + c3 = c1 + c2 + self.assertEqual(c3.r, 20) + self.assertEqual(c3.g, 33) + self.assertEqual(c3.b, 82) + self.assertEqual(c3.a, 193) + + c3 = c3 + c2 + self.assertEqual(c3.r, 40) + self.assertEqual(c3.g, 66) + self.assertEqual(c3.b, 164) + self.assertEqual(c3.a, 255) + + # Issue #286: Is type checking done for Python 3.x? + self.assertRaises(TypeError, operator.add, c1, None) + self.assertRaises(TypeError, operator.add, None, c1) + + def test_sub(self): + c1 = pygame.Color(0xFFFFFFFF) + self.assertEqual(c1.r, 255) + self.assertEqual(c1.g, 255) + self.assertEqual(c1.b, 255) + self.assertEqual(c1.a, 255) + + c2 = pygame.Color(20, 33, 82, 193) + self.assertEqual(c2.r, 20) + self.assertEqual(c2.g, 33) + self.assertEqual(c2.b, 82) + self.assertEqual(c2.a, 193) + + c3 = c1 - c2 + self.assertEqual(c3.r, 235) + self.assertEqual(c3.g, 222) + self.assertEqual(c3.b, 173) + self.assertEqual(c3.a, 62) + + c3 = c3 - c2 + self.assertEqual(c3.r, 215) + self.assertEqual(c3.g, 189) + self.assertEqual(c3.b, 91) + self.assertEqual(c3.a, 0) + + # Issue #286: Is type checking done for Python 3.x? + self.assertRaises(TypeError, operator.sub, c1, None) + self.assertRaises(TypeError, operator.sub, None, c1) + + def test_mul(self): + c1 = pygame.Color(0x01010101) + self.assertEqual(c1.r, 1) + self.assertEqual(c1.g, 1) + self.assertEqual(c1.b, 1) + self.assertEqual(c1.a, 1) + + c2 = pygame.Color(2, 5, 3, 22) + self.assertEqual(c2.r, 2) + self.assertEqual(c2.g, 5) + self.assertEqual(c2.b, 3) + self.assertEqual(c2.a, 22) + + c3 = c1 * c2 + self.assertEqual(c3.r, 2) + self.assertEqual(c3.g, 5) + self.assertEqual(c3.b, 3) + self.assertEqual(c3.a, 22) + + c3 = c3 * c2 + self.assertEqual(c3.r, 4) + self.assertEqual(c3.g, 25) + self.assertEqual(c3.b, 9) + self.assertEqual(c3.a, 255) + + # Issue #286: Is type checking done for Python 3.x? + self.assertRaises(TypeError, operator.mul, c1, None) + self.assertRaises(TypeError, operator.mul, None, c1) + + def test_div(self): + c1 = pygame.Color(0x80808080) + self.assertEqual(c1.r, 128) + self.assertEqual(c1.g, 128) + self.assertEqual(c1.b, 128) + self.assertEqual(c1.a, 128) + + c2 = pygame.Color(2, 4, 8, 16) + self.assertEqual(c2.r, 2) + self.assertEqual(c2.g, 4) + self.assertEqual(c2.b, 8) + self.assertEqual(c2.a, 16) + + c3 = c1 // c2 + self.assertEqual(c3.r, 64) + self.assertEqual(c3.g, 32) + self.assertEqual(c3.b, 16) + self.assertEqual(c3.a, 8) + + c3 = c3 // c2 + self.assertEqual(c3.r, 32) + self.assertEqual(c3.g, 8) + self.assertEqual(c3.b, 2) + self.assertEqual(c3.a, 0) + + # Issue #286: Is type checking done for Python 3.x? + self.assertRaises(TypeError, operator.floordiv, c1, None) + self.assertRaises(TypeError, operator.floordiv, None, c1) + + # Division by zero check + dividend = pygame.Color(255, 255, 255, 255) + for i in range(4): + divisor = pygame.Color(64, 64, 64, 64) + divisor[i] = 0 + quotient = pygame.Color(3, 3, 3, 3) + quotient[i] = 0 + self.assertEqual(dividend // divisor, quotient) + + def test_mod(self): + c1 = pygame.Color(0xFFFFFFFF) + self.assertEqual(c1.r, 255) + self.assertEqual(c1.g, 255) + self.assertEqual(c1.b, 255) + self.assertEqual(c1.a, 255) + + c2 = pygame.Color(2, 4, 8, 16) + self.assertEqual(c2.r, 2) + self.assertEqual(c2.g, 4) + self.assertEqual(c2.b, 8) + self.assertEqual(c2.a, 16) + + c3 = c1 % c2 + self.assertEqual(c3.r, 1) + self.assertEqual(c3.g, 3) + self.assertEqual(c3.b, 7) + self.assertEqual(c3.a, 15) + + # Issue #286: Is type checking done for Python 3.x? + self.assertRaises(TypeError, operator.mod, c1, None) + self.assertRaises(TypeError, operator.mod, None, c1) + + # Division by zero check + dividend = pygame.Color(255, 255, 255, 255) + for i in range(4): + divisor = pygame.Color(64, 64, 64, 64) + divisor[i] = 0 + quotient = pygame.Color(63, 63, 63, 63) + quotient[i] = 0 + self.assertEqual(dividend % divisor, quotient) + + def test_float(self): + c = pygame.Color(0xCC00CC00) + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 0) + self.assertEqual(float(c), float(0xCC00CC00)) + + c = pygame.Color(0x33727592) + self.assertEqual(c.r, 51) + self.assertEqual(c.g, 114) + self.assertEqual(c.b, 117) + self.assertEqual(c.a, 146) + self.assertEqual(float(c), float(0x33727592)) + + def test_oct(self): + c = pygame.Color(0xCC00CC00) + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 0) + self.assertEqual(oct(c), oct(0xCC00CC00)) + + c = pygame.Color(0x33727592) + self.assertEqual(c.r, 51) + self.assertEqual(c.g, 114) + self.assertEqual(c.b, 117) + self.assertEqual(c.a, 146) + self.assertEqual(oct(c), oct(0x33727592)) + + def test_hex(self): + c = pygame.Color(0xCC00CC00) + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 0) + self.assertEqual(hex(c), hex(0xCC00CC00)) + + c = pygame.Color(0x33727592) + self.assertEqual(c.r, 51) + self.assertEqual(c.g, 114) + self.assertEqual(c.b, 117) + self.assertEqual(c.a, 146) + self.assertEqual(hex(c), hex(0x33727592)) + + def test_webstyle(self): + c = pygame.Color("#CC00CC11") + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 17) + self.assertEqual(hex(c), hex(0xCC00CC11)) + + c = pygame.Color("#CC00CC") + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 255) + self.assertEqual(hex(c), hex(0xCC00CCFF)) + + c = pygame.Color("0xCC00CC11") + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 17) + self.assertEqual(hex(c), hex(0xCC00CC11)) + + c = pygame.Color("0xCC00CC") + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 255) + self.assertEqual(hex(c), hex(0xCC00CCFF)) + + self.assertRaises(ValueError, pygame.Color, "#cc00qq") + self.assertRaises(ValueError, pygame.Color, "0xcc00qq") + self.assertRaises(ValueError, pygame.Color, "09abcdef") + self.assertRaises(ValueError, pygame.Color, "09abcde") + self.assertRaises(ValueError, pygame.Color, "quarky") + + def test_int(self): + # This will be a long + c = pygame.Color(0xCC00CC00) + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 0) + self.assertEqual(int(c), int(0xCC00CC00)) + + # This will be an int + c = pygame.Color(0x33727592) + self.assertEqual(c.r, 51) + self.assertEqual(c.g, 114) + self.assertEqual(c.b, 117) + self.assertEqual(c.a, 146) + self.assertEqual(int(c), int(0x33727592)) + + def test_long(self): + # This will be a long + c = pygame.Color(0xCC00CC00) + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 204) + self.assertEqual(c.a, 0) + self.assertEqual(int(c), int(0xCC00CC00)) + + # This will be an int + c = pygame.Color(0x33727592) + self.assertEqual(c.r, 51) + self.assertEqual(c.g, 114) + self.assertEqual(c.b, 117) + self.assertEqual(c.a, 146) + self.assertEqual(int(c), int(0x33727592)) + + def test_normalize(self): + c = pygame.Color(204, 38, 194, 55) + self.assertEqual(c.r, 204) + self.assertEqual(c.g, 38) + self.assertEqual(c.b, 194) + self.assertEqual(c.a, 55) + + t = c.normalize() + + self.assertAlmostEqual(t[0], 0.800000, 5) + self.assertAlmostEqual(t[1], 0.149016, 5) + self.assertAlmostEqual(t[2], 0.760784, 5) + self.assertAlmostEqual(t[3], 0.215686, 5) + + def test_len(self): + c = pygame.Color(204, 38, 194, 55) + self.assertEqual(len(c), 4) + + def test_get_item(self): + c = pygame.Color(204, 38, 194, 55) + self.assertEqual(c[0], 204) + self.assertEqual(c[1], 38) + self.assertEqual(c[2], 194) + self.assertEqual(c[3], 55) + + def test_set_item(self): + c = pygame.Color(204, 38, 194, 55) + self.assertEqual(c[0], 204) + self.assertEqual(c[1], 38) + self.assertEqual(c[2], 194) + self.assertEqual(c[3], 55) + + c[0] = 33 + self.assertEqual(c[0], 33) + c[1] = 48 + self.assertEqual(c[1], 48) + c[2] = 173 + self.assertEqual(c[2], 173) + c[3] = 213 + self.assertEqual(c[3], 213) + + # Now try some 'invalid' ones + self.assertRaises(TypeError, _assign_item, c, 0, 95.485) + self.assertEqual(c[0], 33) + self.assertRaises(ValueError, _assign_item, c, 1, -83) + self.assertEqual(c[1], 48) + self.assertRaises(TypeError, _assign_item, c, 2, "Hello") + self.assertEqual(c[2], 173) + + def test_Color_type_works_for_Surface_get_and_set_colorkey(self): + s = pygame.Surface((32, 32)) + + c = pygame.Color(33, 22, 11, 255) + s.set_colorkey(c) + + get_r, get_g, get_b, get_a = s.get_colorkey() + + self.assertTrue(get_r == c.r) + self.assertTrue(get_g == c.g) + self.assertTrue(get_b == c.b) + self.assertTrue(get_a == c.a) + + ########## HSLA, HSVA, CMY, I1I2I3 ALL ELEMENTS WITHIN SPECIFIED RANGE ######### + + def test_hsla__all_elements_within_limits(self): + for c in rgba_combos_Color_generator(): + h, s, l, a = c.hsla + self.assertTrue(0 <= h <= 360) + self.assertTrue(0 <= s <= 100) + self.assertTrue(0 <= l <= 100) + self.assertTrue(0 <= a <= 100) + + def test_hsva__all_elements_within_limits(self): + for c in rgba_combos_Color_generator(): + h, s, v, a = c.hsva + self.assertTrue(0 <= h <= 360) + self.assertTrue(0 <= s <= 100) + self.assertTrue(0 <= v <= 100) + self.assertTrue(0 <= a <= 100) + + def test_cmy__all_elements_within_limits(self): + for c in rgba_combos_Color_generator(): + c, m, y = c.cmy + self.assertTrue(0 <= c <= 1) + self.assertTrue(0 <= m <= 1) + self.assertTrue(0 <= y <= 1) + + def test_i1i2i3__all_elements_within_limits(self): + for c in rgba_combos_Color_generator(): + i1, i2, i3 = c.i1i2i3 + self.assertTrue(0 <= i1 <= 1) + self.assertTrue(-0.5 <= i2 <= 0.5) + self.assertTrue(-0.5 <= i3 <= 0.5) + + def test_issue_269(self): + """PyColor OverflowError on HSVA with hue value of 360 + + >>> c = pygame.Color(0) + >>> c.hsva = (360,0,0,0) + Traceback (most recent call last): + File "", line 1, in + OverflowError: this is not allowed to happen ever + >>> pygame.ver + '1.9.1release' + >>> + + """ + + c = pygame.Color(0) + c.hsva = 360, 0, 0, 0 + self.assertEqual(c.hsva, (0, 0, 0, 0)) + c.hsva = 360, 100, 100, 100 + self.assertEqual(c.hsva, (0, 100, 100, 100)) + self.assertEqual(c, (255, 0, 0, 255)) + + ####################### COLORSPACE PROPERTY SANITY TESTS ####################### + + def colorspaces_converted_should_not_raise(self, prop): + fails = 0 + + x = 0 + for c in rgba_combos_Color_generator(): + x += 1 + + other = pygame.Color(0) + + try: + setattr(other, prop, getattr(c, prop)) + # eg other.hsla = c.hsla + + except ValueError: + fails += 1 + + self.assertTrue(x > 0, "x is combination counter, 0 means no tests!") + self.assertTrue((fails, x) == (0, x)) + + def test_hsla__sanity_testing_converted_should_not_raise(self): + self.colorspaces_converted_should_not_raise("hsla") + + def test_hsva__sanity_testing_converted_should_not_raise(self): + self.colorspaces_converted_should_not_raise("hsva") + + def test_cmy__sanity_testing_converted_should_not_raise(self): + self.colorspaces_converted_should_not_raise("cmy") + + def test_i1i2i3__sanity_testing_converted_should_not_raise(self): + self.colorspaces_converted_should_not_raise("i1i2i3") + + ################################################################################ + + def colorspaces_converted_should_equate_bar_rounding(self, prop): + for c in rgba_combos_Color_generator(): + other = pygame.Color(0) + + try: + setattr(other, prop, getattr(c, prop)) + # eg other.hsla = c.hsla + + self.assertTrue(abs(other.r - c.r) <= 1) + self.assertTrue(abs(other.b - c.b) <= 1) + self.assertTrue(abs(other.g - c.g) <= 1) + # CMY and I1I2I3 do not care about the alpha + if not prop in ("cmy", "i1i2i3"): + self.assertTrue(abs(other.a - c.a) <= 1) + + except ValueError: + pass # other tests will notify, this tests equation + + def test_hsla__sanity_testing_converted_should_equate_bar_rounding(self): + self.colorspaces_converted_should_equate_bar_rounding("hsla") + + def test_hsva__sanity_testing_converted_should_equate_bar_rounding(self): + self.colorspaces_converted_should_equate_bar_rounding("hsva") + + def test_cmy__sanity_testing_converted_should_equate_bar_rounding(self): + self.colorspaces_converted_should_equate_bar_rounding("cmy") + + def test_i1i2i3__sanity_testing_converted_should_equate_bar_rounding(self): + self.colorspaces_converted_should_equate_bar_rounding("i1i2i3") + + ################################################################################ + + def test_correct_gamma__verified_against_python_implementation(self): + "|tags:slow|" + # gamma_correct defined at top of page + + gammas = [i / 10.0 for i in range(1, 31)] # [0.1 ... 3.0] + gammas_len = len(gammas) + + for i, c in enumerate(rgba_combos_Color_generator()): + gamma = gammas[i % gammas_len] + + corrected = pygame.Color(*[gamma_correct(x, gamma) for x in tuple(c)]) + lib_corrected = c.correct_gamma(gamma) + + self.assertTrue(corrected.r == lib_corrected.r) + self.assertTrue(corrected.g == lib_corrected.g) + self.assertTrue(corrected.b == lib_corrected.b) + self.assertTrue(corrected.a == lib_corrected.a) + + # TODO: test against statically defined verified _correct_ values + # assert corrected.r == 125 etc. + + def test_pickle(self): + import pickle + + c1 = pygame.Color(1, 2, 3, 4) + # c2 = pygame.Color(255,254,253,252) + pickle_string = pickle.dumps(c1) + c1_frompickle = pickle.loads(pickle_string) + self.assertEqual(c1, c1_frompickle) + + ################################################################################ + # only available if ctypes module is also available + + @unittest.skipIf(IS_PYPY, "PyPy has no ctypes") + def test_arraystruct(self): + + import pygame.tests.test_utils.arrinter as ai + import ctypes as ct + + c_byte_p = ct.POINTER(ct.c_byte) + c = pygame.Color(5, 7, 13, 23) + flags = ai.PAI_CONTIGUOUS | ai.PAI_FORTRAN | ai.PAI_ALIGNED | ai.PAI_NOTSWAPPED + for i in range(1, 5): + c.set_length(i) + inter = ai.ArrayInterface(c) + self.assertEqual(inter.two, 2) + self.assertEqual(inter.nd, 1) + self.assertEqual(inter.typekind, "u") + self.assertEqual(inter.itemsize, 1) + self.assertEqual(inter.flags, flags) + self.assertEqual(inter.shape[0], i) + self.assertEqual(inter.strides[0], 1) + data = ct.cast(inter.data, c_byte_p) + for j in range(i): + self.assertEqual(data[j], c[j]) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_newbuf(self): + from pygame.tests.test_utils import buftools + from ctypes import cast, POINTER, c_uint8 + + class ColorImporter(buftools.Importer): + def __init__(self, color, flags): + super(ColorImporter, self).__init__(color, flags) + self.items = cast(self.buf, POINTER(c_uint8)) + + def __getitem__(self, index): + if 0 <= index < 4: + return self.items[index] + raise IndexError( + "valid index values are between 0 and 3: " "got {}".format(index) + ) + + def __setitem__(self, index, value): + if 0 <= index < 4: + self.items[index] = value + else: + raise IndexError( + "valid index values are between 0 and 3: " + "got {}".format(index) + ) + + c = pygame.Color(50, 100, 150, 200) + imp = ColorImporter(c, buftools.PyBUF_SIMPLE) + self.assertTrue(imp.obj is c) + self.assertEqual(imp.ndim, 0) + self.assertEqual(imp.itemsize, 1) + self.assertEqual(imp.len, 4) + self.assertTrue(imp.readonly) + self.assertTrue(imp.format is None) + self.assertTrue(imp.shape is None) + self.assertTrue(imp.strides is None) + self.assertTrue(imp.suboffsets is None) + for i in range(4): + self.assertEqual(c[i], imp[i]) + imp[0] = 60 + self.assertEqual(c.r, 60) + imp[1] = 110 + self.assertEqual(c.g, 110) + imp[2] = 160 + self.assertEqual(c.b, 160) + imp[3] = 210 + self.assertEqual(c.a, 210) + imp = ColorImporter(c, buftools.PyBUF_FORMAT) + self.assertEqual(imp.ndim, 0) + self.assertEqual(imp.itemsize, 1) + self.assertEqual(imp.len, 4) + self.assertEqual(imp.format, "B") + self.assertEqual(imp.ndim, 0) + self.assertEqual(imp.itemsize, 1) + self.assertEqual(imp.len, 4) + imp = ColorImporter(c, buftools.PyBUF_ND) + self.assertEqual(imp.ndim, 1) + self.assertEqual(imp.itemsize, 1) + self.assertEqual(imp.len, 4) + self.assertTrue(imp.format is None) + self.assertEqual(imp.shape, (4,)) + self.assertEqual(imp.strides, None) + imp = ColorImporter(c, buftools.PyBUF_STRIDES) + self.assertEqual(imp.ndim, 1) + self.assertTrue(imp.format is None) + self.assertEqual(imp.shape, (4,)) + self.assertEqual(imp.strides, (1,)) + imp = ColorImporter(c, buftools.PyBUF_C_CONTIGUOUS) + self.assertEqual(imp.ndim, 1) + imp = ColorImporter(c, buftools.PyBUF_F_CONTIGUOUS) + self.assertEqual(imp.ndim, 1) + imp = ColorImporter(c, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertEqual(imp.ndim, 1) + for i in range(1, 5): + c.set_length(i) + imp = ColorImporter(c, buftools.PyBUF_ND) + self.assertEqual(imp.ndim, 1) + self.assertEqual(imp.len, i) + self.assertEqual(imp.shape, (i,)) + self.assertRaises(BufferError, ColorImporter, c, buftools.PyBUF_WRITABLE) + + def test_lerp(self): + # setup + Color = pygame.color.Color + + color0 = Color(0, 0, 0, 0) + color128 = Color(128, 128, 128, 128) + color255 = Color(255, 255, 255, 255) + color100 = Color(100, 100, 100, 100) + + # type checking + self.assertTrue(isinstance(color0.lerp(color128, 0.5), Color)) + + # common value testing + self.assertEqual(color0.lerp(color128, 0.5), Color(64, 64, 64, 64)) + self.assertEqual(color0.lerp(color128, 0.5), Color(64, 64, 64, 64)) + self.assertEqual(color128.lerp(color255, 0.5), Color(192, 192, 192, 192)) + self.assertEqual(color0.lerp(color255, 0.5), Color(128, 128, 128, 128)) + + # testing extremes + self.assertEqual(color0.lerp(color100, 0), color0) + self.assertEqual(color0.lerp(color100, 0.01), Color(1, 1, 1, 1)) + self.assertEqual(color0.lerp(color100, 0.99), Color(99, 99, 99, 99)) + self.assertEqual(color0.lerp(color100, 1), color100) + + # kwarg testing + self.assertEqual(color0.lerp(color=color100, amount=0.5), Color(50, 50, 50, 50)) + self.assertEqual(color0.lerp(amount=0.5, color=color100), Color(50, 50, 50, 50)) + + # invalid input testing + self.assertRaises(ValueError, lambda: color0.lerp(color128, 2.5)) + self.assertRaises(ValueError, lambda: color0.lerp(color128, -0.5)) + self.assertRaises(ValueError, lambda: color0.lerp((256, 0, 0, 0), 0.5)) + self.assertRaises(ValueError, lambda: color0.lerp((0, 256, 0, 0), 0.5)) + self.assertRaises(ValueError, lambda: color0.lerp((0, 0, 256, 0), 0.5)) + self.assertRaises(ValueError, lambda: color0.lerp((0, 0, 0, 256), 0.5)) + self.assertRaises(TypeError, lambda: color0.lerp(0.2, 0.5)) + + def test_premul_alpha(self): + # setup + Color = pygame.color.Color + + color0 = Color(0, 0, 0, 0) + alpha0 = Color(255, 255, 255, 0) + alpha49 = Color(255, 0, 0, 49) + alpha67 = Color(0, 255, 0, 67) + alpha73 = Color(0, 0, 255, 73) + alpha128 = Color(255, 255, 255, 128) + alpha199 = Color(255, 255, 255, 199) + alpha255 = Color(128, 128, 128, 255) + + # type checking + self.assertTrue(isinstance(color0.premul_alpha(), Color)) + + # hand crafted value testing + self.assertEqual(alpha0.premul_alpha(), Color(0, 0, 0, 0)) + self.assertEqual(alpha49.premul_alpha(), Color(49, 0, 0, 49)) + self.assertEqual(alpha67.premul_alpha(), Color(0, 67, 0, 67)) + self.assertEqual(alpha73.premul_alpha(), Color(0, 0, 73, 73)) + self.assertEqual(alpha128.premul_alpha(), Color(128, 128, 128, 128)) + self.assertEqual(alpha199.premul_alpha(), Color(199, 199, 199, 199)) + self.assertEqual(alpha255.premul_alpha(), Color(128, 128, 128, 255)) + + # full range of alpha auto sub-testing + test_colors = [ + (200, 30, 74), + (76, 83, 24), + (184, 21, 6), + (74, 4, 74), + (76, 83, 24), + (184, 21, 234), + (160, 30, 74), + (96, 147, 204), + (198, 201, 60), + (132, 89, 74), + (245, 9, 224), + (184, 112, 6), + ] + + for r, g, b in test_colors: + for a in range(255): + with self.subTest(r=r, g=g, b=b, a=a): + alpha = a / 255.0 + self.assertEqual( + Color(r, g, b, a).premul_alpha(), + Color( + ((r + 1) * a) >> 8, + ((g + 1) * a) >> 8, + ((b + 1) * a) >> 8, + a, + ), + ) + + def test_update(self): + c = pygame.color.Color(0, 0, 0) + c.update(1, 2, 3, 4) + + self.assertEqual(c.r, 1) + self.assertEqual(c.g, 2) + self.assertEqual(c.b, 3) + self.assertEqual(c.a, 4) + + c = pygame.color.Color(0, 0, 0) + c.update([1, 2, 3, 4]) + + self.assertEqual(c.r, 1) + self.assertEqual(c.g, 2) + self.assertEqual(c.b, 3) + self.assertEqual(c.a, 4) + + c = pygame.color.Color(0, 0, 0) + c2 = pygame.color.Color(1, 2, 3, 4) + c.update(c2) + + self.assertEqual(c.r, 1) + self.assertEqual(c.g, 2) + self.assertEqual(c.b, 3) + self.assertEqual(c.a, 4) + + c = pygame.color.Color(1, 1, 1) + c.update("black") + + self.assertEqual(c.r, 0) + self.assertEqual(c.g, 0) + self.assertEqual(c.b, 0) + self.assertEqual(c.a, 255) + + c = pygame.color.Color(0, 0, 0, 120) + c.set_length(3) + c.update(1, 2, 3) + self.assertEqual(len(c), 3) + c.set_length(4) + self.assertEqual(c[3], 120) + + c.set_length(3) + c.update(1, 2, 3, 4) + self.assertEqual(len(c), 4) + + +class SubclassTest(unittest.TestCase): + class MyColor(pygame.Color): + def __init__(self, *args, **kwds): + super(SubclassTest.MyColor, self).__init__(*args, **kwds) + self.an_attribute = True + + def test_add(self): + mc1 = self.MyColor(128, 128, 128, 255) + self.assertTrue(mc1.an_attribute) + c2 = pygame.Color(64, 64, 64, 255) + mc2 = mc1 + c2 + self.assertTrue(isinstance(mc2, self.MyColor)) + self.assertRaises(AttributeError, getattr, mc2, "an_attribute") + c3 = c2 + mc1 + self.assertTrue(type(c3) is pygame.Color) + + def test_sub(self): + mc1 = self.MyColor(128, 128, 128, 255) + self.assertTrue(mc1.an_attribute) + c2 = pygame.Color(64, 64, 64, 255) + mc2 = mc1 - c2 + self.assertTrue(isinstance(mc2, self.MyColor)) + self.assertRaises(AttributeError, getattr, mc2, "an_attribute") + c3 = c2 - mc1 + self.assertTrue(type(c3) is pygame.Color) + + def test_mul(self): + mc1 = self.MyColor(128, 128, 128, 255) + self.assertTrue(mc1.an_attribute) + c2 = pygame.Color(64, 64, 64, 255) + mc2 = mc1 * c2 + self.assertTrue(isinstance(mc2, self.MyColor)) + self.assertRaises(AttributeError, getattr, mc2, "an_attribute") + c3 = c2 * mc1 + self.assertTrue(type(c3) is pygame.Color) + + def test_div(self): + mc1 = self.MyColor(128, 128, 128, 255) + self.assertTrue(mc1.an_attribute) + c2 = pygame.Color(64, 64, 64, 255) + mc2 = mc1 // c2 + self.assertTrue(isinstance(mc2, self.MyColor)) + self.assertRaises(AttributeError, getattr, mc2, "an_attribute") + c3 = c2 // mc1 + self.assertTrue(type(c3) is pygame.Color) + + def test_mod(self): + mc1 = self.MyColor(128, 128, 128, 255) + self.assertTrue(mc1.an_attribute) + c2 = pygame.Color(64, 64, 64, 255) + mc2 = mc1 % c2 + self.assertTrue(isinstance(mc2, self.MyColor)) + self.assertRaises(AttributeError, getattr, mc2, "an_attribute") + c3 = c2 % mc1 + self.assertTrue(type(c3) is pygame.Color) + + def test_inv(self): + mc1 = self.MyColor(64, 64, 64, 64) + self.assertTrue(mc1.an_attribute) + mc2 = ~mc1 + self.assertTrue(isinstance(mc2, self.MyColor)) + self.assertRaises(AttributeError, getattr, mc2, "an_attribute") + + def test_correct_gamma(self): + mc1 = self.MyColor(64, 70, 75, 255) + self.assertTrue(mc1.an_attribute) + mc2 = mc1.correct_gamma(0.03) + self.assertTrue(isinstance(mc2, self.MyColor)) + self.assertRaises(AttributeError, getattr, mc2, "an_attribute") + + +################################################################################ + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/constants_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/constants_test.py new file mode 100644 index 0000000..452a8fe --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/constants_test.py @@ -0,0 +1,437 @@ +import unittest +import pygame.constants + + +# K_* and KSCAN_* common names. +K_AND_KSCAN_COMMON_NAMES = ( + "UNKNOWN", + "BACKSPACE", + "TAB", + "CLEAR", + "RETURN", + "PAUSE", + "ESCAPE", + "SPACE", + "COMMA", + "MINUS", + "PERIOD", + "SLASH", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "SEMICOLON", + "EQUALS", + "LEFTBRACKET", + "BACKSLASH", + "RIGHTBRACKET", + "DELETE", + "KP0", + "KP1", + "KP2", + "KP3", + "KP4", + "KP5", + "KP6", + "KP7", + "KP8", + "KP9", + "KP_PERIOD", + "KP_DIVIDE", + "KP_MULTIPLY", + "KP_MINUS", + "KP_PLUS", + "KP_ENTER", + "KP_EQUALS", + "UP", + "DOWN", + "RIGHT", + "LEFT", + "INSERT", + "HOME", + "END", + "PAGEUP", + "PAGEDOWN", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "F13", + "F14", + "F15", + "NUMLOCK", + "CAPSLOCK", + "SCROLLOCK", + "RSHIFT", + "LSHIFT", + "RCTRL", + "LCTRL", + "RALT", + "LALT", + "RMETA", + "LMETA", + "LSUPER", + "RSUPER", + "MODE", + "HELP", + "PRINT", + "SYSREQ", + "BREAK", + "MENU", + "POWER", + "EURO", + "KP_0", + "KP_1", + "KP_2", + "KP_3", + "KP_4", + "KP_5", + "KP_6", + "KP_7", + "KP_8", + "KP_9", + "NUMLOCKCLEAR", + "SCROLLLOCK", + "RGUI", + "LGUI", + "PRINTSCREEN", + "CURRENCYUNIT", + "CURRENCYSUBUNIT", +) + +# Constants that have the same value. +K_AND_KSCAN_COMMON_OVERLAPS = ( + ("KP0", "KP_0"), + ("KP1", "KP_1"), + ("KP2", "KP_2"), + ("KP3", "KP_3"), + ("KP4", "KP_4"), + ("KP5", "KP_5"), + ("KP6", "KP_6"), + ("KP7", "KP_7"), + ("KP8", "KP_8"), + ("KP9", "KP_9"), + ("NUMLOCK", "NUMLOCKCLEAR"), + ("SCROLLOCK", "SCROLLLOCK"), + ("LSUPER", "LMETA", "LGUI"), + ("RSUPER", "RMETA", "RGUI"), + ("PRINT", "PRINTSCREEN"), + ("BREAK", "PAUSE"), + ("EURO", "CURRENCYUNIT"), +) + + +def create_overlap_set(constant_names): + """Helper function to find overlapping constant values/names. + + Returns a set of fronzensets: + set(frozenset(names of overlapping constants), ...) + """ + # Create an overlap dict. + overlap_dict = {} + + for name in constant_names: + value = getattr(pygame.constants, name) + overlap_dict.setdefault(value, set()).add(name) + + # Get all entries with more than 1 value. + overlaps = set() + + for overlap_names in overlap_dict.values(): + if len(overlap_names) > 1: + overlaps.add(frozenset(overlap_names)) + + return overlaps + + +class KConstantsTests(unittest.TestCase): + """Test K_* (key) constants.""" + + # K_* specific names. + K_SPECIFIC_NAMES = ( + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "QUOTE", + "BACKQUOTE", + "EXCLAIM", + "QUOTEDBL", + "HASH", + "DOLLAR", + "AMPERSAND", + "LEFTPAREN", + "RIGHTPAREN", + "ASTERISK", + "PLUS", + "COLON", + "LESS", + "GREATER", + "QUESTION", + "AT", + "CARET", + "UNDERSCORE", + "PERCENT", + ) + + # Create a sequence of all the K_* constant names. + K_NAMES = tuple("K_" + n for n in K_AND_KSCAN_COMMON_NAMES + K_SPECIFIC_NAMES) + + def test_k__existence(self): + """Ensures K constants exist.""" + for name in self.K_NAMES: + self.assertTrue( + hasattr(pygame.constants, name), "missing constant {}".format(name) + ) + + def test_k__type(self): + """Ensures K constants are the correct type.""" + for name in self.K_NAMES: + value = getattr(pygame.constants, name) + + self.assertIs(type(value), int) + + def test_k__value_overlap(self): + """Ensures no unexpected K constant values overlap.""" + EXPECTED_OVERLAPS = set( + [ + frozenset(["K_" + n for n in item]) + for item in K_AND_KSCAN_COMMON_OVERLAPS + ] + ) + + overlaps = create_overlap_set(self.K_NAMES) + + self.assertSetEqual(overlaps, EXPECTED_OVERLAPS) + + +class KscanConstantsTests(unittest.TestCase): + """Test KSCAN_* (scancode) constants.""" + + # KSCAN_* specific names. + KSCAN_SPECIFIC_NAMES = ( + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "APOSTROPHE", + "GRAVE", + "INTERNATIONAL1", + "INTERNATIONAL2", + "INTERNATIONAL3", + "INTERNATIONAL4", + "INTERNATIONAL5", + "INTERNATIONAL6", + "INTERNATIONAL7", + "INTERNATIONAL8", + "INTERNATIONAL9", + "LANG1", + "LANG2", + "LANG3", + "LANG4", + "LANG5", + "LANG6", + "LANG7", + "LANG8", + "LANG9", + "NONUSBACKSLASH", + "NONUSHASH", + ) + + # Create a sequence of all the KSCAN_* constant names. + KSCAN_NAMES = tuple( + "KSCAN_" + n for n in K_AND_KSCAN_COMMON_NAMES + KSCAN_SPECIFIC_NAMES + ) + + def test_kscan__existence(self): + """Ensures KSCAN constants exist.""" + for name in self.KSCAN_NAMES: + self.assertTrue( + hasattr(pygame.constants, name), "missing constant {}".format(name) + ) + + def test_kscan__type(self): + """Ensures KSCAN constants are the correct type.""" + for name in self.KSCAN_NAMES: + value = getattr(pygame.constants, name) + + self.assertIs(type(value), int) + + def test_kscan__value_overlap(self): + """Ensures no unexpected KSCAN constant values overlap.""" + EXPECTED_OVERLAPS = set( + [ + frozenset(["KSCAN_" + n for n in item]) + for item in K_AND_KSCAN_COMMON_OVERLAPS + ] + ) + + overlaps = create_overlap_set(self.KSCAN_NAMES) + + self.assertSetEqual(overlaps, EXPECTED_OVERLAPS) + + +class KmodConstantsTests(unittest.TestCase): + """Test KMOD_* (key modifier) constants.""" + + # KMOD_* constant names. + KMOD_CONSTANTS = ( + "KMOD_NONE", + "KMOD_LSHIFT", + "KMOD_RSHIFT", + "KMOD_SHIFT", + "KMOD_LCTRL", + "KMOD_RCTRL", + "KMOD_CTRL", + "KMOD_LALT", + "KMOD_RALT", + "KMOD_ALT", + "KMOD_LMETA", + "KMOD_RMETA", + "KMOD_META", + "KMOD_NUM", + "KMOD_CAPS", + "KMOD_MODE", + "KMOD_LGUI", + "KMOD_RGUI", + "KMOD_GUI", + ) + + def test_kmod__existence(self): + """Ensures KMOD constants exist.""" + for name in self.KMOD_CONSTANTS: + self.assertTrue( + hasattr(pygame.constants, name), "missing constant {}".format(name) + ) + + def test_kmod__type(self): + """Ensures KMOD constants are the correct type.""" + for name in self.KMOD_CONSTANTS: + value = getattr(pygame.constants, name) + + self.assertIs(type(value), int) + + def test_kmod__value_overlap(self): + """Ensures no unexpected KMOD constant values overlap.""" + # KMODs that have the same values. + EXPECTED_OVERLAPS = { + frozenset(["KMOD_LGUI", "KMOD_LMETA"]), + frozenset(["KMOD_RGUI", "KMOD_RMETA"]), + frozenset(["KMOD_GUI", "KMOD_META"]), + } + + overlaps = create_overlap_set(self.KMOD_CONSTANTS) + + self.assertSetEqual(overlaps, EXPECTED_OVERLAPS) + + def test_kmod__no_bitwise_overlap(self): + """Ensures certain KMOD constants have no overlapping bits.""" + NO_BITWISE_OVERLAP = ( + "KMOD_NONE", + "KMOD_LSHIFT", + "KMOD_RSHIFT", + "KMOD_LCTRL", + "KMOD_RCTRL", + "KMOD_LALT", + "KMOD_RALT", + "KMOD_LMETA", + "KMOD_RMETA", + "KMOD_NUM", + "KMOD_CAPS", + "KMOD_MODE", + ) + + kmods = 0 + + for name in NO_BITWISE_OVERLAP: + value = getattr(pygame.constants, name) + + self.assertFalse(kmods & value) + + kmods |= value + + def test_kmod__bitwise_overlap(self): + """Ensures certain KMOD constants have overlapping bits.""" + # KMODS that are comprised of other KMODs. + KMOD_COMPRISED_DICT = { + "KMOD_SHIFT": ("KMOD_LSHIFT", "KMOD_RSHIFT"), + "KMOD_CTRL": ("KMOD_LCTRL", "KMOD_RCTRL"), + "KMOD_ALT": ("KMOD_LALT", "KMOD_RALT"), + "KMOD_META": ("KMOD_LMETA", "KMOD_RMETA"), + "KMOD_GUI": ("KMOD_LGUI", "KMOD_RGUI"), + } + + for base_name, seq_names in KMOD_COMPRISED_DICT.items(): + expected_value = 0 # Reset. + + for name in seq_names: + expected_value |= getattr(pygame.constants, name) + + value = getattr(pygame.constants, base_name) + + self.assertEqual(value, expected_value) + + +################################################################################ + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/controller_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/controller_test.py new file mode 100644 index 0000000..f05c00c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/controller_test.py @@ -0,0 +1,357 @@ +import unittest +import pygame +import pygame._sdl2.controller as controller +from pygame.tests.test_utils import prompt, question + + +class ControllerModuleTest(unittest.TestCase): + def setUp(self): + controller.init() + + def tearDown(self): + controller.quit() + + def test_init(self): + controller.quit() + controller.init() + self.assertTrue(controller.get_init()) + + def test_init__multiple(self): + controller.init() + controller.init() + self.assertTrue(controller.get_init()) + + def test_quit(self): + controller.quit() + self.assertFalse(controller.get_init()) + + def test_quit__multiple(self): + controller.quit() + controller.quit() + self.assertFalse(controller.get_init()) + + def test_get_init(self): + self.assertTrue(controller.get_init()) + + def test_get_eventstate(self): + controller.set_eventstate(True) + self.assertTrue(controller.get_eventstate()) + + controller.set_eventstate(False) + self.assertFalse(controller.get_eventstate()) + + controller.set_eventstate(True) + + def test_get_count(self): + self.assertGreaterEqual(controller.get_count(), 0) + + def test_is_controller(self): + for i in range(controller.get_count()): + if controller.is_controller(i): + c = controller.Controller(i) + self.assertIsInstance(c, controller.Controller) + c.quit() + else: + with self.assertRaises(pygame._sdl2.sdl2.error): + c = controller.Controller(i) + + with self.assertRaises(TypeError): + controller.is_controller("Test") + + def test_name_forindex(self): + self.assertIsNone(controller.name_forindex(-1)) + + +class ControllerTypeTest(unittest.TestCase): + def setUp(self): + controller.init() + + def tearDown(self): + controller.quit() + + def _get_first_controller(self): + for i in range(controller.get_count()): + if controller.is_controller(i): + return controller.Controller(i) + + def test_construction(self): + c = self._get_first_controller() + if c: + self.assertIsInstance(c, controller.Controller) + else: + self.skipTest("No controller connected") + + def test__auto_init(self): + c = self._get_first_controller() + if c: + self.assertTrue(c.get_init()) + else: + self.skipTest("No controller connected") + + def test_get_init(self): + c = self._get_first_controller() + if c: + self.assertTrue(c.get_init()) + c.quit() + self.assertFalse(c.get_init()) + else: + self.skipTest("No controller connected") + + def test_from_joystick(self): + for i in range(controller.get_count()): + if controller.is_controller(i): + joy = pygame.joystick.Joystick(i) + break + else: + self.skipTest("No controller connected") + + c = controller.Controller.from_joystick(joy) + self.assertIsInstance(c, controller.Controller) + + def test_as_joystick(self): + c = self._get_first_controller() + if c: + joy = c.as_joystick() + self.assertIsInstance(joy, type(pygame.joystick.Joystick(0))) + else: + self.skipTest("No controller connected") + + def test_get_mapping(self): + c = self._get_first_controller() + if c: + mapping = c.get_mapping() + self.assertIsInstance(mapping, dict) + self.assertIsNotNone(mapping["a"]) + else: + self.skipTest("No controller connected") + + def test_set_mapping(self): + c = self._get_first_controller() + if c: + mapping = c.get_mapping() + mapping["a"] = "b3" + mapping["y"] = "b0" + c.set_mapping(mapping) + new_mapping = c.get_mapping() + + self.assertEqual(len(mapping), len(new_mapping)) + for i in mapping: + if mapping[i] not in ("a", "y"): + self.assertEqual(mapping[i], new_mapping[i]) + else: + if i == "a": + self.assertEqual(new_mapping[i], mapping["y"]) + else: + self.assertEqual(new_mapping[i], mapping["a"]) + else: + self.skipTest("No controller connected") + + +class ControllerInteractiveTest(unittest.TestCase): + __tags__ = ["interactive"] + + def _get_first_controller(self): + for i in range(controller.get_count()): + if controller.is_controller(i): + return controller.Controller(i) + + def setUp(self): + controller.init() + + def tearDown(self): + controller.quit() + + def test__get_count_interactive(self): + prompt( + "Please connect at least one controller " + "before the test for controller.get_count() starts" + ) + + # Reset the number of joysticks counted + controller.quit() + controller.init() + + joystick_num = controller.get_count() + ans = question( + "get_count() thinks there are {} joysticks " + "connected. Is that correct?".format(joystick_num) + ) + + self.assertTrue(ans) + + def test_set_eventstate_on_interactive(self): + c = self._get_first_controller() + if not c: + self.skipTest("No controller connected") + + pygame.display.init() + pygame.font.init() + + screen = pygame.display.set_mode((400, 400)) + font = pygame.font.Font(None, 20) + running = True + + screen.fill((255, 255, 255)) + screen.blit( + font.render("Press button 'x' (on ps4) or 'a' (on xbox).", True, (0, 0, 0)), + (0, 0), + ) + pygame.display.update() + + controller.set_eventstate(True) + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + if event.type == pygame.CONTROLLERBUTTONDOWN: + running = False + + pygame.display.quit() + pygame.font.quit() + + def test_set_eventstate_off_interactive(self): + c = self._get_first_controller() + if not c: + self.skipTest("No controller connected") + + pygame.display.init() + pygame.font.init() + + screen = pygame.display.set_mode((400, 400)) + font = pygame.font.Font(None, 20) + running = True + + screen.fill((255, 255, 255)) + screen.blit( + font.render("Press button 'x' (on ps4) or 'a' (on xbox).", True, (0, 0, 0)), + (0, 0), + ) + pygame.display.update() + + controller.set_eventstate(False) + + while running: + for event in pygame.event.get(pygame.QUIT): + if event: + running = False + + if c.get_button(pygame.CONTROLLER_BUTTON_A): + if pygame.event.peek(pygame.CONTROLLERBUTTONDOWN): + pygame.display.quit() + pygame.font.quit() + self.fail() + else: + running = False + + pygame.display.quit() + pygame.font.quit() + + def test_get_button_interactive(self): + c = self._get_first_controller() + if not c: + self.skipTest("No controller connected") + + pygame.display.init() + pygame.font.init() + + screen = pygame.display.set_mode((400, 400)) + font = pygame.font.Font(None, 20) + running = True + + label1 = font.render( + "Press button 'x' (on ps4) or 'a' (on xbox).", True, (0, 0, 0) + ) + + label2 = font.render( + 'The two values should match up. Press "y" or "n" to confirm.', + True, + (0, 0, 0), + ) + + is_pressed = [False, False] # event, get_button() + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + if event.type == pygame.CONTROLLERBUTTONDOWN and event.button == 0: + is_pressed[0] = True + if event.type == pygame.CONTROLLERBUTTONUP and event.button == 0: + is_pressed[0] = False + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_y: + running = False + if event.key == pygame.K_n: + running = False + pygame.display.quit() + pygame.font.quit() + self.fail() + + is_pressed[1] = c.get_button(pygame.CONTROLLER_BUTTON_A) + + screen.fill((255, 255, 255)) + screen.blit(label1, (0, 0)) + screen.blit(label2, (0, 20)) + screen.blit(font.render(str(is_pressed), True, (0, 0, 0)), (0, 40)) + pygame.display.update() + + pygame.display.quit() + pygame.font.quit() + + def test_get_axis_interactive(self): + c = self._get_first_controller() + if not c: + self.skipTest("No controller connected") + + pygame.display.init() + pygame.font.init() + + screen = pygame.display.set_mode((400, 400)) + font = pygame.font.Font(None, 20) + running = True + + label1 = font.render( + "Press down the right trigger. The value on-screen should", True, (0, 0, 0) + ) + + label2 = font.render( + "indicate how far the trigger is pressed down. This value should", + True, + (0, 0, 0), + ) + + label3 = font.render( + 'be in the range of 0-32767. Press "y" or "n" to confirm.', True, (0, 0, 0) + ) + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_y: + running = False + if event.key == pygame.K_n: + running = False + pygame.display.quit() + pygame.font.quit() + self.fail() + + right_trigger = c.get_axis(pygame.CONTROLLER_AXIS_TRIGGERRIGHT) + + screen.fill((255, 255, 255)) + screen.blit(label1, (0, 0)) + screen.blit(label2, (0, 20)) + screen.blit(label3, (0, 40)) + screen.blit(font.render(str(right_trigger), True, (0, 0, 0)), (0, 60)) + pygame.display.update() + + pygame.display.quit() + pygame.font.quit() + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/cursors_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/cursors_test.py new file mode 100644 index 0000000..bb8f766 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/cursors_test.py @@ -0,0 +1,291 @@ +import unittest +from pygame.tests.test_utils import fixture_path +import pygame + + +class CursorsModuleTest(unittest.TestCase): + def test_compile(self): + + # __doc__ (as of 2008-06-25) for pygame.cursors.compile: + + # pygame.cursors.compile(strings, black, white,xor) -> data, mask + # compile cursor strings into cursor data + # + # This takes a set of strings with equal length and computes + # the binary data for that cursor. The string widths must be + # divisible by 8. + # + # The black and white arguments are single letter strings that + # tells which characters will represent black pixels, and which + # characters represent white pixels. All other characters are + # considered clear. + # + # This returns a tuple containing the cursor data and cursor mask + # data. Both these arguments are used when setting a cursor with + # pygame.mouse.set_cursor(). + + # Various types of input strings + test_cursor1 = ("X.X.XXXX", "XXXXXX..", " XXXX ") + + test_cursor2 = ( + "X.X.XXXX", + "XXXXXX..", + "XXXXXX ", + "XXXXXX..", + "XXXXXX..", + "XXXXXX", + "XXXXXX..", + "XXXXXX..", + ) + test_cursor3 = (".XX.", " ", ".. ", "X.. X") + + # Test such that total number of strings is not divisible by 8 + with self.assertRaises(ValueError): + pygame.cursors.compile(test_cursor1) + + # Test such that size of individual string is not divisible by 8 + with self.assertRaises(ValueError): + pygame.cursors.compile(test_cursor2) + + # Test such that neither size of individual string nor total number of strings is divisible by 8 + with self.assertRaises(ValueError): + pygame.cursors.compile(test_cursor3) + + # Test that checks whether the byte data from compile funtion is equal to actual byte data + actual_byte_data = ( + 192, + 0, + 0, + 224, + 0, + 0, + 240, + 0, + 0, + 216, + 0, + 0, + 204, + 0, + 0, + 198, + 0, + 0, + 195, + 0, + 0, + 193, + 128, + 0, + 192, + 192, + 0, + 192, + 96, + 0, + 192, + 48, + 0, + 192, + 56, + 0, + 192, + 248, + 0, + 220, + 192, + 0, + 246, + 96, + 0, + 198, + 96, + 0, + 6, + 96, + 0, + 3, + 48, + 0, + 3, + 48, + 0, + 1, + 224, + 0, + 1, + 128, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ), ( + 192, + 0, + 0, + 224, + 0, + 0, + 240, + 0, + 0, + 248, + 0, + 0, + 252, + 0, + 0, + 254, + 0, + 0, + 255, + 0, + 0, + 255, + 128, + 0, + 255, + 192, + 0, + 255, + 224, + 0, + 255, + 240, + 0, + 255, + 248, + 0, + 255, + 248, + 0, + 255, + 192, + 0, + 247, + 224, + 0, + 199, + 224, + 0, + 7, + 224, + 0, + 3, + 240, + 0, + 3, + 240, + 0, + 1, + 224, + 0, + 1, + 128, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ) + + cursor = pygame.cursors.compile(pygame.cursors.thickarrow_strings) + self.assertEqual(cursor, actual_byte_data) + + # Test such that cursor byte data obtained from compile function is valid in pygame.mouse.set_cursor() + pygame.display.init() + try: + pygame.mouse.set_cursor((24, 24), (0, 0), *cursor) + except pygame.error as e: + if "not currently supported" in str(e): + unittest.skip("skipping test as set_cursor() is not supported") + finally: + pygame.display.quit() + + ################################################################################ + + def test_load_xbm(self): + # __doc__ (as of 2008-06-25) for pygame.cursors.load_xbm: + + # pygame.cursors.load_xbm(cursorfile, maskfile) -> cursor_args + # reads a pair of XBM files into set_cursor arguments + # + # Arguments can either be filenames or filelike objects + # with the readlines method. Not largely tested, but + # should work with typical XBM files. + + # Test that load_xbm will take filenames as arguments + cursorfile = fixture_path(r"xbm_cursors/white_sizing.xbm") + maskfile = fixture_path(r"xbm_cursors/white_sizing_mask.xbm") + cursor = pygame.cursors.load_xbm(cursorfile, maskfile) + + # Test that load_xbm will take file objects as arguments + with open(cursorfile) as cursor_f, open(maskfile) as mask_f: + cursor = pygame.cursors.load_xbm(cursor_f, mask_f) + + # Can it load using pathlib.Path? + import pathlib + + cursor = pygame.cursors.load_xbm( + pathlib.Path(cursorfile), pathlib.Path(maskfile) + ) + + # Is it in a format that mouse.set_cursor won't blow up on? + pygame.display.init() + try: + pygame.mouse.set_cursor(*cursor) + except pygame.error as e: + if "not currently supported" in str(e): + unittest.skip("skipping test as set_cursor() is not supported") + finally: + pygame.display.quit() + + def test_Cursor(self): + """Ensure that the cursor object parses information properly""" + + c1 = pygame.cursors.Cursor(pygame.SYSTEM_CURSOR_CROSSHAIR) + + self.assertEqual(c1.data, (pygame.SYSTEM_CURSOR_CROSSHAIR,)) + self.assertEqual(c1.type, "system") + + c2 = pygame.cursors.Cursor(c1) + + self.assertEqual(c1, c2) + + with self.assertRaises(TypeError): + pygame.cursors.Cursor(-34002) + with self.assertRaises(TypeError): + pygame.cursors.Cursor("a", "b", "c", "d") + with self.assertRaises(TypeError): + pygame.cursors.Cursor((2,)) + + c3 = pygame.cursors.Cursor((0, 0), pygame.Surface((20, 20))) + + self.assertEqual(c3.data[0], (0, 0)) + self.assertEqual(c3.data[1].get_size(), (20, 20)) + self.assertEqual(c3.type, "color") + + xormask, andmask = pygame.cursors.compile(pygame.cursors.thickarrow_strings) + c4 = pygame.cursors.Cursor((24, 24), (0, 0), xormask, andmask) + + self.assertEqual(c4.data, ((24, 24), (0, 0), xormask, andmask)) + self.assertEqual(c4.type, "bitmap") + + +################################################################################ + +if __name__ == "__main__": + unittest.main() + +################################################################################ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/display_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/display_test.py new file mode 100644 index 0000000..a44ff15 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/display_test.py @@ -0,0 +1,811 @@ +# -*- coding: utf-8 -*- + +import unittest +import os +import time + +import pygame, pygame.transform + +from pygame.tests.test_utils import question + +from pygame import display + + +class DisplayModuleTest(unittest.TestCase): + default_caption = "pygame window" + + def setUp(self): + display.init() + + def tearDown(self): + display.quit() + + def test_Info(self): + inf = pygame.display.Info() + self.assertNotEqual(inf.current_h, -1) + self.assertNotEqual(inf.current_w, -1) + # probably have an older SDL than 1.2.10 if -1. + + screen = pygame.display.set_mode((128, 128)) + inf = pygame.display.Info() + self.assertEqual(inf.current_h, 128) + self.assertEqual(inf.current_w, 128) + + def test_flip(self): + screen = pygame.display.set_mode((100, 100)) + + # test without a change + self.assertIsNone(pygame.display.flip()) + + # test with a change + pygame.Surface.fill(screen, (66, 66, 53)) + self.assertIsNone(pygame.display.flip()) + + # test without display init + pygame.display.quit() + with self.assertRaises(pygame.error): + (pygame.display.flip()) + + # test without window + del screen + with self.assertRaises(pygame.error): + (pygame.display.flip()) + + def test_get_active(self): + """Test the get_active function""" + + # Initially, the display is not active + pygame.display.quit() + self.assertEqual(pygame.display.get_active(), False) + + # get_active defaults to true after a set_mode + pygame.display.init() + pygame.display.set_mode((640, 480)) + self.assertEqual(pygame.display.get_active(), True) + + # get_active after init/quit should be False + # since no display is visible + pygame.display.quit() + pygame.display.init() + self.assertEqual(pygame.display.get_active(), False) + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == "dummy", + "requires the SDL_VIDEODRIVER to be a non dummy value", + ) + def test_get_active_iconify(self): + """Test the get_active function after an iconify""" + + # According to the docs, get_active should return + # false if the display is iconified + pygame.display.set_mode((640, 480)) + + pygame.event.clear() + pygame.display.iconify() + + for _ in range(100): + time.sleep(0.01) + pygame.event.pump() + + self.assertEqual(pygame.display.get_active(), False) + + def test_get_caption(self): + screen = display.set_mode((100, 100)) + + self.assertEqual(display.get_caption()[0], self.default_caption) + + def test_set_caption(self): + TEST_CAPTION = "test" + screen = display.set_mode((100, 100)) + + self.assertIsNone(display.set_caption(TEST_CAPTION)) + self.assertEqual(display.get_caption()[0], TEST_CAPTION) + self.assertEqual(display.get_caption()[1], TEST_CAPTION) + + def test_caption_unicode(self): + TEST_CAPTION = "å°" + display.set_caption(TEST_CAPTION) + self.assertEqual(display.get_caption()[0], TEST_CAPTION) + + def test_get_driver(self): + drivers = [ + "aalib", + "android", + "arm", + "cocoa", + "dga", + "directx", + "directfb", + "dummy", + "emscripten", + "fbcon", + "ggi", + "haiku", + "khronos", + "kmsdrm", + "nacl", + "offscreen", + "pandora", + "psp", + "qnx", + "raspberry", + "svgalib", + "uikit", + "vgl", + "vivante", + "wayland", + "windows", + "windib", + "winrt", + "x11", + ] + driver = display.get_driver() + self.assertIn(driver, drivers) + + display.quit() + with self.assertRaises(pygame.error): + driver = display.get_driver() + + def test_get_init(self): + """Ensures the module's initialization state can be retrieved.""" + # display.init() already called in setUp() + self.assertTrue(display.get_init()) + + # This test can be uncommented when issues #991 and #993 are resolved. + @unittest.skipIf(True, "SDL2 issues") + def test_get_surface(self): + """Ensures get_surface gets the current display surface.""" + lengths = (1, 5, 100) + + for expected_size in ((w, h) for w in lengths for h in lengths): + for expected_depth in (8, 16, 24, 32): + expected_surface = display.set_mode(expected_size, 0, expected_depth) + + surface = pygame.display.get_surface() + + self.assertEqual(surface, expected_surface) + self.assertIsInstance(surface, pygame.Surface) + self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_bitsize(), expected_depth) + + def test_get_surface__mode_not_set(self): + """Ensures get_surface handles the display mode not being set.""" + surface = pygame.display.get_surface() + + self.assertIsNone(surface) + + def test_get_wm_info(self): + wm_info = display.get_wm_info() + # Assert function returns a dictionary type + self.assertIsInstance(wm_info, dict) + + wm_info_potential_keys = { + "colorbuffer", + "connection", + "data", + "dfb", + "display", + "framebuffer", + "fswindow", + "hdc", + "hglrc", + "hinstance", + "lock_func", + "resolveFramebuffer", + "shell_surface", + "surface", + "taskHandle", + "unlock_func", + "wimpVersion", + "window", + "wmwindow", + } + + # If any unexpected dict keys are present, they + # will be stored in set wm_info_remaining_keys + wm_info_remaining_keys = set(wm_info.keys()).difference(wm_info_potential_keys) + + # Assert set is empty (& therefore does not + # contain unexpected dict keys) + self.assertFalse(wm_info_remaining_keys) + + @unittest.skipIf( + ( + "skipping for all because some failures on rasppi and maybe other platforms" + or os.environ.get("SDL_VIDEODRIVER") == "dummy" + ), + 'OpenGL requires a non-"dummy" SDL_VIDEODRIVER', + ) + def test_gl_get_attribute(self): + + screen = display.set_mode((0, 0), pygame.OPENGL) + + # We create a list where we store the original values of the + # flags before setting them with a different value. + original_values = [] + + original_values.append(pygame.display.gl_get_attribute(pygame.GL_ALPHA_SIZE)) + original_values.append(pygame.display.gl_get_attribute(pygame.GL_DEPTH_SIZE)) + original_values.append(pygame.display.gl_get_attribute(pygame.GL_STENCIL_SIZE)) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_ACCUM_RED_SIZE) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_ACCUM_GREEN_SIZE) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_ACCUM_BLUE_SIZE) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_ACCUM_ALPHA_SIZE) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_MULTISAMPLEBUFFERS) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_MULTISAMPLESAMPLES) + ) + original_values.append(pygame.display.gl_get_attribute(pygame.GL_STEREO)) + + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_ACCELERATED_VISUAL) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_CONTEXT_MAJOR_VERSION) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_CONTEXT_MINOR_VERSION) + ) + original_values.append(pygame.display.gl_get_attribute(pygame.GL_CONTEXT_FLAGS)) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_CONTEXT_PROFILE_MASK) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_SHARE_WITH_CURRENT_CONTEXT) + ) + original_values.append( + pygame.display.gl_get_attribute(pygame.GL_FRAMEBUFFER_SRGB_CAPABLE) + ) + + # Setting the flags with values supposedly different from the original values + + # assign SDL1-supported values with gl_set_attribute + pygame.display.gl_set_attribute(pygame.GL_ALPHA_SIZE, 8) + pygame.display.gl_set_attribute(pygame.GL_DEPTH_SIZE, 24) + pygame.display.gl_set_attribute(pygame.GL_STENCIL_SIZE, 8) + pygame.display.gl_set_attribute(pygame.GL_ACCUM_RED_SIZE, 16) + pygame.display.gl_set_attribute(pygame.GL_ACCUM_GREEN_SIZE, 16) + pygame.display.gl_set_attribute(pygame.GL_ACCUM_BLUE_SIZE, 16) + pygame.display.gl_set_attribute(pygame.GL_ACCUM_ALPHA_SIZE, 16) + pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLEBUFFERS, 1) + pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES, 1) + pygame.display.gl_set_attribute(pygame.GL_STEREO, 0) + pygame.display.gl_set_attribute(pygame.GL_ACCELERATED_VISUAL, 0) + pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MAJOR_VERSION, 1) + pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MINOR_VERSION, 1) + pygame.display.gl_set_attribute(pygame.GL_CONTEXT_FLAGS, 0) + pygame.display.gl_set_attribute(pygame.GL_CONTEXT_PROFILE_MASK, 0) + pygame.display.gl_set_attribute(pygame.GL_SHARE_WITH_CURRENT_CONTEXT, 0) + pygame.display.gl_set_attribute(pygame.GL_FRAMEBUFFER_SRGB_CAPABLE, 0) + + # We create a list where we store the values that we set each flag to + set_values = [8, 24, 8, 16, 16, 16, 16, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0] + + # We create a list where we store the values after getting them + get_values = [] + + get_values.append(pygame.display.gl_get_attribute(pygame.GL_ALPHA_SIZE)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_DEPTH_SIZE)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_STENCIL_SIZE)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_ACCUM_RED_SIZE)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_ACCUM_GREEN_SIZE)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_ACCUM_BLUE_SIZE)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_ACCUM_ALPHA_SIZE)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_MULTISAMPLEBUFFERS)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_MULTISAMPLESAMPLES)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_STEREO)) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_ACCELERATED_VISUAL)) + get_values.append( + pygame.display.gl_get_attribute(pygame.GL_CONTEXT_MAJOR_VERSION) + ) + get_values.append( + pygame.display.gl_get_attribute(pygame.GL_CONTEXT_MINOR_VERSION) + ) + get_values.append(pygame.display.gl_get_attribute(pygame.GL_CONTEXT_FLAGS)) + get_values.append( + pygame.display.gl_get_attribute(pygame.GL_CONTEXT_PROFILE_MASK) + ) + get_values.append( + pygame.display.gl_get_attribute(pygame.GL_SHARE_WITH_CURRENT_CONTEXT) + ) + get_values.append( + pygame.display.gl_get_attribute(pygame.GL_FRAMEBUFFER_SRGB_CAPABLE) + ) + + # We check to see if the values that we get correspond to the values that we set + # them to or to the original values. + for i in range(len(original_values)): + self.assertTrue( + (get_values[i] == original_values[i]) + or (get_values[i] == set_values[i]) + ) + + # test using non-flag argument + with self.assertRaises(TypeError): + pygame.display.gl_get_attribute("DUMMY") + + def todo_test_gl_set_attribute(self): + + # __doc__ (as of 2008-08-02) for pygame.display.gl_set_attribute: + + # pygame.display.gl_set_attribute(flag, value): return None + # request an opengl display attribute for the display mode + # + # When calling pygame.display.set_mode() with the pygame.OPENGL flag, + # Pygame automatically handles setting the OpenGL attributes like + # color and doublebuffering. OpenGL offers several other attributes + # you may want control over. Pass one of these attributes as the flag, + # and its appropriate value. This must be called before + # pygame.display.set_mode() + # + # The OPENGL flags are; + # GL_ALPHA_SIZE, GL_DEPTH_SIZE, GL_STENCIL_SIZE, GL_ACCUM_RED_SIZE, + # GL_ACCUM_GREEN_SIZE, GL_ACCUM_BLUE_SIZE, GL_ACCUM_ALPHA_SIZE, + # GL_MULTISAMPLEBUFFERS, GL_MULTISAMPLESAMPLES, GL_STEREO + + self.fail() + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") in ["dummy", "android"], + "iconify is only supported on some video drivers/platforms", + ) + def test_iconify(self): + pygame.display.set_mode((640, 480)) + + self.assertEqual(pygame.display.get_active(), True) + + success = pygame.display.iconify() + + if success: + active_event = window_minimized_event = False + # make sure we cycle the event loop enough to get the display + # hidden. Test that both ACTIVEEVENT and WINDOWMINIMISED event appears + for _ in range(50): + time.sleep(0.01) + for event in pygame.event.get(): + if event.type == pygame.ACTIVEEVENT: + if not event.gain and event.state == pygame.APPACTIVE: + active_event = True + if event.type == pygame.WINDOWMINIMIZED: + window_minimized_event = True + + self.assertTrue(window_minimized_event) + self.assertTrue(active_event) + self.assertFalse(pygame.display.get_active()) + + else: + self.fail("Iconify not supported on this platform, please skip") + + def test_init(self): + """Ensures the module is initialized after init called.""" + # display.init() already called in setUp(), so quit and re-init + display.quit() + display.init() + + self.assertTrue(display.get_init()) + + def test_init__multiple(self): + """Ensures the module is initialized after multiple init calls.""" + display.init() + display.init() + + self.assertTrue(display.get_init()) + + def test_list_modes(self): + modes = pygame.display.list_modes(depth=0, flags=pygame.FULLSCREEN, display=0) + # modes == -1 means any mode is supported. + if modes != -1: + self.assertEqual(len(modes[0]), 2) + self.assertEqual(type(modes[0][0]), int) + + modes = pygame.display.list_modes() + if modes != -1: + self.assertEqual(len(modes[0]), 2) + self.assertEqual(type(modes[0][0]), int) + self.assertEqual(len(modes), len(set(modes))) + + modes = pygame.display.list_modes(depth=0, flags=0, display=0) + if modes != -1: + self.assertEqual(len(modes[0]), 2) + self.assertEqual(type(modes[0][0]), int) + + def test_mode_ok(self): + pygame.display.mode_ok((128, 128)) + modes = pygame.display.list_modes() + if modes != -1: + size = modes[0] + self.assertNotEqual(pygame.display.mode_ok(size), 0) + + pygame.display.mode_ok((128, 128), 0, 32) + pygame.display.mode_ok((128, 128), flags=0, depth=32, display=0) + + def test_mode_ok_fullscreen(self): + modes = pygame.display.list_modes() + if modes != -1: + size = modes[0] + self.assertNotEqual( + pygame.display.mode_ok(size, flags=pygame.FULLSCREEN), 0 + ) + + def test_mode_ok_scaled(self): + modes = pygame.display.list_modes() + if modes != -1: + size = modes[0] + self.assertNotEqual(pygame.display.mode_ok(size, flags=pygame.SCALED), 0) + + def test_get_num_displays(self): + self.assertGreater(pygame.display.get_num_displays(), 0) + + def test_quit(self): + """Ensures the module is not initialized after quit called.""" + display.quit() + + self.assertFalse(display.get_init()) + + def test_quit__multiple(self): + """Ensures the module is not initialized after multiple quit calls.""" + display.quit() + display.quit() + + self.assertFalse(display.get_init()) + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == "dummy", "Needs a not dummy videodriver" + ) + def test_set_gamma(self): + pygame.display.set_mode((1, 1)) + + gammas = [0.25, 0.5, 0.88, 1.0] + for gamma in gammas: + with self.subTest(gamma=gamma): + self.assertEqual(pygame.display.set_gamma(gamma), True) + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == "dummy", "Needs a not dummy videodriver" + ) + def test_set_gamma__tuple(self): + pygame.display.set_mode((1, 1)) + + gammas = [(0.5, 0.5, 0.5), (1.0, 1.0, 1.0), (0.25, 0.33, 0.44)] + for r, g, b in gammas: + with self.subTest(r=r, g=g, b=b): + self.assertEqual(pygame.display.set_gamma(r, g, b), True) + + @unittest.skipIf( + not hasattr(pygame.display, "set_gamma_ramp"), + "Not all systems and hardware support gamma ramps", + ) + def test_set_gamma_ramp(self): + + # __doc__ (as of 2008-08-02) for pygame.display.set_gamma_ramp: + + # change the hardware gamma ramps with a custom lookup + # pygame.display.set_gamma_ramp(red, green, blue): return bool + # set_gamma_ramp(red, green, blue): return bool + # + # Set the red, green, and blue gamma ramps with an explicit lookup + # table. Each argument should be sequence of 256 integers. The + # integers should range between 0 and 0xffff. Not all systems and + # hardware support gamma ramps, if the function succeeds it will + # return True. + # + pygame.display.set_mode((5, 5)) + r = list(range(256)) + g = [number + 256 for number in r] + b = [number + 256 for number in g] + isSupported = pygame.display.set_gamma_ramp(r, g, b) + if isSupported: + self.assertTrue(pygame.display.set_gamma_ramp(r, g, b)) + else: + self.assertFalse(pygame.display.set_gamma_ramp(r, g, b)) + + def test_set_mode_kwargs(self): + + pygame.display.set_mode(size=(1, 1), flags=0, depth=0, display=0) + + def test_set_mode_scaled(self): + surf = pygame.display.set_mode( + size=(1, 1), flags=pygame.SCALED, depth=0, display=0 + ) + winsize = pygame.display.get_window_size() + self.assertEqual( + winsize[0] % surf.get_size()[0], + 0, + "window width should be a multiple of the surface width", + ) + self.assertEqual( + winsize[1] % surf.get_size()[1], + 0, + "window height should be a multiple of the surface height", + ) + self.assertEqual( + winsize[0] / surf.get_size()[0], winsize[1] / surf.get_size()[1] + ) + + def test_set_mode_vector2(self): + pygame.display.set_mode(pygame.Vector2(1, 1)) + + def test_set_mode_unscaled(self): + """Ensures a window created with SCALED can become smaller.""" + # see https://github.com/pygame/pygame/issues/2327 + + screen = pygame.display.set_mode((300, 300), pygame.SCALED) + self.assertEqual(screen.get_size(), (300, 300)) + + screen = pygame.display.set_mode((200, 200)) + self.assertEqual(screen.get_size(), (200, 200)) + + def test_screensaver_support(self): + pygame.display.set_allow_screensaver(True) + self.assertTrue(pygame.display.get_allow_screensaver()) + pygame.display.set_allow_screensaver(False) + self.assertFalse(pygame.display.get_allow_screensaver()) + pygame.display.set_allow_screensaver() + self.assertTrue(pygame.display.get_allow_screensaver()) + + # the following test fails always with SDL2 + @unittest.skipIf(True, "set_palette() not supported in SDL2") + def test_set_palette(self): + with self.assertRaises(pygame.error): + palette = [1, 2, 3] + pygame.display.set_palette(palette) + pygame.display.set_mode((1024, 768), 0, 8) + palette = [] + self.assertIsNone(pygame.display.set_palette(palette)) + + with self.assertRaises(ValueError): + palette = 12 + pygame.display.set_palette(palette) + with self.assertRaises(TypeError): + palette = [[1, 2], [1, 2]] + pygame.display.set_palette(palette) + with self.assertRaises(TypeError): + palette = [[0, 0, 0, 0, 0]] + [[x, x, x, x, x] for x in range(1, 255)] + pygame.display.set_palette(palette) + with self.assertRaises(TypeError): + palette = "qwerty" + pygame.display.set_palette(palette) + with self.assertRaises(TypeError): + palette = [[123, 123, 123] * 10000] + pygame.display.set_palette(palette) + with self.assertRaises(TypeError): + palette = [1, 2, 3] + pygame.display.set_palette(palette) + + skip_list = ["dummy", "android"] + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") in skip_list, + "requires the SDL_VIDEODRIVER to be non dummy", + ) + def test_toggle_fullscreen(self): + """Test for toggle fullscreen""" + + # try to toggle fullscreen with no active display + # this should result in an error + pygame.display.quit() + with self.assertRaises(pygame.error): + pygame.display.toggle_fullscreen() + + pygame.display.init() + width_height = (640, 480) + test_surf = pygame.display.set_mode(width_height) + + # try to toggle fullscreen + try: + pygame.display.toggle_fullscreen() + + except pygame.error: + self.fail() + + else: + # if toggle success, the width/height should be a + # value found in list_modes + if pygame.display.toggle_fullscreen() == 1: + boolean = ( + test_surf.get_width(), + test_surf.get_height(), + ) in pygame.display.list_modes( + depth=0, flags=pygame.FULLSCREEN, display=0 + ) + + self.assertEqual(boolean, True) + + # if not original width/height should be preserved + else: + self.assertEqual( + (test_surf.get_width(), test_surf.get_height()), width_height + ) + + +class DisplayUpdateTest(unittest.TestCase): + def question(self, qstr): + """this is used in the interactive subclass.""" + + def setUp(self): + display.init() + self.screen = pygame.display.set_mode((500, 500)) + self.screen.fill("black") + pygame.display.flip() + pygame.event.pump() # so mac updates + + def tearDown(self): + display.quit() + + def test_update_negative(self): + """takes rects with negative values.""" + self.screen.fill("green") + + r1 = pygame.Rect(0, 0, 100, 100) + pygame.display.update(r1) + + r2 = pygame.Rect(-10, 0, 100, 100) + pygame.display.update(r2) + + r3 = pygame.Rect(-10, 0, -100, -100) + pygame.display.update(r3) + + self.question("Is the screen green in (0, 0, 100, 100)?") + + def test_update_sequence(self): + """only updates the part of the display given by the rects.""" + self.screen.fill("green") + rects = [ + pygame.Rect(0, 0, 100, 100), + pygame.Rect(100, 0, 100, 100), + pygame.Rect(200, 0, 100, 100), + pygame.Rect(300, 300, 100, 100), + ] + pygame.display.update(rects) + pygame.event.pump() # so mac updates + + self.question(f"Is the screen green in {rects}?") + + def test_update_none_skipped(self): + """None is skipped inside sequences.""" + self.screen.fill("green") + rects = ( + None, + pygame.Rect(100, 0, 100, 100), + None, + pygame.Rect(200, 0, 100, 100), + pygame.Rect(300, 300, 100, 100), + ) + pygame.display.update(rects) + pygame.event.pump() # so mac updates + + self.question(f"Is the screen green in {rects}?") + + def test_update_none(self): + """does NOT update the display.""" + self.screen.fill("green") + pygame.display.update(None) + pygame.event.pump() # so mac updates + self.question(f"Is the screen black and NOT green?") + + def test_update_no_args(self): + """does NOT update the display.""" + self.screen.fill("green") + pygame.display.update() + pygame.event.pump() # so mac updates + self.question(f"Is the WHOLE screen green?") + + def test_update_args(self): + """updates the display using the args as a rect.""" + self.screen.fill("green") + pygame.display.update(100, 100, 100, 100) + pygame.event.pump() # so mac updates + self.question("Is the screen green in (100, 100, 100, 100)?") + + def test_update_incorrect_args(self): + """raises a ValueError when inputs are wrong.""" + + with self.assertRaises(ValueError): + pygame.display.update(100, "asdf", 100, 100) + + with self.assertRaises(ValueError): + pygame.display.update([100, "asdf", 100, 100]) + + def test_update_no_init(self): + """raises a pygame.error.""" + + pygame.display.quit() + with self.assertRaises(pygame.error): + pygame.display.update() + + +class DisplayUpdateInteractiveTest(DisplayUpdateTest): + """Because we want these tests to run as interactive and not interactive.""" + + __tags__ = ["interactive"] + + def question(self, qstr): + """since this is the interactive sublcass we ask a question.""" + question(qstr) + + +class DisplayInteractiveTest(unittest.TestCase): + + __tags__ = ["interactive"] + + def test_set_icon_interactive(self): + + os.environ["SDL_VIDEO_WINDOW_POS"] = "100,250" + pygame.display.quit() + pygame.display.init() + + test_icon = pygame.Surface((32, 32)) + test_icon.fill((255, 0, 0)) + + pygame.display.set_icon(test_icon) + screen = pygame.display.set_mode((400, 100)) + pygame.display.set_caption("Is the window icon a red square?") + + response = question("Is the display icon red square?") + + self.assertTrue(response) + pygame.display.quit() + + def test_set_gamma_ramp(self): + + os.environ["SDL_VIDEO_WINDOW_POS"] = "100,250" + pygame.display.quit() + pygame.display.init() + + screen = pygame.display.set_mode((400, 100)) + screen.fill((100, 100, 100)) + + blue_ramp = [x * 256 for x in range(0, 256)] + blue_ramp[100] = 150 * 256 # Can't tint too far or gamma ramps fail + normal_ramp = [x * 256 for x in range(0, 256)] + # test to see if this platform supports gamma ramps + gamma_success = False + if pygame.display.set_gamma_ramp(normal_ramp, normal_ramp, blue_ramp): + pygame.display.update() + gamma_success = True + + if gamma_success: + response = question("Is the window background tinted blue?") + self.assertTrue(response) + # restore normal ramp + pygame.display.set_gamma_ramp(normal_ramp, normal_ramp, normal_ramp) + + pygame.display.quit() + + +@unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == "dummy", + 'OpenGL requires a non-"dummy" SDL_VIDEODRIVER', +) +class DisplayOpenGLTest(unittest.TestCase): + def test_screen_size_opengl(self): + """returns a surface with the same size requested. + |tags:display,slow,opengl| + """ + pygame.display.init() + screen = pygame.display.set_mode((640, 480), pygame.OPENGL) + self.assertEqual((640, 480), screen.get_size()) + + +class X11CrashTest(unittest.TestCase): + def test_x11_set_mode_crash_gh1654(self): + # Test for https://github.com/pygame/pygame/issues/1654 + # If unfixed, this will trip a segmentation fault + pygame.display.init() + pygame.display.quit() + screen = pygame.display.set_mode((640, 480), 0) + self.assertEqual((640, 480), screen.get_size()) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/docs_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/docs_test.py new file mode 100644 index 0000000..de021a8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/docs_test.py @@ -0,0 +1,35 @@ +import os +import subprocess +import sys +import unittest + + +class DocsIncludedTest(unittest.TestCase): + def test_doc_import_works(self): + from pygame.docs.__main__ import has_local_docs, open_docs + + @unittest.skipIf("CI" not in os.environ, "Docs not required for local builds") + def test_docs_included(self): + from pygame.docs.__main__ import has_local_docs + + self.assertTrue(has_local_docs()) + + @unittest.skipIf("CI" not in os.environ, "Docs not required for local builds") + def test_docs_command(self): + try: + subprocess.run( + [sys.executable, "-m", "pygame.docs"], + timeout=5, + # check ensures an exception is raised when the process fails + check=True, + # pipe stdout/stderr so that they don't clutter main stdout + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + except subprocess.TimeoutExpired: + # timeout errors are not an issue + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/draw_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/draw_test.py new file mode 100644 index 0000000..d876060 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/draw_test.py @@ -0,0 +1,6563 @@ +import math +import unittest +import sys +import warnings + +import pygame +from pygame import draw +from pygame import draw_py +from pygame.locals import SRCALPHA +from pygame.tests import test_utils +from pygame.math import Vector2 + + +RED = BG_RED = pygame.Color("red") +GREEN = FG_GREEN = pygame.Color("green") + +# Clockwise from the top left corner and ending with the center point. +RECT_POSITION_ATTRIBUTES = ( + "topleft", + "midtop", + "topright", + "midright", + "bottomright", + "midbottom", + "bottomleft", + "midleft", + "center", +) + + +def get_border_values(surface, width, height): + """Returns a list containing lists with the values of the surface's + borders. + """ + border_top = [surface.get_at((x, 0)) for x in range(width)] + border_left = [surface.get_at((0, y)) for y in range(height)] + border_right = [surface.get_at((width - 1, y)) for y in range(height)] + border_bottom = [surface.get_at((x, height - 1)) for x in range(width)] + + return [border_top, border_left, border_right, border_bottom] + + +def corners(surface): + """Returns a tuple with the corner positions of the given surface. + + Clockwise from the top left corner. + """ + width, height = surface.get_size() + return ((0, 0), (width - 1, 0), (width - 1, height - 1), (0, height - 1)) + + +def rect_corners_mids_and_center(rect): + """Returns a tuple with each corner, mid, and the center for a given rect. + + Clockwise from the top left corner and ending with the center point. + """ + return ( + rect.topleft, + rect.midtop, + rect.topright, + rect.midright, + rect.bottomright, + rect.midbottom, + rect.bottomleft, + rect.midleft, + rect.center, + ) + + +def border_pos_and_color(surface): + """Yields each border position and its color for a given surface. + + Clockwise from the top left corner. + """ + width, height = surface.get_size() + right, bottom = width - 1, height - 1 + + # Top edge. + for x in range(width): + pos = (x, 0) + yield pos, surface.get_at(pos) + + # Right edge. + # Top right done in top edge loop. + for y in range(1, height): + pos = (right, y) + yield pos, surface.get_at(pos) + + # Bottom edge. + # Bottom right done in right edge loop. + for x in range(right - 1, -1, -1): + pos = (x, bottom) + yield pos, surface.get_at(pos) + + # Left edge. + # Bottom left done in bottom edge loop. Top left done in top edge loop. + for y in range(bottom - 1, 0, -1): + pos = (0, y) + yield pos, surface.get_at(pos) + + +def get_color_points(surface, color, bounds_rect=None, match_color=True): + """Get all the points of a given color on the surface within the given + bounds. + + If bounds_rect is None the full surface is checked. + If match_color is True, all points matching the color are returned, + otherwise all points not matching the color are returned. + """ + get_at = surface.get_at # For possible speed up. + + if bounds_rect is None: + x_range = range(surface.get_width()) + y_range = range(surface.get_height()) + else: + x_range = range(bounds_rect.left, bounds_rect.right) + y_range = range(bounds_rect.top, bounds_rect.bottom) + + surface.lock() # For possible speed up. + + if match_color: + pts = [(x, y) for x in x_range for y in y_range if get_at((x, y)) == color] + else: + pts = [(x, y) for x in x_range for y in y_range if get_at((x, y)) != color] + + surface.unlock() + return pts + + +def create_bounding_rect(surface, surf_color, default_pos): + """Create a rect to bound all the pixels that don't match surf_color. + + The default_pos parameter is used to position the bounding rect for the + case where all pixels match the surf_color. + """ + width, height = surface.get_clip().size + xmin, ymin = width, height + xmax, ymax = -1, -1 + get_at = surface.get_at # For possible speed up. + + surface.lock() # For possible speed up. + + for y in range(height): + for x in range(width): + if get_at((x, y)) != surf_color: + xmin = min(x, xmin) + xmax = max(x, xmax) + ymin = min(y, ymin) + ymax = max(y, ymax) + + surface.unlock() + + if -1 == xmax: + # No points means a 0 sized rect positioned at default_pos. + return pygame.Rect(default_pos, (0, 0)) + return pygame.Rect((xmin, ymin), (xmax - xmin + 1, ymax - ymin + 1)) + + +class InvalidBool(object): + """To help test invalid bool values.""" + + __nonzero__ = None + __bool__ = None + + +class DrawTestCase(unittest.TestCase): + """Base class to test draw module functions.""" + + draw_rect = staticmethod(draw.rect) + draw_polygon = staticmethod(draw.polygon) + draw_circle = staticmethod(draw.circle) + draw_ellipse = staticmethod(draw.ellipse) + draw_arc = staticmethod(draw.arc) + draw_line = staticmethod(draw.line) + draw_lines = staticmethod(draw.lines) + draw_aaline = staticmethod(draw.aaline) + draw_aalines = staticmethod(draw.aalines) + + +class PythonDrawTestCase(unittest.TestCase): + """Base class to test draw_py module functions.""" + + # draw_py is currently missing some functions. + # draw_rect = staticmethod(draw_py.draw_rect) + draw_polygon = staticmethod(draw_py.draw_polygon) + # draw_circle = staticmethod(draw_py.draw_circle) + # draw_ellipse = staticmethod(draw_py.draw_ellipse) + # draw_arc = staticmethod(draw_py.draw_arc) + draw_line = staticmethod(draw_py.draw_line) + draw_lines = staticmethod(draw_py.draw_lines) + draw_aaline = staticmethod(draw_py.draw_aaline) + draw_aalines = staticmethod(draw_py.draw_aalines) + + +### Ellipse Testing ########################################################### + + +class DrawEllipseMixin(object): + """Mixin tests for drawing ellipses. + + This class contains all the general ellipse drawing tests. + """ + + def test_ellipse__args(self): + """Ensures draw ellipse accepts the correct args.""" + bounds_rect = self.draw_ellipse( + pygame.Surface((3, 3)), (0, 10, 0, 50), pygame.Rect((0, 0), (3, 2)), 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__args_without_width(self): + """Ensures draw ellipse accepts the args without a width.""" + bounds_rect = self.draw_ellipse( + pygame.Surface((2, 2)), (1, 1, 1, 99), pygame.Rect((1, 1), (1, 1)) + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__args_with_negative_width(self): + """Ensures draw ellipse accepts the args with negative width.""" + bounds_rect = self.draw_ellipse( + pygame.Surface((3, 3)), (0, 10, 0, 50), pygame.Rect((2, 3), (3, 2)), -1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + self.assertEqual(bounds_rect, pygame.Rect(2, 3, 0, 0)) + + def test_ellipse__args_with_width_gt_radius(self): + """Ensures draw ellipse accepts the args with + width > rect.w // 2 and width > rect.h // 2. + """ + rect = pygame.Rect((0, 0), (4, 4)) + bounds_rect = self.draw_ellipse( + pygame.Surface((3, 3)), (0, 10, 0, 50), rect, rect.w // 2 + 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + bounds_rect = self.draw_ellipse( + pygame.Surface((3, 3)), (0, 10, 0, 50), rect, rect.h // 2 + 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__kwargs(self): + """Ensures draw ellipse accepts the correct kwargs + with and without a width arg. + """ + kwargs_list = [ + { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("yellow"), + "rect": pygame.Rect((0, 0), (3, 2)), + "width": 1, + }, + { + "surface": pygame.Surface((2, 1)), + "color": (0, 10, 20), + "rect": (0, 0, 1, 1), + }, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_ellipse(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__kwargs_order_independent(self): + """Ensures draw ellipse's kwargs are not order dependent.""" + bounds_rect = self.draw_ellipse( + color=(1, 2, 3), + surface=pygame.Surface((3, 2)), + width=0, + rect=pygame.Rect((1, 0), (1, 1)), + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__args_missing(self): + """Ensures draw ellipse detects any missing required args.""" + surface = pygame.Surface((1, 1)) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_ellipse(surface, pygame.Color("red")) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_ellipse(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_ellipse() + + def test_ellipse__kwargs_missing(self): + """Ensures draw ellipse detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((1, 2)), + "color": pygame.Color("red"), + "rect": pygame.Rect((1, 0), (2, 2)), + "width": 2, + } + + for name in ("rect", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_ellipse(**invalid_kwargs) + + def test_ellipse__arg_invalid_types(self): + """Ensures draw ellipse detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + rect = pygame.Rect((1, 1), (1, 1)) + + with self.assertRaises(TypeError): + # Invalid width. + bounds_rect = self.draw_ellipse(surface, color, rect, "1") + + with self.assertRaises(TypeError): + # Invalid rect. + bounds_rect = self.draw_ellipse(surface, color, (1, 2, 3, 4, 5), 1) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_ellipse(surface, 2.3, rect, 0) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_ellipse(rect, color, rect, 2) + + def test_ellipse__kwarg_invalid_types(self): + """Ensures draw ellipse detects invalid kwarg types.""" + surface = pygame.Surface((3, 3)) + color = pygame.Color("green") + rect = pygame.Rect((0, 1), (1, 1)) + kwargs_list = [ + { + "surface": pygame.Surface, # Invalid surface. + "color": color, + "rect": rect, + "width": 1, + }, + { + "surface": surface, + "color": 2.3, # Invalid color. + "rect": rect, + "width": 1, + }, + { + "surface": surface, + "color": color, + "rect": (0, 0, 0), # Invalid rect. + "width": 1, + }, + {"surface": surface, "color": color, "rect": rect, "width": 1.1}, + ] # Invalid width. + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_ellipse(**kwargs) + + def test_ellipse__kwarg_invalid_name(self): + """Ensures draw ellipse detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + rect = pygame.Rect((0, 1), (2, 2)) + kwargs_list = [ + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1, + "invalid": 1, + }, + {"surface": surface, "color": color, "rect": rect, "invalid": 1}, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_ellipse(**kwargs) + + def test_ellipse__args_and_kwargs(self): + """Ensures draw ellipse accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 1)) + color = (255, 255, 0, 0) + rect = pygame.Rect((1, 0), (2, 1)) + width = 0 + kwargs = {"surface": surface, "color": color, "rect": rect, "width": width} + + for name in ("surface", "color", "rect", "width"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_ellipse(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_ellipse(surface, color, **kwargs) + elif "rect" == name: + bounds_rect = self.draw_ellipse(surface, color, rect, **kwargs) + else: + bounds_rect = self.draw_ellipse(surface, color, rect, width, **kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__valid_width_values(self): + """Ensures draw ellipse accepts different width values.""" + pos = (1, 1) + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + color = (10, 20, 30, 255) + kwargs = { + "surface": surface, + "color": color, + "rect": pygame.Rect(pos, (3, 2)), + "width": None, + } + + for width in (-1000, -10, -1, 0, 1, 10, 1000): + surface.fill(surface_color) # Clear for each test. + kwargs["width"] = width + expected_color = color if width >= 0 else surface_color + + bounds_rect = self.draw_ellipse(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__valid_rect_formats(self): + """Ensures draw ellipse accepts different rect formats.""" + pos = (1, 1) + expected_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((4, 4)) + kwargs = {"surface": surface, "color": expected_color, "rect": None, "width": 0} + rects = (pygame.Rect(pos, (1, 3)), (pos, (2, 1)), (pos[0], pos[1], 1, 1)) + + for rect in rects: + surface.fill(surface_color) # Clear for each test. + kwargs["rect"] = rect + + bounds_rect = self.draw_ellipse(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__valid_color_formats(self): + """Ensures draw ellipse accepts different color formats.""" + pos = (1, 1) + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": None, + "rect": pygame.Rect(pos, (1, 2)), + "width": 0, + } + reds = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in reds: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_ellipse(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_ellipse__invalid_color_formats(self): + """Ensures draw ellipse handles invalid color formats correctly.""" + pos = (1, 1) + surface = pygame.Surface((4, 3)) + kwargs = { + "surface": surface, + "color": None, + "rect": pygame.Rect(pos, (2, 2)), + "width": 1, + } + + for expected_color in (2.3, surface): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_ellipse(**kwargs) + + def test_ellipse(self): + """Tests ellipses of differing sizes on surfaces of differing sizes. + + Checks if the number of sides touching the border of the surface is + correct. + """ + left_top = [(0, 0), (1, 0), (0, 1), (1, 1)] + sizes = [(4, 4), (5, 4), (4, 5), (5, 5)] + color = (1, 13, 24, 255) + + def same_size(width, height, border_width): + """Test for ellipses with the same size as the surface.""" + surface = pygame.Surface((width, height)) + + self.draw_ellipse(surface, color, (0, 0, width, height), border_width) + + # For each of the four borders check if it contains the color + borders = get_border_values(surface, width, height) + for border in borders: + self.assertTrue(color in border) + + def not_same_size(width, height, border_width, left, top): + """Test for ellipses that aren't the same size as the surface.""" + surface = pygame.Surface((width, height)) + + self.draw_ellipse( + surface, color, (left, top, width - 1, height - 1), border_width + ) + + borders = get_border_values(surface, width, height) + + # Check if two sides of the ellipse are touching the border + sides_touching = [color in border for border in borders].count(True) + self.assertEqual(sides_touching, 2) + + for width, height in sizes: + for border_width in (0, 1): + same_size(width, height, border_width) + for left, top in left_top: + not_same_size(width, height, border_width, left, top) + + def test_ellipse__big_ellipse(self): + """Test for big ellipse that could overflow in algorithm""" + width = 1025 + height = 1025 + border = 1 + x_value_test = int(0.4 * height) + y_value_test = int(0.4 * height) + surface = pygame.Surface((width, height)) + + self.draw_ellipse(surface, (255, 0, 0), (0, 0, width, height), border) + colored_pixels = 0 + for y in range(height): + if surface.get_at((x_value_test, y)) == (255, 0, 0): + colored_pixels += 1 + for x in range(width): + if surface.get_at((x, y_value_test)) == (255, 0, 0): + colored_pixels += 1 + self.assertEqual(colored_pixels, border * 4) + + def test_ellipse__thick_line(self): + """Ensures a thick lined ellipse is drawn correctly.""" + ellipse_color = pygame.Color("yellow") + surface_color = pygame.Color("black") + surface = pygame.Surface((40, 40)) + rect = pygame.Rect((0, 0), (31, 23)) + rect.center = surface.get_rect().center + + # As the lines get thicker the internals of the ellipse are not + # cleanly defined. So only test up to a few thicknesses before the + # maximum thickness. + for thickness in range(1, min(*rect.size) // 2 - 2): + surface.fill(surface_color) # Clear for each test. + + self.draw_ellipse(surface, ellipse_color, rect, thickness) + + surface.lock() # For possible speed up. + + # Check vertical thickness on the ellipse's top. + x = rect.centerx + y_start = rect.top + y_end = rect.top + thickness - 1 + + for y in range(y_start, y_end + 1): + self.assertEqual(surface.get_at((x, y)), ellipse_color, thickness) + + # Check pixels above and below this line. + self.assertEqual(surface.get_at((x, y_start - 1)), surface_color, thickness) + self.assertEqual(surface.get_at((x, y_end + 1)), surface_color, thickness) + + # Check vertical thickness on the ellipse's bottom. + x = rect.centerx + y_start = rect.bottom - thickness + y_end = rect.bottom - 1 + + for y in range(y_start, y_end + 1): + self.assertEqual(surface.get_at((x, y)), ellipse_color, thickness) + + # Check pixels above and below this line. + self.assertEqual(surface.get_at((x, y_start - 1)), surface_color, thickness) + self.assertEqual(surface.get_at((x, y_end + 1)), surface_color, thickness) + + # Check horizontal thickness on the ellipse's left. + x_start = rect.left + x_end = rect.left + thickness - 1 + y = rect.centery + + for x in range(x_start, x_end + 1): + self.assertEqual(surface.get_at((x, y)), ellipse_color, thickness) + + # Check pixels to the left and right of this line. + self.assertEqual(surface.get_at((x_start - 1, y)), surface_color, thickness) + self.assertEqual(surface.get_at((x_end + 1, y)), surface_color, thickness) + + # Check horizontal thickness on the ellipse's right. + x_start = rect.right - thickness + x_end = rect.right - 1 + y = rect.centery + + for x in range(x_start, x_end + 1): + self.assertEqual(surface.get_at((x, y)), ellipse_color, thickness) + + # Check pixels to the left and right of this line. + self.assertEqual(surface.get_at((x_start - 1, y)), surface_color, thickness) + self.assertEqual(surface.get_at((x_end + 1, y)), surface_color, thickness) + + surface.unlock() + + def test_ellipse__no_holes(self): + width = 80 + height = 70 + surface = pygame.Surface((width + 1, height)) + rect = pygame.Rect(0, 0, width, height) + for thickness in range(1, 37, 5): + surface.fill("BLACK") + self.draw_ellipse(surface, "RED", rect, thickness) + for y in range(height): + number_of_changes = 0 + drawn_pixel = False + for x in range(width + 1): + if ( + not drawn_pixel + and surface.get_at((x, y)) == pygame.Color("RED") + or drawn_pixel + and surface.get_at((x, y)) == pygame.Color("BLACK") + ): + drawn_pixel = not drawn_pixel + number_of_changes += 1 + if y < thickness or y > height - thickness - 1: + self.assertEqual(number_of_changes, 2) + else: + self.assertEqual(number_of_changes, 4) + + def test_ellipse__max_width(self): + """Ensures an ellipse with max width (and greater) is drawn correctly.""" + ellipse_color = pygame.Color("yellow") + surface_color = pygame.Color("black") + surface = pygame.Surface((40, 40)) + rect = pygame.Rect((0, 0), (31, 21)) + rect.center = surface.get_rect().center + max_thickness = (min(*rect.size) + 1) // 2 + + for thickness in range(max_thickness, max_thickness + 3): + surface.fill(surface_color) # Clear for each test. + + self.draw_ellipse(surface, ellipse_color, rect, thickness) + + surface.lock() # For possible speed up. + + # Check vertical thickness. + for y in range(rect.top, rect.bottom): + self.assertEqual(surface.get_at((rect.centerx, y)), ellipse_color) + + # Check horizontal thickness. + for x in range(rect.left, rect.right): + self.assertEqual(surface.get_at((x, rect.centery)), ellipse_color) + + # Check pixels above and below ellipse. + self.assertEqual( + surface.get_at((rect.centerx, rect.top - 1)), surface_color + ) + self.assertEqual( + surface.get_at((rect.centerx, rect.bottom + 1)), surface_color + ) + + # Check pixels to the left and right of the ellipse. + self.assertEqual( + surface.get_at((rect.left - 1, rect.centery)), surface_color + ) + self.assertEqual( + surface.get_at((rect.right + 1, rect.centery)), surface_color + ) + + surface.unlock() + + def _check_1_pixel_sized_ellipse( + self, surface, collide_rect, surface_color, ellipse_color + ): + # Helper method to check the surface for 1 pixel wide and/or high + # ellipses. + surf_w, surf_h = surface.get_size() + + surface.lock() # For possible speed up. + + for pos in ((x, y) for y in range(surf_h) for x in range(surf_w)): + # Since the ellipse is just a line we can use a rect to help find + # where it is expected to be drawn. + if collide_rect.collidepoint(pos): + expected_color = ellipse_color + else: + expected_color = surface_color + + self.assertEqual( + surface.get_at(pos), + expected_color, + "collide_rect={}, pos={}".format(collide_rect, pos), + ) + + surface.unlock() + + def test_ellipse__1_pixel_width(self): + """Ensures an ellipse with a width of 1 is drawn correctly. + + An ellipse with a width of 1 pixel is a vertical line. + """ + ellipse_color = pygame.Color("red") + surface_color = pygame.Color("black") + surf_w, surf_h = 10, 20 + + surface = pygame.Surface((surf_w, surf_h)) + rect = pygame.Rect((0, 0), (1, 0)) + collide_rect = rect.copy() + + # Calculate some positions. + off_left = -1 + off_right = surf_w + off_bottom = surf_h + center_x = surf_w // 2 + center_y = surf_h // 2 + + # Test some even and odd heights. + for ellipse_h in range(6, 10): + collide_rect.h = ellipse_h + rect.h = ellipse_h + + # Calculate some variable positions. + off_top = -(ellipse_h + 1) + half_off_top = -(ellipse_h // 2) + half_off_bottom = surf_h - (ellipse_h // 2) + + # Draw the ellipse in different positions: fully on-surface, + # partially off-surface, and fully off-surface. + positions = ( + (off_left, off_top), + (off_left, half_off_top), + (off_left, center_y), + (off_left, half_off_bottom), + (off_left, off_bottom), + (center_x, off_top), + (center_x, half_off_top), + (center_x, center_y), + (center_x, half_off_bottom), + (center_x, off_bottom), + (off_right, off_top), + (off_right, half_off_top), + (off_right, center_y), + (off_right, half_off_bottom), + (off_right, off_bottom), + ) + + for rect_pos in positions: + surface.fill(surface_color) # Clear before each draw. + rect.topleft = rect_pos + collide_rect.topleft = rect_pos + + self.draw_ellipse(surface, ellipse_color, rect) + + self._check_1_pixel_sized_ellipse( + surface, collide_rect, surface_color, ellipse_color + ) + + def test_ellipse__1_pixel_width_spanning_surface(self): + """Ensures an ellipse with a width of 1 is drawn correctly + when spanning the height of the surface. + + An ellipse with a width of 1 pixel is a vertical line. + """ + ellipse_color = pygame.Color("red") + surface_color = pygame.Color("black") + surf_w, surf_h = 10, 20 + + surface = pygame.Surface((surf_w, surf_h)) + rect = pygame.Rect((0, 0), (1, surf_h + 2)) # Longer than the surface. + + # Draw the ellipse in different positions: on-surface and off-surface. + positions = ( + (-1, -1), # (off_left, off_top) + (0, -1), # (left_edge, off_top) + (surf_w // 2, -1), # (center_x, off_top) + (surf_w - 1, -1), # (right_edge, off_top) + (surf_w, -1), + ) # (off_right, off_top) + + for rect_pos in positions: + surface.fill(surface_color) # Clear before each draw. + rect.topleft = rect_pos + + self.draw_ellipse(surface, ellipse_color, rect) + + self._check_1_pixel_sized_ellipse( + surface, rect, surface_color, ellipse_color + ) + + def test_ellipse__1_pixel_height(self): + """Ensures an ellipse with a height of 1 is drawn correctly. + + An ellipse with a height of 1 pixel is a horizontal line. + """ + ellipse_color = pygame.Color("red") + surface_color = pygame.Color("black") + surf_w, surf_h = 20, 10 + + surface = pygame.Surface((surf_w, surf_h)) + rect = pygame.Rect((0, 0), (0, 1)) + collide_rect = rect.copy() + + # Calculate some positions. + off_right = surf_w + off_top = -1 + off_bottom = surf_h + center_x = surf_w // 2 + center_y = surf_h // 2 + + # Test some even and odd widths. + for ellipse_w in range(6, 10): + collide_rect.w = ellipse_w + rect.w = ellipse_w + + # Calculate some variable positions. + off_left = -(ellipse_w + 1) + half_off_left = -(ellipse_w // 2) + half_off_right = surf_w - (ellipse_w // 2) + + # Draw the ellipse in different positions: fully on-surface, + # partially off-surface, and fully off-surface. + positions = ( + (off_left, off_top), + (half_off_left, off_top), + (center_x, off_top), + (half_off_right, off_top), + (off_right, off_top), + (off_left, center_y), + (half_off_left, center_y), + (center_x, center_y), + (half_off_right, center_y), + (off_right, center_y), + (off_left, off_bottom), + (half_off_left, off_bottom), + (center_x, off_bottom), + (half_off_right, off_bottom), + (off_right, off_bottom), + ) + + for rect_pos in positions: + surface.fill(surface_color) # Clear before each draw. + rect.topleft = rect_pos + collide_rect.topleft = rect_pos + + self.draw_ellipse(surface, ellipse_color, rect) + + self._check_1_pixel_sized_ellipse( + surface, collide_rect, surface_color, ellipse_color + ) + + def test_ellipse__1_pixel_height_spanning_surface(self): + """Ensures an ellipse with a height of 1 is drawn correctly + when spanning the width of the surface. + + An ellipse with a height of 1 pixel is a horizontal line. + """ + ellipse_color = pygame.Color("red") + surface_color = pygame.Color("black") + surf_w, surf_h = 20, 10 + + surface = pygame.Surface((surf_w, surf_h)) + rect = pygame.Rect((0, 0), (surf_w + 2, 1)) # Wider than the surface. + + # Draw the ellipse in different positions: on-surface and off-surface. + positions = ( + (-1, -1), # (off_left, off_top) + (-1, 0), # (off_left, top_edge) + (-1, surf_h // 2), # (off_left, center_y) + (-1, surf_h - 1), # (off_left, bottom_edge) + (-1, surf_h), + ) # (off_left, off_bottom) + + for rect_pos in positions: + surface.fill(surface_color) # Clear before each draw. + rect.topleft = rect_pos + + self.draw_ellipse(surface, ellipse_color, rect) + + self._check_1_pixel_sized_ellipse( + surface, rect, surface_color, ellipse_color + ) + + def test_ellipse__1_pixel_width_and_height(self): + """Ensures an ellipse with a width and height of 1 is drawn correctly. + + An ellipse with a width and height of 1 pixel is a single pixel. + """ + ellipse_color = pygame.Color("red") + surface_color = pygame.Color("black") + surf_w, surf_h = 10, 10 + + surface = pygame.Surface((surf_w, surf_h)) + rect = pygame.Rect((0, 0), (1, 1)) + + # Calculate some positions. + off_left = -1 + off_right = surf_w + off_top = -1 + off_bottom = surf_h + left_edge = 0 + right_edge = surf_w - 1 + top_edge = 0 + bottom_edge = surf_h - 1 + center_x = surf_w // 2 + center_y = surf_h // 2 + + # Draw the ellipse in different positions: center surface, + # top/bottom/left/right edges, and off-surface. + positions = ( + (off_left, off_top), + (off_left, top_edge), + (off_left, center_y), + (off_left, bottom_edge), + (off_left, off_bottom), + (left_edge, off_top), + (left_edge, top_edge), + (left_edge, center_y), + (left_edge, bottom_edge), + (left_edge, off_bottom), + (center_x, off_top), + (center_x, top_edge), + (center_x, center_y), + (center_x, bottom_edge), + (center_x, off_bottom), + (right_edge, off_top), + (right_edge, top_edge), + (right_edge, center_y), + (right_edge, bottom_edge), + (right_edge, off_bottom), + (off_right, off_top), + (off_right, top_edge), + (off_right, center_y), + (off_right, bottom_edge), + (off_right, off_bottom), + ) + + for rect_pos in positions: + surface.fill(surface_color) # Clear before each draw. + rect.topleft = rect_pos + + self.draw_ellipse(surface, ellipse_color, rect) + + self._check_1_pixel_sized_ellipse( + surface, rect, surface_color, ellipse_color + ) + + def test_ellipse__bounding_rect(self): + """Ensures draw ellipse returns the correct bounding rect. + + Tests ellipses on and off the surface and a range of width/thickness + values. + """ + ellipse_color = pygame.Color("red") + surf_color = pygame.Color("black") + min_width = min_height = 5 + max_width = max_height = 7 + sizes = ((min_width, min_height), (max_width, max_height)) + surface = pygame.Surface((20, 20), 0, 32) + surf_rect = surface.get_rect() + # Make a rect that is bigger than the surface to help test drawing + # ellipses off and partially off the surface. + big_rect = surf_rect.inflate(min_width * 2 + 1, min_height * 2 + 1) + + for pos in rect_corners_mids_and_center( + surf_rect + ) + rect_corners_mids_and_center(big_rect): + # Each of the ellipse's rect position attributes will be set to + # the pos value. + for attr in RECT_POSITION_ATTRIBUTES: + # Test using different rect sizes and thickness values. + for width, height in sizes: + ellipse_rect = pygame.Rect((0, 0), (width, height)) + setattr(ellipse_rect, attr, pos) + + for thickness in (0, 1, 2, 3, min(width, height)): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_ellipse( + surface, ellipse_color, ellipse_rect, thickness + ) + + # Calculating the expected_rect after the ellipse + # is drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect( + surface, surf_color, ellipse_rect.topleft + ) + + self.assertEqual(bounding_rect, expected_rect) + + def test_ellipse__surface_clip(self): + """Ensures draw ellipse respects a surface's clip area. + + Tests drawing the ellipse filled and unfilled. + """ + surfw = surfh = 30 + ellipse_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (11, 11)) + clip_rect.center = surface.get_rect().center + pos_rect = clip_rect.copy() # Manages the ellipse's pos. + + for width in (0, 1): # Filled and unfilled. + # Test centering the ellipse along the clip rect's edge. + for center in rect_corners_mids_and_center(clip_rect): + # Get the expected points by drawing the ellipse without the + # clip area set. + pos_rect.center = center + surface.set_clip(None) + surface.fill(surface_color) + self.draw_ellipse(surface, ellipse_color, pos_rect, width) + expected_pts = get_color_points(surface, ellipse_color, clip_rect) + + # Clear the surface and set the clip area. Redraw the ellipse + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_ellipse(surface, ellipse_color, pos_rect, width) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure only the expected_pts + # are the ellipse_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + expected_color = ellipse_color + else: + expected_color = surface_color + + self.assertEqual(surface.get_at(pt), expected_color, pt) + + surface.unlock() + + +class DrawEllipseTest(DrawEllipseMixin, DrawTestCase): + """Test draw module function ellipse. + + This class inherits the general tests from DrawEllipseMixin. It is also + the class to add any draw.ellipse specific tests to. + """ + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever properly supports drawing ellipses. +# @unittest.skip('draw_py.draw_ellipse not supported yet') +# class PythonDrawEllipseTest(DrawEllipseMixin, PythonDrawTestCase): +# """Test draw_py module function draw_ellipse. +# +# This class inherits the general tests from DrawEllipseMixin. It is also +# the class to add any draw_py.draw_ellipse specific tests to. +# """ + + +### Line/Lines/AALine/AALines Testing ######################################### + + +class BaseLineMixin(object): + """Mixin base for drawing various lines. + + This class contains general helper methods and setup for testing the + different types of lines. + """ + + COLORS = ( + (0, 0, 0), + (255, 0, 0), + (0, 255, 0), + (0, 0, 255), + (255, 255, 0), + (255, 0, 255), + (0, 255, 255), + (255, 255, 255), + ) + + @staticmethod + def _create_surfaces(): + # Create some surfaces with different sizes, depths, and flags. + surfaces = [] + for size in ((49, 49), (50, 50)): + for depth in (8, 16, 24, 32): + for flags in (0, SRCALPHA): + surface = pygame.display.set_mode(size, flags, depth) + surfaces.append(surface) + surfaces.append(surface.convert_alpha()) + return surfaces + + @staticmethod + def _rect_lines(rect): + # Yields pairs of end points and their reverse (to test symmetry). + # Uses a rect with the points radiating from its midleft. + for pt in rect_corners_mids_and_center(rect): + if pt in [rect.midleft, rect.center]: + # Don't bother with these points. + continue + yield (rect.midleft, pt) + yield (pt, rect.midleft) + + +### Line Testing ############################################################## + + +class LineMixin(BaseLineMixin): + """Mixin test for drawing a single line. + + This class contains all the general single line drawing tests. + """ + + def test_line__args(self): + """Ensures draw line accepts the correct args.""" + bounds_rect = self.draw_line( + pygame.Surface((3, 3)), (0, 10, 0, 50), (0, 0), (1, 1), 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__args_without_width(self): + """Ensures draw line accepts the args without a width.""" + bounds_rect = self.draw_line( + pygame.Surface((2, 2)), (0, 0, 0, 50), (0, 0), (2, 2) + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__kwargs(self): + """Ensures draw line accepts the correct kwargs + with and without a width arg. + """ + surface = pygame.Surface((4, 4)) + color = pygame.Color("yellow") + start_pos = (1, 1) + end_pos = (2, 2) + kwargs_list = [ + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "width": 1, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + }, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_line(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__kwargs_order_independent(self): + """Ensures draw line's kwargs are not order dependent.""" + bounds_rect = self.draw_line( + start_pos=(1, 2), + end_pos=(2, 1), + width=2, + color=(10, 20, 30), + surface=pygame.Surface((3, 2)), + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__args_missing(self): + """Ensures draw line detects any missing required args.""" + surface = pygame.Surface((1, 1)) + color = pygame.Color("blue") + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(surface, color, (0, 0)) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(surface, color) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line() + + def test_line__kwargs_missing(self): + """Ensures draw line detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((3, 2)), + "color": pygame.Color("red"), + "start_pos": (2, 1), + "end_pos": (2, 2), + "width": 1, + } + + for name in ("end_pos", "start_pos", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(**invalid_kwargs) + + def test_line__arg_invalid_types(self): + """Ensures draw line detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + start_pos = (0, 1) + end_pos = (1, 2) + + with self.assertRaises(TypeError): + # Invalid width. + bounds_rect = self.draw_line(surface, color, start_pos, end_pos, "1") + + with self.assertRaises(TypeError): + # Invalid end_pos. + bounds_rect = self.draw_line(surface, color, start_pos, (1, 2, 3)) + + with self.assertRaises(TypeError): + # Invalid start_pos. + bounds_rect = self.draw_line(surface, color, (1,), end_pos) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_line(surface, 2.3, start_pos, end_pos) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_line((1, 2, 3, 4), color, start_pos, end_pos) + + def test_line__kwarg_invalid_types(self): + """Ensures draw line detects invalid kwarg types.""" + surface = pygame.Surface((3, 3)) + color = pygame.Color("green") + start_pos = (1, 0) + end_pos = (2, 0) + width = 1 + kwargs_list = [ + { + "surface": pygame.Surface, # Invalid surface. + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "width": width, + }, + { + "surface": surface, + "color": 2.3, # Invalid color. + "start_pos": start_pos, + "end_pos": end_pos, + "width": width, + }, + { + "surface": surface, + "color": color, + "start_pos": (0, 0, 0), # Invalid start_pos. + "end_pos": end_pos, + "width": width, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": (0,), # Invalid end_pos. + "width": width, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "width": 1.2, + }, + ] # Invalid width. + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(**kwargs) + + def test_line__kwarg_invalid_name(self): + """Ensures draw line detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + start_pos = (1, 1) + end_pos = (2, 0) + kwargs_list = [ + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "width": 1, + "invalid": 1, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "invalid": 1, + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(**kwargs) + + def test_line__args_and_kwargs(self): + """Ensures draw line accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 2)) + color = (255, 255, 0, 0) + start_pos = (0, 1) + end_pos = (1, 2) + width = 0 + kwargs = { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "width": width, + } + + for name in ("surface", "color", "start_pos", "end_pos", "width"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_line(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_line(surface, color, **kwargs) + elif "start_pos" == name: + bounds_rect = self.draw_line(surface, color, start_pos, **kwargs) + elif "end_pos" == name: + bounds_rect = self.draw_line( + surface, color, start_pos, end_pos, **kwargs + ) + else: + bounds_rect = self.draw_line( + surface, color, start_pos, end_pos, width, **kwargs + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__valid_width_values(self): + """Ensures draw line accepts different width values.""" + line_color = pygame.Color("yellow") + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + pos = (2, 1) + kwargs = { + "surface": surface, + "color": line_color, + "start_pos": pos, + "end_pos": (2, 2), + "width": None, + } + + for width in (-100, -10, -1, 0, 1, 10, 100): + surface.fill(surface_color) # Clear for each test. + kwargs["width"] = width + expected_color = line_color if width > 0 else surface_color + + bounds_rect = self.draw_line(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__valid_start_pos_formats(self): + """Ensures draw line accepts different start_pos formats.""" + expected_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((4, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "start_pos": None, + "end_pos": (2, 2), + "width": 2, + } + x, y = 2, 1 # start position + + # The point values can be ints or floats. + for start_pos in ((x, y), (x + 0.1, y), (x, y + 0.1), (x + 0.1, y + 0.1)): + # The point type can be a tuple/list/Vector2. + for seq_type in (tuple, list, Vector2): + surface.fill(surface_color) # Clear for each test. + kwargs["start_pos"] = seq_type(start_pos) + + bounds_rect = self.draw_line(**kwargs) + + self.assertEqual(surface.get_at((x, y)), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__valid_end_pos_formats(self): + """Ensures draw line accepts different end_pos formats.""" + expected_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((4, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "start_pos": (2, 1), + "end_pos": None, + "width": 2, + } + x, y = 2, 2 # end position + + # The point values can be ints or floats. + for end_pos in ((x, y), (x + 0.2, y), (x, y + 0.2), (x + 0.2, y + 0.2)): + # The point type can be a tuple/list/Vector2. + for seq_type in (tuple, list, Vector2): + surface.fill(surface_color) # Clear for each test. + kwargs["end_pos"] = seq_type(end_pos) + + bounds_rect = self.draw_line(**kwargs) + + self.assertEqual(surface.get_at((x, y)), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__invalid_start_pos_formats(self): + """Ensures draw line handles invalid start_pos formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "start_pos": None, + "end_pos": (2, 2), + "width": 1, + } + + start_pos_fmts = ( + (2,), # Too few coords. + (2, 1, 0), # Too many coords. + (2, "1"), # Wrong type. + set([2, 1]), # Wrong type. + dict(((2, 1),)), + ) # Wrong type. + + for start_pos in start_pos_fmts: + kwargs["start_pos"] = start_pos + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(**kwargs) + + def test_line__invalid_end_pos_formats(self): + """Ensures draw line handles invalid end_pos formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "start_pos": (2, 2), + "end_pos": None, + "width": 1, + } + + end_pos_fmts = ( + (2,), # Too few coords. + (2, 1, 0), # Too many coords. + (2, "1"), # Wrong type. + set([2, 1]), # Wrong type. + dict(((2, 1),)), + ) # Wrong type. + + for end_pos in end_pos_fmts: + kwargs["end_pos"] = end_pos + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(**kwargs) + + def test_line__valid_color_formats(self): + """Ensures draw line accepts different color formats.""" + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + pos = (1, 1) + kwargs = { + "surface": surface, + "color": None, + "start_pos": pos, + "end_pos": (2, 1), + "width": 3, + } + greens = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in greens: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_line(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_line__invalid_color_formats(self): + """Ensures draw line handles invalid color formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 3)), + "color": None, + "start_pos": (1, 1), + "end_pos": (2, 1), + "width": 1, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_line(**kwargs) + + def test_line__color(self): + """Tests if the line drawn is the correct color.""" + pos = (0, 0) + for surface in self._create_surfaces(): + for expected_color in self.COLORS: + self.draw_line(surface, expected_color, pos, (1, 0)) + + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + + def test_line__color_with_thickness(self): + """Ensures a thick line is drawn using the correct color.""" + from_x = 5 + to_x = 10 + y = 5 + for surface in self._create_surfaces(): + for expected_color in self.COLORS: + self.draw_line(surface, expected_color, (from_x, y), (to_x, y), 5) + for pos in ((x, y + i) for i in (-2, 0, 2) for x in (from_x, to_x)): + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + + def test_line__gaps(self): + """Tests if the line drawn contains any gaps.""" + expected_color = (255, 255, 255) + for surface in self._create_surfaces(): + width = surface.get_width() + self.draw_line(surface, expected_color, (0, 0), (width - 1, 0)) + + for x in range(width): + pos = (x, 0) + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + + def test_line__gaps_with_thickness(self): + """Ensures a thick line is drawn without any gaps.""" + expected_color = (255, 255, 255) + thickness = 5 + for surface in self._create_surfaces(): + width = surface.get_width() - 1 + h = width // 5 + w = h * 5 + self.draw_line(surface, expected_color, (0, 5), (w, 5 + h), thickness) + + for x in range(w + 1): + for y in range(3, 8): + pos = (x, y + ((x + 2) // 5)) + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + + def test_line__bounding_rect(self): + """Ensures draw line returns the correct bounding rect. + + Tests lines with endpoints on and off the surface and a range of + width/thickness values. + """ + if isinstance(self, PythonDrawTestCase): + self.skipTest("bounding rects not supported in draw_py.draw_line") + + line_color = pygame.Color("red") + surf_color = pygame.Color("black") + width = height = 30 + # Using a rect to help manage where the lines are drawn. + helper_rect = pygame.Rect((0, 0), (width, height)) + + # Testing surfaces of different sizes. One larger than the helper_rect + # and one smaller (to test lines that span the surface). + for size in ((width + 5, height + 5), (width - 5, height - 5)): + surface = pygame.Surface(size, 0, 32) + surf_rect = surface.get_rect() + + # Move the helper rect to different positions to test line + # endpoints on and off the surface. + for pos in rect_corners_mids_and_center(surf_rect): + helper_rect.center = pos + + # Draw using different thicknesses. + for thickness in range(-1, 5): + for start, end in self._rect_lines(helper_rect): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_line( + surface, line_color, start, end, thickness + ) + + if 0 < thickness: + # Calculating the expected_rect after the line is + # drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect( + surface, surf_color, start + ) + else: + # Nothing drawn. + expected_rect = pygame.Rect(start, (0, 0)) + + self.assertEqual( + bounding_rect, + expected_rect, + "start={}, end={}, size={}, thickness={}".format( + start, end, size, thickness + ), + ) + + def test_line__surface_clip(self): + """Ensures draw line respects a surface's clip area.""" + surfw = surfh = 30 + line_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (11, 11)) + clip_rect.center = surface.get_rect().center + pos_rect = clip_rect.copy() # Manages the line's pos. + + for thickness in (1, 3): # Test different line widths. + # Test centering the line along the clip rect's edge. + for center in rect_corners_mids_and_center(clip_rect): + # Get the expected points by drawing the line without the + # clip area set. + pos_rect.center = center + surface.set_clip(None) + surface.fill(surface_color) + self.draw_line( + surface, line_color, pos_rect.midtop, pos_rect.midbottom, thickness + ) + expected_pts = get_color_points(surface, line_color, clip_rect) + + # Clear the surface and set the clip area. Redraw the line + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_line( + surface, line_color, pos_rect.midtop, pos_rect.midbottom, thickness + ) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure only the expected_pts + # are the line_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + expected_color = line_color + else: + expected_color = surface_color + + self.assertEqual(surface.get_at(pt), expected_color, pt) + + surface.unlock() + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever fully supports drawing single lines. +# @unittest.skip('draw_py.draw_line not fully supported yet') +# class PythonDrawLineTest(LineMixin, PythonDrawTestCase): +# """Test draw_py module function line. +# +# This class inherits the general tests from LineMixin. It is also the class +# to add any draw_py.draw_line specific tests to. +# """ + + +class DrawLineTest(LineMixin, DrawTestCase): + """Test draw module function line. + + This class inherits the general tests from LineMixin. It is also the class + to add any draw.line specific tests to. + """ + + def test_line_endianness(self): + """test color component order""" + for depth in (24, 32): + surface = pygame.Surface((5, 3), 0, depth) + surface.fill(pygame.Color(0, 0, 0)) + self.draw_line(surface, pygame.Color(255, 0, 0), (0, 1), (2, 1), 1) + + self.assertGreater(surface.get_at((1, 1)).r, 0, "there should be red here") + + surface.fill(pygame.Color(0, 0, 0)) + self.draw_line(surface, pygame.Color(0, 0, 255), (0, 1), (2, 1), 1) + + self.assertGreater(surface.get_at((1, 1)).b, 0, "there should be blue here") + + def test_line(self): + # (l, t), (l, t) + self.surf_size = (320, 200) + self.surf = pygame.Surface(self.surf_size, pygame.SRCALPHA) + self.color = (1, 13, 24, 205) + + drawn = draw.line(self.surf, self.color, (1, 0), (200, 0)) + self.assertEqual( + drawn.right, 201, "end point arg should be (or at least was) inclusive" + ) + + # Should be colored where it's supposed to be + for pt in test_utils.rect_area_pts(drawn): + self.assertEqual(self.surf.get_at(pt), self.color) + + # And not where it shouldn't + for pt in test_utils.rect_outer_bounds(drawn): + self.assertNotEqual(self.surf.get_at(pt), self.color) + + # Line width greater that 1 + line_width = 2 + offset = 5 + a = (offset, offset) + b = (self.surf_size[0] - offset, a[1]) + c = (a[0], self.surf_size[1] - offset) + d = (b[0], c[1]) + e = (a[0] + offset, c[1]) + f = (b[0], c[0] + 5) + lines = [ + (a, d), + (b, c), + (c, b), + (d, a), + (a, b), + (b, a), + (a, c), + (c, a), + (a, e), + (e, a), + (a, f), + (f, a), + (a, a), + ] + + for p1, p2 in lines: + msg = "%s - %s" % (p1, p2) + if p1[0] <= p2[0]: + plow = p1 + phigh = p2 + else: + plow = p2 + phigh = p1 + + self.surf.fill((0, 0, 0)) + rec = draw.line(self.surf, (255, 255, 255), p1, p2, line_width) + xinc = yinc = 0 + + if abs(p1[0] - p2[0]) > abs(p1[1] - p2[1]): + yinc = 1 + else: + xinc = 1 + + for i in range(line_width): + p = (p1[0] + xinc * i, p1[1] + yinc * i) + self.assertEqual(self.surf.get_at(p), (255, 255, 255), msg) + + p = (p2[0] + xinc * i, p2[1] + yinc * i) + self.assertEqual(self.surf.get_at(p), (255, 255, 255), msg) + + p = (plow[0] - 1, plow[1]) + self.assertEqual(self.surf.get_at(p), (0, 0, 0), msg) + + p = (plow[0] + xinc * line_width, plow[1] + yinc * line_width) + self.assertEqual(self.surf.get_at(p), (0, 0, 0), msg) + + p = (phigh[0] + xinc * line_width, phigh[1] + yinc * line_width) + self.assertEqual(self.surf.get_at(p), (0, 0, 0), msg) + + if p1[0] < p2[0]: + rx = p1[0] + else: + rx = p2[0] + + if p1[1] < p2[1]: + ry = p1[1] + else: + ry = p2[1] + + w = abs(p2[0] - p1[0]) + 1 + xinc * (line_width - 1) + h = abs(p2[1] - p1[1]) + 1 + yinc * (line_width - 1) + msg += ", %s" % (rec,) + + self.assertEqual(rec, (rx, ry, w, h), msg) + + def test_line_for_gaps(self): + # This checks bug Thick Line Bug #448 + + width = 200 + height = 200 + surf = pygame.Surface((width, height), pygame.SRCALPHA) + + def white_surrounded_pixels(x, y): + offsets = [(1, 0), (0, 1), (-1, 0), (0, -1)] + WHITE = (255, 255, 255, 255) + return len( + [1 for dx, dy in offsets if surf.get_at((x + dx, y + dy)) == WHITE] + ) + + def check_white_line(start, end): + surf.fill((0, 0, 0)) + pygame.draw.line(surf, (255, 255, 255), start, end, 30) + + BLACK = (0, 0, 0, 255) + for x in range(1, width - 1): + for y in range(1, height - 1): + if surf.get_at((x, y)) == BLACK: + self.assertTrue(white_surrounded_pixels(x, y) < 3) + + check_white_line((50, 50), (140, 0)) + check_white_line((50, 50), (0, 120)) + check_white_line((50, 50), (199, 198)) + + +### Lines Testing ############################################################# + + +class LinesMixin(BaseLineMixin): + """Mixin test for drawing lines. + + This class contains all the general lines drawing tests. + """ + + def test_lines__args(self): + """Ensures draw lines accepts the correct args.""" + bounds_rect = self.draw_lines( + pygame.Surface((3, 3)), (0, 10, 0, 50), False, ((0, 0), (1, 1)), 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__args_without_width(self): + """Ensures draw lines accepts the args without a width.""" + bounds_rect = self.draw_lines( + pygame.Surface((2, 2)), (0, 0, 0, 50), False, ((0, 0), (1, 1)) + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__kwargs(self): + """Ensures draw lines accepts the correct kwargs + with and without a width arg. + """ + surface = pygame.Surface((4, 4)) + color = pygame.Color("yellow") + points = ((0, 0), (1, 1), (2, 2)) + kwargs_list = [ + { + "surface": surface, + "color": color, + "closed": False, + "points": points, + "width": 1, + }, + {"surface": surface, "color": color, "closed": False, "points": points}, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_lines(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__kwargs_order_independent(self): + """Ensures draw lines's kwargs are not order dependent.""" + bounds_rect = self.draw_lines( + closed=1, + points=((0, 0), (1, 1), (2, 2)), + width=2, + color=(10, 20, 30), + surface=pygame.Surface((3, 2)), + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__args_missing(self): + """Ensures draw lines detects any missing required args.""" + surface = pygame.Surface((1, 1)) + color = pygame.Color("blue") + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(surface, color, 0) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(surface, color) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines() + + def test_lines__kwargs_missing(self): + """Ensures draw lines detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((3, 2)), + "color": pygame.Color("red"), + "closed": 1, + "points": ((2, 2), (1, 1)), + "width": 1, + } + + for name in ("points", "closed", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(**invalid_kwargs) + + def test_lines__arg_invalid_types(self): + """Ensures draw lines detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + closed = 0 + points = ((1, 2), (2, 1)) + + with self.assertRaises(TypeError): + # Invalid width. + bounds_rect = self.draw_lines(surface, color, closed, points, "1") + + with self.assertRaises(TypeError): + # Invalid points. + bounds_rect = self.draw_lines(surface, color, closed, (1, 2, 3)) + + with self.assertRaises(TypeError): + # Invalid closed. + bounds_rect = self.draw_lines(surface, color, InvalidBool(), points) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_lines(surface, 2.3, closed, points) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_lines((1, 2, 3, 4), color, closed, points) + + def test_lines__kwarg_invalid_types(self): + """Ensures draw lines detects invalid kwarg types.""" + valid_kwargs = { + "surface": pygame.Surface((3, 3)), + "color": pygame.Color("green"), + "closed": False, + "points": ((1, 2), (2, 1)), + "width": 1, + } + + invalid_kwargs = { + "surface": pygame.Surface, + "color": 2.3, + "closed": InvalidBool(), + "points": (0, 0, 0), + "width": 1.2, + } + + for kwarg in ("surface", "color", "closed", "points", "width"): + kwargs = dict(valid_kwargs) + kwargs[kwarg] = invalid_kwargs[kwarg] + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(**kwargs) + + def test_lines__kwarg_invalid_name(self): + """Ensures draw lines detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + closed = 1 + points = ((1, 2), (2, 1)) + kwargs_list = [ + { + "surface": surface, + "color": color, + "closed": closed, + "points": points, + "width": 1, + "invalid": 1, + }, + { + "surface": surface, + "color": color, + "closed": closed, + "points": points, + "invalid": 1, + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(**kwargs) + + def test_lines__args_and_kwargs(self): + """Ensures draw lines accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 2)) + color = (255, 255, 0, 0) + closed = 0 + points = ((1, 2), (2, 1)) + width = 1 + kwargs = { + "surface": surface, + "color": color, + "closed": closed, + "points": points, + "width": width, + } + + for name in ("surface", "color", "closed", "points", "width"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_lines(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_lines(surface, color, **kwargs) + elif "closed" == name: + bounds_rect = self.draw_lines(surface, color, closed, **kwargs) + elif "points" == name: + bounds_rect = self.draw_lines(surface, color, closed, points, **kwargs) + else: + bounds_rect = self.draw_lines( + surface, color, closed, points, width, **kwargs + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__valid_width_values(self): + """Ensures draw lines accepts different width values.""" + line_color = pygame.Color("yellow") + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + pos = (1, 1) + kwargs = { + "surface": surface, + "color": line_color, + "closed": False, + "points": (pos, (2, 1)), + "width": None, + } + + for width in (-100, -10, -1, 0, 1, 10, 100): + surface.fill(surface_color) # Clear for each test. + kwargs["width"] = width + expected_color = line_color if width > 0 else surface_color + + bounds_rect = self.draw_lines(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__valid_points_format(self): + """Ensures draw lines accepts different points formats.""" + expected_color = (10, 20, 30, 255) + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "closed": False, + "points": None, + "width": 1, + } + + # The point type can be a tuple/list/Vector2. + point_types = ( + (tuple, tuple, tuple, tuple), # all tuples + (list, list, list, list), # all lists + (Vector2, Vector2, Vector2, Vector2), # all Vector2s + (list, Vector2, tuple, Vector2), + ) # mix + + # The point values can be ints or floats. + point_values = ( + ((1, 1), (2, 1), (2, 2), (1, 2)), + ((1, 1), (2.2, 1), (2.1, 2.2), (1, 2.1)), + ) + + # Each sequence of points can be a tuple or a list. + seq_types = (tuple, list) + + for point_type in point_types: + for values in point_values: + check_pos = values[0] + points = [point_type[i](pt) for i, pt in enumerate(values)] + + for seq_type in seq_types: + surface.fill(surface_color) # Clear for each test. + kwargs["points"] = seq_type(points) + + bounds_rect = self.draw_lines(**kwargs) + + self.assertEqual(surface.get_at(check_pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__invalid_points_formats(self): + """Ensures draw lines handles invalid points formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "closed": False, + "points": None, + "width": 1, + } + + points_fmts = ( + ((1, 1), (2,)), # Too few coords. + ((1, 1), (2, 2, 2)), # Too many coords. + ((1, 1), (2, "2")), # Wrong type. + ((1, 1), set([2, 3])), # Wrong type. + ((1, 1), dict(((2, 2), (3, 3)))), # Wrong type. + set(((1, 1), (1, 2))), # Wrong type. + dict(((1, 1), (4, 4))), + ) # Wrong type. + + for points in points_fmts: + kwargs["points"] = points + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(**kwargs) + + def test_lines__invalid_points_values(self): + """Ensures draw lines handles invalid points values correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "closed": False, + "points": None, + "width": 1, + } + + for points in ([], ((1, 1),)): # Too few points. + for seq_type in (tuple, list): # Test as tuples and lists. + kwargs["points"] = seq_type(points) + + with self.assertRaises(ValueError): + bounds_rect = self.draw_lines(**kwargs) + + def test_lines__valid_closed_values(self): + """Ensures draw lines accepts different closed values.""" + line_color = pygame.Color("blue") + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + pos = (1, 2) + kwargs = { + "surface": surface, + "color": line_color, + "closed": None, + "points": ((1, 1), (3, 1), (3, 3), (1, 3)), + "width": 1, + } + + true_values = (-7, 1, 10, "2", 3.1, (4,), [5], True) + false_values = (None, "", 0, (), [], False) + + for closed in true_values + false_values: + surface.fill(surface_color) # Clear for each test. + kwargs["closed"] = closed + expected_color = line_color if closed else surface_color + + bounds_rect = self.draw_lines(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__valid_color_formats(self): + """Ensures draw lines accepts different color formats.""" + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + pos = (1, 1) + kwargs = { + "surface": surface, + "color": None, + "closed": False, + "points": (pos, (2, 1)), + "width": 3, + } + greens = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in greens: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_lines(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_lines__invalid_color_formats(self): + """Ensures draw lines handles invalid color formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 3)), + "color": None, + "closed": False, + "points": ((1, 1), (1, 2)), + "width": 1, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_lines(**kwargs) + + def test_lines__color(self): + """Tests if the lines drawn are the correct color. + + Draws lines around the border of the given surface and checks if all + borders of the surface only contain the given color. + """ + for surface in self._create_surfaces(): + for expected_color in self.COLORS: + self.draw_lines(surface, expected_color, True, corners(surface)) + + for pos, color in border_pos_and_color(surface): + self.assertEqual(color, expected_color, "pos={}".format(pos)) + + def test_lines__color_with_thickness(self): + """Ensures thick lines are drawn using the correct color.""" + x_left = y_top = 5 + for surface in self._create_surfaces(): + x_right = surface.get_width() - 5 + y_bottom = surface.get_height() - 5 + endpoints = ( + (x_left, y_top), + (x_right, y_top), + (x_right, y_bottom), + (x_left, y_bottom), + ) + for expected_color in self.COLORS: + self.draw_lines(surface, expected_color, True, endpoints, 3) + + for t in (-1, 0, 1): + for x in range(x_left, x_right + 1): + for y in (y_top, y_bottom): + pos = (x, y + t) + self.assertEqual( + surface.get_at(pos), + expected_color, + "pos={}".format(pos), + ) + for y in range(y_top, y_bottom + 1): + for x in (x_left, x_right): + pos = (x + t, y) + self.assertEqual( + surface.get_at(pos), + expected_color, + "pos={}".format(pos), + ) + + def test_lines__gaps(self): + """Tests if the lines drawn contain any gaps. + + Draws lines around the border of the given surface and checks if + all borders of the surface contain any gaps. + """ + expected_color = (255, 255, 255) + for surface in self._create_surfaces(): + self.draw_lines(surface, expected_color, True, corners(surface)) + + for pos, color in border_pos_and_color(surface): + self.assertEqual(color, expected_color, "pos={}".format(pos)) + + def test_lines__gaps_with_thickness(self): + """Ensures thick lines are drawn without any gaps.""" + expected_color = (255, 255, 255) + x_left = y_top = 5 + for surface in self._create_surfaces(): + h = (surface.get_width() - 11) // 5 + w = h * 5 + x_right = x_left + w + y_bottom = y_top + h + endpoints = ((x_left, y_top), (x_right, y_top), (x_right, y_bottom)) + self.draw_lines(surface, expected_color, True, endpoints, 3) + + for x in range(x_left, x_right + 1): + for t in (-1, 0, 1): + pos = (x, y_top + t) + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + pos = (x, y_top + t + ((x - 3) // 5)) + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + for y in range(y_top, y_bottom + 1): + for t in (-1, 0, 1): + pos = (x_right + t, y) + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + + def test_lines__bounding_rect(self): + """Ensures draw lines returns the correct bounding rect. + + Tests lines with endpoints on and off the surface and a range of + width/thickness values. + """ + line_color = pygame.Color("red") + surf_color = pygame.Color("black") + width = height = 30 + # Using a rect to help manage where the lines are drawn. + pos_rect = pygame.Rect((0, 0), (width, height)) + + # Testing surfaces of different sizes. One larger than the pos_rect + # and one smaller (to test lines that span the surface). + for size in ((width + 5, height + 5), (width - 5, height - 5)): + surface = pygame.Surface(size, 0, 32) + surf_rect = surface.get_rect() + + # Move pos_rect to different positions to test line endpoints on + # and off the surface. + for pos in rect_corners_mids_and_center(surf_rect): + pos_rect.center = pos + # Shape: Triangle (if closed), ^ caret (if not closed). + pts = (pos_rect.midleft, pos_rect.midtop, pos_rect.midright) + pos = pts[0] # Rect position if nothing drawn. + + # Draw using different thickness and closed values. + for thickness in range(-1, 5): + for closed in (True, False): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_lines( + surface, line_color, closed, pts, thickness + ) + + if 0 < thickness: + # Calculating the expected_rect after the lines are + # drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect( + surface, surf_color, pos + ) + else: + # Nothing drawn. + expected_rect = pygame.Rect(pos, (0, 0)) + + self.assertEqual(bounding_rect, expected_rect) + + def test_lines__surface_clip(self): + """Ensures draw lines respects a surface's clip area.""" + surfw = surfh = 30 + line_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (11, 11)) + clip_rect.center = surface.get_rect().center + pos_rect = clip_rect.copy() # Manages the lines's pos. + + # Test centering the pos_rect along the clip rect's edge to allow for + # drawing the lines over the clip_rect's bounds. + for center in rect_corners_mids_and_center(clip_rect): + pos_rect.center = center + pts = (pos_rect.midtop, pos_rect.center, pos_rect.midbottom) + + for closed in (True, False): # Test closed and not closed. + for thickness in (1, 3): # Test different line widths. + # Get the expected points by drawing the lines without the + # clip area set. + surface.set_clip(None) + surface.fill(surface_color) + self.draw_lines(surface, line_color, closed, pts, thickness) + expected_pts = get_color_points(surface, line_color, clip_rect) + + # Clear the surface and set the clip area. Redraw the lines + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_lines(surface, line_color, closed, pts, thickness) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure only the + # expected_pts are the line_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + expected_color = line_color + else: + expected_color = surface_color + + self.assertEqual(surface.get_at(pt), expected_color, pt) + + surface.unlock() + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever fully supports drawing lines. +# class PythonDrawLinesTest(LinesMixin, PythonDrawTestCase): +# """Test draw_py module function lines. +# +# This class inherits the general tests from LinesMixin. It is also the +# class to add any draw_py.draw_lines specific tests to. +# """ + + +class DrawLinesTest(LinesMixin, DrawTestCase): + """Test draw module function lines. + + This class inherits the general tests from LinesMixin. It is also the class + to add any draw.lines specific tests to. + """ + + +### AALine Testing ############################################################ + + +class AALineMixin(BaseLineMixin): + """Mixin test for drawing a single aaline. + + This class contains all the general single aaline drawing tests. + """ + + def test_aaline__args(self): + """Ensures draw aaline accepts the correct args.""" + bounds_rect = self.draw_aaline( + pygame.Surface((3, 3)), (0, 10, 0, 50), (0, 0), (1, 1), 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aaline__args_without_blend(self): + """Ensures draw aaline accepts the args without a blend.""" + bounds_rect = self.draw_aaline( + pygame.Surface((2, 2)), (0, 0, 0, 50), (0, 0), (2, 2) + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aaline__blend_warning(self): + """From pygame 2, blend=False should raise DeprecationWarning.""" + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger DeprecationWarning. + self.draw_aaline( + pygame.Surface((2, 2)), (0, 0, 0, 50), (0, 0), (2, 2), False + ) + # Check if there is only one warning and is a DeprecationWarning. + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) + + def test_aaline__kwargs(self): + """Ensures draw aaline accepts the correct kwargs + with and without a blend arg. + """ + surface = pygame.Surface((4, 4)) + color = pygame.Color("yellow") + start_pos = (1, 1) + end_pos = (2, 2) + kwargs_list = [ + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "blend": 1, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + }, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_aaline(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aaline__kwargs_order_independent(self): + """Ensures draw aaline's kwargs are not order dependent.""" + bounds_rect = self.draw_aaline( + start_pos=(1, 2), + end_pos=(2, 1), + blend=1, + color=(10, 20, 30), + surface=pygame.Surface((3, 2)), + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aaline__args_missing(self): + """Ensures draw aaline detects any missing required args.""" + surface = pygame.Surface((1, 1)) + color = pygame.Color("blue") + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(surface, color, (0, 0)) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(surface, color) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline() + + def test_aaline__kwargs_missing(self): + """Ensures draw aaline detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((3, 2)), + "color": pygame.Color("red"), + "start_pos": (2, 1), + "end_pos": (2, 2), + "blend": 1, + } + + for name in ("end_pos", "start_pos", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(**invalid_kwargs) + + def test_aaline__arg_invalid_types(self): + """Ensures draw aaline detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + start_pos = (0, 1) + end_pos = (1, 2) + + with self.assertRaises(TypeError): + # Invalid blend. + bounds_rect = self.draw_aaline(surface, color, start_pos, end_pos, "1") + + with self.assertRaises(TypeError): + # Invalid end_pos. + bounds_rect = self.draw_aaline(surface, color, start_pos, (1, 2, 3)) + + with self.assertRaises(TypeError): + # Invalid start_pos. + bounds_rect = self.draw_aaline(surface, color, (1,), end_pos) + + with self.assertRaises(ValueError): + # Invalid color. + bounds_rect = self.draw_aaline(surface, "invalid-color", start_pos, end_pos) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_aaline((1, 2, 3, 4), color, start_pos, end_pos) + + def test_aaline__kwarg_invalid_types(self): + """Ensures draw aaline detects invalid kwarg types.""" + surface = pygame.Surface((3, 3)) + color = pygame.Color("green") + start_pos = (1, 0) + end_pos = (2, 0) + blend = 1 + kwargs_list = [ + { + "surface": pygame.Surface, # Invalid surface. + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "blend": blend, + }, + { + "surface": surface, + "color": 2.3, # Invalid color. + "start_pos": start_pos, + "end_pos": end_pos, + "blend": blend, + }, + { + "surface": surface, + "color": color, + "start_pos": (0, 0, 0), # Invalid start_pos. + "end_pos": end_pos, + "blend": blend, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": (0,), # Invalid end_pos. + "blend": blend, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "blend": 1.2, + }, + ] # Invalid blend. + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(**kwargs) + + def test_aaline__kwarg_invalid_name(self): + """Ensures draw aaline detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + start_pos = (1, 1) + end_pos = (2, 0) + kwargs_list = [ + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "blend": 1, + "invalid": 1, + }, + { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "invalid": 1, + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(**kwargs) + + def test_aaline__args_and_kwargs(self): + """Ensures draw aaline accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 2)) + color = (255, 255, 0, 0) + start_pos = (0, 1) + end_pos = (1, 2) + blend = 0 + kwargs = { + "surface": surface, + "color": color, + "start_pos": start_pos, + "end_pos": end_pos, + "blend": blend, + } + + for name in ("surface", "color", "start_pos", "end_pos", "blend"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_aaline(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_aaline(surface, color, **kwargs) + elif "start_pos" == name: + bounds_rect = self.draw_aaline(surface, color, start_pos, **kwargs) + elif "end_pos" == name: + bounds_rect = self.draw_aaline( + surface, color, start_pos, end_pos, **kwargs + ) + else: + bounds_rect = self.draw_aaline( + surface, color, start_pos, end_pos, blend, **kwargs + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aaline__valid_blend_values(self): + """Ensures draw aaline accepts different blend values.""" + expected_color = pygame.Color("yellow") + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + pos = (2, 1) + kwargs = { + "surface": surface, + "color": expected_color, + "start_pos": pos, + "end_pos": (2, 2), + "blend": None, + } + + for blend in (-10, -2, -1, 0, 1, 2, 10): + surface.fill(surface_color) # Clear for each test. + kwargs["blend"] = blend + + bounds_rect = self.draw_aaline(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aaline__valid_start_pos_formats(self): + """Ensures draw aaline accepts different start_pos formats.""" + expected_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((4, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "start_pos": None, + "end_pos": (2, 2), + "blend": 0, + } + x, y = 2, 1 # start position + positions = ((x, y), (x + 0.01, y), (x, y + 0.01), (x + 0.01, y + 0.01)) + + for start_pos in positions: + for seq_type in (tuple, list, Vector2): + surface.fill(surface_color) # Clear for each test. + kwargs["start_pos"] = seq_type(start_pos) + + bounds_rect = self.draw_aaline(**kwargs) + + color = surface.get_at((x, y)) + for i, sub_color in enumerate(expected_color): + # The color could be slightly off the expected color due to + # any fractional position arguments. + self.assertGreaterEqual(color[i] + 6, sub_color, start_pos) + self.assertIsInstance(bounds_rect, pygame.Rect, start_pos) + + def test_aaline__valid_end_pos_formats(self): + """Ensures draw aaline accepts different end_pos formats.""" + expected_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((4, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "start_pos": (2, 1), + "end_pos": None, + "blend": 0, + } + x, y = 2, 2 # end position + positions = ((x, y), (x + 0.02, y), (x, y + 0.02), (x + 0.02, y + 0.02)) + + for end_pos in positions: + for seq_type in (tuple, list, Vector2): + surface.fill(surface_color) # Clear for each test. + kwargs["end_pos"] = seq_type(end_pos) + + bounds_rect = self.draw_aaline(**kwargs) + + color = surface.get_at((x, y)) + for i, sub_color in enumerate(expected_color): + # The color could be slightly off the expected color due to + # any fractional position arguments. + self.assertGreaterEqual(color[i] + 15, sub_color, end_pos) + self.assertIsInstance(bounds_rect, pygame.Rect, end_pos) + + def test_aaline__invalid_start_pos_formats(self): + """Ensures draw aaline handles invalid start_pos formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "start_pos": None, + "end_pos": (2, 2), + "blend": 0, + } + + start_pos_fmts = ( + (2,), # Too few coords. + (2, 1, 0), # Too many coords. + (2, "1"), # Wrong type. + set([2, 1]), # Wrong type. + dict(((2, 1),)), + ) # Wrong type. + + for start_pos in start_pos_fmts: + kwargs["start_pos"] = start_pos + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(**kwargs) + + def test_aaline__invalid_end_pos_formats(self): + """Ensures draw aaline handles invalid end_pos formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "start_pos": (2, 2), + "end_pos": None, + "blend": 0, + } + + end_pos_fmts = ( + (2,), # Too few coords. + (2, 1, 0), # Too many coords. + (2, "1"), # Wrong type. + set([2, 1]), # Wrong type. + dict(((2, 1),)), + ) # Wrong type. + + for end_pos in end_pos_fmts: + kwargs["end_pos"] = end_pos + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(**kwargs) + + def test_aaline__valid_color_formats(self): + """Ensures draw aaline accepts different color formats.""" + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + pos = (1, 1) + kwargs = { + "surface": surface, + "color": None, + "start_pos": pos, + "end_pos": (2, 1), + "blend": 0, + } + greens = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in greens: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_aaline(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aaline__invalid_color_formats(self): + """Ensures draw aaline handles invalid color formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 3)), + "color": None, + "start_pos": (1, 1), + "end_pos": (2, 1), + "blend": 0, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aaline(**kwargs) + + def test_aaline__color(self): + """Tests if the aaline drawn is the correct color.""" + pos = (0, 0) + for surface in self._create_surfaces(): + for expected_color in self.COLORS: + self.draw_aaline(surface, expected_color, pos, (1, 0)) + + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + + def test_aaline__gaps(self): + """Tests if the aaline drawn contains any gaps. + + See: #512 + """ + expected_color = (255, 255, 255) + for surface in self._create_surfaces(): + width = surface.get_width() + self.draw_aaline(surface, expected_color, (0, 0), (width - 1, 0)) + + for x in range(width): + pos = (x, 0) + self.assertEqual( + surface.get_at(pos), expected_color, "pos={}".format(pos) + ) + + def test_aaline__bounding_rect(self): + """Ensures draw aaline returns the correct bounding rect. + + Tests lines with endpoints on and off the surface and blending + enabled and disabled. + """ + line_color = pygame.Color("red") + surf_color = pygame.Color("blue") + width = height = 30 + # Using a rect to help manage where the lines are drawn. + helper_rect = pygame.Rect((0, 0), (width, height)) + + # Testing surfaces of different sizes. One larger than the helper_rect + # and one smaller (to test lines that span the surface). + for size in ((width + 5, height + 5), (width - 5, height - 5)): + surface = pygame.Surface(size, 0, 32) + surf_rect = surface.get_rect() + + # Move the helper rect to different positions to test line + # endpoints on and off the surface. + for pos in rect_corners_mids_and_center(surf_rect): + helper_rect.center = pos + + for blend in (False, True): # Test non-blending and blending. + for start, end in self._rect_lines(helper_rect): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_aaline( + surface, line_color, start, end, blend + ) + + # Calculating the expected_rect after the line is + # drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect(surface, surf_color, start) + + self.assertEqual(bounding_rect, expected_rect) + + def test_aaline__surface_clip(self): + """Ensures draw aaline respects a surface's clip area.""" + surfw = surfh = 30 + aaline_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (11, 11)) + clip_rect.center = surface.get_rect().center + pos_rect = clip_rect.copy() # Manages the aaline's pos. + + # Test centering the pos_rect along the clip rect's edge to allow for + # drawing the aaline over the clip_rect's bounds. + for center in rect_corners_mids_and_center(clip_rect): + pos_rect.center = center + + for blend in (0, 1): # Test non-blending and blending. + # Get the expected points by drawing the aaline without the + # clip area set. + surface.set_clip(None) + surface.fill(surface_color) + self.draw_aaline( + surface, aaline_color, pos_rect.midtop, pos_rect.midbottom, blend + ) + + # Need to get the points that are NOT surface_color due to the + # way blend=0 uses the color black to antialias. + expected_pts = get_color_points( + surface, surface_color, clip_rect, False + ) + + # Clear the surface and set the clip area. Redraw the aaline + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_aaline( + surface, aaline_color, pos_rect.midtop, pos_rect.midbottom, blend + ) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure the expected_pts + # are not surface_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + self.assertNotEqual(surface.get_at(pt), surface_color, pt) + else: + self.assertEqual(surface.get_at(pt), surface_color, pt) + + surface.unlock() + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever fully supports drawing single aalines. +# class PythonDrawAALineTest(AALineMixin, PythonDrawTestCase): +# """Test draw_py module function aaline. +# +# This class inherits the general tests from AALineMixin. It is also the +# class to add any draw_py.draw_aaline specific tests to. +# """ + + +class DrawAALineTest(AALineMixin, DrawTestCase): + """Test draw module function aaline. + + This class inherits the general tests from AALineMixin. It is also the + class to add any draw.aaline specific tests to. + """ + + def test_aaline_endianness(self): + """test color component order""" + for depth in (24, 32): + surface = pygame.Surface((5, 3), 0, depth) + surface.fill(pygame.Color(0, 0, 0)) + self.draw_aaline(surface, pygame.Color(255, 0, 0), (0, 1), (2, 1), 1) + + self.assertGreater(surface.get_at((1, 1)).r, 0, "there should be red here") + + surface.fill(pygame.Color(0, 0, 0)) + self.draw_aaline(surface, pygame.Color(0, 0, 255), (0, 1), (2, 1), 1) + + self.assertGreater(surface.get_at((1, 1)).b, 0, "there should be blue here") + + def _check_antialiasing( + self, from_point, to_point, should, check_points, set_endpoints=True + ): + """Draw a line between two points and check colors of check_points.""" + if set_endpoints: + should[from_point] = should[to_point] = FG_GREEN + + def check_one_direction(from_point, to_point, should): + self.draw_aaline(self.surface, FG_GREEN, from_point, to_point, True) + + for pt in check_points: + color = should.get(pt, BG_RED) + with self.subTest(from_pt=from_point, pt=pt, to=to_point): + self.assertEqual(self.surface.get_at(pt), color) + + # reset + draw.rect(self.surface, BG_RED, (0, 0, 10, 10), 0) + + # it is important to test also opposite direction, the algorithm + # is (#512) or was not symmetric + check_one_direction(from_point, to_point, should) + if from_point != to_point: + check_one_direction(to_point, from_point, should) + + def test_short_non_antialiased_lines(self): + """test very short not anti aliased lines in all directions.""" + + # Horizontal, vertical and diagonal lines should not be anti-aliased, + # even with draw.aaline ... + self.surface = pygame.Surface((10, 10)) + draw.rect(self.surface, BG_RED, (0, 0, 10, 10), 0) + + check_points = [(i, j) for i in range(3, 8) for j in range(3, 8)] + + def check_both_directions(from_pt, to_pt, other_points): + should = {pt: FG_GREEN for pt in other_points} + self._check_antialiasing(from_pt, to_pt, should, check_points) + + # 0. one point + check_both_directions((5, 5), (5, 5), []) + # 1. horizontal + check_both_directions((4, 7), (5, 7), []) + check_both_directions((5, 4), (7, 4), [(6, 4)]) + + # 2. vertical + check_both_directions((5, 5), (5, 6), []) + check_both_directions((6, 4), (6, 6), [(6, 5)]) + # 3. diagonals + check_both_directions((5, 5), (6, 6), []) + check_both_directions((5, 5), (7, 7), [(6, 6)]) + check_both_directions((5, 6), (6, 5), []) + check_both_directions((6, 4), (4, 6), [(5, 5)]) + + def test_short_line_anti_aliasing(self): + + self.surface = pygame.Surface((10, 10)) + draw.rect(self.surface, BG_RED, (0, 0, 10, 10), 0) + + check_points = [(i, j) for i in range(3, 8) for j in range(3, 8)] + + def check_both_directions(from_pt, to_pt, should): + self._check_antialiasing(from_pt, to_pt, should, check_points) + + brown = (127, 127, 0) + reddish = (191, 63, 0) + greenish = (63, 191, 0) + + # lets say dx = abs(x0 - x1) ; dy = abs(y0 - y1) + + # dy / dx = 0.5 + check_both_directions((4, 4), (6, 5), {(5, 4): brown, (5, 5): brown}) + check_both_directions((4, 5), (6, 4), {(5, 4): brown, (5, 5): brown}) + + # dy / dx = 2 + check_both_directions((4, 4), (5, 6), {(4, 5): brown, (5, 5): brown}) + check_both_directions((5, 4), (4, 6), {(4, 5): brown, (5, 5): brown}) + + # some little longer lines; so we need to check more points: + check_points = [(i, j) for i in range(2, 9) for j in range(2, 9)] + # dy / dx = 0.25 + should = { + (4, 3): greenish, + (5, 3): brown, + (6, 3): reddish, + (4, 4): reddish, + (5, 4): brown, + (6, 4): greenish, + } + check_both_directions((3, 3), (7, 4), should) + + should = { + (4, 3): reddish, + (5, 3): brown, + (6, 3): greenish, + (4, 4): greenish, + (5, 4): brown, + (6, 4): reddish, + } + check_both_directions((3, 4), (7, 3), should) + + # dy / dx = 4 + should = { + (4, 4): greenish, + (4, 5): brown, + (4, 6): reddish, + (5, 4): reddish, + (5, 5): brown, + (5, 6): greenish, + } + check_both_directions((4, 3), (5, 7), should) + + should = { + (4, 4): reddish, + (4, 5): brown, + (4, 6): greenish, + (5, 4): greenish, + (5, 5): brown, + (5, 6): reddish, + } + check_both_directions((5, 3), (4, 7), should) + + def test_anti_aliasing_float_coordinates(self): + """Float coordinates should be blended smoothly.""" + + self.surface = pygame.Surface((10, 10)) + draw.rect(self.surface, BG_RED, (0, 0, 10, 10), 0) + + check_points = [(i, j) for i in range(5) for j in range(5)] + brown = (127, 127, 0) + reddish = (191, 63, 0) + greenish = (63, 191, 0) + + # 0. identical point : current implementation does no smoothing... + expected = {(2, 2): FG_GREEN} + self._check_antialiasing( + (1.5, 2), (1.5, 2), expected, check_points, set_endpoints=False + ) + expected = {(2, 3): FG_GREEN} + self._check_antialiasing( + (2.49, 2.7), (2.49, 2.7), expected, check_points, set_endpoints=False + ) + + # 1. horizontal lines + # a) blend endpoints + expected = {(1, 2): brown, (2, 2): FG_GREEN} + self._check_antialiasing( + (1.5, 2), (2, 2), expected, check_points, set_endpoints=False + ) + expected = {(1, 2): brown, (2, 2): FG_GREEN, (3, 2): brown} + self._check_antialiasing( + (1.5, 2), (2.5, 2), expected, check_points, set_endpoints=False + ) + expected = {(2, 2): brown, (1, 2): FG_GREEN} + self._check_antialiasing( + (1, 2), (1.5, 2), expected, check_points, set_endpoints=False + ) + expected = {(1, 2): brown, (2, 2): greenish} + self._check_antialiasing( + (1.5, 2), (1.75, 2), expected, check_points, set_endpoints=False + ) + + # b) blend y-coordinate + expected = {(x, y): brown for x in range(2, 5) for y in (1, 2)} + self._check_antialiasing( + (2, 1.5), (4, 1.5), expected, check_points, set_endpoints=False + ) + + # 2. vertical lines + # a) blend endpoints + expected = {(2, 1): brown, (2, 2): FG_GREEN, (2, 3): brown} + self._check_antialiasing( + (2, 1.5), (2, 2.5), expected, check_points, set_endpoints=False + ) + expected = {(2, 1): brown, (2, 2): greenish} + self._check_antialiasing( + (2, 1.5), (2, 1.75), expected, check_points, set_endpoints=False + ) + # b) blend x-coordinate + expected = {(x, y): brown for x in (1, 2) for y in range(2, 5)} + self._check_antialiasing( + (1.5, 2), (1.5, 4), expected, check_points, set_endpoints=False + ) + # 3. diagonal lines + # a) blend endpoints + expected = {(1, 1): brown, (2, 2): FG_GREEN, (3, 3): brown} + self._check_antialiasing( + (1.5, 1.5), (2.5, 2.5), expected, check_points, set_endpoints=False + ) + expected = {(3, 1): brown, (2, 2): FG_GREEN, (1, 3): brown} + self._check_antialiasing( + (2.5, 1.5), (1.5, 2.5), expected, check_points, set_endpoints=False + ) + # b) blend sidewards + expected = {(2, 1): brown, (2, 2): brown, (3, 2): brown, (3, 3): brown} + self._check_antialiasing( + (2, 1.5), (3, 2.5), expected, check_points, set_endpoints=False + ) + + expected = { + (2, 1): greenish, + (2, 2): reddish, + (3, 2): greenish, + (3, 3): reddish, + (4, 3): greenish, + (4, 4): reddish, + } + + self._check_antialiasing( + (2, 1.25), (4, 3.25), expected, check_points, set_endpoints=False + ) + + def test_anti_aliasing_at_and_outside_the_border(self): + """Ensures antialiasing works correct at a surface's borders.""" + + self.surface = pygame.Surface((10, 10)) + draw.rect(self.surface, BG_RED, (0, 0, 10, 10), 0) + + check_points = [(i, j) for i in range(10) for j in range(10)] + + reddish = (191, 63, 0) + brown = (127, 127, 0) + greenish = (63, 191, 0) + from_point, to_point = (3, 3), (7, 4) + should = { + (4, 3): greenish, + (5, 3): brown, + (6, 3): reddish, + (4, 4): reddish, + (5, 4): brown, + (6, 4): greenish, + } + + for dx, dy in ( + (-4, 0), + (4, 0), # moved to left and right borders + (0, -5), + (0, -4), + (0, -3), # upper border + (0, 5), + (0, 6), + (0, 7), # lower border + (-4, -4), + (-4, -3), + (-3, -4), + ): # upper left corner + first = from_point[0] + dx, from_point[1] + dy + second = to_point[0] + dx, to_point[1] + dy + expected = {(x + dx, y + dy): color for (x, y), color in should.items()} + + self._check_antialiasing(first, second, expected, check_points) + + +### AALines Testing ########################################################### + + +class AALinesMixin(BaseLineMixin): + """Mixin test for drawing aalines. + + This class contains all the general aalines drawing tests. + """ + + def test_aalines__args(self): + """Ensures draw aalines accepts the correct args.""" + bounds_rect = self.draw_aalines( + pygame.Surface((3, 3)), (0, 10, 0, 50), False, ((0, 0), (1, 1)), 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__args_without_blend(self): + """Ensures draw aalines accepts the args without a blend.""" + bounds_rect = self.draw_aalines( + pygame.Surface((2, 2)), (0, 0, 0, 50), False, ((0, 0), (1, 1)) + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__blend_warning(self): + """From pygame 2, blend=False should raise DeprecationWarning.""" + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger DeprecationWarning. + self.draw_aalines( + pygame.Surface((2, 2)), (0, 0, 0, 50), False, ((0, 0), (1, 1)), False + ) + # Check if there is only one warning and is a DeprecationWarning. + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) + + def test_aalines__kwargs(self): + """Ensures draw aalines accepts the correct kwargs + with and without a blend arg. + """ + surface = pygame.Surface((4, 4)) + color = pygame.Color("yellow") + points = ((0, 0), (1, 1), (2, 2)) + kwargs_list = [ + { + "surface": surface, + "color": color, + "closed": False, + "points": points, + "blend": 1, + }, + {"surface": surface, "color": color, "closed": False, "points": points}, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_aalines(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__kwargs_order_independent(self): + """Ensures draw aalines's kwargs are not order dependent.""" + bounds_rect = self.draw_aalines( + closed=1, + points=((0, 0), (1, 1), (2, 2)), + blend=1, + color=(10, 20, 30), + surface=pygame.Surface((3, 2)), + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__args_missing(self): + """Ensures draw aalines detects any missing required args.""" + surface = pygame.Surface((1, 1)) + color = pygame.Color("blue") + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(surface, color, 0) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(surface, color) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines() + + def test_aalines__kwargs_missing(self): + """Ensures draw aalines detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((3, 2)), + "color": pygame.Color("red"), + "closed": 1, + "points": ((2, 2), (1, 1)), + "blend": 1, + } + + for name in ("points", "closed", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(**invalid_kwargs) + + def test_aalines__arg_invalid_types(self): + """Ensures draw aalines detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + closed = 0 + points = ((1, 2), (2, 1)) + + with self.assertRaises(TypeError): + # Invalid blend. + bounds_rect = self.draw_aalines(surface, color, closed, points, "1") + + with self.assertRaises(TypeError): + # Invalid points. + bounds_rect = self.draw_aalines(surface, color, closed, (1, 2, 3)) + + with self.assertRaises(TypeError): + # Invalid closed. + bounds_rect = self.draw_aalines(surface, color, InvalidBool(), points) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_aalines(surface, 2.3, closed, points) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_aalines((1, 2, 3, 4), color, closed, points) + + def test_aalines__kwarg_invalid_types(self): + """Ensures draw aalines detects invalid kwarg types.""" + valid_kwargs = { + "surface": pygame.Surface((3, 3)), + "color": pygame.Color("green"), + "closed": False, + "points": ((1, 2), (2, 1)), + "blend": 1, + } + + invalid_kwargs = { + "surface": pygame.Surface, + "color": 2.3, + "closed": InvalidBool(), + "points": (0, 0, 0), + "blend": 1.2, + } + + for kwarg in ("surface", "color", "closed", "points", "blend"): + kwargs = dict(valid_kwargs) + kwargs[kwarg] = invalid_kwargs[kwarg] + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(**kwargs) + + def test_aalines__kwarg_invalid_name(self): + """Ensures draw aalines detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + closed = 1 + points = ((1, 2), (2, 1)) + kwargs_list = [ + { + "surface": surface, + "color": color, + "closed": closed, + "points": points, + "blend": 1, + "invalid": 1, + }, + { + "surface": surface, + "color": color, + "closed": closed, + "points": points, + "invalid": 1, + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(**kwargs) + + def test_aalines__args_and_kwargs(self): + """Ensures draw aalines accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 2)) + color = (255, 255, 0, 0) + closed = 0 + points = ((1, 2), (2, 1)) + blend = 1 + kwargs = { + "surface": surface, + "color": color, + "closed": closed, + "points": points, + "blend": blend, + } + + for name in ("surface", "color", "closed", "points", "blend"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_aalines(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_aalines(surface, color, **kwargs) + elif "closed" == name: + bounds_rect = self.draw_aalines(surface, color, closed, **kwargs) + elif "points" == name: + bounds_rect = self.draw_aalines( + surface, color, closed, points, **kwargs + ) + else: + bounds_rect = self.draw_aalines( + surface, color, closed, points, blend, **kwargs + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__valid_blend_values(self): + """Ensures draw aalines accepts different blend values.""" + expected_color = pygame.Color("yellow") + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + pos = (1, 1) + kwargs = { + "surface": surface, + "color": expected_color, + "closed": False, + "points": (pos, (1, 3)), + "blend": None, + } + + for blend in (-10, -2, -1, 0, 1, 2, 10): + surface.fill(surface_color) # Clear for each test. + kwargs["blend"] = blend + + bounds_rect = self.draw_aalines(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color, blend) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__valid_points_format(self): + """Ensures draw aalines accepts different points formats.""" + expected_color = (10, 20, 30, 255) + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "closed": False, + "points": None, + "blend": 0, + } + + # The point type can be a tuple/list/Vector2. + point_types = ( + (tuple, tuple, tuple, tuple), # all tuples + (list, list, list, list), # all lists + (Vector2, Vector2, Vector2, Vector2), # all Vector2s + (list, Vector2, tuple, Vector2), + ) # mix + + # The point values can be ints or floats. + point_values = ( + ((1, 1), (2, 1), (2, 2), (1, 2)), + ((1, 1), (2.2, 1), (2.1, 2.2), (1, 2.1)), + ) + + # Each sequence of points can be a tuple or a list. + seq_types = (tuple, list) + + for point_type in point_types: + for values in point_values: + check_pos = values[0] + points = [point_type[i](pt) for i, pt in enumerate(values)] + + for seq_type in seq_types: + surface.fill(surface_color) # Clear for each test. + kwargs["points"] = seq_type(points) + + bounds_rect = self.draw_aalines(**kwargs) + + self.assertEqual(surface.get_at(check_pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__invalid_points_formats(self): + """Ensures draw aalines handles invalid points formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "closed": False, + "points": None, + "blend": 1, + } + + points_fmts = ( + ((1, 1), (2,)), # Too few coords. + ((1, 1), (2, 2, 2)), # Too many coords. + ((1, 1), (2, "2")), # Wrong type. + ((1, 1), set([2, 3])), # Wrong type. + ((1, 1), dict(((2, 2), (3, 3)))), # Wrong type. + set(((1, 1), (1, 2))), # Wrong type. + dict(((1, 1), (4, 4))), + ) # Wrong type. + + for points in points_fmts: + kwargs["points"] = points + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(**kwargs) + + def test_aalines__invalid_points_values(self): + """Ensures draw aalines handles invalid points values correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "closed": False, + "points": None, + "blend": 1, + } + + for points in ([], ((1, 1),)): # Too few points. + for seq_type in (tuple, list): # Test as tuples and lists. + kwargs["points"] = seq_type(points) + + with self.assertRaises(ValueError): + bounds_rect = self.draw_aalines(**kwargs) + + def test_aalines__valid_closed_values(self): + """Ensures draw aalines accepts different closed values.""" + line_color = pygame.Color("blue") + surface_color = pygame.Color("white") + surface = pygame.Surface((5, 5)) + pos = (1, 3) + kwargs = { + "surface": surface, + "color": line_color, + "closed": None, + "points": ((1, 1), (4, 1), (4, 4), (1, 4)), + "blend": 0, + } + + true_values = (-7, 1, 10, "2", 3.1, (4,), [5], True) + false_values = (None, "", 0, (), [], False) + + for closed in true_values + false_values: + surface.fill(surface_color) # Clear for each test. + kwargs["closed"] = closed + expected_color = line_color if closed else surface_color + + bounds_rect = self.draw_aalines(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__valid_color_formats(self): + """Ensures draw aalines accepts different color formats.""" + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + pos = (1, 1) + kwargs = { + "surface": surface, + "color": None, + "closed": False, + "points": (pos, (2, 1)), + "blend": 0, + } + greens = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in greens: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_aalines(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_aalines__invalid_color_formats(self): + """Ensures draw aalines handles invalid color formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 3)), + "color": None, + "closed": False, + "points": ((1, 1), (1, 2)), + "blend": 0, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_aalines(**kwargs) + + def test_aalines__color(self): + """Tests if the aalines drawn are the correct color. + + Draws aalines around the border of the given surface and checks if all + borders of the surface only contain the given color. + """ + for surface in self._create_surfaces(): + for expected_color in self.COLORS: + self.draw_aalines(surface, expected_color, True, corners(surface)) + + for pos, color in border_pos_and_color(surface): + self.assertEqual(color, expected_color, "pos={}".format(pos)) + + def test_aalines__gaps(self): + """Tests if the aalines drawn contain any gaps. + + Draws aalines around the border of the given surface and checks if + all borders of the surface contain any gaps. + + See: #512 + """ + expected_color = (255, 255, 255) + for surface in self._create_surfaces(): + self.draw_aalines(surface, expected_color, True, corners(surface)) + + for pos, color in border_pos_and_color(surface): + self.assertEqual(color, expected_color, "pos={}".format(pos)) + + def test_aalines__bounding_rect(self): + """Ensures draw aalines returns the correct bounding rect. + + Tests lines with endpoints on and off the surface and blending + enabled and disabled. + """ + line_color = pygame.Color("red") + surf_color = pygame.Color("blue") + width = height = 30 + # Using a rect to help manage where the lines are drawn. + pos_rect = pygame.Rect((0, 0), (width, height)) + + # Testing surfaces of different sizes. One larger than the pos_rect + # and one smaller (to test lines that span the surface). + for size in ((width + 5, height + 5), (width - 5, height - 5)): + surface = pygame.Surface(size, 0, 32) + surf_rect = surface.get_rect() + + # Move pos_rect to different positions to test line endpoints on + # and off the surface. + for pos in rect_corners_mids_and_center(surf_rect): + pos_rect.center = pos + # Shape: Triangle (if closed), ^ caret (if not closed). + pts = (pos_rect.midleft, pos_rect.midtop, pos_rect.midright) + pos = pts[0] # Rect position if nothing drawn. + + for blend in (False, True): # Test non-blending and blending. + for closed in (True, False): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_aalines( + surface, line_color, closed, pts, blend + ) + + # Calculating the expected_rect after the lines are + # drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect(surface, surf_color, pos) + + self.assertEqual(bounding_rect, expected_rect) + + def test_aalines__surface_clip(self): + """Ensures draw aalines respects a surface's clip area.""" + surfw = surfh = 30 + aaline_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (11, 11)) + clip_rect.center = surface.get_rect().center + pos_rect = clip_rect.copy() # Manages the aalines's pos. + + # Test centering the pos_rect along the clip rect's edge to allow for + # drawing the aalines over the clip_rect's bounds. + for center in rect_corners_mids_and_center(clip_rect): + pos_rect.center = center + pts = (pos_rect.midtop, pos_rect.center, pos_rect.midbottom) + + for closed in (True, False): # Test closed and not closed. + for blend in (0, 1): # Test non-blending and blending. + # Get the expected points by drawing the aalines without + # the clip area set. + surface.set_clip(None) + surface.fill(surface_color) + self.draw_aalines(surface, aaline_color, closed, pts, blend) + + # Need to get the points that are NOT surface_color due to + # the way blend=0 uses the color black to antialias. + expected_pts = get_color_points( + surface, surface_color, clip_rect, False + ) + + # Clear the surface and set the clip area. Redraw the + # aalines and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_aalines(surface, aaline_color, closed, pts, blend) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure the expected_pts + # are not surface_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + self.assertNotEqual(surface.get_at(pt), surface_color, pt) + else: + self.assertEqual(surface.get_at(pt), surface_color, pt) + + surface.unlock() + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever fully supports drawing aalines. +# class PythonDrawAALinesTest(AALinesMixin, PythonDrawTestCase): +# """Test draw_py module function aalines. +# +# This class inherits the general tests from AALinesMixin. It is also the +# class to add any draw_py.draw_aalines specific tests to. +# """ + + +class DrawAALinesTest(AALinesMixin, DrawTestCase): + """Test draw module function aalines. + + This class inherits the general tests from AALinesMixin. It is also the + class to add any draw.aalines specific tests to. + """ + + +### Polygon Testing ########################################################### + +SQUARE = ([0, 0], [3, 0], [3, 3], [0, 3]) +DIAMOND = [(1, 3), (3, 5), (5, 3), (3, 1)] +CROSS = ( + [2, 0], + [4, 0], + [4, 2], + [6, 2], + [6, 4], + [4, 4], + [4, 6], + [2, 6], + [2, 4], + [0, 4], + [0, 2], + [2, 2], +) + + +class DrawPolygonMixin(object): + """Mixin tests for drawing polygons. + + This class contains all the general polygon drawing tests. + """ + + def setUp(self): + self.surface = pygame.Surface((20, 20)) + + def test_polygon__args(self): + """Ensures draw polygon accepts the correct args.""" + bounds_rect = self.draw_polygon( + pygame.Surface((3, 3)), (0, 10, 0, 50), ((0, 0), (1, 1), (2, 2)), 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__args_without_width(self): + """Ensures draw polygon accepts the args without a width.""" + bounds_rect = self.draw_polygon( + pygame.Surface((2, 2)), (0, 0, 0, 50), ((0, 0), (1, 1), (2, 2)) + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__kwargs(self): + """Ensures draw polygon accepts the correct kwargs + with and without a width arg. + """ + surface = pygame.Surface((4, 4)) + color = pygame.Color("yellow") + points = ((0, 0), (1, 1), (2, 2)) + kwargs_list = [ + {"surface": surface, "color": color, "points": points, "width": 1}, + {"surface": surface, "color": color, "points": points}, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_polygon(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__kwargs_order_independent(self): + """Ensures draw polygon's kwargs are not order dependent.""" + bounds_rect = self.draw_polygon( + color=(10, 20, 30), + surface=pygame.Surface((3, 2)), + width=0, + points=((0, 1), (1, 2), (2, 3)), + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__args_missing(self): + """Ensures draw polygon detects any missing required args.""" + surface = pygame.Surface((1, 1)) + color = pygame.Color("blue") + + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon(surface, color) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon() + + def test_polygon__kwargs_missing(self): + """Ensures draw polygon detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((1, 2)), + "color": pygame.Color("red"), + "points": ((2, 1), (2, 2), (2, 3)), + "width": 1, + } + + for name in ("points", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon(**invalid_kwargs) + + def test_polygon__arg_invalid_types(self): + """Ensures draw polygon detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + points = ((0, 1), (1, 2), (1, 3)) + + with self.assertRaises(TypeError): + # Invalid width. + bounds_rect = self.draw_polygon(surface, color, points, "1") + + with self.assertRaises(TypeError): + # Invalid points. + bounds_rect = self.draw_polygon(surface, color, (1, 2, 3)) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_polygon(surface, 2.3, points) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_polygon((1, 2, 3, 4), color, points) + + def test_polygon__kwarg_invalid_types(self): + """Ensures draw polygon detects invalid kwarg types.""" + surface = pygame.Surface((3, 3)) + color = pygame.Color("green") + points = ((0, 0), (1, 0), (2, 0)) + width = 1 + kwargs_list = [ + { + "surface": pygame.Surface, # Invalid surface. + "color": color, + "points": points, + "width": width, + }, + { + "surface": surface, + "color": 2.3, # Invalid color. + "points": points, + "width": width, + }, + { + "surface": surface, + "color": color, + "points": ((1,), (1,), (1,)), # Invalid points. + "width": width, + }, + {"surface": surface, "color": color, "points": points, "width": 1.2}, + ] # Invalid width. + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon(**kwargs) + + def test_polygon__kwarg_invalid_name(self): + """Ensures draw polygon detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + points = ((1, 1), (1, 2), (1, 3)) + kwargs_list = [ + { + "surface": surface, + "color": color, + "points": points, + "width": 1, + "invalid": 1, + }, + {"surface": surface, "color": color, "points": points, "invalid": 1}, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon(**kwargs) + + def test_polygon__args_and_kwargs(self): + """Ensures draw polygon accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 1)) + color = (255, 255, 0, 0) + points = ((0, 1), (1, 2), (2, 3)) + width = 0 + kwargs = {"surface": surface, "color": color, "points": points, "width": width} + + for name in ("surface", "color", "points", "width"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_polygon(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_polygon(surface, color, **kwargs) + elif "points" == name: + bounds_rect = self.draw_polygon(surface, color, points, **kwargs) + else: + bounds_rect = self.draw_polygon(surface, color, points, width, **kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__valid_width_values(self): + """Ensures draw polygon accepts different width values.""" + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + color = (10, 20, 30, 255) + kwargs = { + "surface": surface, + "color": color, + "points": ((1, 1), (2, 1), (2, 2), (1, 2)), + "width": None, + } + pos = kwargs["points"][0] + + for width in (-100, -10, -1, 0, 1, 10, 100): + surface.fill(surface_color) # Clear for each test. + kwargs["width"] = width + expected_color = color if width >= 0 else surface_color + + bounds_rect = self.draw_polygon(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__valid_points_format(self): + """Ensures draw polygon accepts different points formats.""" + expected_color = (10, 20, 30, 255) + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "points": None, + "width": 0, + } + + # The point type can be a tuple/list/Vector2. + point_types = ( + (tuple, tuple, tuple, tuple), # all tuples + (list, list, list, list), # all lists + (Vector2, Vector2, Vector2, Vector2), # all Vector2s + (list, Vector2, tuple, Vector2), + ) # mix + + # The point values can be ints or floats. + point_values = ( + ((1, 1), (2, 1), (2, 2), (1, 2)), + ((1, 1), (2.2, 1), (2.1, 2.2), (1, 2.1)), + ) + + # Each sequence of points can be a tuple or a list. + seq_types = (tuple, list) + + for point_type in point_types: + for values in point_values: + check_pos = values[0] + points = [point_type[i](pt) for i, pt in enumerate(values)] + + for seq_type in seq_types: + surface.fill(surface_color) # Clear for each test. + kwargs["points"] = seq_type(points) + + bounds_rect = self.draw_polygon(**kwargs) + + self.assertEqual(surface.get_at(check_pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__invalid_points_formats(self): + """Ensures draw polygon handles invalid points formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "points": None, + "width": 0, + } + + points_fmts = ( + ((1, 1), (2, 1), (2,)), # Too few coords. + ((1, 1), (2, 1), (2, 2, 2)), # Too many coords. + ((1, 1), (2, 1), (2, "2")), # Wrong type. + ((1, 1), (2, 1), set([2, 3])), # Wrong type. + ((1, 1), (2, 1), dict(((2, 2), (3, 3)))), # Wrong type. + set(((1, 1), (2, 1), (2, 2), (1, 2))), # Wrong type. + dict(((1, 1), (2, 2), (3, 3), (4, 4))), + ) # Wrong type. + + for points in points_fmts: + kwargs["points"] = points + + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon(**kwargs) + + def test_polygon__invalid_points_values(self): + """Ensures draw polygon handles invalid points values correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "points": None, + "width": 0, + } + + points_fmts = ( + tuple(), # Too few points. + ((1, 1),), # Too few points. + ((1, 1), (2, 1)), + ) # Too few points. + + for points in points_fmts: + for seq_type in (tuple, list): # Test as tuples and lists. + kwargs["points"] = seq_type(points) + + with self.assertRaises(ValueError): + bounds_rect = self.draw_polygon(**kwargs) + + def test_polygon__valid_color_formats(self): + """Ensures draw polygon accepts different color formats.""" + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": None, + "points": ((1, 1), (2, 1), (2, 2), (1, 2)), + "width": 0, + } + pos = kwargs["points"][0] + greens = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in greens: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_polygon(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_polygon__invalid_color_formats(self): + """Ensures draw polygon handles invalid color formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 3)), + "color": None, + "points": ((1, 1), (2, 1), (2, 2), (1, 2)), + "width": 0, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_polygon(**kwargs) + + def test_draw_square(self): + self.draw_polygon(self.surface, RED, SQUARE, 0) + # note : there is a discussion (#234) if draw.polygon should include or + # not the right or lower border; here we stick with current behavior, + # eg include those borders ... + for x in range(4): + for y in range(4): + self.assertEqual(self.surface.get_at((x, y)), RED) + + def test_draw_diamond(self): + pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0) + self.draw_polygon(self.surface, GREEN, DIAMOND, 0) + # this diamond shape is equivalent to its four corners, plus inner square + for x, y in DIAMOND: + self.assertEqual(self.surface.get_at((x, y)), GREEN, msg=str((x, y))) + for x in range(2, 5): + for y in range(2, 5): + self.assertEqual(self.surface.get_at((x, y)), GREEN) + + def test_1_pixel_high_or_wide_shapes(self): + # 1. one-pixel-high, filled + pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0) + self.draw_polygon(self.surface, GREEN, [(x, 2) for x, _y in CROSS], 0) + cross_size = 6 # the maximum x or y coordinate of the cross + for x in range(cross_size + 1): + self.assertEqual(self.surface.get_at((x, 1)), RED) + self.assertEqual(self.surface.get_at((x, 2)), GREEN) + self.assertEqual(self.surface.get_at((x, 3)), RED) + pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0) + # 2. one-pixel-high, not filled + self.draw_polygon(self.surface, GREEN, [(x, 5) for x, _y in CROSS], 1) + for x in range(cross_size + 1): + self.assertEqual(self.surface.get_at((x, 4)), RED) + self.assertEqual(self.surface.get_at((x, 5)), GREEN) + self.assertEqual(self.surface.get_at((x, 6)), RED) + pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0) + # 3. one-pixel-wide, filled + self.draw_polygon(self.surface, GREEN, [(3, y) for _x, y in CROSS], 0) + for y in range(cross_size + 1): + self.assertEqual(self.surface.get_at((2, y)), RED) + self.assertEqual(self.surface.get_at((3, y)), GREEN) + self.assertEqual(self.surface.get_at((4, y)), RED) + pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0) + # 4. one-pixel-wide, not filled + self.draw_polygon(self.surface, GREEN, [(4, y) for _x, y in CROSS], 1) + for y in range(cross_size + 1): + self.assertEqual(self.surface.get_at((3, y)), RED) + self.assertEqual(self.surface.get_at((4, y)), GREEN) + self.assertEqual(self.surface.get_at((5, y)), RED) + + def test_draw_symetric_cross(self): + """non-regression on issue #234 : x and y where handled inconsistently. + + Also, the result is/was different whether we fill or not the polygon. + """ + # 1. case width = 1 (not filled: `polygon` calls internally the `lines` function) + pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0) + self.draw_polygon(self.surface, GREEN, CROSS, 1) + inside = [(x, 3) for x in range(1, 6)] + [(3, y) for y in range(1, 6)] + for x in range(10): + for y in range(10): + if (x, y) in inside: + self.assertEqual(self.surface.get_at((x, y)), RED) + elif (x in range(2, 5) and y < 7) or (y in range(2, 5) and x < 7): + # we are on the border of the cross: + self.assertEqual(self.surface.get_at((x, y)), GREEN) + else: + # we are outside + self.assertEqual(self.surface.get_at((x, y)), RED) + + # 2. case width = 0 (filled; this is the example from #234) + pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0) + self.draw_polygon(self.surface, GREEN, CROSS, 0) + inside = [(x, 3) for x in range(1, 6)] + [(3, y) for y in range(1, 6)] + for x in range(10): + for y in range(10): + if (x in range(2, 5) and y < 7) or (y in range(2, 5) and x < 7): + # we are on the border of the cross: + self.assertEqual( + self.surface.get_at((x, y)), GREEN, msg=str((x, y)) + ) + else: + # we are outside + self.assertEqual(self.surface.get_at((x, y)), RED) + + def test_illumine_shape(self): + """non-regression on issue #313""" + rect = pygame.Rect((0, 0, 20, 20)) + path_data = [ + (0, 0), + (rect.width - 1, 0), # upper border + (rect.width - 5, 5 - 1), + (5 - 1, 5 - 1), # upper inner + (5 - 1, rect.height - 5), + (0, rect.height - 1), + ] # lower diagonal + # The shape looks like this (the numbers are the indices of path_data) + + # 0**********************1 <-- upper border + # *********************** + # ********************** + # ********************* + # ****3**************2 <-- upper inner border + # ***** + # ***** (more lines here) + # ***** + # ****4 + # **** + # *** + # ** + # 5 + # + + # the current bug is that the "upper inner" line is not drawn, but only + # if 4 or some lower corner exists + pygame.draw.rect(self.surface, RED, (0, 0, 20, 20), 0) + + # 1. First without the corners 4 & 5 + self.draw_polygon(self.surface, GREEN, path_data[:4], 0) + for x in range(20): + self.assertEqual(self.surface.get_at((x, 0)), GREEN) # upper border + for x in range(4, rect.width - 5 + 1): + self.assertEqual(self.surface.get_at((x, 4)), GREEN) # upper inner + + # 2. with the corners 4 & 5 + pygame.draw.rect(self.surface, RED, (0, 0, 20, 20), 0) + self.draw_polygon(self.surface, GREEN, path_data, 0) + for x in range(4, rect.width - 5 + 1): + self.assertEqual(self.surface.get_at((x, 4)), GREEN) # upper inner + + def test_invalid_points(self): + self.assertRaises( + TypeError, + lambda: self.draw_polygon( + self.surface, RED, ((0, 0), (0, 20), (20, 20), 20), 0 + ), + ) + + def test_polygon__bounding_rect(self): + """Ensures draw polygon returns the correct bounding rect. + + Tests polygons on and off the surface and a range of width/thickness + values. + """ + polygon_color = pygame.Color("red") + surf_color = pygame.Color("black") + min_width = min_height = 5 + max_width = max_height = 7 + sizes = ((min_width, min_height), (max_width, max_height)) + surface = pygame.Surface((20, 20), 0, 32) + surf_rect = surface.get_rect() + # Make a rect that is bigger than the surface to help test drawing + # polygons off and partially off the surface. + big_rect = surf_rect.inflate(min_width * 2 + 1, min_height * 2 + 1) + + for pos in rect_corners_mids_and_center( + surf_rect + ) + rect_corners_mids_and_center(big_rect): + # A rect (pos_rect) is used to help create and position the + # polygon. Each of this rect's position attributes will be set to + # the pos value. + for attr in RECT_POSITION_ATTRIBUTES: + # Test using different rect sizes and thickness values. + for width, height in sizes: + pos_rect = pygame.Rect((0, 0), (width, height)) + setattr(pos_rect, attr, pos) + # Points form a triangle with no fully + # horizontal/vertical lines. + vertices = ( + pos_rect.midleft, + pos_rect.midtop, + pos_rect.bottomright, + ) + + for thickness in range(4): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_polygon( + surface, polygon_color, vertices, thickness + ) + + # Calculating the expected_rect after the polygon + # is drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect( + surface, surf_color, vertices[0] + ) + + self.assertEqual( + bounding_rect, + expected_rect, + "thickness={}".format(thickness), + ) + + def test_polygon__surface_clip(self): + """Ensures draw polygon respects a surface's clip area. + + Tests drawing the polygon filled and unfilled. + """ + surfw = surfh = 30 + polygon_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (8, 10)) + clip_rect.center = surface.get_rect().center + pos_rect = clip_rect.copy() # Manages the polygon's pos. + + for width in (0, 1): # Filled and unfilled. + # Test centering the polygon along the clip rect's edge. + for center in rect_corners_mids_and_center(clip_rect): + # Get the expected points by drawing the polygon without the + # clip area set. + pos_rect.center = center + vertices = ( + pos_rect.topleft, + pos_rect.topright, + pos_rect.bottomright, + pos_rect.bottomleft, + ) + surface.set_clip(None) + surface.fill(surface_color) + self.draw_polygon(surface, polygon_color, vertices, width) + expected_pts = get_color_points(surface, polygon_color, clip_rect) + + # Clear the surface and set the clip area. Redraw the polygon + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_polygon(surface, polygon_color, vertices, width) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure only the expected_pts + # are the polygon_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + expected_color = polygon_color + else: + expected_color = surface_color + + self.assertEqual(surface.get_at(pt), expected_color, pt) + + surface.unlock() + + +class DrawPolygonTest(DrawPolygonMixin, DrawTestCase): + """Test draw module function polygon. + + This class inherits the general tests from DrawPolygonMixin. It is also + the class to add any draw.polygon specific tests to. + """ + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever fully supports drawing polygons. +# @unittest.skip('draw_py.draw_polygon not fully supported yet') +# class PythonDrawPolygonTest(DrawPolygonMixin, PythonDrawTestCase): +# """Test draw_py module function draw_polygon. +# +# This class inherits the general tests from DrawPolygonMixin. It is also +# the class to add any draw_py.draw_polygon specific tests to. +# """ + + +### Rect Testing ############################################################## + + +class DrawRectMixin(object): + """Mixin tests for drawing rects. + + This class contains all the general rect drawing tests. + """ + + def test_rect__args(self): + """Ensures draw rect accepts the correct args.""" + bounds_rect = self.draw_rect( + pygame.Surface((2, 2)), + (20, 10, 20, 150), + pygame.Rect((0, 0), (1, 1)), + 2, + 1, + 2, + 3, + 4, + 5, + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__args_without_width(self): + """Ensures draw rect accepts the args without a width and borders.""" + bounds_rect = self.draw_rect( + pygame.Surface((3, 5)), (0, 0, 0, 255), pygame.Rect((0, 0), (1, 1)) + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__kwargs(self): + """Ensures draw rect accepts the correct kwargs + with and without a width and border_radius arg. + """ + kwargs_list = [ + { + "surface": pygame.Surface((5, 5)), + "color": pygame.Color("red"), + "rect": pygame.Rect((0, 0), (1, 2)), + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": pygame.Surface((1, 2)), + "color": (0, 100, 200), + "rect": (0, 0, 1, 1), + }, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_rect(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__kwargs_order_independent(self): + """Ensures draw rect's kwargs are not order dependent.""" + bounds_rect = self.draw_rect( + color=(0, 1, 2), + border_radius=10, + surface=pygame.Surface((2, 3)), + border_top_left_radius=5, + width=-2, + border_top_right_radius=20, + border_bottom_right_radius=0, + rect=pygame.Rect((0, 0), (0, 0)), + border_bottom_left_radius=15, + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__args_missing(self): + """Ensures draw rect detects any missing required args.""" + surface = pygame.Surface((1, 1)) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect(surface, pygame.Color("white")) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect() + + def test_rect__kwargs_missing(self): + """Ensures draw rect detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((1, 3)), + "color": pygame.Color("red"), + "rect": pygame.Rect((0, 0), (2, 2)), + "width": 5, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + } + + for name in ("rect", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect(**invalid_kwargs) + + def test_rect__arg_invalid_types(self): + """Ensures draw rect detects invalid arg types.""" + surface = pygame.Surface((3, 3)) + color = pygame.Color("white") + rect = pygame.Rect((1, 1), (1, 1)) + + with self.assertRaises(TypeError): + # Invalid border_bottom_right_radius. + bounds_rect = self.draw_rect( + surface, color, rect, 2, border_bottom_right_radius="rad" + ) + + with self.assertRaises(TypeError): + # Invalid border_bottom_left_radius. + bounds_rect = self.draw_rect( + surface, color, rect, 2, border_bottom_left_radius="rad" + ) + + with self.assertRaises(TypeError): + # Invalid border_top_right_radius. + bounds_rect = self.draw_rect( + surface, color, rect, 2, border_top_right_radius="rad" + ) + + with self.assertRaises(TypeError): + # Invalid border_top_left_radius. + bounds_rect = self.draw_rect( + surface, color, rect, 2, border_top_left_radius="draw" + ) + + with self.assertRaises(TypeError): + # Invalid border_radius. + bounds_rect = self.draw_rect(surface, color, rect, 2, "rad") + + with self.assertRaises(TypeError): + # Invalid width. + bounds_rect = self.draw_rect(surface, color, rect, "2", 4) + + with self.assertRaises(TypeError): + # Invalid rect. + bounds_rect = self.draw_rect(surface, color, (1, 2, 3), 2, 6) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_rect(surface, 2.3, rect, 3, 8) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_rect(rect, color, rect, 4, 10) + + def test_rect__kwarg_invalid_types(self): + """Ensures draw rect detects invalid kwarg types.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("red") + rect = pygame.Rect((0, 0), (1, 1)) + kwargs_list = [ + { + "surface": pygame.Surface, # Invalid surface. + "color": color, + "rect": rect, + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": 2.3, # Invalid color. + "rect": rect, + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": color, + "rect": (1, 1, 2), # Invalid rect. + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1.1, # Invalid width. + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1, + "border_radius": 10.5, # Invalid border_radius. + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5.5, # Invalid top_left_radius. + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": "a", # Invalid top_right_radius. + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": "c", # Invalid bottom_left_radius + "border_bottom_right_radius": 0, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": "d", # Invalid bottom_right. + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect(**kwargs) + + def test_rect__kwarg_invalid_name(self): + """Ensures draw rect detects invalid kwarg names.""" + surface = pygame.Surface((2, 1)) + color = pygame.Color("green") + rect = pygame.Rect((0, 0), (3, 3)) + kwargs_list = [ + { + "surface": surface, + "color": color, + "rect": rect, + "width": 1, + "border_radius": 10, + "border_top_left_radius": 5, + "border_top_right_radius": 20, + "border_bottom_left_radius": 15, + "border_bottom_right_radius": 0, + "invalid": 1, + }, + {"surface": surface, "color": color, "rect": rect, "invalid": 1}, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect(**kwargs) + + def test_rect__args_and_kwargs(self): + """Ensures draw rect accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 1)) + color = (255, 255, 255, 0) + rect = pygame.Rect((1, 0), (2, 5)) + width = 0 + kwargs = {"surface": surface, "color": color, "rect": rect, "width": width} + + for name in ("surface", "color", "rect", "width"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_rect(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_rect(surface, color, **kwargs) + elif "rect" == name: + bounds_rect = self.draw_rect(surface, color, rect, **kwargs) + else: + bounds_rect = self.draw_rect(surface, color, rect, width, **kwargs) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__valid_width_values(self): + """Ensures draw rect accepts different width values.""" + pos = (1, 1) + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + color = (1, 2, 3, 255) + kwargs = { + "surface": surface, + "color": color, + "rect": pygame.Rect(pos, (2, 2)), + "width": None, + } + + for width in (-1000, -10, -1, 0, 1, 10, 1000): + surface.fill(surface_color) # Clear for each test. + kwargs["width"] = width + expected_color = color if width >= 0 else surface_color + + bounds_rect = self.draw_rect(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__valid_rect_formats(self): + """Ensures draw rect accepts different rect formats.""" + pos = (1, 1) + expected_color = pygame.Color("yellow") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + kwargs = {"surface": surface, "color": expected_color, "rect": None, "width": 0} + rects = ( + pygame.Rect(pos, (1, 1)), + (pos, (2, 2)), + (pos[0], pos[1], 3, 3), + [pos, (2.1, 2.2)], + ) + + for rect in rects: + surface.fill(surface_color) # Clear for each test. + kwargs["rect"] = rect + + bounds_rect = self.draw_rect(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__invalid_rect_formats(self): + """Ensures draw rect handles invalid rect formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("red"), + "rect": None, + "width": 0, + } + + invalid_fmts = ( + [], + [1], + [1, 2], + [1, 2, 3], + [1, 2, 3, 4, 5], + set([1, 2, 3, 4]), + [1, 2, 3, "4"], + ) + + for rect in invalid_fmts: + kwargs["rect"] = rect + + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect(**kwargs) + + def test_rect__valid_color_formats(self): + """Ensures draw rect accepts different color formats.""" + pos = (1, 1) + red_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": None, + "rect": pygame.Rect(pos, (1, 1)), + "width": 3, + } + reds = ((255, 0, 0), (255, 0, 0, 255), surface.map_rgb(red_color), red_color) + + for color in reds: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = red_color + + bounds_rect = self.draw_rect(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_rect__invalid_color_formats(self): + """Ensures draw rect handles invalid color formats correctly.""" + pos = (1, 1) + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": None, + "rect": pygame.Rect(pos, (1, 1)), + "width": 1, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_rect(**kwargs) + + def test_rect__fill(self): + self.surf_w, self.surf_h = self.surf_size = (320, 200) + self.surf = pygame.Surface(self.surf_size, pygame.SRCALPHA) + self.color = (1, 13, 24, 205) + rect = pygame.Rect(10, 10, 25, 20) + drawn = self.draw_rect(self.surf, self.color, rect, 0) + + self.assertEqual(drawn, rect) + + # Should be colored where it's supposed to be + for pt in test_utils.rect_area_pts(rect): + color_at_pt = self.surf.get_at(pt) + + self.assertEqual(color_at_pt, self.color) + + # And not where it shouldn't + for pt in test_utils.rect_outer_bounds(rect): + color_at_pt = self.surf.get_at(pt) + + self.assertNotEqual(color_at_pt, self.color) + + # Issue #310: Cannot draw rectangles that are 1 pixel high + bgcolor = pygame.Color("black") + self.surf.fill(bgcolor) + hrect = pygame.Rect(1, 1, self.surf_w - 2, 1) + vrect = pygame.Rect(1, 3, 1, self.surf_h - 4) + + drawn = self.draw_rect(self.surf, self.color, hrect, 0) + + self.assertEqual(drawn, hrect) + + x, y = hrect.topleft + w, h = hrect.size + + self.assertEqual(self.surf.get_at((x - 1, y)), bgcolor) + self.assertEqual(self.surf.get_at((x + w, y)), bgcolor) + for i in range(x, x + w): + self.assertEqual(self.surf.get_at((i, y)), self.color) + + drawn = self.draw_rect(self.surf, self.color, vrect, 0) + + self.assertEqual(drawn, vrect) + + x, y = vrect.topleft + w, h = vrect.size + + self.assertEqual(self.surf.get_at((x, y - 1)), bgcolor) + self.assertEqual(self.surf.get_at((x, y + h)), bgcolor) + for i in range(y, y + h): + self.assertEqual(self.surf.get_at((x, i)), self.color) + + def test_rect__one_pixel_lines(self): + self.surf = pygame.Surface((320, 200), pygame.SRCALPHA) + self.color = (1, 13, 24, 205) + + rect = pygame.Rect(10, 10, 56, 20) + + drawn = self.draw_rect(self.surf, self.color, rect, 1) + + self.assertEqual(drawn, rect) + + # Should be colored where it's supposed to be + for pt in test_utils.rect_perimeter_pts(drawn): + color_at_pt = self.surf.get_at(pt) + + self.assertEqual(color_at_pt, self.color) + + # And not where it shouldn't + for pt in test_utils.rect_outer_bounds(drawn): + color_at_pt = self.surf.get_at(pt) + + self.assertNotEqual(color_at_pt, self.color) + + def test_rect__draw_line_width(self): + surface = pygame.Surface((100, 100)) + surface.fill("black") + color = pygame.Color(255, 255, 255) + rect_width = 80 + rect_height = 50 + line_width = 10 + pygame.draw.rect( + surface, color, pygame.Rect(0, 0, rect_width, rect_height), line_width + ) + for i in range(line_width): + self.assertEqual(surface.get_at((i, i)), color) + self.assertEqual(surface.get_at((rect_width - i - 1, i)), color) + self.assertEqual(surface.get_at((i, rect_height - i - 1)), color) + self.assertEqual( + surface.get_at((rect_width - i - 1, rect_height - i - 1)), color + ) + self.assertEqual(surface.get_at((line_width, line_width)), (0, 0, 0)) + self.assertEqual( + surface.get_at((rect_width - line_width - 1, line_width)), (0, 0, 0) + ) + self.assertEqual( + surface.get_at((line_width, rect_height - line_width - 1)), (0, 0, 0) + ) + self.assertEqual( + surface.get_at((rect_width - line_width - 1, rect_height - line_width - 1)), + (0, 0, 0), + ) + + def test_rect__bounding_rect(self): + """Ensures draw rect returns the correct bounding rect. + + Tests rects on and off the surface and a range of width/thickness + values. + """ + rect_color = pygame.Color("red") + surf_color = pygame.Color("black") + min_width = min_height = 5 + max_width = max_height = 7 + sizes = ((min_width, min_height), (max_width, max_height)) + surface = pygame.Surface((20, 20), 0, 32) + surf_rect = surface.get_rect() + # Make a rect that is bigger than the surface to help test drawing + # rects off and partially off the surface. + big_rect = surf_rect.inflate(min_width * 2 + 1, min_height * 2 + 1) + + for pos in rect_corners_mids_and_center( + surf_rect + ) + rect_corners_mids_and_center(big_rect): + # Each of the rect's position attributes will be set to the pos + # value. + for attr in RECT_POSITION_ATTRIBUTES: + # Test using different rect sizes and thickness values. + for width, height in sizes: + rect = pygame.Rect((0, 0), (width, height)) + setattr(rect, attr, pos) + + for thickness in range(4): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_rect( + surface, rect_color, rect, thickness + ) + + # Calculating the expected_rect after the rect is + # drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect( + surface, surf_color, rect.topleft + ) + + self.assertEqual( + bounding_rect, + expected_rect, + "thickness={}".format(thickness), + ) + + def test_rect__surface_clip(self): + """Ensures draw rect respects a surface's clip area. + + Tests drawing the rect filled and unfilled. + """ + surfw = surfh = 30 + rect_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (8, 10)) + clip_rect.center = surface.get_rect().center + test_rect = clip_rect.copy() # Manages the rect's pos. + + for width in (0, 1): # Filled and unfilled. + # Test centering the rect along the clip rect's edge. + for center in rect_corners_mids_and_center(clip_rect): + # Get the expected points by drawing the rect without the + # clip area set. + test_rect.center = center + surface.set_clip(None) + surface.fill(surface_color) + self.draw_rect(surface, rect_color, test_rect, width) + expected_pts = get_color_points(surface, rect_color, clip_rect) + + # Clear the surface and set the clip area. Redraw the rect + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_rect(surface, rect_color, test_rect, width) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure only the expected_pts + # are the rect_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + expected_color = rect_color + else: + expected_color = surface_color + + self.assertEqual(surface.get_at(pt), expected_color, pt) + + surface.unlock() + + +class DrawRectTest(DrawRectMixin, DrawTestCase): + """Test draw module function rect. + + This class inherits the general tests from DrawRectMixin. It is also the + class to add any draw.rect specific tests to. + """ + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever properly supports drawing rects. +# @unittest.skip('draw_py.draw_rect not supported yet') +# class PythonDrawRectTest(DrawRectMixin, PythonDrawTestCase): +# """Test draw_py module function draw_rect. +# +# This class inherits the general tests from DrawRectMixin. It is also the +# class to add any draw_py.draw_rect specific tests to. +# """ + + +### Circle Testing ############################################################ + + +class DrawCircleMixin(object): + """Mixin tests for drawing circles. + + This class contains all the general circle drawing tests. + """ + + def test_circle__args(self): + """Ensures draw circle accepts the correct args.""" + bounds_rect = self.draw_circle( + pygame.Surface((3, 3)), (0, 10, 0, 50), (0, 0), 3, 1, 1, 0, 1, 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__args_without_width(self): + """Ensures draw circle accepts the args without a width and + quadrants.""" + bounds_rect = self.draw_circle(pygame.Surface((2, 2)), (0, 0, 0, 50), (1, 1), 1) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__args_with_negative_width(self): + """Ensures draw circle accepts the args with negative width.""" + bounds_rect = self.draw_circle( + pygame.Surface((2, 2)), (0, 0, 0, 50), (1, 1), 1, -1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + self.assertEqual(bounds_rect, pygame.Rect(1, 1, 0, 0)) + + def test_circle__args_with_width_gt_radius(self): + """Ensures draw circle accepts the args with width > radius.""" + bounds_rect = self.draw_circle( + pygame.Surface((2, 2)), (0, 0, 0, 50), (1, 1), 2, 3, 0, 0, 0, 0 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + self.assertEqual(bounds_rect, pygame.Rect(0, 0, 2, 2)) + + def test_circle__kwargs(self): + """Ensures draw circle accepts the correct kwargs + with and without a width and quadrant arguments. + """ + kwargs_list = [ + { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("yellow"), + "center": (2, 2), + "radius": 2, + "width": 1, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": False, + "draw_bottom_right": True, + }, + { + "surface": pygame.Surface((2, 1)), + "color": (0, 10, 20), + "center": (1, 1), + "radius": 1, + }, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_circle(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__kwargs_order_independent(self): + """Ensures draw circle's kwargs are not order dependent.""" + bounds_rect = self.draw_circle( + draw_top_right=False, + color=(10, 20, 30), + surface=pygame.Surface((3, 2)), + width=0, + draw_bottom_left=False, + center=(1, 0), + draw_bottom_right=False, + radius=2, + draw_top_left=True, + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__args_missing(self): + """Ensures draw circle detects any missing required args.""" + surface = pygame.Surface((1, 1)) + color = pygame.Color("blue") + + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle(surface, color, (0, 0)) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle(surface, color) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle() + + def test_circle__kwargs_missing(self): + """Ensures draw circle detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((1, 2)), + "color": pygame.Color("red"), + "center": (1, 0), + "radius": 2, + "width": 1, + "draw_top_right": False, + "draw_top_left": False, + "draw_bottom_left": False, + "draw_bottom_right": True, + } + + for name in ("radius", "center", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle(**invalid_kwargs) + + def test_circle__arg_invalid_types(self): + """Ensures draw circle detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + center = (1, 1) + radius = 1 + + with self.assertRaises(TypeError): + # Invalid draw_top_right. + bounds_rect = self.draw_circle( + surface, color, center, radius, 1, "a", 1, 1, 1 + ) + + with self.assertRaises(TypeError): + # Invalid draw_top_left. + bounds_rect = self.draw_circle( + surface, color, center, radius, 1, 1, "b", 1, 1 + ) + + with self.assertRaises(TypeError): + # Invalid draw_bottom_left. + bounds_rect = self.draw_circle( + surface, color, center, radius, 1, 1, 1, "c", 1 + ) + + with self.assertRaises(TypeError): + # Invalid draw_bottom_right. + bounds_rect = self.draw_circle( + surface, color, center, radius, 1, 1, 1, 1, "d" + ) + + with self.assertRaises(TypeError): + # Invalid width. + bounds_rect = self.draw_circle(surface, color, center, radius, "1") + + with self.assertRaises(TypeError): + # Invalid radius. + bounds_rect = self.draw_circle(surface, color, center, "2") + + with self.assertRaises(TypeError): + # Invalid center. + bounds_rect = self.draw_circle(surface, color, (1, 2, 3), radius) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_circle(surface, 2.3, center, radius) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_circle((1, 2, 3, 4), color, center, radius) + + def test_circle__kwarg_invalid_types(self): + """Ensures draw circle detects invalid kwarg types.""" + surface = pygame.Surface((3, 3)) + color = pygame.Color("green") + center = (0, 1) + radius = 1 + width = 1 + quadrant = 1 + kwargs_list = [ + { + "surface": pygame.Surface, # Invalid surface. + "color": color, + "center": center, + "radius": radius, + "width": width, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": 2.3, # Invalid color. + "center": center, + "radius": radius, + "width": width, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": (1, 1, 1), # Invalid center. + "radius": radius, + "width": width, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": center, + "radius": "1", # Invalid radius. + "width": width, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": 1.2, # Invalid width. + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": width, + "draw_top_right": "True", # Invalid draw_top_right + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": width, + "draw_top_right": True, + "draw_top_left": "True", # Invalid draw_top_left + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": width, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": 3.14, # Invalid draw_bottom_left + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": width, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": "quadrant", # Invalid draw_bottom_right + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle(**kwargs) + + def test_circle__kwarg_invalid_name(self): + """Ensures draw circle detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + center = (0, 0) + radius = 2 + kwargs_list = [ + { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": 1, + "quadrant": 1, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + }, + { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "invalid": 1, + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle(**kwargs) + + def test_circle__args_and_kwargs(self): + """Ensures draw circle accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 1)) + color = (255, 255, 0, 0) + center = (1, 0) + radius = 2 + width = 0 + draw_top_right = True + draw_top_left = False + draw_bottom_left = False + draw_bottom_right = True + kwargs = { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": width, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + } + + for name in ( + "surface", + "color", + "center", + "radius", + "width", + "draw_top_right", + "draw_top_left", + "draw_bottom_left", + "draw_bottom_right", + ): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_circle(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_circle(surface, color, **kwargs) + elif "center" == name: + bounds_rect = self.draw_circle(surface, color, center, **kwargs) + elif "radius" == name: + bounds_rect = self.draw_circle(surface, color, center, radius, **kwargs) + elif "width" == name: + bounds_rect = self.draw_circle( + surface, color, center, radius, width, **kwargs + ) + elif "draw_top_right" == name: + bounds_rect = self.draw_circle( + surface, color, center, radius, width, draw_top_right, **kwargs + ) + elif "draw_top_left" == name: + bounds_rect = self.draw_circle( + surface, + color, + center, + radius, + width, + draw_top_right, + draw_top_left, + **kwargs + ) + elif "draw_bottom_left" == name: + bounds_rect = self.draw_circle( + surface, + color, + center, + radius, + width, + draw_top_right, + draw_top_left, + draw_bottom_left, + **kwargs + ) + else: + bounds_rect = self.draw_circle( + surface, + color, + center, + radius, + width, + draw_top_right, + draw_top_left, + draw_bottom_left, + draw_bottom_right, + **kwargs + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__valid_width_values(self): + """Ensures draw circle accepts different width values.""" + center = (2, 2) + radius = 1 + pos = (center[0] - radius, center[1]) + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + color = (10, 20, 30, 255) + kwargs = { + "surface": surface, + "color": color, + "center": center, + "radius": radius, + "width": None, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + } + + for width in (-100, -10, -1, 0, 1, 10, 100): + surface.fill(surface_color) # Clear for each test. + kwargs["width"] = width + expected_color = color if width >= 0 else surface_color + + bounds_rect = self.draw_circle(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__valid_radius_values(self): + """Ensures draw circle accepts different radius values.""" + pos = center = (2, 2) + surface_color = pygame.Color("white") + surface = pygame.Surface((3, 4)) + color = (10, 20, 30, 255) + kwargs = { + "surface": surface, + "color": color, + "center": center, + "radius": None, + "width": 0, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + } + + for radius in (-10, -1, 0, 1, 10): + surface.fill(surface_color) # Clear for each test. + kwargs["radius"] = radius + expected_color = color if radius > 0 else surface_color + + bounds_rect = self.draw_circle(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__valid_center_formats(self): + """Ensures draw circle accepts different center formats.""" + expected_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((4, 4)) + kwargs = { + "surface": surface, + "color": expected_color, + "center": None, + "radius": 1, + "width": 0, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + } + x, y = 2, 2 # center position + + # The center values can be ints or floats. + for center in ((x, y), (x + 0.1, y), (x, y + 0.1), (x + 0.1, y + 0.1)): + # The center type can be a tuple/list/Vector2. + for seq_type in (tuple, list, Vector2): + surface.fill(surface_color) # Clear for each test. + kwargs["center"] = seq_type(center) + + bounds_rect = self.draw_circle(**kwargs) + + self.assertEqual(surface.get_at((x, y)), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__valid_color_formats(self): + """Ensures draw circle accepts different color formats.""" + center = (2, 2) + radius = 1 + pos = (center[0] - radius, center[1]) + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((3, 4)) + kwargs = { + "surface": surface, + "color": None, + "center": center, + "radius": radius, + "width": 0, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + } + greens = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in greens: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_circle(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_circle__invalid_color_formats(self): + """Ensures draw circle handles invalid color formats correctly.""" + kwargs = { + "surface": pygame.Surface((4, 3)), + "color": None, + "center": (1, 2), + "radius": 1, + "width": 0, + "draw_top_right": True, + "draw_top_left": True, + "draw_bottom_left": True, + "draw_bottom_right": True, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_circle(**kwargs) + + def test_circle__floats(self): + """Ensure that floats are accepted.""" + draw.circle( + surface=pygame.Surface((4, 4)), + color=(255, 255, 127), + center=(1.5, 1.5), + radius=1.3, + width=0, + draw_top_right=True, + draw_top_left=True, + draw_bottom_left=True, + draw_bottom_right=True, + ) + + draw.circle( + surface=pygame.Surface((4, 4)), + color=(255, 255, 127), + center=Vector2(1.5, 1.5), + radius=1.3, + width=0, + draw_top_right=True, + draw_top_left=True, + draw_bottom_left=True, + draw_bottom_right=True, + ) + + draw.circle(pygame.Surface((2, 2)), (0, 0, 0, 50), (1.3, 1.3), 1.2) + + # def test_circle_clip(self): + # """ maybe useful to help work out circle clip algorithm.""" + # MAX = max + # MIN = min + # posx=30 + # posy=15 + # radius=1 + # l=29 + # t=14 + # r=30 + # b=16 + # clip_rect_x=0 + # clip_rect_y=0 + # clip_rect_w=30 + # clip_rect_h=30 + + # l = MAX(posx - radius, clip_rect_x) + # t = MAX(posy - radius, clip_rect_y) + # r = MIN(posx + radius, clip_rect_x + clip_rect_w) + # b = MIN(posy + radius, clip_rect_y + clip_rect_h) + + # l, t, MAX(r - l, 0), MAX(b - t, 0) + + def test_circle__bounding_rect(self): + """Ensures draw circle returns the correct bounding rect. + + Tests circles on and off the surface and a range of width/thickness + values. + """ + circle_color = pygame.Color("red") + surf_color = pygame.Color("black") + max_radius = 3 + surface = pygame.Surface((30, 30), 0, 32) + surf_rect = surface.get_rect() + # Make a rect that is bigger than the surface to help test drawing + # circles off and partially off the surface. Make this rect such that + # when centering the test circle on one of its corners, the circle is + # drawn fully off the test surface, but a rect bounding the circle + # would still overlap with the test surface. + big_rect = surf_rect.inflate(max_radius * 2 - 1, max_radius * 2 - 1) + + for pos in rect_corners_mids_and_center( + surf_rect + ) + rect_corners_mids_and_center(big_rect): + # Test using different radius and thickness values. + for radius in range(max_radius + 1): + for thickness in range(radius + 1): + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_circle( + surface, circle_color, pos, radius, thickness + ) + + # Calculating the expected_rect after the circle is + # drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect(surface, surf_color, pos) + # print("pos:%s:, radius:%s:, thickness:%s:" % (pos, radius, thickness)) + self.assertEqual(bounding_rect, expected_rect) + + def test_circle_negative_radius(self): + """Ensures negative radius circles return zero sized bounding rect.""" + surf = pygame.Surface((200, 200)) + color = (0, 0, 0, 50) + center = surf.get_height() // 2, surf.get_height() // 2 + + bounding_rect = self.draw_circle(surf, color, center, radius=-1, width=1) + self.assertEqual(bounding_rect.size, (0, 0)) + + def test_circle_zero_radius(self): + """Ensures zero radius circles does not draw a center pixel. + + NOTE: This is backwards incompatible behaviour with 1.9.x. + """ + surf = pygame.Surface((200, 200)) + circle_color = pygame.Color("red") + surf_color = pygame.Color("black") + surf.fill((0, 0, 0)) + center = (100, 100) + radius = 0 + width = 1 + + bounding_rect = self.draw_circle(surf, circle_color, center, radius, width) + expected_rect = create_bounding_rect(surf, surf_color, center) + self.assertEqual(bounding_rect, expected_rect) + self.assertEqual(bounding_rect, pygame.Rect(100, 100, 0, 0)) + + def test_circle__surface_clip(self): + """Ensures draw circle respects a surface's clip area. + + Tests drawing the circle filled and unfilled. + """ + surfw = surfh = 25 + circle_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (10, 10)) + clip_rect.center = surface.get_rect().center + radius = clip_rect.w // 2 + 1 + + for width in (0, 1): # Filled and unfilled. + # Test centering the circle along the clip rect's edge. + for center in rect_corners_mids_and_center(clip_rect): + # Get the expected points by drawing the circle without the + # clip area set. + surface.set_clip(None) + surface.fill(surface_color) + self.draw_circle(surface, circle_color, center, radius, width) + expected_pts = get_color_points(surface, circle_color, clip_rect) + + # Clear the surface and set the clip area. Redraw the circle + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_circle(surface, circle_color, center, radius, width) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure only the expected_pts + # are the circle_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + expected_color = circle_color + else: + expected_color = surface_color + + self.assertEqual(surface.get_at(pt), expected_color, pt) + + surface.unlock() + + def test_circle_shape(self): + """Ensures there are no holes in the circle, and no overdrawing. + + Tests drawing a thick circle. + Measures the distance of the drawn pixels from the circle center. + """ + surfw = surfh = 100 + circle_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + (cx, cy) = center = (50, 50) + radius = 45 + width = 25 + + dest_rect = self.draw_circle(surface, circle_color, center, radius, width) + + for pt in test_utils.rect_area_pts(dest_rect): + x, y = pt + sqr_distance = (x - cx) ** 2 + (y - cy) ** 2 + if (radius - width + 1) ** 2 < sqr_distance < (radius - 1) ** 2: + self.assertEqual(surface.get_at(pt), circle_color) + if ( + sqr_distance < (radius - width - 1) ** 2 + or sqr_distance > (radius + 1) ** 2 + ): + self.assertEqual(surface.get_at(pt), surface_color) + + def test_circle__diameter(self): + """Ensures draw circle is twice size of radius high and wide.""" + surf = pygame.Surface((200, 200)) + color = (0, 0, 0, 50) + center = surf.get_height() // 2, surf.get_height() // 2 + width = 1 + radius = 6 + for radius in range(1, 65): + bounding_rect = self.draw_circle(surf, color, center, radius, width) + self.assertEqual(bounding_rect.width, radius * 2) + self.assertEqual(bounding_rect.height, radius * 2) + + +class DrawCircleTest(DrawCircleMixin, DrawTestCase): + """Test draw module function circle. + + This class inherits the general tests from DrawCircleMixin. It is also + the class to add any draw.circle specific tests to. + """ + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever properly supports drawing circles. +# @unittest.skip('draw_py.draw_circle not supported yet') +# class PythonDrawCircleTest(DrawCircleMixin, PythonDrawTestCase): +# """Test draw_py module function draw_circle." +# +# This class inherits the general tests from DrawCircleMixin. It is also +# the class to add any draw_py.draw_circle specific tests to. +# """ + + +### Arc Testing ############################################################### + + +class DrawArcMixin(object): + """Mixin tests for drawing arcs. + + This class contains all the general arc drawing tests. + """ + + def test_arc__args(self): + """Ensures draw arc accepts the correct args.""" + bounds_rect = self.draw_arc( + pygame.Surface((3, 3)), (0, 10, 0, 50), (1, 1, 2, 2), 0, 1, 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__args_without_width(self): + """Ensures draw arc accepts the args without a width.""" + bounds_rect = self.draw_arc( + pygame.Surface((2, 2)), (1, 1, 1, 99), pygame.Rect((0, 0), (2, 2)), 1.1, 2.1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__args_with_negative_width(self): + """Ensures draw arc accepts the args with negative width.""" + bounds_rect = self.draw_arc( + pygame.Surface((3, 3)), (10, 10, 50, 50), (1, 1, 2, 2), 0, 1, -1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + self.assertEqual(bounds_rect, pygame.Rect(1, 1, 0, 0)) + + def test_arc__args_with_width_gt_radius(self): + """Ensures draw arc accepts the args with + width > rect.w // 2 and width > rect.h // 2. + """ + rect = pygame.Rect((0, 0), (4, 4)) + bounds_rect = self.draw_arc( + pygame.Surface((3, 3)), (10, 10, 50, 50), rect, 0, 45, rect.w // 2 + 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + bounds_rect = self.draw_arc( + pygame.Surface((3, 3)), (10, 10, 50, 50), rect, 0, 45, rect.h // 2 + 1 + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__kwargs(self): + """Ensures draw arc accepts the correct kwargs + with and without a width arg. + """ + kwargs_list = [ + { + "surface": pygame.Surface((4, 4)), + "color": pygame.Color("yellow"), + "rect": pygame.Rect((0, 0), (3, 2)), + "start_angle": 0.5, + "stop_angle": 3, + "width": 1, + }, + { + "surface": pygame.Surface((2, 1)), + "color": (0, 10, 20), + "rect": (0, 0, 2, 2), + "start_angle": 1, + "stop_angle": 3.1, + }, + ] + + for kwargs in kwargs_list: + bounds_rect = self.draw_arc(**kwargs) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__kwargs_order_independent(self): + """Ensures draw arc's kwargs are not order dependent.""" + bounds_rect = self.draw_arc( + stop_angle=1, + start_angle=2.2, + color=(1, 2, 3), + surface=pygame.Surface((3, 2)), + width=1, + rect=pygame.Rect((1, 0), (2, 3)), + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__args_missing(self): + """Ensures draw arc detects any missing required args.""" + surface = pygame.Surface((1, 1)) + color = pygame.Color("red") + rect = pygame.Rect((0, 0), (2, 2)) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(surface, color, rect, 0.1) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(surface, color, rect) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(surface, color) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(surface) + + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc() + + def test_arc__kwargs_missing(self): + """Ensures draw arc detects any missing required kwargs.""" + kwargs = { + "surface": pygame.Surface((1, 2)), + "color": pygame.Color("red"), + "rect": pygame.Rect((1, 0), (2, 2)), + "start_angle": 0.1, + "stop_angle": 2, + "width": 1, + } + + for name in ("stop_angle", "start_angle", "rect", "color", "surface"): + invalid_kwargs = dict(kwargs) + invalid_kwargs.pop(name) # Pop from a copy. + + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(**invalid_kwargs) + + def test_arc__arg_invalid_types(self): + """Ensures draw arc detects invalid arg types.""" + surface = pygame.Surface((2, 2)) + color = pygame.Color("blue") + rect = pygame.Rect((1, 1), (3, 3)) + + with self.assertRaises(TypeError): + # Invalid width. + bounds_rect = self.draw_arc(surface, color, rect, 0, 1, "1") + + with self.assertRaises(TypeError): + # Invalid stop_angle. + bounds_rect = self.draw_arc(surface, color, rect, 0, "1", 1) + + with self.assertRaises(TypeError): + # Invalid start_angle. + bounds_rect = self.draw_arc(surface, color, rect, "1", 0, 1) + + with self.assertRaises(TypeError): + # Invalid rect. + bounds_rect = self.draw_arc(surface, color, (1, 2, 3, 4, 5), 0, 1, 1) + + with self.assertRaises(TypeError): + # Invalid color. + bounds_rect = self.draw_arc(surface, 2.3, rect, 0, 1, 1) + + with self.assertRaises(TypeError): + # Invalid surface. + bounds_rect = self.draw_arc(rect, color, rect, 0, 1, 1) + + def test_arc__kwarg_invalid_types(self): + """Ensures draw arc detects invalid kwarg types.""" + surface = pygame.Surface((3, 3)) + color = pygame.Color("green") + rect = pygame.Rect((0, 1), (4, 2)) + start = 3 + stop = 4 + kwargs_list = [ + { + "surface": pygame.Surface, # Invalid surface. + "color": color, + "rect": rect, + "start_angle": start, + "stop_angle": stop, + "width": 1, + }, + { + "surface": surface, + "color": 2.3, # Invalid color. + "rect": rect, + "start_angle": start, + "stop_angle": stop, + "width": 1, + }, + { + "surface": surface, + "color": color, + "rect": (0, 0, 0), # Invalid rect. + "start_angle": start, + "stop_angle": stop, + "width": 1, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "start_angle": "1", # Invalid start_angle. + "stop_angle": stop, + "width": 1, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "start_angle": start, + "stop_angle": "1", # Invalid stop_angle. + "width": 1, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "start_angle": start, + "stop_angle": stop, + "width": 1.1, + }, + ] # Invalid width. + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(**kwargs) + + def test_arc__kwarg_invalid_name(self): + """Ensures draw arc detects invalid kwarg names.""" + surface = pygame.Surface((2, 3)) + color = pygame.Color("cyan") + rect = pygame.Rect((0, 1), (2, 2)) + start = 0.9 + stop = 2.3 + kwargs_list = [ + { + "surface": surface, + "color": color, + "rect": rect, + "start_angle": start, + "stop_angle": stop, + "width": 1, + "invalid": 1, + }, + { + "surface": surface, + "color": color, + "rect": rect, + "start_angle": start, + "stop_angle": stop, + "invalid": 1, + }, + ] + + for kwargs in kwargs_list: + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(**kwargs) + + def test_arc__args_and_kwargs(self): + """Ensures draw arc accepts a combination of args/kwargs""" + surface = pygame.Surface((3, 1)) + color = (255, 255, 0, 0) + rect = pygame.Rect((1, 0), (2, 3)) + start = 0.6 + stop = 2 + width = 1 + kwargs = { + "surface": surface, + "color": color, + "rect": rect, + "start_angle": start, + "stop_angle": stop, + "width": width, + } + + for name in ("surface", "color", "rect", "start_angle", "stop_angle"): + kwargs.pop(name) + + if "surface" == name: + bounds_rect = self.draw_arc(surface, **kwargs) + elif "color" == name: + bounds_rect = self.draw_arc(surface, color, **kwargs) + elif "rect" == name: + bounds_rect = self.draw_arc(surface, color, rect, **kwargs) + elif "start_angle" == name: + bounds_rect = self.draw_arc(surface, color, rect, start, **kwargs) + elif "stop_angle" == name: + bounds_rect = self.draw_arc(surface, color, rect, start, stop, **kwargs) + else: + bounds_rect = self.draw_arc( + surface, color, rect, start, stop, width, **kwargs + ) + + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__valid_width_values(self): + """Ensures draw arc accepts different width values.""" + arc_color = pygame.Color("yellow") + surface_color = pygame.Color("white") + surface = pygame.Surface((6, 6)) + rect = pygame.Rect((0, 0), (4, 4)) + rect.center = surface.get_rect().center + pos = rect.centerx + 1, rect.centery + 1 + kwargs = { + "surface": surface, + "color": arc_color, + "rect": rect, + "start_angle": 0, + "stop_angle": 7, + "width": None, + } + + for width in (-50, -10, -3, -2, -1, 0, 1, 2, 3, 10, 50): + msg = "width={}".format(width) + surface.fill(surface_color) # Clear for each test. + kwargs["width"] = width + expected_color = arc_color if width > 0 else surface_color + + bounds_rect = self.draw_arc(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color, msg) + self.assertIsInstance(bounds_rect, pygame.Rect, msg) + + def test_arc__valid_stop_angle_values(self): + """Ensures draw arc accepts different stop_angle values.""" + expected_color = pygame.Color("blue") + surface_color = pygame.Color("white") + surface = pygame.Surface((6, 6)) + rect = pygame.Rect((0, 0), (4, 4)) + rect.center = surface.get_rect().center + pos = rect.centerx, rect.centery + 1 + kwargs = { + "surface": surface, + "color": expected_color, + "rect": rect, + "start_angle": -17, + "stop_angle": None, + "width": 1, + } + + for stop_angle in (-10, -5.5, -1, 0, 1, 5.5, 10): + msg = "stop_angle={}".format(stop_angle) + surface.fill(surface_color) # Clear for each test. + kwargs["stop_angle"] = stop_angle + + bounds_rect = self.draw_arc(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color, msg) + self.assertIsInstance(bounds_rect, pygame.Rect, msg) + + def test_arc__valid_start_angle_values(self): + """Ensures draw arc accepts different start_angle values.""" + expected_color = pygame.Color("blue") + surface_color = pygame.Color("white") + surface = pygame.Surface((6, 6)) + rect = pygame.Rect((0, 0), (4, 4)) + rect.center = surface.get_rect().center + pos = rect.centerx + 1, rect.centery + 1 + kwargs = { + "surface": surface, + "color": expected_color, + "rect": rect, + "start_angle": None, + "stop_angle": 17, + "width": 1, + } + + for start_angle in (-10.0, -5.5, -1, 0, 1, 5.5, 10.0): + msg = "start_angle={}".format(start_angle) + surface.fill(surface_color) # Clear for each test. + kwargs["start_angle"] = start_angle + + bounds_rect = self.draw_arc(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color, msg) + self.assertIsInstance(bounds_rect, pygame.Rect, msg) + + def test_arc__valid_rect_formats(self): + """Ensures draw arc accepts different rect formats.""" + expected_color = pygame.Color("red") + surface_color = pygame.Color("black") + surface = pygame.Surface((6, 6)) + rect = pygame.Rect((0, 0), (4, 4)) + rect.center = surface.get_rect().center + pos = rect.centerx + 1, rect.centery + 1 + kwargs = { + "surface": surface, + "color": expected_color, + "rect": None, + "start_angle": 0, + "stop_angle": 7, + "width": 1, + } + rects = (rect, (rect.topleft, rect.size), (rect.x, rect.y, rect.w, rect.h)) + + for rect in rects: + surface.fill(surface_color) # Clear for each test. + kwargs["rect"] = rect + + bounds_rect = self.draw_arc(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__valid_color_formats(self): + """Ensures draw arc accepts different color formats.""" + green_color = pygame.Color("green") + surface_color = pygame.Color("black") + surface = pygame.Surface((6, 6)) + rect = pygame.Rect((0, 0), (4, 4)) + rect.center = surface.get_rect().center + pos = rect.centerx + 1, rect.centery + 1 + kwargs = { + "surface": surface, + "color": None, + "rect": rect, + "start_angle": 0, + "stop_angle": 7, + "width": 1, + } + greens = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(green_color), + green_color, + ) + + for color in greens: + surface.fill(surface_color) # Clear for each test. + kwargs["color"] = color + + if isinstance(color, int): + expected_color = surface.unmap_rgb(color) + else: + expected_color = green_color + + bounds_rect = self.draw_arc(**kwargs) + + self.assertEqual(surface.get_at(pos), expected_color) + self.assertIsInstance(bounds_rect, pygame.Rect) + + def test_arc__invalid_color_formats(self): + """Ensures draw arc handles invalid color formats correctly.""" + pos = (1, 1) + surface = pygame.Surface((4, 3)) + kwargs = { + "surface": surface, + "color": None, + "rect": pygame.Rect(pos, (2, 2)), + "start_angle": 5, + "stop_angle": 6.1, + "width": 1, + } + + for expected_color in (2.3, self): + kwargs["color"] = expected_color + + with self.assertRaises(TypeError): + bounds_rect = self.draw_arc(**kwargs) + + def todo_test_arc(self): + """Ensure draw arc works correctly.""" + self.fail() + + def test_arc__bounding_rect(self): + """Ensures draw arc returns the correct bounding rect. + + Tests arcs on and off the surface and a range of width/thickness + values. + """ + arc_color = pygame.Color("red") + surf_color = pygame.Color("black") + min_width = min_height = 5 + max_width = max_height = 7 + sizes = ((min_width, min_height), (max_width, max_height)) + surface = pygame.Surface((20, 20), 0, 32) + surf_rect = surface.get_rect() + # Make a rect that is bigger than the surface to help test drawing + # arcs off and partially off the surface. + big_rect = surf_rect.inflate(min_width * 2 + 1, min_height * 2 + 1) + + # Max angle allows for a full circle to be drawn. + start_angle = 0 + stop_angles = (0, 2, 3, 5, math.ceil(2 * math.pi)) + + for pos in rect_corners_mids_and_center( + surf_rect + ) + rect_corners_mids_and_center(big_rect): + # Each of the arc's rect position attributes will be set to the pos + # value. + for attr in RECT_POSITION_ATTRIBUTES: + # Test using different rect sizes, thickness values and stop + # angles. + for width, height in sizes: + arc_rect = pygame.Rect((0, 0), (width, height)) + setattr(arc_rect, attr, pos) + + for thickness in (0, 1, 2, 3, min(width, height)): + for stop_angle in stop_angles: + surface.fill(surf_color) # Clear for each test. + + bounding_rect = self.draw_arc( + surface, + arc_color, + arc_rect, + start_angle, + stop_angle, + thickness, + ) + + # Calculating the expected_rect after the arc + # is drawn (it uses what is actually drawn). + expected_rect = create_bounding_rect( + surface, surf_color, arc_rect.topleft + ) + + self.assertEqual( + bounding_rect, + expected_rect, + "thickness={}".format(thickness), + ) + + def test_arc__surface_clip(self): + """Ensures draw arc respects a surface's clip area.""" + surfw = surfh = 30 + start = 0.1 + end = 0 # end < start so a full circle will be drawn + arc_color = pygame.Color("red") + surface_color = pygame.Color("green") + surface = pygame.Surface((surfw, surfh)) + surface.fill(surface_color) + + clip_rect = pygame.Rect((0, 0), (11, 11)) + clip_rect.center = surface.get_rect().center + pos_rect = clip_rect.copy() # Manages the arc's pos. + + for thickness in (1, 3): # Different line widths. + # Test centering the arc along the clip rect's edge. + for center in rect_corners_mids_and_center(clip_rect): + # Get the expected points by drawing the arc without the + # clip area set. + pos_rect.center = center + surface.set_clip(None) + surface.fill(surface_color) + self.draw_arc(surface, arc_color, pos_rect, start, end, thickness) + expected_pts = get_color_points(surface, arc_color, clip_rect) + + # Clear the surface and set the clip area. Redraw the arc + # and check that only the clip area is modified. + surface.fill(surface_color) + surface.set_clip(clip_rect) + + self.draw_arc(surface, arc_color, pos_rect, start, end, thickness) + + surface.lock() # For possible speed up. + + # Check all the surface points to ensure only the expected_pts + # are the arc_color. + for pt in ((x, y) for x in range(surfw) for y in range(surfh)): + if pt in expected_pts: + expected_color = arc_color + else: + expected_color = surface_color + + self.assertEqual(surface.get_at(pt), expected_color, pt) + + surface.unlock() + + +class DrawArcTest(DrawArcMixin, DrawTestCase): + """Test draw module function arc. + + This class inherits the general tests from DrawArcMixin. It is also the + class to add any draw.arc specific tests to. + """ + + +# Commented out to avoid cluttering the test output. Add back in if draw_py +# ever properly supports drawing arcs. +# @unittest.skip('draw_py.draw_arc not supported yet') +# class PythonDrawArcTest(DrawArcMixin, PythonDrawTestCase): +# """Test draw_py module function draw_arc. +# +# This class inherits the general tests from DrawArcMixin. It is also the +# class to add any draw_py.draw_arc specific tests to. +# """ + + +### Draw Module Testing ####################################################### + + +class DrawModuleTest(unittest.TestCase): + """General draw module tests.""" + + def test_path_data_validation(self): + """Test validation of multi-point drawing methods. + + See bug #521 + """ + surf = pygame.Surface((5, 5)) + rect = pygame.Rect(0, 0, 5, 5) + bad_values = ( + "text", + b"bytes", + 1 + 1j, # string, bytes, complex, + object(), + (lambda x: x), + ) # object, function + bad_points = list(bad_values) + [(1,), (1, 2, 3)] # wrong tuple length + bad_points.extend((1, v) for v in bad_values) # one wrong value + good_path = [(1, 1), (1, 3), (3, 3), (3, 1)] + # A) draw.lines + check_pts = [(x, y) for x in range(5) for y in range(5)] + + for method, is_polgon in ( + (draw.lines, 0), + (draw.aalines, 0), + (draw.polygon, 1), + ): + for val in bad_values: + # 1. at the beginning + draw.rect(surf, RED, rect, 0) + with self.assertRaises(TypeError): + if is_polgon: + method(surf, GREEN, [val] + good_path, 0) + else: + method(surf, GREEN, True, [val] + good_path) + + # make sure, nothing was drawn : + self.assertTrue(all(surf.get_at(pt) == RED for pt in check_pts)) + + # 2. not at the beginning (was not checked) + draw.rect(surf, RED, rect, 0) + with self.assertRaises(TypeError): + path = good_path[:2] + [val] + good_path[2:] + if is_polgon: + method(surf, GREEN, path, 0) + else: + method(surf, GREEN, True, path) + + # make sure, nothing was drawn : + self.assertTrue(all(surf.get_at(pt) == RED for pt in check_pts)) + + def test_color_validation(self): + surf = pygame.Surface((10, 10)) + colors = 123456, (1, 10, 100), RED, "#ab12df", "red" + points = ((0, 0), (1, 1), (1, 0)) + + # 1. valid colors + for col in colors: + draw.line(surf, col, (0, 0), (1, 1)) + draw.aaline(surf, col, (0, 0), (1, 1)) + draw.aalines(surf, col, True, points) + draw.lines(surf, col, True, points) + draw.arc(surf, col, pygame.Rect(0, 0, 3, 3), 15, 150) + draw.ellipse(surf, col, pygame.Rect(0, 0, 3, 6), 1) + draw.circle(surf, col, (7, 3), 2) + draw.polygon(surf, col, points, 0) + + # 2. invalid colors + for col in (1.256, object(), None): + with self.assertRaises(TypeError): + draw.line(surf, col, (0, 0), (1, 1)) + + with self.assertRaises(TypeError): + draw.aaline(surf, col, (0, 0), (1, 1)) + + with self.assertRaises(TypeError): + draw.aalines(surf, col, True, points) + + with self.assertRaises(TypeError): + draw.lines(surf, col, True, points) + + with self.assertRaises(TypeError): + draw.arc(surf, col, pygame.Rect(0, 0, 3, 3), 15, 150) + + with self.assertRaises(TypeError): + draw.ellipse(surf, col, pygame.Rect(0, 0, 3, 6), 1) + + with self.assertRaises(TypeError): + draw.circle(surf, col, (7, 3), 2) + + with self.assertRaises(TypeError): + draw.polygon(surf, col, points, 0) + + +############################################################################### + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/event_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/event_test.py new file mode 100644 index 0000000..8d7dfe0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/event_test.py @@ -0,0 +1,840 @@ +import os +import sys +import unittest +import collections + +import pygame + + +EVENT_TYPES = ( + # pygame.NOEVENT, + # pygame.ACTIVEEVENT, + pygame.KEYDOWN, + pygame.KEYUP, + pygame.MOUSEMOTION, + pygame.MOUSEBUTTONDOWN, + pygame.MOUSEBUTTONUP, + pygame.JOYAXISMOTION, + pygame.JOYBALLMOTION, + pygame.JOYHATMOTION, + pygame.JOYBUTTONDOWN, + pygame.JOYBUTTONUP, + pygame.VIDEORESIZE, + pygame.VIDEOEXPOSE, + pygame.QUIT, + pygame.SYSWMEVENT, + pygame.USEREVENT, + # pygame.NUMEVENTS, +) + +EVENT_TEST_PARAMS = collections.defaultdict(dict) +EVENT_TEST_PARAMS.update( + { + pygame.KEYDOWN: {"key": pygame.K_SPACE}, + pygame.KEYUP: {"key": pygame.K_SPACE}, + pygame.MOUSEMOTION: dict(), + pygame.MOUSEBUTTONDOWN: dict(button=1), + pygame.MOUSEBUTTONUP: dict(button=1), + } +) + + +NAMES_AND_EVENTS = ( + ("NoEvent", pygame.NOEVENT), + ("ActiveEvent", pygame.ACTIVEEVENT), + ("KeyDown", pygame.KEYDOWN), + ("KeyUp", pygame.KEYUP), + ("MouseMotion", pygame.MOUSEMOTION), + ("MouseButtonDown", pygame.MOUSEBUTTONDOWN), + ("MouseButtonUp", pygame.MOUSEBUTTONUP), + ("JoyAxisMotion", pygame.JOYAXISMOTION), + ("JoyBallMotion", pygame.JOYBALLMOTION), + ("JoyHatMotion", pygame.JOYHATMOTION), + ("JoyButtonDown", pygame.JOYBUTTONDOWN), + ("JoyButtonUp", pygame.JOYBUTTONUP), + ("VideoResize", pygame.VIDEORESIZE), + ("VideoExpose", pygame.VIDEOEXPOSE), + ("Quit", pygame.QUIT), + ("SysWMEvent", pygame.SYSWMEVENT), + ("MidiIn", pygame.MIDIIN), + ("MidiOut", pygame.MIDIOUT), + ("UserEvent", pygame.USEREVENT), + ("Unknown", 0xFFFF), + ("FingerMotion", pygame.FINGERMOTION), + ("FingerDown", pygame.FINGERDOWN), + ("FingerUp", pygame.FINGERUP), + ("MultiGesture", pygame.MULTIGESTURE), + ("MouseWheel", pygame.MOUSEWHEEL), + ("TextInput", pygame.TEXTINPUT), + ("TextEditing", pygame.TEXTEDITING), + ("ControllerAxisMotion", pygame.CONTROLLERAXISMOTION), + ("ControllerButtonDown", pygame.CONTROLLERBUTTONDOWN), + ("ControllerButtonUp", pygame.CONTROLLERBUTTONUP), + ("ControllerDeviceAdded", pygame.CONTROLLERDEVICEADDED), + ("ControllerDeviceRemoved", pygame.CONTROLLERDEVICEREMOVED), + ("ControllerDeviceMapped", pygame.CONTROLLERDEVICEREMAPPED), + ("DropFile", pygame.DROPFILE), +) + +# Add in any SDL 2.0.4 specific events. +if pygame.get_sdl_version() >= (2, 0, 4): + NAMES_AND_EVENTS += ( + ("AudioDeviceAdded", pygame.AUDIODEVICEADDED), + ("AudioDeviceRemoved", pygame.AUDIODEVICEREMOVED), + ) + +# Add in any SDL 2.0.5 specific events. +if pygame.get_sdl_version() >= (2, 0, 5): + NAMES_AND_EVENTS += ( + ("DropText", pygame.DROPTEXT), + ("DropBegin", pygame.DROPBEGIN), + ("DropComplete", pygame.DROPCOMPLETE), + ) + + +class EventTypeTest(unittest.TestCase): + def test_Event(self): + """Ensure an Event object can be created.""" + e = pygame.event.Event(pygame.USEREVENT, some_attr=1, other_attr="1") + + self.assertEqual(e.some_attr, 1) + self.assertEqual(e.other_attr, "1") + + # Event now uses tp_dictoffset and tp_members: request 62 + # on Motherhamster Bugzilla. + self.assertEqual(e.type, pygame.USEREVENT) + self.assertIs(e.dict, e.__dict__) + + e.some_attr = 12 + + self.assertEqual(e.some_attr, 12) + + e.new_attr = 15 + + self.assertEqual(e.new_attr, 15) + + self.assertRaises(AttributeError, setattr, e, "type", 0) + self.assertRaises(AttributeError, setattr, e, "dict", None) + + # Ensure attributes are visible to dir(), part of the original + # posted request. + d = dir(e) + attrs = ("type", "dict", "__dict__", "some_attr", "other_attr", "new_attr") + + for attr in attrs: + self.assertIn(attr, d) + + def test_as_str(self): + # Bug reported on Pygame mailing list July 24, 2011: + # For Python 3.x str(event) to raises an UnicodeEncodeError when + # an event attribute is a string with a non-ascii character. + try: + str(pygame.event.Event(EVENT_TYPES[0], a="\xed")) + except UnicodeEncodeError: + self.fail("Event object raised exception for non-ascii character") + # Passed. + + +race_condition_notification = """ +This test is dependent on timing. The event queue is cleared in preparation for +tests. There is a small window where outside events from the OS may have effected +results. Try running the test again. +""" + + +class EventModuleArgsTest(unittest.TestCase): + def setUp(self): + pygame.display.init() + pygame.event.clear() + + def tearDown(self): + pygame.display.quit() + + def test_get(self): + pygame.event.get() + pygame.event.get(None) + pygame.event.get(None, True) + + pygame.event.get(pump=False) + pygame.event.get(pump=True) + pygame.event.get(eventtype=None) + pygame.event.get(eventtype=[pygame.KEYUP, pygame.KEYDOWN]) + pygame.event.get(eventtype=pygame.USEREVENT, pump=False) + + def test_clear(self): + pygame.event.clear() + pygame.event.clear(None) + pygame.event.clear(None, True) + + pygame.event.clear(pump=False) + pygame.event.clear(pump=True) + pygame.event.clear(eventtype=None) + pygame.event.clear(eventtype=[pygame.KEYUP, pygame.KEYDOWN]) + pygame.event.clear(eventtype=pygame.USEREVENT, pump=False) + + def test_peek(self): + pygame.event.peek() + pygame.event.peek(None) + pygame.event.peek(None, True) + + pygame.event.peek(pump=False) + pygame.event.peek(pump=True) + pygame.event.peek(eventtype=None) + pygame.event.peek(eventtype=[pygame.KEYUP, pygame.KEYDOWN]) + pygame.event.peek(eventtype=pygame.USEREVENT, pump=False) + + +class EventCustomTypeTest(unittest.TestCase): + """Those tests are special in that they need the _custom_event counter to + be reset before and/or after being run.""" + + def setUp(self): + pygame.quit() + pygame.init() + pygame.display.init() + + def tearDown(self): + pygame.quit() + + def test_custom_type(self): + self.assertEqual(pygame.event.custom_type(), pygame.USEREVENT + 1) + atype = pygame.event.custom_type() + atype2 = pygame.event.custom_type() + + self.assertEqual(atype, atype2 - 1) + + ev = pygame.event.Event(atype) + pygame.event.post(ev) + queue = pygame.event.get(atype) + self.assertEqual(len(queue), 1) + self.assertEqual(queue[0].type, atype) + + def test_custom_type__end_boundary(self): + """Ensure custom_type() raises error when no more custom types. + + The last allowed custom type number should be (pygame.NUMEVENTS - 1). + """ + start = pygame.event.custom_type() + 1 + for i in range(start, pygame.NUMEVENTS): + last = pygame.event.custom_type() + self.assertEqual(last, pygame.NUMEVENTS - 1) + with self.assertRaises(pygame.error): + pygame.event.custom_type() + + def test_custom_type__reset(self): + """Ensure custom events get 'deregistered' by quit().""" + before = pygame.event.custom_type() + self.assertEqual(before, pygame.event.custom_type() - 1) + pygame.quit() + pygame.init() + pygame.display.init() + self.assertEqual(before, pygame.event.custom_type()) + + +class EventModuleTest(unittest.TestCase): + def _assertCountEqual(self, *args, **kwargs): + # Handle method name differences between Python versions. + # Is this still needed? + self.assertCountEqual(*args, **kwargs) + + def _assertExpectedEvents(self, expected, got): + """Find events like expected events, raise on unexpected or missing, + ignore additional event properties if expected properties are present.""" + + # This does greedy matching, don't encode an NP-hard problem + # into your input data, *please* + items_left = got[:] + for expected_element in expected: + for item in items_left: + for key in expected_element.__dict__: + if item.__dict__[key] != expected_element.__dict__[key]: + break + else: + # found item! + items_left.remove(item) + break + else: + raise AssertionError( + "Expected " + + str(expected_element) + + " among remaining events " + + str(items_left) + + " out of " + + str(got) + ) + if len(items_left) > 0: + raise AssertionError("Unexpected Events: " + str(items_left)) + + def setUp(self): + pygame.display.init() + pygame.event.clear() # flush events + + def tearDown(self): + pygame.event.clear() # flush events + pygame.display.quit() + + def test_event_numevents(self): + """Ensures NUMEVENTS does not exceed the maximum SDL number of events.""" + # Ref: https://www.libsdl.org/tmp/SDL/include/SDL_events.h + MAX_SDL_EVENTS = 0xFFFF # SDL_LASTEVENT = 0xFFFF + + self.assertLessEqual(pygame.NUMEVENTS, MAX_SDL_EVENTS) + + def test_event_attribute(self): + e1 = pygame.event.Event(pygame.USEREVENT, attr1="attr1") + self.assertEqual(e1.attr1, "attr1") + + def test_set_blocked(self): + """Ensure events can be blocked from the queue.""" + event = EVENT_TYPES[0] + pygame.event.set_blocked(event) + + self.assertTrue(pygame.event.get_blocked(event)) + + pygame.event.post( + pygame.event.Event(event, **EVENT_TEST_PARAMS[EVENT_TYPES[0]]) + ) + ret = pygame.event.get() + should_be_blocked = [e for e in ret if e.type == event] + + self.assertEqual(should_be_blocked, []) + + def test_set_blocked__event_sequence(self): + """Ensure a sequence of event types can be blocked.""" + event_types = [ + pygame.KEYDOWN, + pygame.KEYUP, + pygame.MOUSEMOTION, + pygame.MOUSEBUTTONDOWN, + pygame.MOUSEBUTTONUP, + ] + + pygame.event.set_blocked(event_types) + + for etype in event_types: + self.assertTrue(pygame.event.get_blocked(etype)) + + def test_set_blocked_all(self): + """Ensure all events can be unblocked at once.""" + pygame.event.set_blocked(None) + + for e in EVENT_TYPES: + self.assertTrue(pygame.event.get_blocked(e)) + + def test_post__and_poll(self): + """Ensure events can be posted to the queue.""" + e1 = pygame.event.Event(pygame.USEREVENT, attr1="attr1") + pygame.event.post(e1) + posted_event = pygame.event.poll() + + self.assertEqual(e1.attr1, posted_event.attr1, race_condition_notification) + + # fuzzing event types + for i in range(1, 13): + pygame.event.post( + pygame.event.Event(EVENT_TYPES[i], **EVENT_TEST_PARAMS[EVENT_TYPES[i]]) + ) + + self.assertEqual( + pygame.event.poll().type, EVENT_TYPES[i], race_condition_notification + ) + + def test_post_and_get_keydown(self): + """Ensure keydown events can be posted to the queue.""" + activemodkeys = pygame.key.get_mods() + + events = [ + pygame.event.Event(pygame.KEYDOWN, key=pygame.K_p), + pygame.event.Event(pygame.KEYDOWN, key=pygame.K_y, mod=activemodkeys), + pygame.event.Event(pygame.KEYDOWN, key=pygame.K_g, unicode="g"), + pygame.event.Event(pygame.KEYDOWN, key=pygame.K_a, unicode=None), + pygame.event.Event(pygame.KEYDOWN, key=pygame.K_m, mod=None, window=None), + pygame.event.Event( + pygame.KEYDOWN, key=pygame.K_e, mod=activemodkeys, unicode="e" + ), + ] + + for e in events: + pygame.event.post(e) + posted_event = pygame.event.poll() + self.assertEqual(e, posted_event, race_condition_notification) + + def test_post_large_user_event(self): + pygame.event.post(pygame.event.Event(pygame.USEREVENT, {"a": "a" * 1024})) + e = pygame.event.poll() + + self.assertEqual(e.type, pygame.USEREVENT) + self.assertEqual(e.a, "a" * 1024) + + def test_post_blocked(self): + """ + Test blocked events are not posted. Also test whether post() + returns a boolean correctly + """ + pygame.event.set_blocked(pygame.USEREVENT) + self.assertFalse(pygame.event.post(pygame.event.Event(pygame.USEREVENT))) + self.assertFalse(pygame.event.poll()) + pygame.event.set_allowed(pygame.USEREVENT) + self.assertTrue(pygame.event.post(pygame.event.Event(pygame.USEREVENT))) + self.assertEqual(pygame.event.poll(), pygame.event.Event(pygame.USEREVENT)) + + def test_get(self): + """Ensure get() retrieves all the events on the queue.""" + event_cnt = 10 + for _ in range(event_cnt): + pygame.event.post(pygame.event.Event(pygame.USEREVENT)) + + queue = pygame.event.get() + + self.assertEqual(len(queue), event_cnt) + self.assertTrue(all(e.type == pygame.USEREVENT for e in queue)) + + def test_get_type(self): + ev = pygame.event.Event(pygame.USEREVENT) + pygame.event.post(ev) + queue = pygame.event.get(pygame.USEREVENT) + self.assertEqual(len(queue), 1) + self.assertEqual(queue[0].type, pygame.USEREVENT) + + TESTEVENTS = 10 + for _ in range(TESTEVENTS): + pygame.event.post(ev) + q = pygame.event.get([pygame.USEREVENT]) + self.assertEqual(len(q), TESTEVENTS) + for event in q: + self.assertEqual(event, ev) + + def test_get_exclude_throw(self): + self.assertRaises( + pygame.error, pygame.event.get, pygame.KEYDOWN, False, pygame.KEYUP + ) + + def test_get_exclude(self): + pygame.event.post(pygame.event.Event(pygame.USEREVENT)) + pygame.event.post(pygame.event.Event(pygame.KEYDOWN)) + + queue = pygame.event.get(exclude=pygame.KEYDOWN) + self.assertEqual(len(queue), 1) + self.assertEqual(queue[0].type, pygame.USEREVENT) + + pygame.event.post(pygame.event.Event(pygame.KEYUP)) + pygame.event.post(pygame.event.Event(pygame.USEREVENT)) + queue = pygame.event.get(exclude=(pygame.KEYDOWN, pygame.KEYUP)) + self.assertEqual(len(queue), 1) + self.assertEqual(queue[0].type, pygame.USEREVENT) + + queue = pygame.event.get() + self.assertEqual(len(queue), 2) + + def test_get__empty_queue(self): + """Ensure get() works correctly on an empty queue.""" + expected_events = [] + pygame.event.clear() + + # Ensure all events can be checked. + retrieved_events = pygame.event.get() + + self.assertListEqual(retrieved_events, expected_events) + + # Ensure events can be checked individually. + for event_type in EVENT_TYPES: + retrieved_events = pygame.event.get(event_type) + + self.assertListEqual(retrieved_events, expected_events) + + # Ensure events can be checked as a sequence. + retrieved_events = pygame.event.get(EVENT_TYPES) + + self.assertListEqual(retrieved_events, expected_events) + + def test_get__event_sequence(self): + """Ensure get() can handle a sequence of event types.""" + event_types = [pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEMOTION] + other_event_type = pygame.MOUSEBUTTONUP + + # Test when no events in the queue. + expected_events = [] + pygame.event.clear() + retrieved_events = pygame.event.get(event_types) + + # don't use self._assertCountEqual here. This checks for + # expected properties in events, and ignores unexpected ones, for + # forward compatibility with SDL2. + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) + + # Test when an event type not in the list is in the queue. + expected_events = [] + pygame.event.clear() + pygame.event.post( + pygame.event.Event(other_event_type, **EVENT_TEST_PARAMS[other_event_type]) + ) + + retrieved_events = pygame.event.get(event_types) + + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) + + # Test when 1 event type in the list is in the queue. + expected_events = [ + pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]]) + ] + pygame.event.clear() + pygame.event.post(expected_events[0]) + + retrieved_events = pygame.event.get(event_types) + + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) + + # Test all events in the list are in the queue. + pygame.event.clear() + expected_events = [] + + for etype in event_types: + expected_events.append( + pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype]) + ) + pygame.event.post(expected_events[-1]) + + retrieved_events = pygame.event.get(event_types) + + self._assertExpectedEvents(expected=expected_events, got=retrieved_events) + + def test_clear(self): + """Ensure clear() removes all the events on the queue.""" + for e in EVENT_TYPES: + pygame.event.post(pygame.event.Event(e, **EVENT_TEST_PARAMS[e])) + poll_event = pygame.event.poll() + + self.assertNotEqual(poll_event.type, pygame.NOEVENT) + + pygame.event.clear() + poll_event = pygame.event.poll() + + self.assertEqual(poll_event.type, pygame.NOEVENT, race_condition_notification) + + def test_clear__empty_queue(self): + """Ensure clear() works correctly on an empty queue.""" + expected_events = [] + pygame.event.clear() + + # Test calling clear() on an already empty queue. + pygame.event.clear() + + retrieved_events = pygame.event.get() + + self.assertListEqual(retrieved_events, expected_events) + + def test_clear__event_sequence(self): + """Ensure a sequence of event types can be cleared from the queue.""" + cleared_event_types = EVENT_TYPES[:5] + expected_event_types = EVENT_TYPES[5:10] + expected_events = [] + + # Add the events to the queue. + for etype in cleared_event_types: + pygame.event.post(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])) + + for etype in expected_events: + expected_events.append( + pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype]) + ) + pygame.event.post(expected_events[-1]) + + # Clear the cleared_events from the queue. + pygame.event.clear(cleared_event_types) + + # Check the rest of the events in the queue. + remaining_events = pygame.event.get() + + self._assertCountEqual(remaining_events, expected_events) + + def test_event_name(self): + """Ensure event_name() returns the correct event name.""" + for expected_name, event in NAMES_AND_EVENTS: + self.assertEqual( + pygame.event.event_name(event), expected_name, "0x{:X}".format(event) + ) + + def test_event_name__userevent_range(self): + """Ensures event_name() returns the correct name for user events. + + Tests the full range of user events. + """ + expected_name = "UserEvent" + + for event in range(pygame.USEREVENT, pygame.NUMEVENTS): + self.assertEqual( + pygame.event.event_name(event), expected_name, "0x{:X}".format(event) + ) + + def test_event_name__userevent_boundary(self): + """Ensures event_name() does not return 'UserEvent' for events + just outside the user event range. + """ + unexpected_name = "UserEvent" + + for event in (pygame.USEREVENT - 1, pygame.NUMEVENTS): + self.assertNotEqual( + pygame.event.event_name(event), unexpected_name, "0x{:X}".format(event) + ) + + def test_wait(self): + """Ensure wait() waits for an event on the queue.""" + # Test case without timeout. + event = pygame.event.Event(EVENT_TYPES[0], **EVENT_TEST_PARAMS[EVENT_TYPES[0]]) + pygame.event.post(event) + wait_event = pygame.event.wait() + + self.assertEqual(wait_event.type, event.type) + + # Test case with timeout and no event in the queue. + wait_event = pygame.event.wait(250) + self.assertEqual(wait_event.type, pygame.NOEVENT) + + # Test case with timeout and an event in the queue. + event = pygame.event.Event(EVENT_TYPES[0], **EVENT_TEST_PARAMS[EVENT_TYPES[0]]) + pygame.event.post(event) + wait_event = pygame.event.wait(250) + + self.assertEqual(wait_event.type, event.type) + + def test_peek(self): + """Ensure queued events can be peeked at.""" + event_types = [pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEMOTION] + + for event_type in event_types: + pygame.event.post( + pygame.event.Event(event_type, **EVENT_TEST_PARAMS[event_type]) + ) + + # Ensure events can be checked individually. + for event_type in event_types: + self.assertTrue(pygame.event.peek(event_type)) + + # Ensure events can be checked as a sequence. + self.assertTrue(pygame.event.peek(event_types)) + + def test_peek__event_sequence(self): + """Ensure peek() can handle a sequence of event types.""" + event_types = [pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEMOTION] + other_event_type = pygame.MOUSEBUTTONUP + + # Test when no events in the queue. + pygame.event.clear() + peeked = pygame.event.peek(event_types) + + self.assertFalse(peeked) + + # Test when an event type not in the list is in the queue. + pygame.event.clear() + pygame.event.post( + pygame.event.Event(other_event_type, **EVENT_TEST_PARAMS[other_event_type]) + ) + + peeked = pygame.event.peek(event_types) + + self.assertFalse(peeked) + + # Test when 1 event type in the list is in the queue. + pygame.event.clear() + pygame.event.post( + pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]]) + ) + + peeked = pygame.event.peek(event_types) + + self.assertTrue(peeked) + + # Test all events in the list are in the queue. + pygame.event.clear() + for etype in event_types: + pygame.event.post(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])) + + peeked = pygame.event.peek(event_types) + + self.assertTrue(peeked) + + def test_peek__empty_queue(self): + """Ensure peek() works correctly on an empty queue.""" + pygame.event.clear() + + # Ensure all events can be checked. + peeked = pygame.event.peek() + + self.assertFalse(peeked) + + # Ensure events can be checked individually. + for event_type in EVENT_TYPES: + peeked = pygame.event.peek(event_type) + self.assertFalse(peeked) + + # Ensure events can be checked as a sequence. + peeked = pygame.event.peek(EVENT_TYPES) + + self.assertFalse(peeked) + + def test_set_allowed(self): + """Ensure a blocked event type can be unblocked/allowed.""" + event = EVENT_TYPES[0] + pygame.event.set_blocked(event) + + self.assertTrue(pygame.event.get_blocked(event)) + + pygame.event.set_allowed(event) + + self.assertFalse(pygame.event.get_blocked(event)) + + def test_set_allowed__event_sequence(self): + """Ensure a sequence of blocked event types can be unblocked/allowed.""" + event_types = [ + pygame.KEYDOWN, + pygame.KEYUP, + pygame.MOUSEMOTION, + pygame.MOUSEBUTTONDOWN, + pygame.MOUSEBUTTONUP, + ] + pygame.event.set_blocked(event_types) + + pygame.event.set_allowed(event_types) + + for etype in event_types: + self.assertFalse(pygame.event.get_blocked(etype)) + + def test_set_allowed_all(self): + """Ensure all events can be unblocked/allowed at once.""" + pygame.event.set_blocked(None) + + for e in EVENT_TYPES: + self.assertTrue(pygame.event.get_blocked(e)) + + pygame.event.set_allowed(None) + + for e in EVENT_TYPES: + self.assertFalse(pygame.event.get_blocked(e)) + + def test_pump(self): + """Ensure pump() functions properly.""" + pygame.event.pump() + + # @unittest.skipIf( + # os.environ.get("SDL_VIDEODRIVER") == "dummy", + # 'requires the SDL_VIDEODRIVER to be a non "dummy" value', + # ) + # Fails on SDL 2.0.18 + @unittest.skip("flaky test, and broken on 2.0.18 windows") + def test_set_grab__and_get_symmetric(self): + """Ensure event grabbing can be enabled and disabled. + + WARNING: Moving the mouse off the display during this test can cause it + to fail. + """ + surf = pygame.display.set_mode((10, 10)) + pygame.event.set_grab(True) + + self.assertTrue(pygame.event.get_grab()) + + pygame.event.set_grab(False) + + self.assertFalse(pygame.event.get_grab()) + + def test_event_equality(self): + """Ensure an events can be compared correctly.""" + a = pygame.event.Event(EVENT_TYPES[0], a=1) + b = pygame.event.Event(EVENT_TYPES[0], a=1) + c = pygame.event.Event(EVENT_TYPES[1], a=1) + d = pygame.event.Event(EVENT_TYPES[0], a=2) + + self.assertTrue(a == a) + self.assertFalse(a != a) + self.assertTrue(a == b) + self.assertFalse(a != b) + self.assertTrue(a != c) + self.assertFalse(a == c) + self.assertTrue(a != d) + self.assertFalse(a == d) + + def test_get_blocked(self): + """Ensure an event's blocked state can be retrieved.""" + # Test each event is not blocked. + pygame.event.set_allowed(None) + + for etype in EVENT_TYPES: + blocked = pygame.event.get_blocked(etype) + + self.assertFalse(blocked) + + # Test each event type is blocked. + pygame.event.set_blocked(None) + + for etype in EVENT_TYPES: + blocked = pygame.event.get_blocked(etype) + + self.assertTrue(blocked) + + def test_get_blocked__event_sequence(self): + """Ensure get_blocked() can handle a sequence of event types.""" + event_types = [ + pygame.KEYDOWN, + pygame.KEYUP, + pygame.MOUSEMOTION, + pygame.MOUSEBUTTONDOWN, + pygame.MOUSEBUTTONUP, + ] + + # Test no event types in the list are blocked. + blocked = pygame.event.get_blocked(event_types) + + self.assertFalse(blocked) + + # Test when 1 event type in the list is blocked. + pygame.event.set_blocked(event_types[2]) + + blocked = pygame.event.get_blocked(event_types) + + self.assertTrue(blocked) + + # Test all event types in the list are blocked. + pygame.event.set_blocked(event_types) + + blocked = pygame.event.get_blocked(event_types) + + self.assertTrue(blocked) + + # @unittest.skipIf( + # os.environ.get("SDL_VIDEODRIVER") == "dummy", + # 'requires the SDL_VIDEODRIVER to be a non "dummy" value', + # ) + # Fails on SDL 2.0.18 + @unittest.skip("flaky test, and broken on 2.0.18 windows") + def test_get_grab(self): + """Ensure get_grab() works as expected""" + surf = pygame.display.set_mode((10, 10)) + # Test 5 times + for i in range(5): + pygame.event.set_grab(i % 2) + self.assertEqual(pygame.event.get_grab(), i % 2) + + def test_poll(self): + """Ensure poll() works as expected""" + pygame.event.clear() + ev = pygame.event.poll() + # poll() on empty queue should return NOEVENT + self.assertEqual(ev.type, pygame.NOEVENT) + + # test poll returns stuff in same order + e1 = pygame.event.Event(pygame.USEREVENT) + e2 = pygame.event.Event(pygame.KEYDOWN, key=pygame.K_a) + e3 = pygame.event.Event(pygame.KEYUP, key=pygame.K_a) + pygame.event.post(e1) + pygame.event.post(e2) + pygame.event.post(e3) + + self.assertEqual(pygame.event.poll().type, e1.type) + self.assertEqual(pygame.event.poll().type, e2.type) + self.assertEqual(pygame.event.poll().type, e3.type) + self.assertEqual(pygame.event.poll().type, pygame.NOEVENT) + + +################################################################################ + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/A_PyGameMono-8.png b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/A_PyGameMono-8.png new file mode 100644 index 0000000..b15961f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/A_PyGameMono-8.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-18-100dpi.bdf b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-18-100dpi.bdf new file mode 100644 index 0000000..a88f083 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-18-100dpi.bdf @@ -0,0 +1,165 @@ +STARTFONT 2.1 +FONT -FontForge-PyGameMono-Medium-R-Normal--25-180-100-100-M-250-ISO10646-1 +SIZE 18 100 100 +FONTBOUNDINGBOX 21 22 0 0 +COMMENT "Generated by fontforge, http://fontforge.sourceforge.net" +COMMENT "Created by Lenard Lindstrom,,, with FontForge 2.0 (http://fontforge.sf.net)" +STARTPROPERTIES 29 +FOUNDRY "FontForge" +FAMILY_NAME "PyGameMono" +WEIGHT_NAME "Medium" +SLANT "R" +SETWIDTH_NAME "Normal" +ADD_STYLE_NAME "" +PIXEL_SIZE 25 +POINT_SIZE 180 +RESOLUTION_X 100 +RESOLUTION_Y 100 +SPACING "M" +AVERAGE_WIDTH 250 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +FONTNAME_REGISTRY "" +CHARSET_COLLECTIONS "ISO10646-1" +FONT_NAME "PyGameMono" +FACE_NAME "PyGame Mono" +FONT_VERSION "001.000" +FONT_ASCENT 20 +FONT_DESCENT 5 +UNDERLINE_POSITION -2 +UNDERLINE_THICKNESS 2 +RAW_ASCENT 800 +RAW_DESCENT 200 +RELATIVE_WEIGHT 50 +RELATIVE_SETWIDTH 50 +FIGURE_WIDTH -1 +AVG_UPPERCASE_WIDTH 250 +ENDPROPERTIES +CHARS 5 +STARTCHAR .notdef +ENCODING 0 +SWIDTH 1000 0 +DWIDTH 25 0 +BBX 20 20 0 0 +BITMAP +FFFFF0 +FFFFF0 +FE07F0 +F801F0 +F000F0 +E00070 +E00070 +C00030 +C00030 +C00030 +C00030 +C00030 +C00030 +E00070 +E00070 +F000F0 +F801F0 +FE07F0 +FFFFF0 +FFFFF0 +ENDCHAR +STARTCHAR A +ENCODING 65 +SWIDTH 1000 0 +DWIDTH 25 0 +BBX 20 21 0 1 +BITMAP +03FC00 +1FFF80 +3FFFC0 +7C03E0 +F000F0 +E00070 +E00070 +F000F0 +FC03F0 +FFFFF0 +FFFFF0 +FFFFF0 +FF0FF0 +7C03F0 +7801E0 +7800E0 +7000E0 +700060 +600060 +200040 +200040 +ENDCHAR +STARTCHAR B +ENCODING 66 +SWIDTH 1000 0 +DWIDTH 25 0 +BBX 18 20 1 0 +BITMAP +FFFE00 +FFFF80 +7E0780 +7801C0 +7000C0 +3000C0 +3000C0 +3801C0 +3E0780 +3FFF00 +3FFF00 +3E0780 +380180 +3000C0 +3000C0 +3000C0 +7801C0 +7E07C0 +FFFF80 +FFFE00 +ENDCHAR +STARTCHAR C +ENCODING 67 +SWIDTH 1000 0 +DWIDTH 25 0 +BBX 20 20 0 0 +BITMAP +00FC00 +03FF00 +0FFF80 +1F03E0 +3E0070 +7C0010 +780000 +F80000 +F00000 +F00000 +F00000 +F00000 +F80000 +780000 +7C0010 +3E0070 +1F01E0 +0FFFC0 +03FF80 +00FE00 +ENDCHAR +STARTCHAR u13079 +ENCODING 77945 +SWIDTH 1000 0 +DWIDTH 25 0 +BBX 21 10 0 5 +BITMAP +03FC00 +0FFF80 +1E73C0 +78F8F0 +F0F878 +70F870 +3870E0 +1E03C0 +0FFF80 +03FC00 +ENDCHAR +ENDFONT diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-18-75dpi.bdf b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-18-75dpi.bdf new file mode 100644 index 0000000..127f704 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-18-75dpi.bdf @@ -0,0 +1,143 @@ +STARTFONT 2.1 +FONT -FontForge-PyGameMono-Medium-R-Normal--19-180-75-75-M-190-ISO10646-1 +SIZE 18 75 75 +FONTBOUNDINGBOX 15 17 0 0 +COMMENT "Generated by fontforge, http://fontforge.sourceforge.net" +COMMENT "Created by Lenard Lindstrom,,, with FontForge 2.0 (http://fontforge.sf.net)" +STARTPROPERTIES 29 +FOUNDRY "FontForge" +FAMILY_NAME "PyGameMono" +WEIGHT_NAME "Medium" +SLANT "R" +SETWIDTH_NAME "Normal" +ADD_STYLE_NAME "" +PIXEL_SIZE 19 +POINT_SIZE 180 +RESOLUTION_X 75 +RESOLUTION_Y 75 +SPACING "M" +AVERAGE_WIDTH 190 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +FONTNAME_REGISTRY "" +CHARSET_COLLECTIONS "ISO10646-1" +FONT_NAME "PyGameMono" +FACE_NAME "PyGame Mono" +FONT_VERSION "001.000" +FONT_ASCENT 15 +FONT_DESCENT 4 +UNDERLINE_POSITION -2 +UNDERLINE_THICKNESS 1 +RAW_ASCENT 800 +RAW_DESCENT 200 +RELATIVE_WEIGHT 50 +RELATIVE_SETWIDTH 50 +FIGURE_WIDTH -1 +AVG_UPPERCASE_WIDTH 190 +ENDPROPERTIES +CHARS 5 +STARTCHAR .notdef +ENCODING 0 +SWIDTH 1000 0 +DWIDTH 19 0 +BBX 15 15 0 0 +BITMAP +FFFE +FFFE +FC7E +F01E +E00E +C006 +C006 +C006 +C006 +C006 +E00E +F01E +FC7E +FFFE +FFFE +ENDCHAR +STARTCHAR A +ENCODING 65 +SWIDTH 1000 0 +DWIDTH 19 0 +BBX 15 17 0 0 +BITMAP +0FE0 +3FF8 +783C +F01E +E00E +E00E +F01E +F83E +FFFE +FFFE +FC7E +701C +701C +600C +600C +4004 +4004 +ENDCHAR +STARTCHAR B +ENCODING 66 +SWIDTH 1000 0 +DWIDTH 19 0 +BBX 15 15 0 0 +BITMAP +FFF8 +7FFC +780E +3006 +3006 +380E +3FF8 +3FF8 +3FF8 +380E +3006 +3006 +7C1E +7FFC +FFF8 +ENDCHAR +STARTCHAR C +ENCODING 67 +SWIDTH 1000 0 +DWIDTH 19 0 +BBX 15 15 0 0 +BITMAP +03E0 +0FF8 +3C1C +7806 +7000 +E000 +E000 +E000 +E000 +E000 +7000 +7806 +3C1C +0FF8 +03E0 +ENDCHAR +STARTCHAR u13079 +ENCODING 77945 +SWIDTH 1000 0 +DWIDTH 19 0 +BBX 15 7 0 4 +BITMAP +0FE0 +3838 +638C +E38E +638C +3838 +0FE0 +ENDCHAR +ENDFONT diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-8.bdf b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-8.bdf new file mode 100644 index 0000000..17bef06 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono-8.bdf @@ -0,0 +1,103 @@ +STARTFONT 2.1 +FONT -FontForge-PyGameMono-Medium-R-Normal--8-80-75-75-C-80-ISO10646-1 +SIZE 8 75 75 +FONTBOUNDINGBOX 6 7 0 0 +COMMENT "Generated by fontforge, http://fontforge.sourceforge.net" +COMMENT "Created by Lenard Lindstrom,,, with FontForge 2.0 (http://fontforge.sf.net)" +STARTPROPERTIES 29 +FOUNDRY "FontForge" +FAMILY_NAME "PyGameMono" +WEIGHT_NAME "Medium" +SLANT "R" +SETWIDTH_NAME "Normal" +ADD_STYLE_NAME "" +PIXEL_SIZE 8 +POINT_SIZE 80 +RESOLUTION_X 75 +RESOLUTION_Y 75 +SPACING "C" +AVERAGE_WIDTH 80 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +FONTNAME_REGISTRY "" +CHARSET_COLLECTIONS "ISO10646-1" +FONT_NAME "PyGameMono" +FACE_NAME "PyGame Mono" +FONT_VERSION "001.000" +FONT_ASCENT 6 +FONT_DESCENT 2 +UNDERLINE_POSITION -1 +UNDERLINE_THICKNESS 1 +RAW_ASCENT 800 +RAW_DESCENT 200 +RELATIVE_WEIGHT 50 +RELATIVE_SETWIDTH 50 +FIGURE_WIDTH -1 +AVG_UPPERCASE_WIDTH 80 +ENDPROPERTIES +CHARS 5 +STARTCHAR .notdef +ENCODING 0 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 6 6 0 0 +BITMAP +FC +84 +84 +84 +84 +FC +ENDCHAR +STARTCHAR A +ENCODING 65 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 6 7 0 0 +BITMAP +78 +84 +84 +FC +84 +84 +84 +ENDCHAR +STARTCHAR B +ENCODING 66 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 6 6 0 0 +BITMAP +FC +44 +78 +4C +44 +FC +ENDCHAR +STARTCHAR C +ENCODING 67 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 6 6 0 0 +BITMAP +78 +C4 +C0 +C0 +C4 +78 +ENDCHAR +STARTCHAR u13079 +ENCODING 77945 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 6 4 0 1 +BITMAP +78 +B4 +B4 +78 +ENDCHAR +ENDFONT diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono.otf b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono.otf new file mode 100644 index 0000000..5e9b66c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/PyGameMono.otf differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/test_fixed.otf b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/test_fixed.otf new file mode 100644 index 0000000..3488898 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/test_fixed.otf differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/test_sans.ttf b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/test_sans.ttf new file mode 100644 index 0000000..09fac2f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/test_sans.ttf differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/u13079_PyGameMono-8.png b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/u13079_PyGameMono-8.png new file mode 100644 index 0000000..911da8a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/fonts/u13079_PyGameMono-8.png differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/xbm_cursors/white_sizing.xbm b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/xbm_cursors/white_sizing.xbm new file mode 100644 index 0000000..d334d8d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/xbm_cursors/white_sizing.xbm @@ -0,0 +1,8 @@ +#define resize_white_width 16 +#define resize_white_height 16 +#define resize_white_x_hot 7 +#define resize_white_y_hot 7 +static unsigned char resize_white_bits[] = { + 0xff, 0x03, 0x01, 0x02, 0xfd, 0x03, 0x05, 0x00, 0xf5, 0x0f, 0x15, 0x08, + 0xd5, 0xeb, 0x55, 0xaa, 0x55, 0xaa, 0xd7, 0xab, 0x10, 0xa8, 0xf0, 0xb7, + 0x00, 0xa8, 0xc0, 0x9f, 0x40, 0x80, 0xc0, 0xff}; diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/xbm_cursors/white_sizing_mask.xbm b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/xbm_cursors/white_sizing_mask.xbm new file mode 100644 index 0000000..f00bc46 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/fixtures/xbm_cursors/white_sizing_mask.xbm @@ -0,0 +1,8 @@ +#define resize_white_mask_width 16 +#define resize_white_mask_height 16 +#define resize_white_mask_x_hot 7 +#define resize_white_mask_y_hot 7 +static unsigned char resize_white_mask_bits[] = { + 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0x07, 0x00, 0xf7, 0x0f, 0xf7, 0x0f, + 0xf7, 0xef, 0x77, 0xee, 0x77, 0xee, 0xf7, 0xef, 0xf0, 0xef, 0xf0, 0xff, + 0x00, 0xf8, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff}; diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/font_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/font_test.py new file mode 100644 index 0000000..e7bbf67 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/font_test.py @@ -0,0 +1,633 @@ +# -*- coding: utf-8 -*- + +import sys +import os +import unittest +import pathlib +import platform + +import pygame +from pygame import font as pygame_font # So font can be replaced with ftfont + + +FONTDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures", "fonts") + +UCS_4 = sys.maxunicode > 0xFFFF + + +def equal_images(s1, s2): + size = s1.get_size() + if s2.get_size() != size: + return False + w, h = size + for x in range(w): + for y in range(h): + if s1.get_at((x, y)) != s2.get_at((x, y)): + return False + return True + + +IS_PYPY = "PyPy" == platform.python_implementation() + + +@unittest.skipIf(IS_PYPY, "pypy skip known failure") # TODO +class FontModuleTest(unittest.TestCase): + def setUp(self): + pygame_font.init() + + def tearDown(self): + pygame_font.quit() + + def test_SysFont(self): + # Can only check that a font object is returned. + fonts = pygame_font.get_fonts() + if "arial" in fonts: + # Try to use arial font if it is there, rather than a random font + # which can be different depending on installed fonts on the system. + font_name = "arial" + else: + font_name = sorted(fonts)[0] + o = pygame_font.SysFont(font_name, 20) + self.assertTrue(isinstance(o, pygame_font.FontType)) + o = pygame_font.SysFont(font_name, 20, italic=True) + self.assertTrue(isinstance(o, pygame_font.FontType)) + o = pygame_font.SysFont(font_name, 20, bold=True) + self.assertTrue(isinstance(o, pygame_font.FontType)) + o = pygame_font.SysFont("thisisnotafont", 20) + self.assertTrue(isinstance(o, pygame_font.FontType)) + + def test_get_default_font(self): + self.assertEqual(pygame_font.get_default_font(), "freesansbold.ttf") + + def test_get_fonts_returns_something(self): + fnts = pygame_font.get_fonts() + self.assertTrue(fnts) + + # to test if some files exist... + # def XXtest_has_file_osx_10_5_sdk(self): + # import os + # f = "/Developer/SDKs/MacOSX10.5.sdk/usr/X11/include/ft2build.h" + # self.assertEqual(os.path.exists(f), True) + + # def XXtest_has_file_osx_10_4_sdk(self): + # import os + # f = "/Developer/SDKs/MacOSX10.4u.sdk/usr/X11R6/include/ft2build.h" + # self.assertEqual(os.path.exists(f), True) + + def test_get_fonts(self): + fnts = pygame_font.get_fonts() + + self.assertTrue(fnts, msg=repr(fnts)) + + for name in fnts: + # note, on ubuntu 2.6 they are all unicode strings. + + self.assertTrue(isinstance(name, str), name) + # Font names can be comprised of only numeric characters, so + # just checking name.islower() will not work as expected here. + self.assertFalse(any(c.isupper() for c in name)) + self.assertTrue(name.isalnum(), name) + + def test_get_init(self): + self.assertTrue(pygame_font.get_init()) + pygame_font.quit() + self.assertFalse(pygame_font.get_init()) + + def test_init(self): + pygame_font.init() + + def test_match_font_all_exist(self): + fonts = pygame_font.get_fonts() + + # Ensure all listed fonts are in fact available, and the returned file + # name is a full path. + for font in fonts: + path = pygame_font.match_font(font) + self.assertFalse(path is None) + self.assertTrue(os.path.isabs(path)) + + def test_match_font_name(self): + """That match_font accepts names of various types""" + font = pygame_font.get_fonts()[0] + font_path = pygame_font.match_font(font) + self.assertIsNotNone(font_path) + font_b = font.encode() + not_a_font = "thisisnotafont" + not_a_font_b = b"thisisnotafont" + good_font_names = [ + # Check single name bytes. + font_b, + # Check string of comma-separated names. + ",".join([not_a_font, font, not_a_font]), + # Check list of names. + [not_a_font, font, not_a_font], + # Check generator: + (name for name in [not_a_font, font, not_a_font]), + # Check comma-separated bytes. + b",".join([not_a_font_b, font_b, not_a_font_b]), + # Check list of bytes. + [not_a_font_b, font_b, not_a_font_b], + # Check mixed list of bytes and string. + [font, not_a_font, font_b, not_a_font_b], + ] + for font_name in good_font_names: + self.assertEqual(pygame_font.match_font(font_name), font_path, font_name) + + def test_not_match_font_name(self): + """match_font return None when names of various types do not exist""" + not_a_font = "thisisnotafont" + not_a_font_b = b"thisisnotafont" + bad_font_names = [ + not_a_font, + ",".join([not_a_font, not_a_font, not_a_font]), + [not_a_font, not_a_font, not_a_font], + (name for name in [not_a_font, not_a_font, not_a_font]), + not_a_font_b, + b",".join([not_a_font_b, not_a_font_b, not_a_font_b]), + [not_a_font_b, not_a_font_b, not_a_font_b], + [not_a_font, not_a_font_b, not_a_font], + ] + for font_name in bad_font_names: + self.assertIsNone(pygame_font.match_font(font_name), font_name) + + def test_match_font_bold(self): + fonts = pygame_font.get_fonts() + + # Look for a bold font. + self.assertTrue(any(pygame_font.match_font(font, bold=True) for font in fonts)) + + def test_match_font_italic(self): + fonts = pygame_font.get_fonts() + + # Look for an italic font. + self.assertTrue( + any(pygame_font.match_font(font, italic=True) for font in fonts) + ) + + def test_issue_742(self): + """that the font background does not crash.""" + surf = pygame.Surface((320, 240)) + font = pygame_font.Font(None, 24) + image = font.render("Test", 0, (255, 255, 255), (0, 0, 0)) + self.assertIsNone(image.get_colorkey()) + image.set_alpha(255) + surf.blit(image, (0, 0)) + + def test_issue_font_alphablit(self): + """Check that blitting anti-aliased text doesn't + change the background blue""" + pygame.display.set_mode((600, 400)) + + font = pygame_font.Font(None, 24) + + (color, text, center, pos) = ((160, 200, 250), "Music", (190, 170), "midright") + img1 = font.render(text, True, color) + + img = pygame.Surface(img1.get_size(), depth=32) + pre_blit_corner_pixel = img.get_at((0, 0)) + img.blit(img1, (0, 0)) + post_blit_corner_pixel = img.get_at((0, 0)) + + self.assertEqual(pre_blit_corner_pixel, post_blit_corner_pixel) + + def test_segfault_after_reinit(self): + """Reinitialization of font module should not cause + segmentation fault""" + import gc + + font = pygame_font.Font(None, 20) + pygame_font.quit() + pygame_font.init() + del font + gc.collect() + + def test_quit(self): + pygame_font.quit() + + +@unittest.skipIf(IS_PYPY, "pypy skip known failure") # TODO +class FontTest(unittest.TestCase): + def setUp(self): + pygame_font.init() + + def tearDown(self): + pygame_font.quit() + + def test_render_args(self): + screen = pygame.display.set_mode((600, 400)) + rect = screen.get_rect() + f = pygame_font.Font(None, 20) + screen.fill((10, 10, 10)) + font_surface = f.render(" bar", True, (0, 0, 0), (255, 255, 255)) + font_rect = font_surface.get_rect() + font_rect.topleft = rect.topleft + self.assertTrue(font_surface) + screen.blit(font_surface, font_rect, font_rect) + pygame.display.update() + self.assertEqual(tuple(screen.get_at((0, 0)))[:3], (255, 255, 255)) + self.assertEqual(tuple(screen.get_at(font_rect.topleft))[:3], (255, 255, 255)) + + # If we don't have a real display, don't do this test. + # Transparent background doesn't seem to work without a read video card. + if os.environ.get("SDL_VIDEODRIVER") != "dummy": + screen.fill((10, 10, 10)) + font_surface = f.render(" bar", True, (0, 0, 0), None) + font_rect = font_surface.get_rect() + font_rect.topleft = rect.topleft + self.assertTrue(font_surface) + screen.blit(font_surface, font_rect, font_rect) + pygame.display.update() + self.assertEqual(tuple(screen.get_at((0, 0)))[:3], (10, 10, 10)) + self.assertEqual(tuple(screen.get_at(font_rect.topleft))[:3], (10, 10, 10)) + + screen.fill((10, 10, 10)) + font_surface = f.render(" bar", True, (0, 0, 0)) + font_rect = font_surface.get_rect() + font_rect.topleft = rect.topleft + self.assertTrue(font_surface) + screen.blit(font_surface, font_rect, font_rect) + pygame.display.update(rect) + self.assertEqual(tuple(screen.get_at((0, 0)))[:3], (10, 10, 10)) + self.assertEqual(tuple(screen.get_at(font_rect.topleft))[:3], (10, 10, 10)) + + +@unittest.skipIf(IS_PYPY, "pypy skip known failure") # TODO +class FontTypeTest(unittest.TestCase): + def setUp(self): + pygame_font.init() + + def tearDown(self): + pygame_font.quit() + + def test_get_ascent(self): + # Ckecking ascent would need a custom test font to do properly. + f = pygame_font.Font(None, 20) + ascent = f.get_ascent() + self.assertTrue(isinstance(ascent, int)) + self.assertTrue(ascent > 0) + s = f.render("X", False, (255, 255, 255)) + self.assertTrue(s.get_size()[1] > ascent) + + def test_get_descent(self): + # Ckecking descent would need a custom test font to do properly. + f = pygame_font.Font(None, 20) + descent = f.get_descent() + self.assertTrue(isinstance(descent, int)) + self.assertTrue(descent < 0) + + def test_get_height(self): + # Ckecking height would need a custom test font to do properly. + f = pygame_font.Font(None, 20) + height = f.get_height() + self.assertTrue(isinstance(height, int)) + self.assertTrue(height > 0) + s = f.render("X", False, (255, 255, 255)) + self.assertTrue(s.get_size()[1] == height) + + def test_get_linesize(self): + # Ckecking linesize would need a custom test font to do properly. + # Questions: How do linesize, height and descent relate? + f = pygame_font.Font(None, 20) + linesize = f.get_linesize() + self.assertTrue(isinstance(linesize, int)) + self.assertTrue(linesize > 0) + + def test_metrics(self): + # Ensure bytes decoding works correctly. Can only compare results + # with unicode for now. + f = pygame_font.Font(None, 20) + um = f.metrics(".") + bm = f.metrics(b".") + + self.assertEqual(len(um), 1) + self.assertEqual(len(bm), 1) + self.assertIsNotNone(um[0]) + self.assertEqual(um, bm) + + u = "\u212A" + b = u.encode("UTF-16")[2:] # Keep byte order consistent. [2:] skips BOM + bm = f.metrics(b) + + self.assertEqual(len(bm), 2) + + try: # FIXME why do we do this try/except ? + um = f.metrics(u) + except pygame.error: + pass + else: + self.assertEqual(len(um), 1) + self.assertNotEqual(bm[0], um[0]) + self.assertNotEqual(bm[1], um[0]) + + if UCS_4: + u = u"\U00013000" + bm = f.metrics(u) + + self.assertEqual(len(bm), 1) + self.assertIsNone(bm[0]) + + return # unfinished + # The documentation is useless here. How large a list? + # How do list positions relate to character codes? + # What about unicode characters? + + # __doc__ (as of 2008-08-02) for pygame_font.Font.metrics: + + # Font.metrics(text): return list + # Gets the metrics for each character in the pased string. + # + # The list contains tuples for each character, which contain the + # minimum X offset, the maximum X offset, the minimum Y offset, the + # maximum Y offset and the advance offset (bearing plus width) of the + # character. [(minx, maxx, miny, maxy, advance), (minx, maxx, miny, + # maxy, advance), ...] + + self.fail() + + def test_render(self): + f = pygame_font.Font(None, 20) + s = f.render("foo", True, [0, 0, 0], [255, 255, 255]) + s = f.render("xxx", True, [0, 0, 0], [255, 255, 255]) + s = f.render("", True, [0, 0, 0], [255, 255, 255]) + s = f.render("foo", False, [0, 0, 0], [255, 255, 255]) + s = f.render("xxx", False, [0, 0, 0], [255, 255, 255]) + s = f.render("xxx", False, [0, 0, 0]) + s = f.render(" ", False, [0, 0, 0]) + s = f.render(" ", False, [0, 0, 0], [255, 255, 255]) + # null text should be 0 pixel wide. + s = f.render("", False, [0, 0, 0], [255, 255, 255]) + self.assertEqual(s.get_size()[0], 0) + # None text should be 0 pixel wide. + s = f.render(None, False, [0, 0, 0], [255, 255, 255]) + self.assertEqual(s.get_size()[0], 0) + # Non-text should raise a TypeError. + self.assertRaises(TypeError, f.render, [], False, [0, 0, 0], [255, 255, 255]) + self.assertRaises(TypeError, f.render, 1, False, [0, 0, 0], [255, 255, 255]) + # is background transparent for antialiasing? + s = f.render(".", True, [255, 255, 255]) + self.assertEqual(s.get_at((0, 0))[3], 0) + # is Unicode and bytes encoding correct? + # Cannot really test if the correct characters are rendered, but + # at least can assert the encodings differ. + su = f.render(".", False, [0, 0, 0], [255, 255, 255]) + sb = f.render(b".", False, [0, 0, 0], [255, 255, 255]) + self.assertTrue(equal_images(su, sb)) + u = "\u212A" + b = u.encode("UTF-16")[2:] # Keep byte order consistent. [2:] skips BOM + sb = f.render(b, False, [0, 0, 0], [255, 255, 255]) + try: # FIXME why do we do this try/except ? + su = f.render(u, False, [0, 0, 0], [255, 255, 255]) + except pygame.error: + pass + else: + self.assertFalse(equal_images(su, sb)) + + b = b"ab\x00cd" + self.assertRaises(ValueError, f.render, b, 0, [0, 0, 0]) + u = "ab\x00cd" + self.assertRaises(ValueError, f.render, b, 0, [0, 0, 0]) + + def test_render_ucs2_ucs4(self): + """that it renders without raising if there is a new enough SDL_ttf.""" + f = pygame_font.Font(None, 20) + # If the font module is SDL_ttf < 2.0.15 based, then it only supports UCS-2 + # it will raise an exception for an out-of-range UCS-4 code point. + if UCS_4 and hasattr(pygame_font, "UCS_4"): + ucs_2 = "\uFFEE" + s = f.render(ucs_2, False, [0, 0, 0], [255, 255, 255]) + ucs_4 = "\U00010000" + s = f.render(ucs_4, False, [0, 0, 0], [255, 255, 255]) + + def test_set_bold(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.get_bold()) + f.set_bold(True) + self.assertTrue(f.get_bold()) + f.set_bold(False) + self.assertFalse(f.get_bold()) + + def test_set_italic(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.get_italic()) + f.set_italic(True) + self.assertTrue(f.get_italic()) + f.set_italic(False) + self.assertFalse(f.get_italic()) + + def test_set_underline(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.get_underline()) + f.set_underline(True) + self.assertTrue(f.get_underline()) + f.set_underline(False) + self.assertFalse(f.get_underline()) + + def test_bold_attr(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.bold) + f.bold = True + self.assertTrue(f.bold) + f.bold = False + self.assertFalse(f.bold) + + def test_set_italic_property(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.italic) + f.italic = True + self.assertTrue(f.italic) + f.italic = False + self.assertFalse(f.italic) + + def test_set_underline_property(self): + f = pygame_font.Font(None, 20) + self.assertFalse(f.underline) + f.underline = True + self.assertTrue(f.underline) + f.underline = False + self.assertFalse(f.underline) + + def test_size(self): + f = pygame_font.Font(None, 20) + text = "Xg" + size = f.size(text) + w, h = size + s = f.render(text, False, (255, 255, 255)) + btext = text.encode("ascii") + + self.assertIsInstance(w, int) + self.assertIsInstance(h, int) + self.assertEqual(s.get_size(), size) + self.assertEqual(f.size(btext), size) + + text = "\u212A" + btext = text.encode("UTF-16")[2:] # Keep the byte order consistent. + bsize = f.size(btext) + size = f.size(text) + + self.assertNotEqual(size, bsize) + + def test_font_file_not_found(self): + # A per BUG reported by Bo Jangeborg on pygame-user mailing list, + # http://www.mail-archive.com/pygame-users@seul.org/msg11675.html + + pygame_font.init() + self.assertRaises( + FileNotFoundError, pygame_font.Font, str("some-fictional-font.ttf"), 20 + ) + + def test_load_from_file(self): + font_name = pygame_font.get_default_font() + font_path = os.path.join( + os.path.split(pygame.__file__)[0], pygame_font.get_default_font() + ) + f = pygame_font.Font(font_path, 20) + + def test_load_from_pathlib(self): + font_name = pygame_font.get_default_font() + font_path = os.path.join( + os.path.split(pygame.__file__)[0], pygame_font.get_default_font() + ) + f = pygame_font.Font(pathlib.Path(font_path), 20) + + def test_load_from_file_obj(self): + font_name = pygame_font.get_default_font() + font_path = os.path.join( + os.path.split(pygame.__file__)[0], pygame_font.get_default_font() + ) + with open(font_path, "rb") as f: + font = pygame_font.Font(f, 20) + + def test_load_default_font_filename(self): + # In font_init, a special case is when the filename argument is + # identical to the default font file name. + f = pygame_font.Font(pygame_font.get_default_font(), 20) + + def _load_unicode(self, path): + import shutil + + fdir = str(FONTDIR) + temp = os.path.join(fdir, path) + pgfont = os.path.join(fdir, u"test_sans.ttf") + shutil.copy(pgfont, temp) + try: + with open(temp, "rb") as f: + pass + except FileNotFoundError: + raise unittest.SkipTest("the path cannot be opened") + try: + pygame_font.Font(temp, 20) + finally: + os.remove(temp) + + def test_load_from_file_unicode_0(self): + """ASCII string as a unicode object""" + self._load_unicode(u"temp_file.ttf") + + def test_load_from_file_unicode_1(self): + self._load_unicode(u"你好.ttf") + + def test_load_from_file_bytes(self): + font_path = os.path.join( + os.path.split(pygame.__file__)[0], pygame_font.get_default_font() + ) + filesystem_encoding = sys.getfilesystemencoding() + filesystem_errors = "replace" if sys.platform == "win32" else "surrogateescape" + try: # FIXME why do we do this try/except ? + font_path = font_path.decode(filesystem_encoding, filesystem_errors) + except AttributeError: + pass + bfont_path = font_path.encode(filesystem_encoding, filesystem_errors) + f = pygame_font.Font(bfont_path, 20) + + +@unittest.skipIf(IS_PYPY, "pypy skip known failure") # TODO +class VisualTests(unittest.TestCase): + + __tags__ = ["interactive"] + + screen = None + aborted = False + + def setUp(self): + if self.screen is None: + pygame.init() + self.screen = pygame.display.set_mode((600, 200)) + self.screen.fill((255, 255, 255)) + pygame.display.flip() + self.f = pygame_font.Font(None, 32) + + def abort(self): + if self.screen is not None: + pygame.quit() + self.aborted = True + + def query(self, bold=False, italic=False, underline=False, antialiase=False): + if self.aborted: + return False + spacing = 10 + offset = 20 + y = spacing + f = self.f + screen = self.screen + screen.fill((255, 255, 255)) + pygame.display.flip() + if not (bold or italic or underline or antialiase): + text = "normal" + else: + modes = [] + if bold: + modes.append("bold") + if italic: + modes.append("italic") + if underline: + modes.append("underlined") + if antialiase: + modes.append("antialiased") + text = "%s (y/n):" % ("-".join(modes),) + f.set_bold(bold) + f.set_italic(italic) + f.set_underline(underline) + s = f.render(text, antialiase, (0, 0, 0)) + screen.blit(s, (offset, y)) + y += s.get_size()[1] + spacing + f.set_bold(False) + f.set_italic(False) + f.set_underline(False) + s = f.render("(some comparison text)", False, (0, 0, 0)) + screen.blit(s, (offset, y)) + pygame.display.flip() + while 1: + for evt in pygame.event.get(): + if evt.type == pygame.KEYDOWN: + if evt.key == pygame.K_ESCAPE: + self.abort() + return False + if evt.key == pygame.K_y: + return True + if evt.key == pygame.K_n: + return False + if evt.type == pygame.QUIT: + self.abort() + return False + + def test_bold(self): + self.assertTrue(self.query(bold=True)) + + def test_italic(self): + self.assertTrue(self.query(italic=True)) + + def test_underline(self): + self.assertTrue(self.query(underline=True)) + + def test_antialiase(self): + self.assertTrue(self.query(antialiase=True)) + + def test_bold_antialiase(self): + self.assertTrue(self.query(bold=True, antialiase=True)) + + def test_italic_underline(self): + self.assertTrue(self.query(italic=True, underline=True)) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/freetype_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/freetype_tags.py new file mode 100644 index 0000000..d84cbb7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/freetype_tags.py @@ -0,0 +1,11 @@ +__tags__ = ["development"] + +exclude = False + +try: + import pygame.freetype +except ImportError: + exclude = True + +if exclude: + __tags__.extend(["ignore", "subprocess_ignore"]) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/freetype_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/freetype_test.py new file mode 100644 index 0000000..815dfe3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/freetype_test.py @@ -0,0 +1,1799 @@ +import os + +if os.environ.get("SDL_VIDEODRIVER") == "dummy": + __tags__ = ("ignore", "subprocess_ignore") + +import unittest +import ctypes +import weakref +import gc +import pathlib +import platform + +IS_PYPY = "PyPy" == platform.python_implementation() + + +try: + from pygame.tests.test_utils import arrinter +except NameError: + pass + +import pygame + +try: + import pygame.freetype as ft +except ImportError: + ft = None + + +FONTDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures", "fonts") + + +def nullfont(): + """return an uninitialized font instance""" + return ft.Font.__new__(ft.Font) + + +max_point_size_FX6 = 0x7FFFFFFF +max_point_size = max_point_size_FX6 >> 6 +max_point_size_f = max_point_size_FX6 * 0.015625 + + +def surf_same_image(a, b): + """Return True if a's pixel buffer is identical to b's""" + + a_sz = a.get_height() * a.get_pitch() + b_sz = b.get_height() * b.get_pitch() + if a_sz != b_sz: + return False + a_bytes = ctypes.string_at(a._pixels_address, a_sz) + b_bytes = ctypes.string_at(b._pixels_address, b_sz) + return a_bytes == b_bytes + + +class FreeTypeFontTest(unittest.TestCase): + + _fixed_path = os.path.join(FONTDIR, "test_fixed.otf") + _sans_path = os.path.join(FONTDIR, "test_sans.ttf") + _mono_path = os.path.join(FONTDIR, "PyGameMono.otf") + _bmp_8_75dpi_path = os.path.join(FONTDIR, "PyGameMono-8.bdf") + _bmp_18_75dpi_path = os.path.join(FONTDIR, "PyGameMono-18-75dpi.bdf") + _bmp_18_100dpi_path = os.path.join(FONTDIR, "PyGameMono-18-100dpi.bdf") + _TEST_FONTS = {} + + @classmethod + def setUpClass(cls): + ft.init() + + # Setup the test fonts. + + # Inconsolata is an open-source font designed by Raph Levien. + # Licensed under the Open Font License. + # http://www.levien.com/type/myfonts/inconsolata.html + cls._TEST_FONTS["fixed"] = ft.Font(cls._fixed_path) + + # Liberation Sans is an open-source font designed by Steve Matteson. + # Licensed under the GNU GPL. + # https://fedorahosted.org/liberation-fonts/ + cls._TEST_FONTS["sans"] = ft.Font(cls._sans_path) + + # A scalable mono test font made for pygame. It contains only + # a few glyphs: '\0', 'A', 'B', 'C', and U+13079. + # It also contains two bitmap sizes: 8.0 X 8.0 and 19.0 X 19.0. + cls._TEST_FONTS["mono"] = ft.Font(cls._mono_path) + + # A fixed size bitmap mono test font made for pygame. + # It contains only a few glyphs: '\0', 'A', 'B', 'C', and U+13079. + # The size is 8.0 X 8.0. + cls._TEST_FONTS["bmp-8-75dpi"] = ft.Font(cls._bmp_8_75dpi_path) + + # A fixed size bitmap mono test font made for pygame. + # It contains only a few glyphs: '\0', 'A', 'B', 'C', and U+13079. + # The size is 8.0 X 8.0. + cls._TEST_FONTS["bmp-18-75dpi"] = ft.Font(cls._bmp_18_75dpi_path) + + # A fixed size bitmap mono test font made for pygame. + # It contains only a few glyphs: '\0', 'A', 'B', 'C', and U+13079. + # The size is 8.0 X 8.0. + cls._TEST_FONTS["bmp-18-100dpi"] = ft.Font(cls._bmp_18_100dpi_path) + + @classmethod + def tearDownClass(cls): + ft.quit() + + def test_freetype_defaultfont(self): + font = ft.Font(None) + self.assertEqual(font.name, "FreeSans") + + def test_freetype_Font_init(self): + + self.assertRaises( + FileNotFoundError, ft.Font, os.path.join(FONTDIR, "nonexistent.ttf") + ) + + f = self._TEST_FONTS["sans"] + self.assertIsInstance(f, ft.Font) + + f = self._TEST_FONTS["fixed"] + self.assertIsInstance(f, ft.Font) + + # Test keyword arguments + f = ft.Font(size=22, file=None) + self.assertEqual(f.size, 22) + f = ft.Font(font_index=0, file=None) + self.assertNotEqual(ft.get_default_resolution(), 100) + f = ft.Font(resolution=100, file=None) + self.assertEqual(f.resolution, 100) + f = ft.Font(ucs4=True, file=None) + self.assertTrue(f.ucs4) + self.assertRaises(OverflowError, ft.Font, file=None, size=(max_point_size + 1)) + self.assertRaises(OverflowError, ft.Font, file=None, size=-1) + + f = ft.Font(None, size=24) + self.assertTrue(f.height > 0) + self.assertRaises( + FileNotFoundError, f.__init__, os.path.join(FONTDIR, "nonexistent.ttf") + ) + + # Test attribute preservation during reinitalization + f = ft.Font(self._sans_path, size=24, ucs4=True) + self.assertEqual(f.name, "Liberation Sans") + self.assertTrue(f.scalable) + self.assertFalse(f.fixed_width) + self.assertTrue(f.antialiased) + self.assertFalse(f.oblique) + self.assertTrue(f.ucs4) + f.antialiased = False + f.oblique = True + f.__init__(self._mono_path) + self.assertEqual(f.name, "PyGameMono") + self.assertTrue(f.scalable) + self.assertTrue(f.fixed_width) + self.assertFalse(f.antialiased) + self.assertTrue(f.oblique) + self.assertTrue(f.ucs4) + + # For a bitmap font, the size is automatically set to the first + # size in the available sizes list. + f = ft.Font(self._bmp_8_75dpi_path) + sizes = f.get_sizes() + self.assertEqual(len(sizes), 1) + size_pt, width_px, height_px, x_ppem, y_ppem = sizes[0] + self.assertEqual(f.size, (x_ppem, y_ppem)) + f.__init__(self._bmp_8_75dpi_path, size=12) + self.assertEqual(f.size, 12.0) + + @unittest.skipIf(IS_PYPY, "PyPy doesn't use refcounting") + def test_freetype_Font_dealloc(self): + import sys + + handle = open(self._sans_path, "rb") + + def load_font(): + tempFont = ft.Font(handle) + + try: + load_font() + + self.assertEqual(sys.getrefcount(handle), 2) + finally: + # Ensures file is closed even if test fails. + handle.close() + + def test_freetype_Font_kerning(self): + """Ensures get/set works with the kerning property.""" + ft_font = self._TEST_FONTS["sans"] + + # Test default is disabled. + self.assertFalse(ft_font.kerning) + + # Test setting to True. + ft_font.kerning = True + + self.assertTrue(ft_font.kerning) + + # Test setting to False. + ft_font.kerning = False + + self.assertFalse(ft_font.kerning) + + def test_freetype_Font_kerning__enabled(self): + """Ensures exceptions are not raised when calling freetype methods + while kerning is enabled. + + Note: This does not test what changes occur to a rendered font by + having kerning enabled. + + Related to issue #367. + """ + surface = pygame.Surface((10, 10), 0, 32) + TEST_TEXT = "Freetype Font" + ft_font = self._TEST_FONTS["bmp-8-75dpi"] + + ft_font.kerning = True + + # Call different methods to ensure they don't raise an exception. + metrics = ft_font.get_metrics(TEST_TEXT) + self.assertIsInstance(metrics, list) + + rect = ft_font.get_rect(TEST_TEXT) + self.assertIsInstance(rect, pygame.Rect) + + font_surf, rect = ft_font.render(TEST_TEXT) + self.assertIsInstance(font_surf, pygame.Surface) + self.assertIsInstance(rect, pygame.Rect) + + rect = ft_font.render_to(surface, (0, 0), TEST_TEXT) + self.assertIsInstance(rect, pygame.Rect) + + buf, size = ft_font.render_raw(TEST_TEXT) + self.assertIsInstance(buf, bytes) + self.assertIsInstance(size, tuple) + + rect = ft_font.render_raw_to(surface.get_view("2"), TEST_TEXT) + self.assertIsInstance(rect, pygame.Rect) + + def test_freetype_Font_scalable(self): + + f = self._TEST_FONTS["sans"] + self.assertTrue(f.scalable) + + self.assertRaises(RuntimeError, lambda: nullfont().scalable) + + def test_freetype_Font_fixed_width(self): + + f = self._TEST_FONTS["sans"] + self.assertFalse(f.fixed_width) + + f = self._TEST_FONTS["mono"] + self.assertTrue(f.fixed_width) + + self.assertRaises(RuntimeError, lambda: nullfont().fixed_width) + + def test_freetype_Font_fixed_sizes(self): + + f = self._TEST_FONTS["sans"] + self.assertEqual(f.fixed_sizes, 0) + f = self._TEST_FONTS["bmp-8-75dpi"] + self.assertEqual(f.fixed_sizes, 1) + f = self._TEST_FONTS["mono"] + self.assertEqual(f.fixed_sizes, 2) + + def test_freetype_Font_get_sizes(self): + f = self._TEST_FONTS["sans"] + szlist = f.get_sizes() + self.assertIsInstance(szlist, list) + self.assertEqual(len(szlist), 0) + + f = self._TEST_FONTS["bmp-8-75dpi"] + szlist = f.get_sizes() + self.assertIsInstance(szlist, list) + self.assertEqual(len(szlist), 1) + + size8 = szlist[0] + self.assertIsInstance(size8[0], int) + self.assertEqual(size8[0], 8) + self.assertIsInstance(size8[1], int) + self.assertIsInstance(size8[2], int) + self.assertIsInstance(size8[3], float) + self.assertEqual(int(size8[3] * 64.0 + 0.5), 8 * 64) + self.assertIsInstance(size8[4], float) + self.assertEqual(int(size8[4] * 64.0 + 0.5), 8 * 64) + + f = self._TEST_FONTS["mono"] + szlist = f.get_sizes() + self.assertIsInstance(szlist, list) + self.assertEqual(len(szlist), 2) + + size8 = szlist[0] + self.assertEqual(size8[3], 8) + self.assertEqual(int(size8[3] * 64.0 + 0.5), 8 * 64) + self.assertEqual(int(size8[4] * 64.0 + 0.5), 8 * 64) + + size19 = szlist[1] + self.assertEqual(size19[3], 19) + self.assertEqual(int(size19[3] * 64.0 + 0.5), 19 * 64) + self.assertEqual(int(size19[4] * 64.0 + 0.5), 19 * 64) + + def test_freetype_Font_use_bitmap_strikes(self): + f = self._TEST_FONTS["mono"] + try: + # use_bitmap_strikes == True + # + self.assertTrue(f.use_bitmap_strikes) + + # bitmap compatible properties + s_strike, sz = f.render_raw("A", size=19) + try: + f.vertical = True + s_strike_vert, sz = f.render_raw("A", size=19) + finally: + f.vertical = False + try: + f.wide = True + s_strike_wide, sz = f.render_raw("A", size=19) + finally: + f.wide = False + try: + f.underline = True + s_strike_underline, sz = f.render_raw("A", size=19) + finally: + f.underline = False + + # bitmap incompatible properties + s_strike_rot45, sz = f.render_raw("A", size=19, rotation=45) + try: + f.strong = True + s_strike_strong, sz = f.render_raw("A", size=19) + finally: + f.strong = False + try: + f.oblique = True + s_strike_oblique, sz = f.render_raw("A", size=19) + finally: + f.oblique = False + + # compare with use_bitmap_strikes == False + # + f.use_bitmap_strikes = False + self.assertFalse(f.use_bitmap_strikes) + + # bitmap compatible properties + s_outline, sz = f.render_raw("A", size=19) + self.assertNotEqual(s_outline, s_strike) + try: + f.vertical = True + s_outline, sz = f.render_raw("A", size=19) + self.assertNotEqual(s_outline, s_strike_vert) + finally: + f.vertical = False + try: + f.wide = True + s_outline, sz = f.render_raw("A", size=19) + self.assertNotEqual(s_outline, s_strike_wide) + finally: + f.wide = False + try: + f.underline = True + s_outline, sz = f.render_raw("A", size=19) + self.assertNotEqual(s_outline, s_strike_underline) + finally: + f.underline = False + + # bitmap incompatible properties + s_outline, sz = f.render_raw("A", size=19, rotation=45) + self.assertEqual(s_outline, s_strike_rot45) + try: + f.strong = True + s_outline, sz = f.render_raw("A", size=19) + self.assertEqual(s_outline, s_strike_strong) + finally: + f.strong = False + try: + f.oblique = True + s_outline, sz = f.render_raw("A", size=19) + self.assertEqual(s_outline, s_strike_oblique) + finally: + f.oblique = False + finally: + f.use_bitmap_strikes = True + + def test_freetype_Font_bitmap_files(self): + """Ensure bitmap file restrictions are caught""" + f = self._TEST_FONTS["bmp-8-75dpi"] + f_null = nullfont() + s = pygame.Surface((10, 10), 0, 32) + a = s.get_view("3") + + exception = AttributeError + self.assertRaises(exception, setattr, f, "strong", True) + self.assertRaises(exception, setattr, f, "oblique", True) + self.assertRaises(exception, setattr, f, "style", ft.STYLE_STRONG) + self.assertRaises(exception, setattr, f, "style", ft.STYLE_OBLIQUE) + exception = RuntimeError + self.assertRaises(exception, setattr, f_null, "strong", True) + self.assertRaises(exception, setattr, f_null, "oblique", True) + self.assertRaises(exception, setattr, f_null, "style", ft.STYLE_STRONG) + self.assertRaises(exception, setattr, f_null, "style", ft.STYLE_OBLIQUE) + exception = ValueError + self.assertRaises(exception, f.render, "A", (0, 0, 0), size=8, rotation=1) + self.assertRaises( + exception, f.render, "A", (0, 0, 0), size=8, style=ft.STYLE_OBLIQUE + ) + self.assertRaises( + exception, f.render, "A", (0, 0, 0), size=8, style=ft.STYLE_STRONG + ) + self.assertRaises(exception, f.render_raw, "A", size=8, rotation=1) + self.assertRaises(exception, f.render_raw, "A", size=8, style=ft.STYLE_OBLIQUE) + self.assertRaises(exception, f.render_raw, "A", size=8, style=ft.STYLE_STRONG) + self.assertRaises( + exception, f.render_to, s, (0, 0), "A", (0, 0, 0), size=8, rotation=1 + ) + self.assertRaises( + exception, + f.render_to, + s, + (0, 0), + "A", + (0, 0, 0), + size=8, + style=ft.STYLE_OBLIQUE, + ) + self.assertRaises( + exception, + f.render_to, + s, + (0, 0), + "A", + (0, 0, 0), + size=8, + style=ft.STYLE_STRONG, + ) + self.assertRaises(exception, f.render_raw_to, a, "A", size=8, rotation=1) + self.assertRaises( + exception, f.render_raw_to, a, "A", size=8, style=ft.STYLE_OBLIQUE + ) + self.assertRaises( + exception, f.render_raw_to, a, "A", size=8, style=ft.STYLE_STRONG + ) + self.assertRaises(exception, f.get_rect, "A", size=8, rotation=1) + self.assertRaises(exception, f.get_rect, "A", size=8, style=ft.STYLE_OBLIQUE) + self.assertRaises(exception, f.get_rect, "A", size=8, style=ft.STYLE_STRONG) + + # Unsupported point size + exception = pygame.error + self.assertRaises(exception, f.get_rect, "A", size=42) + self.assertRaises(exception, f.get_metrics, "A", size=42) + self.assertRaises(exception, f.get_sized_ascender, 42) + self.assertRaises(exception, f.get_sized_descender, 42) + self.assertRaises(exception, f.get_sized_height, 42) + self.assertRaises(exception, f.get_sized_glyph_height, 42) + + def test_freetype_Font_get_metrics(self): + + font = self._TEST_FONTS["sans"] + + metrics = font.get_metrics("ABCD", size=24) + self.assertEqual(len(metrics), len("ABCD")) + self.assertIsInstance(metrics, list) + + for metrics_tuple in metrics: + self.assertIsInstance(metrics_tuple, tuple, metrics_tuple) + self.assertEqual(len(metrics_tuple), 6) + + for m in metrics_tuple[:4]: + self.assertIsInstance(m, int) + + for m in metrics_tuple[4:]: + self.assertIsInstance(m, float) + + # test for empty string + metrics = font.get_metrics("", size=24) + self.assertEqual(metrics, []) + + # test for invalid string + self.assertRaises(TypeError, font.get_metrics, 24, 24) + + # raises exception when uninitalized + self.assertRaises(RuntimeError, nullfont().get_metrics, "a", size=24) + + def test_freetype_Font_get_rect(self): + + font = self._TEST_FONTS["sans"] + + def test_rect(r): + self.assertIsInstance(r, pygame.Rect) + + rect_default = font.get_rect("ABCDabcd", size=24) + test_rect(rect_default) + self.assertTrue(rect_default.size > (0, 0)) + self.assertTrue(rect_default.width > rect_default.height) + + rect_bigger = font.get_rect("ABCDabcd", size=32) + test_rect(rect_bigger) + self.assertTrue(rect_bigger.size > rect_default.size) + + rect_strong = font.get_rect("ABCDabcd", size=24, style=ft.STYLE_STRONG) + test_rect(rect_strong) + self.assertTrue(rect_strong.size > rect_default.size) + + font.vertical = True + rect_vert = font.get_rect("ABCDabcd", size=24) + test_rect(rect_vert) + self.assertTrue(rect_vert.width < rect_vert.height) + font.vertical = False + + rect_oblique = font.get_rect("ABCDabcd", size=24, style=ft.STYLE_OBLIQUE) + test_rect(rect_oblique) + self.assertTrue(rect_oblique.width > rect_default.width) + self.assertTrue(rect_oblique.height == rect_default.height) + + rect_under = font.get_rect("ABCDabcd", size=24, style=ft.STYLE_UNDERLINE) + test_rect(rect_under) + self.assertTrue(rect_under.width == rect_default.width) + self.assertTrue(rect_under.height > rect_default.height) + + # Rect size should change if UTF surrogate pairs are treated as + # one code point or two. + ufont = self._TEST_FONTS["mono"] + rect_utf32 = ufont.get_rect("\U00013079", size=24) + rect_utf16 = ufont.get_rect("\uD80C\uDC79", size=24) + self.assertEqual(rect_utf16, rect_utf32) + ufont.ucs4 = True + try: + rect_utf16 = ufont.get_rect("\uD80C\uDC79", size=24) + finally: + ufont.ucs4 = False + self.assertNotEqual(rect_utf16, rect_utf32) + + self.assertRaises(RuntimeError, nullfont().get_rect, "a", size=24) + + # text stretching + rect12 = font.get_rect("A", size=12.0) + rect24 = font.get_rect("A", size=24.0) + rect_x = font.get_rect("A", size=(24.0, 12.0)) + self.assertEqual(rect_x.width, rect24.width) + self.assertEqual(rect_x.height, rect12.height) + rect_y = font.get_rect("A", size=(12.0, 24.0)) + self.assertEqual(rect_y.width, rect12.width) + self.assertEqual(rect_y.height, rect24.height) + + def test_freetype_Font_height(self): + + f = self._TEST_FONTS["sans"] + self.assertEqual(f.height, 2355) + + f = self._TEST_FONTS["fixed"] + self.assertEqual(f.height, 1100) + + self.assertRaises(RuntimeError, lambda: nullfont().height) + + def test_freetype_Font_name(self): + + f = self._TEST_FONTS["sans"] + self.assertEqual(f.name, "Liberation Sans") + + f = self._TEST_FONTS["fixed"] + self.assertEqual(f.name, "Inconsolata") + + nf = nullfont() + self.assertEqual(nf.name, repr(nf)) + + def test_freetype_Font_size(self): + + f = ft.Font(None, size=12) + self.assertEqual(f.size, 12) + f.size = 22 + self.assertEqual(f.size, 22) + f.size = 0 + self.assertEqual(f.size, 0) + f.size = max_point_size + self.assertEqual(f.size, max_point_size) + f.size = 6.5 + self.assertEqual(f.size, 6.5) + f.size = max_point_size_f + self.assertEqual(f.size, max_point_size_f) + self.assertRaises(OverflowError, setattr, f, "size", -1) + self.assertRaises(OverflowError, setattr, f, "size", (max_point_size + 1)) + + f.size = 24.0, 0 + size = f.size + self.assertIsInstance(size, float) + self.assertEqual(size, 24.0) + + f.size = 16, 16 + size = f.size + self.assertIsInstance(size, tuple) + self.assertEqual(len(size), 2) + + x, y = size + self.assertIsInstance(x, float) + self.assertEqual(x, 16.0) + self.assertIsInstance(y, float) + self.assertEqual(y, 16.0) + + f.size = 20.5, 22.25 + x, y = f.size + self.assertEqual(x, 20.5) + self.assertEqual(y, 22.25) + + f.size = 0, 0 + size = f.size + self.assertIsInstance(size, float) + self.assertEqual(size, 0.0) + self.assertRaises(ValueError, setattr, f, "size", (0, 24.0)) + self.assertRaises(TypeError, setattr, f, "size", (24.0,)) + self.assertRaises(TypeError, setattr, f, "size", (24.0, 0, 0)) + self.assertRaises(TypeError, setattr, f, "size", (24.0j, 24.0)) + self.assertRaises(TypeError, setattr, f, "size", (24.0, 24.0j)) + self.assertRaises(OverflowError, setattr, f, "size", (-1, 16)) + self.assertRaises(OverflowError, setattr, f, "size", (max_point_size + 1, 16)) + self.assertRaises(OverflowError, setattr, f, "size", (16, -1)) + self.assertRaises(OverflowError, setattr, f, "size", (16, max_point_size + 1)) + + # bitmap files with identical point size but differing ppems. + f75 = self._TEST_FONTS["bmp-18-75dpi"] + sizes = f75.get_sizes() + self.assertEqual(len(sizes), 1) + size_pt, width_px, height_px, x_ppem, y_ppem = sizes[0] + self.assertEqual(size_pt, 18) + self.assertEqual(x_ppem, 19.0) + self.assertEqual(y_ppem, 19.0) + rect = f75.get_rect("A", size=18) + rect = f75.get_rect("A", size=19) + rect = f75.get_rect("A", size=(19.0, 19.0)) + self.assertRaises(pygame.error, f75.get_rect, "A", size=17) + f100 = self._TEST_FONTS["bmp-18-100dpi"] + sizes = f100.get_sizes() + self.assertEqual(len(sizes), 1) + size_pt, width_px, height_px, x_ppem, y_ppem = sizes[0] + self.assertEqual(size_pt, 18) + self.assertEqual(x_ppem, 25.0) + self.assertEqual(y_ppem, 25.0) + rect = f100.get_rect("A", size=18) + rect = f100.get_rect("A", size=25) + rect = f100.get_rect("A", size=(25.0, 25.0)) + self.assertRaises(pygame.error, f100.get_rect, "A", size=17) + + def test_freetype_Font_rotation(self): + + test_angles = [ + (30, 30), + (360, 0), + (390, 30), + (720, 0), + (764, 44), + (-30, 330), + (-360, 0), + (-390, 330), + (-720, 0), + (-764, 316), + ] + + f = ft.Font(None) + self.assertEqual(f.rotation, 0) + for r, r_reduced in test_angles: + f.rotation = r + self.assertEqual( + f.rotation, + r_reduced, + "for angle %d: %d != %d" % (r, f.rotation, r_reduced), + ) + self.assertRaises(TypeError, setattr, f, "rotation", "12") + + def test_freetype_Font_render_to(self): + # Rendering to an existing target surface is equivalent to + # blitting a surface returned by Font.render with the target. + font = self._TEST_FONTS["sans"] + + surf = pygame.Surface((800, 600)) + color = pygame.Color(0, 0, 0) + + rrect = font.render_to(surf, (32, 32), "FoobarBaz", color, None, size=24) + self.assertIsInstance(rrect, pygame.Rect) + self.assertEqual(rrect.topleft, (32, 32)) + self.assertNotEqual(rrect.bottomright, (32, 32)) + + rcopy = rrect.copy() + rcopy.topleft = (32, 32) + self.assertTrue(surf.get_rect().contains(rcopy)) + + rect = pygame.Rect(20, 20, 2, 2) + rrect = font.render_to(surf, rect, "FoobarBax", color, None, size=24) + self.assertEqual(rect.topleft, rrect.topleft) + self.assertNotEqual(rrect.size, rect.size) + rrect = font.render_to(surf, (20.1, 18.9), "FoobarBax", color, None, size=24) + + rrect = font.render_to(surf, rect, "", color, None, size=24) + self.assertFalse(rrect) + self.assertEqual(rrect.height, font.get_sized_height(24)) + + # invalid surf test + self.assertRaises(TypeError, font.render_to, "not a surface", "text", color) + self.assertRaises(TypeError, font.render_to, pygame.Surface, "text", color) + + # invalid dest test + for dest in [ + None, + 0, + "a", + "ab", + (), + (1,), + ("a", 2), + (1, "a"), + (1 + 2j, 2), + (1, 1 + 2j), + (1, int), + (int, 1), + ]: + self.assertRaises( + TypeError, font.render_to, surf, dest, "foobar", color, size=24 + ) + + # misc parameter test + self.assertRaises(ValueError, font.render_to, surf, (0, 0), "foobar", color) + self.assertRaises( + TypeError, font.render_to, surf, (0, 0), "foobar", color, 2.3, size=24 + ) + self.assertRaises( + ValueError, + font.render_to, + surf, + (0, 0), + "foobar", + color, + None, + style=42, + size=24, + ) + self.assertRaises( + TypeError, + font.render_to, + surf, + (0, 0), + "foobar", + color, + None, + style=None, + size=24, + ) + self.assertRaises( + ValueError, + font.render_to, + surf, + (0, 0), + "foobar", + color, + None, + style=97, + size=24, + ) + + def test_freetype_Font_render(self): + + font = self._TEST_FONTS["sans"] + + surf = pygame.Surface((800, 600)) + color = pygame.Color(0, 0, 0) + + rend = font.render("FoobarBaz", pygame.Color(0, 0, 0), None, size=24) + self.assertIsInstance(rend, tuple) + self.assertEqual(len(rend), 2) + self.assertIsInstance(rend[0], pygame.Surface) + self.assertIsInstance(rend[1], pygame.Rect) + self.assertEqual(rend[0].get_rect().size, rend[1].size) + + s, r = font.render("", pygame.Color(0, 0, 0), None, size=24) + self.assertEqual(r.width, 0) + self.assertEqual(r.height, font.get_sized_height(24)) + self.assertEqual(s.get_size(), r.size) + self.assertEqual(s.get_bitsize(), 32) + + # misc parameter test + self.assertRaises(ValueError, font.render, "foobar", color) + self.assertRaises(TypeError, font.render, "foobar", color, 2.3, size=24) + self.assertRaises( + ValueError, font.render, "foobar", color, None, style=42, size=24 + ) + self.assertRaises( + TypeError, font.render, "foobar", color, None, style=None, size=24 + ) + self.assertRaises( + ValueError, font.render, "foobar", color, None, style=97, size=24 + ) + + # valid surrogate pairs + font2 = self._TEST_FONTS["mono"] + ucs4 = font2.ucs4 + try: + font2.ucs4 = False + rend1 = font2.render("\uD80C\uDC79", color, size=24) + rend2 = font2.render("\U00013079", color, size=24) + self.assertEqual(rend1[1], rend2[1]) + font2.ucs4 = True + rend1 = font2.render("\uD80C\uDC79", color, size=24) + self.assertNotEqual(rend1[1], rend2[1]) + finally: + font2.ucs4 = ucs4 + + # malformed surrogate pairs + self.assertRaises(UnicodeEncodeError, font.render, "\uD80C", color, size=24) + self.assertRaises(UnicodeEncodeError, font.render, "\uDCA7", color, size=24) + self.assertRaises( + UnicodeEncodeError, font.render, "\uD7FF\uDCA7", color, size=24 + ) + self.assertRaises( + UnicodeEncodeError, font.render, "\uDC00\uDCA7", color, size=24 + ) + self.assertRaises( + UnicodeEncodeError, font.render, "\uD80C\uDBFF", color, size=24 + ) + self.assertRaises( + UnicodeEncodeError, font.render, "\uD80C\uE000", color, size=24 + ) + + # raises exception when uninitalized + self.assertRaises(RuntimeError, nullfont().render, "a", (0, 0, 0), size=24) + + # Confirm the correct glpyhs are returned for a couple of + # unicode code points, 'A' and '\U00023079'. For each code point + # the rendered glyph is compared with an image of glyph bitmap + # as exported by FontForge. + path = os.path.join(FONTDIR, "A_PyGameMono-8.png") + A = pygame.image.load(path) + path = os.path.join(FONTDIR, "u13079_PyGameMono-8.png") + u13079 = pygame.image.load(path) + + font = self._TEST_FONTS["mono"] + font.ucs4 = False + A_rendered, r = font.render("A", bgcolor=pygame.Color("white"), size=8) + u13079_rendered, r = font.render( + "\U00013079", bgcolor=pygame.Color("white"), size=8 + ) + + # before comparing the surfaces, make sure they are the same + # pixel format. Use 32-bit SRCALPHA to avoid row padding and + # undefined bytes (the alpha byte will be set to 255.) + bitmap = pygame.Surface(A.get_size(), pygame.SRCALPHA, 32) + bitmap.blit(A, (0, 0)) + rendering = pygame.Surface(A_rendered.get_size(), pygame.SRCALPHA, 32) + rendering.blit(A_rendered, (0, 0)) + self.assertTrue(surf_same_image(rendering, bitmap)) + bitmap = pygame.Surface(u13079.get_size(), pygame.SRCALPHA, 32) + bitmap.blit(u13079, (0, 0)) + rendering = pygame.Surface(u13079_rendered.get_size(), pygame.SRCALPHA, 32) + rendering.blit(u13079_rendered, (0, 0)) + self.assertTrue(surf_same_image(rendering, bitmap)) + + def test_freetype_Font_render_mono(self): + font = self._TEST_FONTS["sans"] + color = pygame.Color("black") + colorkey = pygame.Color("white") + text = "." + + save_antialiased = font.antialiased + font.antialiased = False + try: + surf, r = font.render(text, color, size=24) + self.assertEqual(surf.get_bitsize(), 8) + flags = surf.get_flags() + self.assertTrue(flags & pygame.SRCCOLORKEY) + self.assertFalse(flags & (pygame.SRCALPHA | pygame.HWSURFACE)) + self.assertEqual(surf.get_colorkey(), colorkey) + self.assertIsNone(surf.get_alpha()) + + translucent_color = pygame.Color(*color) + translucent_color.a = 55 + surf, r = font.render(text, translucent_color, size=24) + self.assertEqual(surf.get_bitsize(), 8) + flags = surf.get_flags() + self.assertTrue(flags & (pygame.SRCCOLORKEY | pygame.SRCALPHA)) + self.assertFalse(flags & pygame.HWSURFACE) + self.assertEqual(surf.get_colorkey(), colorkey) + self.assertEqual(surf.get_alpha(), translucent_color.a) + + surf, r = font.render(text, color, colorkey, size=24) + self.assertEqual(surf.get_bitsize(), 32) + finally: + font.antialiased = save_antialiased + + def test_freetype_Font_render_to_mono(self): + # Blitting is done in two stages. First the target is alpha filled + # with the background color, if any. Second, the foreground + # color is alpha blitted to the background. + font = self._TEST_FONTS["sans"] + text = " ." + rect = font.get_rect(text, size=24) + size = rect.size + fg = pygame.Surface((1, 1), pygame.SRCALPHA, 32) + bg = pygame.Surface((1, 1), pygame.SRCALPHA, 32) + surrogate = pygame.Surface((1, 1), pygame.SRCALPHA, 32) + surfaces = [ + pygame.Surface(size, 0, 8), + pygame.Surface(size, 0, 16), + pygame.Surface(size, pygame.SRCALPHA, 16), + pygame.Surface(size, 0, 24), + pygame.Surface(size, 0, 32), + pygame.Surface(size, pygame.SRCALPHA, 32), + ] + fg_colors = [ + surfaces[0].get_palette_at(2), + surfaces[1].unmap_rgb(surfaces[1].map_rgb((128, 64, 200))), + surfaces[2].unmap_rgb(surfaces[2].map_rgb((99, 0, 100, 64))), + (128, 97, 213), + (128, 97, 213), + (128, 97, 213, 60), + ] + fg_colors = [pygame.Color(*c) for c in fg_colors] + self.assertEqual(len(surfaces), len(fg_colors)) # integrity check + bg_colors = [ + surfaces[0].get_palette_at(4), + surfaces[1].unmap_rgb(surfaces[1].map_rgb((220, 20, 99))), + surfaces[2].unmap_rgb(surfaces[2].map_rgb((55, 200, 0, 86))), + (255, 120, 13), + (255, 120, 13), + (255, 120, 13, 180), + ] + bg_colors = [pygame.Color(*c) for c in bg_colors] + self.assertEqual(len(surfaces), len(bg_colors)) # integrity check + + save_antialiased = font.antialiased + font.antialiased = False + try: + fill_color = pygame.Color("black") + for i, surf in enumerate(surfaces): + surf.fill(fill_color) + fg_color = fg_colors[i] + fg.set_at((0, 0), fg_color) + surf.blit(fg, (0, 0)) + r_fg_color = surf.get_at((0, 0)) + surf.set_at((0, 0), fill_color) + rrect = font.render_to(surf, (0, 0), text, fg_color, size=24) + bottomleft = 0, rrect.height - 1 + self.assertEqual( + surf.get_at(bottomleft), + fill_color, + "Position: {}. Depth: {}." + " fg_color: {}.".format(bottomleft, surf.get_bitsize(), fg_color), + ) + bottomright = rrect.width - 1, rrect.height - 1 + self.assertEqual( + surf.get_at(bottomright), + r_fg_color, + "Position: {}. Depth: {}." + " fg_color: {}.".format(bottomright, surf.get_bitsize(), fg_color), + ) + for i, surf in enumerate(surfaces): + surf.fill(fill_color) + fg_color = fg_colors[i] + bg_color = bg_colors[i] + bg.set_at((0, 0), bg_color) + fg.set_at((0, 0), fg_color) + if surf.get_bitsize() == 24: + # For a 24 bit target surface test against Pygame's alpha + # blit as there appears to be a problem with SDL's alpha + # blit: + # + # self.assertEqual(surf.get_at(bottomright), r_fg_color) + # + # raises + # + # AssertionError: (128, 97, 213, 255) != (129, 98, 213, 255) + # + surrogate.set_at((0, 0), fill_color) + surrogate.blit(bg, (0, 0)) + r_bg_color = surrogate.get_at((0, 0)) + surrogate.blit(fg, (0, 0)) + r_fg_color = surrogate.get_at((0, 0)) + else: + # Surface blit values for comparison. + surf.blit(bg, (0, 0)) + r_bg_color = surf.get_at((0, 0)) + surf.blit(fg, (0, 0)) + r_fg_color = surf.get_at((0, 0)) + surf.set_at((0, 0), fill_color) + rrect = font.render_to(surf, (0, 0), text, fg_color, bg_color, size=24) + bottomleft = 0, rrect.height - 1 + self.assertEqual(surf.get_at(bottomleft), r_bg_color) + bottomright = rrect.width - 1, rrect.height - 1 + self.assertEqual(surf.get_at(bottomright), r_fg_color) + finally: + font.antialiased = save_antialiased + + def test_freetype_Font_render_raw(self): + + font = self._TEST_FONTS["sans"] + + text = "abc" + size = font.get_rect(text, size=24).size + rend = font.render_raw(text, size=24) + self.assertIsInstance(rend, tuple) + self.assertEqual(len(rend), 2) + + r, s = rend + self.assertIsInstance(r, bytes) + self.assertIsInstance(s, tuple) + self.assertTrue(len(s), 2) + + w, h = s + self.assertIsInstance(w, int) + self.assertIsInstance(h, int) + self.assertEqual(s, size) + self.assertEqual(len(r), w * h) + + r, (w, h) = font.render_raw("", size=24) + self.assertEqual(w, 0) + self.assertEqual(h, font.height) + self.assertEqual(len(r), 0) + + # bug with decenders: this would crash + rend = font.render_raw("render_raw", size=24) + + # bug with non-printable characters: this would cause a crash + # because the text length was not adjusted for skipped characters. + text = "".join([chr(i) for i in range(31, 64)]) + rend = font.render_raw(text, size=10) + + def test_freetype_Font_render_raw_to(self): + + # This only checks that blits do not crash. It needs to check: + # - int values + # - invert option + # + + font = self._TEST_FONTS["sans"] + text = "abc" + + # No frills antialiased render to int1 (__render_glyph_INT) + srect = font.get_rect(text, size=24) + surf = pygame.Surface(srect.size, 0, 8) + rrect = font.render_raw_to(surf.get_view("2"), text, size=24) + self.assertEqual(rrect, srect) + + for bpp in [24, 32]: + surf = pygame.Surface(srect.size, 0, bpp) + rrect = font.render_raw_to(surf.get_view("r"), text, size=24) + self.assertEqual(rrect, srect) + + # Underlining to int1 (__fill_glyph_INT) + srect = font.get_rect(text, size=24, style=ft.STYLE_UNDERLINE) + surf = pygame.Surface(srect.size, 0, 8) + rrect = font.render_raw_to( + surf.get_view("2"), text, size=24, style=ft.STYLE_UNDERLINE + ) + self.assertEqual(rrect, srect) + + for bpp in [24, 32]: + surf = pygame.Surface(srect.size, 0, bpp) + rrect = font.render_raw_to( + surf.get_view("r"), text, size=24, style=ft.STYLE_UNDERLINE + ) + self.assertEqual(rrect, srect) + + # Unaliased (mono) rendering to int1 (__render_glyph_MONO_as_INT) + font.antialiased = False + try: + srect = font.get_rect(text, size=24) + surf = pygame.Surface(srect.size, 0, 8) + rrect = font.render_raw_to(surf.get_view("2"), text, size=24) + self.assertEqual(rrect, srect) + + for bpp in [24, 32]: + surf = pygame.Surface(srect.size, 0, bpp) + rrect = font.render_raw_to(surf.get_view("r"), text, size=24) + self.assertEqual(rrect, srect) + finally: + font.antialiased = True + + # Antialiased render to ints sized greater than 1 byte + # (__render_glyph_INT) + srect = font.get_rect(text, size=24) + + for bpp in [16, 24, 32]: + surf = pygame.Surface(srect.size, 0, bpp) + rrect = font.render_raw_to(surf.get_view("2"), text, size=24) + self.assertEqual(rrect, srect) + + # Underline render to ints sized greater than 1 byte + # (__fill_glyph_INT) + srect = font.get_rect(text, size=24, style=ft.STYLE_UNDERLINE) + + for bpp in [16, 24, 32]: + surf = pygame.Surface(srect.size, 0, bpp) + rrect = font.render_raw_to( + surf.get_view("2"), text, size=24, style=ft.STYLE_UNDERLINE + ) + self.assertEqual(rrect, srect) + + # Unaliased (mono) rendering to ints greater than 1 byte + # (__render_glyph_MONO_as_INT) + font.antialiased = False + try: + srect = font.get_rect(text, size=24) + + for bpp in [16, 24, 32]: + surf = pygame.Surface(srect.size, 0, bpp) + rrect = font.render_raw_to(surf.get_view("2"), text, size=24) + self.assertEqual(rrect, srect) + finally: + font.antialiased = True + + # Invalid dest parameter test. + srect = font.get_rect(text, size=24) + surf_buf = pygame.Surface(srect.size, 0, 32).get_view("2") + + for dest in [ + 0, + "a", + "ab", + (), + (1,), + ("a", 2), + (1, "a"), + (1 + 2j, 2), + (1, 1 + 2j), + (1, int), + (int, 1), + ]: + self.assertRaises( + TypeError, font.render_raw_to, surf_buf, text, dest, size=24 + ) + + def test_freetype_Font_text_is_None(self): + f = ft.Font(self._sans_path, 36) + f.style = ft.STYLE_NORMAL + f.rotation = 0 + text = "ABCD" + + # reference values + get_rect = f.get_rect(text) + f.vertical = True + get_rect_vert = f.get_rect(text) + + self.assertTrue(get_rect_vert.width < get_rect.width) + self.assertTrue(get_rect_vert.height > get_rect.height) + f.vertical = False + render_to_surf = pygame.Surface(get_rect.size, pygame.SRCALPHA, 32) + + if IS_PYPY: + return + + arr = arrinter.Array(get_rect.size, "u", 1) + render = f.render(text, (0, 0, 0)) + render_to = f.render_to(render_to_surf, (0, 0), text, (0, 0, 0)) + render_raw = f.render_raw(text) + render_raw_to = f.render_raw_to(arr, text) + + # comparisons + surf = pygame.Surface(get_rect.size, pygame.SRCALPHA, 32) + self.assertEqual(f.get_rect(None), get_rect) + s, r = f.render(None, (0, 0, 0)) + self.assertEqual(r, render[1]) + self.assertTrue(surf_same_image(s, render[0])) + r = f.render_to(surf, (0, 0), None, (0, 0, 0)) + self.assertEqual(r, render_to) + self.assertTrue(surf_same_image(surf, render_to_surf)) + px, sz = f.render_raw(None) + self.assertEqual(sz, render_raw[1]) + self.assertEqual(px, render_raw[0]) + sz = f.render_raw_to(arr, None) + self.assertEqual(sz, render_raw_to) + + def test_freetype_Font_text_is_None(self): + f = ft.Font(self._sans_path, 36) + f.style = ft.STYLE_NORMAL + f.rotation = 0 + text = "ABCD" + + # reference values + get_rect = f.get_rect(text) + f.vertical = True + get_rect_vert = f.get_rect(text) + + # vertical: trigger glyph positioning. + f.vertical = True + r = f.get_rect(None) + self.assertEqual(r, get_rect_vert) + f.vertical = False + + # wide style: trigger glyph reload + r = f.get_rect(None, style=ft.STYLE_WIDE) + self.assertEqual(r.height, get_rect.height) + self.assertTrue(r.width > get_rect.width) + r = f.get_rect(None) + self.assertEqual(r, get_rect) + + # rotated: trigger glyph reload + r = f.get_rect(None, rotation=90) + self.assertEqual(r.width, get_rect.height) + self.assertEqual(r.height, get_rect.width) + + # this method will not support None text + self.assertRaises(TypeError, f.get_metrics, None) + + def test_freetype_Font_fgcolor(self): + f = ft.Font(self._bmp_8_75dpi_path) + notdef = "\0" # the PyGameMono .notdef glyph has a pixel at (0, 0) + f.origin = False + f.pad = False + black = pygame.Color("black") # initial color + green = pygame.Color("green") + alpha128 = pygame.Color(10, 20, 30, 128) + + c = f.fgcolor + self.assertIsInstance(c, pygame.Color) + self.assertEqual(c, black) + + s, r = f.render(notdef) + self.assertEqual(s.get_at((0, 0)), black) + + f.fgcolor = green + self.assertEqual(f.fgcolor, green) + + s, r = f.render(notdef) + self.assertEqual(s.get_at((0, 0)), green) + + f.fgcolor = alpha128 + s, r = f.render(notdef) + self.assertEqual(s.get_at((0, 0)), alpha128) + + surf = pygame.Surface(f.get_rect(notdef).size, pygame.SRCALPHA, 32) + f.render_to(surf, (0, 0), None) + self.assertEqual(surf.get_at((0, 0)), alpha128) + + self.assertRaises(AttributeError, setattr, f, "fgcolor", None) + + def test_freetype_Font_bgcolor(self): + f = ft.Font(None, 32) + zero = "0" # the default font 0 glyph does not have a pixel at (0, 0) + f.origin = False + f.pad = False + + transparent_black = pygame.Color(0, 0, 0, 0) # initial color + green = pygame.Color("green") + alpha128 = pygame.Color(10, 20, 30, 128) + + c = f.bgcolor + self.assertIsInstance(c, pygame.Color) + self.assertEqual(c, transparent_black) + + s, r = f.render(zero, pygame.Color(255, 255, 255)) + self.assertEqual(s.get_at((0, 0)), transparent_black) + + f.bgcolor = green + self.assertEqual(f.bgcolor, green) + + s, r = f.render(zero) + self.assertEqual(s.get_at((0, 0)), green) + + f.bgcolor = alpha128 + s, r = f.render(zero) + self.assertEqual(s.get_at((0, 0)), alpha128) + + surf = pygame.Surface(f.get_rect(zero).size, pygame.SRCALPHA, 32) + f.render_to(surf, (0, 0), None) + self.assertEqual(surf.get_at((0, 0)), alpha128) + + self.assertRaises(AttributeError, setattr, f, "bgcolor", None) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + @unittest.skipIf(IS_PYPY, "pypy no likey") + def test_newbuf(self): + from pygame.tests.test_utils import buftools + + Exporter = buftools.Exporter + font = self._TEST_FONTS["sans"] + srect = font.get_rect("Hi", size=12) + for format in [ + "b", + "B", + "h", + "H", + "i", + "I", + "l", + "L", + "q", + "Q", + "x", + "1x", + "2x", + "3x", + "4x", + "5x", + "6x", + "7x", + "8x", + "9x", + "h", + "=h", + "@h", + "!h", + "1h", + "=1h", + ]: + newbuf = Exporter(srect.size, format=format) + rrect = font.render_raw_to(newbuf, "Hi", size=12) + self.assertEqual(rrect, srect) + # Some unsupported formats + for format in ["f", "d", "2h", "?", "hh"]: + newbuf = Exporter(srect.size, format=format, itemsize=4) + self.assertRaises(ValueError, font.render_raw_to, newbuf, "Hi", size=12) + + def test_freetype_Font_style(self): + + font = self._TEST_FONTS["sans"] + + # make sure STYLE_NORMAL is the default value + self.assertEqual(ft.STYLE_NORMAL, font.style) + + # make sure we check for style type + with self.assertRaises(TypeError): + font.style = "None" + with self.assertRaises(TypeError): + font.style = None + + # make sure we only accept valid constants + with self.assertRaises(ValueError): + font.style = 112 + + # make assure no assignments happened + self.assertEqual(ft.STYLE_NORMAL, font.style) + + # test assignement + font.style = ft.STYLE_UNDERLINE + self.assertEqual(ft.STYLE_UNDERLINE, font.style) + + # test complex styles + st = ft.STYLE_STRONG | ft.STYLE_UNDERLINE | ft.STYLE_OBLIQUE + + font.style = st + self.assertEqual(st, font.style) + + # and that STYLE_DEFAULT has no effect (continued from above) + self.assertNotEqual(st, ft.STYLE_DEFAULT) + font.style = ft.STYLE_DEFAULT + self.assertEqual(st, font.style) + + # revert changes + font.style = ft.STYLE_NORMAL + self.assertEqual(ft.STYLE_NORMAL, font.style) + + def test_freetype_Font_resolution(self): + text = "|" # Differs in width and height + resolution = ft.get_default_resolution() + new_font = ft.Font(self._sans_path, resolution=2 * resolution) + self.assertEqual(new_font.resolution, 2 * resolution) + size_normal = self._TEST_FONTS["sans"].get_rect(text, size=24).size + size_scaled = new_font.get_rect(text, size=24).size + size_by_2 = size_normal[0] * 2 + self.assertTrue( + size_by_2 + 2 >= size_scaled[0] >= size_by_2 - 2, + "%i not equal %i" % (size_scaled[1], size_by_2), + ) + size_by_2 = size_normal[1] * 2 + self.assertTrue( + size_by_2 + 2 >= size_scaled[1] >= size_by_2 - 2, + "%i not equal %i" % (size_scaled[1], size_by_2), + ) + new_resolution = resolution + 10 + ft.set_default_resolution(new_resolution) + try: + new_font = ft.Font(self._sans_path, resolution=0) + self.assertEqual(new_font.resolution, new_resolution) + finally: + ft.set_default_resolution() + + def test_freetype_Font_path(self): + self.assertEqual(self._TEST_FONTS["sans"].path, self._sans_path) + self.assertRaises(AttributeError, getattr, nullfont(), "path") + + # This Font cache test is conditional on freetype being built by a debug + # version of Python or with the C macro PGFT_DEBUG_CACHE defined. + def test_freetype_Font_cache(self): + glyphs = "abcde" + glen = len(glyphs) + other_glyphs = "123" + oglen = len(other_glyphs) + uempty = str("") + ## many_glyphs = (uempty.join([chr(i) for i in range(32,127)] + + ## [chr(i) for i in range(161,172)] + + ## [chr(i) for i in range(174,239)])) + many_glyphs = uempty.join([chr(i) for i in range(32, 127)]) + mglen = len(many_glyphs) + + count = 0 + access = 0 + hit = 0 + miss = 0 + + f = ft.Font(None, size=24, font_index=0, resolution=72, ucs4=False) + f.style = ft.STYLE_NORMAL + f.antialiased = True + + # Ensure debug counters are zero + self.assertEqual(f._debug_cache_stats, (0, 0, 0, 0, 0)) + # Load some basic glyphs + count = access = miss = glen + f.render_raw(glyphs) + self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) + # Vertical should not affect the cache + access += glen + hit += glen + f.vertical = True + f.render_raw(glyphs) + f.vertical = False + self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) + # New glyphs will + count += oglen + access += oglen + miss += oglen + f.render_raw(other_glyphs) + self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) + # Point size does + count += glen + access += glen + miss += glen + f.render_raw(glyphs, size=12) + self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) + # Underline style does not + access += oglen + hit += oglen + f.underline = True + f.render_raw(other_glyphs) + f.underline = False + self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) + # Oblique style does + count += glen + access += glen + miss += glen + f.oblique = True + f.render_raw(glyphs) + f.oblique = False + self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) + # Strong style does; by this point cache clears can happen + count += glen + access += glen + miss += glen + f.strong = True + f.render_raw(glyphs) + f.strong = False + ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats + self.assertEqual( + (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) + ) + # Rotation does + count += glen + access += glen + miss += glen + f.render_raw(glyphs, rotation=10) + ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats + self.assertEqual( + (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) + ) + # aliased (mono) glyphs do + count += oglen + access += oglen + miss += oglen + f.antialiased = False + f.render_raw(other_glyphs) + f.antialiased = True + ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats + self.assertEqual( + (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) + ) + # Trigger a cleanup for sure. + count += 2 * mglen + access += 2 * mglen + miss += 2 * mglen + f.get_metrics(many_glyphs, size=8) + f.get_metrics(many_glyphs, size=10) + ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats + self.assertTrue(ccount < count) + self.assertEqual( + (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) + ) + + try: + ft.Font._debug_cache_stats + except AttributeError: + del test_freetype_Font_cache + + def test_undefined_character_code(self): + # To be consistent with pygame.font.Font, undefined codes + # are rendered as the undefined character, and has metrics + # of None. + font = self._TEST_FONTS["sans"] + + img, size1 = font.render(chr(1), (0, 0, 0), size=24) + img, size0 = font.render("", (0, 0, 0), size=24) + self.assertTrue(size1.width > size0.width) + + metrics = font.get_metrics(chr(1) + chr(48), size=24) + self.assertEqual(len(metrics), 2) + self.assertIsNone(metrics[0]) + self.assertIsInstance(metrics[1], tuple) + + def test_issue_242(self): + """Issue #242: get_rect() uses 0 as default style""" + + # Issue #242: freetype.Font.get_rect() ignores style defaults when + # the style argument is not given + # + # The text boundary rectangle returned by freetype.Font.get_rect() + # should match the boundary of the same text rendered directly to a + # surface. This permits accurate text positioning. To work properly, + # get_rect() should calculate the text boundary to reflect text style, + # such as underline. Instead, it ignores the style settings for the + # Font object when the style argument is omitted. + # + # When the style argument is not given, freetype.get_rect() uses + # unstyled text when calculating the boundary rectangle. This is + # because _ftfont_getrect(), in _freetype.c, set the default + # style to 0 rather than FT_STYLE_DEFAULT. + # + font = self._TEST_FONTS["sans"] + + # Try wide style on a wide character. + prev_style = font.wide + font.wide = True + try: + rect = font.get_rect("M", size=64) + surf, rrect = font.render(None, size=64) + self.assertEqual(rect, rrect) + finally: + font.wide = prev_style + + # Try strong style on several wide characters. + prev_style = font.strong + font.strong = True + try: + rect = font.get_rect("Mm_", size=64) + surf, rrect = font.render(None, size=64) + self.assertEqual(rect, rrect) + finally: + font.strong = prev_style + + # Try oblique style on a tall, narrow character. + prev_style = font.oblique + font.oblique = True + try: + rect = font.get_rect("|", size=64) + surf, rrect = font.render(None, size=64) + self.assertEqual(rect, rrect) + finally: + font.oblique = prev_style + + # Try underline style on a glyphless character. + prev_style = font.underline + font.underline = True + try: + rect = font.get_rect(" ", size=64) + surf, rrect = font.render(None, size=64) + self.assertEqual(rect, rrect) + finally: + font.underline = prev_style + + def test_issue_237(self): + """Issue #237: Memory overrun when rendered with underlining""" + + # Issue #237: Memory overrun when text without descenders is rendered + # with underlining + # + # The bug crashes the Python interpreter. The bug is caught with C + # assertions in ft_render_cb.c when the Pygame module is compiled + # for debugging. So far it is only known to affect Times New Roman. + # + name = "Times New Roman" + font = ft.SysFont(name, 19) + if font.name != name: + # The font is unavailable, so skip the test. + return + font.underline = True + s, r = font.render("Amazon", size=19) + + # Some other checks to make sure nothing else broke. + for adj in [-2, -1.9, -1, 0, 1.9, 2]: + font.underline_adjustment = adj + s, r = font.render("Amazon", size=19) + + def test_issue_243(self): + """Issue Y: trailing space ignored in boundary calculation""" + + # Issue #243: For a string with trailing spaces, freetype ignores the + # last space in boundary calculations + # + font = self._TEST_FONTS["fixed"] + r1 = font.get_rect(" ", size=64) + self.assertTrue(r1.width > 1) + r2 = font.get_rect(" ", size=64) + self.assertEqual(r2.width, 2 * r1.width) + + def test_garbage_collection(self): + """Check reference counting on returned new references""" + + def ref_items(seq): + return [weakref.ref(o) for o in seq] + + font = self._TEST_FONTS["bmp-8-75dpi"] + font.size = font.get_sizes()[0][0] + text = "A" + rect = font.get_rect(text) + surf = pygame.Surface(rect.size, pygame.SRCALPHA, 32) + refs = [] + refs.extend(ref_items(font.render(text, (0, 0, 0)))) + refs.append(weakref.ref(font.render_to(surf, (0, 0), text, (0, 0, 0)))) + refs.append(weakref.ref(font.get_rect(text))) + + n = len(refs) + self.assertTrue(n > 0) + + # for pypy we garbage collection twice. + for i in range(2): + gc.collect() + + for i in range(n): + self.assertIsNone(refs[i](), "ref %d not collected" % i) + + try: + from sys import getrefcount + except ImportError: + pass + else: + array = arrinter.Array(rect.size, "u", 1) + o = font.render_raw(text) + self.assertEqual(getrefcount(o), 2) + self.assertEqual(getrefcount(o[0]), 2) + self.assertEqual(getrefcount(o[1]), 2) + self.assertEqual(getrefcount(font.render_raw_to(array, text)), 1) + o = font.get_metrics("AB") + self.assertEqual(getrefcount(o), 2) + for i in range(len(o)): + self.assertEqual(getrefcount(o[i]), 2, "refcount fail for item %d" % i) + o = font.get_sizes() + self.assertEqual(getrefcount(o), 2) + for i in range(len(o)): + self.assertEqual(getrefcount(o[i]), 2, "refcount fail for item %d" % i) + + def test_display_surface_quit(self): + """Font.render_to() on a closed display surface""" + + # The Font.render_to() method checks that PySurfaceObject.surf is NULL + # and raise a exception if it is. This fixes a bug in Pygame revision + # 0600ea4f1cfb and earlier where Pygame segfaults instead. + null_surface = pygame.Surface.__new__(pygame.Surface) + f = self._TEST_FONTS["sans"] + self.assertRaises( + pygame.error, f.render_to, null_surface, (0, 0), "Crash!", size=12 + ) + + def test_issue_565(self): + """get_metrics supporting rotation/styles/size""" + + tests = [ + {"method": "size", "value": 36, "msg": "metrics same for size"}, + {"method": "rotation", "value": 90, "msg": "metrics same for rotation"}, + {"method": "oblique", "value": True, "msg": "metrics same for oblique"}, + ] + text = "|" + + def run_test(method, value, msg): + font = ft.Font(self._sans_path, size=24) + before = font.get_metrics(text) + font.__setattr__(method, value) + after = font.get_metrics(text) + self.assertNotEqual(before, after, msg) + + for test in tests: + run_test(test["method"], test["value"], test["msg"]) + + def test_freetype_SysFont_name(self): + """that SysFont accepts names of various types""" + fonts = pygame.font.get_fonts() + size = 12 + + # Check single name string: + font_name = ft.SysFont(fonts[0], size).name + self.assertFalse(font_name is None) + + # Check string of comma-separated names. + names = ",".join(fonts) + font_name_2 = ft.SysFont(names, size).name + self.assertEqual(font_name_2, font_name) + + # Check list of names. + font_name_2 = ft.SysFont(fonts, size).name + self.assertEqual(font_name_2, font_name) + + # Check generator: + names = (name for name in fonts) + font_name_2 = ft.SysFont(names, size).name + self.assertEqual(font_name_2, font_name) + + fonts_b = [f.encode() for f in fonts] + + # Check single name bytes. + font_name_2 = ft.SysFont(fonts_b[0], size).name + self.assertEqual(font_name_2, font_name) + + # Check comma-separated bytes. + names = b",".join(fonts_b) + font_name_2 = ft.SysFont(names, size).name + self.assertEqual(font_name_2, font_name) + + # Check list of bytes. + font_name_2 = ft.SysFont(fonts_b, size).name + self.assertEqual(font_name_2, font_name) + + # Check mixed list of bytes and string. + names = [fonts[0], fonts_b[1], fonts[2], fonts_b[3]] + font_name_2 = ft.SysFont(names, size).name + self.assertEqual(font_name_2, font_name) + + def test_pathlib(self): + f = ft.Font(pathlib.Path(self._fixed_path), 20) + + +class FreeTypeTest(unittest.TestCase): + def setUp(self): + ft.init() + + def tearDown(self): + ft.quit() + + def test_resolution(self): + try: + ft.set_default_resolution() + resolution = ft.get_default_resolution() + self.assertEqual(resolution, 72) + new_resolution = resolution + 10 + ft.set_default_resolution(new_resolution) + self.assertEqual(ft.get_default_resolution(), new_resolution) + ft.init(resolution=resolution + 20) + self.assertEqual(ft.get_default_resolution(), new_resolution) + finally: + ft.set_default_resolution() + + def test_autoinit_and_autoquit(self): + pygame.init() + self.assertTrue(ft.get_init()) + pygame.quit() + self.assertFalse(ft.get_init()) + + # Ensure autoquit is replaced at init time + pygame.init() + self.assertTrue(ft.get_init()) + pygame.quit() + self.assertFalse(ft.get_init()) + + def test_init(self): + # Test if module initialized after calling init(). + ft.quit() + ft.init() + + self.assertTrue(ft.get_init()) + + def test_init__multiple(self): + # Test if module initialized after multiple init() calls. + ft.init() + ft.init() + + self.assertTrue(ft.get_init()) + + def test_quit(self): + # Test if module uninitialized after calling quit(). + ft.quit() + + self.assertFalse(ft.get_init()) + + def test_quit__multiple(self): + # Test if module initialized after multiple quit() calls. + ft.quit() + ft.quit() + + self.assertFalse(ft.get_init()) + + def test_get_init(self): + # Test if get_init() gets the init state. + self.assertTrue(ft.get_init()) + + def test_cache_size(self): + DEFAULT_CACHE_SIZE = 64 + self.assertEqual(ft.get_cache_size(), DEFAULT_CACHE_SIZE) + ft.quit() + self.assertEqual(ft.get_cache_size(), 0) + new_cache_size = DEFAULT_CACHE_SIZE * 2 + ft.init(cache_size=new_cache_size) + self.assertEqual(ft.get_cache_size(), new_cache_size) + + def test_get_error(self): + """Ensures get_error() is initially empty (None).""" + error_msg = ft.get_error() + + self.assertIsNone(error_msg) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/ftfont_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/ftfont_tags.py new file mode 100644 index 0000000..0d538f4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/ftfont_tags.py @@ -0,0 +1,11 @@ +__tags__ = ["development"] + +exclude = False + +try: + import pygame.ftfont +except ImportError: + exclude = True + +if exclude: + __tags__.extend(["ignore", "subprocess_ignore"]) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/ftfont_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/ftfont_test.py new file mode 100644 index 0000000..1f71204 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/ftfont_test.py @@ -0,0 +1,19 @@ +import sys +import os +import unittest +from pygame.tests import font_test + +import pygame.ftfont + +font_test.pygame_font = pygame.ftfont +# Disable UCS-4 specific tests as this "Font" type does accept UCS-4 codes. +font_test.UCS_4 = False + +for name in dir(font_test): + obj = getattr(font_test, name) + if isinstance(obj, type) and issubclass(obj, unittest.TestCase): # conditional and + new_name = "Ft%s" % name + globals()[new_name] = type(new_name, (obj,), {}) + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/gfxdraw_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/gfxdraw_test.py new file mode 100644 index 0000000..293cef3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/gfxdraw_test.py @@ -0,0 +1,877 @@ +import unittest +import pygame +import pygame.gfxdraw +from pygame.locals import * +from pygame.tests.test_utils import SurfaceSubclass + + +def intensity(c, i): + """Return color c changed by intensity i + + For 0 <= i <= 127 the color is a shade, with 0 being black, 127 being the + unaltered color. + + For 128 <= i <= 255 the color is a tint, with 255 being white, 128 the + unaltered color. + + """ + r, g, b = c[0:3] + if 0 <= i <= 127: + # Darken + return ((r * i) // 127, (g * i) // 127, (b * i) // 127) + # Lighten + return ( + r + ((255 - r) * (255 - i)) // 127, + g + ((255 - g) * (255 - i)) // 127, + b + ((255 - b) * (255 - i)) // 127, + ) + + +class GfxdrawDefaultTest(unittest.TestCase): + + is_started = False + + foreground_color = (128, 64, 8) + background_color = (255, 255, 255) + + def make_palette(base_color): + """Return color palette that is various intensities of base_color""" + # Need this function for Python 3.x so the base_color + # is within the scope of the list comprehension. + return [intensity(base_color, i) for i in range(0, 256)] + + default_palette = make_palette(foreground_color) + + default_size = (100, 100) + + def check_at(self, surf, posn, color): + sc = surf.get_at(posn) + fail_msg = "%s != %s at %s, bitsize: %i, flags: %i, masks: %s" % ( + sc, + color, + posn, + surf.get_bitsize(), + surf.get_flags(), + surf.get_masks(), + ) + self.assertEqual(sc, color, fail_msg) + + def check_not_at(self, surf, posn, color): + sc = surf.get_at(posn) + fail_msg = "%s != %s at %s, bitsize: %i, flags: %i, masks: %s" % ( + sc, + color, + posn, + surf.get_bitsize(), + surf.get_flags(), + surf.get_masks(), + ) + self.assertNotEqual(sc, color, fail_msg) + + @classmethod + def setUpClass(cls): + # Necessary for Surface.set_palette. + pygame.init() + pygame.display.set_mode((1, 1)) + + @classmethod + def tearDownClass(cls): + pygame.quit() + + def setUp(self): + # This makes sure pygame is always initialized before each test (in + # case a test calls pygame.quit()). + if not pygame.get_init(): + pygame.init() + + Surface = pygame.Surface + size = self.default_size + palette = self.default_palette + if not self.is_started: + # Create test surfaces + self.surfaces = [ + Surface(size, 0, 8), + Surface(size, SRCALPHA, 16), + Surface(size, SRCALPHA, 32), + ] + self.surfaces[0].set_palette(palette) + nonpalette_fmts = ( + # (8, (0xe0, 0x1c, 0x3, 0x0)), + (12, (0xF00, 0xF0, 0xF, 0x0)), + (15, (0x7C00, 0x3E0, 0x1F, 0x0)), + (15, (0x1F, 0x3E0, 0x7C00, 0x0)), + (16, (0xF00, 0xF0, 0xF, 0xF000)), + (16, (0xF000, 0xF00, 0xF0, 0xF)), + (16, (0xF, 0xF0, 0xF00, 0xF000)), + (16, (0xF0, 0xF00, 0xF000, 0xF)), + (16, (0x7C00, 0x3E0, 0x1F, 0x8000)), + (16, (0xF800, 0x7C0, 0x3E, 0x1)), + (16, (0x1F, 0x3E0, 0x7C00, 0x8000)), + (16, (0x3E, 0x7C0, 0xF800, 0x1)), + (16, (0xF800, 0x7E0, 0x1F, 0x0)), + (16, (0x1F, 0x7E0, 0xF800, 0x0)), + (24, (0xFF, 0xFF00, 0xFF0000, 0x0)), + (24, (0xFF0000, 0xFF00, 0xFF, 0x0)), + (32, (0xFF0000, 0xFF00, 0xFF, 0x0)), + (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)), + (32, (0xFF, 0xFF00, 0xFF0000, 0x0)), + (32, (0xFF00, 0xFF0000, 0xFF000000, 0x0)), + (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)), + (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)), + (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)), + (32, (0xFF00, 0xFF0000, 0xFF000000, 0xFF)), + ) + for bitsize, masks in nonpalette_fmts: + self.surfaces.append(Surface(size, 0, bitsize, masks)) + for surf in self.surfaces: + surf.fill(self.background_color) + + def test_gfxdraw__subclassed_surface(self): + """Ensure pygame.gfxdraw works on subclassed surfaces.""" + surface = SurfaceSubclass((11, 13), SRCALPHA, 32) + surface.fill(pygame.Color("blue")) + expected_color = pygame.Color("red") + x, y = 1, 2 + + pygame.gfxdraw.pixel(surface, x, y, expected_color) + + self.assertEqual(surface.get_at((x, y)), expected_color) + + def test_pixel(self): + """pixel(surface, x, y, color): return None""" + fg = self.foreground_color + bg = self.background_color + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.pixel(surf, 2, 2, fg) + for x in range(1, 4): + for y in range(1, 4): + if x == 2 and y == 2: + self.check_at(surf, (x, y), fg_adjusted) + else: + self.check_at(surf, (x, y), bg_adjusted) + + def test_hline(self): + """hline(surface, x1, x2, y, color): return None""" + fg = self.foreground_color + bg = self.background_color + startx = 10 + stopx = 80 + y = 50 + fg_test_points = [(startx, y), (stopx, y), ((stopx - startx) // 2, y)] + bg_test_points = [ + (startx - 1, y), + (stopx + 1, y), + (startx, y - 1), + (startx, y + 1), + (stopx, y - 1), + (stopx, y + 1), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.hline(surf, startx, stopx, y, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_vline(self): + """vline(surface, x, y1, y2, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 50 + starty = 10 + stopy = 80 + fg_test_points = [(x, starty), (x, stopy), (x, (stopy - starty) // 2)] + bg_test_points = [ + (x, starty - 1), + (x, stopy + 1), + (x - 1, starty), + (x + 1, starty), + (x - 1, stopy), + (x + 1, stopy), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.vline(surf, x, starty, stopy, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_rectangle(self): + """rectangle(surface, rect, color): return None""" + fg = self.foreground_color + bg = self.background_color + rect = pygame.Rect(10, 15, 55, 62) + rect_tuple = tuple(rect) + fg_test_points = [ + rect.topleft, + (rect.right - 1, rect.top), + (rect.left, rect.bottom - 1), + (rect.right - 1, rect.bottom - 1), + ] + bg_test_points = [ + (rect.left - 1, rect.top - 1), + (rect.left + 1, rect.top + 1), + (rect.right, rect.top - 1), + (rect.right - 2, rect.top + 1), + (rect.left - 1, rect.bottom), + (rect.left + 1, rect.bottom - 2), + (rect.right, rect.bottom), + (rect.right - 2, rect.bottom - 2), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.rectangle(surf, rect, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + surf.fill(bg) + pygame.gfxdraw.rectangle(surf, rect_tuple, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_box(self): + """box(surface, rect, color): return None""" + fg = self.foreground_color + bg = self.background_color + rect = pygame.Rect(10, 15, 55, 62) + rect_tuple = tuple(rect) + fg_test_points = [ + rect.topleft, + (rect.left + 1, rect.top + 1), + (rect.right - 1, rect.top), + (rect.right - 2, rect.top + 1), + (rect.left, rect.bottom - 1), + (rect.left + 1, rect.bottom - 2), + (rect.right - 1, rect.bottom - 1), + (rect.right - 2, rect.bottom - 2), + ] + bg_test_points = [ + (rect.left - 1, rect.top - 1), + (rect.right, rect.top - 1), + (rect.left - 1, rect.bottom), + (rect.right, rect.bottom), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.box(surf, rect, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + surf.fill(bg) + pygame.gfxdraw.box(surf, rect_tuple, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_line(self): + """line(surface, x1, y1, x2, y2, color): return None""" + fg = self.foreground_color + bg = self.background_color + x1 = 10 + y1 = 15 + x2 = 92 + y2 = 77 + fg_test_points = [(x1, y1), (x2, y2)] + bg_test_points = [ + (x1 - 1, y1), + (x1, y1 - 1), + (x1 - 1, y1 - 1), + (x2 + 1, y2), + (x2, y2 + 1), + (x2 + 1, y2 + 1), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.line(surf, x1, y1, x2, y2, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_circle(self): + """circle(surface, x, y, r, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + r = 30 + fg_test_points = [(x, y - r), (x, y + r), (x - r, y), (x + r, y)] + bg_test_points = [ + (x, y), + (x, y - r + 1), + (x, y - r - 1), + (x, y + r + 1), + (x, y + r - 1), + (x - r - 1, y), + (x - r + 1, y), + (x + r + 1, y), + (x + r - 1, y), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.circle(surf, x, y, r, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_arc(self): + """arc(surface, x, y, r, start, end, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + r = 30 + start = 0 # +x direction, but not (x + r, y) (?) + end = 90 # -y direction, including (x, y + r) + fg_test_points = [(x, y + r), (x + r, y + 1)] + bg_test_points = [ + (x, y), + (x, y - r), + (x - r, y), + (x, y + r + 1), + (x, y + r - 1), + (x - 1, y + r), + (x + r + 1, y), + (x + r - 1, y), + (x + r, y), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.arc(surf, x, y, r, start, end, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_aacircle(self): + """aacircle(surface, x, y, r, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + r = 30 + fg_test_points = [(x, y - r), (x, y + r), (x - r, y), (x + r, y)] + bg_test_points = [ + (x, y), + (x, y - r + 1), + (x, y - r - 1), + (x, y + r + 1), + (x, y + r - 1), + (x - r - 1, y), + (x - r + 1, y), + (x + r + 1, y), + (x + r - 1, y), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.aacircle(surf, x, y, r, fg) + for posn in fg_test_points: + self.check_not_at(surf, posn, bg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_filled_circle(self): + """filled_circle(surface, x, y, r, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + r = 30 + fg_test_points = [ + (x, y - r), + (x, y - r + 1), + (x, y + r), + (x, y + r - 1), + (x - r, y), + (x - r + 1, y), + (x + r, y), + (x + r - 1, y), + (x, y), + ] + bg_test_points = [ + (x, y - r - 1), + (x, y + r + 1), + (x - r - 1, y), + (x + r + 1, y), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.filled_circle(surf, x, y, r, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_ellipse(self): + """ellipse(surface, x, y, rx, ry, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + rx = 30 + ry = 35 + fg_test_points = [(x, y - ry), (x, y + ry), (x - rx, y), (x + rx, y)] + bg_test_points = [ + (x, y), + (x, y - ry + 1), + (x, y - ry - 1), + (x, y + ry + 1), + (x, y + ry - 1), + (x - rx - 1, y), + (x - rx + 1, y), + (x + rx + 1, y), + (x + rx - 1, y), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.ellipse(surf, x, y, rx, ry, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_aaellipse(self): + """aaellipse(surface, x, y, rx, ry, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + rx = 30 + ry = 35 + fg_test_points = [(x, y - ry), (x, y + ry), (x - rx, y), (x + rx, y)] + bg_test_points = [ + (x, y), + (x, y - ry + 1), + (x, y - ry - 1), + (x, y + ry + 1), + (x, y + ry - 1), + (x - rx - 1, y), + (x - rx + 1, y), + (x + rx + 1, y), + (x + rx - 1, y), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.aaellipse(surf, x, y, rx, ry, fg) + for posn in fg_test_points: + self.check_not_at(surf, posn, bg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_filled_ellipse(self): + """filled_ellipse(surface, x, y, rx, ry, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + rx = 30 + ry = 35 + fg_test_points = [ + (x, y - ry), + (x, y - ry + 1), + (x, y + ry), + (x, y + ry - 1), + (x - rx, y), + (x - rx + 1, y), + (x + rx, y), + (x + rx - 1, y), + (x, y), + ] + bg_test_points = [ + (x, y - ry - 1), + (x, y + ry + 1), + (x - rx - 1, y), + (x + rx + 1, y), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.filled_ellipse(surf, x, y, rx, ry, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_pie(self): + """pie(surface, x, y, r, start, end, color): return None""" + fg = self.foreground_color + bg = self.background_color + x = 45 + y = 40 + r = 30 + start = 0 # +x direction, including (x + r, y) + end = 90 # -y direction, but not (x, y + r) (?) + fg_test_points = [(x, y), (x + 1, y), (x, y + 1), (x + r, y)] + bg_test_points = [ + (x - 1, y), + (x, y - 1), + (x - 1, y - 1), + (x + 1, y + 1), + (x + r + 1, y), + (x + r, y - 1), + (x, y + r + 1), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.pie(surf, x, y, r, start, end, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_trigon(self): + """trigon(surface, x1, y1, x2, y2, x3, y3, color): return None""" + fg = self.foreground_color + bg = self.background_color + x1 = 10 + y1 = 15 + x2 = 92 + y2 = 77 + x3 = 20 + y3 = 60 + fg_test_points = [(x1, y1), (x2, y2), (x3, y3)] + bg_test_points = [ + (x1 - 1, y1 - 1), + (x2 + 1, y2 + 1), + (x3 - 1, y3 + 1), + (x1 + 10, y1 + 30), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.trigon(surf, x1, y1, x2, y2, x3, y3, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_aatrigon(self): + """aatrigon(surface, x1, y1, x2, y2, x3, y3, color): return None""" + fg = self.foreground_color + bg = self.background_color + x1 = 10 + y1 = 15 + x2 = 92 + y2 = 77 + x3 = 20 + y3 = 60 + fg_test_points = [(x1, y1), (x2, y2), (x3, y3)] + bg_test_points = [ + (x1 - 1, y1 - 1), + (x2 + 1, y2 + 1), + (x3 - 1, y3 + 1), + (x1 + 10, y1 + 30), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.aatrigon(surf, x1, y1, x2, y2, x3, y3, fg) + for posn in fg_test_points: + self.check_not_at(surf, posn, bg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_aatrigon__with_horizontal_edge(self): + """Ensure aatrigon draws horizontal edges correctly. + + This test creates 2 surfaces and draws an aatrigon on each. The pixels + on each surface are compared to ensure they are the same. The only + difference between the 2 aatrigons is the order the points are drawn. + The order of the points should have no impact on the final drawing. + + Related to issue #622. + """ + bg_color = pygame.Color("white") + line_color = pygame.Color("black") + width, height = 11, 10 + expected_surface = pygame.Surface((width, height), 0, 32) + expected_surface.fill(bg_color) + surface = pygame.Surface((width, height), 0, 32) + surface.fill(bg_color) + + x1, y1 = width - 1, 0 + x2, y2 = (width - 1) // 2, height - 1 + x3, y3 = 0, 0 + + # The points in this order draw as expected. + pygame.gfxdraw.aatrigon(expected_surface, x1, y1, x2, y2, x3, y3, line_color) + + # The points in reverse order fail to draw the horizontal edge along + # the top. + pygame.gfxdraw.aatrigon(surface, x3, y3, x2, y2, x1, y1, line_color) + + # The surfaces are locked for a possible speed up of pixel access. + expected_surface.lock() + surface.lock() + for x in range(width): + for y in range(height): + self.assertEqual( + expected_surface.get_at((x, y)), + surface.get_at((x, y)), + "pos=({}, {})".format(x, y), + ) + + surface.unlock() + expected_surface.unlock() + + def test_filled_trigon(self): + """filled_trigon(surface, x1, y1, x2, y2, x3, y3, color): return None""" + fg = self.foreground_color + bg = self.background_color + x1 = 10 + y1 = 15 + x2 = 92 + y2 = 77 + x3 = 20 + y3 = 60 + fg_test_points = [(x1, y1), (x2, y2), (x3, y3), (x1 + 10, y1 + 30)] + bg_test_points = [(x1 - 1, y1 - 1), (x2 + 1, y2 + 1), (x3 - 1, y3 + 1)] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.filled_trigon(surf, x1, y1, x2, y2, x3, y3, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_polygon(self): + """polygon(surface, points, color): return None""" + fg = self.foreground_color + bg = self.background_color + points = [(10, 80), (10, 15), (92, 25), (92, 80)] + fg_test_points = points + [ + (points[0][0], points[0][1] - 1), + (points[0][0] + 1, points[0][1]), + (points[3][0] - 1, points[3][1]), + (points[3][0], points[3][1] - 1), + (points[2][0], points[2][1] + 1), + ] + bg_test_points = [ + (points[0][0] - 1, points[0][1]), + (points[0][0], points[0][1] + 1), + (points[0][0] - 1, points[0][1] + 1), + (points[0][0] + 1, points[0][1] - 1), + (points[3][0] + 1, points[3][1]), + (points[3][0], points[3][1] + 1), + (points[3][0] + 1, points[3][1] + 1), + (points[3][0] - 1, points[3][1] - 1), + (points[2][0] + 1, points[2][1]), + (points[2][0] - 1, points[2][1] + 1), + (points[1][0] - 1, points[1][1]), + (points[1][0], points[1][1] - 1), + (points[1][0] - 1, points[1][1] - 1), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.polygon(surf, points, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_aapolygon(self): + """aapolygon(surface, points, color): return None""" + fg = self.foreground_color + bg = self.background_color + points = [(10, 80), (10, 15), (92, 25), (92, 80)] + fg_test_points = points + bg_test_points = [ + (points[0][0] - 1, points[0][1]), + (points[0][0], points[0][1] + 1), + (points[0][0] - 1, points[0][1] + 1), + (points[0][0] + 1, points[0][1] - 1), + (points[3][0] + 1, points[3][1]), + (points[3][0], points[3][1] + 1), + (points[3][0] + 1, points[3][1] + 1), + (points[3][0] - 1, points[3][1] - 1), + (points[2][0] + 1, points[2][1]), + (points[2][0] - 1, points[2][1] + 1), + (points[1][0] - 1, points[1][1]), + (points[1][0], points[1][1] - 1), + (points[1][0] - 1, points[1][1] - 1), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.aapolygon(surf, points, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_not_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_aapolygon__with_horizontal_edge(self): + """Ensure aapolygon draws horizontal edges correctly. + + This test creates 2 surfaces and draws a polygon on each. The pixels + on each surface are compared to ensure they are the same. The only + difference between the 2 polygons is that one is drawn using + aapolygon() and the other using multiple line() calls. They should + produce the same final drawing. + + Related to issue #622. + """ + bg_color = pygame.Color("white") + line_color = pygame.Color("black") + width, height = 11, 10 + expected_surface = pygame.Surface((width, height), 0, 32) + expected_surface.fill(bg_color) + surface = pygame.Surface((width, height), 0, 32) + surface.fill(bg_color) + + points = ((0, 0), (0, height - 1), (width - 1, height - 1), (width - 1, 0)) + + # The points are used to draw the expected aapolygon using the line() + # function. + for (x1, y1), (x2, y2) in zip(points, points[1:] + points[:1]): + pygame.gfxdraw.line(expected_surface, x1, y1, x2, y2, line_color) + + # The points in this order fail to draw the horizontal edge along + # the top. + pygame.gfxdraw.aapolygon(surface, points, line_color) + + # The surfaces are locked for a possible speed up of pixel access. + expected_surface.lock() + surface.lock() + for x in range(width): + for y in range(height): + self.assertEqual( + expected_surface.get_at((x, y)), + surface.get_at((x, y)), + "pos=({}, {})".format(x, y), + ) + + surface.unlock() + expected_surface.unlock() + + def test_filled_polygon(self): + """filled_polygon(surface, points, color): return None""" + fg = self.foreground_color + bg = self.background_color + points = [(10, 80), (10, 15), (92, 25), (92, 80)] + fg_test_points = points + [ + (points[0][0], points[0][1] - 1), + (points[0][0] + 1, points[0][1]), + (points[0][0] + 1, points[0][1] - 1), + (points[3][0] - 1, points[3][1]), + (points[3][0], points[3][1] - 1), + (points[3][0] - 1, points[3][1] - 1), + (points[2][0], points[2][1] + 1), + (points[2][0] - 1, points[2][1] + 1), + ] + bg_test_points = [ + (points[0][0] - 1, points[0][1]), + (points[0][0], points[0][1] + 1), + (points[0][0] - 1, points[0][1] + 1), + (points[3][0] + 1, points[3][1]), + (points[3][0], points[3][1] + 1), + (points[3][0] + 1, points[3][1] + 1), + (points[2][0] + 1, points[2][1]), + (points[1][0] - 1, points[1][1]), + (points[1][0], points[1][1] - 1), + (points[1][0] - 1, points[1][1] - 1), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.filled_polygon(surf, points, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + def test_textured_polygon(self): + """textured_polygon(surface, points, texture, tx, ty): return None""" + w, h = self.default_size + fg = self.foreground_color + bg = self.background_color + tx = 0 + ty = 0 + texture = pygame.Surface((w + tx, h + ty), 0, 24) + texture.fill(fg, (0, 0, w, h)) + points = [(10, 80), (10, 15), (92, 25), (92, 80)] + # Don't know how to really check this as boarder points may + # or may not be included in the textured polygon. + fg_test_points = [(points[1][0] + 30, points[1][1] + 40)] + bg_test_points = [ + (points[0][0] - 1, points[0][1]), + (points[0][0], points[0][1] + 1), + (points[0][0] - 1, points[0][1] + 1), + (points[3][0] + 1, points[3][1]), + (points[3][0], points[3][1] + 1), + (points[3][0] + 1, points[3][1] + 1), + (points[2][0] + 1, points[2][1]), + (points[1][0] - 1, points[1][1]), + (points[1][0], points[1][1] - 1), + (points[1][0] - 1, points[1][1] - 1), + ] + for surf in self.surfaces[1:]: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.textured_polygon(surf, points, texture, -tx, -ty) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + # Alpha blit to 8 bits-per-pixel surface forbidden. + texture = pygame.Surface(self.default_size, SRCALPHA, 32) + self.assertRaises( + ValueError, + pygame.gfxdraw.textured_polygon, + self.surfaces[0], + points, + texture, + 0, + 0, + ) + + def test_bezier(self): + """bezier(surface, points, steps, color): return None""" + fg = self.foreground_color + bg = self.background_color + points = [(10, 50), (25, 15), (60, 80), (92, 30)] + fg_test_points = [points[0], points[3]] + bg_test_points = [ + (points[0][0] - 1, points[0][1]), + (points[3][0] + 1, points[3][1]), + (points[1][0], points[1][1] + 3), + (points[2][0], points[2][1] - 3), + ] + for surf in self.surfaces: + fg_adjusted = surf.unmap_rgb(surf.map_rgb(fg)) + bg_adjusted = surf.unmap_rgb(surf.map_rgb(bg)) + pygame.gfxdraw.bezier(surf, points, 30, fg) + for posn in fg_test_points: + self.check_at(surf, posn, fg_adjusted) + for posn in bg_test_points: + self.check_at(surf, posn, bg_adjusted) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/image__save_gl_surface_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/image__save_gl_surface_test.py new file mode 100644 index 0000000..2932f42 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/image__save_gl_surface_test.py @@ -0,0 +1,46 @@ +import os +import unittest + +from pygame.tests import test_utils +import pygame +from pygame.locals import * + + +@unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == "dummy", + 'OpenGL requires a non-"dummy" SDL_VIDEODRIVER', +) +class GL_ImageSave(unittest.TestCase): + def test_image_save_works_with_opengl_surfaces(self): + """ + |tags:display,slow,opengl| + """ + + pygame.display.init() + screen = pygame.display.set_mode((640, 480), OPENGL | DOUBLEBUF) + pygame.display.flip() + + tmp_dir = test_utils.get_tmp_dir() + # Try the imageext module. + tmp_file = os.path.join(tmp_dir, "opengl_save_surface_test.png") + pygame.image.save(screen, tmp_file) + + self.assertTrue(os.path.exists(tmp_file)) + + os.remove(tmp_file) + + # Only test the image module. + tmp_file = os.path.join(tmp_dir, "opengl_save_surface_test.bmp") + pygame.image.save(screen, tmp_file) + + self.assertTrue(os.path.exists(tmp_file)) + + os.remove(tmp_file) + + # stops tonnes of tmp dirs building up in trunk dir + os.rmdir(tmp_dir) + pygame.display.quit() + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/image_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/image_tags.py new file mode 100644 index 0000000..d847903 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/image_tags.py @@ -0,0 +1,7 @@ +__tags__ = [] + +import pygame +import sys + +if "pygame.image" not in sys.modules: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/image_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/image_test.py new file mode 100644 index 0000000..a3522fb --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/image_test.py @@ -0,0 +1,1115 @@ +# -*- coding: utf-8 -*- + +import array +import binascii +import io +import os +import tempfile +import unittest +import glob +import pathlib + +from pygame.tests.test_utils import example_path, png, tostring +import pygame, pygame.image, pygame.pkgdata + + +def test_magic(f, magic_hexes): + """Tests a given file to see if the magic hex matches.""" + data = f.read(len(magic_hexes)) + if len(data) != len(magic_hexes): + return 0 + for i, magic_hex in enumerate(magic_hexes): + if magic_hex != data[i]: + return 0 + return 1 + + +class ImageModuleTest(unittest.TestCase): + def testLoadIcon(self): + """see if we can load the pygame icon.""" + f = pygame.pkgdata.getResource("pygame_icon.bmp") + self.assertEqual(f.mode, "rb") + + surf = pygame.image.load_basic(f) + + self.assertEqual(surf.get_at((0, 0)), (5, 4, 5, 255)) + self.assertEqual(surf.get_height(), 32) + self.assertEqual(surf.get_width(), 32) + + def testLoadPNG(self): + """see if we can load a png with color values in the proper channels.""" + # Create a PNG file with known colors + reddish_pixel = (210, 0, 0, 255) + greenish_pixel = (0, 220, 0, 255) + bluish_pixel = (0, 0, 230, 255) + greyish_pixel = (110, 120, 130, 140) + pixel_array = [reddish_pixel + greenish_pixel, bluish_pixel + greyish_pixel] + + f_descriptor, f_path = tempfile.mkstemp(suffix=".png") + + with os.fdopen(f_descriptor, "wb") as f: + w = png.Writer(2, 2, alpha=True) + w.write(f, pixel_array) + + # Read the PNG file and verify that pygame interprets it correctly + surf = pygame.image.load(f_path) + + self.assertEqual(surf.get_at((0, 0)), reddish_pixel) + self.assertEqual(surf.get_at((1, 0)), greenish_pixel) + self.assertEqual(surf.get_at((0, 1)), bluish_pixel) + self.assertEqual(surf.get_at((1, 1)), greyish_pixel) + + # Read the PNG file obj. and verify that pygame interprets it correctly + with open(f_path, "rb") as f: + surf = pygame.image.load(f) + + self.assertEqual(surf.get_at((0, 0)), reddish_pixel) + self.assertEqual(surf.get_at((1, 0)), greenish_pixel) + self.assertEqual(surf.get_at((0, 1)), bluish_pixel) + self.assertEqual(surf.get_at((1, 1)), greyish_pixel) + + os.remove(f_path) + + def testLoadJPG(self): + """to see if we can load a jpg.""" + f = example_path("data/alien1.jpg") + surf = pygame.image.load(f) + + with open(f, "rb") as f: + surf = pygame.image.load(f) + + def testLoadBytesIO(self): + """to see if we can load images with BytesIO.""" + files = [ + "data/alien1.png", + "data/alien1.jpg", + "data/alien1.gif", + "data/asprite.bmp", + ] + + for fname in files: + with self.subTest(fname=fname): + with open(example_path(fname), "rb") as f: + img_bytes = f.read() + img_file = io.BytesIO(img_bytes) + image = pygame.image.load(img_file) + + def testSaveJPG(self): + """JPG equivalent to issue #211 - color channel swapping + + Make sure the SDL surface color masks represent the rgb memory format + required by the JPG library. The masks are machine endian dependent + """ + + from pygame import Color, Rect + + # The source image is a 2 by 2 square of four colors. Since JPEG is + # lossy, there can be color bleed. Make each color square 16 by 16, + # to avoid the significantly color value distorts found at color + # boundaries due to the compression value set by Pygame. + square_len = 16 + sz = 2 * square_len, 2 * square_len + + # +---------------------------------+ + # | red | green | + # |----------------+----------------| + # | blue | (255, 128, 64) | + # +---------------------------------+ + # + # as (rect, color) pairs. + def as_rect(square_x, square_y): + return Rect( + square_x * square_len, square_y * square_len, square_len, square_len + ) + + squares = [ + (as_rect(0, 0), Color("red")), + (as_rect(1, 0), Color("green")), + (as_rect(0, 1), Color("blue")), + (as_rect(1, 1), Color(255, 128, 64)), + ] + + # A surface format which is not directly usable with libjpeg. + surf = pygame.Surface(sz, 0, 32) + for rect, color in squares: + surf.fill(color, rect) + + # Assume pygame.image.Load works correctly as it is handled by the + # third party SDL_image library. + f_path = tempfile.mktemp(suffix=".jpg") + pygame.image.save(surf, f_path) + jpg_surf = pygame.image.load(f_path) + + # Allow for small differences in the restored colors. + def approx(c): + mask = 0xFC + return pygame.Color(c.r & mask, c.g & mask, c.b & mask) + + offset = square_len // 2 + for rect, color in squares: + posn = rect.move((offset, offset)).topleft + self.assertEqual(approx(jpg_surf.get_at(posn)), approx(color)) + + os.remove(f_path) + + def testSavePNG32(self): + """see if we can save a png with color values in the proper channels.""" + # Create a PNG file with known colors + reddish_pixel = (215, 0, 0, 255) + greenish_pixel = (0, 225, 0, 255) + bluish_pixel = (0, 0, 235, 255) + greyish_pixel = (115, 125, 135, 145) + + surf = pygame.Surface((1, 4), pygame.SRCALPHA, 32) + surf.set_at((0, 0), reddish_pixel) + surf.set_at((0, 1), greenish_pixel) + surf.set_at((0, 2), bluish_pixel) + surf.set_at((0, 3), greyish_pixel) + + f_path = tempfile.mktemp(suffix=".png") + pygame.image.save(surf, f_path) + + try: + # Read the PNG file and verify that pygame saved it correctly + reader = png.Reader(filename=f_path) + width, height, pixels, metadata = reader.asRGBA8() + + # pixels is a generator + self.assertEqual(tuple(next(pixels)), reddish_pixel) + self.assertEqual(tuple(next(pixels)), greenish_pixel) + self.assertEqual(tuple(next(pixels)), bluish_pixel) + self.assertEqual(tuple(next(pixels)), greyish_pixel) + + finally: + # Ensures proper clean up. + if not reader.file.closed: + reader.file.close() + del reader + os.remove(f_path) + + def testSavePNG24(self): + """see if we can save a png with color values in the proper channels.""" + # Create a PNG file with known colors + reddish_pixel = (215, 0, 0) + greenish_pixel = (0, 225, 0) + bluish_pixel = (0, 0, 235) + greyish_pixel = (115, 125, 135) + + surf = pygame.Surface((1, 4), 0, 24) + surf.set_at((0, 0), reddish_pixel) + surf.set_at((0, 1), greenish_pixel) + surf.set_at((0, 2), bluish_pixel) + surf.set_at((0, 3), greyish_pixel) + + f_path = tempfile.mktemp(suffix=".png") + pygame.image.save(surf, f_path) + + try: + # Read the PNG file and verify that pygame saved it correctly + reader = png.Reader(filename=f_path) + width, height, pixels, metadata = reader.asRGB8() + + # pixels is a generator + self.assertEqual(tuple(next(pixels)), reddish_pixel) + self.assertEqual(tuple(next(pixels)), greenish_pixel) + self.assertEqual(tuple(next(pixels)), bluish_pixel) + self.assertEqual(tuple(next(pixels)), greyish_pixel) + + finally: + # Ensures proper clean up. + if not reader.file.closed: + reader.file.close() + del reader + os.remove(f_path) + + def test_save(self): + + s = pygame.Surface((10, 10)) + s.fill((23, 23, 23)) + magic_hex = {} + magic_hex["jpg"] = [0xFF, 0xD8, 0xFF, 0xE0] + magic_hex["png"] = [0x89, 0x50, 0x4E, 0x47] + # magic_hex['tga'] = [0x0, 0x0, 0xa] + magic_hex["bmp"] = [0x42, 0x4D] + + formats = ["jpg", "png", "bmp"] + # uppercase too... JPG + formats = formats + [x.upper() for x in formats] + + for fmt in formats: + try: + temp_filename = "%s.%s" % ("tmpimg", fmt) + pygame.image.save(s, temp_filename) + + # Using 'with' ensures the file is closed even if test fails. + with open(temp_filename, "rb") as handle: + # Test the magic numbers at the start of the file to ensure + # they are saved as the correct file type. + self.assertEqual( + (1, fmt), (test_magic(handle, magic_hex[fmt.lower()]), fmt) + ) + + # load the file to make sure it was saved correctly. + # Note load can load a jpg saved with a .png file name. + s2 = pygame.image.load(temp_filename) + # compare contents, might only work reliably for png... + # but because it's all one color it seems to work with jpg. + self.assertEqual(s2.get_at((0, 0)), s.get_at((0, 0))) + finally: + # clean up the temp file, comment out to leave tmp file after run. + os.remove(temp_filename) + + def test_save_to_fileobject(self): + s = pygame.Surface((1, 1)) + s.fill((23, 23, 23)) + bytes_stream = io.BytesIO() + + pygame.image.save(s, bytes_stream) + bytes_stream.seek(0) + s2 = pygame.image.load(bytes_stream, "tga") + self.assertEqual(s.get_at((0, 0)), s2.get_at((0, 0))) + + def test_save_tga(self): + s = pygame.Surface((1, 1)) + s.fill((23, 23, 23)) + with tempfile.NamedTemporaryFile(suffix=".tga", delete=False) as f: + temp_filename = f.name + + try: + pygame.image.save(s, temp_filename) + s2 = pygame.image.load(temp_filename) + self.assertEqual(s2.get_at((0, 0)), s.get_at((0, 0))) + finally: + # clean up the temp file, even if test fails + os.remove(temp_filename) + + def test_save_pathlib(self): + surf = pygame.Surface((1, 1)) + surf.fill((23, 23, 23)) + with tempfile.NamedTemporaryFile(suffix=".tga", delete=False) as f: + temp_filename = f.name + + path = pathlib.Path(temp_filename) + try: + pygame.image.save(surf, path) + s2 = pygame.image.load(path) + self.assertEqual(s2.get_at((0, 0)), surf.get_at((0, 0))) + finally: + os.remove(temp_filename) + + def test_save__to_fileobject_w_namehint_argument(self): + s = pygame.Surface((10, 10)) + s.fill((23, 23, 23)) + magic_hex = {} + magic_hex["jpg"] = [0xFF, 0xD8, 0xFF, 0xE0] + magic_hex["png"] = [0x89, 0x50, 0x4E, 0x47] + magic_hex["bmp"] = [0x42, 0x4D] + + formats = ["tga", "jpg", "bmp", "png"] + # uppercase too... JPG + formats = formats + [x.upper() for x in formats] + + SDL_Im_version = pygame.image.get_sdl_image_version() + # We assume here that minor version and patch level of SDL_Image + # never goes above 99 + isAtLeastSDL_image_2_0_2 = (SDL_Im_version is not None) and ( + SDL_Im_version[0] * 10000 + SDL_Im_version[1] * 100 + SDL_Im_version[2] + ) >= 20002 + for fmt in formats: + tmp_file, tmp_filename = tempfile.mkstemp(suffix=".%s" % fmt) + if not isAtLeastSDL_image_2_0_2 and fmt.lower() == "jpg": + with os.fdopen(tmp_file, "wb") as handle: + with self.assertRaises(pygame.error): + pygame.image.save(s, handle, tmp_filename) + else: + with os.fdopen(tmp_file, "r+b") as handle: + pygame.image.save(s, handle, tmp_filename) + + if fmt.lower() in magic_hex: + # Test the magic numbers at the start of the file to + # ensure they are saved as the correct file type. + handle.seek(0) + self.assertEqual( + (1, fmt), (test_magic(handle, magic_hex[fmt.lower()]), fmt) + ) + # load the file to make sure it was saved correctly. + handle.flush() + handle.seek(0) + s2 = pygame.image.load(handle, tmp_filename) + self.assertEqual(s2.get_at((0, 0)), s.get_at((0, 0))) + os.remove(tmp_filename) + + def test_save_colorkey(self): + """make sure the color key is not changed when saving.""" + s = pygame.Surface((10, 10), pygame.SRCALPHA, 32) + s.fill((23, 23, 23)) + s.set_colorkey((0, 0, 0)) + colorkey1 = s.get_colorkey() + p1 = s.get_at((0, 0)) + + temp_filename = "tmpimg.png" + try: + pygame.image.save(s, temp_filename) + s2 = pygame.image.load(temp_filename) + finally: + os.remove(temp_filename) + + colorkey2 = s.get_colorkey() + # check that the pixel and the colorkey is correct. + self.assertEqual(colorkey1, colorkey2) + self.assertEqual(p1, s2.get_at((0, 0))) + + def test_load_unicode_path(self): + import shutil + + orig = example_path("data/asprite.bmp") + temp = os.path.join(example_path("data"), u"你好.bmp") + shutil.copy(orig, temp) + try: + im = pygame.image.load(temp) + finally: + os.remove(temp) + + def _unicode_save(self, temp_file): + im = pygame.Surface((10, 10), 0, 32) + try: + with open(temp_file, "w") as f: + pass + os.remove(temp_file) + except IOError: + raise unittest.SkipTest("the path cannot be opened") + + self.assertFalse(os.path.exists(temp_file)) + + try: + pygame.image.save(im, temp_file) + + self.assertGreater(os.path.getsize(temp_file), 10) + finally: + try: + os.remove(temp_file) + except EnvironmentError: + pass + + def test_save_unicode_path(self): + """save unicode object with non-ASCII chars""" + self._unicode_save(u"你好.bmp") + + def assertPremultipliedAreEqual(self, string1, string2, source_string): + self.assertEqual(len(string1), len(string2)) + block_size = 20 + if string1 != string2: + for block_start in range(0, len(string1), block_size): + block_end = min(block_start + block_size, len(string1)) + block1 = string1[block_start:block_end] + block2 = string2[block_start:block_end] + if block1 != block2: + source_block = source_string[block_start:block_end] + msg = ( + "string difference in %d to %d of %d:\n%s\n%s\nsource:\n%s" + % ( + block_start, + block_end, + len(string1), + binascii.hexlify(block1), + binascii.hexlify(block2), + binascii.hexlify(source_block), + ) + ) + self.fail(msg) + + def test_to_string__premultiplied(self): + """test to make sure we can export a surface to a premultiplied alpha string""" + + def convertRGBAtoPremultiplied(surface_to_modify): + for x in range(surface_to_modify.get_width()): + for y in range(surface_to_modify.get_height()): + color = surface_to_modify.get_at((x, y)) + premult_color = ( + color[0] * color[3] / 255, + color[1] * color[3] / 255, + color[2] * color[3] / 255, + color[3], + ) + surface_to_modify.set_at((x, y), premult_color) + + test_surface = pygame.Surface((256, 256), pygame.SRCALPHA, 32) + for x in range(test_surface.get_width()): + for y in range(test_surface.get_height()): + i = x + y * test_surface.get_width() + test_surface.set_at( + (x, y), ((i * 7) % 256, (i * 13) % 256, (i * 27) % 256, y) + ) + premultiplied_copy = test_surface.copy() + convertRGBAtoPremultiplied(premultiplied_copy) + self.assertPremultipliedAreEqual( + pygame.image.tostring(test_surface, "RGBA_PREMULT"), + pygame.image.tostring(premultiplied_copy, "RGBA"), + pygame.image.tostring(test_surface, "RGBA"), + ) + self.assertPremultipliedAreEqual( + pygame.image.tostring(test_surface, "ARGB_PREMULT"), + pygame.image.tostring(premultiplied_copy, "ARGB"), + pygame.image.tostring(test_surface, "ARGB"), + ) + + no_alpha_surface = pygame.Surface((256, 256), 0, 24) + self.assertRaises( + ValueError, pygame.image.tostring, no_alpha_surface, "RGBA_PREMULT" + ) + + # Custom assert method to check for identical surfaces. + def _assertSurfaceEqual(self, surf_a, surf_b, msg=None): + a_width, a_height = surf_a.get_width(), surf_a.get_height() + + # Check a few things to see if the surfaces are equal. + self.assertEqual(a_width, surf_b.get_width(), msg) + self.assertEqual(a_height, surf_b.get_height(), msg) + self.assertEqual(surf_a.get_size(), surf_b.get_size(), msg) + self.assertEqual(surf_a.get_rect(), surf_b.get_rect(), msg) + self.assertEqual(surf_a.get_colorkey(), surf_b.get_colorkey(), msg) + self.assertEqual(surf_a.get_alpha(), surf_b.get_alpha(), msg) + self.assertEqual(surf_a.get_flags(), surf_b.get_flags(), msg) + self.assertEqual(surf_a.get_bitsize(), surf_b.get_bitsize(), msg) + self.assertEqual(surf_a.get_bytesize(), surf_b.get_bytesize(), msg) + # Anything else? + + # Making the method lookups local for a possible speed up. + surf_a_get_at = surf_a.get_at + surf_b_get_at = surf_b.get_at + for y in range(a_height): + for x in range(a_width): + self.assertEqual( + surf_a_get_at((x, y)), + surf_b_get_at((x, y)), + "%s (pixel: %d, %d)" % (msg, x, y), + ) + + def test_fromstring__and_tostring(self): + """Ensure methods tostring() and fromstring() are symmetric.""" + + #################################################################### + def RotateRGBAtoARGB(str_buf): + byte_buf = array.array("B", str_buf) + num_quads = len(byte_buf) // 4 + for i in range(num_quads): + alpha = byte_buf[i * 4 + 3] + byte_buf[i * 4 + 3] = byte_buf[i * 4 + 2] + byte_buf[i * 4 + 2] = byte_buf[i * 4 + 1] + byte_buf[i * 4 + 1] = byte_buf[i * 4 + 0] + byte_buf[i * 4 + 0] = alpha + return tostring(byte_buf) + + #################################################################### + def RotateARGBtoRGBA(str_buf): + byte_buf = array.array("B", str_buf) + num_quads = len(byte_buf) // 4 + for i in range(num_quads): + alpha = byte_buf[i * 4 + 0] + byte_buf[i * 4 + 0] = byte_buf[i * 4 + 1] + byte_buf[i * 4 + 1] = byte_buf[i * 4 + 2] + byte_buf[i * 4 + 2] = byte_buf[i * 4 + 3] + byte_buf[i * 4 + 3] = alpha + return tostring(byte_buf) + + #################################################################### + test_surface = pygame.Surface((64, 256), flags=pygame.SRCALPHA, depth=32) + for i in range(256): + for j in range(16): + intensity = j * 16 + 15 + test_surface.set_at((j + 0, i), (intensity, i, i, i)) + test_surface.set_at((j + 16, i), (i, intensity, i, i)) + test_surface.set_at((j + 32, i), (i, i, intensity, i)) + test_surface.set_at((j + 32, i), (i, i, i, intensity)) + + self._assertSurfaceEqual( + test_surface, test_surface, "failing with identical surfaces" + ) + + rgba_buf = pygame.image.tostring(test_surface, "RGBA") + rgba_buf = RotateARGBtoRGBA(RotateRGBAtoARGB(rgba_buf)) + test_rotate_functions = pygame.image.fromstring( + rgba_buf, test_surface.get_size(), "RGBA" + ) + + self._assertSurfaceEqual( + test_surface, test_rotate_functions, "rotate functions are not symmetric" + ) + + rgba_buf = pygame.image.tostring(test_surface, "RGBA") + argb_buf = RotateRGBAtoARGB(rgba_buf) + test_from_argb_string = pygame.image.fromstring( + argb_buf, test_surface.get_size(), "ARGB" + ) + + self._assertSurfaceEqual( + test_surface, test_from_argb_string, '"RGBA" rotated to "ARGB" failed' + ) + + argb_buf = pygame.image.tostring(test_surface, "ARGB") + rgba_buf = RotateARGBtoRGBA(argb_buf) + test_to_argb_string = pygame.image.fromstring( + rgba_buf, test_surface.get_size(), "RGBA" + ) + + self._assertSurfaceEqual( + test_surface, test_to_argb_string, '"ARGB" rotated to "RGBA" failed' + ) + + for fmt in ("ARGB", "RGBA"): + fmt_buf = pygame.image.tostring(test_surface, fmt) + test_to_from_fmt_string = pygame.image.fromstring( + fmt_buf, test_surface.get_size(), fmt + ) + + self._assertSurfaceEqual( + test_surface, + test_to_from_fmt_string, + "tostring/fromstring functions are not " + 'symmetric with "{}" format'.format(fmt), + ) + + def test_tostring_depth_24(self): + test_surface = pygame.Surface((64, 256), depth=24) + for i in range(256): + for j in range(16): + intensity = j * 16 + 15 + test_surface.set_at((j + 0, i), (intensity, i, i, i)) + test_surface.set_at((j + 16, i), (i, intensity, i, i)) + test_surface.set_at((j + 32, i), (i, i, intensity, i)) + test_surface.set_at((j + 32, i), (i, i, i, intensity)) + + fmt = "RGB" + fmt_buf = pygame.image.tostring(test_surface, fmt) + test_to_from_fmt_string = pygame.image.fromstring( + fmt_buf, test_surface.get_size(), fmt + ) + + self._assertSurfaceEqual( + test_surface, + test_to_from_fmt_string, + "tostring/fromstring functions are not " + 'symmetric with "{}" format'.format(fmt), + ) + + def test_frombuffer_8bit(self): + """test reading pixel data from a bytes buffer""" + pygame.display.init() + eight_bit_palette_buffer = bytearray( + [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3] + ) + + eight_bit_surf = pygame.image.frombuffer(eight_bit_palette_buffer, (4, 4), "P") + eight_bit_surf.set_palette( + [(255, 10, 20), (255, 255, 255), (0, 0, 0), (50, 200, 20)] + ) + self.assertEqual(eight_bit_surf.get_at((0, 0)), pygame.Color(255, 10, 20)) + self.assertEqual(eight_bit_surf.get_at((1, 1)), pygame.Color(255, 255, 255)) + self.assertEqual(eight_bit_surf.get_at((2, 2)), pygame.Color(0, 0, 0)) + self.assertEqual(eight_bit_surf.get_at((3, 3)), pygame.Color(50, 200, 20)) + + def test_frombuffer_RGB(self): + rgb_buffer = bytearray( + [ + 255, + 10, + 20, + 255, + 10, + 20, + 255, + 10, + 20, + 255, + 10, + 20, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 200, + 20, + 50, + 200, + 20, + 50, + 200, + 20, + 50, + 200, + 20, + ] + ) + + rgb_surf = pygame.image.frombuffer(rgb_buffer, (4, 4), "RGB") + self.assertEqual(rgb_surf.get_at((0, 0)), pygame.Color(255, 10, 20)) + self.assertEqual(rgb_surf.get_at((1, 1)), pygame.Color(255, 255, 255)) + self.assertEqual(rgb_surf.get_at((2, 2)), pygame.Color(0, 0, 0)) + self.assertEqual(rgb_surf.get_at((3, 3)), pygame.Color(50, 200, 20)) + + def test_frombuffer_BGR(self): + bgr_buffer = bytearray( + [ + 20, + 10, + 255, + 20, + 10, + 255, + 20, + 10, + 255, + 20, + 10, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 20, + 200, + 50, + 20, + 200, + 50, + 20, + 200, + 50, + 20, + 200, + 50, + ] + ) + + bgr_surf = pygame.image.frombuffer(bgr_buffer, (4, 4), "BGR") + self.assertEqual(bgr_surf.get_at((0, 0)), pygame.Color(255, 10, 20)) + self.assertEqual(bgr_surf.get_at((1, 1)), pygame.Color(255, 255, 255)) + self.assertEqual(bgr_surf.get_at((2, 2)), pygame.Color(0, 0, 0)) + self.assertEqual(bgr_surf.get_at((3, 3)), pygame.Color(50, 200, 20)) + + def test_frombuffer_RGBX(self): + rgbx_buffer = bytearray( + [ + 255, + 10, + 20, + 255, + 255, + 10, + 20, + 255, + 255, + 10, + 20, + 255, + 255, + 10, + 20, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 255, + 0, + 0, + 0, + 255, + 0, + 0, + 0, + 255, + 0, + 0, + 0, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + ] + ) + + rgbx_surf = pygame.image.frombuffer(rgbx_buffer, (4, 4), "RGBX") + self.assertEqual(rgbx_surf.get_at((0, 0)), pygame.Color(255, 10, 20, 255)) + self.assertEqual(rgbx_surf.get_at((1, 1)), pygame.Color(255, 255, 255, 255)) + self.assertEqual(rgbx_surf.get_at((2, 2)), pygame.Color(0, 0, 0, 255)) + self.assertEqual(rgbx_surf.get_at((3, 3)), pygame.Color(50, 200, 20, 255)) + + def test_frombuffer_RGBA(self): + rgba_buffer = bytearray( + [ + 255, + 10, + 20, + 200, + 255, + 10, + 20, + 200, + 255, + 10, + 20, + 200, + 255, + 10, + 20, + 200, + 255, + 255, + 255, + 127, + 255, + 255, + 255, + 127, + 255, + 255, + 255, + 127, + 255, + 255, + 255, + 127, + 0, + 0, + 0, + 79, + 0, + 0, + 0, + 79, + 0, + 0, + 0, + 79, + 0, + 0, + 0, + 79, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + ] + ) + + rgba_surf = pygame.image.frombuffer(rgba_buffer, (4, 4), "RGBA") + self.assertEqual(rgba_surf.get_at((0, 0)), pygame.Color(255, 10, 20, 200)) + self.assertEqual(rgba_surf.get_at((1, 1)), pygame.Color(255, 255, 255, 127)) + self.assertEqual(rgba_surf.get_at((2, 2)), pygame.Color(0, 0, 0, 79)) + self.assertEqual(rgba_surf.get_at((3, 3)), pygame.Color(50, 200, 20, 255)) + + def test_frombuffer_ARGB(self): + argb_buffer = bytearray( + [ + 200, + 255, + 10, + 20, + 200, + 255, + 10, + 20, + 200, + 255, + 10, + 20, + 200, + 255, + 10, + 20, + 127, + 255, + 255, + 255, + 127, + 255, + 255, + 255, + 127, + 255, + 255, + 255, + 127, + 255, + 255, + 255, + 79, + 0, + 0, + 0, + 79, + 0, + 0, + 0, + 79, + 0, + 0, + 0, + 79, + 0, + 0, + 0, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + 255, + 50, + 200, + 20, + ] + ) + + argb_surf = pygame.image.frombuffer(argb_buffer, (4, 4), "ARGB") + self.assertEqual(argb_surf.get_at((0, 0)), pygame.Color(255, 10, 20, 200)) + self.assertEqual(argb_surf.get_at((1, 1)), pygame.Color(255, 255, 255, 127)) + self.assertEqual(argb_surf.get_at((2, 2)), pygame.Color(0, 0, 0, 79)) + self.assertEqual(argb_surf.get_at((3, 3)), pygame.Color(50, 200, 20, 255)) + + def test_get_extended(self): + # Create a png file and try to load it. If it cannot, get_extended() should return False + raw_image = [] + raw_image.append((200, 200, 200, 255, 100, 100, 100, 255)) + + f_descriptor, f_path = tempfile.mkstemp(suffix=".png") + + with os.fdopen(f_descriptor, "wb") as file: + w = png.Writer(2, 1, alpha=True) + w.write(file, raw_image) + + try: + surf = pygame.image.load(f_path) + loaded = True + except pygame.error: + loaded = False + + self.assertEqual(pygame.image.get_extended(), loaded) + os.remove(f_path) + + def test_get_sdl_image_version(self): + # If get_extended() returns False then get_sdl_image_version() should + # return None + if not pygame.image.get_extended(): + self.assertIsNone(pygame.image.get_sdl_image_version()) + else: + expected_length = 3 + expected_type = tuple + expected_item_type = int + + version = pygame.image.get_sdl_image_version() + + self.assertIsInstance(version, expected_type) + self.assertEqual(len(version), expected_length) + + for item in version: + self.assertIsInstance(item, expected_item_type) + + def test_load_basic(self): + """to see if we can load bmp from files and/or file-like objects in memory""" + + # pygame.image.load(filename): return Surface + + # test loading from a file + s = pygame.image.load_basic(example_path("data/asprite.bmp")) + self.assertEqual(s.get_at((0, 0)), (255, 255, 255, 255)) + + # test loading from io.BufferedReader + f = pygame.pkgdata.getResource("pygame_icon.bmp") + self.assertEqual(f.mode, "rb") + + surf = pygame.image.load_basic(f) + + self.assertEqual(surf.get_at((0, 0)), (5, 4, 5, 255)) + self.assertEqual(surf.get_height(), 32) + self.assertEqual(surf.get_width(), 32) + + f.close() + + def test_load_extended(self): + """can load different format images. + + We test loading the following file types: + bmp, png, jpg, gif (non-animated), pcx, tga (uncompressed), tif, xpm, ppm, pgm + Following file types are tested when using SDL 2 + svg, pnm, webp + All the loaded images are smaller than 32 x 32 pixels. + """ + + filename_expected_color = [ + ("asprite.bmp", (255, 255, 255, 255)), + ("laplacian.png", (10, 10, 70, 255)), + ("red.jpg", (254, 0, 0, 255)), + ("blue.gif", (0, 0, 255, 255)), + ("green.pcx", (0, 255, 0, 255)), + ("yellow.tga", (255, 255, 0, 255)), + ("turquoise.tif", (0, 255, 255, 255)), + ("purple.xpm", (255, 0, 255, 255)), + ("black.ppm", (0, 0, 0, 255)), + ("grey.pgm", (120, 120, 120, 255)), + ("teal.svg", (0, 128, 128, 255)), + ("crimson.pnm", (220, 20, 60, 255)), + ("scarlet.webp", (252, 14, 53, 255)), + ] + + for filename, expected_color in filename_expected_color: + with self.subTest( + "Test loading a " + filename.split(".")[-1], + filename="examples/data/" + filename, + expected_color=expected_color, + ): + surf = pygame.image.load_extended(example_path("data/" + filename)) + self.assertEqual(surf.get_at((0, 0)), expected_color) + + def test_load_pathlib(self): + """works loading using a Path argument.""" + path = pathlib.Path(example_path("data/asprite.bmp")) + surf = pygame.image.load_extended(path) + self.assertEqual(surf.get_at((0, 0)), (255, 255, 255, 255)) + + def test_save_extended(self): + surf = pygame.Surface((5, 5)) + surf.fill((23, 23, 23)) + + passing_formats = ["jpg", "png"] + passing_formats += [fmt.upper() for fmt in passing_formats] + + magic_hex = {} + magic_hex["jpg"] = [0xFF, 0xD8, 0xFF, 0xE0] + magic_hex["png"] = [0x89, 0x50, 0x4E, 0x47] + + failing_formats = ["bmp", "tga"] + failing_formats += [fmt.upper() for fmt in failing_formats] + + # check that .jpg and .png save + for fmt in passing_formats: + temp_file_name = "temp_file.%s" % fmt + # save image as .jpg and .png + pygame.image.save_extended(surf, temp_file_name) + with open(temp_file_name, "rb") as file: + # Test the magic numbers at the start of the file to ensure + # they are saved as the correct file type. + self.assertEqual(1, (test_magic(file, magic_hex[fmt.lower()]))) + # load the file to make sure it was saved correctly + loaded_file = pygame.image.load(temp_file_name) + self.assertEqual(loaded_file.get_at((0, 0)), surf.get_at((0, 0))) + # clean up the temp file + os.remove(temp_file_name) + # check that .bmp and .tga do not save + for fmt in failing_formats: + self.assertRaises( + pygame.error, pygame.image.save_extended, surf, "temp_file.%s" % fmt + ) + + def threads_load(self, images): + import pygame.threads + + for i in range(10): + surfs = pygame.threads.tmap(pygame.image.load, images) + for s in surfs: + self.assertIsInstance(s, pygame.Surface) + + def test_load_png_threads(self): + self.threads_load(glob.glob(example_path("data/*.png"))) + + def test_load_jpg_threads(self): + self.threads_load(glob.glob(example_path("data/*.jpg"))) + + def test_load_bmp_threads(self): + self.threads_load(glob.glob(example_path("data/*.bmp"))) + + def test_load_gif_threads(self): + self.threads_load(glob.glob(example_path("data/*.gif"))) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/imageext_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/imageext_tags.py new file mode 100644 index 0000000..25cff74 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/imageext_tags.py @@ -0,0 +1,7 @@ +__tags__ = [] + +import pygame +import sys + +if "pygame.imageext" not in sys.modules: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/imageext_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/imageext_test.py new file mode 100644 index 0000000..19faf83 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/imageext_test.py @@ -0,0 +1,94 @@ +# -*- coding: utf8 -*- +import os +import os.path +import sys +import unittest + +from pygame.tests.test_utils import example_path +import pygame, pygame.image, pygame.pkgdata + + +imageext = sys.modules["pygame.imageext"] + + +class ImageextModuleTest(unittest.TestCase): + # Most of the testing is done indirectly through image_test.py + # This just confirms file path encoding and error handling. + def test_save_non_string_file(self): + im = pygame.Surface((10, 10), 0, 32) + self.assertRaises(TypeError, imageext.save_extended, im, []) + + def test_load_non_string_file(self): + self.assertRaises(TypeError, imageext.load_extended, []) + + @unittest.skip("SDL silently removes invalid characters") + def test_save_bad_filename(self): + im = pygame.Surface((10, 10), 0, 32) + u = u"a\x00b\x00c.png" + self.assertRaises(pygame.error, imageext.save_extended, im, u) + + @unittest.skip("SDL silently removes invalid characters") + def test_load_bad_filename(self): + u = u"a\x00b\x00c.png" + self.assertRaises(pygame.error, imageext.load_extended, u) + + def test_save_unknown_extension(self): + im = pygame.Surface((10, 10), 0, 32) + s = "foo.bar" + self.assertRaises(pygame.error, imageext.save_extended, im, s) + + def test_load_unknown_extension(self): + s = "foo.bar" + self.assertRaises(FileNotFoundError, imageext.load_extended, s) + + def test_load_unknown_file(self): + s = "nonexistent.png" + self.assertRaises(FileNotFoundError, imageext.load_extended, s) + + def test_load_unicode_path_0(self): + u = example_path("data/alien1.png") + im = imageext.load_extended(u) + + def test_load_unicode_path_1(self): + """non-ASCII unicode""" + import shutil + + orig = example_path("data/alien1.png") + temp = os.path.join(example_path("data"), u"你好.png") + shutil.copy(orig, temp) + try: + im = imageext.load_extended(temp) + finally: + os.remove(temp) + + def _unicode_save(self, temp_file): + im = pygame.Surface((10, 10), 0, 32) + try: + with open(temp_file, "w") as f: + pass + os.remove(temp_file) + except IOError: + raise unittest.SkipTest("the path cannot be opened") + + self.assertFalse(os.path.exists(temp_file)) + + try: + imageext.save_extended(im, temp_file) + + self.assertGreater(os.path.getsize(temp_file), 10) + finally: + try: + os.remove(temp_file) + except EnvironmentError: + pass + + def test_save_unicode_path_0(self): + """unicode object with ASCII chars""" + self._unicode_save(u"temp_file.png") + + def test_save_unicode_path_1(self): + self._unicode_save(u"你好.png") + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/joystick_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/joystick_test.py new file mode 100644 index 0000000..e8cea2d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/joystick_test.py @@ -0,0 +1,172 @@ +import unittest +from pygame.tests.test_utils import question, prompt + +import pygame +import pygame._sdl2.controller + + +class JoystickTypeTest(unittest.TestCase): + def todo_test_Joystick(self): + + # __doc__ (as of 2008-08-02) for pygame.joystick.Joystick: + + # pygame.joystick.Joystick(id): return Joystick + # create a new Joystick object + # + # Create a new joystick to access a physical device. The id argument + # must be a value from 0 to pygame.joystick.get_count()-1. + # + # To access most of the Joystick methods, you'll need to init() the + # Joystick. This is separate from making sure the joystick module is + # initialized. When multiple Joysticks objects are created for the + # same physical joystick device (i.e., they have the same ID number), + # the state and values for those Joystick objects will be shared. + # + # The Joystick object allows you to get information about the types of + # controls on a joystick device. Once the device is initialized the + # Pygame event queue will start receiving events about its input. + # + # You can call the Joystick.get_name() and Joystick.get_id() functions + # without initializing the Joystick object. + # + + self.fail() + + +class JoystickModuleTest(unittest.TestCase): + def test_get_init(self): + # Check that get_init() matches what is actually happening + def error_check_get_init(): + try: + pygame.joystick.get_count() + except pygame.error: + return False + return True + + # Start uninitialised + self.assertEqual(pygame.joystick.get_init(), False) + + pygame.joystick.init() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # True + pygame.joystick.quit() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # False + + pygame.joystick.init() + pygame.joystick.init() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # True + pygame.joystick.quit() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # False + + pygame.joystick.quit() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # False + + for i in range(100): + pygame.joystick.init() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # True + pygame.joystick.quit() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # False + + for i in range(100): + pygame.joystick.quit() + self.assertEqual(pygame.joystick.get_init(), error_check_get_init()) # False + + def test_init(self): + """ + This unit test is for joystick.init() + It was written to help reduce maintenance costs + and to help test against changes to the code or + different platforms. + """ + pygame.quit() + # test that pygame.init automatically calls joystick.init + pygame.init() + self.assertEqual(pygame.joystick.get_init(), True) + + # Controller module interferes with the joystick module. + pygame._sdl2.controller.quit() + + # test that get_count doesn't work w/o joystick init + # this is done before and after an init to test + # that init activates the joystick functions + pygame.joystick.quit() + with self.assertRaises(pygame.error): + pygame.joystick.get_count() + + # test explicit call(s) to joystick.init. + # Also test that get_count works once init is called + iterations = 20 + for i in range(iterations): + pygame.joystick.init() + self.assertEqual(pygame.joystick.get_init(), True) + self.assertIsNotNone(pygame.joystick.get_count()) + + def test_quit(self): + """Test if joystick.quit works.""" + + pygame.joystick.init() + + self.assertIsNotNone(pygame.joystick.get_count()) # Is not None before quit + + pygame.joystick.quit() + + with self.assertRaises(pygame.error): # Raises error if quit worked + pygame.joystick.get_count() + + def test_get_count(self): + # Test that get_count correctly returns a non-negative number of joysticks + pygame.joystick.init() + + try: + count = pygame.joystick.get_count() + self.assertGreaterEqual( + count, 0, ("joystick.get_count() must " "return a value >= 0") + ) + finally: + pygame.joystick.quit() + + +class JoystickInteractiveTest(unittest.TestCase): + + __tags__ = ["interactive"] + + def test_get_count_interactive(self): + # Test get_count correctly identifies number of connected joysticks + prompt( + ( + "Please connect any joysticks/controllers now before starting the " + "joystick.get_count() test." + ) + ) + + pygame.joystick.init() + # pygame.joystick.get_count(): return count + # number of joysticks on the system, 0 means no joysticks connected + count = pygame.joystick.get_count() + + response = question( + ( + "NOTE: Having Steam open may add an extra virtual controller for " + "each joystick/controller physically plugged in.\n" + "joystick.get_count() thinks there is [{}] joystick(s)/controller(s)" + "connected to this system. Is this correct?".format(count) + ) + ) + + self.assertTrue(response) + + # When you create Joystick objects using Joystick(id), you pass an + # integer that must be lower than this count. + # Test Joystick(id) for each connected joystick + if count != 0: + for x in range(count): + pygame.joystick.Joystick(x) + with self.assertRaises(pygame.error): + pygame.joystick.Joystick(count) + + pygame.joystick.quit() + + +################################################################################ + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/key_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/key_test.py new file mode 100644 index 0000000..a15199f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/key_test.py @@ -0,0 +1,110 @@ +import os +import sys +import time +import unittest +import pygame +import pygame.key + + +class KeyModuleTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + pygame.init() + + @classmethod + def tearDownClass(cls): + pygame.quit() + + def setUp(cls): + # This makes sure pygame is always initialized before each test (in + # case a test calls pygame.quit()). + if not pygame.get_init(): + pygame.init() + if not pygame.display.get_init(): + pygame.display.init() + + def test_import(self): + """does it import?""" + import pygame.key + + # fixme: test_get_focused failing systematically in some linux + # fixme: test_get_focused failing on SDL 2.0.18 on Windows + @unittest.skip("flaky test, and broken on 2.0.18 windows") + def test_get_focused(self): + # This test fails in SDL2 in some linux + # This test was skipped in SDL1. + focused = pygame.key.get_focused() + self.assertFalse(focused) # No window to focus + self.assertIsInstance(focused, int) + # Dummy video driver never gets keyboard focus. + if os.environ.get("SDL_VIDEODRIVER") != "dummy": + # Positive test, fullscreen with events grabbed + display_sizes = pygame.display.list_modes() + if display_sizes == -1: + display_sizes = [(500, 500)] + pygame.display.set_mode(size=display_sizes[-1], flags=pygame.FULLSCREEN) + pygame.event.set_grab(True) + # Pump event queue to get window focus on macos + pygame.event.pump() + focused = pygame.key.get_focused() + self.assertIsInstance(focused, int) + self.assertTrue(focused) + # Now test negative, iconify takes away focus + pygame.event.clear() + # TODO: iconify test fails in windows + if os.name != "nt": + pygame.display.iconify() + # Apparent need to pump event queue in order to make sure iconify + # happens. See display_test.py's test_get_active_iconify + for _ in range(50): + time.sleep(0.01) + pygame.event.pump() + self.assertFalse(pygame.key.get_focused()) + # Test if focus is returned when iconify is gone + pygame.display.set_mode(size=display_sizes[-1], flags=pygame.FULLSCREEN) + for i in range(50): + time.sleep(0.01) + pygame.event.pump() + self.assertTrue(pygame.key.get_focused()) + # Test if a quit display raises an error: + pygame.display.quit() + with self.assertRaises(pygame.error) as cm: + pygame.key.get_focused() + + def test_get_pressed(self): + states = pygame.key.get_pressed() + self.assertEqual(states[pygame.K_RIGHT], 0) + + def test_name(self): + self.assertEqual(pygame.key.name(pygame.K_RETURN), "return") + self.assertEqual(pygame.key.name(pygame.K_0), "0") + self.assertEqual(pygame.key.name(pygame.K_SPACE), "space") + + def test_key_code(self): + self.assertEqual(pygame.key.key_code("return"), pygame.K_RETURN) + self.assertEqual(pygame.key.key_code("0"), pygame.K_0) + self.assertEqual(pygame.key.key_code("space"), pygame.K_SPACE) + + self.assertRaises(ValueError, pygame.key.key_code, "fizzbuzz") + + def test_set_and_get_mods(self): + pygame.key.set_mods(pygame.KMOD_CTRL) + self.assertEqual(pygame.key.get_mods(), pygame.KMOD_CTRL) + + pygame.key.set_mods(pygame.KMOD_ALT) + self.assertEqual(pygame.key.get_mods(), pygame.KMOD_ALT) + pygame.key.set_mods(pygame.KMOD_CTRL | pygame.KMOD_ALT) + self.assertEqual(pygame.key.get_mods(), pygame.KMOD_CTRL | pygame.KMOD_ALT) + + def test_set_and_get_repeat(self): + self.assertEqual(pygame.key.get_repeat(), (0, 0)) + + pygame.key.set_repeat(10, 15) + self.assertEqual(pygame.key.get_repeat(), (10, 15)) + + pygame.key.set_repeat() + self.assertEqual(pygame.key.get_repeat(), (0, 0)) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/mask_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/mask_test.py new file mode 100644 index 0000000..56c0917 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/mask_test.py @@ -0,0 +1,6440 @@ +from collections import OrderedDict +import copy +import platform +import random +import unittest +import sys + +import pygame +from pygame.locals import * +from pygame.math import Vector2 +from pygame.tests.test_utils import AssertRaisesRegexMixin + + +IS_PYPY = "PyPy" == platform.python_implementation() + + +def random_mask(size=(100, 100)): + """random_mask(size=(100,100)): return Mask + Create a mask of the given size, with roughly half the bits set at random.""" + m = pygame.Mask(size) + for i in range(size[0] * size[1] // 2): + x, y = random.randint(0, size[0] - 1), random.randint(0, size[1] - 1) + m.set_at((x, y)) + return m + + +def maskFromSurface(surface, threshold=127): + mask = pygame.Mask(surface.get_size()) + key = surface.get_colorkey() + if key: + for y in range(surface.get_height()): + for x in range(surface.get_width()): + if surface.get_at((x + 0.1, y + 0.1)) != key: + mask.set_at((x, y), 1) + else: + for y in range(surface.get_height()): + for x in range(surface.get_width()): + if surface.get_at((x, y))[3] > threshold: + mask.set_at((x, y), 1) + return mask + + +def create_bounding_rect(points): + """Creates a bounding rect from the given points.""" + xmin = xmax = points[0][0] + ymin = ymax = points[0][1] + + for x, y in points[1:]: + xmin = min(x, xmin) + xmax = max(x, xmax) + ymin = min(y, ymin) + ymax = max(y, ymax) + + return pygame.Rect((xmin, ymin), (xmax - xmin + 1, ymax - ymin + 1)) + + +def zero_size_pairs(width, height): + """Creates a generator which yields pairs of sizes. + + For each pair of sizes at least one of the sizes will have a 0 in it. + """ + sizes = ((width, height), (width, 0), (0, height), (0, 0)) + + return ((a, b) for a in sizes for b in sizes if 0 in a or 0 in b) + + +def corners(mask): + """Returns a tuple with the corner positions of the given mask. + + Clockwise from the top left corner. + """ + width, height = mask.get_size() + return ((0, 0), (width - 1, 0), (width - 1, height - 1), (0, height - 1)) + + +def off_corners(rect): + """Returns a tuple with the positions off of the corners of the given rect. + + Clockwise from the top left corner. + """ + return ( + (rect.left - 1, rect.top), + (rect.left - 1, rect.top - 1), + (rect.left, rect.top - 1), + (rect.right - 1, rect.top - 1), + (rect.right, rect.top - 1), + (rect.right, rect.top), + (rect.right, rect.bottom - 1), + (rect.right, rect.bottom), + (rect.right - 1, rect.bottom), + (rect.left, rect.bottom), + (rect.left - 1, rect.bottom), + (rect.left - 1, rect.bottom - 1), + ) + + +def assertSurfaceFilled(testcase, surface, expected_color, area_rect=None): + """Checks to see if the given surface is filled with the given color. + + If an area_rect is provided, only check that area of the surface. + """ + if area_rect is None: + x_range = range(surface.get_width()) + y_range = range(surface.get_height()) + else: + area_rect.normalize() + area_rect = area_rect.clip(surface.get_rect()) + x_range = range(area_rect.left, area_rect.right) + y_range = range(area_rect.top, area_rect.bottom) + + surface.lock() # Lock for possible speed up. + for pos in ((x, y) for y in y_range for x in x_range): + testcase.assertEqual(surface.get_at(pos), expected_color, pos) + surface.unlock() + + +def assertSurfaceFilledIgnoreArea(testcase, surface, expected_color, ignore_rect): + """Checks if the surface is filled with the given color. The + ignore_rect area is not checked. + """ + x_range = range(surface.get_width()) + y_range = range(surface.get_height()) + ignore_rect.normalize() + + surface.lock() # Lock for possible speed up. + for pos in ((x, y) for y in y_range for x in x_range): + if not ignore_rect.collidepoint(pos): + testcase.assertEqual(surface.get_at(pos), expected_color, pos) + surface.unlock() + + +def assertMaskEqual(testcase, m1, m2, msg=None): + """Checks to see if the 2 given masks are equal.""" + m1_count = m1.count() + + testcase.assertEqual(m1.get_size(), m2.get_size(), msg=msg) + testcase.assertEqual(m1_count, m2.count(), msg=msg) + testcase.assertEqual(m1_count, m1.overlap_area(m2, (0, 0)), msg=msg) + + # This can be used to help debug exact locations. + ##for i in range(m1.get_size()[0]): + ## for j in range(m1.get_size()[1]): + ## testcase.assertEqual(m1.get_at((i, j)), m2.get_at((i, j))) + + +# @unittest.skipIf(IS_PYPY, "pypy has lots of mask failures") # TODO +class MaskTypeTest(AssertRaisesRegexMixin, unittest.TestCase): + ORIGIN_OFFSETS = ( + (0, 0), + (0, 1), + (1, 1), + (1, 0), + (1, -1), + (0, -1), + (-1, -1), + (-1, 0), + (-1, 1), + ) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_mask(self): + """Ensure masks are created correctly without fill parameter.""" + expected_count = 0 + expected_size = (11, 23) + + mask1 = pygame.mask.Mask(expected_size) + mask2 = pygame.mask.Mask(size=expected_size) + + self.assertIsInstance(mask1, pygame.mask.Mask) + self.assertEqual(mask1.count(), expected_count) + self.assertEqual(mask1.get_size(), expected_size) + + self.assertIsInstance(mask2, pygame.mask.Mask) + self.assertEqual(mask2.count(), expected_count) + self.assertEqual(mask2.get_size(), expected_size) + + def test_mask__negative_size(self): + """Ensure the mask constructor handles negative sizes correctly.""" + for size in ((1, -1), (-1, 1), (-1, -1)): + with self.assertRaises(ValueError): + mask = pygame.Mask(size) + + def test_mask__fill_kwarg(self): + """Ensure masks are created correctly using the fill keyword.""" + width, height = 37, 47 + expected_size = (width, height) + fill_counts = {True: width * height, False: 0} + + for fill, expected_count in fill_counts.items(): + msg = "fill={}".format(fill) + + mask = pygame.mask.Mask(expected_size, fill=fill) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.count(), expected_count, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_mask__fill_kwarg_bit_boundaries(self): + """Ensures masks are created correctly using the fill keyword + over a range of sizes. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for height in range(1, 4): + for width in range(1, 66): + expected_count = width * height + expected_size = (width, height) + msg = "size={}".format(expected_size) + + mask = pygame.mask.Mask(expected_size, fill=True) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.count(), expected_count, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + + def test_mask__fill_arg(self): + """Ensure masks are created correctly using a fill arg.""" + width, height = 59, 71 + expected_size = (width, height) + fill_counts = {True: width * height, False: 0} + + for fill, expected_count in fill_counts.items(): + msg = "fill={}".format(fill) + + mask = pygame.mask.Mask(expected_size, fill) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.count(), expected_count, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + + def test_mask__size_kwarg(self): + """Ensure masks are created correctly using the size keyword.""" + width, height = 73, 83 + expected_size = (width, height) + fill_counts = {True: width * height, False: 0} + + for fill, expected_count in fill_counts.items(): + msg = "fill={}".format(fill) + + mask1 = pygame.mask.Mask(fill=fill, size=expected_size) + mask2 = pygame.mask.Mask(size=expected_size, fill=fill) + + self.assertIsInstance(mask1, pygame.mask.Mask, msg) + self.assertIsInstance(mask2, pygame.mask.Mask, msg) + self.assertEqual(mask1.count(), expected_count, msg) + self.assertEqual(mask2.count(), expected_count, msg) + self.assertEqual(mask1.get_size(), expected_size, msg) + self.assertEqual(mask2.get_size(), expected_size, msg) + + def test_copy(self): + """Ensures copy works correctly with some bits set and unset.""" + # Test different widths and heights. + for width in (31, 32, 33, 63, 64, 65): + for height in (31, 32, 33, 63, 64, 65): + mask = pygame.mask.Mask((width, height)) + + # Create a checkerboard pattern of set/unset bits. + for x in range(width): + for y in range(x & 1, height, 2): + mask.set_at((x, y)) + + # Test both the copy() and __copy__() methods. + for mask_copy in (mask.copy(), copy.copy(mask)): + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + + def test_copy__full(self): + """Ensures copy works correctly on a filled masked.""" + # Test different widths and heights. + for width in (31, 32, 33, 63, 64, 65): + for height in (31, 32, 33, 63, 64, 65): + mask = pygame.mask.Mask((width, height), fill=True) + + # Test both the copy() and __copy__() methods. + for mask_copy in (mask.copy(), copy.copy(mask)): + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + + def test_copy__empty(self): + """Ensures copy works correctly on an empty mask.""" + for width in (31, 32, 33, 63, 64, 65): + for height in (31, 32, 33, 63, 64, 65): + mask = pygame.mask.Mask((width, height)) + + # Test both the copy() and __copy__() methods. + for mask_copy in (mask.copy(), copy.copy(mask)): + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + + def test_copy__independent(self): + """Ensures copy makes an independent copy of the mask.""" + mask_set_pos = (64, 1) + mask_copy_set_pos = (64, 2) + mask = pygame.mask.Mask((65, 3)) + + # Test both the copy() and __copy__() methods. + mask_copies = (mask.copy(), copy.copy(mask)) + mask.set_at(mask_set_pos) + + for mask_copy in mask_copies: + mask_copy.set_at(mask_copy_set_pos) + + self.assertIsNot(mask_copy, mask) + self.assertNotEqual( + mask_copy.get_at(mask_set_pos), mask.get_at(mask_set_pos) + ) + self.assertNotEqual( + mask_copy.get_at(mask_copy_set_pos), mask.get_at(mask_copy_set_pos) + ) + + def test_get_size(self): + """Ensure a mask's size is correctly retrieved.""" + expected_size = (93, 101) + mask = pygame.mask.Mask(expected_size) + + self.assertEqual(mask.get_size(), expected_size) + + def test_get_rect(self): + """Ensures get_rect works correctly.""" + expected_rect = pygame.Rect((0, 0), (11, 13)) + + # Test on full and empty masks. + for fill in (True, False): + mask = pygame.mask.Mask(expected_rect.size, fill=fill) + + rect = mask.get_rect() + + self.assertEqual(rect, expected_rect) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_get_rect__one_kwarg(self): + """Ensures get_rect supports a single rect attribute kwarg. + + Tests all the rect attributes. + """ + # Rect attributes that take a single value. + RECT_SINGLE_VALUE_ATTRIBUTES = ( + "x", + "y", + "top", + "left", + "bottom", + "right", + "centerx", + "centery", + "width", + "height", + "w", + "h", + ) + + # Rect attributes that take 2 values. + RECT_DOUBLE_VALUE_ATTRIBUTES = ( + "topleft", + "bottomleft", + "topright", + "bottomright", + "midtop", + "midleft", + "midbottom", + "midright", + "center", + "size", + ) + + # Testing ints/floats and tuples/lists/Vector2s. + # {attribute_names : attribute_values} + rect_attributes = { + RECT_SINGLE_VALUE_ATTRIBUTES: (3, 5.1), + RECT_DOUBLE_VALUE_ATTRIBUTES: ((1, 2.2), [2.3, 3], Vector2(0, 1)), + } + + size = (7, 3) + mask = pygame.mask.Mask(size) + + for attributes, values in rect_attributes.items(): + for attribute in attributes: + for value in values: + expected_rect = pygame.Rect((0, 0), size) + setattr(expected_rect, attribute, value) + + rect = mask.get_rect(**{attribute: value}) + + self.assertEqual(rect, expected_rect) + + def test_get_rect__multiple_kwargs(self): + """Ensures get_rect supports multiple rect attribute kwargs.""" + mask = pygame.mask.Mask((5, 4)) + expected_rect = pygame.Rect((0, 0), (0, 0)) + kwargs = {"x": 7.1, "top": -1, "size": Vector2(2, 3.2)} + + for attrib, value in kwargs.items(): + setattr(expected_rect, attrib, value) + + rect = mask.get_rect(**kwargs) + + self.assertEqual(rect, expected_rect) + + def test_get_rect__no_arg_support(self): + """Ensures get_rect only supports kwargs.""" + mask = pygame.mask.Mask((4, 5)) + + with self.assertRaises(TypeError): + rect = mask.get_rect(3) + + with self.assertRaises(TypeError): + rect = mask.get_rect((1, 2)) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_get_rect__invalid_kwarg_name(self): + """Ensures get_rect detects invalid kwargs.""" + mask = pygame.mask.Mask((1, 2)) + + with self.assertRaises(AttributeError): + rect = mask.get_rect(righte=11) + + with self.assertRaises(AttributeError): + rect = mask.get_rect(toplef=(1, 1)) + + with self.assertRaises(AttributeError): + rect = mask.get_rect(move=(3, 2)) + + def test_get_rect__invalid_kwarg_format(self): + """Ensures get_rect detects invalid kwarg formats.""" + mask = pygame.mask.Mask((3, 11)) + + with self.assertRaises(TypeError): + rect = mask.get_rect(right="1") # Wrong type. + + with self.assertRaises(TypeError): + rect = mask.get_rect(bottom=(1,)) # Wrong type. + + with self.assertRaises(TypeError): + rect = mask.get_rect(centerx=(1, 1)) # Wrong type. + + with self.assertRaises(TypeError): + rect = mask.get_rect(midleft=(1, "1")) # Wrong type. + + with self.assertRaises(TypeError): + rect = mask.get_rect(topright=(1,)) # Too few. + + with self.assertRaises(TypeError): + rect = mask.get_rect(bottomleft=(1, 2, 3)) # Too many. + + with self.assertRaises(TypeError): + rect = mask.get_rect(midbottom=1) # Wrong type. + + def test_get_at(self): + """Ensure individual mask bits are correctly retrieved.""" + width, height = 5, 7 + mask0 = pygame.mask.Mask((width, height)) + mask1 = pygame.mask.Mask((width, height), fill=True) + mask0_expected_bit = 0 + mask1_expected_bit = 1 + pos = (width - 1, height - 1) + + # Check twice to make sure bits aren't toggled. + self.assertEqual(mask0.get_at(pos), mask0_expected_bit) + self.assertEqual(mask0.get_at(pos=pos), mask0_expected_bit) + self.assertEqual(mask1.get_at(Vector2(pos)), mask1_expected_bit) + self.assertEqual(mask1.get_at(pos=Vector2(pos)), mask1_expected_bit) + + def test_get_at__out_of_bounds(self): + """Ensure get_at() checks bounds.""" + width, height = 11, 3 + mask = pygame.mask.Mask((width, height)) + + with self.assertRaises(IndexError): + mask.get_at((width, 0)) + + with self.assertRaises(IndexError): + mask.get_at((0, height)) + + with self.assertRaises(IndexError): + mask.get_at((-1, 0)) + + with self.assertRaises(IndexError): + mask.get_at((0, -1)) + + def test_set_at(self): + """Ensure individual mask bits are set to 1.""" + width, height = 13, 17 + mask0 = pygame.mask.Mask((width, height)) + mask1 = pygame.mask.Mask((width, height), fill=True) + mask0_expected_count = 1 + mask1_expected_count = mask1.count() + expected_bit = 1 + pos = (width - 1, height - 1) + + mask0.set_at(pos, expected_bit) # set 0 to 1 + mask1.set_at(pos=Vector2(pos), value=expected_bit) # set 1 to 1 + + self.assertEqual(mask0.get_at(pos), expected_bit) + self.assertEqual(mask0.count(), mask0_expected_count) + self.assertEqual(mask1.get_at(pos), expected_bit) + self.assertEqual(mask1.count(), mask1_expected_count) + + def test_set_at__to_0(self): + """Ensure individual mask bits are set to 0.""" + width, height = 11, 7 + mask0 = pygame.mask.Mask((width, height)) + mask1 = pygame.mask.Mask((width, height), fill=True) + mask0_expected_count = 0 + mask1_expected_count = mask1.count() - 1 + expected_bit = 0 + pos = (width - 1, height - 1) + + mask0.set_at(pos, expected_bit) # set 0 to 0 + mask1.set_at(pos, expected_bit) # set 1 to 0 + + self.assertEqual(mask0.get_at(pos), expected_bit) + self.assertEqual(mask0.count(), mask0_expected_count) + self.assertEqual(mask1.get_at(pos), expected_bit) + self.assertEqual(mask1.count(), mask1_expected_count) + + def test_set_at__default_value(self): + """Ensure individual mask bits are set using the default value.""" + width, height = 3, 21 + mask0 = pygame.mask.Mask((width, height)) + mask1 = pygame.mask.Mask((width, height), fill=True) + mask0_expected_count = 1 + mask1_expected_count = mask1.count() + expected_bit = 1 + pos = (width - 1, height - 1) + + mask0.set_at(pos) # set 0 to 1 + mask1.set_at(pos) # set 1 to 1 + + self.assertEqual(mask0.get_at(pos), expected_bit) + self.assertEqual(mask0.count(), mask0_expected_count) + self.assertEqual(mask1.get_at(pos), expected_bit) + self.assertEqual(mask1.count(), mask1_expected_count) + + def test_set_at__out_of_bounds(self): + """Ensure set_at() checks bounds.""" + width, height = 11, 3 + mask = pygame.mask.Mask((width, height)) + + with self.assertRaises(IndexError): + mask.set_at((width, 0)) + + with self.assertRaises(IndexError): + mask.set_at((0, height)) + + with self.assertRaises(IndexError): + mask.set_at((-1, 0)) + + with self.assertRaises(IndexError): + mask.set_at((0, -1)) + + def test_overlap(self): + """Ensure the overlap intersection is correctly calculated. + + Testing the different combinations of full/empty masks: + (mask1-filled) 1 overlap 1 (mask2-filled) + (mask1-empty) 0 overlap 1 (mask2-filled) + (mask1-filled) 1 overlap 0 (mask2-empty) + (mask1-empty) 0 overlap 0 (mask2-empty) + """ + expected_size = (4, 4) + offset = (0, 0) + expected_default = None + expected_overlaps = {(True, True): offset} + + for fill2 in (True, False): + mask2 = pygame.mask.Mask(expected_size, fill=fill2) + mask2_count = mask2.count() + + for fill1 in (True, False): + key = (fill1, fill2) + msg = "key={}".format(key) + mask1 = pygame.mask.Mask(expected_size, fill=fill1) + mask1_count = mask1.count() + expected_pos = expected_overlaps.get(key, expected_default) + + overlap_pos = mask1.overlap(mask2, offset) + + self.assertEqual(overlap_pos, expected_pos, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), expected_size, msg) + self.assertEqual(mask2.get_size(), expected_size, msg) + + def test_overlap__offset(self): + """Ensure an offset overlap intersection is correctly calculated.""" + mask1 = pygame.mask.Mask((65, 3), fill=True) + mask2 = pygame.mask.Mask((66, 4), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + + for offset in self.ORIGIN_OFFSETS: + msg = "offset={}".format(offset) + expected_pos = (max(offset[0], 0), max(offset[1], 0)) + + overlap_pos = mask1.overlap(other=mask2, offset=offset) + + self.assertEqual(overlap_pos, expected_pos, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + def test_overlap__offset_with_unset_bits(self): + """Ensure an offset overlap intersection is correctly calculated + when (0, 0) bits not set.""" + mask1 = pygame.mask.Mask((65, 3), fill=True) + mask2 = pygame.mask.Mask((66, 4), fill=True) + unset_pos = (0, 0) + mask1.set_at(unset_pos, 0) + mask2.set_at(unset_pos, 0) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + + for offset in self.ORIGIN_OFFSETS: + msg = "offset={}".format(offset) + x, y = offset + expected_y = max(y, 0) + if 0 == y: + expected_x = max(x + 1, 1) + elif 0 < y: + expected_x = max(x + 1, 0) + else: + expected_x = max(x, 1) + + overlap_pos = mask1.overlap(mask2, Vector2(offset)) + + self.assertEqual(overlap_pos, (expected_x, expected_y), msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + self.assertEqual(mask1.get_at(unset_pos), 0, msg) + self.assertEqual(mask2.get_at(unset_pos), 0, msg) + + def test_overlap__no_overlap(self): + """Ensure an offset overlap intersection is correctly calculated + when there is no overlap.""" + mask1 = pygame.mask.Mask((65, 3), fill=True) + mask1_count = mask1.count() + mask1_size = mask1.get_size() + + mask2_w, mask2_h = 67, 5 + mask2_size = (mask2_w, mask2_h) + mask2 = pygame.mask.Mask(mask2_size) + set_pos = (mask2_w - 1, mask2_h - 1) + mask2.set_at(set_pos) + mask2_count = 1 + + for offset in self.ORIGIN_OFFSETS: + msg = "offset={}".format(offset) + + overlap_pos = mask1.overlap(mask2, offset) + + self.assertIsNone(overlap_pos, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + self.assertEqual(mask2.get_at(set_pos), 1, msg) + + def test_overlap__offset_boundary(self): + """Ensures overlap handles offsets and boundaries correctly.""" + mask1 = pygame.mask.Mask((13, 3), fill=True) + mask2 = pygame.mask.Mask((7, 5), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + + # Check the 4 boundaries. + offsets = ( + (mask1_size[0], 0), # off right + (0, mask1_size[1]), # off bottom + (-mask2_size[0], 0), # off left + (0, -mask2_size[1]), + ) # off top + + for offset in offsets: + msg = "offset={}".format(offset) + + overlap_pos = mask1.overlap(mask2, offset) + + self.assertIsNone(overlap_pos, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap__bit_boundaries(self): + """Ensures overlap handles masks of different sizes correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for height in range(2, 4): + for width in range(2, 66): + mask_size = (width, height) + mask_count = width * height + mask1 = pygame.mask.Mask(mask_size, fill=True) + mask2 = pygame.mask.Mask(mask_size, fill=True) + + # Testing masks offset from each other. + for offset in self.ORIGIN_OFFSETS: + msg = "size={}, offset={}".format(mask_size, offset) + expected_pos = (max(offset[0], 0), max(offset[1], 0)) + + overlap_pos = mask1.overlap(mask2, offset) + + self.assertEqual(overlap_pos, expected_pos, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask_count, msg) + self.assertEqual(mask2.count(), mask_count, msg) + self.assertEqual(mask1.get_size(), mask_size, msg) + self.assertEqual(mask2.get_size(), mask_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap__invalid_mask_arg(self): + """Ensure overlap handles invalid mask arguments correctly.""" + size = (5, 3) + offset = (0, 0) + mask = pygame.mask.Mask(size) + invalid_mask = pygame.Surface(size) + + with self.assertRaises(TypeError): + overlap_pos = mask.overlap(invalid_mask, offset) + + def test_overlap__invalid_offset_arg(self): + """Ensure overlap handles invalid offset arguments correctly.""" + size = (2, 7) + offset = "(0, 0)" + mask1 = pygame.mask.Mask(size) + mask2 = pygame.mask.Mask(size) + + with self.assertRaises(TypeError): + overlap_pos = mask1.overlap(mask2, offset) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap_area(self): + """Ensure the overlap_area is correctly calculated. + + Testing the different combinations of full/empty masks: + (mask1-filled) 1 overlap_area 1 (mask2-filled) + (mask1-empty) 0 overlap_area 1 (mask2-filled) + (mask1-filled) 1 overlap_area 0 (mask2-empty) + (mask1-empty) 0 overlap_area 0 (mask2-empty) + """ + expected_size = width, height = (4, 4) + offset = (0, 0) + expected_default = 0 + expected_counts = {(True, True): width * height} + + for fill2 in (True, False): + mask2 = pygame.mask.Mask(expected_size, fill=fill2) + mask2_count = mask2.count() + + for fill1 in (True, False): + key = (fill1, fill2) + msg = "key={}".format(key) + mask1 = pygame.mask.Mask(expected_size, fill=fill1) + mask1_count = mask1.count() + expected_count = expected_counts.get(key, expected_default) + + overlap_count = mask1.overlap_area(mask2, offset) + + self.assertEqual(overlap_count, expected_count, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), expected_size, msg) + self.assertEqual(mask2.get_size(), expected_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap_area__offset(self): + """Ensure an offset overlap_area is correctly calculated.""" + mask1 = pygame.mask.Mask((65, 3), fill=True) + mask2 = pygame.mask.Mask((66, 4), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + for offset in self.ORIGIN_OFFSETS: + msg = "offset={}".format(offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_count = overlap_rect.w * overlap_rect.h + + overlap_count = mask1.overlap_area(other=mask2, offset=offset) + + self.assertEqual(overlap_count, expected_count, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + def test_overlap_area__offset_boundary(self): + """Ensures overlap_area handles offsets and boundaries correctly.""" + mask1 = pygame.mask.Mask((11, 3), fill=True) + mask2 = pygame.mask.Mask((5, 7), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + expected_count = 0 + + # Check the 4 boundaries. + offsets = ( + (mask1_size[0], 0), # off right + (0, mask1_size[1]), # off bottom + (-mask2_size[0], 0), # off left + (0, -mask2_size[1]), + ) # off top + + for offset in offsets: + msg = "offset={}".format(offset) + + overlap_count = mask1.overlap_area(mask2, Vector2(offset)) + + self.assertEqual(overlap_count, expected_count, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap_area__bit_boundaries(self): + """Ensures overlap_area handles masks of different sizes correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for height in range(2, 4): + for width in range(2, 66): + mask_size = (width, height) + mask_count = width * height + mask1 = pygame.mask.Mask(mask_size, fill=True) + mask2 = pygame.mask.Mask(mask_size, fill=True) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + # Testing masks offset from each other. + for offset in self.ORIGIN_OFFSETS: + msg = "size={}, offset={}".format(mask_size, offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_overlap_count = overlap_rect.w * overlap_rect.h + + overlap_count = mask1.overlap_area(mask2, offset) + + self.assertEqual(overlap_count, expected_overlap_count, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask_count, msg) + self.assertEqual(mask2.count(), mask_count, msg) + self.assertEqual(mask1.get_size(), mask_size, msg) + self.assertEqual(mask2.get_size(), mask_size, msg) + + def test_overlap_area__invalid_mask_arg(self): + """Ensure overlap_area handles invalid mask arguments correctly.""" + size = (3, 5) + offset = (0, 0) + mask = pygame.mask.Mask(size) + invalid_mask = pygame.Surface(size) + + with self.assertRaises(TypeError): + overlap_count = mask.overlap_area(invalid_mask, offset) + + def test_overlap_area__invalid_offset_arg(self): + """Ensure overlap_area handles invalid offset arguments correctly.""" + size = (7, 2) + offset = "(0, 0)" + mask1 = pygame.mask.Mask(size) + mask2 = pygame.mask.Mask(size) + + with self.assertRaises(TypeError): + overlap_count = mask1.overlap_area(mask2, offset) + + def test_overlap_mask(self): + """Ensure overlap_mask's mask has correct bits set. + + Testing the different combinations of full/empty masks: + (mask1-filled) 1 overlap_mask 1 (mask2-filled) + (mask1-empty) 0 overlap_mask 1 (mask2-filled) + (mask1-filled) 1 overlap_mask 0 (mask2-empty) + (mask1-empty) 0 overlap_mask 0 (mask2-empty) + """ + expected_size = (4, 4) + offset = (0, 0) + expected_default = pygame.mask.Mask(expected_size) + expected_masks = {(True, True): pygame.mask.Mask(expected_size, fill=True)} + + for fill2 in (True, False): + mask2 = pygame.mask.Mask(expected_size, fill=fill2) + mask2_count = mask2.count() + + for fill1 in (True, False): + key = (fill1, fill2) + msg = "key={}".format(key) + mask1 = pygame.mask.Mask(expected_size, fill=fill1) + mask1_count = mask1.count() + expected_mask = expected_masks.get(key, expected_default) + + overlap_mask = mask1.overlap_mask(other=mask2, offset=offset) + + self.assertIsInstance(overlap_mask, pygame.mask.Mask, msg) + assertMaskEqual(self, overlap_mask, expected_mask, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), expected_size, msg) + self.assertEqual(mask2.get_size(), expected_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap_mask__bits_set(self): + """Ensure overlap_mask's mask has correct bits set.""" + mask1 = pygame.mask.Mask((50, 50), fill=True) + mask2 = pygame.mask.Mask((300, 10), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + + mask3 = mask1.overlap_mask(mask2, (-1, 0)) + + for i in range(50): + for j in range(10): + self.assertEqual(mask3.get_at((i, j)), 1, "({}, {})".format(i, j)) + + for i in range(50): + for j in range(11, 50): + self.assertEqual(mask3.get_at((i, j)), 0, "({}, {})".format(i, j)) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count) + self.assertEqual(mask2.count(), mask2_count) + self.assertEqual(mask1.get_size(), mask1_size) + self.assertEqual(mask2.get_size(), mask2_size) + + def test_overlap_mask__offset(self): + """Ensure an offset overlap_mask's mask is correctly calculated.""" + mask1 = pygame.mask.Mask((65, 3), fill=True) + mask2 = pygame.mask.Mask((66, 4), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + expected_mask = pygame.Mask(mask1_size) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + for offset in self.ORIGIN_OFFSETS: + msg = "offset={}".format(offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_mask.clear() + expected_mask.draw( + pygame.Mask(overlap_rect.size, fill=True), overlap_rect.topleft + ) + + overlap_mask = mask1.overlap_mask(mask2, offset) + + self.assertIsInstance(overlap_mask, pygame.mask.Mask, msg) + assertMaskEqual(self, overlap_mask, expected_mask, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap_mask__specific_offsets(self): + """Ensure an offset overlap_mask's mask is correctly calculated. + + Testing the specific case of: + -both masks are wider than 32 bits + -a positive offset is used + -the mask calling overlap_mask() is wider than the mask passed in + """ + mask1 = pygame.mask.Mask((65, 5), fill=True) + mask2 = pygame.mask.Mask((33, 3), fill=True) + expected_mask = pygame.Mask(mask1.get_size()) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + # This rect's corners are used to move rect2 around the inside of + # rect1. + corner_rect = rect1.inflate(-2, -2) + + for corner in ("topleft", "topright", "bottomright", "bottomleft"): + setattr(rect2, corner, getattr(corner_rect, corner)) + offset = rect2.topleft + msg = "offset={}".format(offset) + overlap_rect = rect1.clip(rect2) + expected_mask.clear() + expected_mask.draw( + pygame.Mask(overlap_rect.size, fill=True), overlap_rect.topleft + ) + + overlap_mask = mask1.overlap_mask(mask2, offset) + + self.assertIsInstance(overlap_mask, pygame.mask.Mask, msg) + assertMaskEqual(self, overlap_mask, expected_mask, msg) + + def test_overlap_mask__offset_boundary(self): + """Ensures overlap_mask handles offsets and boundaries correctly.""" + mask1 = pygame.mask.Mask((9, 3), fill=True) + mask2 = pygame.mask.Mask((11, 5), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + expected_count = 0 + expected_size = mask1_size + + # Check the 4 boundaries. + offsets = ( + (mask1_size[0], 0), # off right + (0, mask1_size[1]), # off bottom + (-mask2_size[0], 0), # off left + (0, -mask2_size[1]), + ) # off top + + for offset in offsets: + msg = "offset={}".format(offset) + + overlap_mask = mask1.overlap_mask(mask2, offset) + + self.assertIsInstance(overlap_mask, pygame.mask.Mask, msg) + self.assertEqual(overlap_mask.count(), expected_count, msg) + self.assertEqual(overlap_mask.get_size(), expected_size, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_overlap_mask__bit_boundaries(self): + """Ensures overlap_mask handles masks of different sizes correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for height in range(2, 4): + for width in range(2, 66): + mask_size = (width, height) + mask_count = width * height + mask1 = pygame.mask.Mask(mask_size, fill=True) + mask2 = pygame.mask.Mask(mask_size, fill=True) + expected_mask = pygame.Mask(mask_size) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + # Testing masks offset from each other. + for offset in self.ORIGIN_OFFSETS: + msg = "size={}, offset={}".format(mask_size, offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_mask.clear() + expected_mask.draw( + pygame.Mask(overlap_rect.size, fill=True), overlap_rect.topleft + ) + + overlap_mask = mask1.overlap_mask(mask2, offset) + + self.assertIsInstance(overlap_mask, pygame.mask.Mask, msg) + assertMaskEqual(self, overlap_mask, expected_mask, msg) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask_count, msg) + self.assertEqual(mask2.count(), mask_count, msg) + self.assertEqual(mask1.get_size(), mask_size, msg) + self.assertEqual(mask2.get_size(), mask_size, msg) + + def test_overlap_mask__invalid_mask_arg(self): + """Ensure overlap_mask handles invalid mask arguments correctly.""" + size = (3, 2) + offset = (0, 0) + mask = pygame.mask.Mask(size) + invalid_mask = pygame.Surface(size) + + with self.assertRaises(TypeError): + overlap_mask = mask.overlap_mask(invalid_mask, offset) + + def test_overlap_mask__invalid_offset_arg(self): + """Ensure overlap_mask handles invalid offset arguments correctly.""" + size = (5, 2) + offset = "(0, 0)" + mask1 = pygame.mask.Mask(size) + mask2 = pygame.mask.Mask(size) + + with self.assertRaises(TypeError): + overlap_mask = mask1.overlap_mask(mask2, offset) + + def test_mask_access(self): + """do the set_at, and get_at parts work correctly?""" + m = pygame.Mask((10, 10)) + m.set_at((0, 0), 1) + self.assertEqual(m.get_at((0, 0)), 1) + m.set_at((9, 0), 1) + self.assertEqual(m.get_at((9, 0)), 1) + + # s = pygame.Surface((10,10)) + # s.set_at((1,0), (0, 0, 1, 255)) + # self.assertEqual(s.get_at((1,0)), (0, 0, 1, 255)) + # s.set_at((-1,0), (0, 0, 1, 255)) + + # out of bounds, should get IndexError + self.assertRaises(IndexError, lambda: m.get_at((-1, 0))) + self.assertRaises(IndexError, lambda: m.set_at((-1, 0), 1)) + self.assertRaises(IndexError, lambda: m.set_at((10, 0), 1)) + self.assertRaises(IndexError, lambda: m.set_at((0, 10), 1)) + + def test_fill(self): + """Ensure a mask can be filled.""" + width, height = 11, 23 + expected_count = width * height + expected_size = (width, height) + mask = pygame.mask.Mask(expected_size) + + mask.fill() + + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_fill__bit_boundaries(self): + """Ensures masks of different sizes are filled correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for height in range(1, 4): + for width in range(1, 66): + mask = pygame.mask.Mask((width, height)) + expected_count = width * height + + mask.fill() + + self.assertEqual( + mask.count(), expected_count, "size=({}, {})".format(width, height) + ) + + def test_clear(self): + """Ensure a mask can be cleared.""" + expected_count = 0 + expected_size = (13, 27) + mask = pygame.mask.Mask(expected_size, fill=True) + + mask.clear() + + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + + def test_clear__bit_boundaries(self): + """Ensures masks of different sizes are cleared correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + expected_count = 0 + + for height in range(1, 4): + for width in range(1, 66): + mask = pygame.mask.Mask((width, height), fill=True) + + mask.clear() + + self.assertEqual( + mask.count(), expected_count, "size=({}, {})".format(width, height) + ) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_invert(self): + """Ensure a mask can be inverted.""" + side = 73 + expected_size = (side, side) + mask1 = pygame.mask.Mask(expected_size) + mask2 = pygame.mask.Mask(expected_size, fill=True) + expected_count1 = side * side + expected_count2 = 0 + + for i in range(side): + expected_count1 -= 1 + expected_count2 += 1 + pos = (i, i) + mask1.set_at(pos) + mask2.set_at(pos, 0) + + mask1.invert() + mask2.invert() + + self.assertEqual(mask1.count(), expected_count1) + self.assertEqual(mask2.count(), expected_count2) + self.assertEqual(mask1.get_size(), expected_size) + self.assertEqual(mask2.get_size(), expected_size) + + for i in range(side): + pos = (i, i) + msg = "pos={}".format(pos) + + self.assertEqual(mask1.get_at(pos), 0, msg) + self.assertEqual(mask2.get_at(pos), 1, msg) + + def test_invert__full(self): + """Ensure a full mask can be inverted.""" + expected_count = 0 + expected_size = (43, 97) + mask = pygame.mask.Mask(expected_size, fill=True) + + mask.invert() + + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + + def test_invert__empty(self): + """Ensure an empty mask can be inverted.""" + width, height = 43, 97 + expected_size = (width, height) + expected_count = width * height + mask = pygame.mask.Mask(expected_size) + + mask.invert() + + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_invert__bit_boundaries(self): + """Ensures masks of different sizes are inverted correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for fill in (True, False): + for height in range(1, 4): + for width in range(1, 66): + mask = pygame.mask.Mask((width, height), fill=fill) + expected_count = 0 if fill else width * height + + mask.invert() + + self.assertEqual( + mask.count(), + expected_count, + "fill={}, size=({}, {})".format(fill, width, height), + ) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_scale(self): + """Ensure a mask can be scaled.""" + width, height = 43, 61 + original_size = (width, height) + + for fill in (True, False): + original_mask = pygame.mask.Mask(original_size, fill=fill) + original_count = width * height if fill else 0 + + # Test a range of sizes. Also tests scaling to 'same' + # size when new_w, new_h = width, height + for new_w in range(width - 10, width + 10): + for new_h in range(height - 10, height + 10): + expected_size = (new_w, new_h) + expected_count = new_w * new_h if fill else 0 + msg = "size={}".format(expected_size) + + mask = original_mask.scale(scale=expected_size) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.count(), expected_count, msg) + self.assertEqual(mask.get_size(), expected_size) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), original_count, msg) + self.assertEqual(original_mask.get_size(), original_size, msg) + + def test_scale__negative_size(self): + """Ensure scale handles negative sizes correctly.""" + mask = pygame.Mask((100, 100)) + + with self.assertRaises(ValueError): + mask.scale((-1, -1)) + + with self.assertRaises(ValueError): + mask.scale(Vector2(-1, 10)) + + with self.assertRaises(ValueError): + mask.scale((10, -1)) + + def test_draw(self): + """Ensure a mask can be drawn onto another mask. + + Testing the different combinations of full/empty masks: + (mask1-filled) 1 draw 1 (mask2-filled) + (mask1-empty) 0 draw 1 (mask2-filled) + (mask1-filled) 1 draw 0 (mask2-empty) + (mask1-empty) 0 draw 0 (mask2-empty) + """ + expected_size = (4, 4) + offset = (0, 0) + expected_default = pygame.mask.Mask(expected_size, fill=True) + expected_masks = {(False, False): pygame.mask.Mask(expected_size)} + + for fill2 in (True, False): + mask2 = pygame.mask.Mask(expected_size, fill=fill2) + mask2_count = mask2.count() + + for fill1 in (True, False): + key = (fill1, fill2) + msg = "key={}".format(key) + mask1 = pygame.mask.Mask(expected_size, fill=fill1) + expected_mask = expected_masks.get(key, expected_default) + + mask1.draw(mask2, offset) + + assertMaskEqual(self, mask1, expected_mask, msg) + + # Ensure mask2 unchanged. + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask2.get_size(), expected_size, msg) + + def test_draw__offset(self): + """Ensure an offset mask can be drawn onto another mask.""" + mask1 = pygame.mask.Mask((65, 3)) + mask2 = pygame.mask.Mask((66, 4), fill=True) + mask2_count = mask2.count() + mask2_size = mask2.get_size() + expected_mask = pygame.Mask(mask1.get_size()) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + for offset in self.ORIGIN_OFFSETS: + msg = "offset={}".format(offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_mask.clear() + + # Normally draw() could be used to set these bits, but the draw() + # method is being tested here, so a loop is used instead. + for x in range(overlap_rect.left, overlap_rect.right): + for y in range(overlap_rect.top, overlap_rect.bottom): + expected_mask.set_at((x, y)) + mask1.clear() # Ensure it's empty for testing each offset. + + mask1.draw(other=mask2, offset=offset) + + assertMaskEqual(self, mask1, expected_mask, msg) + + # Ensure mask2 unchanged. + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + def test_draw__specific_offsets(self): + """Ensure an offset mask can be drawn onto another mask. + + Testing the specific case of: + -both masks are wider than 32 bits + -a positive offset is used + -the mask calling draw() is wider than the mask passed in + """ + mask1 = pygame.mask.Mask((65, 5)) + mask2 = pygame.mask.Mask((33, 3), fill=True) + expected_mask = pygame.Mask(mask1.get_size()) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + # This rect's corners are used to move rect2 around the inside of + # rect1. + corner_rect = rect1.inflate(-2, -2) + + for corner in ("topleft", "topright", "bottomright", "bottomleft"): + setattr(rect2, corner, getattr(corner_rect, corner)) + offset = rect2.topleft + msg = "offset={}".format(offset) + overlap_rect = rect1.clip(rect2) + expected_mask.clear() + + # Normally draw() could be used to set these bits, but the draw() + # method is being tested here, so a loop is used instead. + for x in range(overlap_rect.left, overlap_rect.right): + for y in range(overlap_rect.top, overlap_rect.bottom): + expected_mask.set_at((x, y)) + mask1.clear() # Ensure it's empty for testing each offset. + + mask1.draw(mask2, offset) + + assertMaskEqual(self, mask1, expected_mask, msg) + + def test_draw__offset_boundary(self): + """Ensures draw handles offsets and boundaries correctly.""" + mask1 = pygame.mask.Mask((13, 5)) + mask2 = pygame.mask.Mask((7, 3), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + + # Check the 4 boundaries. + offsets = ( + (mask1_size[0], 0), # off right + (0, mask1_size[1]), # off bottom + (-mask2_size[0], 0), # off left + (0, -mask2_size[1]), + ) # off top + + for offset in offsets: + msg = "offset={}".format(offset) + + mask1.draw(mask2, offset) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_draw__bit_boundaries(self): + """Ensures draw handles masks of different sizes correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for height in range(2, 4): + for width in range(2, 66): + mask_size = (width, height) + mask_count = width * height + mask1 = pygame.mask.Mask(mask_size) + mask2 = pygame.mask.Mask(mask_size, fill=True) + expected_mask = pygame.Mask(mask_size) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + # Testing masks offset from each other. + for offset in self.ORIGIN_OFFSETS: + msg = "size={}, offset={}".format(mask_size, offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_mask.clear() + + # Normally draw() could be used to set these bits, but the + # draw() method is being tested here, so a loop is used + # instead. + for x in range(overlap_rect.left, overlap_rect.right): + for y in range(overlap_rect.top, overlap_rect.bottom): + expected_mask.set_at((x, y)) + mask1.clear() # Ensure it's empty for each test. + + mask1.draw(mask2, offset) + + assertMaskEqual(self, mask1, expected_mask, msg) + + # Ensure mask2 unchanged. + self.assertEqual(mask2.count(), mask_count, msg) + self.assertEqual(mask2.get_size(), mask_size, msg) + + def test_draw__invalid_mask_arg(self): + """Ensure draw handles invalid mask arguments correctly.""" + size = (7, 3) + offset = (0, 0) + mask = pygame.mask.Mask(size) + invalid_mask = pygame.Surface(size) + + with self.assertRaises(TypeError): + mask.draw(invalid_mask, offset) + + def test_draw__invalid_offset_arg(self): + """Ensure draw handles invalid offset arguments correctly.""" + size = (5, 7) + offset = "(0, 0)" + mask1 = pygame.mask.Mask(size) + mask2 = pygame.mask.Mask(size) + + with self.assertRaises(TypeError): + mask1.draw(mask2, offset) + + def test_erase(self): + """Ensure a mask can erase another mask. + + Testing the different combinations of full/empty masks: + (mask1-filled) 1 erase 1 (mask2-filled) + (mask1-empty) 0 erase 1 (mask2-filled) + (mask1-filled) 1 erase 0 (mask2-empty) + (mask1-empty) 0 erase 0 (mask2-empty) + """ + expected_size = (4, 4) + offset = (0, 0) + expected_default = pygame.mask.Mask(expected_size) + expected_masks = {(True, False): pygame.mask.Mask(expected_size, fill=True)} + + for fill2 in (True, False): + mask2 = pygame.mask.Mask(expected_size, fill=fill2) + mask2_count = mask2.count() + + for fill1 in (True, False): + key = (fill1, fill2) + msg = "key={}".format(key) + mask1 = pygame.mask.Mask(expected_size, fill=fill1) + expected_mask = expected_masks.get(key, expected_default) + + mask1.erase(mask2, offset) + + assertMaskEqual(self, mask1, expected_mask, msg) + + # Ensure mask2 unchanged. + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask2.get_size(), expected_size, msg) + + def test_erase__offset(self): + """Ensure an offset mask can erase another mask.""" + mask1 = pygame.mask.Mask((65, 3)) + mask2 = pygame.mask.Mask((66, 4), fill=True) + mask2_count = mask2.count() + mask2_size = mask2.get_size() + expected_mask = pygame.Mask(mask1.get_size()) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + for offset in self.ORIGIN_OFFSETS: + msg = "offset={}".format(offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_mask.fill() + + # Normally erase() could be used to clear these bits, but the + # erase() method is being tested here, so a loop is used instead. + for x in range(overlap_rect.left, overlap_rect.right): + for y in range(overlap_rect.top, overlap_rect.bottom): + expected_mask.set_at((x, y), 0) + mask1.fill() # Ensure it's filled for testing each offset. + + mask1.erase(other=mask2, offset=offset) + + assertMaskEqual(self, mask1, expected_mask, msg) + + # Ensure mask2 unchanged. + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + def test_erase__specific_offsets(self): + """Ensure an offset mask can erase another mask. + + Testing the specific case of: + -both masks are wider than 32 bits + -a positive offset is used + -the mask calling erase() is wider than the mask passed in + """ + mask1 = pygame.mask.Mask((65, 5)) + mask2 = pygame.mask.Mask((33, 3), fill=True) + expected_mask = pygame.Mask(mask1.get_size()) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + # This rect's corners are used to move rect2 around the inside of + # rect1. + corner_rect = rect1.inflate(-2, -2) + + for corner in ("topleft", "topright", "bottomright", "bottomleft"): + setattr(rect2, corner, getattr(corner_rect, corner)) + offset = rect2.topleft + msg = "offset={}".format(offset) + overlap_rect = rect1.clip(rect2) + expected_mask.fill() + + # Normally erase() could be used to clear these bits, but the + # erase() method is being tested here, so a loop is used instead. + for x in range(overlap_rect.left, overlap_rect.right): + for y in range(overlap_rect.top, overlap_rect.bottom): + expected_mask.set_at((x, y), 0) + mask1.fill() # Ensure it's filled for testing each offset. + + mask1.erase(mask2, Vector2(offset)) + + assertMaskEqual(self, mask1, expected_mask, msg) + + def test_erase__offset_boundary(self): + """Ensures erase handles offsets and boundaries correctly.""" + mask1 = pygame.mask.Mask((7, 11), fill=True) + mask2 = pygame.mask.Mask((3, 13), fill=True) + mask1_count = mask1.count() + mask2_count = mask2.count() + mask1_size = mask1.get_size() + mask2_size = mask2.get_size() + + # Check the 4 boundaries. + offsets = ( + (mask1_size[0], 0), # off right + (0, mask1_size[1]), # off bottom + (-mask2_size[0], 0), # off left + (0, -mask2_size[1]), + ) # off top + + for offset in offsets: + msg = "offset={}".format(offset) + + mask1.erase(mask2, offset) + + # Ensure mask1/mask2 unchanged. + self.assertEqual(mask1.count(), mask1_count, msg) + self.assertEqual(mask2.count(), mask2_count, msg) + self.assertEqual(mask1.get_size(), mask1_size, msg) + self.assertEqual(mask2.get_size(), mask2_size, msg) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_erase__bit_boundaries(self): + """Ensures erase handles masks of different sizes correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for height in range(2, 4): + for width in range(2, 66): + mask_size = (width, height) + mask_count = width * height + mask1 = pygame.mask.Mask(mask_size) + mask2 = pygame.mask.Mask(mask_size, fill=True) + expected_mask = pygame.Mask(mask_size) + + # Using rects to help determine the overlapping area. + rect1 = mask1.get_rect() + rect2 = mask2.get_rect() + + # Testing masks offset from each other. + for offset in self.ORIGIN_OFFSETS: + msg = "size={}, offset={}".format(mask_size, offset) + rect2.topleft = offset + overlap_rect = rect1.clip(rect2) + expected_mask.fill() + + # Normally erase() could be used to clear these bits, but + # the erase() method is being tested here, so a loop is + # used instead. + for x in range(overlap_rect.left, overlap_rect.right): + for y in range(overlap_rect.top, overlap_rect.bottom): + expected_mask.set_at((x, y), 0) + mask1.fill() # Ensure it's filled for each test. + + mask1.erase(mask2, offset) + + assertMaskEqual(self, mask1, expected_mask, msg) + + # Ensure mask2 unchanged. + self.assertEqual(mask2.count(), mask_count, msg) + self.assertEqual(mask2.get_size(), mask_size, msg) + + def test_erase__invalid_mask_arg(self): + """Ensure erase handles invalid mask arguments correctly.""" + size = (3, 7) + offset = (0, 0) + mask = pygame.mask.Mask(size) + invalid_mask = pygame.Surface(size) + + with self.assertRaises(TypeError): + mask.erase(invalid_mask, offset) + + def test_erase__invalid_offset_arg(self): + """Ensure erase handles invalid offset arguments correctly.""" + size = (7, 5) + offset = "(0, 0)" + mask1 = pygame.mask.Mask(size) + mask2 = pygame.mask.Mask(size) + + with self.assertRaises(TypeError): + mask1.erase(mask2, offset) + + def test_count(self): + """Ensure a mask's set bits are correctly counted.""" + side = 67 + expected_size = (side, side) + expected_count = 0 + mask = pygame.mask.Mask(expected_size) + + for i in range(side): + expected_count += 1 + mask.set_at((i, i)) + + count = mask.count() + + self.assertEqual(count, expected_count) + self.assertEqual(mask.get_size(), expected_size) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_count__bit_boundaries(self): + """Ensures the set bits of different sized masks are counted correctly. + + Tests masks of different sizes, including: + -masks 31 to 33 bits wide (32 bit boundaries) + -masks 63 to 65 bits wide (64 bit boundaries) + """ + for fill in (True, False): + for height in range(1, 4): + for width in range(1, 66): + mask = pygame.mask.Mask((width, height), fill=fill) + expected_count = width * height if fill else 0 + + # Test toggling each bit. + for pos in ((x, y) for y in range(height) for x in range(width)): + if fill: + mask.set_at(pos, 0) + expected_count -= 1 + else: + mask.set_at(pos, 1) + expected_count += 1 + + count = mask.count() + + self.assertEqual( + count, + expected_count, + "fill={}, size=({}, {}), pos={}".format( + fill, width, height, pos + ), + ) + + def test_count__full_mask(self): + """Ensure a full mask's set bits are correctly counted.""" + width, height = 17, 97 + expected_size = (width, height) + expected_count = width * height + mask = pygame.mask.Mask(expected_size, fill=True) + + count = mask.count() + + self.assertEqual(count, expected_count) + self.assertEqual(mask.get_size(), expected_size) + + def test_count__empty_mask(self): + """Ensure an empty mask's set bits are correctly counted.""" + expected_count = 0 + expected_size = (13, 27) + mask = pygame.mask.Mask(expected_size) + + count = mask.count() + + self.assertEqual(count, expected_count) + self.assertEqual(mask.get_size(), expected_size) + + def test_centroid(self): + """Ensure a filled mask's centroid is correctly calculated.""" + mask = pygame.mask.Mask((5, 7), fill=True) + expected_centroid = mask.get_rect().center + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def test_centroid__empty_mask(self): + """Ensure an empty mask's centroid is correctly calculated.""" + expected_centroid = (0, 0) + expected_size = (101, 103) + mask = pygame.mask.Mask(expected_size) + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + self.assertEqual(mask.get_size(), expected_size) + + def test_centroid__single_row(self): + """Ensure a mask's centroid is correctly calculated + when setting points along a single row.""" + width, height = (5, 7) + mask = pygame.mask.Mask((width, height)) + + for y in range(height): + mask.clear() # Clear for each row. + + for x in range(width): + mask.set_at((x, y)) + expected_centroid = (x // 2, y) + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def test_centroid__two_rows(self): + """Ensure a mask's centroid is correctly calculated + when setting points along two rows.""" + width, height = (5, 7) + mask = pygame.mask.Mask((width, height)) + + # The first row is tested with each of the other rows. + for y in range(1, height): + mask.clear() # Clear for each set of rows. + + for x in range(width): + mask.set_at((x, 0)) + mask.set_at((x, y)) + expected_centroid = (x // 2, y // 2) + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def test_centroid__single_column(self): + """Ensure a mask's centroid is correctly calculated + when setting points along a single column.""" + width, height = (5, 7) + mask = pygame.mask.Mask((width, height)) + + for x in range(width): + mask.clear() # Clear for each column. + + for y in range(height): + mask.set_at((x, y)) + expected_centroid = (x, y // 2) + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def test_centroid__two_columns(self): + """Ensure a mask's centroid is correctly calculated + when setting points along two columns.""" + width, height = (5, 7) + mask = pygame.mask.Mask((width, height)) + + # The first column is tested with each of the other columns. + for x in range(1, width): + mask.clear() # Clear for each set of columns. + + for y in range(height): + mask.set_at((0, y)) + mask.set_at((x, y)) + expected_centroid = (x // 2, y // 2) + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def test_centroid__all_corners(self): + """Ensure a mask's centroid is correctly calculated + when its corners are set.""" + mask = pygame.mask.Mask((5, 7)) + expected_centroid = mask.get_rect().center + + for corner in corners(mask): + mask.set_at(corner) + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def test_centroid__two_corners(self): + """Ensure a mask's centroid is correctly calculated + when only two corners are set.""" + mask = pygame.mask.Mask((5, 7)) + mask_rect = mask.get_rect() + mask_corners = corners(mask) + + for i, corner1 in enumerate(mask_corners): + for corner2 in mask_corners[i + 1 :]: + mask.clear() # Clear for each pair of corners. + mask.set_at(corner1) + mask.set_at(corner2) + + if corner1[0] == corner2[0]: + expected_centroid = (corner1[0], abs(corner1[1] - corner2[1]) // 2) + elif corner1[1] == corner2[1]: + expected_centroid = (abs(corner1[0] - corner2[0]) // 2, corner1[1]) + else: + expected_centroid = mask_rect.center + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def todo_test_angle(self): + """Ensure a mask's orientation angle is correctly calculated.""" + self.fail() + + def test_angle__empty_mask(self): + """Ensure an empty mask's angle is correctly calculated.""" + expected_angle = 0.0 + expected_size = (107, 43) + mask = pygame.mask.Mask(expected_size) + + angle = mask.angle() + + self.assertIsInstance(angle, float) + self.assertAlmostEqual(angle, expected_angle) + self.assertEqual(mask.get_size(), expected_size) + + def test_drawing(self): + """Test fill, clear, invert, draw, erase""" + m = pygame.Mask((100, 100)) + self.assertEqual(m.count(), 0) + + m.fill() + self.assertEqual(m.count(), 10000) + + m2 = pygame.Mask((10, 10), fill=True) + m.erase(m2, (50, 50)) + self.assertEqual(m.count(), 9900) + + m.invert() + self.assertEqual(m.count(), 100) + + m.draw(m2, (0, 0)) + self.assertEqual(m.count(), 200) + + m.clear() + self.assertEqual(m.count(), 0) + + def test_outline(self): + """ """ + + m = pygame.Mask((20, 20)) + self.assertEqual(m.outline(), []) + + m.set_at((10, 10), 1) + self.assertEqual(m.outline(), [(10, 10)]) + + m.set_at((10, 12), 1) + self.assertEqual(m.outline(10), [(10, 10)]) + + m.set_at((11, 11), 1) + self.assertEqual( + m.outline(), [(10, 10), (11, 11), (10, 12), (11, 11), (10, 10)] + ) + self.assertEqual(m.outline(every=2), [(10, 10), (10, 12), (10, 10)]) + + # TODO: Test more corner case outlines. + + def test_convolve__size(self): + sizes = [(1, 1), (31, 31), (32, 32), (100, 100)] + for s1 in sizes: + m1 = pygame.Mask(s1) + for s2 in sizes: + m2 = pygame.Mask(s2) + o = m1.convolve(m2) + + self.assertIsInstance(o, pygame.mask.Mask) + + for i in (0, 1): + self.assertEqual( + o.get_size()[i], m1.get_size()[i] + m2.get_size()[i] - 1 + ) + + def test_convolve__point_identities(self): + """Convolving with a single point is the identity, while convolving a point with something flips it.""" + m = random_mask((100, 100)) + k = pygame.Mask((1, 1)) + k.set_at((0, 0)) + + convolve_mask = m.convolve(k) + + self.assertIsInstance(convolve_mask, pygame.mask.Mask) + assertMaskEqual(self, m, convolve_mask) + + convolve_mask = k.convolve(k.convolve(m)) + + self.assertIsInstance(convolve_mask, pygame.mask.Mask) + assertMaskEqual(self, m, convolve_mask) + + def test_convolve__with_output(self): + """checks that convolution modifies only the correct portion of the output""" + + m = random_mask((10, 10)) + k = pygame.Mask((2, 2)) + k.set_at((0, 0)) + + o = pygame.Mask((50, 50)) + test = pygame.Mask((50, 50)) + + m.convolve(k, o) + test.draw(m, (1, 1)) + + self.assertIsInstance(o, pygame.mask.Mask) + assertMaskEqual(self, o, test) + + o.clear() + test.clear() + + m.convolve(other=k, output=o, offset=Vector2(10, 10)) + test.draw(m, (11, 11)) + + self.assertIsInstance(o, pygame.mask.Mask) + assertMaskEqual(self, o, test) + + def test_convolve__out_of_range(self): + full = pygame.Mask((2, 2), fill=True) + # Tuple of points (out of range) and the expected count for each. + pts_data = (((0, 3), 0), ((0, 2), 3), ((-2, -2), 1), ((-3, -3), 0)) + + for pt, expected_count in pts_data: + convolve_mask = full.convolve(full, None, pt) + + self.assertIsInstance(convolve_mask, pygame.mask.Mask) + self.assertEqual(convolve_mask.count(), expected_count) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_convolve(self): + """Tests the definition of convolution""" + m1 = random_mask((100, 100)) + m2 = random_mask((100, 100)) + conv = m1.convolve(m2) + + self.assertIsInstance(conv, pygame.mask.Mask) + for i in range(conv.get_size()[0]): + for j in range(conv.get_size()[1]): + self.assertEqual( + conv.get_at((i, j)) == 0, m1.overlap(m2, (i - 99, j - 99)) is None + ) + + def _draw_component_pattern_box(self, mask, size, pos, inverse=False): + # Helper method to create/draw a 'box' pattern for testing. + # + # 111 + # 101 3x3 example pattern + # 111 + pattern = pygame.mask.Mask((size, size), fill=True) + pattern.set_at((size // 2, size // 2), 0) + + if inverse: + mask.erase(pattern, pos) + pattern.invert() + else: + mask.draw(pattern, pos) + + return pattern + + def _draw_component_pattern_x(self, mask, size, pos, inverse=False): + # Helper method to create/draw an 'X' pattern for testing. + # + # 101 + # 010 3x3 example pattern + # 101 + pattern = pygame.mask.Mask((size, size)) + + ymax = size - 1 + for y in range(size): + for x in range(size): + if x in [y, ymax - y]: + pattern.set_at((x, y)) + + if inverse: + mask.erase(pattern, pos) + pattern.invert() + else: + mask.draw(pattern, pos) + + return pattern + + def _draw_component_pattern_plus(self, mask, size, pos, inverse=False): + # Helper method to create/draw a '+' pattern for testing. + # + # 010 + # 111 3x3 example pattern + # 010 + pattern = pygame.mask.Mask((size, size)) + + xmid = ymid = size // 2 + for y in range(size): + for x in range(size): + if x == xmid or y == ymid: + pattern.set_at((x, y)) + + if inverse: + mask.erase(pattern, pos) + pattern.invert() + else: + mask.draw(pattern, pos) + + return pattern + + def test_connected_component(self): + """Ensure a mask's connected component is correctly calculated.""" + width, height = 41, 27 + expected_size = (width, height) + original_mask = pygame.mask.Mask(expected_size) + patterns = [] # Patterns and offsets. + + # Draw some connected patterns on the original mask. + offset = (0, 0) + pattern = self._draw_component_pattern_x(original_mask, 3, offset) + patterns.append((pattern, offset)) + + size = 4 + offset = (width - size, 0) + pattern = self._draw_component_pattern_plus(original_mask, size, offset) + patterns.append((pattern, offset)) + + # Make this one the largest connected component. + offset = (width // 2, height // 2) + pattern = self._draw_component_pattern_box(original_mask, 7, offset) + patterns.append((pattern, offset)) + + expected_pattern, expected_offset = patterns[-1] + expected_count = expected_pattern.count() + original_count = sum(p.count() for p, _ in patterns) + + mask = original_mask.connected_component() + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + self.assertEqual( + mask.overlap_area(expected_pattern, expected_offset), expected_count + ) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), original_count) + self.assertEqual(original_mask.get_size(), expected_size) + + for pattern, offset in patterns: + self.assertEqual( + original_mask.overlap_area(pattern, offset), pattern.count() + ) + + def test_connected_component__full_mask(self): + """Ensure a mask's connected component is correctly calculated + when the mask is full. + """ + expected_size = (23, 31) + original_mask = pygame.mask.Mask(expected_size, fill=True) + expected_count = original_mask.count() + + mask = original_mask.connected_component() + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), expected_count) + self.assertEqual(original_mask.get_size(), expected_size) + + def test_connected_component__empty_mask(self): + """Ensure a mask's connected component is correctly calculated + when the mask is empty. + """ + expected_size = (37, 43) + original_mask = pygame.mask.Mask(expected_size) + original_count = original_mask.count() + expected_count = 0 + + mask = original_mask.connected_component() + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), original_count) + self.assertEqual(original_mask.get_size(), expected_size) + + def test_connected_component__one_set_bit(self): + """Ensure a mask's connected component is correctly calculated + when the coordinate's bit is set with a connected component of 1 bit. + """ + width, height = 71, 67 + expected_size = (width, height) + original_mask = pygame.mask.Mask(expected_size, fill=True) + xset, yset = width // 2, height // 2 + set_pos = (xset, yset) + expected_offset = (xset - 1, yset - 1) + + # This isolates the bit at set_pos from all the other bits. + expected_pattern = self._draw_component_pattern_box( + original_mask, 3, expected_offset, inverse=True + ) + expected_count = 1 + original_count = original_mask.count() + + mask = original_mask.connected_component(set_pos) + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + self.assertEqual( + mask.overlap_area(expected_pattern, expected_offset), expected_count + ) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), original_count) + self.assertEqual(original_mask.get_size(), expected_size) + self.assertEqual( + original_mask.overlap_area(expected_pattern, expected_offset), + expected_count, + ) + + def test_connected_component__multi_set_bits(self): + """Ensure a mask's connected component is correctly calculated + when the coordinate's bit is set with a connected component of > 1 bit. + """ + expected_size = (113, 67) + original_mask = pygame.mask.Mask(expected_size) + p_width, p_height = 11, 13 + set_pos = xset, yset = 11, 21 + expected_offset = (xset - 1, yset - 1) + expected_pattern = pygame.mask.Mask((p_width, p_height), fill=True) + + # Make an unsymmetrical pattern. All the set bits need to be connected + # in the resulting pattern for this to work properly. + for y in range(3, p_height): + for x in range(1, p_width): + if x in [y, y - 3, p_width - 4]: + expected_pattern.set_at((x, y), 0) + + expected_count = expected_pattern.count() + original_mask.draw(expected_pattern, expected_offset) + + mask = original_mask.connected_component(set_pos) + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + self.assertEqual( + mask.overlap_area(expected_pattern, expected_offset), expected_count + ) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), expected_count) + self.assertEqual(original_mask.get_size(), expected_size) + self.assertEqual( + original_mask.overlap_area(expected_pattern, expected_offset), + expected_count, + ) + + def test_connected_component__unset_bit(self): + """Ensure a mask's connected component is correctly calculated + when the coordinate's bit is unset. + """ + width, height = 109, 101 + expected_size = (width, height) + original_mask = pygame.mask.Mask(expected_size, fill=True) + unset_pos = (width // 2, height // 2) + original_mask.set_at(unset_pos, 0) + original_count = original_mask.count() + expected_count = 0 + + mask = original_mask.connected_component(unset_pos) + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), expected_count) + self.assertEqual(mask.get_size(), expected_size) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), original_count) + self.assertEqual(original_mask.get_size(), expected_size) + self.assertEqual(original_mask.get_at(unset_pos), 0) + + def test_connected_component__out_of_bounds(self): + """Ensure connected_component() checks bounds.""" + width, height = 19, 11 + original_size = (width, height) + original_mask = pygame.mask.Mask(original_size, fill=True) + original_count = original_mask.count() + + for pos in ((0, -1), (-1, 0), (0, height + 1), (width + 1, 0)): + with self.assertRaises(IndexError): + mask = original_mask.connected_component(pos) + + # Ensure the original mask is unchanged. + self.assertEqual(original_mask.count(), original_count) + self.assertEqual(original_mask.get_size(), original_size) + + def test_connected_components(self): + """ """ + m = pygame.Mask((10, 10)) + + self.assertListEqual(m.connected_components(), []) + + comp = m.connected_component() + + self.assertEqual(m.count(), comp.count()) + + m.set_at((0, 0), 1) + m.set_at((1, 1), 1) + comp = m.connected_component() + comps = m.connected_components() + comps1 = m.connected_components(1) + comps2 = m.connected_components(2) + comps3 = m.connected_components(3) + + self.assertEqual(comp.count(), comps[0].count()) + self.assertEqual(comps1[0].count(), 2) + self.assertEqual(comps2[0].count(), 2) + self.assertListEqual(comps3, []) + + m.set_at((9, 9), 1) + comp = m.connected_component() + comp1 = m.connected_component((1, 1)) + comp2 = m.connected_component((2, 2)) + comps = m.connected_components() + comps1 = m.connected_components(1) + comps2 = m.connected_components(minimum=2) + comps3 = m.connected_components(3) + + self.assertEqual(comp.count(), 2) + self.assertEqual(comp1.count(), 2) + self.assertEqual(comp2.count(), 0) + self.assertEqual(len(comps), 2) + self.assertEqual(len(comps1), 2) + self.assertEqual(len(comps2), 1) + self.assertEqual(len(comps3), 0) + + for mask in comps: + self.assertIsInstance(mask, pygame.mask.Mask) + + def test_connected_components__negative_min_with_empty_mask(self): + """Ensures connected_components() properly handles negative min values + when the mask is empty. + + Negative and zero values for the min parameter (minimum number of bits + per connected component) equate to setting it to one. + """ + expected_comps = [] + mask_count = 0 + mask_size = (65, 13) + mask = pygame.mask.Mask(mask_size) + + connected_comps = mask.connected_components(-1) + + self.assertListEqual(connected_comps, expected_comps) + + # Ensure the original mask is unchanged. + self.assertEqual(mask.count(), mask_count) + self.assertEqual(mask.get_size(), mask_size) + + def test_connected_components__negative_min_with_full_mask(self): + """Ensures connected_components() properly handles negative min values + when the mask is full. + + Negative and zero values for the min parameter (minimum number of bits + per connected component) equate to setting it to one. + """ + mask_size = (64, 11) + mask = pygame.mask.Mask(mask_size, fill=True) + mask_count = mask.count() + expected_len = 1 + + connected_comps = mask.connected_components(-2) + + self.assertEqual(len(connected_comps), expected_len) + assertMaskEqual(self, connected_comps[0], mask) + + # Ensure the original mask is unchanged. + self.assertEqual(mask.count(), mask_count) + self.assertEqual(mask.get_size(), mask_size) + + def test_connected_components__negative_min_with_some_bits_set(self): + """Ensures connected_components() properly handles negative min values + when the mask has some bits set. + + Negative and zero values for the min parameter (minimum number of bits + per connected component) equate to setting it to one. + """ + mask_size = (64, 12) + mask = pygame.mask.Mask(mask_size) + expected_comps = {} + + # Set the corners and the center positions. A new expected component + # mask is created for each point. + for corner in corners(mask): + mask.set_at(corner) + + new_mask = pygame.mask.Mask(mask_size) + new_mask.set_at(corner) + expected_comps[corner] = new_mask + + center = (mask_size[0] // 2, mask_size[1] // 2) + mask.set_at(center) + + new_mask = pygame.mask.Mask(mask_size) + new_mask.set_at(center) + expected_comps[center] = new_mask + mask_count = mask.count() + + connected_comps = mask.connected_components(-3) + + self.assertEqual(len(connected_comps), len(expected_comps)) + + for comp in connected_comps: + # Since the masks in the connected component list can be in any + # order, loop the expected components to find its match. + found = False + + for pt in tuple(expected_comps.keys()): + if comp.get_at(pt): + found = True + assertMaskEqual(self, comp, expected_comps[pt]) + del expected_comps[pt] # Entry removed so it isn't reused. + break + + self.assertTrue(found, "missing component for pt={}".format(pt)) + + # Ensure the original mask is unchanged. + self.assertEqual(mask.count(), mask_count) + self.assertEqual(mask.get_size(), mask_size) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_get_bounding_rects(self): + """Ensures get_bounding_rects works correctly.""" + # Create masks with different set point groups. Each group of + # connected set points will be contained in its own bounding rect. + # Diagonal points are considered connected. + mask_data = [] # [((size), ((rect1_pts), ...)), ...] + + # Mask 1: + # |0123456789 + # -+---------- + # 0|1100000000 + # 1|1000000000 + # 2|0000000000 + # 3|1001000000 + # 4|0000000000 + # 5|0000000000 + # 6|0000000000 + # 7|0000000000 + # 8|0000000000 + # 9|0000000000 + mask_data.append( + ( + (10, 10), # size + # Points to set for the 3 bounding rects. + (((0, 0), (1, 0), (0, 1)), ((0, 3),), ((3, 3),)), # rect1 # rect2 + ) + ) # rect3 + + # Mask 2: + # |0123 + # -+---- + # 0|1100 + # 1|1111 + mask_data.append( + ( + (4, 2), # size + # Points to set for the 1 bounding rect. + (((0, 0), (1, 0), (0, 1), (1, 1), (2, 1), (3, 1)),), + ) + ) + + # Mask 3: + # |01234 + # -+----- + # 0|00100 + # 1|01110 + # 2|00100 + mask_data.append( + ( + (5, 3), # size + # Points to set for the 1 bounding rect. + (((2, 0), (1, 1), (2, 1), (3, 1), (2, 2)),), + ) + ) + + # Mask 4: + # |01234 + # -+----- + # 0|00010 + # 1|00100 + # 2|01000 + mask_data.append( + ( + (5, 3), # size + # Points to set for the 1 bounding rect. + (((3, 0), (2, 1), (1, 2)),), + ) + ) + + # Mask 5: + # |01234 + # -+----- + # 0|00011 + # 1|11111 + mask_data.append( + ( + (5, 2), # size + # Points to set for the 1 bounding rect. + (((3, 0), (4, 0), (0, 1), (1, 1), (2, 1), (3, 1)),), + ) + ) + + # Mask 6: + # |01234 + # -+----- + # 0|10001 + # 1|00100 + # 2|10001 + mask_data.append( + ( + (5, 3), # size + # Points to set for the 5 bounding rects. + ( + ((0, 0),), # rect1 + ((4, 0),), # rect2 + ((2, 1),), # rect3 + ((0, 2),), # rect4 + ((4, 2),), + ), + ) + ) # rect5 + + for size, rect_point_tuples in mask_data: + rects = [] + mask = pygame.Mask(size) + + for rect_points in rect_point_tuples: + rects.append(create_bounding_rect(rect_points)) + for pt in rect_points: + mask.set_at(pt) + + expected_rects = sorted(rects, key=tuple) + + rects = mask.get_bounding_rects() + + self.assertListEqual( + sorted(mask.get_bounding_rects(), key=tuple), + expected_rects, + "size={}".format(size), + ) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_to_surface(self): + """Ensures empty and full masks can be drawn onto surfaces.""" + expected_ref_count = 3 + size = (33, 65) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + test_fills = ((pygame.Color("white"), True), (pygame.Color("black"), False)) + + for expected_color, fill in test_fills: + surface.fill(surface_color) + mask = pygame.mask.Mask(size, fill=fill) + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__create_surface(self): + """Ensures empty and full masks can be drawn onto a created surface.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + size = (33, 65) + test_fills = ((pygame.Color("white"), True), (pygame.Color("black"), False)) + + for expected_color, fill in test_fills: + mask = pygame.mask.Mask(size, fill=fill) + + for use_arg in (True, False): + if use_arg: + to_surface = mask.to_surface(None) + else: + to_surface = mask.to_surface() + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__surface_param(self): + """Ensures to_surface accepts a surface arg/kwarg.""" + expected_ref_count = 4 + expected_color = pygame.Color("white") + surface_color = pygame.Color("red") + size = (5, 3) + mask = pygame.mask.Mask(size, fill=True) + surface = pygame.Surface(size) + kwargs = {"surface": surface} + + for use_kwargs in (True, False): + surface.fill(surface_color) + + if use_kwargs: + to_surface = mask.to_surface(**kwargs) + else: + to_surface = mask.to_surface(kwargs["surface"]) + + self.assertIs(to_surface, surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__setsurface_param(self): + """Ensures to_surface accepts a setsurface arg/kwarg.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + expected_color = pygame.Color("red") + size = (5, 3) + mask = pygame.mask.Mask(size, fill=True) + setsurface = pygame.Surface(size, expected_flag, expected_depth) + setsurface.fill(expected_color) + kwargs = {"setsurface": setsurface} + + for use_kwargs in (True, False): + if use_kwargs: + to_surface = mask.to_surface(**kwargs) + else: + to_surface = mask.to_surface(None, kwargs["setsurface"]) + + self.assertIsInstance(to_surface, pygame.Surface) + + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__unsetsurface_param(self): + """Ensures to_surface accepts a unsetsurface arg/kwarg.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + expected_color = pygame.Color("red") + size = (5, 3) + mask = pygame.mask.Mask(size) + unsetsurface = pygame.Surface(size, expected_flag, expected_depth) + unsetsurface.fill(expected_color) + kwargs = {"unsetsurface": unsetsurface} + + for use_kwargs in (True, False): + if use_kwargs: + to_surface = mask.to_surface(**kwargs) + else: + to_surface = mask.to_surface(None, None, kwargs["unsetsurface"]) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__setcolor_param(self): + """Ensures to_surface accepts a setcolor arg/kwarg.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + expected_color = pygame.Color("red") + size = (5, 3) + mask = pygame.mask.Mask(size, fill=True) + kwargs = {"setcolor": expected_color} + + for use_kwargs in (True, False): + if use_kwargs: + to_surface = mask.to_surface(**kwargs) + else: + to_surface = mask.to_surface(None, None, None, kwargs["setcolor"]) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__setcolor_default(self): + """Ensures the default setcolor is correct.""" + expected_color = pygame.Color("white") + size = (3, 7) + mask = pygame.mask.Mask(size, fill=True) + + to_surface = mask.to_surface( + surface=None, setsurface=None, unsetsurface=None, unsetcolor=None + ) + + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__unsetcolor_param(self): + """Ensures to_surface accepts a unsetcolor arg/kwarg.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + expected_color = pygame.Color("red") + size = (5, 3) + mask = pygame.mask.Mask(size) + kwargs = {"unsetcolor": expected_color} + + for use_kwargs in (True, False): + if use_kwargs: + to_surface = mask.to_surface(**kwargs) + else: + to_surface = mask.to_surface( + None, None, None, None, kwargs["unsetcolor"] + ) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__unsetcolor_default(self): + """Ensures the default unsetcolor is correct.""" + expected_color = pygame.Color("black") + size = (3, 7) + mask = pygame.mask.Mask(size) + + to_surface = mask.to_surface( + surface=None, setsurface=None, unsetsurface=None, setcolor=None + ) + + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__dest_param(self): + """Ensures to_surface accepts a dest arg/kwarg.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + default_surface_color = (0, 0, 0, 0) + default_unsetcolor = pygame.Color("black") + dest = (0, 0) + size = (5, 3) + mask = pygame.mask.Mask(size) + kwargs = {"dest": dest} + + for use_kwargs in (True, False): + if use_kwargs: + expected_color = default_unsetcolor + + to_surface = mask.to_surface(**kwargs) + else: + expected_color = default_surface_color + + to_surface = mask.to_surface( + None, None, None, None, None, kwargs["dest"] + ) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__dest_default(self): + """Ensures the default dest is correct.""" + expected_color = pygame.Color("white") + surface_color = pygame.Color("red") + + mask_size = (3, 2) + mask = pygame.mask.Mask(mask_size, fill=True) + mask_rect = mask.get_rect() + + # Make the surface bigger than the mask. + surf_size = (mask_size[0] + 2, mask_size[1] + 1) + surface = pygame.Surface(surf_size, SRCALPHA, 32) + surface.fill(surface_color) + + to_surface = mask.to_surface( + surface, setsurface=None, unsetsurface=None, unsetcolor=None + ) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), surf_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + @unittest.expectedFailure + def test_to_surface__area_param(self): + """Ensures to_surface accepts an area arg/kwarg.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + default_surface_color = (0, 0, 0, 0) + default_unsetcolor = pygame.Color("black") + size = (5, 3) + mask = pygame.mask.Mask(size) + kwargs = {"area": mask.get_rect()} + + for use_kwargs in (True, False): + if use_kwargs: + expected_color = default_unsetcolor + + to_surface = mask.to_surface(**kwargs) + else: + expected_color = default_surface_color + + to_surface = mask.to_surface( + None, None, None, None, None, (0, 0), kwargs["area"] + ) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__area_default(self): + """Ensures the default area is correct.""" + expected_color = pygame.Color("white") + surface_color = pygame.Color("red") + + mask_size = (3, 2) + mask = pygame.mask.Mask(mask_size, fill=True) + mask_rect = mask.get_rect() + + # Make the surface bigger than the mask. The default area is the full + # area of the mask. + surf_size = (mask_size[0] + 2, mask_size[1] + 1) + surface = pygame.Surface(surf_size, SRCALPHA, 32) + surface.fill(surface_color) + + to_surface = mask.to_surface( + surface, setsurface=None, unsetsurface=None, unsetcolor=None + ) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), surf_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + def test_to_surface__kwargs(self): + """Ensures to_surface accepts the correct kwargs.""" + expected_color = pygame.Color("white") + size = (5, 3) + mask = pygame.mask.Mask(size, fill=True) + surface = pygame.Surface(size) + surface_color = pygame.Color("red") + setsurface = surface.copy() + setsurface.fill(expected_color) + + test_data = ( + (None, None), # None entry allows loop to test all kwargs on first pass. + ("dest", (0, 0)), + ("unsetcolor", pygame.Color("yellow")), + ("setcolor", expected_color), + ("unsetsurface", surface.copy()), + ("setsurface", setsurface), + ("surface", surface), + ) + + kwargs = dict(test_data) + + for name, _ in test_data: + kwargs.pop(name) + surface.fill(surface_color) # Clear for each test. + + to_surface = mask.to_surface(**kwargs) + + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__kwargs_create_surface(self): + """Ensures to_surface accepts the correct kwargs + when creating a surface. + """ + expected_color = pygame.Color("black") + size = (5, 3) + mask = pygame.mask.Mask(size) + setsurface = pygame.Surface(size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + unsetsurface = setsurface.copy() + unsetsurface.fill(expected_color) + + test_data = ( + (None, None), # None entry allows loop to test all kwargs on first pass. + ("dest", (0, 0)), + ("unsetcolor", expected_color), + ("setcolor", pygame.Color("yellow")), + ("unsetsurface", unsetsurface), + ("setsurface", setsurface), + ("surface", None), + ) + kwargs = dict(test_data) + + for name, _ in test_data: + kwargs.pop(name) + + to_surface = mask.to_surface(**kwargs) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__kwargs_order_independent(self): + """Ensures to_surface kwargs are not order dependent.""" + expected_color = pygame.Color("blue") + size = (3, 2) + mask = pygame.mask.Mask(size, fill=True) + surface = pygame.Surface(size) + + to_surface = mask.to_surface( + dest=(0, 0), + setcolor=expected_color, + unsetcolor=None, + surface=surface, + unsetsurface=pygame.Surface(size), + setsurface=None, + ) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__args_invalid_types(self): + """Ensures to_surface detects invalid kwarg types.""" + size = (3, 2) + mask = pygame.mask.Mask(size, fill=True) + invalid_surf = pygame.Color("green") + invalid_color = pygame.Surface(size) + + with self.assertRaises(TypeError): + # Invalid dest. + mask.to_surface(None, None, None, None, None, (0,)) + + with self.assertRaises(TypeError): + # Invalid unsetcolor. + mask.to_surface(None, None, None, None, invalid_color) + + with self.assertRaises(TypeError): + # Invalid setcolor. + mask.to_surface(None, None, None, invalid_color, None) + + with self.assertRaises(TypeError): + # Invalid unsetsurface. + mask.to_surface(None, None, invalid_surf, None, None) + + with self.assertRaises(TypeError): + # Invalid setsurface. + mask.to_surface(None, invalid_surf, None, None, None) + + with self.assertRaises(TypeError): + # Invalid surface. + mask.to_surface(invalid_surf, None, None, None, None) + + def test_to_surface__kwargs_invalid_types(self): + """Ensures to_surface detects invalid kwarg types.""" + size = (3, 2) + mask = pygame.mask.Mask(size) + + valid_kwargs = { + "surface": pygame.Surface(size), + "setsurface": pygame.Surface(size), + "unsetsurface": pygame.Surface(size), + "setcolor": pygame.Color("green"), + "unsetcolor": pygame.Color("green"), + "dest": (0, 0), + } + + invalid_kwargs = { + "surface": (1, 2, 3, 4), + "setsurface": pygame.Color("green"), + "unsetsurface": ((1, 2), (2, 1)), + "setcolor": pygame.Mask((1, 2)), + "unsetcolor": pygame.Surface((2, 2)), + "dest": (0, 0, 0), + } + + kwarg_order = ( + "surface", + "setsurface", + "unsetsurface", + "setcolor", + "unsetcolor", + "dest", + ) + + for kwarg in kwarg_order: + kwargs = dict(valid_kwargs) + kwargs[kwarg] = invalid_kwargs[kwarg] + + with self.assertRaises(TypeError): + mask.to_surface(**kwargs) + + def test_to_surface__kwargs_invalid_name(self): + """Ensures to_surface detects invalid kwarg names.""" + mask = pygame.mask.Mask((3, 2)) + kwargs = {"setcolour": pygame.Color("red")} + + with self.assertRaises(TypeError): + mask.to_surface(**kwargs) + + def test_to_surface__args_and_kwargs(self): + """Ensures to_surface accepts a combination of args/kwargs""" + size = (5, 3) + + surface_color = pygame.Color("red") + setsurface_color = pygame.Color("yellow") + unsetsurface_color = pygame.Color("blue") + setcolor = pygame.Color("green") + unsetcolor = pygame.Color("cyan") + + surface = pygame.Surface(size, SRCALPHA, 32) + setsurface = surface.copy() + unsetsurface = surface.copy() + + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + mask = pygame.mask.Mask(size, fill=True) + expected_color = setsurface_color + + test_data = ( + (None, None), # None entry allows loop to test all kwargs on first pass. + ("surface", surface), + ("setsurface", setsurface), + ("unsetsurface", unsetsurface), + ("setcolor", setcolor), + ("unsetcolor", unsetcolor), + ("dest", (0, 0)), + ) + + args = [] + kwargs = dict(test_data) + + # Loop gradually moves the kwargs to args. + for name, value in test_data: + if name is not None: + args.append(value) + kwargs.pop(name) + + surface.fill(surface_color) + + to_surface = mask.to_surface(*args, **kwargs) + + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__valid_setcolor_formats(self): + """Ensures to_surface handles valid setcolor formats correctly.""" + size = (5, 3) + mask = pygame.mask.Mask(size, fill=True) + surface = pygame.Surface(size, SRCALPHA, 32) + expected_color = pygame.Color("green") + test_colors = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(expected_color), + expected_color, + "green", + "#00FF00FF", + "0x00FF00FF", + ) + + for setcolor in test_colors: + to_surface = mask.to_surface(setcolor=setcolor) + + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__valid_unsetcolor_formats(self): + """Ensures to_surface handles valid unsetcolor formats correctly.""" + size = (5, 3) + mask = pygame.mask.Mask(size) + surface = pygame.Surface(size, SRCALPHA, 32) + expected_color = pygame.Color("green") + test_colors = ( + (0, 255, 0), + (0, 255, 0, 255), + surface.map_rgb(expected_color), + expected_color, + "green", + "#00FF00FF", + "0x00FF00FF", + ) + + for unsetcolor in test_colors: + to_surface = mask.to_surface(unsetcolor=unsetcolor) + + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__invalid_setcolor_formats(self): + """Ensures to_surface handles invalid setcolor formats correctly.""" + mask = pygame.mask.Mask((5, 3)) + + for setcolor in ("green color", "#00FF00FF0", "0x00FF00FF0", (1, 2)): + with self.assertRaises(ValueError): + mask.to_surface(setcolor=setcolor) + + for setcolor in (pygame.Surface((1, 2)), pygame.Mask((2, 1)), 1.1): + with self.assertRaises(TypeError): + mask.to_surface(setcolor=setcolor) + + def test_to_surface__invalid_unsetcolor_formats(self): + """Ensures to_surface handles invalid unsetcolor formats correctly.""" + mask = pygame.mask.Mask((5, 3)) + + for unsetcolor in ("green color", "#00FF00FF0", "0x00FF00FF0", (1, 2)): + with self.assertRaises(ValueError): + mask.to_surface(unsetcolor=unsetcolor) + + for unsetcolor in (pygame.Surface((1, 2)), pygame.Mask((2, 1)), 1.1): + with self.assertRaises(TypeError): + mask.to_surface(unsetcolor=unsetcolor) + + def test_to_surface__valid_dest_formats(self): + """Ensures to_surface handles valid dest formats correctly.""" + expected_color = pygame.Color("white") + mask = pygame.mask.Mask((3, 5), fill=True) + dests = ( + (0, 0), + [0, 0], + Vector2(0, 0), + (0, 0, 100, 100), + pygame.Rect((0, 0), (10, 10)), + ) + + for dest in dests: + to_surface = mask.to_surface(dest=dest) + + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__invalid_dest_formats(self): + """Ensures to_surface handles invalid dest formats correctly.""" + mask = pygame.mask.Mask((3, 5)) + invalid_dests = ( + (0,), # Incorrect size. + (0, 0, 0), # Incorrect size. + set([0, 1]), # Incorrect type. + {0: 1}, # Incorrect type. + Rect, + ) # Incorrect type. + + for dest in invalid_dests: + with self.assertRaises(TypeError): + mask.to_surface(dest=dest) + + def test_to_surface__negative_sized_dest_rect(self): + """Ensures to_surface correctly handles negative sized dest rects.""" + expected_color = pygame.Color("white") + mask = pygame.mask.Mask((3, 5), fill=True) + dests = ( + pygame.Rect((0, 0), (10, -10)), + pygame.Rect((0, 0), (-10, 10)), + pygame.Rect((0, 0), (-10, -10)), + ) + + for dest in dests: + to_surface = mask.to_surface(dest=dest) + + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__zero_sized_dest_rect(self): + """Ensures to_surface correctly handles zero sized dest rects.""" + expected_color = pygame.Color("white") + mask = pygame.mask.Mask((3, 5), fill=True) + dests = ( + pygame.Rect((0, 0), (0, 10)), + pygame.Rect((0, 0), (10, 0)), + pygame.Rect((0, 0), (0, 0)), + ) + + for dest in dests: + to_surface = mask.to_surface(dest=dest) + + assertSurfaceFilled(self, to_surface, expected_color) + + @unittest.expectedFailure + def test_to_surface__valid_area_formats(self): + """Ensures to_surface handles valid area formats correctly.""" + size = (3, 5) + surface_color = pygame.Color("red") + expected_color = pygame.Color("white") + surface = pygame.Surface(size) + mask = pygame.mask.Mask(size, fill=True) + area_pos = (0, 0) + area_size = (2, 1) + areas = ( + (area_pos[0], area_pos[1], area_size[0], area_size[1]), + (area_pos, area_size), + (area_pos, list(area_size)), + (list(area_pos), area_size), + (list(area_pos), list(area_size)), + [area_pos[0], area_pos[1], area_size[0], area_size[1]], + [area_pos, area_size], + [area_pos, list(area_size)], + [list(area_pos), area_size], + [list(area_pos), list(area_size)], + pygame.Rect(area_pos, area_size), + ) + + for area in areas: + surface.fill(surface_color) + area_rect = pygame.Rect(area) + + to_surface = mask.to_surface(surface, area=area) + + assertSurfaceFilled(self, to_surface, expected_color, area_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, area_rect) + + @unittest.expectedFailure + def test_to_surface__invalid_area_formats(self): + """Ensures to_surface handles invalid area formats correctly.""" + mask = pygame.mask.Mask((3, 5)) + invalid_areas = ( + (0,), # Incorrect size. + (0, 0), # Incorrect size. + (0, 0, 1), # Incorrect size. + ((0, 0), (1,)), # Incorrect size. + ((0,), (1, 1)), # Incorrect size. + set([0, 1, 2, 3]), # Incorrect type. + {0: 1, 2: 3}, # Incorrect type. + Rect, # Incorrect type. + ) + + for area in invalid_areas: + with self.assertRaisesRegex(TypeError, "invalid area argument"): + unused_to_surface = mask.to_surface(area=area) + + @unittest.expectedFailure + def test_to_surface__negative_sized_area_rect(self): + """Ensures to_surface correctly handles negative sized area rects.""" + size = (3, 5) + surface_color = pygame.Color("red") + expected_color = pygame.Color("white") + surface = pygame.Surface(size) + mask = pygame.mask.Mask(size) + mask.set_at((0, 0)) + + # These rects should cause position (0, 0) of the mask to be drawn. + areas = ( + pygame.Rect((0, 1), (1, -1)), + pygame.Rect((1, 0), (-1, 1)), + pygame.Rect((1, 1), (-1, -1)), + ) + + for area in areas: + surface.fill(surface_color) + + to_surface = mask.to_surface(surface, area=area) + + assertSurfaceFilled(self, to_surface, expected_color, area) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, area) + + @unittest.expectedFailure + def test_to_surface__zero_sized_area_rect(self): + """Ensures to_surface correctly handles zero sized area rects.""" + size = (3, 5) + expected_color = pygame.Color("red") + surface = pygame.Surface(size) + mask = pygame.mask.Mask(size, fill=True) + + # Zero sized rect areas should cause none of the mask to be drawn. + areas = ( + pygame.Rect((0, 0), (0, 1)), + pygame.Rect((0, 0), (1, 0)), + pygame.Rect((0, 0), (0, 0)), + ) + + for area in areas: + surface.fill(expected_color) + + to_surface = mask.to_surface(surface, area=area) + + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__default_surface_with_param_combinations(self): + """Ensures to_surface works with a default surface value + and combinations of other parameters. + + This tests many different parameter combinations with full and empty + masks. + """ + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + size = (5, 3) + dest = (0, 0) + + default_surface_color = (0, 0, 0, 0) + setsurface_color = pygame.Color("yellow") + unsetsurface_color = pygame.Color("blue") + setcolor = pygame.Color("green") + unsetcolor = pygame.Color("cyan") + + setsurface = pygame.Surface(size, expected_flag, expected_depth) + unsetsurface = setsurface.copy() + + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + kwargs = { + "setsurface": None, + "unsetsurface": None, + "setcolor": None, + "unsetcolor": None, + "dest": None, + } + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + + # Test different combinations of parameters. + for setsurface_param in (setsurface, None): + kwargs["setsurface"] = setsurface_param + + for unsetsurface_param in (unsetsurface, None): + kwargs["unsetsurface"] = unsetsurface_param + + for setcolor_param in (setcolor, None): + kwargs["setcolor"] = setcolor_param + + for unsetcolor_param in (unsetcolor, None): + kwargs["unsetcolor"] = unsetcolor_param + + for dest_param in (dest, None): + if dest_param is None: + kwargs.pop("dest", None) + else: + kwargs["dest"] = dest_param + + if fill: + if setsurface_param is not None: + expected_color = setsurface_color + elif setcolor_param is not None: + expected_color = setcolor + else: + expected_color = default_surface_color + else: + if unsetsurface_param is not None: + expected_color = unsetsurface_color + elif unsetcolor_param is not None: + expected_color = unsetcolor + else: + expected_color = default_surface_color + + to_surface = mask.to_surface(**kwargs) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual( + sys.getrefcount(to_surface), expected_ref_count + ) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual( + to_surface.get_bitsize(), expected_depth + ) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__surface_with_param_combinations(self): + """Ensures to_surface works with a surface value + and combinations of other parameters. + + This tests many different parameter combinations with full and empty + masks. + """ + expected_ref_count = 4 + expected_flag = SRCALPHA + expected_depth = 32 + size = (5, 3) + dest = (0, 0) + + surface_color = pygame.Color("red") + setsurface_color = pygame.Color("yellow") + unsetsurface_color = pygame.Color("blue") + setcolor = pygame.Color("green") + unsetcolor = pygame.Color("cyan") + + surface = pygame.Surface(size, expected_flag, expected_depth) + setsurface = surface.copy() + unsetsurface = surface.copy() + + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + kwargs = { + "surface": surface, + "setsurface": None, + "unsetsurface": None, + "setcolor": None, + "unsetcolor": None, + "dest": None, + } + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + + # Test different combinations of parameters. + for setsurface_param in (setsurface, None): + kwargs["setsurface"] = setsurface_param + + for unsetsurface_param in (unsetsurface, None): + kwargs["unsetsurface"] = unsetsurface_param + + for setcolor_param in (setcolor, None): + kwargs["setcolor"] = setcolor_param + + for unsetcolor_param in (unsetcolor, None): + kwargs["unsetcolor"] = unsetcolor_param + surface.fill(surface_color) # Clear for each test. + + for dest_param in (dest, None): + if dest_param is None: + kwargs.pop("dest", None) + else: + kwargs["dest"] = dest_param + + if fill: + if setsurface_param is not None: + expected_color = setsurface_color + elif setcolor_param is not None: + expected_color = setcolor + else: + expected_color = surface_color + else: + if unsetsurface_param is not None: + expected_color = unsetsurface_color + elif unsetcolor_param is not None: + expected_color = unsetcolor + else: + expected_color = surface_color + + to_surface = mask.to_surface(**kwargs) + + self.assertIs(to_surface, surface) + if not IS_PYPY: + self.assertEqual( + sys.getrefcount(to_surface), expected_ref_count + ) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual( + to_surface.get_bitsize(), expected_depth + ) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__set_and_unset_bits(self): + """Ensures that to_surface works correctly with with set/unset bits + when using the defaults for setcolor and unsetcolor. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + width, height = size = (10, 20) + mask = pygame.mask.Mask(size) + mask_rect = mask.get_rect() + + surface = pygame.Surface(size) + surface_color = pygame.Color("red") + + # Create a checkerboard pattern of set/unset bits. + for pos in ((x, y) for x in range(width) for y in range(x & 1, height, 2)): + mask.set_at(pos) + + # Test different dest values. + for dest in self.ORIGIN_OFFSETS: + mask_rect.topleft = dest + surface.fill(surface_color) + + to_surface = mask.to_surface(surface, dest=dest) + + to_surface.lock() # Lock for possible speed up. + for pos in ((x, y) for x in range(width) for y in range(height)): + mask_pos = (pos[0] - dest[0], pos[1] - dest[1]) + if not mask_rect.collidepoint(pos): + expected_color = surface_color + elif mask.get_at(mask_pos): + expected_color = default_setcolor + else: + expected_color = default_unsetcolor + + self.assertEqual(to_surface.get_at(pos), expected_color, (dest, pos)) + to_surface.unlock() + + def test_to_surface__set_and_unset_bits_with_setsurface_unsetsurface(self): + """Ensures that to_surface works correctly with with set/unset bits + when using setsurface and unsetsurface. + """ + width, height = size = (10, 20) + mask = pygame.mask.Mask(size) + mask_rect = mask.get_rect() + + surface = pygame.Surface(size) + surface_color = pygame.Color("red") + + setsurface = surface.copy() + setsurface_color = pygame.Color("green") + setsurface.fill(setsurface_color) + + unsetsurface = surface.copy() + unsetsurface_color = pygame.Color("blue") + unsetsurface.fill(unsetsurface_color) + + # Create a checkerboard pattern of set/unset bits. + for pos in ((x, y) for x in range(width) for y in range(x & 1, height, 2)): + mask.set_at(pos) + + # Test different dest values. + for dest in self.ORIGIN_OFFSETS: + mask_rect.topleft = dest + + # Tests the color parameters set to None and also as their + # default values. Should have no effect as they are not being + # used, but this exercises different to_surface() code. + for disable_color_params in (True, False): + surface.fill(surface_color) # Clear for each test. + + if disable_color_params: + to_surface = mask.to_surface( + surface, + dest=dest, + setsurface=setsurface, + unsetsurface=unsetsurface, + setcolor=None, + unsetcolor=None, + ) + else: + to_surface = mask.to_surface( + surface, + dest=dest, + setsurface=setsurface, + unsetsurface=unsetsurface, + ) + + to_surface.lock() # Lock for possible speed up. + + for pos in ((x, y) for x in range(width) for y in range(height)): + mask_pos = (pos[0] - dest[0], pos[1] - dest[1]) + + if not mask_rect.collidepoint(pos): + expected_color = surface_color + elif mask.get_at(mask_pos): + expected_color = setsurface_color + else: + expected_color = unsetsurface_color + + self.assertEqual(to_surface.get_at(pos), expected_color) + to_surface.unlock() + + def test_to_surface__surface_narrower_than_mask(self): + """Ensures that surfaces narrower than the mask work correctly. + + For this test the surface's width is less than the mask's width. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 20) + narrow_size = (6, 20) + + surface = pygame.Surface(narrow_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), narrow_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__setsurface_narrower_than_mask(self): + """Ensures that setsurfaces narrower than the mask work correctly. + + For this test the setsurface's width is less than the mask's width. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 20) + narrow_size = (6, 20) + + setsurface = pygame.Surface(narrow_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + setsurface_rect = setsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, setsurface_color, setsurface_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_setcolor, setsurface_rect + ) + else: + assertSurfaceFilled(self, to_surface, default_unsetcolor) + + def test_to_surface__unsetsurface_narrower_than_mask(self): + """Ensures that unsetsurfaces narrower than the mask work correctly. + + For this test the unsetsurface's width is less than the mask's width. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 20) + narrow_size = (6, 20) + + unsetsurface = pygame.Surface(narrow_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + unsetsurface_rect = unsetsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, default_setcolor) + else: + assertSurfaceFilled( + self, to_surface, unsetsurface_color, unsetsurface_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_unsetcolor, unsetsurface_rect + ) + + def test_to_surface__setsurface_narrower_than_mask_and_colors_none(self): + """Ensures that setsurfaces narrower than the mask work correctly + when setcolor and unsetcolor are set to None. + + For this test the setsurface's width is less than the mask's width. + """ + default_surface_color = (0, 0, 0, 0) + mask_size = (10, 20) + narrow_size = (6, 20) + + setsurface = pygame.Surface(narrow_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + setsurface_rect = setsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface( + setsurface=setsurface, setcolor=None, unsetcolor=None + ) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, setsurface_color, setsurface_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_surface_color, setsurface_rect + ) + else: + assertSurfaceFilled(self, to_surface, default_surface_color) + + def test_to_surface__unsetsurface_narrower_than_mask_and_colors_none(self): + """Ensures that unsetsurfaces narrower than the mask work correctly + when setcolor and unsetcolor are set to None. + + For this test the unsetsurface's width is less than the mask's width. + """ + default_surface_color = (0, 0, 0, 0) + mask_size = (10, 20) + narrow_size = (6, 20) + + unsetsurface = pygame.Surface(narrow_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + unsetsurface_rect = unsetsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface( + unsetsurface=unsetsurface, setcolor=None, unsetcolor=None + ) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, default_surface_color) + else: + assertSurfaceFilled( + self, to_surface, unsetsurface_color, unsetsurface_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_surface_color, unsetsurface_rect + ) + + def test_to_surface__surface_wider_than_mask(self): + """Ensures that surfaces wider than the mask work correctly. + + For this test the surface's width is greater than the mask's width. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (6, 15) + wide_size = (11, 15) + + surface = pygame.Surface(wide_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + mask_rect = mask.get_rect() + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), wide_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + def test_to_surface__setsurface_wider_than_mask(self): + """Ensures that setsurfaces wider than the mask work correctly. + + For this test the setsurface's width is greater than the mask's width. + """ + default_unsetcolor = pygame.Color("black") + mask_size = (6, 15) + wide_size = (11, 15) + + setsurface = pygame.Surface(wide_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + expected_color = setsurface_color if fill else default_unsetcolor + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__unsetsurface_wider_than_mask(self): + """Ensures that unsetsurfaces wider than the mask work correctly. + + For this test the unsetsurface's width is greater than the mask's + width. + """ + default_setcolor = pygame.Color("white") + mask_size = (6, 15) + wide_size = (11, 15) + + unsetsurface = pygame.Surface(wide_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + expected_color = default_setcolor if fill else unsetsurface_color + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__surface_shorter_than_mask(self): + """Ensures that surfaces shorter than the mask work correctly. + + For this test the surface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 11) + short_size = (10, 6) + + surface = pygame.Surface(short_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), short_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__setsurface_shorter_than_mask(self): + """Ensures that setsurfaces shorter than the mask work correctly. + + For this test the setsurface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 11) + short_size = (10, 6) + + setsurface = pygame.Surface(short_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + setsurface_rect = setsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, setsurface_color, setsurface_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_setcolor, setsurface_rect + ) + else: + assertSurfaceFilled(self, to_surface, default_unsetcolor) + + def test_to_surface__unsetsurface_shorter_than_mask(self): + """Ensures that unsetsurfaces shorter than the mask work correctly. + + For this test the unsetsurface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 11) + short_size = (10, 6) + + unsetsurface = pygame.Surface(short_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + unsetsurface_rect = unsetsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, default_setcolor) + else: + assertSurfaceFilled( + self, to_surface, unsetsurface_color, unsetsurface_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_unsetcolor, unsetsurface_rect + ) + + def test_to_surface__setsurface_shorter_than_mask_and_colors_none(self): + """Ensures that setsurfaces shorter than the mask work correctly + when setcolor and unsetcolor are set to None. + + For this test the setsurface's height is less than the mask's height. + """ + default_surface_color = (0, 0, 0, 0) + mask_size = (10, 11) + short_size = (10, 6) + + setsurface = pygame.Surface(short_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + setsurface_rect = setsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface( + setsurface=setsurface, setcolor=None, unsetcolor=None + ) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, setsurface_color, setsurface_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_surface_color, setsurface_rect + ) + else: + assertSurfaceFilled(self, to_surface, default_surface_color) + + def test_to_surface__unsetsurface_shorter_than_mask_and_colors_none(self): + """Ensures that unsetsurfaces shorter than the mask work correctly + when setcolor and unsetcolor are set to None. + + For this test the unsetsurface's height is less than the mask's height. + """ + default_surface_color = (0, 0, 0, 0) + mask_size = (10, 11) + short_size = (10, 6) + + unsetsurface = pygame.Surface(short_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + unsetsurface_rect = unsetsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface( + unsetsurface=unsetsurface, setcolor=None, unsetcolor=None + ) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, default_surface_color) + else: + assertSurfaceFilled( + self, to_surface, unsetsurface_color, unsetsurface_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_surface_color, unsetsurface_rect + ) + + def test_to_surface__surface_taller_than_mask(self): + """Ensures that surfaces taller than the mask work correctly. + + For this test the surface's height is greater than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 6) + tall_size = (10, 11) + + surface = pygame.Surface(tall_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + mask_rect = mask.get_rect() + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), tall_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + def test_to_surface__setsurface_taller_than_mask(self): + """Ensures that setsurfaces taller than the mask work correctly. + + For this test the setsurface's height is greater than the mask's + height. + """ + default_unsetcolor = pygame.Color("black") + mask_size = (10, 6) + tall_size = (10, 11) + + setsurface = pygame.Surface(tall_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + expected_color = setsurface_color if fill else default_unsetcolor + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__unsetsurface_taller_than_mask(self): + """Ensures that unsetsurfaces taller than the mask work correctly. + + For this test the unsetsurface's height is greater than the mask's + height. + """ + default_setcolor = pygame.Color("white") + mask_size = (10, 6) + tall_size = (10, 11) + + unsetsurface = pygame.Surface(tall_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + expected_color = default_setcolor if fill else unsetsurface_color + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__surface_wider_and_taller_than_mask(self): + """Ensures that surfaces wider and taller than the mask work correctly. + + For this test the surface's width is greater than the mask's width and + the surface's height is greater than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (6, 8) + wide_tall_size = (11, 15) + + surface = pygame.Surface(wide_tall_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + mask_rect = mask.get_rect() + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), wide_tall_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + def test_to_surface__setsurface_wider_and_taller_than_mask(self): + """Ensures that setsurfaces wider and taller than the mask work + correctly. + + For this test the setsurface's width is greater than the mask's width + and the setsurface's height is greater than the mask's height. + """ + default_unsetcolor = pygame.Color("black") + mask_size = (6, 8) + wide_tall_size = (11, 15) + + setsurface = pygame.Surface(wide_tall_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + expected_color = setsurface_color if fill else default_unsetcolor + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__unsetsurface_wider_and_taller_than_mask(self): + """Ensures that unsetsurfaces wider and taller than the mask work + correctly. + + For this test the unsetsurface's width is greater than the mask's width + and the unsetsurface's height is greater than the mask's height. + """ + default_setcolor = pygame.Color("white") + mask_size = (6, 8) + wide_tall_size = (11, 15) + + unsetsurface = pygame.Surface(wide_tall_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + expected_color = default_setcolor if fill else unsetsurface_color + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__surface_wider_and_shorter_than_mask(self): + """Ensures that surfaces wider and shorter than the mask work + correctly. + + For this test the surface's width is greater than the mask's width and + the surface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (7, 11) + wide_short_size = (13, 6) + + surface = pygame.Surface(wide_short_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + mask_rect = mask.get_rect() + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), wide_short_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + def test_to_surface__setsurface_wider_and_shorter_than_mask(self): + """Ensures that setsurfaces wider and shorter than the mask work + correctly. + + For this test the setsurface's width is greater than the mask's width + and the setsurface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (7, 11) + wide_short_size = (10, 6) + + setsurface = pygame.Surface(wide_short_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + setsurface_rect = setsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, setsurface_color, setsurface_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_setcolor, setsurface_rect + ) + else: + assertSurfaceFilled(self, to_surface, default_unsetcolor) + + def test_to_surface__unsetsurface_wider_and_shorter_than_mask(self): + """Ensures that unsetsurfaces wider and shorter than the mask work + correctly. + + For this test the unsetsurface's width is greater than the mask's width + and the unsetsurface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (7, 11) + wide_short_size = (10, 6) + + unsetsurface = pygame.Surface(wide_short_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + unsetsurface_rect = unsetsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, default_setcolor) + else: + assertSurfaceFilled( + self, to_surface, unsetsurface_color, unsetsurface_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_unsetcolor, unsetsurface_rect + ) + + def test_to_surface__surface_narrower_and_taller_than_mask(self): + """Ensures that surfaces narrower and taller than the mask work + correctly. + + For this test the surface's width is less than the mask's width and + the surface's height is greater than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 8) + narrow_tall_size = (6, 15) + + surface = pygame.Surface(narrow_tall_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + mask_rect = mask.get_rect() + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), narrow_tall_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + def test_to_surface__setsurface_narrower_and_taller_than_mask(self): + """Ensures that setsurfaces narrower and taller than the mask work + correctly. + + For this test the setsurface's width is less than the mask's width + and the setsurface's height is greater than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 8) + narrow_tall_size = (6, 15) + + setsurface = pygame.Surface(narrow_tall_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + setsurface_rect = setsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, setsurface_color, setsurface_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_setcolor, setsurface_rect + ) + else: + assertSurfaceFilled(self, to_surface, default_unsetcolor) + + def test_to_surface__unsetsurface_narrower_and_taller_than_mask(self): + """Ensures that unsetsurfaces narrower and taller than the mask work + correctly. + + For this test the unsetsurface's width is less than the mask's width + and the unsetsurface's height is greater than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 8) + narrow_tall_size = (6, 15) + + unsetsurface = pygame.Surface(narrow_tall_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + unsetsurface_rect = unsetsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, default_setcolor) + else: + assertSurfaceFilled( + self, to_surface, unsetsurface_color, unsetsurface_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_unsetcolor, unsetsurface_rect + ) + + def test_to_surface__surface_narrower_and_shorter_than_mask(self): + """Ensures that surfaces narrower and shorter than the mask work + correctly. + + For this test the surface's width is less than the mask's width and + the surface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 18) + narrow_short_size = (6, 15) + + surface = pygame.Surface(narrow_short_size) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + mask_rect = mask.get_rect() + surface.fill(surface_color) # Clear for each test. + expected_color = default_setcolor if fill else default_unsetcolor + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), narrow_short_size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect) + + def test_to_surface__setsurface_narrower_and_shorter_than_mask(self): + """Ensures that setsurfaces narrower and shorter than the mask work + correctly. + + For this test the setsurface's width is less than the mask's width + and the setsurface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 18) + narrow_short_size = (6, 15) + + setsurface = pygame.Surface(narrow_short_size, SRCALPHA, 32) + setsurface_color = pygame.Color("red") + setsurface.fill(setsurface_color) + setsurface_rect = setsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, setsurface_color, setsurface_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_setcolor, setsurface_rect + ) + else: + assertSurfaceFilled(self, to_surface, default_unsetcolor) + + def test_to_surface__unsetsurface_narrower_and_shorter_than_mask(self): + """Ensures that unsetsurfaces narrower and shorter than the mask work + correctly. + + For this test the unsetsurface's width is less than the mask's width + and the unsetsurface's height is less than the mask's height. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + mask_size = (10, 18) + narrow_short_size = (6, 15) + + unsetsurface = pygame.Surface(narrow_short_size, SRCALPHA, 32) + unsetsurface_color = pygame.Color("red") + unsetsurface.fill(unsetsurface_color) + unsetsurface_rect = unsetsurface.get_rect() + + for fill in (True, False): + mask = pygame.mask.Mask(mask_size, fill=fill) + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + # Different checks depending on if the mask was filled or not. + if fill: + assertSurfaceFilled(self, to_surface, default_setcolor) + else: + assertSurfaceFilled( + self, to_surface, unsetsurface_color, unsetsurface_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, default_unsetcolor, unsetsurface_rect + ) + + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_to_surface__all_surfaces_different_sizes_than_mask(self): + """Ensures that all the surface parameters can be of different sizes.""" + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + surface_color = pygame.Color("red") + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + + mask_size = (10, 15) + surface_size = (11, 14) + setsurface_size = (9, 8) + unsetsurface_size = (12, 16) + + surface = pygame.Surface(surface_size) + setsurface = pygame.Surface(setsurface_size) + unsetsurface = pygame.Surface(unsetsurface_size) + + surface.fill(surface_color) + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + surface_rect = surface.get_rect() + setsurface_rect = setsurface.get_rect() + unsetsurface_rect = unsetsurface.get_rect() + + # Create a mask that is filled except for a rect in the center. + mask = pygame.mask.Mask(mask_size, fill=True) + mask_rect = mask.get_rect() + unfilled_rect = pygame.Rect((0, 0), (4, 5)) + unfilled_rect.center = mask_rect.center + + for pos in ( + (x, y) + for x in range(unfilled_rect.x, unfilled_rect.w) + for y in range(unfilled_rect.y, unfilled_rect.h) + ): + mask.set_at(pos, 0) + + to_surface = mask.to_surface(surface, setsurface, unsetsurface) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), surface_size) + + # Check each surface pixel for the correct color. + to_surface.lock() # Lock for possible speed up. + + for pos in ( + (x, y) for x in range(surface_rect.w) for y in range(surface_rect.h) + ): + if not mask_rect.collidepoint(pos): + expected_color = surface_color + elif mask.get_at(pos): + # Checking set bit colors. + if setsurface_rect.collidepoint(pos): + expected_color = setsurface_color + else: + expected_color = default_setcolor + else: + # Checking unset bit colors. + if unsetsurface_rect.collidepoint(pos): + expected_color = unsetsurface_color + else: + expected_color = default_unsetcolor + + self.assertEqual(to_surface.get_at(pos), expected_color) + + to_surface.unlock() + + def test_to_surface__dest_locations(self): + """Ensures dest values can be different locations on/off the surface.""" + SIDE = 7 + surface = pygame.Surface((SIDE, SIDE)) + surface_rect = surface.get_rect() + dest_rect = surface_rect.copy() + + surface_color = pygame.Color("red") + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + + directions = ( + ((s, 0) for s in range(-SIDE, SIDE + 1)), # left to right + ((0, s) for s in range(-SIDE, SIDE + 1)), # top to bottom + ((s, s) for s in range(-SIDE, SIDE + 1)), # topleft to bottomright diag + ((-s, s) for s in range(-SIDE, SIDE + 1)), # topright to bottomleft diag + ) + + for fill in (True, False): + mask = pygame.mask.Mask((SIDE, SIDE), fill=fill) + expected_color = default_setcolor if fill else default_unsetcolor + + for direction in directions: + for pos in direction: + dest_rect.topleft = pos + overlap_rect = dest_rect.clip(surface_rect) + surface.fill(surface_color) + + to_surface = mask.to_surface(surface, dest=dest_rect) + + assertSurfaceFilled(self, to_surface, expected_color, overlap_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, overlap_rect + ) + + @unittest.expectedFailure + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_to_surface__area_locations(self): + """Ensures area rects can be different locations on/off the mask.""" + SIDE = 7 + surface = pygame.Surface((SIDE, SIDE)) + + surface_color = pygame.Color("red") + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + + directions = ( + ((s, 0) for s in range(-SIDE, SIDE + 1)), # left to right + ((0, s) for s in range(-SIDE, SIDE + 1)), # top to bottom + ((s, s) for s in range(-SIDE, SIDE + 1)), # topleft to bottomright diag + ((-s, s) for s in range(-SIDE, SIDE + 1)), # topright to bottomleft diag + ) + + for fill in (True, False): + mask = pygame.mask.Mask((SIDE, SIDE), fill=fill) + mask_rect = mask.get_rect() + area_rect = mask_rect.copy() + expected_color = default_setcolor if fill else default_unsetcolor + + for direction in directions: + for pos in direction: + area_rect.topleft = pos + overlap_rect = area_rect.clip(mask_rect) + overlap_rect.topleft = (0, 0) + surface.fill(surface_color) + + to_surface = mask.to_surface(surface, area=area_rect) + + assertSurfaceFilled(self, to_surface, expected_color, overlap_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, overlap_rect + ) + + @unittest.expectedFailure + def test_to_surface__dest_and_area_locations(self): + """Ensures dest/area values can be different locations on/off the + surface/mask. + """ + SIDE = 5 + surface = pygame.Surface((SIDE, SIDE)) + surface_rect = surface.get_rect() + dest_rect = surface_rect.copy() + + surface_color = pygame.Color("red") + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + + dest_directions = ( + ((s, 0) for s in range(-SIDE, SIDE + 1)), # left to right + ((0, s) for s in range(-SIDE, SIDE + 1)), # top to bottom + ((s, s) for s in range(-SIDE, SIDE + 1)), # topleft to bottomright diag + ((-s, s) for s in range(-SIDE, SIDE + 1)), # topright to bottomleft diag + ) + + # Using only the topleft to bottomright diagonal to test the area (to + # reduce the number of loop iterations). + area_positions = list(dest_directions[2]) + + for fill in (True, False): + mask = pygame.mask.Mask((SIDE, SIDE), fill=fill) + mask_rect = mask.get_rect() + area_rect = mask_rect.copy() + expected_color = default_setcolor if fill else default_unsetcolor + + for dest_direction in dest_directions: + for dest_pos in dest_direction: + dest_rect.topleft = dest_pos + + for area_pos in area_positions: + area_rect.topleft = area_pos + area_overlap_rect = area_rect.clip(mask_rect) + area_overlap_rect.topleft = dest_rect.topleft + dest_overlap_rect = dest_rect.clip(area_overlap_rect) + + surface.fill(surface_color) + + to_surface = mask.to_surface( + surface, dest=dest_rect, area=area_rect + ) + + assertSurfaceFilled( + self, to_surface, expected_color, dest_overlap_rect + ) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, dest_overlap_rect + ) + + @unittest.expectedFailure + def test_to_surface__area_sizes(self): + """Ensures area rects can be different sizes.""" + SIDE = 7 + SIZES = ( + (0, 0), + (0, 1), + (1, 0), + (1, 1), + (SIDE - 1, SIDE - 1), + (SIDE - 1, SIDE), + (SIDE, SIDE - 1), + (SIDE, SIDE), + (SIDE + 1, SIDE), + (SIDE, SIDE + 1), + (SIDE + 1, SIDE + 1), + ) + + surface = pygame.Surface((SIDE, SIDE)) + surface_color = pygame.Color("red") + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + + for fill in (True, False): + mask = pygame.mask.Mask((SIDE, SIDE), fill=fill) + mask_rect = mask.get_rect() + expected_color = default_setcolor if fill else default_unsetcolor + + for size in SIZES: + area_rect = pygame.Rect((0, 0), size) + + for pos in self.ORIGIN_OFFSETS: + area_rect.topleft = pos + overlap_rect = area_rect.clip(mask_rect) + overlap_rect.topleft = (0, 0) + surface.fill(surface_color) + + to_surface = mask.to_surface(surface, area=area_rect) + + assertSurfaceFilled(self, to_surface, expected_color, overlap_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, overlap_rect + ) + + def test_to_surface__surface_color_alphas(self): + """Ensures the setsurface/unsetsurface color alpha values are respected.""" + size = (13, 17) + setsurface_color = pygame.Color("green") + setsurface_color.a = 53 + unsetsurface_color = pygame.Color("blue") + unsetsurface_color.a = 109 + + setsurface = pygame.Surface(size, flags=SRCALPHA, depth=32) + unsetsurface = pygame.Surface(size, flags=SRCALPHA, depth=32) + + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + expected_color = setsurface_color if fill else unsetsurface_color + + to_surface = mask.to_surface( + setsurface=setsurface, unsetsurface=unsetsurface + ) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__color_alphas(self): + """Ensures the setcolor/unsetcolor alpha values are respected.""" + size = (13, 17) + setcolor = pygame.Color("green") + setcolor.a = 35 + unsetcolor = pygame.Color("blue") + unsetcolor.a = 213 + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + expected_color = setcolor if fill else unsetcolor + + to_surface = mask.to_surface(setcolor=setcolor, unsetcolor=unsetcolor) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__depths(self): + """Ensures to_surface works correctly with supported surface depths.""" + size = (13, 17) + surface_color = pygame.Color("red") + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + + for depth in (8, 16, 24, 32): + surface = pygame.Surface(size, depth=depth) + setsurface = pygame.Surface(size, depth=depth) + unsetsurface = pygame.Surface(size, depth=depth) + + surface.fill(surface_color) + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + + # For non-32 bit depths, the actual color can be different from + # what was filled. + expected_color = ( + setsurface.get_at((0, 0)) if fill else unsetsurface.get_at((0, 0)) + ) + + to_surface = mask.to_surface(surface, setsurface, unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__different_depths(self): + """Ensures an exception is raised when surfaces have different depths.""" + size = (13, 17) + surface_color = pygame.Color("red") + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + mask = pygame.mask.Mask(size) + + # Test different combinations of depths. + test_depths = ( + (8, 8, 16), # surface/setsurface/unsetsurface + (8, 8, 24), + (8, 8, 32), + (16, 16, 24), + (16, 16, 32), + (24, 16, 8), + (32, 16, 16), + (32, 32, 16), + (32, 24, 32), + ) + + for depths in test_depths: + surface = pygame.Surface(size, depth=depths[0]) + setsurface = pygame.Surface(size, depth=depths[1]) + unsetsurface = pygame.Surface(size, depth=depths[2]) + + surface.fill(surface_color) + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + with self.assertRaises(ValueError): + mask.to_surface(surface, setsurface, unsetsurface) + + def test_to_surface__different_depths_with_created_surfaces(self): + """Ensures an exception is raised when surfaces have different depths + than the created surface. + """ + size = (13, 17) + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + mask = pygame.mask.Mask(size) + + # Test different combinations of depths. The created surface always has + # a depth of 32. + test_depths = ( + (8, 8), # setsurface/unsetsurface + (16, 16), + (24, 24), + (24, 16), + (32, 8), + (32, 16), + (32, 24), + (16, 32), + ) + + for set_depth, unset_depth in test_depths: + setsurface = pygame.Surface(size, depth=set_depth) + unsetsurface = pygame.Surface(size, depth=unset_depth) + + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + with self.assertRaises(ValueError): + mask.to_surface(setsurface=setsurface, unsetsurface=unsetsurface) + + def test_to_surface__same_srcalphas(self): + """Ensures to_surface works correctly when the SRCALPHA flag is set or not.""" + size = (13, 17) + surface_color = pygame.Color("red") + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + + for depth in (16, 32): + for flags in (0, SRCALPHA): + surface = pygame.Surface(size, flags=flags, depth=depth) + setsurface = pygame.Surface(size, flags=flags, depth=depth) + unsetsurface = pygame.Surface(size, flags=flags, depth=depth) + + surface.fill(surface_color) + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + expected_color = setsurface_color if fill else unsetsurface_color + + to_surface = mask.to_surface(surface, setsurface, unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + if flags: + self.assertTrue(to_surface.get_flags() & flags) + + def test_to_surface__same_srcalphas_with_created_surfaces(self): + """Ensures to_surface works correctly when it creates a surface + and the SRCALPHA flag is set on both setsurface and unsetsurface. + """ + size = (13, 17) + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + # The created surface always has a depth of 32 and the SRCALPHA flag set. + expected_flags = SRCALPHA + + setsurface = pygame.Surface(size, flags=expected_flags, depth=32) + unsetsurface = pygame.Surface(size, flags=expected_flags, depth=32) + + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + expected_color = setsurface_color if fill else unsetsurface_color + + to_surface = mask.to_surface( + setsurface=setsurface, unsetsurface=unsetsurface + ) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + self.assertTrue(to_surface.get_flags() & expected_flags) + + def test_to_surface__different_srcalphas(self): + """Ensures an exception is raised when surfaces have different SRCALPHA + flag settings. + """ + size = (13, 17) + surface_color = pygame.Color("red") + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + mask = pygame.mask.Mask(size) + + # Test different combinations of SRCALPHA flags. + test_flags = ( + (SRCALPHA, 0, 0), # surface/setsurface/unsetsurface + (SRCALPHA, SRCALPHA, 0), + (0, SRCALPHA, SRCALPHA), + (0, 0, SRCALPHA), + ) + + for depth in (16, 32): + for flags in test_flags: + surface = pygame.Surface(size, flags=flags[0], depth=depth) + setsurface = pygame.Surface(size, flags=flags[1], depth=depth) + unsetsurface = pygame.Surface(size, flags=flags[2], depth=depth) + + surface.fill(surface_color) + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + with self.assertRaises(ValueError): + mask.to_surface(surface, setsurface, unsetsurface) + + def test_to_surface__different_srcalphas_with_created_surfaces(self): + """Ensures an exception is raised when surfaces have different SRCALPHA + flag settings than the created surface. + """ + size = (13, 17) + setsurface_color = pygame.Color("green") + unsetsurface_color = pygame.Color("blue") + mask = pygame.mask.Mask(size) + + for depth in (16, 32): + # Test different combinations of SRCALPHA flags. The created + # surface always has the SRCALPHA flag set. + for flags in ((0, 0), (SRCALPHA, 0), (0, SRCALPHA)): + setsurface = pygame.Surface(size, flags=flags[0], depth=depth) + unsetsurface = pygame.Surface(size, flags=flags[1], depth=depth) + + setsurface.fill(setsurface_color) + unsetsurface.fill(unsetsurface_color) + + with self.assertRaises(ValueError): + mask.to_surface(setsurface=setsurface, unsetsurface=unsetsurface) + + def test_to_surface__dest_on_surface(self): + """Ensures dest values on the surface work correctly + when using the defaults for setcolor and unsetcolor. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + width, height = size = (5, 9) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + expected_color = default_setcolor if fill else default_unsetcolor + + # Test the dest parameter at different locations on the surface. + for dest in ((x, y) for y in range(height) for x in range(width)): + surface.fill(surface_color) # Clear for each test. + mask_rect.topleft = dest + + to_surface = mask.to_surface(surface, dest=dest) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, mask_rect + ) + + def test_to_surface__dest_on_surface_with_setsurface_unsetsurface(self): + """Ensures dest values on the surface work correctly + when using setsurface and unsetsurface. + """ + width, height = size = (5, 9) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + setsurface = surface.copy() + setsurface_color = pygame.Color("green") + setsurface.fill(setsurface_color) + + unsetsurface = surface.copy() + unsetsurface_color = pygame.Color("blue") + unsetsurface.fill(unsetsurface_color) + + # Using different kwargs to exercise different to_surface() code. + # Should not have any impact on the resulting drawn surfaces. + kwargs = { + "surface": surface, + "setsurface": setsurface, + "unsetsurface": unsetsurface, + "dest": None, + } + + color_kwargs = dict(kwargs) + color_kwargs.update((("setcolor", None), ("unsetcolor", None))) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + expected_color = setsurface_color if fill else unsetsurface_color + + # Test the dest parameter at different locations on the surface. + for dest in ((x, y) for y in range(height) for x in range(width)): + mask_rect.topleft = dest + + for use_color_params in (True, False): + surface.fill(surface_color) # Clear for each test. + + test_kwargs = color_kwargs if use_color_params else kwargs + test_kwargs["dest"] = dest + to_surface = mask.to_surface(**test_kwargs) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, mask_rect + ) + + def test_to_surface__dest_off_surface(self): + """Ensures dest values off the surface work correctly + when using the defaults for setcolor and unsetcolor. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + width, height = size = (5, 7) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + # Test different dests off the surface. + dests = [(-width, -height), (-width, 0), (0, -height)] + dests.extend(off_corners(surface.get_rect())) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + expected_color = default_setcolor if fill else default_unsetcolor + + for dest in dests: + surface.fill(surface_color) # Clear for each test. + mask_rect.topleft = dest + + to_surface = mask.to_surface(surface, dest=dest) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, mask_rect + ) + + def test_to_surface__dest_off_surface_with_setsurface_unsetsurface(self): + """Ensures dest values off the surface work correctly + when using setsurface and unsetsurface. + """ + width, height = size = (5, 7) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + setsurface = surface.copy() + setsurface_color = pygame.Color("green") + setsurface.fill(setsurface_color) + + unsetsurface = surface.copy() + unsetsurface_color = pygame.Color("blue") + unsetsurface.fill(unsetsurface_color) + + # Test different dests off the surface. + dests = [(-width, -height), (-width, 0), (0, -height)] + dests.extend(off_corners(surface.get_rect())) + + # Using different kwargs to exercise different to_surface() code. + # Should not have any impact on the resulting drawn surfaces. + kwargs = { + "surface": surface, + "setsurface": setsurface, + "unsetsurface": unsetsurface, + "dest": None, + } + + color_kwargs = dict(kwargs) + color_kwargs.update((("setcolor", None), ("unsetcolor", None))) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + expected_color = setsurface_color if fill else unsetsurface_color + + for dest in dests: + mask_rect.topleft = dest + + for use_color_params in (True, False): + surface.fill(surface_color) # Clear for each test. + test_kwargs = color_kwargs if use_color_params else kwargs + test_kwargs["dest"] = dest + to_surface = mask.to_surface(**test_kwargs) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, mask_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, mask_rect + ) + + @unittest.expectedFailure + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_to_surface__area_on_mask(self): + """Ensures area values on the mask work correctly + when using the defaults for setcolor and unsetcolor. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + width, height = size = (5, 9) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + area_rect = mask_rect.copy() + expected_color = default_setcolor if fill else default_unsetcolor + + # Testing the area parameter at different locations on the mask. + for pos in ((x, y) for y in range(height) for x in range(width)): + surface.fill(surface_color) # Clear for each test. + area_rect.topleft = pos + overlap_rect = mask_rect.clip(area_rect) + overlap_rect.topleft = (0, 0) + + to_surface = mask.to_surface(surface, area=area_rect) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, overlap_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, overlap_rect + ) + + @unittest.expectedFailure + def test_to_surface__area_on_mask_with_setsurface_unsetsurface(self): + """Ensures area values on the mask work correctly + when using setsurface and unsetsurface. + """ + width, height = size = (5, 9) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + setsurface = surface.copy() + setsurface_color = pygame.Color("green") + setsurface.fill(setsurface_color) + + unsetsurface = surface.copy() + unsetsurface_color = pygame.Color("blue") + unsetsurface.fill(unsetsurface_color) + + # Using the values in kwargs vs color_kwargs tests different to_surface + # code. Should not have any impact on the resulting drawn surfaces. + kwargs = { + "surface": surface, + "setsurface": setsurface, + "unsetsurface": unsetsurface, + "area": pygame.Rect((0, 0), size), + } + + color_kwargs = dict(kwargs) + color_kwargs.update((("setcolor", None), ("unsetcolor", None))) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + area_rect = mask_rect.copy() + expected_color = setsurface_color if fill else unsetsurface_color + + # Testing the area parameter at different locations on the mask. + for pos in ((x, y) for y in range(height) for x in range(width)): + area_rect.topleft = pos + overlap_rect = mask_rect.clip(area_rect) + overlap_rect.topleft = (0, 0) + + for use_color_params in (True, False): + surface.fill(surface_color) # Clear for each test. + test_kwargs = color_kwargs if use_color_params else kwargs + test_kwargs["area"].topleft = pos + overlap_rect = mask_rect.clip(test_kwargs["area"]) + overlap_rect.topleft = (0, 0) + + to_surface = mask.to_surface(**test_kwargs) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, overlap_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, overlap_rect + ) + + @unittest.expectedFailure + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_to_surface__area_off_mask(self): + """Ensures area values off the mask work correctly + when using the defaults for setcolor and unsetcolor. + """ + default_setcolor = pygame.Color("white") + default_unsetcolor = pygame.Color("black") + width, height = size = (5, 7) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + # Testing positions off the mask. + positions = [(-width, -height), (-width, 0), (0, -height)] + positions.extend(off_corners(pygame.Rect((0, 0), (width, height)))) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + area_rect = mask_rect.copy() + expected_color = default_setcolor if fill else default_unsetcolor + + for pos in positions: + surface.fill(surface_color) # Clear for each test. + area_rect.topleft = pos + overlap_rect = mask_rect.clip(area_rect) + overlap_rect.topleft = (0, 0) + + to_surface = mask.to_surface(surface, area=area_rect) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, overlap_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, overlap_rect + ) + + @unittest.expectedFailure + @unittest.skipIf(IS_PYPY, "Segfaults on pypy") + def test_to_surface__area_off_mask_with_setsurface_unsetsurface(self): + """Ensures area values off the mask work correctly + when using setsurface and unsetsurface. + """ + width, height = size = (5, 7) + surface = pygame.Surface(size, SRCALPHA, 32) + surface_color = pygame.Color("red") + + setsurface = surface.copy() + setsurface_color = pygame.Color("green") + setsurface.fill(setsurface_color) + + unsetsurface = surface.copy() + unsetsurface_color = pygame.Color("blue") + unsetsurface.fill(unsetsurface_color) + + # Testing positions off the mask. + positions = [(-width, -height), (-width, 0), (0, -height)] + positions.extend(off_corners(pygame.Rect((0, 0), (width, height)))) + + # Using the values in kwargs vs color_kwargs tests different to_surface + # code. Should not have any impact on the resulting drawn surfaces. + kwargs = { + "surface": surface, + "setsurface": setsurface, + "unsetsurface": unsetsurface, + "area": pygame.Rect((0, 0), size), + } + + color_kwargs = dict(kwargs) + color_kwargs.update((("setcolor", None), ("unsetcolor", None))) + + for fill in (True, False): + mask = pygame.mask.Mask(size, fill=fill) + mask_rect = mask.get_rect() + expected_color = setsurface_color if fill else unsetsurface_color + + for pos in positions: + for use_color_params in (True, False): + surface.fill(surface_color) # Clear for each test. + test_kwargs = color_kwargs if use_color_params else kwargs + test_kwargs["area"].topleft = pos + overlap_rect = mask_rect.clip(test_kwargs["area"]) + overlap_rect.topleft = (0, 0) + + to_surface = mask.to_surface(**test_kwargs) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color, overlap_rect) + assertSurfaceFilledIgnoreArea( + self, to_surface, surface_color, overlap_rect + ) + + def test_to_surface__surface_with_zero_size(self): + """Ensures zero sized surfaces are handled correctly.""" + expected_ref_count = 3 + size = (0, 0) + surface = pygame.Surface(size) + mask = pygame.mask.Mask((3, 4), fill=True) + + to_surface = mask.to_surface(surface) + + self.assertIs(to_surface, surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertEqual(to_surface.get_size(), size) + + def test_to_surface__setsurface_with_zero_size(self): + """Ensures zero sized setsurfaces are handled correctly.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + expected_color = pygame.Color("white") # Default setcolor. + mask_size = (2, 4) + mask = pygame.mask.Mask(mask_size, fill=True) + setsurface = pygame.Surface((0, 0), expected_flag, expected_depth) + + to_surface = mask.to_surface(setsurface=setsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_to_surface__unsetsurface_with_zero_size(self): + """Ensures zero sized unsetsurfaces are handled correctly.""" + expected_ref_count = 2 + expected_flag = SRCALPHA + expected_depth = 32 + expected_color = pygame.Color("black") # Default unsetcolor. + mask_size = (4, 2) + mask = pygame.mask.Mask(mask_size) + unsetsurface = pygame.Surface((0, 0), expected_flag, expected_depth) + + to_surface = mask.to_surface(unsetsurface=unsetsurface) + + self.assertIsInstance(to_surface, pygame.Surface) + if not IS_PYPY: + self.assertEqual(sys.getrefcount(to_surface), expected_ref_count) + self.assertTrue(to_surface.get_flags() & expected_flag) + self.assertEqual(to_surface.get_bitsize(), expected_depth) + self.assertEqual(to_surface.get_size(), mask_size) + assertSurfaceFilled(self, to_surface, expected_color) + + def test_zero_mask(self): + """Ensures masks can be created with zero sizes.""" + for size in ((100, 0), (0, 100), (0, 0)): + for fill in (True, False): + msg = "size={}, fill={}".format(size, fill) + + mask = pygame.mask.Mask(size, fill=fill) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.get_size(), size, msg) + + def test_zero_mask_copy(self): + """Ensures copy correctly handles zero sized masks.""" + for expected_size in ((11, 0), (0, 11), (0, 0)): + mask = pygame.mask.Mask(expected_size) + + mask_copy = mask.copy() + + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + + def test_zero_mask_get_size(self): + """Ensures get_size correctly handles zero sized masks.""" + for expected_size in ((41, 0), (0, 40), (0, 0)): + mask = pygame.mask.Mask(expected_size) + + size = mask.get_size() + + self.assertEqual(size, expected_size) + + def test_zero_mask_get_rect(self): + """Ensures get_rect correctly handles zero sized masks.""" + for expected_size in ((4, 0), (0, 4), (0, 0)): + expected_rect = pygame.Rect((0, 0), expected_size) + mask = pygame.mask.Mask(expected_size) + + rect = mask.get_rect() + + self.assertEqual(rect, expected_rect) + + def test_zero_mask_get_at(self): + """Ensures get_at correctly handles zero sized masks.""" + for size in ((51, 0), (0, 50), (0, 0)): + mask = pygame.mask.Mask(size) + + with self.assertRaises(IndexError): + value = mask.get_at((0, 0)) + + def test_zero_mask_set_at(self): + """Ensures set_at correctly handles zero sized masks.""" + for size in ((31, 0), (0, 30), (0, 0)): + mask = pygame.mask.Mask(size) + + with self.assertRaises(IndexError): + mask.set_at((0, 0)) + + def test_zero_mask_overlap(self): + """Ensures overlap correctly handles zero sized masks. + + Tests combinations of sized and zero sized masks. + """ + offset = (0, 0) + + for size1, size2 in zero_size_pairs(51, 42): + msg = "size1={}, size2={}".format(size1, size2) + mask1 = pygame.mask.Mask(size1, fill=True) + mask2 = pygame.mask.Mask(size2, fill=True) + + overlap_pos = mask1.overlap(mask2, offset) + + self.assertIsNone(overlap_pos, msg) + + def test_zero_mask_overlap_area(self): + """Ensures overlap_area correctly handles zero sized masks. + + Tests combinations of sized and zero sized masks. + """ + offset = (0, 0) + expected_count = 0 + + for size1, size2 in zero_size_pairs(41, 52): + msg = "size1={}, size2={}".format(size1, size2) + mask1 = pygame.mask.Mask(size1, fill=True) + mask2 = pygame.mask.Mask(size2, fill=True) + + overlap_count = mask1.overlap_area(mask2, offset) + + self.assertEqual(overlap_count, expected_count, msg) + + def test_zero_mask_overlap_mask(self): + """Ensures overlap_mask correctly handles zero sized masks. + + Tests combinations of sized and zero sized masks. + """ + offset = (0, 0) + expected_count = 0 + + for size1, size2 in zero_size_pairs(43, 53): + msg = "size1={}, size2={}".format(size1, size2) + mask1 = pygame.mask.Mask(size1, fill=True) + mask2 = pygame.mask.Mask(size2, fill=True) + + overlap_mask = mask1.overlap_mask(mask2, offset) + + self.assertIsInstance(overlap_mask, pygame.mask.Mask, msg) + self.assertEqual(overlap_mask.count(), expected_count, msg) + self.assertEqual(overlap_mask.get_size(), size1, msg) + + def test_zero_mask_fill(self): + """Ensures fill correctly handles zero sized masks.""" + expected_count = 0 + + for size in ((100, 0), (0, 100), (0, 0)): + mask = pygame.mask.Mask(size) + + mask.fill() + + self.assertEqual(mask.count(), expected_count, "size={}".format(size)) + + def test_zero_mask_clear(self): + sizes = ((100, 0), (0, 100), (0, 0)) + + for size in sizes: + mask = pygame.mask.Mask(size) + mask.clear() + self.assertEqual(mask.count(), 0) + + def test_zero_mask_flip(self): + sizes = ((100, 0), (0, 100), (0, 0)) + + for size in sizes: + mask = pygame.mask.Mask(size) + mask.invert() + self.assertEqual(mask.count(), 0) + + def test_zero_mask_scale(self): + sizes = ((100, 0), (0, 100), (0, 0)) + + for size in sizes: + mask = pygame.mask.Mask(size) + mask2 = mask.scale((2, 3)) + + self.assertIsInstance(mask2, pygame.mask.Mask) + self.assertEqual(mask2.get_size(), (2, 3)) + + def test_zero_mask_draw(self): + """Ensures draw correctly handles zero sized masks. + + Tests combinations of sized and zero sized masks. + """ + offset = (0, 0) + + for size1, size2 in zero_size_pairs(31, 37): + msg = "size1={}, size2={}".format(size1, size2) + mask1 = pygame.mask.Mask(size1, fill=True) + mask2 = pygame.mask.Mask(size2, fill=True) + expected_count = mask1.count() + + mask1.draw(mask2, offset) + + self.assertEqual(mask1.count(), expected_count, msg) + self.assertEqual(mask1.get_size(), size1, msg) + + def test_zero_mask_erase(self): + """Ensures erase correctly handles zero sized masks. + + Tests combinations of sized and zero sized masks. + """ + offset = (0, 0) + + for size1, size2 in zero_size_pairs(29, 23): + msg = "size1={}, size2={}".format(size1, size2) + mask1 = pygame.mask.Mask(size1, fill=True) + mask2 = pygame.mask.Mask(size2, fill=True) + expected_count = mask1.count() + + mask1.erase(mask2, offset) + + self.assertEqual(mask1.count(), expected_count, msg) + self.assertEqual(mask1.get_size(), size1, msg) + + def test_zero_mask_count(self): + sizes = ((100, 0), (0, 100), (0, 0)) + + for size in sizes: + mask = pygame.mask.Mask(size, fill=True) + self.assertEqual(mask.count(), 0) + + def test_zero_mask_centroid(self): + sizes = ((100, 0), (0, 100), (0, 0)) + + for size in sizes: + mask = pygame.mask.Mask(size) + self.assertEqual(mask.centroid(), (0, 0)) + + def test_zero_mask_angle(self): + sizes = ((100, 0), (0, 100), (0, 0)) + + for size in sizes: + mask = pygame.mask.Mask(size) + self.assertEqual(mask.angle(), 0.0) + + def test_zero_mask_outline(self): + """Ensures outline correctly handles zero sized masks.""" + expected_points = [] + + for size in ((61, 0), (0, 60), (0, 0)): + mask = pygame.mask.Mask(size) + + points = mask.outline() + + self.assertListEqual(points, expected_points, "size={}".format(size)) + + def test_zero_mask_outline__with_arg(self): + """Ensures outline correctly handles zero sized masks + when using the skip pixels argument.""" + expected_points = [] + + for size in ((66, 0), (0, 65), (0, 0)): + mask = pygame.mask.Mask(size) + + points = mask.outline(10) + + self.assertListEqual(points, expected_points, "size={}".format(size)) + + def test_zero_mask_convolve(self): + """Ensures convolve correctly handles zero sized masks. + + Tests the different combinations of sized and zero sized masks. + """ + for size1 in ((17, 13), (71, 0), (0, 70), (0, 0)): + mask1 = pygame.mask.Mask(size1, fill=True) + + for size2 in ((11, 7), (81, 0), (0, 60), (0, 0)): + msg = "sizes={}, {}".format(size1, size2) + mask2 = pygame.mask.Mask(size2, fill=True) + expected_size = ( + max(0, size1[0] + size2[0] - 1), + max(0, size1[1] + size2[1] - 1), + ) + + mask = mask1.convolve(mask2) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertIsNot(mask, mask2, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + + def test_zero_mask_convolve__with_output_mask(self): + """Ensures convolve correctly handles zero sized masks + when using an output mask argument. + + Tests the different combinations of sized and zero sized masks. + """ + for size1 in ((11, 17), (91, 0), (0, 90), (0, 0)): + mask1 = pygame.mask.Mask(size1, fill=True) + + for size2 in ((13, 11), (83, 0), (0, 62), (0, 0)): + mask2 = pygame.mask.Mask(size2, fill=True) + + for output_size in ((7, 5), (71, 0), (0, 70), (0, 0)): + msg = "sizes={}, {}, {}".format(size1, size2, output_size) + output_mask = pygame.mask.Mask(output_size) + + mask = mask1.convolve(mask2, output_mask) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertIs(mask, output_mask, msg) + self.assertEqual(mask.get_size(), output_size, msg) + + def test_zero_mask_connected_component(self): + """Ensures connected_component correctly handles zero sized masks.""" + expected_count = 0 + + for size in ((81, 0), (0, 80), (0, 0)): + msg = "size={}".format(size) + mask = pygame.mask.Mask(size) + + cc_mask = mask.connected_component() + + self.assertIsInstance(cc_mask, pygame.mask.Mask, msg) + self.assertEqual(cc_mask.get_size(), size) + self.assertEqual(cc_mask.count(), expected_count, msg) + + def test_zero_mask_connected_component__indexed(self): + """Ensures connected_component correctly handles zero sized masks + when using an index argument.""" + for size in ((91, 0), (0, 90), (0, 0)): + mask = pygame.mask.Mask(size) + + with self.assertRaises(IndexError): + cc_mask = mask.connected_component((0, 0)) + + def test_zero_mask_connected_components(self): + """Ensures connected_components correctly handles zero sized masks.""" + expected_cc_masks = [] + + for size in ((11, 0), (0, 10), (0, 0)): + mask = pygame.mask.Mask(size) + + cc_masks = mask.connected_components() + + self.assertListEqual(cc_masks, expected_cc_masks, "size={}".format(size)) + + def test_zero_mask_get_bounding_rects(self): + """Ensures get_bounding_rects correctly handles zero sized masks.""" + expected_bounding_rects = [] + + for size in ((21, 0), (0, 20), (0, 0)): + mask = pygame.mask.Mask(size) + + bounding_rects = mask.get_bounding_rects() + + self.assertListEqual( + bounding_rects, expected_bounding_rects, "size={}".format(size) + ) + + def test_zero_mask_to_surface(self): + """Ensures to_surface correctly handles zero sized masks and surfaces.""" + mask_color = pygame.Color("blue") + surf_color = pygame.Color("red") + + for surf_size in ((7, 3), (7, 0), (0, 7), (0, 0)): + surface = pygame.Surface(surf_size, SRCALPHA, 32) + surface.fill(surf_color) + + for mask_size in ((5, 0), (0, 5), (0, 0)): + mask = pygame.mask.Mask(mask_size, fill=True) + + to_surface = mask.to_surface(surface, setcolor=mask_color) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), surf_size) + + if 0 not in surf_size: + assertSurfaceFilled(self, to_surface, surf_color) + + def test_zero_mask_to_surface__create_surface(self): + """Ensures to_surface correctly handles zero sized masks and surfaces + when it has to create a default surface. + """ + mask_color = pygame.Color("blue") + + for mask_size in ((3, 0), (0, 3), (0, 0)): + mask = pygame.mask.Mask(mask_size, fill=True) + + to_surface = mask.to_surface(setcolor=mask_color) + + self.assertIsInstance(to_surface, pygame.Surface) + self.assertEqual(to_surface.get_size(), mask_size) + + +class SubMask(pygame.mask.Mask): + """Subclass of the Mask class to help test subclassing.""" + + def __init__(self, *args, **kwargs): + super(SubMask, self).__init__(*args, **kwargs) + self.test_attribute = True + + +class SubMaskCopy(SubMask): + """Subclass of the Mask class to help test copying subclasses.""" + + def copy(self): + mask_copy = super(SubMaskCopy, self).copy() + mask_copy.test_attribute = self.test_attribute + return mask_copy + + +class SubMaskDunderCopy(SubMask): + """Subclass of the Mask class to help test copying subclasses.""" + + def __copy__(self): + mask_copy = super(SubMaskDunderCopy, self).__copy__() + mask_copy.test_attribute = self.test_attribute + return mask_copy + + +class SubMaskCopyAndDunderCopy(SubMaskDunderCopy): + """Subclass of the Mask class to help test copying subclasses.""" + + def copy(self): + return super(SubMaskCopyAndDunderCopy, self).copy() + + +class MaskSubclassTest(unittest.TestCase): + """Test subclassed Masks.""" + + def test_subclass_mask(self): + """Ensures the Mask class can be subclassed.""" + mask = SubMask((5, 3), fill=True) + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertIsInstance(mask, SubMask) + self.assertTrue(mask.test_attribute) + + def test_subclass_copy(self): + """Ensures copy works for subclassed Masks.""" + mask = SubMask((65, 2), fill=True) + + # Test both the copy() and __copy__() methods. + for mask_copy in (mask.copy(), copy.copy(mask)): + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsInstance(mask_copy, SubMask) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + # No subclass attributes because copy()/__copy__() not overridden. + self.assertFalse(hasattr(mask_copy, "test_attribute")) + + def test_subclass_copy__override_copy(self): + """Ensures copy works for subclassed Masks overriding copy.""" + mask = SubMaskCopy((65, 2), fill=True) + + # Test both the copy() and __copy__() methods. + for i, mask_copy in enumerate((mask.copy(), copy.copy(mask))): + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsInstance(mask_copy, SubMaskCopy) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + + if 1 == i: + # No subclass attributes because __copy__() not overridden. + self.assertFalse(hasattr(mask_copy, "test_attribute")) + else: + self.assertTrue(mask_copy.test_attribute) + + def test_subclass_copy__override_dunder_copy(self): + """Ensures copy works for subclassed Masks overriding __copy__.""" + mask = SubMaskDunderCopy((65, 2), fill=True) + + # Test both the copy() and __copy__() methods. + for mask_copy in (mask.copy(), copy.copy(mask)): + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsInstance(mask_copy, SubMaskDunderCopy) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + # Calls to copy() eventually call __copy__() internally so the + # attributes will be copied. + self.assertTrue(mask_copy.test_attribute) + + def test_subclass_copy__override_both_copy_methods(self): + """Ensures copy works for subclassed Masks overriding copy/__copy__.""" + mask = SubMaskCopyAndDunderCopy((65, 2), fill=True) + + # Test both the copy() and __copy__() methods. + for mask_copy in (mask.copy(), copy.copy(mask)): + self.assertIsInstance(mask_copy, pygame.mask.Mask) + self.assertIsInstance(mask_copy, SubMaskCopyAndDunderCopy) + self.assertIsNot(mask_copy, mask) + assertMaskEqual(self, mask_copy, mask) + self.assertTrue(mask_copy.test_attribute) + + def test_subclass_get_size(self): + """Ensures get_size works for subclassed Masks.""" + expected_size = (2, 3) + mask = SubMask(expected_size) + + size = mask.get_size() + + self.assertEqual(size, expected_size) + + def test_subclass_mask_get_rect(self): + """Ensures get_rect works for subclassed Masks.""" + expected_rect = pygame.Rect((0, 0), (65, 33)) + mask = SubMask(expected_rect.size, fill=True) + + rect = mask.get_rect() + + self.assertEqual(rect, expected_rect) + + def test_subclass_get_at(self): + """Ensures get_at works for subclassed Masks.""" + expected_bit = 1 + mask = SubMask((3, 2), fill=True) + + bit = mask.get_at((0, 0)) + + self.assertEqual(bit, expected_bit) + + def test_subclass_set_at(self): + """Ensures set_at works for subclassed Masks.""" + expected_bit = 1 + expected_count = 1 + pos = (0, 0) + mask = SubMask(fill=False, size=(4, 2)) + + mask.set_at(pos) + + self.assertEqual(mask.get_at(pos), expected_bit) + self.assertEqual(mask.count(), expected_count) + + def test_subclass_overlap(self): + """Ensures overlap works for subclassed Masks.""" + expected_pos = (0, 0) + mask_size = (2, 3) + masks = (pygame.mask.Mask(fill=True, size=mask_size), SubMask(mask_size, True)) + arg_masks = ( + pygame.mask.Mask(fill=True, size=mask_size), + SubMask(mask_size, True), + ) + + # Test different combinations of subclassed and non-subclassed Masks. + for mask in masks: + for arg_mask in arg_masks: + overlap_pos = mask.overlap(arg_mask, (0, 0)) + + self.assertEqual(overlap_pos, expected_pos) + + def test_subclass_overlap_area(self): + """Ensures overlap_area works for subclassed Masks.""" + mask_size = (3, 2) + expected_count = mask_size[0] * mask_size[1] + masks = (pygame.mask.Mask(fill=True, size=mask_size), SubMask(mask_size, True)) + arg_masks = ( + pygame.mask.Mask(fill=True, size=mask_size), + SubMask(mask_size, True), + ) + + # Test different combinations of subclassed and non-subclassed Masks. + for mask in masks: + for arg_mask in arg_masks: + overlap_count = mask.overlap_area(arg_mask, (0, 0)) + + self.assertEqual(overlap_count, expected_count) + + def test_subclass_overlap_mask(self): + """Ensures overlap_mask works for subclassed Masks.""" + expected_size = (4, 5) + expected_count = expected_size[0] * expected_size[1] + masks = ( + pygame.mask.Mask(fill=True, size=expected_size), + SubMask(expected_size, True), + ) + arg_masks = ( + pygame.mask.Mask(fill=True, size=expected_size), + SubMask(expected_size, True), + ) + + # Test different combinations of subclassed and non-subclassed Masks. + for mask in masks: + for arg_mask in arg_masks: + overlap_mask = mask.overlap_mask(arg_mask, (0, 0)) + + self.assertIsInstance(overlap_mask, pygame.mask.Mask) + self.assertNotIsInstance(overlap_mask, SubMask) + self.assertEqual(overlap_mask.count(), expected_count) + self.assertEqual(overlap_mask.get_size(), expected_size) + + def test_subclass_fill(self): + """Ensures fill works for subclassed Masks.""" + mask_size = (2, 4) + expected_count = mask_size[0] * mask_size[1] + mask = SubMask(fill=False, size=mask_size) + + mask.fill() + + self.assertEqual(mask.count(), expected_count) + + def test_subclass_clear(self): + """Ensures clear works for subclassed Masks.""" + mask_size = (4, 3) + expected_count = 0 + mask = SubMask(mask_size, True) + + mask.clear() + + self.assertEqual(mask.count(), expected_count) + + def test_subclass_invert(self): + """Ensures invert works for subclassed Masks.""" + mask_size = (1, 4) + expected_count = mask_size[0] * mask_size[1] + mask = SubMask(fill=False, size=mask_size) + + mask.invert() + + self.assertEqual(mask.count(), expected_count) + + def test_subclass_scale(self): + """Ensures scale works for subclassed Masks.""" + expected_size = (5, 2) + mask = SubMask((1, 4)) + + scaled_mask = mask.scale(expected_size) + + self.assertIsInstance(scaled_mask, pygame.mask.Mask) + self.assertNotIsInstance(scaled_mask, SubMask) + self.assertEqual(scaled_mask.get_size(), expected_size) + + def test_subclass_draw(self): + """Ensures draw works for subclassed Masks.""" + mask_size = (5, 4) + expected_count = mask_size[0] * mask_size[1] + arg_masks = ( + pygame.mask.Mask(fill=True, size=mask_size), + SubMask(mask_size, True), + ) + + # Test different combinations of subclassed and non-subclassed Masks. + for mask in (pygame.mask.Mask(mask_size), SubMask(mask_size)): + for arg_mask in arg_masks: + mask.clear() # Clear for each test. + + mask.draw(arg_mask, (0, 0)) + + self.assertEqual(mask.count(), expected_count) + + def test_subclass_erase(self): + """Ensures erase works for subclassed Masks.""" + mask_size = (3, 4) + expected_count = 0 + masks = (pygame.mask.Mask(mask_size, True), SubMask(mask_size, True)) + arg_masks = (pygame.mask.Mask(mask_size, True), SubMask(mask_size, True)) + + # Test different combinations of subclassed and non-subclassed Masks. + for mask in masks: + for arg_mask in arg_masks: + mask.fill() # Fill for each test. + + mask.erase(arg_mask, (0, 0)) + + self.assertEqual(mask.count(), expected_count) + + def test_subclass_count(self): + """Ensures count works for subclassed Masks.""" + mask_size = (5, 2) + expected_count = mask_size[0] * mask_size[1] - 1 + mask = SubMask(fill=True, size=mask_size) + mask.set_at((1, 1), 0) + + count = mask.count() + + self.assertEqual(count, expected_count) + + def test_subclass_centroid(self): + """Ensures centroid works for subclassed Masks.""" + expected_centroid = (0, 0) + mask_size = (3, 2) + mask = SubMask((3, 2)) + + centroid = mask.centroid() + + self.assertEqual(centroid, expected_centroid) + + def test_subclass_angle(self): + """Ensures angle works for subclassed Masks.""" + expected_angle = 0.0 + mask = SubMask(size=(5, 4)) + + angle = mask.angle() + + self.assertAlmostEqual(angle, expected_angle) + + def test_subclass_outline(self): + """Ensures outline works for subclassed Masks.""" + expected_outline = [] + mask = SubMask((3, 4)) + + outline = mask.outline() + + self.assertListEqual(outline, expected_outline) + + def test_subclass_convolve(self): + """Ensures convolve works for subclassed Masks.""" + width, height = 7, 5 + mask_size = (width, height) + expected_count = 0 + expected_size = (max(0, width * 2 - 1), max(0, height * 2 - 1)) + + arg_masks = (pygame.mask.Mask(mask_size), SubMask(mask_size)) + output_masks = (pygame.mask.Mask(mask_size), SubMask(mask_size)) + + # Test different combinations of subclassed and non-subclassed Masks. + for mask in (pygame.mask.Mask(mask_size), SubMask(mask_size)): + for arg_mask in arg_masks: + convolve_mask = mask.convolve(arg_mask) + + self.assertIsInstance(convolve_mask, pygame.mask.Mask) + self.assertNotIsInstance(convolve_mask, SubMask) + self.assertEqual(convolve_mask.count(), expected_count) + self.assertEqual(convolve_mask.get_size(), expected_size) + + # Test subclassed masks for the output_mask as well. + for output_mask in output_masks: + convolve_mask = mask.convolve(arg_mask, output_mask) + + self.assertIsInstance(convolve_mask, pygame.mask.Mask) + self.assertEqual(convolve_mask.count(), expected_count) + self.assertEqual(convolve_mask.get_size(), mask_size) + + if isinstance(output_mask, SubMask): + self.assertIsInstance(convolve_mask, SubMask) + else: + self.assertNotIsInstance(convolve_mask, SubMask) + + def test_subclass_connected_component(self): + """Ensures connected_component works for subclassed Masks.""" + expected_count = 0 + expected_size = (3, 4) + mask = SubMask(expected_size) + + cc_mask = mask.connected_component() + + self.assertIsInstance(cc_mask, pygame.mask.Mask) + self.assertNotIsInstance(cc_mask, SubMask) + self.assertEqual(cc_mask.count(), expected_count) + self.assertEqual(cc_mask.get_size(), expected_size) + + def test_subclass_connected_components(self): + """Ensures connected_components works for subclassed Masks.""" + expected_ccs = [] + mask = SubMask((5, 4)) + + ccs = mask.connected_components() + + self.assertListEqual(ccs, expected_ccs) + + def test_subclass_get_bounding_rects(self): + """Ensures get_bounding_rects works for subclassed Masks.""" + expected_bounding_rects = [] + mask = SubMask((3, 2)) + + bounding_rects = mask.get_bounding_rects() + + self.assertListEqual(bounding_rects, expected_bounding_rects) + + def test_subclass_to_surface(self): + """Ensures to_surface works for subclassed Masks.""" + expected_color = pygame.Color("blue") + size = (5, 3) + mask = SubMask(size, fill=True) + surface = pygame.Surface(size, SRCALPHA, 32) + surface.fill(pygame.Color("red")) + + to_surface = mask.to_surface(surface, setcolor=expected_color) + + self.assertIs(to_surface, surface) + self.assertEqual(to_surface.get_size(), size) + assertSurfaceFilled(self, to_surface, expected_color) + + +@unittest.skipIf(IS_PYPY, "pypy has lots of mask failures") # TODO +class MaskModuleTest(unittest.TestCase): + def test_from_surface(self): + """Ensures from_surface creates a mask with the correct bits set. + + This test checks the masks created by the from_surface function using + 16 and 32 bit surfaces. Each alpha value (0-255) is tested against + several different threshold values. + Note: On 16 bit surface the requested alpha value can differ from what + is actually set. This test uses the value read from the surface. + """ + threshold_count = 256 + surface_color = [55, 155, 255, 0] + expected_size = (11, 9) + all_set_count = expected_size[0] * expected_size[1] + none_set_count = 0 + + for depth in (16, 32): + surface = pygame.Surface(expected_size, SRCALPHA, depth) + + for alpha in range(threshold_count): + surface_color[3] = alpha + surface.fill(surface_color) + + if depth < 32: + # On surfaces with depths < 32 the requested alpha can be + # different than what gets set. Use the value read from the + # surface. + alpha = surface.get_at((0, 0))[3] + + # Test the mask created at threshold values low, high and + # around alpha. + threshold_test_values = set( + [-1, 0, alpha - 1, alpha, alpha + 1, 255, 256] + ) + + for threshold in threshold_test_values: + msg = "depth={}, alpha={}, threshold={}".format( + depth, alpha, threshold + ) + + if alpha > threshold: + expected_count = all_set_count + else: + expected_count = none_set_count + + mask = pygame.mask.from_surface( + surface=surface, threshold=threshold + ) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + self.assertEqual(mask.count(), expected_count, msg) + + def test_from_surface__different_alphas_32bit(self): + """Ensures from_surface creates a mask with the correct bits set + when pixels have different alpha values (32 bits surfaces). + + This test checks the masks created by the from_surface function using + a 32 bit surface. The surface is created with each pixel having a + different alpha value (0-255). This surface is tested over a range + of threshold values (0-255). + """ + offset = (0, 0) + threshold_count = 256 + surface_color = [10, 20, 30, 0] + expected_size = (threshold_count, 1) + expected_mask = pygame.Mask(expected_size, fill=True) + surface = pygame.Surface(expected_size, SRCALPHA, 32) + + # Give each pixel a different alpha. + surface.lock() # Lock for possible speed up. + for a in range(threshold_count): + surface_color[3] = a + surface.set_at((a, 0), surface_color) + surface.unlock() + + # Test the mask created for each different alpha threshold. + for threshold in range(threshold_count): + msg = "threshold={}".format(threshold) + expected_mask.set_at((threshold, 0), 0) + expected_count = expected_mask.count() + + mask = pygame.mask.from_surface(surface, threshold) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + self.assertEqual(mask.count(), expected_count, msg) + self.assertEqual( + mask.overlap_area(expected_mask, offset), expected_count, msg + ) + + def test_from_surface__different_alphas_16bit(self): + """Ensures from_surface creates a mask with the correct bits set + when pixels have different alpha values (16 bit surfaces). + + This test checks the masks created by the from_surface function using + a 16 bit surface. Each pixel of the surface is set with a different + alpha value (0-255), but since this is a 16 bit surface the requested + alpha value can differ from what is actually set. The resulting surface + will have groups of alpha values which complicates the test as the + alpha groups will all be set/unset at a given threshold. The setup + calculates these groups and an expected mask for each. This test data + is then used to test each alpha grouping over a range of threshold + values. + """ + threshold_count = 256 + surface_color = [110, 120, 130, 0] + expected_size = (threshold_count, 1) + surface = pygame.Surface(expected_size, SRCALPHA, 16) + + # Give each pixel a different alpha. + surface.lock() # Lock for possible speed up. + for a in range(threshold_count): + surface_color[3] = a + surface.set_at((a, 0), surface_color) + surface.unlock() + + alpha_thresholds = OrderedDict() + special_thresholds = set() + + # Create the threshold ranges and identify any thresholds that need + # special handling. + for threshold in range(threshold_count): + # On surfaces with depths < 32 the requested alpha can be different + # than what gets set. Use the value read from the surface. + alpha = surface.get_at((threshold, 0))[3] + + if alpha not in alpha_thresholds: + alpha_thresholds[alpha] = [threshold] + else: + alpha_thresholds[alpha].append(threshold) + + if threshold < alpha: + special_thresholds.add(threshold) + + # Use each threshold group to create an expected mask. + test_data = [] # [(from_threshold, to_threshold, expected_mask), ...] + offset = (0, 0) + erase_mask = pygame.Mask(expected_size) + exp_mask = pygame.Mask(expected_size, fill=True) + + for thresholds in alpha_thresholds.values(): + for threshold in thresholds: + if threshold in special_thresholds: + # Any special thresholds just reuse previous exp_mask. + test_data.append((threshold, threshold + 1, exp_mask)) + else: + to_threshold = thresholds[-1] + 1 + + # Make the expected mask by erasing the unset bits. + for thres in range(to_threshold): + erase_mask.set_at((thres, 0), 1) + + exp_mask = pygame.Mask(expected_size, fill=True) + exp_mask.erase(erase_mask, offset) + test_data.append((threshold, to_threshold, exp_mask)) + break + + # All the setup is done. Now test the masks created over the threshold + # ranges. + for from_threshold, to_threshold, expected_mask in test_data: + expected_count = expected_mask.count() + + for threshold in range(from_threshold, to_threshold): + msg = "threshold={}".format(threshold) + + mask = pygame.mask.from_surface(surface, threshold) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + self.assertEqual(mask.count(), expected_count, msg) + self.assertEqual( + mask.overlap_area(expected_mask, offset), expected_count, msg + ) + + def test_from_surface__with_colorkey_mask_cleared(self): + """Ensures from_surface creates a mask with the correct bits set + when the surface uses a colorkey. + + The surface is filled with the colorkey color so the resulting masks + are expected to have no bits set. + """ + colorkeys = ((0, 0, 0), (1, 2, 3), (50, 100, 200), (255, 255, 255)) + expected_size = (7, 11) + expected_count = 0 + + for depth in (8, 16, 24, 32): + msg = "depth={}".format(depth) + surface = pygame.Surface(expected_size, 0, depth) + + for colorkey in colorkeys: + surface.set_colorkey(colorkey) + # With some depths (i.e. 8 and 16) the actual colorkey can be + # different than what was requested via the set. + surface.fill(surface.get_colorkey()) + + mask = pygame.mask.from_surface(surface) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + self.assertEqual(mask.count(), expected_count, msg) + + def test_from_surface__with_colorkey_mask_filled(self): + """Ensures from_surface creates a mask with the correct bits set + when the surface uses a colorkey. + + The surface is filled with a color that is not the colorkey color so + the resulting masks are expected to have all bits set. + """ + colorkeys = ((0, 0, 0), (1, 2, 3), (10, 100, 200), (255, 255, 255)) + surface_color = (50, 100, 200) + expected_size = (11, 7) + expected_count = expected_size[0] * expected_size[1] + + for depth in (8, 16, 24, 32): + msg = "depth={}".format(depth) + surface = pygame.Surface(expected_size, 0, depth) + surface.fill(surface_color) + + for colorkey in colorkeys: + surface.set_colorkey(colorkey) + + mask = pygame.mask.from_surface(surface) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + self.assertEqual(mask.count(), expected_count, msg) + + def test_from_surface__with_colorkey_mask_pattern(self): + """Ensures from_surface creates a mask with the correct bits set + when the surface uses a colorkey. + + The surface is filled with alternating pixels of colorkey and + non-colorkey colors, so the resulting masks are expected to have + alternating bits set. + """ + + def alternate(func, set_value, unset_value, width, height): + # Helper function to set alternating values. + setbit = False + for pos in ((x, y) for x in range(width) for y in range(height)): + func(pos, set_value if setbit else unset_value) + setbit = not setbit + + surface_color = (5, 10, 20) + colorkey = (50, 60, 70) + expected_size = (11, 2) + expected_mask = pygame.mask.Mask(expected_size) + alternate(expected_mask.set_at, 1, 0, *expected_size) + expected_count = expected_mask.count() + offset = (0, 0) + + for depth in (8, 16, 24, 32): + msg = "depth={}".format(depth) + surface = pygame.Surface(expected_size, 0, depth) + # Fill the surface with alternating colors. + alternate(surface.set_at, surface_color, colorkey, *expected_size) + surface.set_colorkey(colorkey) + + mask = pygame.mask.from_surface(surface) + + self.assertIsInstance(mask, pygame.mask.Mask, msg) + self.assertEqual(mask.get_size(), expected_size, msg) + self.assertEqual(mask.count(), expected_count, msg) + self.assertEqual( + mask.overlap_area(expected_mask, offset), expected_count, msg + ) + + def test_from_threshold(self): + """Does mask.from_threshold() work correctly?""" + + a = [16, 24, 32] + + for i in a: + surf = pygame.surface.Surface((70, 70), 0, i) + surf.fill((100, 50, 200), (20, 20, 20, 20)) + mask = pygame.mask.from_threshold( + surf, (100, 50, 200, 255), (10, 10, 10, 255) + ) + + rects = mask.get_bounding_rects() + + self.assertEqual(mask.count(), 400) + self.assertEqual(mask.get_bounding_rects(), [pygame.Rect((20, 20, 20, 20))]) + + for i in a: + surf = pygame.surface.Surface((70, 70), 0, i) + surf2 = pygame.surface.Surface((70, 70), 0, i) + surf.fill((100, 100, 100)) + surf2.fill((150, 150, 150)) + surf2.fill((100, 100, 100), (40, 40, 10, 10)) + mask = pygame.mask.from_threshold( + surface=surf, + color=(0, 0, 0, 0), + threshold=(10, 10, 10, 255), + othersurface=surf2, + ) + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), 100) + self.assertEqual(mask.get_bounding_rects(), [pygame.Rect((40, 40, 10, 10))]) + + def test_zero_size_from_surface(self): + """Ensures from_surface can create masks from zero sized surfaces.""" + for size in ((100, 0), (0, 100), (0, 0)): + mask = pygame.mask.from_surface(pygame.Surface(size)) + + self.assertIsInstance(mask, pygame.mask.MaskType, "size={}".format(size)) + self.assertEqual(mask.get_size(), size) + + def test_zero_size_from_threshold(self): + a = [16, 24, 32] + sizes = ((100, 0), (0, 100), (0, 0)) + + for size in sizes: + for i in a: + surf = pygame.surface.Surface(size, 0, i) + surf.fill((100, 50, 200), (20, 20, 20, 20)) + mask = pygame.mask.from_threshold( + surf, (100, 50, 200, 255), (10, 10, 10, 255) + ) + + self.assertEqual(mask.count(), 0) + + rects = mask.get_bounding_rects() + self.assertEqual(rects, []) + + for i in a: + surf = pygame.surface.Surface(size, 0, i) + surf2 = pygame.surface.Surface(size, 0, i) + surf.fill((100, 100, 100)) + surf2.fill((150, 150, 150)) + surf2.fill((100, 100, 100), (40, 40, 10, 10)) + mask = pygame.mask.from_threshold( + surf, (0, 0, 0, 0), (10, 10, 10, 255), surf2 + ) + + self.assertIsInstance(mask, pygame.mask.Mask) + self.assertEqual(mask.count(), 0) + + rects = mask.get_bounding_rects() + self.assertEqual(rects, []) + + def test_buffer_interface(self): + size = (1000, 100) + pixels_set = ((0, 1), (100, 10), (173, 90)) + pixels_unset = ((0, 0), (101, 10), (173, 91)) + + mask = pygame.Mask(size) + for point in pixels_set: + mask.set_at(point, 1) + + view = memoryview(mask) + intwidth = 8 * view.strides[1] + + for point in pixels_set: + x, y = point + col = x // intwidth + self.assertEqual( + (view[col, y] >> (x % intwidth)) & 1, + 1, + "the pixel at {} is not set to 1".format(point), + ) + + for point in pixels_unset: + x, y = point + col = x // intwidth + self.assertEqual( + (view[col, y] >> (x % intwidth)) & 1, + 0, + "the pixel at {} is not set to 0".format(point), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/math_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/math_test.py new file mode 100644 index 0000000..1d8cd63 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/math_test.py @@ -0,0 +1,2326 @@ +# -*- coding: utf-8 -*- +import sys +import unittest +import math +import platform + +import pygame.math +from pygame.math import Vector2, Vector3 + +IS_PYPY = "PyPy" == platform.python_implementation() + + +class Vector2TypeTest(unittest.TestCase): + def setUp(self): + self.zeroVec = Vector2() + self.e1 = Vector2(1, 0) + self.e2 = Vector2(0, 1) + self.t1 = (1.2, 3.4) + self.l1 = list(self.t1) + self.v1 = Vector2(self.t1) + self.t2 = (5.6, 7.8) + self.l2 = list(self.t2) + self.v2 = Vector2(self.t2) + self.s1 = 5.6 + self.s2 = 7.8 + + def testConstructionDefault(self): + v = Vector2() + self.assertEqual(v.x, 0.0) + self.assertEqual(v.y, 0.0) + + def testConstructionScalar(self): + v = Vector2(1) + self.assertEqual(v.x, 1.0) + self.assertEqual(v.y, 1.0) + + def testConstructionScalarKeywords(self): + v = Vector2(x=1) + self.assertEqual(v.x, 1.0) + self.assertEqual(v.y, 1.0) + + def testConstructionKeywords(self): + v = Vector2(x=1, y=2) + self.assertEqual(v.x, 1.0) + self.assertEqual(v.y, 2.0) + + def testConstructionXY(self): + v = Vector2(1.2, 3.4) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testConstructionTuple(self): + v = Vector2((1.2, 3.4)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testConstructionList(self): + v = Vector2([1.2, 3.4]) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testConstructionVector2(self): + v = Vector2(Vector2(1.2, 3.4)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testAttributeAccess(self): + tmp = self.v1.x + self.assertEqual(tmp, self.v1.x) + self.assertEqual(tmp, self.v1[0]) + tmp = self.v1.y + self.assertEqual(tmp, self.v1.y) + self.assertEqual(tmp, self.v1[1]) + self.v1.x = 3.141 + self.assertEqual(self.v1.x, 3.141) + self.v1.y = 3.141 + self.assertEqual(self.v1.y, 3.141) + + def assign_nonfloat(): + v = Vector2() + v.x = "spam" + + self.assertRaises(TypeError, assign_nonfloat) + + def testCopy(self): + v_copy0 = Vector2(2004.0, 2022.0) + v_copy1 = v_copy0.copy() + self.assertEqual(v_copy0.x, v_copy1.x) + self.assertEqual(v_copy0.y, v_copy1.y) + + def testSequence(self): + v = Vector2(1.2, 3.4) + Vector2()[:] + self.assertEqual(len(v), 2) + self.assertEqual(v[0], 1.2) + self.assertEqual(v[1], 3.4) + self.assertRaises(IndexError, lambda: v[2]) + self.assertEqual(v[-1], 3.4) + self.assertEqual(v[-2], 1.2) + self.assertRaises(IndexError, lambda: v[-3]) + self.assertEqual(v[:], [1.2, 3.4]) + self.assertEqual(v[1:], [3.4]) + self.assertEqual(v[:1], [1.2]) + self.assertEqual(list(v), [1.2, 3.4]) + self.assertEqual(tuple(v), (1.2, 3.4)) + v[0] = 5.6 + v[1] = 7.8 + self.assertEqual(v.x, 5.6) + self.assertEqual(v.y, 7.8) + v[:] = [9.1, 11.12] + self.assertEqual(v.x, 9.1) + self.assertEqual(v.y, 11.12) + + def overpopulate(): + v = Vector2() + v[:] = [1, 2, 3] + + self.assertRaises(ValueError, overpopulate) + + def underpopulate(): + v = Vector2() + v[:] = [1] + + self.assertRaises(ValueError, underpopulate) + + def assign_nonfloat(): + v = Vector2() + v[0] = "spam" + + self.assertRaises(TypeError, assign_nonfloat) + + def testExtendedSlicing(self): + # deletion + def delSlice(vec, start=None, stop=None, step=None): + if start is not None and stop is not None and step is not None: + del vec[start:stop:step] + elif start is not None and stop is None and step is not None: + del vec[start::step] + elif start is None and stop is None and step is not None: + del vec[::step] + + v = Vector2(self.v1) + self.assertRaises(TypeError, delSlice, v, None, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, 2, 1) + + # assignment + v = Vector2(self.v1) + v[::2] = [-1] + self.assertEqual(v, [-1, self.v1.y]) + v = Vector2(self.v1) + v[::-2] = [10] + self.assertEqual(v, [self.v1.x, 10]) + v = Vector2(self.v1) + v[::-1] = v + self.assertEqual(v, [self.v1.y, self.v1.x]) + a = Vector2(self.v1) + b = Vector2(self.v1) + c = Vector2(self.v1) + a[1:2] = [2.2] + b[slice(1, 2)] = [2.2] + c[1:2:] = (2.2,) + self.assertEqual(a, b) + self.assertEqual(a, c) + self.assertEqual(type(a), type(self.v1)) + self.assertEqual(type(b), type(self.v1)) + self.assertEqual(type(c), type(self.v1)) + + def testAdd(self): + v3 = self.v1 + self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.v2.x) + self.assertEqual(v3.y, self.v1.y + self.v2.y) + v3 = self.v1 + self.t2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.t2[0]) + self.assertEqual(v3.y, self.v1.y + self.t2[1]) + v3 = self.v1 + self.l2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.l2[0]) + self.assertEqual(v3.y, self.v1.y + self.l2[1]) + v3 = self.t1 + self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] + self.v2.x) + self.assertEqual(v3.y, self.t1[1] + self.v2.y) + v3 = self.l1 + self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] + self.v2.x) + self.assertEqual(v3.y, self.l1[1] + self.v2.y) + + def testSub(self): + v3 = self.v1 - self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.v2.x) + self.assertEqual(v3.y, self.v1.y - self.v2.y) + v3 = self.v1 - self.t2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.t2[0]) + self.assertEqual(v3.y, self.v1.y - self.t2[1]) + v3 = self.v1 - self.l2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.l2[0]) + self.assertEqual(v3.y, self.v1.y - self.l2[1]) + v3 = self.t1 - self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] - self.v2.x) + self.assertEqual(v3.y, self.t1[1] - self.v2.y) + v3 = self.l1 - self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] - self.v2.x) + self.assertEqual(v3.y, self.l1[1] - self.v2.y) + + def testScalarMultiplication(self): + v = self.s1 * self.v1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.s1 * self.v1.x) + self.assertEqual(v.y, self.s1 * self.v1.y) + v = self.v1 * self.s2 + self.assertEqual(v.x, self.v1.x * self.s2) + self.assertEqual(v.y, self.v1.y * self.s2) + + def testScalarDivision(self): + v = self.v1 / self.s1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertAlmostEqual(v.x, self.v1.x / self.s1) + self.assertAlmostEqual(v.y, self.v1.y / self.s1) + v = self.v1 // self.s2 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x // self.s2) + self.assertEqual(v.y, self.v1.y // self.s2) + + def testBool(self): + self.assertEqual(bool(self.zeroVec), False) + self.assertEqual(bool(self.v1), True) + self.assertTrue(not self.zeroVec) + self.assertTrue(self.v1) + + def testUnary(self): + v = +self.v1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x) + self.assertEqual(v.y, self.v1.y) + self.assertNotEqual(id(v), id(self.v1)) + v = -self.v1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, -self.v1.x) + self.assertEqual(v.y, -self.v1.y) + self.assertNotEqual(id(v), id(self.v1)) + + def testCompare(self): + int_vec = Vector2(3, -2) + flt_vec = Vector2(3.0, -2.0) + zero_vec = Vector2(0, 0) + self.assertEqual(int_vec == flt_vec, True) + self.assertEqual(int_vec != flt_vec, False) + self.assertEqual(int_vec != zero_vec, True) + self.assertEqual(flt_vec == zero_vec, False) + self.assertEqual(int_vec == (3, -2), True) + self.assertEqual(int_vec != (3, -2), False) + self.assertEqual(int_vec != [0, 0], True) + self.assertEqual(int_vec == [0, 0], False) + self.assertEqual(int_vec != 5, True) + self.assertEqual(int_vec == 5, False) + self.assertEqual(int_vec != [3, -2, 0], True) + self.assertEqual(int_vec == [3, -2, 0], False) + + def testStr(self): + v = Vector2(1.2, 3.4) + self.assertEqual(str(v), "[1.2, 3.4]") + + def testRepr(self): + v = Vector2(1.2, 3.4) + self.assertEqual(v.__repr__(), "") + self.assertEqual(v, Vector2(v.__repr__())) + + def testIter(self): + it = self.v1.__iter__() + next_ = it.__next__ + self.assertEqual(next_(), self.v1[0]) + self.assertEqual(next_(), self.v1[1]) + self.assertRaises(StopIteration, lambda: next_()) + it1 = self.v1.__iter__() + it2 = self.v1.__iter__() + self.assertNotEqual(id(it1), id(it2)) + self.assertEqual(id(it1), id(it1.__iter__())) + self.assertEqual(list(it1), list(it2)) + self.assertEqual(list(self.v1.__iter__()), self.l1) + idx = 0 + for val in self.v1: + self.assertEqual(val, self.v1[idx]) + idx += 1 + + def test_rotate(self): + v1 = Vector2(1, 0) + v2 = v1.rotate(90) + v3 = v1.rotate(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v2.x, 0) + self.assertEqual(v2.y, 1) + self.assertEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + v1 = Vector2(-1, -1) + v2 = v1.rotate(-90) + self.assertEqual(v2.x, -1) + self.assertEqual(v2.y, 1) + v2 = v1.rotate(360) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + v2 = v1.rotate(0) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + # issue 214 + self.assertEqual(Vector2(0, 1).rotate(359.99999999), Vector2(0, 1)) + + def test_rotate_rad(self): + tests = ( + ((1, 0), math.pi), + ((1, 0), math.pi / 2), + ((1, 0), -math.pi / 2), + ((1, 0), math.pi / 4), + ) + for initialVec, radians in tests: + self.assertEqual( + Vector2(initialVec).rotate_rad(radians), + (math.cos(radians), math.sin(radians)), + ) + + def test_rotate_ip(self): + v = Vector2(1, 0) + self.assertEqual(v.rotate_ip(90), None) + self.assertEqual(v.x, 0) + self.assertEqual(v.y, 1) + v = Vector2(-1, -1) + v.rotate_ip(-90) + self.assertEqual(v.x, -1) + self.assertEqual(v.y, 1) + + def test_rotate_rad_ip(self): + tests = ( + ((1, 0), math.pi), + ((1, 0), math.pi / 2), + ((1, 0), -math.pi / 2), + ((1, 0), math.pi / 4), + ) + for initialVec, radians in tests: + vec = Vector2(initialVec) + vec.rotate_rad_ip(radians) + self.assertEqual(vec, (math.cos(radians), math.sin(radians))) + + def test_normalize(self): + v = self.v1.normalize() + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y, 1.0) + # v1 is unchanged + self.assertEqual(self.v1.x, self.l1[0]) + self.assertEqual(self.v1.y, self.l1[1]) + # v2 is parallel to v1 + self.assertAlmostEqual(self.v1.x * v.y - self.v1.y * v.x, 0.0) + self.assertRaises(ValueError, lambda: self.zeroVec.normalize()) + + def test_normalize_ip(self): + v = +self.v1 + # v has length != 1 before normalizing + self.assertNotEqual(v.x * v.x + v.y * v.y, 1.0) + # inplace operations should return None + self.assertEqual(v.normalize_ip(), None) + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y, 1.0) + # v2 is parallel to v1 + self.assertAlmostEqual(self.v1.x * v.y - self.v1.y * v.x, 0.0) + self.assertRaises(ValueError, lambda: self.zeroVec.normalize_ip()) + + def test_is_normalized(self): + self.assertEqual(self.v1.is_normalized(), False) + v = self.v1.normalize() + self.assertEqual(v.is_normalized(), True) + self.assertEqual(self.e2.is_normalized(), True) + self.assertEqual(self.zeroVec.is_normalized(), False) + + def test_cross(self): + self.assertEqual( + self.v1.cross(self.v2), self.v1.x * self.v2.y - self.v1.y * self.v2.x + ) + self.assertEqual( + self.v1.cross(self.l2), self.v1.x * self.l2[1] - self.v1.y * self.l2[0] + ) + self.assertEqual( + self.v1.cross(self.t2), self.v1.x * self.t2[1] - self.v1.y * self.t2[0] + ) + self.assertEqual(self.v1.cross(self.v2), -self.v2.cross(self.v1)) + self.assertEqual(self.v1.cross(self.v1), 0) + + def test_dot(self): + self.assertAlmostEqual( + self.v1.dot(self.v2), self.v1.x * self.v2.x + self.v1.y * self.v2.y + ) + self.assertAlmostEqual( + self.v1.dot(self.l2), self.v1.x * self.l2[0] + self.v1.y * self.l2[1] + ) + self.assertAlmostEqual( + self.v1.dot(self.t2), self.v1.x * self.t2[0] + self.v1.y * self.t2[1] + ) + self.assertEqual(self.v1.dot(self.v2), self.v2.dot(self.v1)) + self.assertEqual(self.v1.dot(self.v2), self.v1 * self.v2) + + def test_angle_to(self): + self.assertEqual( + self.v1.rotate(self.v1.angle_to(self.v2)).normalize(), self.v2.normalize() + ) + self.assertEqual(Vector2(1, 1).angle_to((-1, 1)), 90) + self.assertEqual(Vector2(1, 0).angle_to((0, -1)), -90) + self.assertEqual(Vector2(1, 0).angle_to((-1, 1)), 135) + self.assertEqual(abs(Vector2(1, 0).angle_to((-1, 0))), 180) + + def test_scale_to_length(self): + v = Vector2(1, 1) + v.scale_to_length(2.5) + self.assertEqual(v, Vector2(2.5, 2.5) / math.sqrt(2)) + self.assertRaises(ValueError, lambda: self.zeroVec.scale_to_length(1)) + self.assertEqual(v.scale_to_length(0), None) + self.assertEqual(v, self.zeroVec) + + def test_length(self): + self.assertEqual(Vector2(3, 4).length(), 5) + self.assertEqual(Vector2(-3, 4).length(), 5) + self.assertEqual(self.zeroVec.length(), 0) + + def test_length_squared(self): + self.assertEqual(Vector2(3, 4).length_squared(), 25) + self.assertEqual(Vector2(-3, 4).length_squared(), 25) + self.assertEqual(self.zeroVec.length_squared(), 0) + + def test_reflect(self): + v = Vector2(1, -1) + n = Vector2(0, 1) + self.assertEqual(v.reflect(n), Vector2(1, 1)) + self.assertEqual(v.reflect(3 * n), v.reflect(n)) + self.assertEqual(v.reflect(-v), -v) + self.assertRaises(ValueError, lambda: v.reflect(self.zeroVec)) + + def test_reflect_ip(self): + v1 = Vector2(1, -1) + v2 = Vector2(v1) + n = Vector2(0, 1) + self.assertEqual(v2.reflect_ip(n), None) + self.assertEqual(v2, Vector2(1, 1)) + v2 = Vector2(v1) + v2.reflect_ip(3 * n) + self.assertEqual(v2, v1.reflect(n)) + v2 = Vector2(v1) + v2.reflect_ip(-v1) + self.assertEqual(v2, -v1) + self.assertRaises(ValueError, lambda: v2.reflect_ip(Vector2())) + + def test_distance_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_to(self.e2), math.sqrt(2)) + self.assertAlmostEqual( + self.v1.distance_to(self.v2), math.sqrt(diff.x * diff.x + diff.y * diff.y) + ) + self.assertEqual(self.v1.distance_to(self.v1), 0) + self.assertEqual(self.v1.distance_to(self.v2), self.v2.distance_to(self.v1)) + + def test_distance_squared_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_squared_to(self.e2), 2) + self.assertAlmostEqual( + self.v1.distance_squared_to(self.v2), diff.x * diff.x + diff.y * diff.y + ) + self.assertEqual(self.v1.distance_squared_to(self.v1), 0) + self.assertEqual( + self.v1.distance_squared_to(self.v2), self.v2.distance_squared_to(self.v1) + ) + + def test_update(self): + v = Vector2(3, 4) + v.update(0) + self.assertEqual(v, Vector2((0, 0))) + v.update(5, 1) + self.assertEqual(v, Vector2(5, 1)) + v.update((4, 1)) + self.assertNotEqual(v, Vector2((5, 1))) + + def test_swizzle(self): + self.assertEqual(self.v1.yx, (self.v1.y, self.v1.x)) + self.assertEqual( + self.v1.xxyyxy, + (self.v1.x, self.v1.x, self.v1.y, self.v1.y, self.v1.x, self.v1.y), + ) + self.v1.xy = self.t2 + self.assertEqual(self.v1, self.t2) + self.v1.yx = self.t2 + self.assertEqual(self.v1, (self.t2[1], self.t2[0])) + self.assertEqual(type(self.v1), Vector2) + + def invalidSwizzleX(): + Vector2().xx = (1, 2) + + def invalidSwizzleY(): + Vector2().yy = (1, 2) + + self.assertRaises(AttributeError, invalidSwizzleX) + self.assertRaises(AttributeError, invalidSwizzleY) + + def invalidAssignment(): + Vector2().xy = 3 + + self.assertRaises(TypeError, invalidAssignment) + + def unicodeAttribute(): + getattr(Vector2(), "ä") + + self.assertRaises(AttributeError, unicodeAttribute) + + def test_swizzle_return_types(self): + self.assertEqual(type(self.v1.x), float) + self.assertEqual(type(self.v1.xy), Vector2) + self.assertEqual(type(self.v1.xyx), Vector3) + # but we don't have vector4 or above... so tuple. + self.assertEqual(type(self.v1.xyxy), tuple) + self.assertEqual(type(self.v1.xyxyx), tuple) + + def test_elementwise(self): + # behaviour for "elementwise op scalar" + self.assertEqual( + self.v1.elementwise() + self.s1, (self.v1.x + self.s1, self.v1.y + self.s1) + ) + self.assertEqual( + self.v1.elementwise() - self.s1, (self.v1.x - self.s1, self.v1.y - self.s1) + ) + self.assertEqual( + self.v1.elementwise() * self.s2, (self.v1.x * self.s2, self.v1.y * self.s2) + ) + self.assertEqual( + self.v1.elementwise() / self.s2, (self.v1.x / self.s2, self.v1.y / self.s2) + ) + self.assertEqual( + self.v1.elementwise() // self.s1, + (self.v1.x // self.s1, self.v1.y // self.s1), + ) + self.assertEqual( + self.v1.elementwise() ** self.s1, + (self.v1.x ** self.s1, self.v1.y ** self.s1), + ) + self.assertEqual( + self.v1.elementwise() % self.s1, (self.v1.x % self.s1, self.v1.y % self.s1) + ) + self.assertEqual( + self.v1.elementwise() > self.s1, self.v1.x > self.s1 and self.v1.y > self.s1 + ) + self.assertEqual( + self.v1.elementwise() < self.s1, self.v1.x < self.s1 and self.v1.y < self.s1 + ) + self.assertEqual( + self.v1.elementwise() == self.s1, + self.v1.x == self.s1 and self.v1.y == self.s1, + ) + self.assertEqual( + self.v1.elementwise() != self.s1, + self.v1.x != self.s1 and self.v1.y != self.s1, + ) + self.assertEqual( + self.v1.elementwise() >= self.s1, + self.v1.x >= self.s1 and self.v1.y >= self.s1, + ) + self.assertEqual( + self.v1.elementwise() <= self.s1, + self.v1.x <= self.s1 and self.v1.y <= self.s1, + ) + self.assertEqual( + self.v1.elementwise() != self.s1, + self.v1.x != self.s1 and self.v1.y != self.s1, + ) + # behaviour for "scalar op elementwise" + self.assertEqual(5 + self.v1.elementwise(), Vector2(5, 5) + self.v1) + self.assertEqual(3.5 - self.v1.elementwise(), Vector2(3.5, 3.5) - self.v1) + self.assertEqual(7.5 * self.v1.elementwise(), 7.5 * self.v1) + self.assertEqual( + -3.5 / self.v1.elementwise(), (-3.5 / self.v1.x, -3.5 / self.v1.y) + ) + self.assertEqual( + -3.5 // self.v1.elementwise(), (-3.5 // self.v1.x, -3.5 // self.v1.y) + ) + self.assertEqual( + -(3.5 ** self.v1.elementwise()), (-(3.5 ** self.v1.x), -(3.5 ** self.v1.y)) + ) + self.assertEqual(3 % self.v1.elementwise(), (3 % self.v1.x, 3 % self.v1.y)) + self.assertEqual(2 < self.v1.elementwise(), 2 < self.v1.x and 2 < self.v1.y) + self.assertEqual(2 > self.v1.elementwise(), 2 > self.v1.x and 2 > self.v1.y) + self.assertEqual(1 == self.v1.elementwise(), 1 == self.v1.x and 1 == self.v1.y) + self.assertEqual(1 != self.v1.elementwise(), 1 != self.v1.x and 1 != self.v1.y) + self.assertEqual(2 <= self.v1.elementwise(), 2 <= self.v1.x and 2 <= self.v1.y) + self.assertEqual( + -7 >= self.v1.elementwise(), -7 >= self.v1.x and -7 >= self.v1.y + ) + self.assertEqual( + -7 != self.v1.elementwise(), -7 != self.v1.x and -7 != self.v1.y + ) + + # behaviour for "elementwise op vector" + self.assertEqual(type(self.v1.elementwise() * self.v2), type(self.v1)) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() - self.v2, self.v1 - self.v2) + self.assertEqual( + self.v1.elementwise() * self.v2, + (self.v1.x * self.v2.x, self.v1.y * self.v2.y), + ) + self.assertEqual( + self.v1.elementwise() / self.v2, + (self.v1.x / self.v2.x, self.v1.y / self.v2.y), + ) + self.assertEqual( + self.v1.elementwise() // self.v2, + (self.v1.x // self.v2.x, self.v1.y // self.v2.y), + ) + self.assertEqual( + self.v1.elementwise() ** self.v2, + (self.v1.x ** self.v2.x, self.v1.y ** self.v2.y), + ) + self.assertEqual( + self.v1.elementwise() % self.v2, + (self.v1.x % self.v2.x, self.v1.y % self.v2.y), + ) + self.assertEqual( + self.v1.elementwise() > self.v2, + self.v1.x > self.v2.x and self.v1.y > self.v2.y, + ) + self.assertEqual( + self.v1.elementwise() < self.v2, + self.v1.x < self.v2.x and self.v1.y < self.v2.y, + ) + self.assertEqual( + self.v1.elementwise() >= self.v2, + self.v1.x >= self.v2.x and self.v1.y >= self.v2.y, + ) + self.assertEqual( + self.v1.elementwise() <= self.v2, + self.v1.x <= self.v2.x and self.v1.y <= self.v2.y, + ) + self.assertEqual( + self.v1.elementwise() == self.v2, + self.v1.x == self.v2.x and self.v1.y == self.v2.y, + ) + self.assertEqual( + self.v1.elementwise() != self.v2, + self.v1.x != self.v2.x and self.v1.y != self.v2.y, + ) + # behaviour for "vector op elementwise" + self.assertEqual(self.v2 + self.v1.elementwise(), self.v2 + self.v1) + self.assertEqual(self.v2 - self.v1.elementwise(), self.v2 - self.v1) + self.assertEqual( + self.v2 * self.v1.elementwise(), + (self.v2.x * self.v1.x, self.v2.y * self.v1.y), + ) + self.assertEqual( + self.v2 / self.v1.elementwise(), + (self.v2.x / self.v1.x, self.v2.y / self.v1.y), + ) + self.assertEqual( + self.v2 // self.v1.elementwise(), + (self.v2.x // self.v1.x, self.v2.y // self.v1.y), + ) + self.assertEqual( + self.v2 ** self.v1.elementwise(), + (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y), + ) + self.assertEqual( + self.v2 % self.v1.elementwise(), + (self.v2.x % self.v1.x, self.v2.y % self.v1.y), + ) + self.assertEqual( + self.v2 < self.v1.elementwise(), + self.v2.x < self.v1.x and self.v2.y < self.v1.y, + ) + self.assertEqual( + self.v2 > self.v1.elementwise(), + self.v2.x > self.v1.x and self.v2.y > self.v1.y, + ) + self.assertEqual( + self.v2 <= self.v1.elementwise(), + self.v2.x <= self.v1.x and self.v2.y <= self.v1.y, + ) + self.assertEqual( + self.v2 >= self.v1.elementwise(), + self.v2.x >= self.v1.x and self.v2.y >= self.v1.y, + ) + self.assertEqual( + self.v2 == self.v1.elementwise(), + self.v2.x == self.v1.x and self.v2.y == self.v1.y, + ) + self.assertEqual( + self.v2 != self.v1.elementwise(), + self.v2.x != self.v1.x and self.v2.y != self.v1.y, + ) + + # behaviour for "elementwise op elementwise" + self.assertEqual( + self.v2.elementwise() + self.v1.elementwise(), self.v2 + self.v1 + ) + self.assertEqual( + self.v2.elementwise() - self.v1.elementwise(), self.v2 - self.v1 + ) + self.assertEqual( + self.v2.elementwise() * self.v1.elementwise(), + (self.v2.x * self.v1.x, self.v2.y * self.v1.y), + ) + self.assertEqual( + self.v2.elementwise() / self.v1.elementwise(), + (self.v2.x / self.v1.x, self.v2.y / self.v1.y), + ) + self.assertEqual( + self.v2.elementwise() // self.v1.elementwise(), + (self.v2.x // self.v1.x, self.v2.y // self.v1.y), + ) + self.assertEqual( + self.v2.elementwise() ** self.v1.elementwise(), + (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y), + ) + self.assertEqual( + self.v2.elementwise() % self.v1.elementwise(), + (self.v2.x % self.v1.x, self.v2.y % self.v1.y), + ) + self.assertEqual( + self.v2.elementwise() < self.v1.elementwise(), + self.v2.x < self.v1.x and self.v2.y < self.v1.y, + ) + self.assertEqual( + self.v2.elementwise() > self.v1.elementwise(), + self.v2.x > self.v1.x and self.v2.y > self.v1.y, + ) + self.assertEqual( + self.v2.elementwise() <= self.v1.elementwise(), + self.v2.x <= self.v1.x and self.v2.y <= self.v1.y, + ) + self.assertEqual( + self.v2.elementwise() >= self.v1.elementwise(), + self.v2.x >= self.v1.x and self.v2.y >= self.v1.y, + ) + self.assertEqual( + self.v2.elementwise() == self.v1.elementwise(), + self.v2.x == self.v1.x and self.v2.y == self.v1.y, + ) + self.assertEqual( + self.v2.elementwise() != self.v1.elementwise(), + self.v2.x != self.v1.x and self.v2.y != self.v1.y, + ) + + # other behaviour + self.assertEqual(abs(self.v1.elementwise()), (abs(self.v1.x), abs(self.v1.y))) + self.assertEqual(-self.v1.elementwise(), -self.v1) + self.assertEqual(+self.v1.elementwise(), +self.v1) + self.assertEqual(bool(self.v1.elementwise()), bool(self.v1)) + self.assertEqual(bool(Vector2().elementwise()), bool(Vector2())) + self.assertEqual(self.zeroVec.elementwise() ** 0, (1, 1)) + self.assertRaises(ValueError, lambda: pow(Vector2(-1, 0).elementwise(), 1.2)) + self.assertRaises(ZeroDivisionError, lambda: self.zeroVec.elementwise() ** -1) + + def test_elementwise(self): + v1 = self.v1 + v2 = self.v2 + s1 = self.s1 + s2 = self.s2 + # behaviour for "elementwise op scalar" + self.assertEqual(v1.elementwise() + s1, (v1.x + s1, v1.y + s1)) + self.assertEqual(v1.elementwise() - s1, (v1.x - s1, v1.y - s1)) + self.assertEqual(v1.elementwise() * s2, (v1.x * s2, v1.y * s2)) + self.assertEqual(v1.elementwise() / s2, (v1.x / s2, v1.y / s2)) + self.assertEqual(v1.elementwise() // s1, (v1.x // s1, v1.y // s1)) + self.assertEqual(v1.elementwise() ** s1, (v1.x ** s1, v1.y ** s1)) + self.assertEqual(v1.elementwise() % s1, (v1.x % s1, v1.y % s1)) + self.assertEqual(v1.elementwise() > s1, v1.x > s1 and v1.y > s1) + self.assertEqual(v1.elementwise() < s1, v1.x < s1 and v1.y < s1) + self.assertEqual(v1.elementwise() == s1, v1.x == s1 and v1.y == s1) + self.assertEqual(v1.elementwise() != s1, s1 not in [v1.x, v1.y]) + self.assertEqual(v1.elementwise() >= s1, v1.x >= s1 and v1.y >= s1) + self.assertEqual(v1.elementwise() <= s1, v1.x <= s1 and v1.y <= s1) + self.assertEqual(v1.elementwise() != s1, s1 not in [v1.x, v1.y]) + # behaviour for "scalar op elementwise" + self.assertEqual(s1 + v1.elementwise(), (s1 + v1.x, s1 + v1.y)) + self.assertEqual(s1 - v1.elementwise(), (s1 - v1.x, s1 - v1.y)) + self.assertEqual(s1 * v1.elementwise(), (s1 * v1.x, s1 * v1.y)) + self.assertEqual(s1 / v1.elementwise(), (s1 / v1.x, s1 / v1.y)) + self.assertEqual(s1 // v1.elementwise(), (s1 // v1.x, s1 // v1.y)) + self.assertEqual(s1 ** v1.elementwise(), (s1 ** v1.x, s1 ** v1.y)) + self.assertEqual(s1 % v1.elementwise(), (s1 % v1.x, s1 % v1.y)) + self.assertEqual(s1 < v1.elementwise(), s1 < v1.x and s1 < v1.y) + self.assertEqual(s1 > v1.elementwise(), s1 > v1.x and s1 > v1.y) + self.assertEqual(s1 == v1.elementwise(), s1 == v1.x and s1 == v1.y) + self.assertEqual(s1 != v1.elementwise(), s1 not in [v1.x, v1.y]) + self.assertEqual(s1 <= v1.elementwise(), s1 <= v1.x and s1 <= v1.y) + self.assertEqual(s1 >= v1.elementwise(), s1 >= v1.x and s1 >= v1.y) + self.assertEqual(s1 != v1.elementwise(), s1 not in [v1.x, v1.y]) + + # behaviour for "elementwise op vector" + self.assertEqual(type(v1.elementwise() * v2), type(v1)) + self.assertEqual(v1.elementwise() + v2, v1 + v2) + self.assertEqual(v1.elementwise() - v2, v1 - v2) + self.assertEqual(v1.elementwise() * v2, (v1.x * v2.x, v1.y * v2.y)) + self.assertEqual(v1.elementwise() / v2, (v1.x / v2.x, v1.y / v2.y)) + self.assertEqual(v1.elementwise() // v2, (v1.x // v2.x, v1.y // v2.y)) + self.assertEqual(v1.elementwise() ** v2, (v1.x ** v2.x, v1.y ** v2.y)) + self.assertEqual(v1.elementwise() % v2, (v1.x % v2.x, v1.y % v2.y)) + self.assertEqual(v1.elementwise() > v2, v1.x > v2.x and v1.y > v2.y) + self.assertEqual(v1.elementwise() < v2, v1.x < v2.x and v1.y < v2.y) + self.assertEqual(v1.elementwise() >= v2, v1.x >= v2.x and v1.y >= v2.y) + self.assertEqual(v1.elementwise() <= v2, v1.x <= v2.x and v1.y <= v2.y) + self.assertEqual(v1.elementwise() == v2, v1.x == v2.x and v1.y == v2.y) + self.assertEqual(v1.elementwise() != v2, v1.x != v2.x and v1.y != v2.y) + # behaviour for "vector op elementwise" + self.assertEqual(v2 + v1.elementwise(), v2 + v1) + self.assertEqual(v2 - v1.elementwise(), v2 - v1) + self.assertEqual(v2 * v1.elementwise(), (v2.x * v1.x, v2.y * v1.y)) + self.assertEqual(v2 / v1.elementwise(), (v2.x / v1.x, v2.y / v1.y)) + self.assertEqual(v2 // v1.elementwise(), (v2.x // v1.x, v2.y // v1.y)) + self.assertEqual(v2 ** v1.elementwise(), (v2.x ** v1.x, v2.y ** v1.y)) + self.assertEqual(v2 % v1.elementwise(), (v2.x % v1.x, v2.y % v1.y)) + self.assertEqual(v2 < v1.elementwise(), v2.x < v1.x and v2.y < v1.y) + self.assertEqual(v2 > v1.elementwise(), v2.x > v1.x and v2.y > v1.y) + self.assertEqual(v2 <= v1.elementwise(), v2.x <= v1.x and v2.y <= v1.y) + self.assertEqual(v2 >= v1.elementwise(), v2.x >= v1.x and v2.y >= v1.y) + self.assertEqual(v2 == v1.elementwise(), v2.x == v1.x and v2.y == v1.y) + self.assertEqual(v2 != v1.elementwise(), v2.x != v1.x and v2.y != v1.y) + + # behaviour for "elementwise op elementwise" + self.assertEqual(v2.elementwise() + v1.elementwise(), v2 + v1) + self.assertEqual(v2.elementwise() - v1.elementwise(), v2 - v1) + self.assertEqual( + v2.elementwise() * v1.elementwise(), (v2.x * v1.x, v2.y * v1.y) + ) + self.assertEqual( + v2.elementwise() / v1.elementwise(), (v2.x / v1.x, v2.y / v1.y) + ) + self.assertEqual( + v2.elementwise() // v1.elementwise(), (v2.x // v1.x, v2.y // v1.y) + ) + self.assertEqual( + v2.elementwise() ** v1.elementwise(), (v2.x ** v1.x, v2.y ** v1.y) + ) + self.assertEqual( + v2.elementwise() % v1.elementwise(), (v2.x % v1.x, v2.y % v1.y) + ) + self.assertEqual( + v2.elementwise() < v1.elementwise(), v2.x < v1.x and v2.y < v1.y + ) + self.assertEqual( + v2.elementwise() > v1.elementwise(), v2.x > v1.x and v2.y > v1.y + ) + self.assertEqual( + v2.elementwise() <= v1.elementwise(), v2.x <= v1.x and v2.y <= v1.y + ) + self.assertEqual( + v2.elementwise() >= v1.elementwise(), v2.x >= v1.x and v2.y >= v1.y + ) + self.assertEqual( + v2.elementwise() == v1.elementwise(), v2.x == v1.x and v2.y == v1.y + ) + self.assertEqual( + v2.elementwise() != v1.elementwise(), v2.x != v1.x and v2.y != v1.y + ) + + # other behaviour + self.assertEqual(abs(v1.elementwise()), (abs(v1.x), abs(v1.y))) + self.assertEqual(-v1.elementwise(), -v1) + self.assertEqual(+v1.elementwise(), +v1) + self.assertEqual(bool(v1.elementwise()), bool(v1)) + self.assertEqual(bool(Vector2().elementwise()), bool(Vector2())) + self.assertEqual(self.zeroVec.elementwise() ** 0, (1, 1)) + self.assertRaises(ValueError, lambda: pow(Vector2(-1, 0).elementwise(), 1.2)) + self.assertRaises(ZeroDivisionError, lambda: self.zeroVec.elementwise() ** -1) + self.assertRaises(ZeroDivisionError, lambda: self.zeroVec.elementwise() ** -1) + self.assertRaises(ZeroDivisionError, lambda: Vector2(1, 1).elementwise() / 0) + self.assertRaises(ZeroDivisionError, lambda: Vector2(1, 1).elementwise() // 0) + self.assertRaises(ZeroDivisionError, lambda: Vector2(1, 1).elementwise() % 0) + self.assertRaises( + ZeroDivisionError, lambda: Vector2(1, 1).elementwise() / self.zeroVec + ) + self.assertRaises( + ZeroDivisionError, lambda: Vector2(1, 1).elementwise() // self.zeroVec + ) + self.assertRaises( + ZeroDivisionError, lambda: Vector2(1, 1).elementwise() % self.zeroVec + ) + self.assertRaises(ZeroDivisionError, lambda: 2 / self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda: 2 // self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda: 2 % self.zeroVec.elementwise()) + + def test_slerp(self): + self.assertRaises(ValueError, lambda: self.zeroVec.slerp(self.v1, 0.5)) + self.assertRaises(ValueError, lambda: self.v1.slerp(self.zeroVec, 0.5)) + self.assertRaises(ValueError, lambda: self.zeroVec.slerp(self.zeroVec, 0.5)) + v1 = Vector2(1, 0) + v2 = Vector2(0, 1) + steps = 10 + angle_step = v1.angle_to(v2) / steps + for i, u in ((i, v1.slerp(v2, i / float(steps))) for i in range(steps + 1)): + self.assertAlmostEqual(u.length(), 1) + self.assertAlmostEqual(v1.angle_to(u), i * angle_step) + self.assertEqual(u, v2) + + v1 = Vector2(100, 0) + v2 = Vector2(0, 10) + radial_factor = v2.length() / v1.length() + for i, u in ((i, v1.slerp(v2, -i / float(steps))) for i in range(steps + 1)): + self.assertAlmostEqual( + u.length(), + (v2.length() - v1.length()) * (float(i) / steps) + v1.length(), + ) + self.assertEqual(u, v2) + self.assertEqual(v1.slerp(v1, 0.5), v1) + self.assertEqual(v2.slerp(v2, 0.5), v2) + self.assertRaises(ValueError, lambda: v1.slerp(-v1, 0.5)) + + def test_lerp(self): + v1 = Vector2(0, 0) + v2 = Vector2(10, 10) + self.assertEqual(v1.lerp(v2, 0.5), (5, 5)) + self.assertRaises(ValueError, lambda: v1.lerp(v2, 2.5)) + + v1 = Vector2(-10, -5) + v2 = Vector2(10, 10) + self.assertEqual(v1.lerp(v2, 0.5), (0, 2.5)) + + def test_polar(self): + v = Vector2() + v.from_polar(self.v1.as_polar()) + self.assertEqual(self.v1, v) + self.assertEqual(self.e1.as_polar(), (1, 0)) + self.assertEqual(self.e2.as_polar(), (1, 90)) + self.assertEqual((2 * self.e2).as_polar(), (2, 90)) + self.assertRaises(TypeError, lambda: v.from_polar((None, None))) + self.assertRaises(TypeError, lambda: v.from_polar("ab")) + self.assertRaises(TypeError, lambda: v.from_polar((None, 1))) + self.assertRaises(TypeError, lambda: v.from_polar((1, 2, 3))) + self.assertRaises(TypeError, lambda: v.from_polar((1,))) + self.assertRaises(TypeError, lambda: v.from_polar(1, 2)) + v.from_polar((0.5, 90)) + self.assertEqual(v, 0.5 * self.e2) + v.from_polar((1, 0)) + self.assertEqual(v, self.e1) + + def test_subclass_operation(self): + class Vector(pygame.math.Vector2): + pass + + vec = Vector() + + vec_a = Vector(2, 0) + vec_b = Vector(0, 1) + + vec_a + vec_b + vec_a *= 2 + + def test_project_v2_onto_x_axis(self): + """Project onto x-axis, e.g. get the component pointing in the x-axis direction.""" + # arrange + v = Vector2(2, 2) + x_axis = Vector2(10, 0) + + # act + actual = v.project(x_axis) + + # assert + self.assertEqual(v.x, actual.x) + self.assertEqual(0, actual.y) + + def test_project_v2_onto_y_axis(self): + """Project onto y-axis, e.g. get the component pointing in the y-axis direction.""" + # arrange + v = Vector2(2, 2) + y_axis = Vector2(0, 100) + + # act + actual = v.project(y_axis) + + # assert + self.assertEqual(0, actual.x) + self.assertEqual(v.y, actual.y) + + def test_project_v2_onto_other(self): + """Project onto other vector.""" + # arrange + v = Vector2(2, 3) + other = Vector2(3, 5) + + # act + actual = v.project(other) + + # assert + expected = v.dot(other) / other.dot(other) * other + self.assertEqual(expected.x, actual.x) + self.assertEqual(expected.y, actual.y) + + def test_project_v2_raises_if_other_has_zero_length(self): + """Check if exception is raise when projected on vector has zero length.""" + # arrange + v = Vector2(2, 3) + other = Vector2(0, 0) + + # act / assert + self.assertRaises(ValueError, v.project, other) + + def test_project_v2_onto_other_as_tuple(self): + """Project onto other tuple as vector.""" + # arrange + v = Vector2(2, 3) + other = Vector2(3, 5) + + # act + actual = v.project(tuple(other)) + + # assert + expected = v.dot(other) / other.dot(other) * other + self.assertEqual(expected.x, actual.x) + self.assertEqual(expected.y, actual.y) + + def test_project_v2_onto_other_as_list(self): + """Project onto other list as vector.""" + # arrange + v = Vector2(2, 3) + other = Vector2(3, 5) + + # act + actual = v.project(list(other)) + + # assert + expected = v.dot(other) / other.dot(other) * other + self.assertEqual(expected.x, actual.x) + self.assertEqual(expected.y, actual.y) + + def test_project_v2_raises_if_other_has_zero_length(self): + """Check if exception is raise when projected on vector has zero length.""" + # arrange + v = Vector2(2, 3) + other = Vector2(0, 0) + + # act / assert + self.assertRaises(ValueError, v.project, other) + + def test_project_v2_raises_if_other_is_not_iterable(self): + """Check if exception is raise when projected on vector is not iterable.""" + # arrange + v = Vector2(2, 3) + other = 10 + + # act / assert + self.assertRaises(TypeError, v.project, other) + + +class Vector3TypeTest(unittest.TestCase): + def setUp(self): + self.zeroVec = Vector3() + self.e1 = Vector3(1, 0, 0) + self.e2 = Vector3(0, 1, 0) + self.e3 = Vector3(0, 0, 1) + self.t1 = (1.2, 3.4, 9.6) + self.l1 = list(self.t1) + self.v1 = Vector3(self.t1) + self.t2 = (5.6, 7.8, 2.1) + self.l2 = list(self.t2) + self.v2 = Vector3(self.t2) + self.s1 = 5.6 + self.s2 = 7.8 + + def testConstructionDefault(self): + v = Vector3() + self.assertEqual(v.x, 0.0) + self.assertEqual(v.y, 0.0) + self.assertEqual(v.z, 0.0) + + def testConstructionXYZ(self): + v = Vector3(1.2, 3.4, 9.6) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, 9.6) + + def testConstructionTuple(self): + v = Vector3((1.2, 3.4, 9.6)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, 9.6) + + def testConstructionList(self): + v = Vector3([1.2, 3.4, -9.6]) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, -9.6) + + def testConstructionVector3(self): + v = Vector3(Vector3(1.2, 3.4, -9.6)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, -9.6) + + def testConstructionScalar(self): + v = Vector3(1) + self.assertEqual(v.x, 1.0) + self.assertEqual(v.y, 1.0) + self.assertEqual(v.z, 1.0) + + def testConstructionScalarKeywords(self): + v = Vector3(x=1) + self.assertEqual(v.x, 1.0) + self.assertEqual(v.y, 1.0) + self.assertEqual(v.z, 1.0) + + def testConstructionKeywords(self): + v = Vector3(x=1, y=2, z=3) + self.assertEqual(v.x, 1.0) + self.assertEqual(v.y, 2.0) + self.assertEqual(v.z, 3.0) + + def testConstructionMissing(self): + def assign_missing_value(): + v = Vector3(1, 2) + + self.assertRaises(ValueError, assign_missing_value) + + def assign_missing_value(): + v = Vector3(x=1, y=2) + + self.assertRaises(ValueError, assign_missing_value) + + def testAttributeAccess(self): + tmp = self.v1.x + self.assertEqual(tmp, self.v1.x) + self.assertEqual(tmp, self.v1[0]) + tmp = self.v1.y + self.assertEqual(tmp, self.v1.y) + self.assertEqual(tmp, self.v1[1]) + tmp = self.v1.z + self.assertEqual(tmp, self.v1.z) + self.assertEqual(tmp, self.v1[2]) + self.v1.x = 3.141 + self.assertEqual(self.v1.x, 3.141) + self.v1.y = 3.141 + self.assertEqual(self.v1.y, 3.141) + self.v1.z = 3.141 + self.assertEqual(self.v1.z, 3.141) + + def assign_nonfloat(): + v = Vector2() + v.x = "spam" + + self.assertRaises(TypeError, assign_nonfloat) + + def testSequence(self): + v = Vector3(1.2, 3.4, -9.6) + self.assertEqual(len(v), 3) + self.assertEqual(v[0], 1.2) + self.assertEqual(v[1], 3.4) + self.assertEqual(v[2], -9.6) + self.assertRaises(IndexError, lambda: v[3]) + self.assertEqual(v[-1], -9.6) + self.assertEqual(v[-2], 3.4) + self.assertEqual(v[-3], 1.2) + self.assertRaises(IndexError, lambda: v[-4]) + self.assertEqual(v[:], [1.2, 3.4, -9.6]) + self.assertEqual(v[1:], [3.4, -9.6]) + self.assertEqual(v[:1], [1.2]) + self.assertEqual(v[:-1], [1.2, 3.4]) + self.assertEqual(v[1:2], [3.4]) + self.assertEqual(list(v), [1.2, 3.4, -9.6]) + self.assertEqual(tuple(v), (1.2, 3.4, -9.6)) + v[0] = 5.6 + v[1] = 7.8 + v[2] = -2.1 + self.assertEqual(v.x, 5.6) + self.assertEqual(v.y, 7.8) + self.assertEqual(v.z, -2.1) + v[:] = [9.1, 11.12, -13.41] + self.assertEqual(v.x, 9.1) + self.assertEqual(v.y, 11.12) + self.assertEqual(v.z, -13.41) + + def overpopulate(): + v = Vector3() + v[:] = [1, 2, 3, 4] + + self.assertRaises(ValueError, overpopulate) + + def underpopulate(): + v = Vector3() + v[:] = [1] + + self.assertRaises(ValueError, underpopulate) + + def assign_nonfloat(): + v = Vector2() + v[0] = "spam" + + self.assertRaises(TypeError, assign_nonfloat) + + def testExtendedSlicing(self): + # deletion + def delSlice(vec, start=None, stop=None, step=None): + if start is not None and stop is not None and step is not None: + del vec[start:stop:step] + elif start is not None and stop is None and step is not None: + del vec[start::step] + elif start is None and stop is None and step is not None: + del vec[::step] + + v = Vector3(self.v1) + self.assertRaises(TypeError, delSlice, v, None, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, 2, 1) + + # assignment + v = Vector3(self.v1) + v[::2] = [-1.1, -2.2] + self.assertEqual(v, [-1.1, self.v1.y, -2.2]) + v = Vector3(self.v1) + v[::-2] = [10, 20] + self.assertEqual(v, [20, self.v1.y, 10]) + v = Vector3(self.v1) + v[::-1] = v + self.assertEqual(v, [self.v1.z, self.v1.y, self.v1.x]) + a = Vector3(self.v1) + b = Vector3(self.v1) + c = Vector3(self.v1) + a[1:2] = [2.2] + b[slice(1, 2)] = [2.2] + c[1:2:] = (2.2,) + self.assertEqual(a, b) + self.assertEqual(a, c) + self.assertEqual(type(a), type(self.v1)) + self.assertEqual(type(b), type(self.v1)) + self.assertEqual(type(c), type(self.v1)) + + def testAdd(self): + v3 = self.v1 + self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.v2.x) + self.assertEqual(v3.y, self.v1.y + self.v2.y) + self.assertEqual(v3.z, self.v1.z + self.v2.z) + v3 = self.v1 + self.t2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.t2[0]) + self.assertEqual(v3.y, self.v1.y + self.t2[1]) + self.assertEqual(v3.z, self.v1.z + self.t2[2]) + v3 = self.v1 + self.l2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.l2[0]) + self.assertEqual(v3.y, self.v1.y + self.l2[1]) + self.assertEqual(v3.z, self.v1.z + self.l2[2]) + v3 = self.t1 + self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] + self.v2.x) + self.assertEqual(v3.y, self.t1[1] + self.v2.y) + self.assertEqual(v3.z, self.t1[2] + self.v2.z) + v3 = self.l1 + self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] + self.v2.x) + self.assertEqual(v3.y, self.l1[1] + self.v2.y) + self.assertEqual(v3.z, self.l1[2] + self.v2.z) + + def testSub(self): + v3 = self.v1 - self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.v2.x) + self.assertEqual(v3.y, self.v1.y - self.v2.y) + self.assertEqual(v3.z, self.v1.z - self.v2.z) + v3 = self.v1 - self.t2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.t2[0]) + self.assertEqual(v3.y, self.v1.y - self.t2[1]) + self.assertEqual(v3.z, self.v1.z - self.t2[2]) + v3 = self.v1 - self.l2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.l2[0]) + self.assertEqual(v3.y, self.v1.y - self.l2[1]) + self.assertEqual(v3.z, self.v1.z - self.l2[2]) + v3 = self.t1 - self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] - self.v2.x) + self.assertEqual(v3.y, self.t1[1] - self.v2.y) + self.assertEqual(v3.z, self.t1[2] - self.v2.z) + v3 = self.l1 - self.v2 + self.assertTrue(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] - self.v2.x) + self.assertEqual(v3.y, self.l1[1] - self.v2.y) + self.assertEqual(v3.z, self.l1[2] - self.v2.z) + + def testScalarMultiplication(self): + v = self.s1 * self.v1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.s1 * self.v1.x) + self.assertEqual(v.y, self.s1 * self.v1.y) + self.assertEqual(v.z, self.s1 * self.v1.z) + v = self.v1 * self.s2 + self.assertEqual(v.x, self.v1.x * self.s2) + self.assertEqual(v.y, self.v1.y * self.s2) + self.assertEqual(v.z, self.v1.z * self.s2) + + def testScalarDivision(self): + v = self.v1 / self.s1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertAlmostEqual(v.x, self.v1.x / self.s1) + self.assertAlmostEqual(v.y, self.v1.y / self.s1) + self.assertAlmostEqual(v.z, self.v1.z / self.s1) + v = self.v1 // self.s2 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x // self.s2) + self.assertEqual(v.y, self.v1.y // self.s2) + self.assertEqual(v.z, self.v1.z // self.s2) + + def testBool(self): + self.assertEqual(bool(self.zeroVec), False) + self.assertEqual(bool(self.v1), True) + self.assertTrue(not self.zeroVec) + self.assertTrue(self.v1) + + def testUnary(self): + v = +self.v1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x) + self.assertEqual(v.y, self.v1.y) + self.assertEqual(v.z, self.v1.z) + self.assertNotEqual(id(v), id(self.v1)) + v = -self.v1 + self.assertTrue(isinstance(v, type(self.v1))) + self.assertEqual(v.x, -self.v1.x) + self.assertEqual(v.y, -self.v1.y) + self.assertEqual(v.z, -self.v1.z) + self.assertNotEqual(id(v), id(self.v1)) + + def testCompare(self): + int_vec = Vector3(3, -2, 13) + flt_vec = Vector3(3.0, -2.0, 13.0) + zero_vec = Vector3(0, 0, 0) + self.assertEqual(int_vec == flt_vec, True) + self.assertEqual(int_vec != flt_vec, False) + self.assertEqual(int_vec != zero_vec, True) + self.assertEqual(flt_vec == zero_vec, False) + self.assertEqual(int_vec == (3, -2, 13), True) + self.assertEqual(int_vec != (3, -2, 13), False) + self.assertEqual(int_vec != [0, 0], True) + self.assertEqual(int_vec == [0, 0], False) + self.assertEqual(int_vec != 5, True) + self.assertEqual(int_vec == 5, False) + self.assertEqual(int_vec != [3, -2, 0, 1], True) + self.assertEqual(int_vec == [3, -2, 0, 1], False) + + def testStr(self): + v = Vector3(1.2, 3.4, 5.6) + self.assertEqual(str(v), "[1.2, 3.4, 5.6]") + + def testRepr(self): + v = Vector3(1.2, 3.4, -9.6) + self.assertEqual(v.__repr__(), "") + self.assertEqual(v, Vector3(v.__repr__())) + + def testIter(self): + it = self.v1.__iter__() + next_ = it.__next__ + self.assertEqual(next_(), self.v1[0]) + self.assertEqual(next_(), self.v1[1]) + self.assertEqual(next_(), self.v1[2]) + self.assertRaises(StopIteration, lambda: next_()) + it1 = self.v1.__iter__() + it2 = self.v1.__iter__() + self.assertNotEqual(id(it1), id(it2)) + self.assertEqual(id(it1), id(it1.__iter__())) + self.assertEqual(list(it1), list(it2)) + self.assertEqual(list(self.v1.__iter__()), self.l1) + idx = 0 + for val in self.v1: + self.assertEqual(val, self.v1[idx]) + idx += 1 + + def test_rotate(self): + v1 = Vector3(1, 0, 0) + axis = Vector3(0, 1, 0) + v2 = v1.rotate(90, axis) + v3 = v1.rotate(90 + 360, axis) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertEqual(v2.x, 0) + self.assertEqual(v2.y, 0) + self.assertEqual(v2.z, -1) + self.assertEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + self.assertEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate(-90, axis) + self.assertEqual(v2.x, 1) + self.assertEqual(v2.y, -1) + self.assertEqual(v2.z, -1) + v2 = v1.rotate(360, axis) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + v2 = v1.rotate(0, axis) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + # issue 214 + self.assertEqual( + Vector3(0, 1, 0).rotate(359.9999999, Vector3(0, 0, 1)), Vector3(0, 1, 0) + ) + + def test_rotate_rad(self): + axis = Vector3(0, 0, 1) + tests = ( + ((1, 0, 0), math.pi), + ((1, 0, 0), math.pi / 2), + ((1, 0, 0), -math.pi / 2), + ((1, 0, 0), math.pi / 4), + ) + for initialVec, radians in tests: + vec = Vector3(initialVec).rotate_rad(radians, axis) + self.assertEqual(vec, (math.cos(radians), math.sin(radians), 0)) + + def test_rotate_ip(self): + v = Vector3(1, 0, 0) + axis = Vector3(0, 1, 0) + self.assertEqual(v.rotate_ip(90, axis), None) + self.assertEqual(v.x, 0) + self.assertEqual(v.y, 0) + self.assertEqual(v.z, -1) + v = Vector3(-1, -1, 1) + v.rotate_ip(-90, axis) + self.assertEqual(v.x, -1) + self.assertEqual(v.y, -1) + self.assertEqual(v.z, -1) + + def test_rotate_rad_ip(self): + axis = Vector3(0, 0, 1) + tests = ( + ((1, 0, 0), math.pi), + ((1, 0, 0), math.pi / 2), + ((1, 0, 0), -math.pi / 2), + ((1, 0, 0), math.pi / 4), + ) + for initialVec, radians in tests: + vec = Vector3(initialVec) + vec.rotate_rad_ip(radians, axis) + self.assertEqual(vec, (math.cos(radians), math.sin(radians), 0)) + + def test_rotate_x(self): + v1 = Vector3(1, 0, 0) + v2 = v1.rotate_x(90) + v3 = v1.rotate_x(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertEqual(v2.x, 1) + self.assertEqual(v2.y, 0) + self.assertEqual(v2.z, 0) + self.assertEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + self.assertEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate_x(-90) + self.assertEqual(v2.x, -1) + self.assertAlmostEqual(v2.y, -1) + self.assertAlmostEqual(v2.z, 1) + v2 = v1.rotate_x(360) + self.assertAlmostEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertAlmostEqual(v1.z, v2.z) + v2 = v1.rotate_x(0) + self.assertEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertAlmostEqual(v1.z, v2.z) + + def test_rotate_x_rad(self): + vec = Vector3(0, 1, 0) + result = vec.rotate_x_rad(math.pi / 2) + self.assertEqual(result, (0, 0, 1)) + + def test_rotate_x_ip(self): + v = Vector3(1, 0, 0) + self.assertEqual(v.rotate_x_ip(90), None) + self.assertEqual(v.x, 1) + self.assertEqual(v.y, 0) + self.assertEqual(v.z, 0) + v = Vector3(-1, -1, 1) + v.rotate_x_ip(-90) + self.assertEqual(v.x, -1) + self.assertAlmostEqual(v.y, 1) + self.assertAlmostEqual(v.z, 1) + + def test_rotate_x_rad_ip(self): + vec = Vector3(0, 1, 0) + vec.rotate_x_rad_ip(math.pi / 2) + self.assertEqual(vec, (0, 0, 1)) + + def test_rotate_y(self): + v1 = Vector3(1, 0, 0) + v2 = v1.rotate_y(90) + v3 = v1.rotate_y(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertAlmostEqual(v2.x, 0) + self.assertEqual(v2.y, 0) + self.assertAlmostEqual(v2.z, -1) + self.assertAlmostEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + self.assertAlmostEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate_y(-90) + self.assertAlmostEqual(v2.x, 1) + self.assertEqual(v2.y, -1) + self.assertAlmostEqual(v2.z, -1) + v2 = v1.rotate_y(360) + self.assertAlmostEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertAlmostEqual(v1.z, v2.z) + v2 = v1.rotate_y(0) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + + def test_rotate_y_rad(self): + vec = Vector3(1, 0, 0) + result = vec.rotate_y_rad(math.pi / 2) + self.assertEqual(result, (0, 0, -1)) + + def test_rotate_y_ip(self): + v = Vector3(1, 0, 0) + self.assertEqual(v.rotate_y_ip(90), None) + self.assertAlmostEqual(v.x, 0) + self.assertEqual(v.y, 0) + self.assertAlmostEqual(v.z, -1) + v = Vector3(-1, -1, 1) + v.rotate_y_ip(-90) + self.assertAlmostEqual(v.x, -1) + self.assertEqual(v.y, -1) + self.assertAlmostEqual(v.z, -1) + + def test_rotate_y_rad_ip(self): + vec = Vector3(1, 0, 0) + vec.rotate_y_rad_ip(math.pi / 2) + self.assertEqual(vec, (0, 0, -1)) + + def test_rotate_z(self): + v1 = Vector3(1, 0, 0) + v2 = v1.rotate_z(90) + v3 = v1.rotate_z(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertAlmostEqual(v2.x, 0) + self.assertAlmostEqual(v2.y, 1) + self.assertEqual(v2.z, 0) + self.assertAlmostEqual(v3.x, v2.x) + self.assertAlmostEqual(v3.y, v2.y) + self.assertEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate_z(-90) + self.assertAlmostEqual(v2.x, -1) + self.assertAlmostEqual(v2.y, 1) + self.assertEqual(v2.z, -1) + v2 = v1.rotate_z(360) + self.assertAlmostEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + v2 = v1.rotate_z(0) + self.assertAlmostEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + + def test_rotate_z_rad(self): + vec = Vector3(1, 0, 0) + result = vec.rotate_z_rad(math.pi / 2) + self.assertEqual(result, (0, 1, 0)) + + def test_rotate_z_ip(self): + v = Vector3(1, 0, 0) + self.assertEqual(v.rotate_z_ip(90), None) + self.assertAlmostEqual(v.x, 0) + self.assertAlmostEqual(v.y, 1) + self.assertEqual(v.z, 0) + v = Vector3(-1, -1, 1) + v.rotate_z_ip(-90) + self.assertAlmostEqual(v.x, -1) + self.assertAlmostEqual(v.y, 1) + self.assertEqual(v.z, 1) + + def test_rotate_z_rad_ip(self): + vec = Vector3(1, 0, 0) + vec.rotate_z_rad_ip(math.pi / 2) + self.assertEqual(vec, (0, 1, 0)) + + def test_normalize(self): + v = self.v1.normalize() + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y + v.z * v.z, 1.0) + # v1 is unchanged + self.assertEqual(self.v1.x, self.l1[0]) + self.assertEqual(self.v1.y, self.l1[1]) + self.assertEqual(self.v1.z, self.l1[2]) + # v2 is parallel to v1 (tested via cross product) + cross = ( + (self.v1.y * v.z - self.v1.z * v.y) ** 2 + + (self.v1.z * v.x - self.v1.x * v.z) ** 2 + + (self.v1.x * v.y - self.v1.y * v.x) ** 2 + ) + self.assertAlmostEqual(cross, 0.0) + self.assertRaises(ValueError, lambda: self.zeroVec.normalize()) + + def test_normalize_ip(self): + v = +self.v1 + # v has length != 1 before normalizing + self.assertNotEqual(v.x * v.x + v.y * v.y + v.z * v.z, 1.0) + # inplace operations should return None + self.assertEqual(v.normalize_ip(), None) + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y + v.z * v.z, 1.0) + # v2 is parallel to v1 (tested via cross product) + cross = ( + (self.v1.y * v.z - self.v1.z * v.y) ** 2 + + (self.v1.z * v.x - self.v1.x * v.z) ** 2 + + (self.v1.x * v.y - self.v1.y * v.x) ** 2 + ) + self.assertAlmostEqual(cross, 0.0) + self.assertRaises(ValueError, lambda: self.zeroVec.normalize_ip()) + + def test_is_normalized(self): + self.assertEqual(self.v1.is_normalized(), False) + v = self.v1.normalize() + self.assertEqual(v.is_normalized(), True) + self.assertEqual(self.e2.is_normalized(), True) + self.assertEqual(self.zeroVec.is_normalized(), False) + + def test_cross(self): + def cross(a, b): + return Vector3( + a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0], + ) + + self.assertEqual(self.v1.cross(self.v2), cross(self.v1, self.v2)) + self.assertEqual(self.v1.cross(self.l2), cross(self.v1, self.l2)) + self.assertEqual(self.v1.cross(self.t2), cross(self.v1, self.t2)) + self.assertEqual(self.v1.cross(self.v2), -self.v2.cross(self.v1)) + self.assertEqual(self.v1.cross(self.v1), self.zeroVec) + + def test_dot(self): + self.assertAlmostEqual( + self.v1.dot(self.v2), + self.v1.x * self.v2.x + self.v1.y * self.v2.y + self.v1.z * self.v2.z, + ) + self.assertAlmostEqual( + self.v1.dot(self.l2), + self.v1.x * self.l2[0] + self.v1.y * self.l2[1] + self.v1.z * self.l2[2], + ) + self.assertAlmostEqual( + self.v1.dot(self.t2), + self.v1.x * self.t2[0] + self.v1.y * self.t2[1] + self.v1.z * self.t2[2], + ) + self.assertAlmostEqual(self.v1.dot(self.v2), self.v2.dot(self.v1)) + self.assertAlmostEqual(self.v1.dot(self.v2), self.v1 * self.v2) + + def test_angle_to(self): + self.assertEqual(Vector3(1, 1, 0).angle_to((-1, 1, 0)), 90) + self.assertEqual(Vector3(1, 0, 0).angle_to((0, 0, -1)), 90) + self.assertEqual(Vector3(1, 0, 0).angle_to((-1, 0, 1)), 135) + self.assertEqual(abs(Vector3(1, 0, 1).angle_to((-1, 0, -1))), 180) + # if we rotate v1 by the angle_to v2 around their cross product + # we should look in the same direction + self.assertEqual( + self.v1.rotate( + self.v1.angle_to(self.v2), self.v1.cross(self.v2) + ).normalize(), + self.v2.normalize(), + ) + + def test_scale_to_length(self): + v = Vector3(1, 1, 1) + v.scale_to_length(2.5) + self.assertEqual(v, Vector3(2.5, 2.5, 2.5) / math.sqrt(3)) + self.assertRaises(ValueError, lambda: self.zeroVec.scale_to_length(1)) + self.assertEqual(v.scale_to_length(0), None) + self.assertEqual(v, self.zeroVec) + + def test_length(self): + self.assertEqual(Vector3(3, 4, 5).length(), math.sqrt(3 * 3 + 4 * 4 + 5 * 5)) + self.assertEqual(Vector3(-3, 4, 5).length(), math.sqrt(-3 * -3 + 4 * 4 + 5 * 5)) + self.assertEqual(self.zeroVec.length(), 0) + + def test_length_squared(self): + self.assertEqual(Vector3(3, 4, 5).length_squared(), 3 * 3 + 4 * 4 + 5 * 5) + self.assertEqual(Vector3(-3, 4, 5).length_squared(), -3 * -3 + 4 * 4 + 5 * 5) + self.assertEqual(self.zeroVec.length_squared(), 0) + + def test_reflect(self): + v = Vector3(1, -1, 1) + n = Vector3(0, 1, 0) + self.assertEqual(v.reflect(n), Vector3(1, 1, 1)) + self.assertEqual(v.reflect(3 * n), v.reflect(n)) + self.assertEqual(v.reflect(-v), -v) + self.assertRaises(ValueError, lambda: v.reflect(self.zeroVec)) + + def test_reflect_ip(self): + v1 = Vector3(1, -1, 1) + v2 = Vector3(v1) + n = Vector3(0, 1, 0) + self.assertEqual(v2.reflect_ip(n), None) + self.assertEqual(v2, Vector3(1, 1, 1)) + v2 = Vector3(v1) + v2.reflect_ip(3 * n) + self.assertEqual(v2, v1.reflect(n)) + v2 = Vector3(v1) + v2.reflect_ip(-v1) + self.assertEqual(v2, -v1) + self.assertRaises(ValueError, lambda: v2.reflect_ip(self.zeroVec)) + + def test_distance_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_to(self.e2), math.sqrt(2)) + self.assertEqual( + self.v1.distance_to(self.v2), + math.sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z), + ) + self.assertEqual(self.v1.distance_to(self.v1), 0) + self.assertEqual(self.v1.distance_to(self.v2), self.v2.distance_to(self.v1)) + + def test_distance_squared_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_squared_to(self.e2), 2) + self.assertAlmostEqual( + self.v1.distance_squared_to(self.v2), + diff.x * diff.x + diff.y * diff.y + diff.z * diff.z, + ) + self.assertEqual(self.v1.distance_squared_to(self.v1), 0) + self.assertEqual( + self.v1.distance_squared_to(self.v2), self.v2.distance_squared_to(self.v1) + ) + + def test_swizzle(self): + self.assertEqual(self.v1.yxz, (self.v1.y, self.v1.x, self.v1.z)) + self.assertEqual( + self.v1.xxyyzzxyz, + ( + self.v1.x, + self.v1.x, + self.v1.y, + self.v1.y, + self.v1.z, + self.v1.z, + self.v1.x, + self.v1.y, + self.v1.z, + ), + ) + self.v1.xyz = self.t2 + self.assertEqual(self.v1, self.t2) + self.v1.zxy = self.t2 + self.assertEqual(self.v1, (self.t2[1], self.t2[2], self.t2[0])) + self.v1.yz = self.t2[:2] + self.assertEqual(self.v1, (self.t2[1], self.t2[0], self.t2[1])) + self.assertEqual(type(self.v1), Vector3) + + @unittest.skipIf(IS_PYPY, "known pypy failure") + def test_invalid_swizzle(self): + def invalidSwizzleX(): + Vector3().xx = (1, 2) + + def invalidSwizzleY(): + Vector3().yy = (1, 2) + + def invalidSwizzleZ(): + Vector3().zz = (1, 2) + + def invalidSwizzleW(): + Vector3().ww = (1, 2) + + self.assertRaises(AttributeError, invalidSwizzleX) + self.assertRaises(AttributeError, invalidSwizzleY) + self.assertRaises(AttributeError, invalidSwizzleZ) + self.assertRaises(AttributeError, invalidSwizzleW) + + def invalidAssignment(): + Vector3().xy = 3 + + self.assertRaises(TypeError, invalidAssignment) + + def test_swizzle_return_types(self): + self.assertEqual(type(self.v1.x), float) + self.assertEqual(type(self.v1.xy), Vector2) + self.assertEqual(type(self.v1.xyz), Vector3) + # but we don't have vector4 or above... so tuple. + self.assertEqual(type(self.v1.xyxy), tuple) + self.assertEqual(type(self.v1.xyxyx), tuple) + + def test_dir_works(self): + # not every single one of the attributes... + attributes = set( + ["lerp", "normalize", "normalize_ip", "reflect", "slerp", "x", "y"] + ) + # check if this selection of attributes are all there. + self.assertTrue(attributes.issubset(set(dir(self.v1)))) + + def test_elementwise(self): + # behaviour for "elementwise op scalar" + self.assertEqual( + self.v1.elementwise() + self.s1, + (self.v1.x + self.s1, self.v1.y + self.s1, self.v1.z + self.s1), + ) + self.assertEqual( + self.v1.elementwise() - self.s1, + (self.v1.x - self.s1, self.v1.y - self.s1, self.v1.z - self.s1), + ) + self.assertEqual( + self.v1.elementwise() * self.s2, + (self.v1.x * self.s2, self.v1.y * self.s2, self.v1.z * self.s2), + ) + self.assertEqual( + self.v1.elementwise() / self.s2, + (self.v1.x / self.s2, self.v1.y / self.s2, self.v1.z / self.s2), + ) + self.assertEqual( + self.v1.elementwise() // self.s1, + (self.v1.x // self.s1, self.v1.y // self.s1, self.v1.z // self.s1), + ) + self.assertEqual( + self.v1.elementwise() ** self.s1, + (self.v1.x ** self.s1, self.v1.y ** self.s1, self.v1.z ** self.s1), + ) + self.assertEqual( + self.v1.elementwise() % self.s1, + (self.v1.x % self.s1, self.v1.y % self.s1, self.v1.z % self.s1), + ) + self.assertEqual( + self.v1.elementwise() > self.s1, + self.v1.x > self.s1 and self.v1.y > self.s1 and self.v1.z > self.s1, + ) + self.assertEqual( + self.v1.elementwise() < self.s1, + self.v1.x < self.s1 and self.v1.y < self.s1 and self.v1.z < self.s1, + ) + self.assertEqual( + self.v1.elementwise() == self.s1, + self.v1.x == self.s1 and self.v1.y == self.s1 and self.v1.z == self.s1, + ) + self.assertEqual( + self.v1.elementwise() != self.s1, + self.v1.x != self.s1 and self.v1.y != self.s1 and self.v1.z != self.s1, + ) + self.assertEqual( + self.v1.elementwise() >= self.s1, + self.v1.x >= self.s1 and self.v1.y >= self.s1 and self.v1.z >= self.s1, + ) + self.assertEqual( + self.v1.elementwise() <= self.s1, + self.v1.x <= self.s1 and self.v1.y <= self.s1 and self.v1.z <= self.s1, + ) + # behaviour for "scalar op elementwise" + self.assertEqual(5 + self.v1.elementwise(), Vector3(5, 5, 5) + self.v1) + self.assertEqual(3.5 - self.v1.elementwise(), Vector3(3.5, 3.5, 3.5) - self.v1) + self.assertEqual(7.5 * self.v1.elementwise(), 7.5 * self.v1) + self.assertEqual( + -3.5 / self.v1.elementwise(), + (-3.5 / self.v1.x, -3.5 / self.v1.y, -3.5 / self.v1.z), + ) + self.assertEqual( + -3.5 // self.v1.elementwise(), + (-3.5 // self.v1.x, -3.5 // self.v1.y, -3.5 // self.v1.z), + ) + self.assertEqual( + -(3.5 ** self.v1.elementwise()), + (-(3.5 ** self.v1.x), -(3.5 ** self.v1.y), -(3.5 ** self.v1.z)), + ) + self.assertEqual( + 3 % self.v1.elementwise(), (3 % self.v1.x, 3 % self.v1.y, 3 % self.v1.z) + ) + self.assertEqual( + 2 < self.v1.elementwise(), 2 < self.v1.x and 2 < self.v1.y and 2 < self.v1.z + ) + self.assertEqual( + 2 > self.v1.elementwise(), 2 > self.v1.x and 2 > self.v1.y and 2 > self.v1.z + ) + self.assertEqual( + 1 == self.v1.elementwise(), + 1 == self.v1.x and 1 == self.v1.y and 1 == self.v1.z, + ) + self.assertEqual( + 1 != self.v1.elementwise(), + 1 != self.v1.x and 1 != self.v1.y and 1 != self.v1.z, + ) + self.assertEqual( + 2 <= self.v1.elementwise(), + 2 <= self.v1.x and 2 <= self.v1.y and 2 <= self.v1.z, + ) + self.assertEqual( + -7 >= self.v1.elementwise(), + -7 >= self.v1.x and -7 >= self.v1.y and -7 >= self.v1.z, + ) + self.assertEqual( + -7 != self.v1.elementwise(), + -7 != self.v1.x and -7 != self.v1.y and -7 != self.v1.z, + ) + + # behaviour for "elementwise op vector" + self.assertEqual(type(self.v1.elementwise() * self.v2), type(self.v1)) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() - self.v2, self.v1 - self.v2) + self.assertEqual( + self.v1.elementwise() * self.v2, + (self.v1.x * self.v2.x, self.v1.y * self.v2.y, self.v1.z * self.v2.z), + ) + self.assertEqual( + self.v1.elementwise() / self.v2, + (self.v1.x / self.v2.x, self.v1.y / self.v2.y, self.v1.z / self.v2.z), + ) + self.assertEqual( + self.v1.elementwise() // self.v2, + (self.v1.x // self.v2.x, self.v1.y // self.v2.y, self.v1.z // self.v2.z), + ) + self.assertEqual( + self.v1.elementwise() ** self.v2, + (self.v1.x ** self.v2.x, self.v1.y ** self.v2.y, self.v1.z ** self.v2.z), + ) + self.assertEqual( + self.v1.elementwise() % self.v2, + (self.v1.x % self.v2.x, self.v1.y % self.v2.y, self.v1.z % self.v2.z), + ) + self.assertEqual( + self.v1.elementwise() > self.v2, + self.v1.x > self.v2.x and self.v1.y > self.v2.y and self.v1.z > self.v2.z, + ) + self.assertEqual( + self.v1.elementwise() < self.v2, + self.v1.x < self.v2.x and self.v1.y < self.v2.y and self.v1.z < self.v2.z, + ) + self.assertEqual( + self.v1.elementwise() >= self.v2, + self.v1.x >= self.v2.x + and self.v1.y >= self.v2.y + and self.v1.z >= self.v2.z, + ) + self.assertEqual( + self.v1.elementwise() <= self.v2, + self.v1.x <= self.v2.x + and self.v1.y <= self.v2.y + and self.v1.z <= self.v2.z, + ) + self.assertEqual( + self.v1.elementwise() == self.v2, + self.v1.x == self.v2.x + and self.v1.y == self.v2.y + and self.v1.z == self.v2.z, + ) + self.assertEqual( + self.v1.elementwise() != self.v2, + self.v1.x != self.v2.x + and self.v1.y != self.v2.y + and self.v1.z != self.v2.z, + ) + # behaviour for "vector op elementwise" + self.assertEqual(self.v2 + self.v1.elementwise(), self.v2 + self.v1) + self.assertEqual(self.v2 - self.v1.elementwise(), self.v2 - self.v1) + self.assertEqual( + self.v2 * self.v1.elementwise(), + (self.v2.x * self.v1.x, self.v2.y * self.v1.y, self.v2.z * self.v1.z), + ) + self.assertEqual( + self.v2 / self.v1.elementwise(), + (self.v2.x / self.v1.x, self.v2.y / self.v1.y, self.v2.z / self.v1.z), + ) + self.assertEqual( + self.v2 // self.v1.elementwise(), + (self.v2.x // self.v1.x, self.v2.y // self.v1.y, self.v2.z // self.v1.z), + ) + self.assertEqual( + self.v2 ** self.v1.elementwise(), + (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y, self.v2.z ** self.v1.z), + ) + self.assertEqual( + self.v2 % self.v1.elementwise(), + (self.v2.x % self.v1.x, self.v2.y % self.v1.y, self.v2.z % self.v1.z), + ) + self.assertEqual( + self.v2 < self.v1.elementwise(), + self.v2.x < self.v1.x and self.v2.y < self.v1.y and self.v2.z < self.v1.z, + ) + self.assertEqual( + self.v2 > self.v1.elementwise(), + self.v2.x > self.v1.x and self.v2.y > self.v1.y and self.v2.z > self.v1.z, + ) + self.assertEqual( + self.v2 <= self.v1.elementwise(), + self.v2.x <= self.v1.x + and self.v2.y <= self.v1.y + and self.v2.z <= self.v1.z, + ) + self.assertEqual( + self.v2 >= self.v1.elementwise(), + self.v2.x >= self.v1.x + and self.v2.y >= self.v1.y + and self.v2.z >= self.v1.z, + ) + self.assertEqual( + self.v2 == self.v1.elementwise(), + self.v2.x == self.v1.x + and self.v2.y == self.v1.y + and self.v2.z == self.v1.z, + ) + self.assertEqual( + self.v2 != self.v1.elementwise(), + self.v2.x != self.v1.x + and self.v2.y != self.v1.y + and self.v2.z != self.v1.z, + ) + + # behaviour for "elementwise op elementwise" + self.assertEqual( + self.v2.elementwise() + self.v1.elementwise(), self.v2 + self.v1 + ) + self.assertEqual( + self.v2.elementwise() - self.v1.elementwise(), self.v2 - self.v1 + ) + self.assertEqual( + self.v2.elementwise() * self.v1.elementwise(), + (self.v2.x * self.v1.x, self.v2.y * self.v1.y, self.v2.z * self.v1.z), + ) + self.assertEqual( + self.v2.elementwise() / self.v1.elementwise(), + (self.v2.x / self.v1.x, self.v2.y / self.v1.y, self.v2.z / self.v1.z), + ) + self.assertEqual( + self.v2.elementwise() // self.v1.elementwise(), + (self.v2.x // self.v1.x, self.v2.y // self.v1.y, self.v2.z // self.v1.z), + ) + self.assertEqual( + self.v2.elementwise() ** self.v1.elementwise(), + (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y, self.v2.z ** self.v1.z), + ) + self.assertEqual( + self.v2.elementwise() % self.v1.elementwise(), + (self.v2.x % self.v1.x, self.v2.y % self.v1.y, self.v2.z % self.v1.z), + ) + self.assertEqual( + self.v2.elementwise() < self.v1.elementwise(), + self.v2.x < self.v1.x and self.v2.y < self.v1.y and self.v2.z < self.v1.z, + ) + self.assertEqual( + self.v2.elementwise() > self.v1.elementwise(), + self.v2.x > self.v1.x and self.v2.y > self.v1.y and self.v2.z > self.v1.z, + ) + self.assertEqual( + self.v2.elementwise() <= self.v1.elementwise(), + self.v2.x <= self.v1.x + and self.v2.y <= self.v1.y + and self.v2.z <= self.v1.z, + ) + self.assertEqual( + self.v2.elementwise() >= self.v1.elementwise(), + self.v2.x >= self.v1.x + and self.v2.y >= self.v1.y + and self.v2.z >= self.v1.z, + ) + self.assertEqual( + self.v2.elementwise() == self.v1.elementwise(), + self.v2.x == self.v1.x + and self.v2.y == self.v1.y + and self.v2.z == self.v1.z, + ) + self.assertEqual( + self.v2.elementwise() != self.v1.elementwise(), + self.v2.x != self.v1.x + and self.v2.y != self.v1.y + and self.v2.z != self.v1.z, + ) + + # other behaviour + self.assertEqual( + abs(self.v1.elementwise()), (abs(self.v1.x), abs(self.v1.y), abs(self.v1.z)) + ) + self.assertEqual(-self.v1.elementwise(), -self.v1) + self.assertEqual(+self.v1.elementwise(), +self.v1) + self.assertEqual(bool(self.v1.elementwise()), bool(self.v1)) + self.assertEqual(bool(Vector3().elementwise()), bool(Vector3())) + self.assertEqual(self.zeroVec.elementwise() ** 0, (1, 1, 1)) + self.assertRaises(ValueError, lambda: pow(Vector3(-1, 0, 0).elementwise(), 1.2)) + self.assertRaises(ZeroDivisionError, lambda: self.zeroVec.elementwise() ** -1) + self.assertRaises(ZeroDivisionError, lambda: Vector3(1, 1, 1).elementwise() / 0) + self.assertRaises( + ZeroDivisionError, lambda: Vector3(1, 1, 1).elementwise() // 0 + ) + self.assertRaises(ZeroDivisionError, lambda: Vector3(1, 1, 1).elementwise() % 0) + self.assertRaises( + ZeroDivisionError, lambda: Vector3(1, 1, 1).elementwise() / self.zeroVec + ) + self.assertRaises( + ZeroDivisionError, lambda: Vector3(1, 1, 1).elementwise() // self.zeroVec + ) + self.assertRaises( + ZeroDivisionError, lambda: Vector3(1, 1, 1).elementwise() % self.zeroVec + ) + self.assertRaises(ZeroDivisionError, lambda: 2 / self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda: 2 // self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda: 2 % self.zeroVec.elementwise()) + + def test_slerp(self): + self.assertRaises(ValueError, lambda: self.zeroVec.slerp(self.v1, 0.5)) + self.assertRaises(ValueError, lambda: self.v1.slerp(self.zeroVec, 0.5)) + self.assertRaises(ValueError, lambda: self.zeroVec.slerp(self.zeroVec, 0.5)) + steps = 10 + angle_step = self.e1.angle_to(self.e2) / steps + for i, u in ( + (i, self.e1.slerp(self.e2, i / float(steps))) for i in range(steps + 1) + ): + self.assertAlmostEqual(u.length(), 1) + self.assertAlmostEqual(self.e1.angle_to(u), i * angle_step) + self.assertEqual(u, self.e2) + + v1 = Vector3(100, 0, 0) + v2 = Vector3(0, 10, 7) + radial_factor = v2.length() / v1.length() + for i, u in ((i, v1.slerp(v2, -i / float(steps))) for i in range(steps + 1)): + self.assertAlmostEqual( + u.length(), + (v2.length() - v1.length()) * (float(i) / steps) + v1.length(), + ) + self.assertEqual(u, v2) + self.assertEqual(v1.slerp(v1, 0.5), v1) + self.assertEqual(v2.slerp(v2, 0.5), v2) + self.assertRaises(ValueError, lambda: v1.slerp(-v1, 0.5)) + + def test_lerp(self): + v1 = Vector3(0, 0, 0) + v2 = Vector3(10, 10, 10) + self.assertEqual(v1.lerp(v2, 0.5), (5, 5, 5)) + self.assertRaises(ValueError, lambda: v1.lerp(v2, 2.5)) + + v1 = Vector3(-10, -5, -20) + v2 = Vector3(10, 10, -20) + self.assertEqual(v1.lerp(v2, 0.5), (0, 2.5, -20)) + + def test_spherical(self): + v = Vector3() + v.from_spherical(self.v1.as_spherical()) + self.assertEqual(self.v1, v) + self.assertEqual(self.e1.as_spherical(), (1, 90, 0)) + self.assertEqual(self.e2.as_spherical(), (1, 90, 90)) + self.assertEqual(self.e3.as_spherical(), (1, 0, 0)) + self.assertEqual((2 * self.e2).as_spherical(), (2, 90, 90)) + self.assertRaises(TypeError, lambda: v.from_spherical((None, None, None))) + self.assertRaises(TypeError, lambda: v.from_spherical("abc")) + self.assertRaises(TypeError, lambda: v.from_spherical((None, 1, 2))) + self.assertRaises(TypeError, lambda: v.from_spherical((1, 2, 3, 4))) + self.assertRaises(TypeError, lambda: v.from_spherical((1, 2))) + self.assertRaises(TypeError, lambda: v.from_spherical(1, 2, 3)) + v.from_spherical((0.5, 90, 90)) + self.assertEqual(v, 0.5 * self.e2) + + def test_inplace_operators(self): + + v = Vector3(1, 1, 1) + v *= 2 + self.assertEqual(v, (2.0, 2.0, 2.0)) + + v = Vector3(4, 4, 4) + v /= 2 + self.assertEqual(v, (2.0, 2.0, 2.0)) + + v = Vector3(3.0, 3.0, 3.0) + v -= (1, 1, 1) + self.assertEqual(v, (2.0, 2.0, 2.0)) + + v = Vector3(3.0, 3.0, 3.0) + v += (1, 1, 1) + self.assertEqual(v, (4.0, 4.0, 4.0)) + + def test_pickle(self): + import pickle + + v2 = Vector2(1, 2) + v3 = Vector3(1, 2, 3) + self.assertEqual(pickle.loads(pickle.dumps(v2)), v2) + self.assertEqual(pickle.loads(pickle.dumps(v3)), v3) + + def test_subclass_operation(self): + class Vector(pygame.math.Vector3): + pass + + v = Vector(2.0, 2.0, 2.0) + v *= 2 + self.assertEqual(v, (4.0, 4.0, 4.0)) + + def test_swizzle_constants(self): + """We can get constant values from a swizzle.""" + v = Vector2(7, 6) + self.assertEqual( + v.xy1, + (7.0, 6.0, 1.0), + ) + + def test_swizzle_four_constants(self): + """We can get 4 constant values from a swizzle.""" + v = Vector2(7, 6) + self.assertEqual( + v.xy01, + (7.0, 6.0, 0.0, 1.0), + ) + + def test_swizzle_oob(self): + """An out-of-bounds swizzle raises an AttributeError.""" + v = Vector2(7, 6) + with self.assertRaises(AttributeError): + v.xyz + + @unittest.skipIf(IS_PYPY, "known pypy failure") + def test_swizzle_set_oob(self): + """An out-of-bounds swizzle set raises an AttributeError.""" + v = Vector2(7, 6) + with self.assertRaises(AttributeError): + v.xz = (1, 1) + + def test_project_v3_onto_x_axis(self): + """Project onto x-axis, e.g. get the component pointing in the x-axis direction.""" + # arrange + v = Vector3(2, 3, 4) + x_axis = Vector3(10, 0, 0) + + # act + actual = v.project(x_axis) + + # assert + self.assertEqual(v.x, actual.x) + self.assertEqual(0, actual.y) + self.assertEqual(0, actual.z) + + def test_project_v3_onto_y_axis(self): + """Project onto y-axis, e.g. get the component pointing in the y-axis direction.""" + # arrange + v = Vector3(2, 3, 4) + y_axis = Vector3(0, 100, 0) + + # act + actual = v.project(y_axis) + + # assert + self.assertEqual(0, actual.x) + self.assertEqual(v.y, actual.y) + self.assertEqual(0, actual.z) + + def test_project_v3_onto_z_axis(self): + """Project onto z-axis, e.g. get the component pointing in the z-axis direction.""" + # arrange + v = Vector3(2, 3, 4) + y_axis = Vector3(0, 0, 77) + + # act + actual = v.project(y_axis) + + # assert + self.assertEqual(0, actual.x) + self.assertEqual(0, actual.y) + self.assertEqual(v.z, actual.z) + + def test_project_v3_onto_other(self): + """Project onto other vector.""" + # arrange + v = Vector3(2, 3, 4) + other = Vector3(3, 5, 7) + + # act + actual = v.project(other) + + # assert + expected = v.dot(other) / other.dot(other) * other + self.assertAlmostEqual(expected.x, actual.x) + self.assertAlmostEqual(expected.y, actual.y) + self.assertAlmostEqual(expected.z, actual.z) + + def test_project_v3_onto_other_as_tuple(self): + """Project onto other tuple as vector.""" + # arrange + v = Vector3(2, 3, 4) + other = Vector3(3, 5, 7) + + # act + actual = v.project(tuple(other)) + + # assert + expected = v.dot(other) / other.dot(other) * other + self.assertAlmostEqual(expected.x, actual.x) + self.assertAlmostEqual(expected.y, actual.y) + self.assertAlmostEqual(expected.z, actual.z) + + def test_project_v3_onto_other_as_list(self): + """Project onto other list as vector.""" + # arrange + v = Vector3(2, 3, 4) + other = Vector3(3, 5, 7) + + # act + actual = v.project(list(other)) + + # assert + expected = v.dot(other) / other.dot(other) * other + self.assertAlmostEqual(expected.x, actual.x) + self.assertAlmostEqual(expected.y, actual.y) + self.assertAlmostEqual(expected.z, actual.z) + + def test_project_v3_raises_if_other_has_zero_length(self): + """Check if exception is raise when projected on vector has zero length.""" + # arrange + v = Vector3(2, 3, 4) + other = Vector3(0, 0, 0) + + # act / assert + self.assertRaises(ValueError, v.project, other) + + def test_project_v3_raises_if_other_is_not_iterable(self): + """Check if exception is raise when projected on vector is not iterable.""" + # arrange + v = Vector3(2, 3, 4) + other = 10 + + # act / assert + self.assertRaises(TypeError, v.project, other) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/midi_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/midi_test.py new file mode 100644 index 0000000..ffbe8ff --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/midi_test.py @@ -0,0 +1,472 @@ +import unittest + + +import pygame + + +class MidiInputTest(unittest.TestCase): + __tags__ = ["interactive"] + + def setUp(self): + import pygame.midi + + pygame.midi.init() + in_id = pygame.midi.get_default_input_id() + if in_id != -1: + self.midi_input = pygame.midi.Input(in_id) + else: + self.midi_input = None + + def tearDown(self): + if self.midi_input: + self.midi_input.close() + pygame.midi.quit() + + def test_Input(self): + i = pygame.midi.get_default_input_id() + if self.midi_input: + self.assertEqual(self.midi_input.device_id, i) + + # try feeding it an input id. + i = pygame.midi.get_default_output_id() + + # can handle some invalid input too. + self.assertRaises(pygame.midi.MidiException, pygame.midi.Input, i) + self.assertRaises(pygame.midi.MidiException, pygame.midi.Input, 9009) + self.assertRaises(pygame.midi.MidiException, pygame.midi.Input, -1) + self.assertRaises(TypeError, pygame.midi.Input, "1234") + self.assertRaises(OverflowError, pygame.midi.Input, pow(2, 99)) + + def test_poll(self): + + if not self.midi_input: + self.skipTest("No midi Input device") + + self.assertFalse(self.midi_input.poll()) + # TODO fake some incoming data + + pygame.midi.quit() + self.assertRaises(RuntimeError, self.midi_input.poll) + # set midi_input to None to avoid error in tearDown + self.midi_input = None + + def test_read(self): + + if not self.midi_input: + self.skipTest("No midi Input device") + + read = self.midi_input.read(5) + self.assertEqual(read, []) + # TODO fake some incoming data + + pygame.midi.quit() + self.assertRaises(RuntimeError, self.midi_input.read, 52) + # set midi_input to None to avoid error in tearDown + self.midi_input = None + + def test_close(self): + if not self.midi_input: + self.skipTest("No midi Input device") + + self.assertIsNotNone(self.midi_input._input) + self.midi_input.close() + self.assertIsNone(self.midi_input._input) + + +class MidiOutputTest(unittest.TestCase): + __tags__ = ["interactive"] + + def setUp(self): + import pygame.midi + + pygame.midi.init() + m_out_id = pygame.midi.get_default_output_id() + if m_out_id != -1: + self.midi_output = pygame.midi.Output(m_out_id) + else: + self.midi_output = None + + def tearDown(self): + if self.midi_output: + self.midi_output.close() + pygame.midi.quit() + + def test_Output(self): + i = pygame.midi.get_default_output_id() + if self.midi_output: + self.assertEqual(self.midi_output.device_id, i) + + # try feeding it an input id. + i = pygame.midi.get_default_input_id() + + # can handle some invalid input too. + self.assertRaises(pygame.midi.MidiException, pygame.midi.Output, i) + self.assertRaises(pygame.midi.MidiException, pygame.midi.Output, 9009) + self.assertRaises(pygame.midi.MidiException, pygame.midi.Output, -1) + self.assertRaises(TypeError, pygame.midi.Output, "1234") + self.assertRaises(OverflowError, pygame.midi.Output, pow(2, 99)) + + def test_note_off(self): + if self.midi_output: + out = self.midi_output + out.note_on(5, 30, 0) + out.note_off(5, 30, 0) + with self.assertRaises(ValueError) as cm: + out.note_off(5, 30, 25) + self.assertEqual(str(cm.exception), "Channel not between 0 and 15.") + with self.assertRaises(ValueError) as cm: + out.note_off(5, 30, -1) + self.assertEqual(str(cm.exception), "Channel not between 0 and 15.") + + def test_note_on(self): + if self.midi_output: + out = self.midi_output + out.note_on(5, 30, 0) + out.note_on(5, 42, 10) + with self.assertRaises(ValueError) as cm: + out.note_on(5, 30, 25) + self.assertEqual(str(cm.exception), "Channel not between 0 and 15.") + with self.assertRaises(ValueError) as cm: + out.note_on(5, 30, -1) + self.assertEqual(str(cm.exception), "Channel not between 0 and 15.") + + def test_set_instrument(self): + + if not self.midi_output: + self.skipTest("No midi device") + out = self.midi_output + out.set_instrument(5) + out.set_instrument(42, channel=2) + with self.assertRaises(ValueError) as cm: + out.set_instrument(-6) + self.assertEqual(str(cm.exception), "Undefined instrument id: -6") + with self.assertRaises(ValueError) as cm: + out.set_instrument(156) + self.assertEqual(str(cm.exception), "Undefined instrument id: 156") + with self.assertRaises(ValueError) as cm: + out.set_instrument(5, -1) + self.assertEqual(str(cm.exception), "Channel not between 0 and 15.") + with self.assertRaises(ValueError) as cm: + out.set_instrument(5, 16) + self.assertEqual(str(cm.exception), "Channel not between 0 and 15.") + + def test_write(self): + if not self.midi_output: + self.skipTest("No midi device") + + out = self.midi_output + out.write([[[0xC0, 0, 0], 20000]]) + # is equivalent to + out.write([[[0xC0], 20000]]) + # example from the docstring : + # 1. choose program change 1 at time 20000 and + # 2. send note 65 with velocity 100 500 ms later + out.write([[[0xC0, 0, 0], 20000], [[0x90, 60, 100], 20500]]) + + out.write([]) + verrry_long = [[[0x90, 60, i % 100], 20000 + 100 * i] for i in range(1024)] + out.write(verrry_long) + + too_long = [[[0x90, 60, i % 100], 20000 + 100 * i] for i in range(1025)] + self.assertRaises(IndexError, out.write, too_long) + # test wrong data + with self.assertRaises(TypeError) as cm: + out.write("Non sens ?") + error_msg = "unsupported operand type(s) for &: 'str' and 'int'" + self.assertEqual(str(cm.exception), error_msg) + + with self.assertRaises(TypeError) as cm: + out.write(["Hey what's that?"]) + self.assertEqual(str(cm.exception), error_msg) + + def test_write_short(self): + if not self.midi_output: + self.skipTest("No midi device") + + out = self.midi_output + # program change + out.write_short(0xC0) + # put a note on, then off. + out.write_short(0x90, 65, 100) + out.write_short(0x80, 65, 100) + out.write_short(0x90) + + def test_write_sys_ex(self): + if not self.midi_output: + self.skipTest("No midi device") + + out = self.midi_output + out.write_sys_ex(pygame.midi.time(), [0xF0, 0x7D, 0x10, 0x11, 0x12, 0x13, 0xF7]) + + def test_pitch_bend(self): + # FIXME : pitch_bend in the code, but not in documentation + if not self.midi_output: + self.skipTest("No midi device") + + out = self.midi_output + with self.assertRaises(ValueError) as cm: + out.pitch_bend(5, channel=-1) + self.assertEqual(str(cm.exception), "Channel not between 0 and 15.") + with self.assertRaises(ValueError) as cm: + out.pitch_bend(5, channel=16) + with self.assertRaises(ValueError) as cm: + out.pitch_bend(-10001, 1) + self.assertEqual( + str(cm.exception), + "Pitch bend value must be between " "-8192 and +8191, not -10001.", + ) + with self.assertRaises(ValueError) as cm: + out.pitch_bend(10665, 2) + + def test_close(self): + if not self.midi_output: + self.skipTest("No midi device") + self.assertIsNotNone(self.midi_output._output) + self.midi_output.close() + self.assertIsNone(self.midi_output._output) + + def test_abort(self): + if not self.midi_output: + self.skipTest("No midi device") + self.assertEqual(self.midi_output._aborted, 0) + self.midi_output.abort() + self.assertEqual(self.midi_output._aborted, 1) + + +class MidiModuleTest(unittest.TestCase): + """Midi module tests that require midi hardware or midi.init(). + + See MidiModuleNonInteractiveTest for non-interactive module tests. + """ + + __tags__ = ["interactive"] + + def setUp(self): + import pygame.midi + + pygame.midi.init() + + def tearDown(self): + pygame.midi.quit() + + def test_get_count(self): + c = pygame.midi.get_count() + self.assertIsInstance(c, int) + self.assertTrue(c >= 0) + + def test_get_default_input_id(self): + + midin_id = pygame.midi.get_default_input_id() + # if there is a not None return make sure it is an int. + self.assertIsInstance(midin_id, int) + self.assertTrue(midin_id >= -1) + pygame.midi.quit() + self.assertRaises(RuntimeError, pygame.midi.get_default_output_id) + + def test_get_default_output_id(self): + + c = pygame.midi.get_default_output_id() + self.assertIsInstance(c, int) + self.assertTrue(c >= -1) + pygame.midi.quit() + self.assertRaises(RuntimeError, pygame.midi.get_default_output_id) + + def test_get_device_info(self): + + an_id = pygame.midi.get_default_output_id() + if an_id != -1: + interf, name, input, output, opened = pygame.midi.get_device_info(an_id) + self.assertEqual(output, 1) + self.assertEqual(input, 0) + self.assertEqual(opened, 0) + + an_in_id = pygame.midi.get_default_input_id() + if an_in_id != -1: + r = pygame.midi.get_device_info(an_in_id) + # if r is None, it means that the id is out of range. + interf, name, input, output, opened = r + + self.assertEqual(output, 0) + self.assertEqual(input, 1) + self.assertEqual(opened, 0) + out_of_range = pygame.midi.get_count() + for num in range(out_of_range): + self.assertIsNotNone(pygame.midi.get_device_info(num)) + info = pygame.midi.get_device_info(out_of_range) + self.assertIsNone(info) + + def test_init(self): + + pygame.midi.quit() + self.assertRaises(RuntimeError, pygame.midi.get_count) + # initialising many times should be fine. + pygame.midi.init() + pygame.midi.init() + pygame.midi.init() + pygame.midi.init() + + self.assertTrue(pygame.midi.get_init()) + + def test_quit(self): + + # It is safe to call this more than once. + pygame.midi.quit() + pygame.midi.init() + pygame.midi.quit() + pygame.midi.quit() + pygame.midi.init() + pygame.midi.init() + pygame.midi.quit() + + self.assertFalse(pygame.midi.get_init()) + + def test_get_init(self): + # Already initialized as pygame.midi.init() was called in setUp(). + self.assertTrue(pygame.midi.get_init()) + + def test_time(self): + + mtime = pygame.midi.time() + self.assertIsInstance(mtime, int) + # should be close to 2-3... since the timer is just init'd. + self.assertTrue(0 <= mtime < 100) + + +class MidiModuleNonInteractiveTest(unittest.TestCase): + """Midi module tests that do not require midi hardware or midi.init(). + + See MidiModuleTest for interactive module tests. + """ + + def setUp(self): + import pygame.midi + + def test_midiin(self): + """Ensures the MIDIIN event id exists in the midi module. + + The MIDIIN event id can be accessed via the midi module for backward + compatibility. + """ + self.assertEqual(pygame.midi.MIDIIN, pygame.MIDIIN) + self.assertEqual(pygame.midi.MIDIIN, pygame.locals.MIDIIN) + + self.assertNotEqual(pygame.midi.MIDIIN, pygame.MIDIOUT) + self.assertNotEqual(pygame.midi.MIDIIN, pygame.locals.MIDIOUT) + + def test_midiout(self): + """Ensures the MIDIOUT event id exists in the midi module. + + The MIDIOUT event id can be accessed via the midi module for backward + compatibility. + """ + self.assertEqual(pygame.midi.MIDIOUT, pygame.MIDIOUT) + self.assertEqual(pygame.midi.MIDIOUT, pygame.locals.MIDIOUT) + + self.assertNotEqual(pygame.midi.MIDIOUT, pygame.MIDIIN) + self.assertNotEqual(pygame.midi.MIDIOUT, pygame.locals.MIDIIN) + + def test_MidiException(self): + """Ensures the MidiException is raised as expected.""" + + def raiseit(): + raise pygame.midi.MidiException("Hello Midi param") + + with self.assertRaises(pygame.midi.MidiException) as cm: + raiseit() + + self.assertEqual(cm.exception.parameter, "Hello Midi param") + + def test_midis2events(self): + """Ensures midi events are properly converted to pygame events.""" + # List/tuple indexes. + MIDI_DATA = 0 + MD_STATUS = 0 + MD_DATA1 = 1 + MD_DATA2 = 2 + MD_DATA3 = 3 + + TIMESTAMP = 1 + + # Midi events take the form of: + # ((status, data1, data2, data3), timestamp) + midi_events = ( + ((0xC0, 0, 1, 2), 20000), + ((0x90, 60, 1000, "string_data"), 20001), + (("0", "1", "2", "3"), "4"), + ) + expected_num_events = len(midi_events) + + # Test different device ids. + for device_id in range(3): + pg_events = pygame.midi.midis2events(midi_events, device_id) + + self.assertEqual(len(pg_events), expected_num_events) + + for i, pg_event in enumerate(pg_events): + # Get the original midi data for comparison. + midi_event = midi_events[i] + midi_event_data = midi_event[MIDI_DATA] + + # Can't directly check event instance as pygame.event.Event is + # a function. + # self.assertIsInstance(pg_event, pygame.event.Event) + self.assertEqual(pg_event.__class__.__name__, "Event") + self.assertEqual(pg_event.type, pygame.MIDIIN) + self.assertEqual(pg_event.status, midi_event_data[MD_STATUS]) + self.assertEqual(pg_event.data1, midi_event_data[MD_DATA1]) + self.assertEqual(pg_event.data2, midi_event_data[MD_DATA2]) + self.assertEqual(pg_event.data3, midi_event_data[MD_DATA3]) + self.assertEqual(pg_event.timestamp, midi_event[TIMESTAMP]) + self.assertEqual(pg_event.vice_id, device_id) + + def test_midis2events__missing_event_data(self): + """Ensures midi events with missing values are handled properly.""" + midi_event_missing_data = ((0xC0, 0, 1), 20000) + midi_event_missing_timestamp = ((0xC0, 0, 1, 2),) + + for midi_event in (midi_event_missing_data, midi_event_missing_timestamp): + with self.assertRaises(ValueError): + events = pygame.midi.midis2events([midi_event], 0) + + def test_midis2events__extra_event_data(self): + """Ensures midi events with extra values are handled properly.""" + midi_event_extra_data = ((0xC0, 0, 1, 2, "extra"), 20000) + midi_event_extra_timestamp = ((0xC0, 0, 1, 2), 20000, "extra") + + for midi_event in (midi_event_extra_data, midi_event_extra_timestamp): + with self.assertRaises(ValueError): + events = pygame.midi.midis2events([midi_event], 0) + + def test_midis2events__extra_event_data_missing_timestamp(self): + """Ensures midi events with extra data and no timestamps are handled + properly. + """ + midi_event_extra_data_no_timestamp = ((0xC0, 0, 1, 2, "extra"),) + + with self.assertRaises(ValueError): + events = pygame.midi.midis2events([midi_event_extra_data_no_timestamp], 0) + + def test_conversions(self): + """of frequencies to midi note numbers and ansi note names.""" + from pygame.midi import frequency_to_midi, midi_to_frequency, midi_to_ansi_note + + self.assertEqual(frequency_to_midi(27.5), 21) + self.assertEqual(frequency_to_midi(36.7), 26) + self.assertEqual(frequency_to_midi(4186.0), 108) + self.assertEqual(midi_to_frequency(21), 27.5) + self.assertEqual(midi_to_frequency(26), 36.7) + self.assertEqual(midi_to_frequency(108), 4186.0) + self.assertEqual(midi_to_ansi_note(21), "A0") + self.assertEqual(midi_to_ansi_note(71), "B4") + self.assertEqual(midi_to_ansi_note(82), "A#5") + self.assertEqual(midi_to_ansi_note(83), "B5") + self.assertEqual(midi_to_ansi_note(93), "A6") + self.assertEqual(midi_to_ansi_note(94), "A#6") + self.assertEqual(midi_to_ansi_note(95), "B6") + self.assertEqual(midi_to_ansi_note(96), "C7") + self.assertEqual(midi_to_ansi_note(102), "F#7") + self.assertEqual(midi_to_ansi_note(108), "C8") + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/mixer_music_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_music_tags.py new file mode 100644 index 0000000..30f6893 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_music_tags.py @@ -0,0 +1,7 @@ +__tags__ = [] + +import pygame +import sys + +if "pygame.mixer_music" not in sys.modules: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/mixer_music_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_music_test.py new file mode 100644 index 0000000..3d1c1f7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_music_test.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- + +import os +import sys +import platform +import unittest +import time + +from pygame.tests.test_utils import example_path +import pygame + + +class MixerMusicModuleTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Initializing the mixer is slow, so minimize the times it is called. + pygame.mixer.init() + + @classmethod + def tearDownClass(cls): + pygame.mixer.quit() + + def setUp(cls): + # This makes sure the mixer is always initialized before each test (in + # case a test calls pygame.mixer.quit()). + if pygame.mixer.get_init() is None: + pygame.mixer.init() + + def test_load_mp3(self): + "|tags:music|" + self.music_load("mp3") + + def test_load_ogg(self): + "|tags:music|" + self.music_load("ogg") + + def test_load_wav(self): + "|tags:music|" + self.music_load("wav") + + def music_load(self, format): + data_fname = example_path("data") + + path = os.path.join(data_fname, "house_lo.%s" % format) + if os.sep == "\\": + path = path.replace("\\", "\\\\") + umusfn = str(path) + bmusfn = umusfn.encode() + + pygame.mixer.music.load(umusfn) + pygame.mixer.music.load(bmusfn) + + def test_load_object(self): + """test loading music from file-like objects.""" + formats = ["ogg", "wav"] + data_fname = example_path("data") + for f in formats: + path = os.path.join(data_fname, "house_lo.%s" % f) + if os.sep == "\\": + path = path.replace("\\", "\\\\") + bmusfn = path.encode() + + with open(bmusfn, "rb") as musf: + pygame.mixer.music.load(musf) + + def test_object_namehint(self): + """test loading & queuing music from file-like objects with namehint argument.""" + formats = ["wav", "ogg"] + data_fname = example_path("data") + for f in formats: + path = os.path.join(data_fname, "house_lo.%s" % f) + if os.sep == "\\": + path = path.replace("\\", "\\\\") + bmusfn = path.encode() + + # these two "with open" blocks need to be separate, which is kinda weird + with open(bmusfn, "rb") as musf: + pygame.mixer.music.load(musf, f) + + with open(bmusfn, "rb") as musf: + pygame.mixer.music.queue(musf, f) + + with open(bmusfn, "rb") as musf: + pygame.mixer.music.load(musf, namehint=f) + + with open(bmusfn, "rb") as musf: + pygame.mixer.music.queue(musf, namehint=f) + + def test_load_unicode(self): + """test non-ASCII unicode path""" + import shutil + + ep = example_path("data") + temp_file = os.path.join(ep, u"你好.wav") + org_file = os.path.join(ep, u"house_lo.wav") + try: + with open(temp_file, "w") as f: + pass + os.remove(temp_file) + except IOError: + raise unittest.SkipTest("the path cannot be opened") + shutil.copy(org_file, temp_file) + try: + pygame.mixer.music.load(temp_file) + pygame.mixer.music.load(org_file) # unload + finally: + os.remove(temp_file) + + def test_unload(self): + import shutil + import tempfile + + ep = example_path("data") + org_file = os.path.join(ep, u"house_lo.wav") + tmpfd, tmppath = tempfile.mkstemp(".wav") + os.close(tmpfd) + shutil.copy(org_file, tmppath) + try: + pygame.mixer.music.load(tmppath) + pygame.mixer.music.unload() + finally: + os.remove(tmppath) + + def test_queue_mp3(self): + """Ensures queue() accepts mp3 files. + + |tags:music| + """ + filename = example_path(os.path.join("data", "house_lo.mp3")) + pygame.mixer.music.queue(filename) + + def test_queue_ogg(self): + """Ensures queue() accepts ogg files. + + |tags:music| + """ + filename = example_path(os.path.join("data", "house_lo.ogg")) + pygame.mixer.music.queue(filename) + + def test_queue_wav(self): + """Ensures queue() accepts wav files. + + |tags:music| + """ + filename = example_path(os.path.join("data", "house_lo.wav")) + pygame.mixer.music.queue(filename) + + def test_queue__multiple_calls(self): + """Ensures queue() can be called multiple times.""" + ogg_file = example_path(os.path.join("data", "house_lo.ogg")) + wav_file = example_path(os.path.join("data", "house_lo.wav")) + + pygame.mixer.music.queue(ogg_file) + pygame.mixer.music.queue(wav_file) + + def test_queue__arguments(self): + """Ensures queue() can be called with proper arguments.""" + wav_file = example_path(os.path.join("data", "house_lo.wav")) + + pygame.mixer.music.queue(wav_file, loops=2) + pygame.mixer.music.queue(wav_file, namehint="") + pygame.mixer.music.queue(wav_file, "") + pygame.mixer.music.queue(wav_file, "", 2) + + def test_queue__no_file(self): + """Ensures queue() correctly handles missing the file argument.""" + with self.assertRaises(TypeError): + pygame.mixer.music.queue() + + def test_queue__invalid_sound_type(self): + """Ensures queue() correctly handles invalid file types.""" + not_a_sound_file = example_path(os.path.join("data", "city.png")) + + with self.assertRaises(pygame.error): + pygame.mixer.music.queue(not_a_sound_file) + + def test_queue__invalid_filename(self): + """Ensures queue() correctly handles invalid filenames.""" + with self.assertRaises(pygame.error): + pygame.mixer.music.queue("") + + def test_music_pause__unpause(self): + """Ensure music has the correct position immediately after unpausing + + |tags:music| + """ + filename = example_path(os.path.join("data", "house_lo.mp3")) + pygame.mixer.music.load(filename) + pygame.mixer.music.play() + + # Wait 0.05s, then pause + time.sleep(0.05) + pygame.mixer.music.pause() + # Wait 0.05s, get position, unpause, then get position again + time.sleep(0.05) + before_unpause = pygame.mixer.music.get_pos() + pygame.mixer.music.unpause() + after_unpause = pygame.mixer.music.get_pos() + + self.assertEqual(before_unpause, after_unpause) + + def todo_test_stop(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.stop: + + # Stops the music playback if it is currently playing. + + self.fail() + + def todo_test_rewind(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.rewind: + + # Resets playback of the current music to the beginning. + + self.fail() + + def todo_test_get_pos(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.get_pos: + + # This gets the number of milliseconds that the music has been playing + # for. The returned time only represents how long the music has been + # playing; it does not take into account any starting position + # offsets. + # + + self.fail() + + def todo_test_fadeout(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.fadeout: + + # This will stop the music playback after it has been faded out over + # the specified time (measured in milliseconds). + # + # Note, that this function blocks until the music has faded out. + + self.fail() + + @unittest.skipIf( + os.environ.get("SDL_AUDIODRIVER") == "disk", + 'disk audio driver "playback" writing to disk is slow', + ) + def test_play__start_time(self): + + pygame.display.init() + + # music file is 7 seconds long + filename = example_path(os.path.join("data", "house_lo.ogg")) + pygame.mixer.music.load(filename) + start_time_in_seconds = 6.0 # 6 seconds + + music_finished = False + clock = pygame.time.Clock() + start_time_in_ms = clock.tick() + # should play the last 1 second + pygame.mixer.music.play(0, start=start_time_in_seconds) + running = True + while running: + pygame.event.pump() + + if not (pygame.mixer.music.get_busy() or music_finished): + music_finished = True + time_to_finish = (clock.tick() - start_time_in_ms) // 1000 + self.assertEqual(time_to_finish, 1) + running = False + + def todo_test_play(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.play: + + # This will play the loaded music stream. If the music is already + # playing it will be restarted. + # + # The loops argument controls the number of repeats a music will play. + # play(5) will cause the music to played once, then repeated five + # times, for a total of six. If the loops is -1 then the music will + # repeat indefinitely. + # + # The starting position argument controls where in the music the song + # starts playing. The starting position is dependent on the format of + # music playing. MP3 and OGG use the position as time (in seconds). + # MOD music it is the pattern order number. Passing a startpos will + # raise a NotImplementedError if it cannot set the start position + # + + self.fail() + + def todo_test_load(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.load: + + # This will load a music file and prepare it for playback. If a music + # stream is already playing it will be stopped. This does not start + # the music playing. + # + # Music can only be loaded from filenames, not python file objects + # like the other pygame loading functions. + # + + self.fail() + + def todo_test_get_volume(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.get_volume: + + # Returns the current volume for the mixer. The value will be between + # 0.0 and 1.0. + # + + self.fail() + + def todo_test_set_endevent(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.set_endevent: + + # This causes Pygame to signal (by means of the event queue) when the + # music is done playing. The argument determines the type of event + # that will be queued. + # + # The event will be queued every time the music finishes, not just the + # first time. To stop the event from being queued, call this method + # with no argument. + # + + self.fail() + + def todo_test_pause(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.pause: + + # Temporarily stop playback of the music stream. It can be resumed + # with the pygame.mixer.music.unpause() function. + # + + self.fail() + + def test_get_busy(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.get_busy: + + # Returns True when the music stream is actively playing. When the + # music is idle this returns False. + # + + self.music_load("ogg") + self.assertFalse(pygame.mixer.music.get_busy()) + pygame.mixer.music.play() + self.assertTrue(pygame.mixer.music.get_busy()) + pygame.mixer.music.pause() + self.assertFalse(pygame.mixer.music.get_busy()) + + def todo_test_get_endevent(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.get_endevent: + + # Returns the event type to be sent every time the music finishes + # playback. If there is no endevent the function returns + # pygame.NOEVENT. + # + + self.fail() + + def todo_test_unpause(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.unpause: + + # This will resume the playback of a music stream after it has been paused. + + self.fail() + + def todo_test_set_volume(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer_music.set_volume: + + # Set the volume of the music playback. The value argument is between + # 0.0 and 1.0. When new music is loaded the volume is reset. + # + + self.fail() + + def todo_test_set_pos(self): + + # __doc__ (as of 2010-24-05) for pygame.mixer_music.set_pos: + + # This sets the position in the music file where playback will start. The + # meaning of "pos", a float (or a number that can be converted to a float), + # depends on the music format. Newer versions of SDL_mixer have better + # positioning support than earlier. An SDLError is raised if a particular + # format does not support positioning. + # + + self.fail() + + def test_init(self): + """issue #955. unload music whenever mixer.quit() is called""" + import tempfile + import shutil + + testfile = example_path(os.path.join("data", "house_lo.wav")) + tempcopy = os.path.join(tempfile.gettempdir(), "tempfile.wav") + + for i in range(10): + pygame.mixer.init() + try: + shutil.copy2(testfile, tempcopy) + pygame.mixer.music.load(tempcopy) + pygame.mixer.quit() + finally: + os.remove(tempcopy) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/mixer_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_tags.py new file mode 100644 index 0000000..06a9de2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_tags.py @@ -0,0 +1,7 @@ +__tags__ = [] + +import pygame +import sys + +if "pygame.mixer" not in sys.modules: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/mixer_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_test.py new file mode 100644 index 0000000..0d5985d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/mixer_test.py @@ -0,0 +1,1193 @@ +# -*- coding: utf8 -*- + +import sys +import os +import unittest +import pathlib +import platform + +from pygame.tests.test_utils import example_path, AssertRaisesRegexMixin + +import pygame +from pygame import mixer + +IS_PYPY = "PyPy" == platform.python_implementation() + +################################### CONSTANTS ################################## + +FREQUENCIES = [11025, 22050, 44100, 48000] +SIZES = [-16, -8, 8, 16] # fixme +# size 32 failed in test_get_init__returns_exact_values_used_for_init +CHANNELS = [1, 2] +BUFFERS = [3024] + +CONFIGS = [ + {"frequency": f, "size": s, "channels": c} + for f in FREQUENCIES + for s in SIZES + for c in CHANNELS +] +# Using all CONFIGS fails on a Mac; probably older SDL_mixer; we could do: +# if platform.system() == 'Darwin': +# But using all CONFIGS is very slow (> 10 sec for example) +# And probably, we don't need to be so exhaustive, hence: + +CONFIG = {"frequency": 44100, "size": 32, "channels": 2, "allowedchanges": 0} + + +class InvalidBool(object): + """To help test invalid bool values.""" + + __nonzero__ = None + __bool__ = None + + +############################## MODULE LEVEL TESTS ############################# + + +class MixerModuleTest(unittest.TestCase): + def tearDown(self): + mixer.quit() + mixer.pre_init(0, 0, 0, 0) + + def test_init__keyword_args(self): + # note: this test used to loop over all CONFIGS, but it's very slow.. + mixer.init(**CONFIG) + mixer_conf = mixer.get_init() + + self.assertEqual(mixer_conf[0], CONFIG["frequency"]) + # Not all "sizes" are supported on all systems, hence "abs". + self.assertEqual(abs(mixer_conf[1]), abs(CONFIG["size"])) + self.assertGreaterEqual(mixer_conf[2], CONFIG["channels"]) + + def test_pre_init__keyword_args(self): + # note: this test used to loop over all CONFIGS, but it's very slow.. + mixer.pre_init(**CONFIG) + mixer.init() + + mixer_conf = mixer.get_init() + + self.assertEqual(mixer_conf[0], CONFIG["frequency"]) + # Not all "sizes" are supported on all systems, hence "abs". + self.assertEqual(abs(mixer_conf[1]), abs(CONFIG["size"])) + self.assertGreaterEqual(mixer_conf[2], CONFIG["channels"]) + + def test_pre_init__zero_values(self): + # Ensure that argument values of 0 are replaced with + # default values. No way to check buffer size though. + mixer.pre_init(22050, -8, 1) # Non default values + mixer.pre_init(0, 0, 0) # Should reset to default values + mixer.init(allowedchanges=0) + self.assertEqual(mixer.get_init()[0], 44100) + self.assertEqual(mixer.get_init()[1], -16) + self.assertGreaterEqual(mixer.get_init()[2], 2) + + def test_init__zero_values(self): + # Ensure that argument values of 0 are replaced with + # preset values. No way to check buffer size though. + mixer.pre_init(44100, 8, 1, allowedchanges=0) # None default values + mixer.init(0, 0, 0) + self.assertEqual(mixer.get_init(), (44100, 8, 1)) + + def test_get_init__returns_exact_values_used_for_init(self): + # TODO: size 32 fails in this test (maybe SDL_mixer bug) + + for init_conf in CONFIGS: + frequency, size, channels = init_conf.values() + if (frequency, size) == (22050, 16): + continue + mixer.init(frequency, size, channels) + + mixer_conf = mixer.get_init() + + self.assertEqual(tuple(init_conf.values()), mixer_conf) + mixer.quit() + + def test_get_init__returns_None_if_mixer_not_initialized(self): + self.assertIsNone(mixer.get_init()) + + def test_get_num_channels__defaults_eight_after_init(self): + mixer.init() + self.assertEqual(mixer.get_num_channels(), 8) + + def test_set_num_channels(self): + mixer.init() + + default_num_channels = mixer.get_num_channels() + for i in range(1, default_num_channels + 1): + mixer.set_num_channels(i) + self.assertEqual(mixer.get_num_channels(), i) + + def test_quit(self): + """get_num_channels() Should throw pygame.error if uninitialized + after mixer.quit()""" + mixer.init() + mixer.quit() + self.assertRaises(pygame.error, mixer.get_num_channels) + + # TODO: FIXME: appveyor and pypy (on linux) fails here sometimes. + @unittest.skipIf(sys.platform.startswith("win"), "See github issue 892.") + @unittest.skipIf(IS_PYPY, "random errors here with pypy") + def test_sound_args(self): + def get_bytes(snd): + return snd.get_raw() + + mixer.init() + + sample = b"\x00\xff" * 24 + wave_path = example_path(os.path.join("data", "house_lo.wav")) + uwave_path = str(wave_path) + bwave_path = uwave_path.encode(sys.getfilesystemencoding()) + snd = mixer.Sound(file=wave_path) + self.assertTrue(snd.get_length() > 0.5) + snd_bytes = get_bytes(snd) + self.assertTrue(len(snd_bytes) > 1000) + + self.assertEqual(get_bytes(mixer.Sound(wave_path)), snd_bytes) + + self.assertEqual(get_bytes(mixer.Sound(file=uwave_path)), snd_bytes) + self.assertEqual(get_bytes(mixer.Sound(uwave_path)), snd_bytes) + arg_emsg = "Sound takes either 1 positional or 1 keyword argument" + + with self.assertRaises(TypeError) as cm: + mixer.Sound() + self.assertEqual(str(cm.exception), arg_emsg) + with self.assertRaises(TypeError) as cm: + mixer.Sound(wave_path, buffer=sample) + self.assertEqual(str(cm.exception), arg_emsg) + with self.assertRaises(TypeError) as cm: + mixer.Sound(sample, file=wave_path) + self.assertEqual(str(cm.exception), arg_emsg) + with self.assertRaises(TypeError) as cm: + mixer.Sound(buffer=sample, file=wave_path) + self.assertEqual(str(cm.exception), arg_emsg) + + with self.assertRaises(TypeError) as cm: + mixer.Sound(foobar=sample) + self.assertEqual(str(cm.exception), "Unrecognized keyword argument 'foobar'") + + snd = mixer.Sound(wave_path, **{}) + self.assertEqual(get_bytes(snd), snd_bytes) + snd = mixer.Sound(*[], **{"file": wave_path}) + + with self.assertRaises(TypeError) as cm: + mixer.Sound([]) + self.assertEqual(str(cm.exception), "Unrecognized argument (type list)") + + with self.assertRaises(TypeError) as cm: + snd = mixer.Sound(buffer=[]) + emsg = "Expected object with buffer interface: got a list" + self.assertEqual(str(cm.exception), emsg) + + ufake_path = str("12345678") + self.assertRaises(IOError, mixer.Sound, ufake_path) + self.assertRaises(IOError, mixer.Sound, "12345678") + + with self.assertRaises(TypeError) as cm: + mixer.Sound(buffer=str("something")) + emsg = "Unicode object not allowed as buffer object" + self.assertEqual(str(cm.exception), emsg) + self.assertEqual(get_bytes(mixer.Sound(buffer=sample)), sample) + if type(sample) != str: + somebytes = get_bytes(mixer.Sound(sample)) + # on python 2 we do not allow using string except as file name. + self.assertEqual(somebytes, sample) + self.assertEqual(get_bytes(mixer.Sound(file=bwave_path)), snd_bytes) + self.assertEqual(get_bytes(mixer.Sound(bwave_path)), snd_bytes) + + snd = mixer.Sound(wave_path) + with self.assertRaises(TypeError) as cm: + mixer.Sound(wave_path, array=snd) + self.assertEqual(str(cm.exception), arg_emsg) + with self.assertRaises(TypeError) as cm: + mixer.Sound(buffer=sample, array=snd) + self.assertEqual(str(cm.exception), arg_emsg) + snd2 = mixer.Sound(array=snd) + self.assertEqual(snd.get_raw(), snd2.get_raw()) + + def test_sound_unicode(self): + """test non-ASCII unicode path""" + mixer.init() + import shutil + + ep = example_path("data") + temp_file = os.path.join(ep, u"你好.wav") + org_file = os.path.join(ep, u"house_lo.wav") + shutil.copy(org_file, temp_file) + try: + with open(temp_file, "rb") as f: + pass + except IOError: + raise unittest.SkipTest("the path cannot be opened") + + try: + sound = mixer.Sound(temp_file) + del sound + finally: + os.remove(temp_file) + + @unittest.skipIf( + os.environ.get("SDL_AUDIODRIVER") == "disk", + "this test fails without real sound card", + ) + def test_array_keyword(self): + try: + from numpy import ( + array, + arange, + zeros, + int8, + uint8, + int16, + uint16, + int32, + uint32, + ) + except ImportError: + self.skipTest("requires numpy") + + freq = 22050 + format_list = [-8, 8, -16, 16] + channels_list = [1, 2] + + a_lists = dict((f, []) for f in format_list) + a32u_mono = arange(0, 256, 1, uint32) + a16u_mono = a32u_mono.astype(uint16) + a8u_mono = a32u_mono.astype(uint8) + au_list_mono = [(1, a) for a in [a8u_mono, a16u_mono, a32u_mono]] + for format in format_list: + if format > 0: + a_lists[format].extend(au_list_mono) + a32s_mono = arange(-128, 128, 1, int32) + a16s_mono = a32s_mono.astype(int16) + a8s_mono = a32s_mono.astype(int8) + as_list_mono = [(1, a) for a in [a8s_mono, a16s_mono, a32s_mono]] + for format in format_list: + if format < 0: + a_lists[format].extend(as_list_mono) + a32u_stereo = zeros([a32u_mono.shape[0], 2], uint32) + a32u_stereo[:, 0] = a32u_mono + a32u_stereo[:, 1] = 255 - a32u_mono + a16u_stereo = a32u_stereo.astype(uint16) + a8u_stereo = a32u_stereo.astype(uint8) + au_list_stereo = [(2, a) for a in [a8u_stereo, a16u_stereo, a32u_stereo]] + for format in format_list: + if format > 0: + a_lists[format].extend(au_list_stereo) + a32s_stereo = zeros([a32s_mono.shape[0], 2], int32) + a32s_stereo[:, 0] = a32s_mono + a32s_stereo[:, 1] = -1 - a32s_mono + a16s_stereo = a32s_stereo.astype(int16) + a8s_stereo = a32s_stereo.astype(int8) + as_list_stereo = [(2, a) for a in [a8s_stereo, a16s_stereo, a32s_stereo]] + for format in format_list: + if format < 0: + a_lists[format].extend(as_list_stereo) + + for format in format_list: + for channels in channels_list: + try: + mixer.init(freq, format, channels) + except pygame.error: + # Some formats (e.g. 16) may not be supported. + continue + try: + __, f, c = mixer.get_init() + if f != format or c != channels: + # Some formats (e.g. -8) may not be supported. + continue + for c, a in a_lists[format]: + self._test_array_argument(format, a, c == channels) + finally: + mixer.quit() + + def _test_array_argument(self, format, a, test_pass): + from numpy import array, all as all_ + + try: + snd = mixer.Sound(array=a) + except ValueError: + if not test_pass: + return + self.fail("Raised ValueError: Format %i, dtype %s" % (format, a.dtype)) + if not test_pass: + self.fail( + "Did not raise ValueError: Format %i, dtype %s" % (format, a.dtype) + ) + a2 = array(snd) + a3 = a.astype(a2.dtype) + lshift = abs(format) - 8 * a.itemsize + if lshift >= 0: + # This is asymmetric with respect to downcasting. + a3 <<= lshift + self.assertTrue(all_(a2 == a3), "Format %i, dtype %s" % (format, a.dtype)) + + def _test_array_interface_fail(self, a): + self.assertRaises(ValueError, mixer.Sound, array=a) + + def test_array_interface(self): + mixer.init(22050, -16, 1, allowedchanges=0) + snd = mixer.Sound(buffer=b"\x00\x7f" * 20) + d = snd.__array_interface__ + self.assertTrue(isinstance(d, dict)) + if pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN: + typestr = "") if is_lil_endian else (">", "<") + shape = (10, channels)[:ndim] + strides = (channels * itemsize, itemsize)[2 - ndim :] + exp = Exporter(shape, format=frev + "i") + snd = mixer.Sound(array=exp) + buflen = len(exp) * itemsize * channels + imp = Importer(snd, buftools.PyBUF_SIMPLE) + self.assertEqual(imp.ndim, 0) + self.assertTrue(imp.format is None) + self.assertEqual(imp.len, buflen) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.shape is None) + self.assertTrue(imp.strides is None) + self.assertTrue(imp.suboffsets is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.buf, snd._samples_address) + imp = Importer(snd, buftools.PyBUF_WRITABLE) + self.assertEqual(imp.ndim, 0) + self.assertTrue(imp.format is None) + self.assertEqual(imp.len, buflen) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.shape is None) + self.assertTrue(imp.strides is None) + self.assertTrue(imp.suboffsets is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.buf, snd._samples_address) + imp = Importer(snd, buftools.PyBUF_FORMAT) + self.assertEqual(imp.ndim, 0) + self.assertEqual(imp.format, format) + self.assertEqual(imp.len, buflen) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.shape is None) + self.assertTrue(imp.strides is None) + self.assertTrue(imp.suboffsets is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.buf, snd._samples_address) + imp = Importer(snd, buftools.PyBUF_ND) + self.assertEqual(imp.ndim, ndim) + self.assertTrue(imp.format is None) + self.assertEqual(imp.len, buflen) + self.assertEqual(imp.itemsize, itemsize) + self.assertEqual(imp.shape, shape) + self.assertTrue(imp.strides is None) + self.assertTrue(imp.suboffsets is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.buf, snd._samples_address) + imp = Importer(snd, buftools.PyBUF_STRIDES) + self.assertEqual(imp.ndim, ndim) + self.assertTrue(imp.format is None) + self.assertEqual(imp.len, buflen) + self.assertEqual(imp.itemsize, itemsize) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertTrue(imp.suboffsets is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.buf, snd._samples_address) + imp = Importer(snd, buftools.PyBUF_FULL_RO) + self.assertEqual(imp.ndim, ndim) + self.assertEqual(imp.format, format) + self.assertEqual(imp.len, buflen) + self.assertEqual(imp.itemsize, 2) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertTrue(imp.suboffsets is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.buf, snd._samples_address) + imp = Importer(snd, buftools.PyBUF_FULL_RO) + self.assertEqual(imp.ndim, ndim) + self.assertEqual(imp.format, format) + self.assertEqual(imp.len, buflen) + self.assertEqual(imp.itemsize, itemsize) + self.assertEqual(imp.shape, exp.shape) + self.assertEqual(imp.strides, strides) + self.assertTrue(imp.suboffsets is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.buf, snd._samples_address) + imp = Importer(snd, buftools.PyBUF_C_CONTIGUOUS) + self.assertEqual(imp.ndim, ndim) + self.assertTrue(imp.format is None) + self.assertEqual(imp.strides, strides) + imp = Importer(snd, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertEqual(imp.ndim, ndim) + self.assertTrue(imp.format is None) + self.assertEqual(imp.strides, strides) + if ndim == 1: + imp = Importer(snd, buftools.PyBUF_F_CONTIGUOUS) + self.assertEqual(imp.ndim, 1) + self.assertTrue(imp.format is None) + self.assertEqual(imp.strides, strides) + else: + self.assertRaises(BufferError, Importer, snd, buftools.PyBUF_F_CONTIGUOUS) + + def todo_test_fadeout(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.fadeout: + + # pygame.mixer.fadeout(time): return None + # fade out the volume on all sounds before stopping + # + # This will fade out the volume on all active channels over the time + # argument in milliseconds. After the sound is muted the playback will + # stop. + # + + self.fail() + + def test_find_channel(self): + # __doc__ (as of 2008-08-02) for pygame.mixer.find_channel: + + # pygame.mixer.find_channel(force=False): return Channel + # find an unused channel + mixer.init() + + filename = example_path(os.path.join("data", "house_lo.wav")) + sound = mixer.Sound(file=filename) + + num_channels = mixer.get_num_channels() + + if num_channels > 0: + found_channel = mixer.find_channel() + self.assertIsNotNone(found_channel) + + # try playing on all channels + channels = [] + for channel_id in range(0, num_channels): + channel = mixer.Channel(channel_id) + channel.play(sound) + channels.append(channel) + + # should fail without being forceful + found_channel = mixer.find_channel() + self.assertIsNone(found_channel) + + # try forcing without keyword + found_channel = mixer.find_channel(True) + self.assertIsNotNone(found_channel) + + # try forcing with keyword + found_channel = mixer.find_channel(force=True) + self.assertIsNotNone(found_channel) + + for channel in channels: + channel.stop() + found_channel = mixer.find_channel() + self.assertIsNotNone(found_channel) + + def todo_test_get_busy(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.get_busy: + + # pygame.mixer.get_busy(): return bool + # test if any sound is being mixed + # + # Returns True if the mixer is busy mixing any channels. If the mixer + # is idle then this return False. + # + + self.fail() + + def todo_test_pause(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.pause: + + # pygame.mixer.pause(): return None + # temporarily stop playback of all sound channels + # + # This will temporarily stop all playback on the active mixer + # channels. The playback can later be resumed with + # pygame.mixer.unpause() + # + + self.fail() + + def test_set_reserved(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.set_reserved: + + # pygame.mixer.set_reserved(count): return count + mixer.init() + default_num_channels = mixer.get_num_channels() + + # try reserving all the channels + result = mixer.set_reserved(default_num_channels) + self.assertEqual(result, default_num_channels) + + # try reserving all the channels + 1 + result = mixer.set_reserved(default_num_channels + 1) + # should still be default + self.assertEqual(result, default_num_channels) + + # try unreserving all + result = mixer.set_reserved(0) + # should still be default + self.assertEqual(result, 0) + + # try reserving half + result = mixer.set_reserved(int(default_num_channels / 2)) + # should still be default + self.assertEqual(result, int(default_num_channels / 2)) + + def todo_test_stop(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.stop: + + # pygame.mixer.stop(): return None + # stop playback of all sound channels + # + # This will stop all playback of all active mixer channels. + + self.fail() + + def todo_test_unpause(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.unpause: + + # pygame.mixer.unpause(): return None + # resume paused playback of sound channels + # + # This will resume all active sound channels after they have been paused. + + self.fail() + + def test_get_sdl_mixer_version(self): + """Ensures get_sdl_mixer_version works correctly with no args.""" + expected_length = 3 + expected_type = tuple + expected_item_type = int + + version = pygame.mixer.get_sdl_mixer_version() + + self.assertIsInstance(version, expected_type) + self.assertEqual(len(version), expected_length) + + for item in version: + self.assertIsInstance(item, expected_item_type) + + def test_get_sdl_mixer_version__args(self): + """Ensures get_sdl_mixer_version works correctly using args.""" + expected_length = 3 + expected_type = tuple + expected_item_type = int + + for value in (True, False): + version = pygame.mixer.get_sdl_mixer_version(value) + + self.assertIsInstance(version, expected_type) + self.assertEqual(len(version), expected_length) + + for item in version: + self.assertIsInstance(item, expected_item_type) + + def test_get_sdl_mixer_version__kwargs(self): + """Ensures get_sdl_mixer_version works correctly using kwargs.""" + expected_length = 3 + expected_type = tuple + expected_item_type = int + + for value in (True, False): + version = pygame.mixer.get_sdl_mixer_version(linked=value) + + self.assertIsInstance(version, expected_type) + self.assertEqual(len(version), expected_length) + + for item in version: + self.assertIsInstance(item, expected_item_type) + + def test_get_sdl_mixer_version__invalid_args_kwargs(self): + """Ensures get_sdl_mixer_version handles invalid args and kwargs.""" + invalid_bool = InvalidBool() + + with self.assertRaises(TypeError): + version = pygame.mixer.get_sdl_mixer_version(invalid_bool) + + with self.assertRaises(TypeError): + version = pygame.mixer.get_sdl_mixer_version(linked=invalid_bool) + + def test_get_sdl_mixer_version__linked_equals_compiled(self): + """Ensures get_sdl_mixer_version's linked/compiled versions are equal.""" + linked_version = pygame.mixer.get_sdl_mixer_version(linked=True) + complied_version = pygame.mixer.get_sdl_mixer_version(linked=False) + + self.assertTupleEqual(linked_version, complied_version) + + +############################## CHANNEL CLASS TESTS ############################# + + +class ChannelTypeTest(AssertRaisesRegexMixin, unittest.TestCase): + @classmethod + def setUpClass(cls): + # Initializing the mixer is slow, so minimize the times it is called. + mixer.init() + + @classmethod + def tearDownClass(cls): + mixer.quit() + + def setUp(cls): + # This makes sure the mixer is always initialized before each test (in + # case a test calls pygame.mixer.quit()). + if mixer.get_init() is None: + mixer.init() + + def test_channel(self): + """Ensure Channel() creation works.""" + channel = mixer.Channel(0) + + self.assertIsInstance(channel, mixer.ChannelType) + self.assertEqual(channel.__class__.__name__, "Channel") + + def test_channel__without_arg(self): + """Ensure exception for Channel() creation with no argument.""" + with self.assertRaises(TypeError): + mixer.Channel() + + def test_channel__invalid_id(self): + """Ensure exception for Channel() creation with an invalid id.""" + with self.assertRaises(IndexError): + mixer.Channel(-1) + + def test_channel__before_init(self): + """Ensure exception for Channel() creation with non-init mixer.""" + mixer.quit() + + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + mixer.Channel(0) + + def todo_test_fadeout(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.fadeout: + + # Channel.fadeout(time): return None + # stop playback after fading channel out + # + # Stop playback of a channel after fading out the sound over the given + # time argument in milliseconds. + # + + self.fail() + + def test_get_busy(self): + """Ensure an idle channel's busy state is correct.""" + expected_busy = False + channel = mixer.Channel(0) + + busy = channel.get_busy() + + self.assertEqual(busy, expected_busy) + + def todo_test_get_busy__active(self): + """Ensure an active channel's busy state is correct.""" + self.fail() + + def todo_test_get_endevent(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.get_endevent: + + # Channel.get_endevent(): return type + # get the event a channel sends when playback stops + # + # Returns the event type to be sent every time the Channel finishes + # playback of a Sound. If there is no endevent the function returns + # pygame.NOEVENT. + # + + self.fail() + + def todo_test_get_queue(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.get_queue: + + # Channel.get_queue(): return Sound + # return any Sound that is queued + # + # If a Sound is already queued on this channel it will be returned. + # Once the queued sound begins playback it will no longer be on the + # queue. + # + + self.fail() + + def todo_test_get_sound(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.get_sound: + + # Channel.get_sound(): return Sound + # get the currently playing Sound + # + # Return the actual Sound object currently playing on this channel. If + # the channel is idle None is returned. + # + + self.fail() + + def test_get_volume(self): + """Ensure a channel's volume can be retrieved.""" + expected_volume = 1.0 # default + channel = mixer.Channel(0) + + volume = channel.get_volume() + + self.assertAlmostEqual(volume, expected_volume) + + def todo_test_get_volume__while_playing(self): + """Ensure a channel's volume can be retrieved while playing.""" + self.fail() + + def todo_test_pause(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.pause: + + # Channel.pause(): return None + # temporarily stop playback of a channel + # + # Temporarily stop the playback of sound on a channel. It can be + # resumed at a later time with Channel.unpause() + # + + self.fail() + + def todo_test_play(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.play: + + # Channel.play(Sound, loops=0, maxtime=0, fade_ms=0): return None + # play a Sound on a specific Channel + # + # This will begin playback of a Sound on a specific Channel. If the + # Channel is currently playing any other Sound it will be stopped. + # + # The loops argument has the same meaning as in Sound.play(): it is + # the number of times to repeat the sound after the first time. If it + # is 3, the sound will be played 4 times (the first time, then three + # more). If loops is -1 then the playback will repeat indefinitely. + # + # As in Sound.play(), the maxtime argument can be used to stop + # playback of the Sound after a given number of milliseconds. + # + # As in Sound.play(), the fade_ms argument can be used fade in the sound. + + self.fail() + + def todo_test_queue(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.queue: + + # Channel.queue(Sound): return None + # queue a Sound object to follow the current + # + # When a Sound is queued on a Channel, it will begin playing + # immediately after the current Sound is finished. Each channel can + # only have a single Sound queued at a time. The queued Sound will + # only play if the current playback finished automatically. It is + # cleared on any other call to Channel.stop() or Channel.play(). + # + # If there is no sound actively playing on the Channel then the Sound + # will begin playing immediately. + # + + self.fail() + + def todo_test_set_endevent(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.set_endevent: + + # Channel.set_endevent(): return None + # Channel.set_endevent(type): return None + # have the channel send an event when playback stops + # + # When an endevent is set for a channel, it will send an event to the + # pygame queue every time a sound finishes playing on that channel + # (not just the first time). Use pygame.event.get() to retrieve the + # endevent once it's sent. + # + # Note that if you called Sound.play(n) or Channel.play(sound,n), the + # end event is sent only once: after the sound has been played "n+1" + # times (see the documentation of Sound.play). + # + # If Channel.stop() or Channel.play() is called while the sound was + # still playing, the event will be posted immediately. + # + # The type argument will be the event id sent to the queue. This can + # be any valid event type, but a good choice would be a value between + # pygame.locals.USEREVENT and pygame.locals.NUMEVENTS. If no type + # argument is given then the Channel will stop sending endevents. + # + + self.fail() + + def todo_test_set_volume(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.set_volume: + + # Channel.set_volume(value): return None + # Channel.set_volume(left, right): return None + # set the volume of a playing channel + # + # Set the volume (loudness) of a playing sound. When a channel starts + # to play its volume value is reset. This only affects the current + # sound. The value argument is between 0.0 and 1.0. + # + # If one argument is passed, it will be the volume of both speakers. + # If two arguments are passed and the mixer is in stereo mode, the + # first argument will be the volume of the left speaker and the second + # will be the volume of the right speaker. (If the second argument is + # None, the first argument will be the volume of both speakers.) + # + # If the channel is playing a Sound on which set_volume() has also + # been called, both calls are taken into account. For example: + # + # sound = pygame.mixer.Sound("s.wav") + # channel = s.play() # Sound plays at full volume by default + # sound.set_volume(0.9) # Now plays at 90% of full volume. + # sound.set_volume(0.6) # Now plays at 60% (previous value replaced). + # channel.set_volume(0.5) # Now plays at 30% (0.6 * 0.5). + + self.fail() + + def todo_test_stop(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.stop: + + # Channel.stop(): return None + # stop playback on a Channel + # + # Stop sound playback on a channel. After playback is stopped the + # channel becomes available for new Sounds to play on it. + # + + self.fail() + + def todo_test_unpause(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.unpause: + + # Channel.unpause(): return None + # resume pause playback of a channel + # + # Resume the playback on a paused channel. + + self.fail() + + +############################### SOUND CLASS TESTS ############################## + + +class SoundTypeTest(AssertRaisesRegexMixin, unittest.TestCase): + @classmethod + def tearDownClass(cls): + mixer.quit() + + def setUp(cls): + # This makes sure the mixer is always initialized before each test (in + # case a test calls pygame.mixer.quit()). + if mixer.get_init() is None: + mixer.init() + + # See MixerModuleTest's methods test_sound_args(), test_sound_unicode(), + # and test_array_keyword() for additional testing of Sound() creation. + def test_sound(self): + """Ensure Sound() creation with a filename works.""" + filename = example_path(os.path.join("data", "house_lo.wav")) + sound1 = mixer.Sound(filename) + sound2 = mixer.Sound(file=filename) + + self.assertIsInstance(sound1, mixer.Sound) + self.assertIsInstance(sound2, mixer.Sound) + + def test_sound__from_file_object(self): + """Ensure Sound() creation with a file object works.""" + filename = example_path(os.path.join("data", "house_lo.wav")) + + # Using 'with' ensures the file is closed even if test fails. + with open(filename, "rb") as file_obj: + sound = mixer.Sound(file_obj) + + self.assertIsInstance(sound, mixer.Sound) + + def test_sound__from_sound_object(self): + """Ensure Sound() creation with a Sound() object works.""" + filename = example_path(os.path.join("data", "house_lo.wav")) + sound_obj = mixer.Sound(file=filename) + + sound = mixer.Sound(sound_obj) + + self.assertIsInstance(sound, mixer.Sound) + + def test_sound__from_pathlib(self): + """Ensure Sound() creation with a pathlib.Path object works.""" + path = pathlib.Path(example_path(os.path.join("data", "house_lo.wav"))) + sound1 = mixer.Sound(path) + sound2 = mixer.Sound(file=path) + self.assertIsInstance(sound1, mixer.Sound) + self.assertIsInstance(sound2, mixer.Sound) + + def todo_test_sound__from_buffer(self): + """Ensure Sound() creation with a buffer works.""" + self.fail() + + def todo_test_sound__from_array(self): + """Ensure Sound() creation with an array works.""" + self.fail() + + def test_sound__without_arg(self): + """Ensure exception raised for Sound() creation with no argument.""" + with self.assertRaises(TypeError): + mixer.Sound() + + def test_sound__before_init(self): + """Ensure exception raised for Sound() creation with non-init mixer.""" + mixer.quit() + filename = example_path(os.path.join("data", "house_lo.wav")) + + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + mixer.Sound(file=filename) + + @unittest.skipIf(IS_PYPY, "pypy skip") + def test_samples_address(self): + """Test the _samples_address getter.""" + try: + from ctypes import pythonapi, c_void_p, py_object + + Bytes_FromString = pythonapi.PyBytes_FromString + + Bytes_FromString.restype = c_void_p + Bytes_FromString.argtypes = [py_object] + samples = b"abcdefgh" # keep byte size a multiple of 4 + sample_bytes = Bytes_FromString(samples) + + snd = mixer.Sound(buffer=samples) + + self.assertNotEqual(snd._samples_address, sample_bytes) + finally: + pygame.mixer.quit() + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + snd._samples_address + + def todo_test_fadeout(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Sound.fadeout: + + # Sound.fadeout(time): return None + # stop sound playback after fading out + # + # This will stop playback of the sound after fading it out over the + # time argument in milliseconds. The Sound will fade and stop on all + # actively playing channels. + # + + self.fail() + + def test_get_length(self): + """Tests if get_length returns a correct length.""" + try: + for size in SIZES: + pygame.mixer.quit() + pygame.mixer.init(size=size) + filename = example_path(os.path.join("data", "punch.wav")) + sound = mixer.Sound(file=filename) + # The sound data is in the mixer output format. So dividing the + # length of the raw sound data by the mixer settings gives + # the expected length of the sound. + sound_bytes = sound.get_raw() + mix_freq, mix_bits, mix_channels = pygame.mixer.get_init() + mix_bytes = abs(mix_bits) / 8 + expected_length = ( + float(len(sound_bytes)) / mix_freq / mix_bytes / mix_channels + ) + self.assertAlmostEqual(expected_length, sound.get_length()) + finally: + pygame.mixer.quit() + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + sound.get_length() + + def test_get_num_channels(self): + """ + Tests if Sound.get_num_channels returns the correct number + of channels playing a specific sound. + """ + try: + filename = example_path(os.path.join("data", "house_lo.wav")) + sound = mixer.Sound(file=filename) + + self.assertEqual(sound.get_num_channels(), 0) + sound.play() + self.assertEqual(sound.get_num_channels(), 1) + sound.play() + self.assertEqual(sound.get_num_channels(), 2) + sound.stop() + self.assertEqual(sound.get_num_channels(), 0) + finally: + pygame.mixer.quit() + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + sound.get_num_channels() + + def test_get_volume(self): + """Ensure a sound's volume can be retrieved.""" + try: + expected_volume = 1.0 # default + filename = example_path(os.path.join("data", "house_lo.wav")) + sound = mixer.Sound(file=filename) + + volume = sound.get_volume() + + self.assertAlmostEqual(volume, expected_volume) + finally: + pygame.mixer.quit() + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + sound.get_volume() + + def todo_test_get_volume__while_playing(self): + """Ensure a sound's volume can be retrieved while playing.""" + self.fail() + + def todo_test_play(self): + + # __doc__ (as of 2008-08-02) for pygame.mixer.Sound.play: + + # Sound.play(loops=0, maxtime=0, fade_ms=0): return Channel + # begin sound playback + # + # Begin playback of the Sound (i.e., on the computer's speakers) on an + # available Channel. This will forcibly select a Channel, so playback + # may cut off a currently playing sound if necessary. + # + # The loops argument controls how many times the sample will be + # repeated after being played the first time. A value of 5 means that + # the sound will be played once, then repeated five times, and so is + # played a total of six times. The default value (zero) means the + # Sound is not repeated, and so is only played once. If loops is set + # to -1 the Sound will loop indefinitely (though you can still call + # stop() to stop it). + # + # The maxtime argument can be used to stop playback after a given + # number of milliseconds. + # + # The fade_ms argument will make the sound start playing at 0 volume + # and fade up to full volume over the time given. The sample may end + # before the fade-in is complete. + # + # This returns the Channel object for the channel that was selected. + + self.fail() + + def test_set_volume(self): + """Ensure a sound's volume can be set.""" + try: + float_delta = 1.0 / 128 # SDL volume range is 0 to 128 + filename = example_path(os.path.join("data", "house_lo.wav")) + sound = mixer.Sound(file=filename) + current_volume = sound.get_volume() + + # (volume_set_value : expected_volume) + volumes = ( + (-1, current_volume), # value < 0 won't change volume + (0, 0.0), + (0.01, 0.01), + (0.1, 0.1), + (0.5, 0.5), + (0.9, 0.9), + (0.99, 0.99), + (1, 1.0), + (1.1, 1.0), + (2.0, 1.0), + ) + + for volume_set_value, expected_volume in volumes: + sound.set_volume(volume_set_value) + + self.assertAlmostEqual( + sound.get_volume(), expected_volume, delta=float_delta + ) + finally: + pygame.mixer.quit() + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + sound.set_volume(1) + + def todo_test_set_volume__while_playing(self): + """Ensure a sound's volume can be set while playing.""" + self.fail() + + def test_stop(self): + """Ensure stop can be called while not playing a sound.""" + try: + expected_channels = 0 + filename = example_path(os.path.join("data", "house_lo.wav")) + sound = mixer.Sound(file=filename) + + sound.stop() + + self.assertEqual(sound.get_num_channels(), expected_channels) + finally: + pygame.mixer.quit() + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + sound.stop() + + def todo_test_stop__while_playing(self): + """Ensure stop stops a playing sound.""" + self.fail() + + def test_get_raw(self): + """Ensure get_raw returns the correct bytestring.""" + try: + samples = b"abcdefgh" # keep byte size a multiple of 4 + snd = mixer.Sound(buffer=samples) + + raw = snd.get_raw() + + self.assertIsInstance(raw, bytes) + self.assertEqual(raw, samples) + finally: + pygame.mixer.quit() + with self.assertRaisesRegex(pygame.error, "mixer not initialized"): + snd.get_raw() + + +##################################### MAIN ##################################### + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/mouse_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/mouse_test.py new file mode 100644 index 0000000..c23035f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/mouse_test.py @@ -0,0 +1,350 @@ +import unittest +import os +import platform +import warnings +import pygame + + +DARWIN = "Darwin" in platform.platform() + + +class MouseTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + # The display needs to be initialized for mouse functions. + pygame.display.init() + + @classmethod + def tearDownClass(cls): + pygame.display.quit() + + +class MouseModuleInteractiveTest(MouseTests): + + __tags__ = ["interactive"] + + def test_set_pos(self): + """Ensures set_pos works correctly. + Requires tester to move the mouse to be on the window. + """ + pygame.display.set_mode((500, 500)) + pygame.event.get() # Pump event queue to make window get focus on macos. + + if not pygame.mouse.get_focused(): + # The window needs to be focused for the mouse.set_pos to work on macos. + return + clock = pygame.time.Clock() + + expected_pos = ((10, 0), (0, 0), (499, 0), (499, 499), (341, 143), (94, 49)) + + for x, y in expected_pos: + pygame.mouse.set_pos(x, y) + pygame.event.get() + found_pos = pygame.mouse.get_pos() + + clock.tick() + time_passed = 0.0 + ready_to_test = False + + while not ready_to_test and time_passed <= 1000.0: # Avoid endless loop + time_passed += clock.tick() + for event in pygame.event.get(): + if event.type == pygame.MOUSEMOTION: + ready_to_test = True + + self.assertEqual(found_pos, (x, y)) + + +class MouseModuleTest(MouseTests): + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER", "") == "dummy", + "Cursors not supported on headless test machines", + ) + def test_get_cursor(self): + """Ensures get_cursor works correctly.""" + + # error should be raised when the display is unintialized + with self.assertRaises(pygame.error): + pygame.display.quit() + pygame.mouse.get_cursor() + + pygame.display.init() + + size = (8, 8) + hotspot = (0, 0) + xormask = (0, 96, 120, 126, 112, 96, 0, 0) + andmask = (224, 240, 254, 255, 254, 240, 96, 0) + + expected_length = 4 + expected_cursor = pygame.cursors.Cursor(size, hotspot, xormask, andmask) + pygame.mouse.set_cursor(expected_cursor) + + try: + cursor = pygame.mouse.get_cursor() + + self.assertIsInstance(cursor, pygame.cursors.Cursor) + self.assertEqual(len(cursor), expected_length) + + for info in cursor: + self.assertIsInstance(info, tuple) + + pygame.mouse.set_cursor(size, hotspot, xormask, andmask) + self.assertEqual(pygame.mouse.get_cursor(), expected_cursor) + + # SDLError should be raised when the mouse cursor is NULL + except pygame.error: + with self.assertRaises(pygame.error): + pygame.mouse.get_cursor() + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER", "") == "dummy", + "mouse.set_system_cursor only available in SDL2", + ) + def test_set_system_cursor(self): + """Ensures set_system_cursor works correctly.""" + + with warnings.catch_warnings(record=True) as w: + """From Pygame 2.0.1, set_system_cursor() should raise a deprecation warning""" + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + # Error should be raised when the display is uninitialized + with self.assertRaises(pygame.error): + pygame.display.quit() + pygame.mouse.set_system_cursor(pygame.SYSTEM_CURSOR_HAND) + + pygame.display.init() + + # TypeError raised when PyArg_ParseTuple fails to parse parameters + with self.assertRaises(TypeError): + pygame.mouse.set_system_cursor("b") + with self.assertRaises(TypeError): + pygame.mouse.set_system_cursor(None) + with self.assertRaises(TypeError): + pygame.mouse.set_system_cursor((8, 8), (0, 0)) + + # Right type, invalid value + with self.assertRaises(pygame.error): + pygame.mouse.set_system_cursor(2000) + + # Working as intended + self.assertEqual( + pygame.mouse.set_system_cursor(pygame.SYSTEM_CURSOR_ARROW), None + ) + + # Making sure the warnings are working properly + self.assertEqual(len(w), 6) + self.assertTrue( + all([issubclass(warn.category, DeprecationWarning) for warn in w]) + ) + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER", "") == "dummy", + "Cursors not supported on headless test machines", + ) + def test_set_cursor(self): + """Ensures set_cursor works correctly.""" + + # Bitmap cursor information + size = (8, 8) + hotspot = (0, 0) + xormask = (0, 126, 64, 64, 32, 16, 0, 0) + andmask = (254, 255, 254, 112, 56, 28, 12, 0) + bitmap_cursor = pygame.cursors.Cursor(size, hotspot, xormask, andmask) + + # System cursor information + constant = pygame.SYSTEM_CURSOR_ARROW + system_cursor = pygame.cursors.Cursor(constant) + + # Color cursor information (also uses hotspot variable from Bitmap cursor info) + surface = pygame.Surface((10, 10)) + color_cursor = pygame.cursors.Cursor(hotspot, surface) + + pygame.display.quit() + + # Bitmap: Error should be raised when the display is uninitialized + with self.assertRaises(pygame.error): + pygame.mouse.set_cursor(bitmap_cursor) + + # System: Error should be raised when the display is uninitialized + with self.assertRaises(pygame.error): + pygame.mouse.set_cursor(system_cursor) + + # Color: Error should be raised when the display is uninitialized + with self.assertRaises(pygame.error): + pygame.mouse.set_cursor(color_cursor) + + pygame.display.init() + + # Bitmap: TypeError raised when PyArg_ParseTuple fails to parse parameters + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(("w", "h"), hotspot, xormask, andmask) + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(size, ("0", "0"), xormask, andmask) + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(size, ("x", "y", "z"), xormask, andmask) + + # Bitmap: TypeError raised when either mask is not a sequence + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(size, hotspot, 12345678, andmask) + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(size, hotspot, xormask, 12345678) + + # Bitmap: TypeError raised when element of mask is not an integer + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(size, hotspot, "00000000", andmask) + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(size, hotspot, xormask, (2, [0], 4, 0, 0, 8, 0, 1)) + + # Bitmap: ValueError raised when width not divisible by 8 + with self.assertRaises(ValueError): + pygame.mouse.set_cursor((3, 8), hotspot, xormask, andmask) + + # Bitmap: ValueError raised when length of either mask != width * height / 8 + with self.assertRaises(ValueError): + pygame.mouse.set_cursor((16, 2), hotspot, (128, 64, 32), andmask) + with self.assertRaises(ValueError): + pygame.mouse.set_cursor((16, 2), hotspot, xormask, (192, 96, 48, 0, 1)) + + # Bitmap: Working as intended + self.assertEqual( + pygame.mouse.set_cursor((16, 1), hotspot, (8, 0), (0, 192)), None + ) + pygame.mouse.set_cursor(size, hotspot, xormask, andmask) + self.assertEqual(pygame.mouse.get_cursor(), bitmap_cursor) + + # Bitmap: Working as intended + lists + masks with no references + pygame.mouse.set_cursor(size, hotspot, list(xormask), list(andmask)) + self.assertEqual(pygame.mouse.get_cursor(), bitmap_cursor) + + # System: TypeError raised when constant is invalid + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(-50021232) + with self.assertRaises(TypeError): + pygame.mouse.set_cursor("yellow") + + # System: Working as intended + self.assertEqual(pygame.mouse.set_cursor(constant), None) + pygame.mouse.set_cursor(constant) + self.assertEqual(pygame.mouse.get_cursor(), system_cursor) + pygame.mouse.set_cursor(system_cursor) + self.assertEqual(pygame.mouse.get_cursor(), system_cursor) + + # Color: TypeError raised with invalid parameters + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(("x", "y"), surface) + with self.assertRaises(TypeError): + pygame.mouse.set_cursor(hotspot, "not_a_surface") + + # Color: Working as intended + self.assertEqual(pygame.mouse.set_cursor(hotspot, surface), None) + pygame.mouse.set_cursor(hotspot, surface) + self.assertEqual(pygame.mouse.get_cursor(), color_cursor) + pygame.mouse.set_cursor(color_cursor) + self.assertEqual(pygame.mouse.get_cursor(), color_cursor) + + # Color: Working as intended + Surface with no references is returned okay + pygame.mouse.set_cursor((0, 0), pygame.Surface((20, 20))) + cursor = pygame.mouse.get_cursor() + self.assertEqual(cursor.type, "color") + self.assertEqual(cursor.data[0], (0, 0)) + self.assertEqual(cursor.data[1].get_size(), (20, 20)) + + def test_get_focused(self): + """Ensures get_focused returns the correct type.""" + focused = pygame.mouse.get_focused() + + self.assertIsInstance(focused, int) + + def test_get_pressed(self): + """Ensures get_pressed returns the correct types.""" + expected_length = 3 + buttons_pressed = pygame.mouse.get_pressed() + self.assertIsInstance(buttons_pressed, tuple) + self.assertEqual(len(buttons_pressed), expected_length) + for value in buttons_pressed: + self.assertIsInstance(value, bool) + + expected_length = 5 + buttons_pressed = pygame.mouse.get_pressed(num_buttons=5) + self.assertIsInstance(buttons_pressed, tuple) + self.assertEqual(len(buttons_pressed), expected_length) + for value in buttons_pressed: + self.assertIsInstance(value, bool) + + expected_length = 3 + buttons_pressed = pygame.mouse.get_pressed(3) + self.assertIsInstance(buttons_pressed, tuple) + self.assertEqual(len(buttons_pressed), expected_length) + for value in buttons_pressed: + self.assertIsInstance(value, bool) + + expected_length = 5 + buttons_pressed = pygame.mouse.get_pressed(5) + self.assertIsInstance(buttons_pressed, tuple) + self.assertEqual(len(buttons_pressed), expected_length) + for value in buttons_pressed: + self.assertIsInstance(value, bool) + + with self.assertRaises(ValueError): + pygame.mouse.get_pressed(4) + + def test_get_pos(self): + """Ensures get_pos returns the correct types.""" + expected_length = 2 + + pos = pygame.mouse.get_pos() + + self.assertIsInstance(pos, tuple) + self.assertEqual(len(pos), expected_length) + for value in pos: + self.assertIsInstance(value, int) + + def test_set_pos__invalid_pos(self): + """Ensures set_pos handles invalid positions correctly.""" + for invalid_pos in ((1,), [1, 2, 3], 1, "1", (1, "1"), []): + + with self.assertRaises(TypeError): + pygame.mouse.set_pos(invalid_pos) + + def test_get_rel(self): + """Ensures get_rel returns the correct types.""" + expected_length = 2 + + rel = pygame.mouse.get_rel() + + self.assertIsInstance(rel, tuple) + self.assertEqual(len(rel), expected_length) + for value in rel: + self.assertIsInstance(value, int) + + def test_get_visible(self): + """Ensures get_visible works correctly.""" + for expected_value in (False, True): + pygame.mouse.set_visible(expected_value) + + visible = pygame.mouse.get_visible() + + self.assertEqual(visible, expected_value) + + def test_set_visible(self): + """Ensures set_visible returns the correct values.""" + # Set to a known state. + pygame.mouse.set_visible(True) + + for expected_visible in (False, True): + prev_visible = pygame.mouse.set_visible(expected_visible) + + self.assertEqual(prev_visible, not expected_visible) + + def test_set_visible__invalid_value(self): + """Ensures set_visible handles invalid positions correctly.""" + for invalid_value in ((1,), [1, 2, 3], 1.1, "1", (1, "1"), []): + with self.assertRaises(TypeError): + prev_visible = pygame.mouse.set_visible(invalid_value) + + +################################################################################ + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/pixelarray_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/pixelarray_test.py new file mode 100644 index 0000000..c93f36a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/pixelarray_test.py @@ -0,0 +1,1660 @@ +import sys +import platform + +try: + reduce +except NameError: + from functools import reduce +import operator +import weakref +import gc +import unittest + +from pygame.tests.test_utils import SurfaceSubclass + +try: + from pygame.tests.test_utils import arrinter +except NameError: + pass + +import pygame + + +IS_PYPY = "PyPy" == platform.python_implementation() + + +class TestMixin(object): + def assert_surfaces_equal(self, s1, s2, msg=None): + """Checks if two surfaces are equal in size and color.""" + w, h = s1.get_size() + + self.assertTupleEqual((w, h), s2.get_size(), msg) + + msg = "" if msg is None else "{}, ".format(msg) + msg += "size: ({}, {})".format(w, h) + + for x in range(w): + for y in range(h): + self.assertEqual( + s1.get_at((x, y)), + s2.get_at((x, y)), + "{}, position: ({}, {})".format(msg, x, y), + ) + + def assert_surface_filled(self, surface, expected_color, msg=None): + """Checks if the surface is filled with the given color.""" + width, height = surface.get_size() + + surface.lock() # Lock for possible speed up. + for pos in ((x, y) for y in range(height) for x in range(width)): + self.assertEqual(surface.get_at(pos), expected_color, msg) + surface.unlock() + + +@unittest.skipIf(IS_PYPY, "pypy having issues") +class PixelArrayTypeTest(unittest.TestCase, TestMixin): + def test_compare(self): + # __doc__ (as of 2008-06-25) for pygame.pixelarray.PixelArray.compare: + + # PixelArray.compare (array, distance=0, weights=(0.299, 0.587, 0.114)): Return PixelArray + # Compares the PixelArray with another one. + + w = 10 + h = 20 + size = w, h + sf = pygame.Surface(size, 0, 32) + ar = pygame.PixelArray(sf) + sf2 = pygame.Surface(size, 0, 32) + self.assertRaises(TypeError, ar.compare, sf2) + ar2 = pygame.PixelArray(sf2) + ar3 = ar.compare(ar2) + self.assertTrue(isinstance(ar3, pygame.PixelArray)) + self.assertEqual(ar3.shape, size) + sf2.fill(pygame.Color("white")) + self.assert_surfaces_equal(sf2, ar3.surface) + del ar3 + r = pygame.Rect(2, 5, 6, 13) + sf.fill(pygame.Color("blue"), r) + sf2.fill(pygame.Color("red")) + sf2.fill(pygame.Color("blue"), r) + ar3 = ar.compare(ar2) + sf.fill(pygame.Color("white"), r) + self.assert_surfaces_equal(sf, ar3.surface) + + # FINISH ME! + # Test other bit depths, slices, and distance != 0. + + def test_compare__same_colors_within_distance(self): + """Ensures compare works correctly with same colored surfaces.""" + size = (3, 5) + pixelarray_result_color = pygame.Color("white") + surface_color = (127, 127, 127, 255) + + for depth in (8, 16, 24, 32): + expected_pixelarray_surface = pygame.Surface(size, depth=depth) + expected_pixelarray_surface.fill(pixelarray_result_color) + + # Copy the surface to ensure same dimensions/formatting. + surf_a = expected_pixelarray_surface.copy() + surf_a.fill(surface_color) + # For non-32 bit depths, the actual color can be different from what + # was filled. + expected_surface_color = surf_a.get_at((0, 0)) + + pixelarray_a = pygame.PixelArray(surf_a) + pixelarray_b = pygame.PixelArray(surf_a.copy()) + + for distance in (0.0, 0.01, 0.1, 1.0): + pixelarray_result = pixelarray_a.compare( + pixelarray_b, distance=distance + ) + + # Ensure the resulting pixelarray is correct and that the original + # surfaces were not changed. + self.assert_surfaces_equal( + pixelarray_result.surface, + expected_pixelarray_surface, + (depth, distance), + ) + self.assert_surface_filled( + pixelarray_a.surface, expected_surface_color, (depth, distance) + ) + self.assert_surface_filled( + pixelarray_b.surface, expected_surface_color, (depth, distance) + ) + + pixelarray_a.close() + pixelarray_b.close() + pixelarray_result.close() + + def test_compare__different_colors_within_distance(self): + """Ensures compare works correctly with different colored surfaces + and the color difference is within the given distance. + """ + size = (3, 5) + pixelarray_result_color = pygame.Color("white") + surface_a_color = (127, 127, 127, 255) + surface_b_color = (128, 127, 127, 255) + + for depth in (8, 16, 24, 32): + expected_pixelarray_surface = pygame.Surface(size, depth=depth) + expected_pixelarray_surface.fill(pixelarray_result_color) + + # Copy the surface to ensure same dimensions/formatting. + surf_a = expected_pixelarray_surface.copy() + surf_a.fill(surface_a_color) + # For non-32 bit depths, the actual color can be different from what + # was filled. + expected_surface_a_color = surf_a.get_at((0, 0)) + pixelarray_a = pygame.PixelArray(surf_a) + + surf_b = expected_pixelarray_surface.copy() + surf_b.fill(surface_b_color) + # For non-32 bit depths, the actual color can be different from what + # was filled. + expected_surface_b_color = surf_b.get_at((0, 0)) + pixelarray_b = pygame.PixelArray(surf_b) + + for distance in (0.2, 0.3, 0.5, 1.0): + pixelarray_result = pixelarray_a.compare( + pixelarray_b, distance=distance + ) + + # Ensure the resulting pixelarray is correct and that the original + # surfaces were not changed. + self.assert_surfaces_equal( + pixelarray_result.surface, + expected_pixelarray_surface, + (depth, distance), + ) + self.assert_surface_filled( + pixelarray_a.surface, expected_surface_a_color, (depth, distance) + ) + self.assert_surface_filled( + pixelarray_b.surface, expected_surface_b_color, (depth, distance) + ) + + pixelarray_a.close() + pixelarray_b.close() + pixelarray_result.close() + + def test_compare__different_colors_not_within_distance(self): + """Ensures compare works correctly with different colored surfaces + and the color difference is not within the given distance. + """ + size = (3, 5) + pixelarray_result_color = pygame.Color("black") + surface_a_color = (127, 127, 127, 255) + surface_b_color = (128, 127, 127, 255) + + for depth in (8, 16, 24, 32): + expected_pixelarray_surface = pygame.Surface(size, depth=depth) + expected_pixelarray_surface.fill(pixelarray_result_color) + + # Copy the surface to ensure same dimensions/formatting. + surf_a = expected_pixelarray_surface.copy() + surf_a.fill(surface_a_color) + # For non-32 bit depths, the actual color can be different from what + # was filled. + expected_surface_a_color = surf_a.get_at((0, 0)) + pixelarray_a = pygame.PixelArray(surf_a) + + surf_b = expected_pixelarray_surface.copy() + surf_b.fill(surface_b_color) + # For non-32 bit depths, the actual color can be different from what + # was filled. + expected_surface_b_color = surf_b.get_at((0, 0)) + pixelarray_b = pygame.PixelArray(surf_b) + + for distance in (0.0, 0.00001, 0.0001, 0.001): + pixelarray_result = pixelarray_a.compare( + pixelarray_b, distance=distance + ) + + # Ensure the resulting pixelarray is correct and that the original + # surfaces were not changed. + self.assert_surfaces_equal( + pixelarray_result.surface, + expected_pixelarray_surface, + (depth, distance), + ) + self.assert_surface_filled( + pixelarray_a.surface, expected_surface_a_color, (depth, distance) + ) + self.assert_surface_filled( + pixelarray_b.surface, expected_surface_b_color, (depth, distance) + ) + + pixelarray_a.close() + pixelarray_b.close() + pixelarray_result.close() + + def test_close(self): + """does not crash when it is deleted.""" + s = pygame.Surface((10, 10)) + a = pygame.PixelArray(s) + a.close() + del a + + def test_close_raises(self): + """when you try to do an operation after it is closed.""" + s = pygame.Surface((10, 10)) + a = pygame.PixelArray(s) + a.close() + + def do_operation(): + a[:] + + self.assertRaises(ValueError, do_operation) + + def do_operation2(): + a[:] = 1 + + self.assertRaises(ValueError, do_operation2) + + def do_operation3(): + a.make_surface() + + self.assertRaises(ValueError, do_operation3) + + def do_operation4(): + for x in a: + pass + + self.assertRaises(ValueError, do_operation4) + + def test_context_manager(self): + """closes properly.""" + s = pygame.Surface((10, 10)) + with pygame.PixelArray(s) as a: + a[:] + + def test_pixel_array(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 20), 0, bpp) + sf.fill((0, 0, 0)) + ar = pygame.PixelArray(sf) + + self.assertEqual(ar._pixels_address, sf._pixels_address) + + if sf.mustlock(): + self.assertTrue(sf.get_locked()) + + self.assertEqual(len(ar), 10) + + del ar + if sf.mustlock(): + self.assertFalse(sf.get_locked()) + + def test_as_class(self): + # Check general new-style class freatures. + sf = pygame.Surface((2, 3), 0, 32) + ar = pygame.PixelArray(sf) + self.assertRaises(AttributeError, getattr, ar, "nonnative") + ar.nonnative = "value" + self.assertEqual(ar.nonnative, "value") + r = weakref.ref(ar) + self.assertTrue(r() is ar) + del ar + gc.collect() + self.assertTrue(r() is None) + + class C(pygame.PixelArray): + def __str__(self): + return "string (%i, %i)" % self.shape + + ar = C(sf) + self.assertEqual(str(ar), "string (2, 3)") + r = weakref.ref(ar) + self.assertTrue(r() is ar) + del ar + gc.collect() + self.assertTrue(r() is None) + + def test_pixelarray__subclassed_surface(self): + """Ensure the PixelArray constructor accepts subclassed surfaces.""" + surface = SurfaceSubclass((3, 5), 0, 32) + pixelarray = pygame.PixelArray(surface) + + self.assertIsInstance(pixelarray, pygame.PixelArray) + + # Sequence interfaces + def test_get_column(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((6, 8), 0, bpp) + sf.fill((0, 0, 255)) + val = sf.map_rgb((0, 0, 255)) + ar = pygame.PixelArray(sf) + + ar2 = ar.__getitem__(1) + self.assertEqual(len(ar2), 8) + self.assertEqual(ar2.__getitem__(0), val) + self.assertEqual(ar2.__getitem__(1), val) + self.assertEqual(ar2.__getitem__(2), val) + + ar2 = ar.__getitem__(-1) + self.assertEqual(len(ar2), 8) + self.assertEqual(ar2.__getitem__(0), val) + self.assertEqual(ar2.__getitem__(1), val) + self.assertEqual(ar2.__getitem__(2), val) + + @unittest.skipIf(IS_PYPY, "pypy malloc abort") + def test_get_pixel(self): + w = 10 + h = 20 + size = w, h + bg_color = (0, 0, 255) + fg_color_y = (0, 0, 128) + fg_color_x = (0, 0, 11) + for bpp in (8, 16, 24, 32): + sf = pygame.Surface(size, 0, bpp) + mapped_bg_color = sf.map_rgb(bg_color) + mapped_fg_color_y = sf.map_rgb(fg_color_y) + mapped_fg_color_x = sf.map_rgb(fg_color_x) + self.assertNotEqual( + mapped_fg_color_y, + mapped_bg_color, + "Unusable test colors for bpp %i" % (bpp,), + ) + self.assertNotEqual( + mapped_fg_color_x, + mapped_bg_color, + "Unusable test colors for bpp %i" % (bpp,), + ) + self.assertNotEqual( + mapped_fg_color_y, + mapped_fg_color_x, + "Unusable test colors for bpp %i" % (bpp,), + ) + sf.fill(bg_color) + + ar = pygame.PixelArray(sf) + + ar_y = ar.__getitem__(1) + for y in range(h): + ar2 = ar_y.__getitem__(y) + self.assertEqual( + ar2, + mapped_bg_color, + "ar[1][%i] == %i, mapped_bg_color == %i" + % (y, ar2, mapped_bg_color), + ) + + sf.set_at((1, y), fg_color_y) + ar2 = ar_y.__getitem__(y) + self.assertEqual( + ar2, + mapped_fg_color_y, + "ar[1][%i] == %i, mapped_fg_color_y == %i" + % (y, ar2, mapped_fg_color_y), + ) + + sf.set_at((1, 1), bg_color) + for x in range(w): + ar2 = ar.__getitem__(x).__getitem__(1) + self.assertEqual( + ar2, + mapped_bg_color, + "ar[%i][1] = %i, mapped_bg_color = %i" % (x, ar2, mapped_bg_color), + ) + sf.set_at((x, 1), fg_color_x) + ar2 = ar.__getitem__(x).__getitem__(1) + self.assertEqual( + ar2, + mapped_fg_color_x, + "ar[%i][1] = %i, mapped_fg_color_x = %i" + % (x, ar2, mapped_fg_color_x), + ) + + ar2 = ar.__getitem__(0).__getitem__(0) + self.assertEqual(ar2, mapped_bg_color, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(1).__getitem__(0) + self.assertEqual(ar2, mapped_fg_color_y, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(-4).__getitem__(1) + self.assertEqual(ar2, mapped_fg_color_x, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(-4).__getitem__(5) + self.assertEqual(ar2, mapped_bg_color, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(-4).__getitem__(0) + self.assertEqual(ar2, mapped_bg_color, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(-w + 1).__getitem__(0) + self.assertEqual(ar2, mapped_fg_color_y, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(-w).__getitem__(0) + self.assertEqual(ar2, mapped_bg_color, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(5).__getitem__(-4) + self.assertEqual(ar2, mapped_bg_color, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(5).__getitem__(-h + 1) + self.assertEqual(ar2, mapped_fg_color_x, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(5).__getitem__(-h) + self.assertEqual(ar2, mapped_bg_color, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(0).__getitem__(-h + 1) + self.assertEqual(ar2, mapped_fg_color_x, "bpp = %i" % (bpp,)) + + ar2 = ar.__getitem__(0).__getitem__(-h) + self.assertEqual(ar2, mapped_bg_color, "bpp = %i" % (bpp,)) + + def test_set_pixel(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 20), 0, bpp) + sf.fill((0, 0, 0)) + ar = pygame.PixelArray(sf) + + ar.__getitem__(0).__setitem__(0, (0, 255, 0)) + self.assertEqual(ar[0][0], sf.map_rgb((0, 255, 0))) + + ar.__getitem__(1).__setitem__(1, (128, 128, 128)) + self.assertEqual(ar[1][1], sf.map_rgb((128, 128, 128))) + + ar.__getitem__(-1).__setitem__(-1, (128, 128, 128)) + self.assertEqual(ar[9][19], sf.map_rgb((128, 128, 128))) + + ar.__getitem__(-2).__setitem__(-2, (128, 128, 128)) + self.assertEqual(ar[8][-2], sf.map_rgb((128, 128, 128))) + + def test_set_column(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((6, 8), 0, bpp) + sf.fill((0, 0, 0)) + ar = pygame.PixelArray(sf) + + sf2 = pygame.Surface((6, 8), 0, bpp) + sf2.fill((0, 255, 255)) + ar2 = pygame.PixelArray(sf2) + + # Test single value assignment + ar.__setitem__(2, (128, 128, 128)) + self.assertEqual(ar[2][0], sf.map_rgb((128, 128, 128))) + self.assertEqual(ar[2][1], sf.map_rgb((128, 128, 128))) + + ar.__setitem__(-1, (0, 255, 255)) + self.assertEqual(ar[5][0], sf.map_rgb((0, 255, 255))) + self.assertEqual(ar[-1][1], sf.map_rgb((0, 255, 255))) + + ar.__setitem__(-2, (255, 255, 0)) + self.assertEqual(ar[4][0], sf.map_rgb((255, 255, 0))) + self.assertEqual(ar[-2][1], sf.map_rgb((255, 255, 0))) + + # Test list assignment. + ar.__setitem__(0, [(255, 255, 255)] * 8) + self.assertEqual(ar[0][0], sf.map_rgb((255, 255, 255))) + self.assertEqual(ar[0][1], sf.map_rgb((255, 255, 255))) + + # Test tuple assignment. + # Changed in Pygame 1.9.2 - Raises an exception. + self.assertRaises( + ValueError, + ar.__setitem__, + 1, + ( + (204, 0, 204), + (17, 17, 17), + (204, 0, 204), + (17, 17, 17), + (204, 0, 204), + (17, 17, 17), + (204, 0, 204), + (17, 17, 17), + ), + ) + + # Test pixel array assignment. + ar.__setitem__(1, ar2.__getitem__(3)) + self.assertEqual(ar[1][0], sf.map_rgb((0, 255, 255))) + self.assertEqual(ar[1][1], sf.map_rgb((0, 255, 255))) + + def test_get_slice(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 20), 0, bpp) + sf.fill((0, 0, 0)) + ar = pygame.PixelArray(sf) + + self.assertEqual(len(ar[0:2]), 2) + self.assertEqual(len(ar[3:7][3]), 20) + + self.assertEqual(ar[0:0], None) + self.assertEqual(ar[5:5], None) + self.assertEqual(ar[9:9], None) + + # Has to resolve to ar[7:8] + self.assertEqual(len(ar[-3:-2]), 1) # 2D + self.assertEqual(len(ar[-3:-2][0]), 20) # 1D + + # Try assignments. + + # 2D assignment. + ar[2:5] = (255, 255, 255) + + # 1D assignment + ar[3][3:7] = (10, 10, 10) + self.assertEqual(ar[3][5], sf.map_rgb((10, 10, 10))) + self.assertEqual(ar[3][6], sf.map_rgb((10, 10, 10))) + + @unittest.skipIf(IS_PYPY, "skipping for PyPy (segfaults on mac pypy3 6.0.0)") + def test_contains(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 20), 0, bpp) + sf.fill((0, 0, 0)) + sf.set_at((8, 8), (255, 255, 255)) + + ar = pygame.PixelArray(sf) + self.assertTrue((0, 0, 0) in ar) + self.assertTrue((255, 255, 255) in ar) + self.assertFalse((255, 255, 0) in ar) + self.assertFalse(0x0000FF in ar) + + # Test sliced array + self.assertTrue((0, 0, 0) in ar[8]) + self.assertTrue((255, 255, 255) in ar[8]) + self.assertFalse((255, 255, 0) in ar[8]) + self.assertFalse(0x0000FF in ar[8]) + + def test_get_surface(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 20), 0, bpp) + sf.fill((0, 0, 0)) + ar = pygame.PixelArray(sf) + self.assertTrue(ar.surface is sf) + + def test_get_surface__subclassed_surface(self): + """Ensure the surface attribute can handle subclassed surfaces.""" + expected_surface = SurfaceSubclass((5, 3), 0, 32) + pixelarray = pygame.PixelArray(expected_surface) + + surface = pixelarray.surface + + self.assertIs(surface, expected_surface) + self.assertIsInstance(surface, pygame.Surface) + self.assertIsInstance(surface, SurfaceSubclass) + + def test_set_slice(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((6, 8), 0, bpp) + sf.fill((0, 0, 0)) + ar = pygame.PixelArray(sf) + + # Test single value assignment + val = sf.map_rgb((128, 128, 128)) + ar[0:2] = val + self.assertEqual(ar[0][0], val) + self.assertEqual(ar[0][1], val) + self.assertEqual(ar[1][0], val) + self.assertEqual(ar[1][1], val) + + val = sf.map_rgb((0, 255, 255)) + ar[-3:-1] = val + self.assertEqual(ar[3][0], val) + self.assertEqual(ar[-2][1], val) + + val = sf.map_rgb((255, 255, 255)) + ar[-3:] = (255, 255, 255) + self.assertEqual(ar[4][0], val) + self.assertEqual(ar[-1][1], val) + + # Test array size mismatch. + # Changed in ver. 1.9.2 + # (was "Test list assignment, this is a vertical assignment.") + val = sf.map_rgb((0, 255, 0)) + self.assertRaises(ValueError, ar.__setitem__, slice(2, 4), [val] * 8) + + # And the horizontal assignment. + val = sf.map_rgb((255, 0, 0)) + val2 = sf.map_rgb((128, 0, 255)) + ar[0:2] = [val, val2] + self.assertEqual(ar[0][0], val) + self.assertEqual(ar[1][0], val2) + self.assertEqual(ar[0][1], val) + self.assertEqual(ar[1][1], val2) + self.assertEqual(ar[0][4], val) + self.assertEqual(ar[1][4], val2) + self.assertEqual(ar[0][5], val) + self.assertEqual(ar[1][5], val2) + + # Test pixelarray assignment. + ar[:] = (0, 0, 0) + sf2 = pygame.Surface((6, 8), 0, bpp) + sf2.fill((255, 0, 255)) + + val = sf.map_rgb((255, 0, 255)) + ar2 = pygame.PixelArray(sf2) + + ar[:] = ar2[:] + self.assertEqual(ar[0][0], val) + self.assertEqual(ar[5][7], val) + + # Ensure p1 ... pn are freed for array[...] = [p1, ..., pn] + # Bug fix: reference counting. + if hasattr(sys, "getrefcount"): + + class Int(int): + """Unique int instances""" + + pass + + sf = pygame.Surface((5, 2), 0, 32) + ar = pygame.PixelArray(sf) + pixel_list = [Int(i) for i in range(ar.shape[0])] + refcnts_before = [sys.getrefcount(i) for i in pixel_list] + ar[...] = pixel_list + refcnts_after = [sys.getrefcount(i) for i in pixel_list] + gc.collect() + self.assertEqual(refcnts_after, refcnts_before) + + def test_subscript(self): + # By default we do not need to work with any special __***__ + # methods as map subscripts are the first looked up by the + # object system. + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((6, 8), 0, bpp) + sf.set_at((1, 3), (0, 255, 0)) + sf.set_at((0, 0), (0, 255, 0)) + sf.set_at((4, 4), (0, 255, 0)) + val = sf.map_rgb((0, 255, 0)) + + ar = pygame.PixelArray(sf) + + # Test single value requests. + self.assertEqual(ar[1, 3], val) + self.assertEqual(ar[0, 0], val) + self.assertEqual(ar[4, 4], val) + self.assertEqual(ar[1][3], val) + self.assertEqual(ar[0][0], val) + self.assertEqual(ar[4][4], val) + + # Test ellipse working. + self.assertEqual(len(ar[..., ...]), 6) + self.assertEqual(len(ar[1, ...]), 8) + self.assertEqual(len(ar[..., 3]), 6) + + # Test simple slicing + self.assertEqual(len(ar[:, :]), 6) + self.assertEqual( + len( + ar[ + :, + ] + ), + 6, + ) + self.assertEqual(len(ar[1, :]), 8) + self.assertEqual(len(ar[:, 2]), 6) + # Empty slices + self.assertEqual( + ar[ + 4:4, + ], + None, + ) + self.assertEqual(ar[4:4, ...], None) + self.assertEqual(ar[4:4, 2:2], None) + self.assertEqual(ar[4:4, 1:4], None) + self.assertEqual( + ar[ + 4:4:2, + ], + None, + ) + self.assertEqual( + ar[ + 4:4:-2, + ], + None, + ) + self.assertEqual(ar[4:4:1, ...], None) + self.assertEqual(ar[4:4:-1, ...], None) + self.assertEqual(ar[4:4:1, 2:2], None) + self.assertEqual(ar[4:4:-1, 1:4], None) + self.assertEqual(ar[..., 4:4], None) + self.assertEqual(ar[1:4, 4:4], None) + self.assertEqual(ar[..., 4:4:1], None) + self.assertEqual(ar[..., 4:4:-1], None) + self.assertEqual(ar[2:2, 4:4:1], None) + self.assertEqual(ar[1:4, 4:4:-1], None) + + # Test advanced slicing + ar[0] = 0 + ar[1] = 1 + ar[2] = 2 + ar[3] = 3 + ar[4] = 4 + ar[5] = 5 + + # We should receive something like [0,2,4] + self.assertEqual(ar[::2, 1][0], 0) + self.assertEqual(ar[::2, 1][1], 2) + self.assertEqual(ar[::2, 1][2], 4) + # We should receive something like [2,2,2] + self.assertEqual(ar[2, ::2][0], 2) + self.assertEqual(ar[2, ::2][1], 2) + self.assertEqual(ar[2, ::2][2], 2) + + # Should create a 3x3 array of [0,2,4] + ar2 = ar[::2, ::2] + self.assertEqual(len(ar2), 3) + self.assertEqual(ar2[0][0], 0) + self.assertEqual(ar2[0][1], 0) + self.assertEqual(ar2[0][2], 0) + self.assertEqual(ar2[2][0], 4) + self.assertEqual(ar2[2][1], 4) + self.assertEqual(ar2[2][2], 4) + self.assertEqual(ar2[1][0], 2) + self.assertEqual(ar2[2][0], 4) + self.assertEqual(ar2[1][1], 2) + + # Should create a reversed 3x8 array over X of [1,2,3] -> [3,2,1] + ar2 = ar[3:0:-1] + self.assertEqual(len(ar2), 3) + self.assertEqual(ar2[0][0], 3) + self.assertEqual(ar2[0][1], 3) + self.assertEqual(ar2[0][2], 3) + self.assertEqual(ar2[0][7], 3) + self.assertEqual(ar2[2][0], 1) + self.assertEqual(ar2[2][1], 1) + self.assertEqual(ar2[2][2], 1) + self.assertEqual(ar2[2][7], 1) + self.assertEqual(ar2[1][0], 2) + self.assertEqual(ar2[1][1], 2) + # Should completely reverse the array over X -> [5,4,3,2,1,0] + ar2 = ar[::-1] + self.assertEqual(len(ar2), 6) + self.assertEqual(ar2[0][0], 5) + self.assertEqual(ar2[0][1], 5) + self.assertEqual(ar2[0][3], 5) + self.assertEqual(ar2[0][-1], 5) + self.assertEqual(ar2[1][0], 4) + self.assertEqual(ar2[1][1], 4) + self.assertEqual(ar2[1][3], 4) + self.assertEqual(ar2[1][-1], 4) + self.assertEqual(ar2[-1][-1], 0) + self.assertEqual(ar2[-2][-2], 1) + self.assertEqual(ar2[-3][-1], 2) + + # Test advanced slicing + ar[:] = 0 + ar2 = ar[:, 1] + ar2[:] = [99] * len(ar2) + self.assertEqual(ar2[0], 99) + self.assertEqual(ar2[-1], 99) + self.assertEqual(ar2[-2], 99) + self.assertEqual(ar2[2], 99) + self.assertEqual(ar[0, 1], 99) + self.assertEqual(ar[1, 1], 99) + self.assertEqual(ar[2, 1], 99) + self.assertEqual(ar[-1, 1], 99) + self.assertEqual(ar[-2, 1], 99) + + # Cases where a 2d array should have a dimension of length 1. + ar2 = ar[1:2, :] + self.assertEqual(ar2.shape, (1, ar.shape[1])) + ar2 = ar[:, 1:2] + self.assertEqual(ar2.shape, (ar.shape[0], 1)) + sf2 = pygame.Surface((1, 5), 0, 32) + ar2 = pygame.PixelArray(sf2) + self.assertEqual(ar2.shape, sf2.get_size()) + sf2 = pygame.Surface((7, 1), 0, 32) + ar2 = pygame.PixelArray(sf2) + self.assertEqual(ar2.shape, sf2.get_size()) + + # Array has a single ellipsis subscript: the identity operator + ar2 = ar[...] + self.assertTrue(ar2 is ar) + + # Ensure x and y are freed for p = array[x, y] + # Bug fix: reference counting + if hasattr(sys, "getrefcount"): + + class Int(int): + """Unique int instances""" + + pass + + sf = pygame.Surface((2, 2), 0, 32) + ar = pygame.PixelArray(sf) + x, y = Int(0), Int(1) + rx_before, ry_before = sys.getrefcount(x), sys.getrefcount(y) + p = ar[x, y] + rx_after, ry_after = sys.getrefcount(x), sys.getrefcount(y) + self.assertEqual(rx_after, rx_before) + self.assertEqual(ry_after, ry_before) + + def test_ass_subscript(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((6, 8), 0, bpp) + sf.fill((255, 255, 255)) + ar = pygame.PixelArray(sf) + + # Test ellipse working + ar[..., ...] = (0, 0, 0) + self.assertEqual(ar[0, 0], 0) + self.assertEqual(ar[1, 0], 0) + self.assertEqual(ar[-1, -1], 0) + ar[ + ..., + ] = (0, 0, 255) + self.assertEqual(ar[0, 0], sf.map_rgb((0, 0, 255))) + self.assertEqual(ar[1, 0], sf.map_rgb((0, 0, 255))) + self.assertEqual(ar[-1, -1], sf.map_rgb((0, 0, 255))) + ar[:, ...] = (255, 0, 0) + self.assertEqual(ar[0, 0], sf.map_rgb((255, 0, 0))) + self.assertEqual(ar[1, 0], sf.map_rgb((255, 0, 0))) + self.assertEqual(ar[-1, -1], sf.map_rgb((255, 0, 0))) + ar[...] = (0, 255, 0) + self.assertEqual(ar[0, 0], sf.map_rgb((0, 255, 0))) + self.assertEqual(ar[1, 0], sf.map_rgb((0, 255, 0))) + self.assertEqual(ar[-1, -1], sf.map_rgb((0, 255, 0))) + + # Ensure x and y are freed for array[x, y] = p + # Bug fix: reference counting + if hasattr(sys, "getrefcount"): + + class Int(int): + """Unique int instances""" + + pass + + sf = pygame.Surface((2, 2), 0, 32) + ar = pygame.PixelArray(sf) + x, y = Int(0), Int(1) + rx_before, ry_before = sys.getrefcount(x), sys.getrefcount(y) + ar[x, y] = 0 + rx_after, ry_after = sys.getrefcount(x), sys.getrefcount(y) + self.assertEqual(rx_after, rx_before) + self.assertEqual(ry_after, ry_before) + + def test_pixels_field(self): + for bpp in [1, 2, 3, 4]: + sf = pygame.Surface((11, 7), 0, bpp * 8) + ar = pygame.PixelArray(sf) + ar2 = ar[1:, :] + self.assertEqual(ar2._pixels_address - ar._pixels_address, ar.itemsize) + ar2 = ar[:, 1:] + self.assertEqual(ar2._pixels_address - ar._pixels_address, ar.strides[1]) + ar2 = ar[::-1, :] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, + (ar.shape[0] - 1) * ar.itemsize, + ) + ar2 = ar[::-2, :] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, + (ar.shape[0] - 1) * ar.itemsize, + ) + ar2 = ar[:, ::-1] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, + (ar.shape[1] - 1) * ar.strides[1], + ) + ar3 = ar2[::-1, :] + self.assertEqual( + ar3._pixels_address - ar._pixels_address, + (ar.shape[0] - 1) * ar.strides[0] + (ar.shape[1] - 1) * ar.strides[1], + ) + ar2 = ar[:, ::-2] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, + (ar.shape[1] - 1) * ar.strides[1], + ) + ar2 = ar[2::, 3::] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, + ar.strides[0] * 2 + ar.strides[1] * 3, + ) + ar2 = ar[2::2, 3::4] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, + ar.strides[0] * 2 + ar.strides[1] * 3, + ) + ar2 = ar[9:2:-1, :] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, ar.strides[0] * 9 + ) + ar2 = ar[:, 5:2:-1] + self.assertEqual( + ar2._pixels_address - ar._pixels_address, ar.strides[1] * 5 + ) + ##? ar2 = ar[:,9:2:-1] + + def test_make_surface(self): + bg_color = pygame.Color(255, 255, 255) + fg_color = pygame.Color(128, 100, 0) + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 20), 0, bpp) + bg_color_adj = sf.unmap_rgb(sf.map_rgb(bg_color)) + fg_color_adj = sf.unmap_rgb(sf.map_rgb(fg_color)) + sf.fill(bg_color_adj) + sf.fill(fg_color_adj, (2, 5, 4, 11)) + ar = pygame.PixelArray(sf) + newsf = ar[::2, ::2].make_surface() + rect = newsf.get_rect() + self.assertEqual(rect.width, 5) + self.assertEqual(rect.height, 10) + for p in [ + (0, 2), + (0, 3), + (1, 2), + (2, 2), + (3, 2), + (3, 3), + (0, 7), + (0, 8), + (1, 8), + (2, 8), + (3, 8), + (3, 7), + ]: + self.assertEqual(newsf.get_at(p), bg_color_adj) + for p in [(1, 3), (2, 3), (1, 5), (2, 5), (1, 7), (2, 7)]: + self.assertEqual(newsf.get_at(p), fg_color_adj) + + # Bug when array width is not a multiple of the slice step. + w = 17 + lst = list(range(w)) + w_slice = len(lst[::2]) + h = 3 + sf = pygame.Surface((w, h), 0, 32) + ar = pygame.PixelArray(sf) + ar2 = ar[::2, :] + sf2 = ar2.make_surface() + w2, h2 = sf2.get_size() + self.assertEqual(w2, w_slice) + self.assertEqual(h2, h) + + # Bug when array height is not a multiple of the slice step. + # This can hang the Python interpreter. + h = 17 + lst = list(range(h)) + h_slice = len(lst[::2]) + w = 3 + sf = pygame.Surface((w, h), 0, 32) + ar = pygame.PixelArray(sf) + ar2 = ar[:, ::2] + sf2 = ar2.make_surface() # Hangs here. + w2, h2 = sf2.get_size() + self.assertEqual(w2, w) + self.assertEqual(h2, h_slice) + + def test_make_surface__subclassed_surface(self): + """Ensure make_surface can handle subclassed surfaces.""" + expected_size = (3, 5) + expected_flags = 0 + expected_depth = 32 + original_surface = SurfaceSubclass( + expected_size, expected_flags, expected_depth + ) + pixelarray = pygame.PixelArray(original_surface) + + surface = pixelarray.make_surface() + + self.assertIsNot(surface, original_surface) + self.assertIsInstance(surface, pygame.Surface) + self.assertNotIsInstance(surface, SurfaceSubclass) + self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_flags(), expected_flags) + self.assertEqual(surface.get_bitsize(), expected_depth) + + def test_iter(self): + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((5, 10), 0, bpp) + ar = pygame.PixelArray(sf) + iterations = 0 + for col in ar: + self.assertEqual(len(col), 10) + iterations += 1 + self.assertEqual(iterations, 5) + + def test_replace(self): + # print "replace start" + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 10), 0, bpp) + sf.fill((255, 0, 0)) + rval = sf.map_rgb((0, 0, 255)) + oval = sf.map_rgb((255, 0, 0)) + ar = pygame.PixelArray(sf) + ar[::2].replace((255, 0, 0), (0, 0, 255)) + self.assertEqual(ar[0][0], rval) + self.assertEqual(ar[1][0], oval) + self.assertEqual(ar[2][3], rval) + self.assertEqual(ar[3][6], oval) + self.assertEqual(ar[8][9], rval) + self.assertEqual(ar[9][9], oval) + + ar[::2].replace((0, 0, 255), (255, 0, 0), weights=(10, 20, 50)) + self.assertEqual(ar[0][0], oval) + self.assertEqual(ar[2][3], oval) + self.assertEqual(ar[3][6], oval) + self.assertEqual(ar[8][9], oval) + self.assertEqual(ar[9][9], oval) + # print "replace end" + + def test_extract(self): + # print "extract start" + for bpp in (8, 16, 24, 32): + sf = pygame.Surface((10, 10), 0, bpp) + sf.fill((0, 0, 255)) + sf.fill((255, 0, 0), (2, 2, 6, 6)) + + white = sf.map_rgb((255, 255, 255)) + black = sf.map_rgb((0, 0, 0)) + + ar = pygame.PixelArray(sf) + newar = ar.extract((255, 0, 0)) + + self.assertEqual(newar[0][0], black) + self.assertEqual(newar[1][0], black) + self.assertEqual(newar[2][3], white) + self.assertEqual(newar[3][6], white) + self.assertEqual(newar[8][9], black) + self.assertEqual(newar[9][9], black) + + newar = ar.extract((255, 0, 0), weights=(10, 0.1, 50)) + self.assertEqual(newar[0][0], black) + self.assertEqual(newar[1][0], black) + self.assertEqual(newar[2][3], white) + self.assertEqual(newar[3][6], white) + self.assertEqual(newar[8][9], black) + self.assertEqual(newar[9][9], black) + # print "extract end" + + def test_2dslice_assignment(self): + w = 2 * 5 * 8 + h = 3 * 5 * 9 + sf = pygame.Surface((w, h), 0, 32) + ar = pygame.PixelArray(sf) + size = (w, h) + strides = (1, w) + offset = 0 + self._test_assignment(sf, ar, size, strides, offset) + xslice = slice(None, None, 2) + yslice = slice(None, None, 3) + ar, size, strides, offset = self._array_slice( + ar, size, (xslice, yslice), strides, offset + ) + self._test_assignment(sf, ar, size, strides, offset) + xslice = slice(5, None, 5) + yslice = slice(5, None, 5) + ar, size, strides, offset = self._array_slice( + ar, size, (xslice, yslice), strides, offset + ) + self._test_assignment(sf, ar, size, strides, offset) + + def _test_assignment(self, sf, ar, ar_size, ar_strides, ar_offset): + self.assertEqual(ar.shape, ar_size) + ar_w, ar_h = ar_size + ar_xstride, ar_ystride = ar_strides + sf_w, sf_h = sf.get_size() + black = pygame.Color("black") + color = pygame.Color(0, 0, 12) + pxcolor = sf.map_rgb(color) + sf.fill(black) + for ar_x, ar_y in [ + (0, 0), + (0, ar_h - 4), + (ar_w - 3, 0), + (0, ar_h - 1), + (ar_w - 1, 0), + (ar_w - 1, ar_h - 1), + ]: + sf_offset = ar_offset + ar_x * ar_xstride + ar_y * ar_ystride + sf_y = sf_offset // sf_w + sf_x = sf_offset - sf_y * sf_w + sf_posn = (sf_x, sf_y) + sf_pix = sf.get_at(sf_posn) + self.assertEqual( + sf_pix, + black, + "at pixarr posn (%i, %i) (surf posn (%i, %i)): " + "%s != %s" % (ar_x, ar_y, sf_x, sf_y, sf_pix, black), + ) + ar[ar_x, ar_y] = pxcolor + sf_pix = sf.get_at(sf_posn) + self.assertEqual( + sf_pix, + color, + "at pixarr posn (%i, %i) (surf posn (%i, %i)): " + "%s != %s" % (ar_x, ar_y, sf_x, sf_y, sf_pix, color), + ) + + def _array_slice(self, ar, size, slices, strides, offset): + ar = ar[slices] + xslice, yslice = slices + w, h = size + xstart, xstop, xstep = xslice.indices(w) + ystart, ystop, ystep = yslice.indices(h) + w = (xstop - xstart + xstep - 1) // xstep + h = (ystop - ystart + ystep - 1) // ystep + xstride, ystride = strides + offset += xstart * xstride + ystart * ystride + xstride *= xstep + ystride *= ystep + return ar, (w, h), (xstride, ystride), offset + + def test_array_properties(self): + # itemsize, ndim, shape, and strides. + for bpp in [1, 2, 3, 4]: + sf = pygame.Surface((2, 2), 0, bpp * 8) + ar = pygame.PixelArray(sf) + self.assertEqual(ar.itemsize, bpp) + + for shape in [(4, 16), (5, 13)]: + w, h = shape + sf = pygame.Surface(shape, 0, 32) + bpp = sf.get_bytesize() + pitch = sf.get_pitch() + ar = pygame.PixelArray(sf) + self.assertEqual(ar.ndim, 2) + self.assertEqual(ar.shape, shape) + self.assertEqual(ar.strides, (bpp, pitch)) + ar2 = ar[::2, :] + w2 = len(([0] * w)[::2]) + self.assertEqual(ar2.ndim, 2) + self.assertEqual(ar2.shape, (w2, h)) + self.assertEqual(ar2.strides, (2 * bpp, pitch)) + ar2 = ar[:, ::2] + h2 = len(([0] * h)[::2]) + self.assertEqual(ar2.ndim, 2) + self.assertEqual(ar2.shape, (w, h2)) + self.assertEqual(ar2.strides, (bpp, 2 * pitch)) + ar2 = ar[1] + self.assertEqual(ar2.ndim, 1) + self.assertEqual(ar2.shape, (h,)) + self.assertEqual(ar2.strides, (pitch,)) + ar2 = ar[:, 1] + self.assertEqual(ar2.ndim, 1) + self.assertEqual(ar2.shape, (w,)) + self.assertEqual(ar2.strides, (bpp,)) + + def test_self_assign(self): + # This differs from NumPy arrays. + w = 10 + max_x = w - 1 + h = 20 + max_y = h - 1 + for bpp in [1, 2, 3, 4]: + sf = pygame.Surface((w, h), 0, bpp * 8) + ar = pygame.PixelArray(sf) + for i in range(w * h): + ar[i % w, i // w] = i + ar[:, :] = ar[::-1, :] + for i in range(w * h): + self.assertEqual(ar[max_x - i % w, i // w], i) + ar = pygame.PixelArray(sf) + for i in range(w * h): + ar[i % w, i // w] = i + ar[:, :] = ar[:, ::-1] + for i in range(w * h): + self.assertEqual(ar[i % w, max_y - i // w], i) + ar = pygame.PixelArray(sf) + for i in range(w * h): + ar[i % w, i // w] = i + ar[:, :] = ar[::-1, ::-1] + for i in range(w * h): + self.assertEqual(ar[max_x - i % w, max_y - i // w], i) + + def test_color_value(self): + # Confirm that a PixelArray slice assignment distinguishes between + # pygame.Color and tuple objects as single (r, g, b[, a]) colors + # and other sequences as sequences of colors to be treated as + # slices. + sf = pygame.Surface((5, 5), 0, 32) + ar = pygame.PixelArray(sf) + index = slice(None, None, 1) + ar.__setitem__(index, (1, 2, 3)) + self.assertEqual(ar[0, 0], sf.map_rgb((1, 2, 3))) + ar.__setitem__(index, pygame.Color(10, 11, 12)) + self.assertEqual(ar[0, 0], sf.map_rgb((10, 11, 12))) + self.assertRaises(ValueError, ar.__setitem__, index, (1, 2, 3, 4, 5)) + self.assertRaises(ValueError, ar.__setitem__, (index, index), (1, 2, 3, 4, 5)) + self.assertRaises(ValueError, ar.__setitem__, index, [1, 2, 3]) + self.assertRaises(ValueError, ar.__setitem__, (index, index), [1, 2, 3]) + sf = pygame.Surface((3, 3), 0, 32) + ar = pygame.PixelArray(sf) + ar[:] = (20, 30, 40) + self.assertEqual(ar[0, 0], sf.map_rgb((20, 30, 40))) + ar[:] = [20, 30, 40] + self.assertEqual(ar[0, 0], 20) + self.assertEqual(ar[1, 0], 30) + self.assertEqual(ar[2, 0], 40) + + def test_transpose(self): + # PixelArray.transpose(): swap axis on a 2D array, add a length + # 1 x axis to a 1D array. + sf = pygame.Surface((3, 7), 0, 32) + ar = pygame.PixelArray(sf) + w, h = ar.shape + dx, dy = ar.strides + for i in range(w * h): + x = i % w + y = i // w + ar[x, y] = i + ar_t = ar.transpose() + self.assertEqual(ar_t.shape, (h, w)) + self.assertEqual(ar_t.strides, (dy, dx)) + for i in range(w * h): + x = i % w + y = i // w + self.assertEqual(ar_t[y, x], ar[x, y]) + ar1D = ar[0] + ar2D = ar1D.transpose() + self.assertEqual(ar2D.shape, (1, h)) + for y in range(h): + self.assertEqual(ar1D[y], ar2D[0, y]) + ar1D = ar[:, 0] + ar2D = ar1D.transpose() + self.assertEqual(ar2D.shape, (1, w)) + for x in range(2): + self.assertEqual(ar1D[x], ar2D[0, x]) + + def test_length_1_dimension_broadcast(self): + w = 5 + sf = pygame.Surface((w, w), 0, 32) + ar = pygame.PixelArray(sf) + # y-axis broadcast. + sf_x = pygame.Surface((w, 1), 0, 32) + ar_x = pygame.PixelArray(sf_x) + for i in range(w): + ar_x[i, 0] = (w + 1) * 10 + ar[...] = ar_x + for y in range(w): + for x in range(w): + self.assertEqual(ar[x, y], ar_x[x, 0]) + # x-axis broadcast. + ar[...] = 0 + sf_y = pygame.Surface((1, w), 0, 32) + ar_y = pygame.PixelArray(sf_y) + for i in range(w): + ar_y[0, i] = (w + 1) * 10 + ar[...] = ar_y + for x in range(w): + for y in range(w): + self.assertEqual(ar[x, y], ar_y[0, y]) + # (1, 1) array broadcast. + ar[...] = 0 + sf_1px = pygame.Surface((1, 1), 0, 32) + ar_1px = pygame.PixelArray(sf_1px) + ar_1px[0, 0] = 42 # Well it had to show up somewhere. + ar[...] = ar_1px + for y in range(w): + for x in range(w): + self.assertEqual(ar[x, y], 42) + + def test_assign_size_mismatch(self): + sf = pygame.Surface((7, 11), 0, 32) + ar = pygame.PixelArray(sf) + self.assertRaises(ValueError, ar.__setitem__, Ellipsis, ar[:, 0:2]) + self.assertRaises(ValueError, ar.__setitem__, Ellipsis, ar[0:2, :]) + + def test_repr(self): + # Python 3.x bug: the tp_repr slot function returned NULL instead + # of a Unicode string, triggering an exception. + sf = pygame.Surface((3, 1), pygame.SRCALPHA, 16) + ar = pygame.PixelArray(sf) + ar[...] = 42 + pixel = sf.get_at_mapped((0, 0)) + self.assertEqual(repr(ar), type(ar).__name__ + "([\n [42, 42, 42]]\n)") + + +@unittest.skipIf(IS_PYPY, "pypy having issues") +class PixelArrayArrayInterfaceTest(unittest.TestCase, TestMixin): + @unittest.skipIf(IS_PYPY, "skipping for PyPy (why?)") + def test_basic(self): + # Check unchanging fields. + sf = pygame.Surface((2, 2), 0, 32) + ar = pygame.PixelArray(sf) + + ai = arrinter.ArrayInterface(ar) + self.assertEqual(ai.two, 2) + self.assertEqual(ai.typekind, "u") + self.assertEqual(ai.nd, 2) + self.assertEqual(ai.data, ar._pixels_address) + + @unittest.skipIf(IS_PYPY, "skipping for PyPy (why?)") + def test_shape(self): + + for shape in [[4, 16], [5, 13]]: + w, h = shape + sf = pygame.Surface(shape, 0, 32) + ar = pygame.PixelArray(sf) + ai = arrinter.ArrayInterface(ar) + ai_shape = [ai.shape[i] for i in range(ai.nd)] + self.assertEqual(ai_shape, shape) + ar2 = ar[::2, :] + ai2 = arrinter.ArrayInterface(ar2) + w2 = len(([0] * w)[::2]) + ai_shape = [ai2.shape[i] for i in range(ai2.nd)] + self.assertEqual(ai_shape, [w2, h]) + ar2 = ar[:, ::2] + ai2 = arrinter.ArrayInterface(ar2) + h2 = len(([0] * h)[::2]) + ai_shape = [ai2.shape[i] for i in range(ai2.nd)] + self.assertEqual(ai_shape, [w, h2]) + + @unittest.skipIf(IS_PYPY, "skipping for PyPy (why?)") + def test_itemsize(self): + for bytes_per_pixel in range(1, 5): + bits_per_pixel = 8 * bytes_per_pixel + sf = pygame.Surface((2, 2), 0, bits_per_pixel) + ar = pygame.PixelArray(sf) + ai = arrinter.ArrayInterface(ar) + self.assertEqual(ai.itemsize, bytes_per_pixel) + + @unittest.skipIf(IS_PYPY, "skipping for PyPy (why?)") + def test_flags(self): + aim = arrinter + common_flags = aim.PAI_NOTSWAPPED | aim.PAI_WRITEABLE | aim.PAI_ALIGNED + s = pygame.Surface((10, 2), 0, 32) + ar = pygame.PixelArray(s) + ai = aim.ArrayInterface(ar) + self.assertEqual(ai.flags, common_flags | aim.PAI_FORTRAN) + + ar2 = ar[::2, :] + ai = aim.ArrayInterface(ar2) + self.assertEqual(ai.flags, common_flags) + + s = pygame.Surface((8, 2), 0, 24) + ar = pygame.PixelArray(s) + ai = aim.ArrayInterface(ar) + self.assertEqual(ai.flags, common_flags | aim.PAI_FORTRAN) + + s = pygame.Surface((7, 2), 0, 24) + ar = pygame.PixelArray(s) + ai = aim.ArrayInterface(ar) + self.assertEqual(ai.flags, common_flags) + + def test_slicing(self): + # This will implicitly test data and strides fields. + # + # Need an 8 bit test surfaces because pixelcopy.make_surface + # returns an 8 bit surface for a 2d array. + + factors = [7, 3, 11] + + w = reduce(operator.mul, factors, 1) + h = 13 + sf = pygame.Surface((w, h), 0, 8) + color = sf.map_rgb((1, 17, 128)) + ar = pygame.PixelArray(sf) + for f in factors[:-1]: + w = w // f + sf.fill((0, 0, 0)) + ar = ar[f : f + w, :] + ar[0][0] = color + ar[-1][-2] = color + ar[0][-3] = color + sf2 = ar.make_surface() + sf3 = pygame.pixelcopy.make_surface(ar) + self.assert_surfaces_equal(sf3, sf2) + + h = reduce(operator.mul, factors, 1) + w = 13 + sf = pygame.Surface((w, h), 0, 8) + color = sf.map_rgb((1, 17, 128)) + ar = pygame.PixelArray(sf) + for f in factors[:-1]: + h = h // f + sf.fill((0, 0, 0)) + ar = ar[:, f : f + h] + ar[0][0] = color + ar[-1][-2] = color + ar[0][-3] = color + sf2 = ar.make_surface() + sf3 = pygame.pixelcopy.make_surface(ar) + self.assert_surfaces_equal(sf3, sf2) + + w = 20 + h = 10 + sf = pygame.Surface((w, h), 0, 8) + color = sf.map_rgb((1, 17, 128)) + ar = pygame.PixelArray(sf) + for slices in [ + (slice(w), slice(h)), + (slice(0, w, 2), slice(h)), + (slice(0, w, 3), slice(h)), + (slice(w), slice(0, h, 2)), + (slice(w), slice(0, h, 3)), + (slice(0, w, 2), slice(0, h, 2)), + (slice(0, w, 3), slice(0, h, 3)), + ]: + sf.fill((0, 0, 0)) + ar2 = ar[slices] + ar2[0][0] = color + ar2[-1][-2] = color + ar2[0][-3] = color + sf2 = ar2.make_surface() + sf3 = pygame.pixelcopy.make_surface(ar2) + self.assert_surfaces_equal(sf3, sf2) + + +@unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") +@unittest.skipIf(IS_PYPY, "pypy having issues") +class PixelArrayNewBufferTest(unittest.TestCase, TestMixin): + + if pygame.HAVE_NEWBUF: + from pygame.tests.test_utils import buftools + + bitsize_to_format = {8: "B", 16: "=H", 24: "3x", 32: "=I"} + + def test_newbuf_2D(self): + buftools = self.buftools + Importer = buftools.Importer + + for bit_size in [8, 16, 24, 32]: + s = pygame.Surface((10, 2), 0, bit_size) + ar = pygame.PixelArray(s) + format = self.bitsize_to_format[bit_size] + itemsize = ar.itemsize + shape = ar.shape + w, h = shape + strides = ar.strides + length = w * h * itemsize + imp = Importer(ar, buftools.PyBUF_FULL) + self.assertTrue(imp.obj, ar) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 2) + self.assertEqual(imp.itemsize, itemsize) + self.assertEqual(imp.format, format) + self.assertFalse(imp.readonly) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertTrue(imp.suboffsets is None) + self.assertEqual(imp.buf, s._pixels_address) + + s = pygame.Surface((8, 16), 0, 32) + ar = pygame.PixelArray(s) + format = self.bitsize_to_format[s.get_bitsize()] + itemsize = ar.itemsize + shape = ar.shape + w, h = shape + strides = ar.strides + length = w * h * itemsize + imp = Importer(ar, buftools.PyBUF_SIMPLE) + self.assertTrue(imp.obj, ar) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 0) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.format is None) + self.assertFalse(imp.readonly) + self.assertTrue(imp.shape is None) + self.assertTrue(imp.strides is None) + self.assertTrue(imp.suboffsets is None) + self.assertEqual(imp.buf, s._pixels_address) + imp = Importer(ar, buftools.PyBUF_FORMAT) + self.assertEqual(imp.ndim, 0) + self.assertEqual(imp.format, format) + imp = Importer(ar, buftools.PyBUF_WRITABLE) + self.assertEqual(imp.ndim, 0) + self.assertTrue(imp.format is None) + imp = Importer(ar, buftools.PyBUF_F_CONTIGUOUS) + self.assertEqual(imp.ndim, 2) + self.assertTrue(imp.format is None) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + imp = Importer(ar, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertEqual(imp.ndim, 2) + self.assertTrue(imp.format is None) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_ND) + + ar_sliced = ar[:, ::2] + format = self.bitsize_to_format[s.get_bitsize()] + itemsize = ar_sliced.itemsize + shape = ar_sliced.shape + w, h = shape + strides = ar_sliced.strides + length = w * h * itemsize + imp = Importer(ar_sliced, buftools.PyBUF_STRIDED) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 2) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.format is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertEqual(imp.buf, s._pixels_address) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises( + BufferError, Importer, ar_sliced, buftools.PyBUF_ANY_CONTIGUOUS + ) + + ar_sliced = ar[::2, :] + format = self.bitsize_to_format[s.get_bitsize()] + itemsize = ar_sliced.itemsize + shape = ar_sliced.shape + w, h = shape + strides = ar_sliced.strides + length = w * h * itemsize + imp = Importer(ar_sliced, buftools.PyBUF_STRIDED) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 2) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.format is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertEqual(imp.buf, s._pixels_address) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, ar_sliced, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises( + BufferError, Importer, ar_sliced, buftools.PyBUF_ANY_CONTIGUOUS + ) + + s2 = s.subsurface((2, 3, 5, 7)) + ar = pygame.PixelArray(s2) + format = self.bitsize_to_format[s.get_bitsize()] + itemsize = ar.itemsize + shape = ar.shape + w, h = shape + strides = ar.strides + length = w * h * itemsize + imp = Importer(ar, buftools.PyBUF_STRIDES) + self.assertTrue(imp.obj, ar) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 2) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.format is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertTrue(imp.suboffsets is None) + self.assertEqual(imp.buf, s2._pixels_address) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_FORMAT) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_ANY_CONTIGUOUS) + + def test_newbuf_1D(self): + buftools = self.buftools + Importer = buftools.Importer + + s = pygame.Surface((2, 16), 0, 32) + ar_2D = pygame.PixelArray(s) + x = 0 + ar = ar_2D[x] + format = self.bitsize_to_format[s.get_bitsize()] + itemsize = ar.itemsize + shape = ar.shape + h = shape[0] + strides = ar.strides + length = h * itemsize + buf = s._pixels_address + x * itemsize + imp = Importer(ar, buftools.PyBUF_STRIDES) + self.assertTrue(imp.obj, ar) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 1) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.format is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertTrue(imp.suboffsets is None) + self.assertEqual(imp.buf, buf) + imp = Importer(ar, buftools.PyBUF_FULL) + self.assertEqual(imp.ndim, 1) + self.assertEqual(imp.format, format) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_FORMAT) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, ar, buftools.PyBUF_ANY_CONTIGUOUS) + y = 10 + ar = ar_2D[:, y] + shape = ar.shape + w = shape[0] + strides = ar.strides + length = w * itemsize + buf = s._pixels_address + y * s.get_pitch() + imp = Importer(ar, buftools.PyBUF_FULL) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 1) + self.assertEqual(imp.itemsize, itemsize) + self.assertEqual(imp.format, format) + self.assertFalse(imp.readonly) + self.assertEqual(imp.shape, shape) + self.assertEqual(imp.strides, strides) + self.assertEqual(imp.buf, buf) + self.assertTrue(imp.suboffsets is None) + imp = Importer(ar, buftools.PyBUF_SIMPLE) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 0) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.format is None) + self.assertFalse(imp.readonly) + self.assertTrue(imp.shape is None) + self.assertTrue(imp.strides is None) + imp = Importer(ar, buftools.PyBUF_ND) + self.assertEqual(imp.len, length) + self.assertEqual(imp.ndim, 1) + self.assertEqual(imp.itemsize, itemsize) + self.assertTrue(imp.format is None) + self.assertFalse(imp.readonly) + self.assertEqual(imp.shape, shape) + self.assertTrue(imp.strides is None) + imp = Importer(ar, buftools.PyBUF_C_CONTIGUOUS) + self.assertEqual(imp.ndim, 1) + imp = Importer(ar, buftools.PyBUF_F_CONTIGUOUS) + self.assertEqual(imp.ndim, 1) + imp = Importer(ar, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertEqual(imp.ndim, 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/pixelcopy_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/pixelcopy_test.py new file mode 100644 index 0000000..f89c665 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/pixelcopy_test.py @@ -0,0 +1,712 @@ +import platform +import unittest + +try: + from pygame.tests.test_utils import arrinter +except NameError: + pass +import pygame +from pygame.locals import * +from pygame.pixelcopy import surface_to_array, map_array, array_to_surface, make_surface + +IS_PYPY = "PyPy" == platform.python_implementation() + + +def unsigned32(i): + """cast signed 32 bit integer to an unsigned integer""" + return i & 0xFFFFFFFF + + +@unittest.skipIf(IS_PYPY, "pypy having illegal instruction on mac") +class PixelcopyModuleTest(unittest.TestCase): + + bitsizes = [8, 16, 32] + + test_palette = [ + (0, 0, 0, 255), + (10, 30, 60, 255), + (25, 75, 100, 255), + (100, 150, 200, 255), + (0, 100, 200, 255), + ] + + surf_size = (10, 12) + test_points = [ + ((0, 0), 1), + ((4, 5), 1), + ((9, 0), 2), + ((5, 5), 2), + ((0, 11), 3), + ((4, 6), 3), + ((9, 11), 4), + ((5, 6), 4), + ] + + def __init__(self, *args, **kwds): + pygame.display.init() + try: + unittest.TestCase.__init__(self, *args, **kwds) + self.sources = [ + self._make_src_surface(8), + self._make_src_surface(16), + self._make_src_surface(16, srcalpha=True), + self._make_src_surface(24), + self._make_src_surface(32), + self._make_src_surface(32, srcalpha=True), + ] + finally: + pygame.display.quit() + + def _make_surface(self, bitsize, srcalpha=False, palette=None): + if palette is None: + palette = self.test_palette + flags = 0 + if srcalpha: + flags |= SRCALPHA + surf = pygame.Surface(self.surf_size, flags, bitsize) + if bitsize == 8: + surf.set_palette([c[:3] for c in palette]) + return surf + + def _fill_surface(self, surf, palette=None): + if palette is None: + palette = self.test_palette + surf.fill(palette[1], (0, 0, 5, 6)) + surf.fill(palette[2], (5, 0, 5, 6)) + surf.fill(palette[3], (0, 6, 5, 6)) + surf.fill(palette[4], (5, 6, 5, 6)) + + def _make_src_surface(self, bitsize, srcalpha=False, palette=None): + surf = self._make_surface(bitsize, srcalpha, palette) + self._fill_surface(surf, palette) + return surf + + def setUp(self): + pygame.display.init() + + def tearDown(self): + pygame.display.quit() + + def test_surface_to_array_2d(self): + alpha_color = (0, 0, 0, 128) + + for surf in self.sources: + src_bitsize = surf.get_bitsize() + for dst_bitsize in self.bitsizes: + # dst in a surface standing in for a 2 dimensional array + # of unsigned integers. The byte order is system dependent. + dst = pygame.Surface(surf.get_size(), 0, dst_bitsize) + dst.fill((0, 0, 0, 0)) + view = dst.get_view("2") + self.assertFalse(surf.get_locked()) + if dst_bitsize < src_bitsize: + self.assertRaises(ValueError, surface_to_array, view, surf) + self.assertFalse(surf.get_locked()) + continue + surface_to_array(view, surf) + self.assertFalse(surf.get_locked()) + for posn, i in self.test_points: + sp = surf.get_at_mapped(posn) + dp = dst.get_at_mapped(posn) + self.assertEqual( + dp, + sp, + "%s != %s: flags: %i" + ", bpp: %i, posn: %s" + % (dp, sp, surf.get_flags(), surf.get_bitsize(), posn), + ) + del view + + if surf.get_masks()[3]: + dst.fill((0, 0, 0, 0)) + view = dst.get_view("2") + posn = (2, 1) + surf.set_at(posn, alpha_color) + self.assertFalse(surf.get_locked()) + surface_to_array(view, surf) + self.assertFalse(surf.get_locked()) + sp = surf.get_at_mapped(posn) + dp = dst.get_at_mapped(posn) + self.assertEqual( + dp, sp, "%s != %s: bpp: %i" % (dp, sp, surf.get_bitsize()) + ) + + if IS_PYPY: + return + # Swapped endian destination array + pai_flags = arrinter.PAI_ALIGNED | arrinter.PAI_WRITEABLE + for surf in self.sources: + for itemsize in [1, 2, 4, 8]: + if itemsize < surf.get_bytesize(): + continue + a = arrinter.Array(surf.get_size(), "u", itemsize, flags=pai_flags) + surface_to_array(a, surf) + for posn, i in self.test_points: + sp = unsigned32(surf.get_at_mapped(posn)) + dp = a[posn] + self.assertEqual( + dp, + sp, + "%s != %s: itemsize: %i, flags: %i" + ", bpp: %i, posn: %s" + % ( + dp, + sp, + itemsize, + surf.get_flags(), + surf.get_bitsize(), + posn, + ), + ) + + def test_surface_to_array_3d(self): + self.iter_surface_to_array_3d((0xFF, 0xFF00, 0xFF0000, 0)) + self.iter_surface_to_array_3d((0xFF0000, 0xFF00, 0xFF, 0)) + + def iter_surface_to_array_3d(self, rgba_masks): + dst = pygame.Surface(self.surf_size, 0, 24, masks=rgba_masks) + + for surf in self.sources: + dst.fill((0, 0, 0, 0)) + src_bitsize = surf.get_bitsize() + view = dst.get_view("3") + self.assertFalse(surf.get_locked()) + surface_to_array(view, surf) + self.assertFalse(surf.get_locked()) + for posn, i in self.test_points: + sc = surf.get_at(posn)[0:3] + dc = dst.get_at(posn)[0:3] + self.assertEqual( + dc, + sc, + "%s != %s: flags: %i" + ", bpp: %i, posn: %s" + % (dc, sc, surf.get_flags(), surf.get_bitsize(), posn), + ) + view = None + + def test_map_array(self): + targets = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + source = pygame.Surface( + self.surf_size, 0, 24, masks=[0xFF, 0xFF00, 0xFF0000, 0] + ) + self._fill_surface(source) + source_view = source.get_view("3") # (w, h, 3) + for t in targets: + map_array(t.get_view("2"), source_view, t) + for posn, i in self.test_points: + sc = t.map_rgb(source.get_at(posn)) + dc = t.get_at_mapped(posn) + self.assertEqual( + dc, + sc, + "%s != %s: flags: %i" + ", bpp: %i, posn: %s" + % (dc, sc, t.get_flags(), t.get_bitsize(), posn), + ) + + color = pygame.Color("salmon") + color.set_length(3) + for t in targets: + map_array(t.get_view("2"), color, t) + sc = t.map_rgb(color) + for posn, i in self.test_points: + dc = t.get_at_mapped(posn) + self.assertEqual( + dc, + sc, + "%s != %s: flags: %i" + ", bpp: %i, posn: %s" + % (dc, sc, t.get_flags(), t.get_bitsize(), posn), + ) + + # mismatched shapes + w, h = source.get_size() + target = pygame.Surface((w, h + 1), 0, 32) + self.assertRaises(ValueError, map_array, target, source, target) + target = pygame.Surface((w - 1, h), 0, 32) + self.assertRaises(ValueError, map_array, target, source, target) + + def test_array_to_surface_broadcasting(self): + # target surfaces + targets = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + + w, h = self.surf_size + + # broadcast column + column = pygame.Surface((1, h), 0, 32) + for target in targets: + source = pygame.Surface((1, h), 0, target) + for y in range(h): + source.set_at((0, y), pygame.Color(y + 1, y + h + 1, y + 2 * h + 1)) + pygame.pixelcopy.surface_to_array(column.get_view("2"), source) + pygame.pixelcopy.array_to_surface(target, column.get_view("2")) + for x in range(w): + for y in range(h): + self.assertEqual( + target.get_at_mapped((x, y)), column.get_at_mapped((0, y)) + ) + + # broadcast row + row = pygame.Surface((w, 1), 0, 32) + for target in targets: + source = pygame.Surface((w, 1), 0, target) + for x in range(w): + source.set_at((x, 0), pygame.Color(x + 1, x + w + 1, x + 2 * w + 1)) + pygame.pixelcopy.surface_to_array(row.get_view("2"), source) + pygame.pixelcopy.array_to_surface(target, row.get_view("2")) + for x in range(w): + for y in range(h): + self.assertEqual( + target.get_at_mapped((x, y)), row.get_at_mapped((x, 0)) + ) + + # broadcast pixel + pixel = pygame.Surface((1, 1), 0, 32) + for target in targets: + source = pygame.Surface((1, 1), 0, target) + source.set_at((0, 0), pygame.Color(13, 47, 101)) + pygame.pixelcopy.surface_to_array(pixel.get_view("2"), source) + pygame.pixelcopy.array_to_surface(target, pixel.get_view("2")) + p = pixel.get_at_mapped((0, 0)) + for x in range(w): + for y in range(h): + self.assertEqual(target.get_at_mapped((x, y)), p) + + +@unittest.skipIf(IS_PYPY, "pypy having illegal instruction on mac") +class PixelCopyTestWithArray(unittest.TestCase): + try: + import numpy + except ImportError: + __tags__ = ["ignore", "subprocess_ignore"] + else: + pygame.surfarray.use_arraytype("numpy") + + bitsizes = [8, 16, 32] + + test_palette = [ + (0, 0, 0, 255), + (10, 30, 60, 255), + (25, 75, 100, 255), + (100, 150, 200, 255), + (0, 100, 200, 255), + ] + + surf_size = (10, 12) + test_points = [ + ((0, 0), 1), + ((4, 5), 1), + ((9, 0), 2), + ((5, 5), 2), + ((0, 11), 3), + ((4, 6), 3), + ((9, 11), 4), + ((5, 6), 4), + ] + + pixels2d = set([8, 16, 32]) + pixels3d = set([24, 32]) + array2d = set([8, 16, 24, 32]) + array3d = set([24, 32]) + + def __init__(self, *args, **kwds): + import numpy + + self.dst_types = [numpy.uint8, numpy.uint16, numpy.uint32] + try: + self.dst_types.append(numpy.uint64) + except AttributeError: + pass + pygame.display.init() + try: + unittest.TestCase.__init__(self, *args, **kwds) + self.sources = [ + self._make_src_surface(8), + self._make_src_surface(16), + self._make_src_surface(16, srcalpha=True), + self._make_src_surface(24), + self._make_src_surface(32), + self._make_src_surface(32, srcalpha=True), + ] + finally: + pygame.display.quit() + + def _make_surface(self, bitsize, srcalpha=False, palette=None): + if palette is None: + palette = self.test_palette + flags = 0 + if srcalpha: + flags |= SRCALPHA + surf = pygame.Surface(self.surf_size, flags, bitsize) + if bitsize == 8: + surf.set_palette([c[:3] for c in palette]) + return surf + + def _fill_surface(self, surf, palette=None): + if palette is None: + palette = self.test_palette + surf.fill(palette[1], (0, 0, 5, 6)) + surf.fill(palette[2], (5, 0, 5, 6)) + surf.fill(palette[3], (0, 6, 5, 6)) + surf.fill(palette[4], (5, 6, 5, 6)) + + def _make_src_surface(self, bitsize, srcalpha=False, palette=None): + surf = self._make_surface(bitsize, srcalpha, palette) + self._fill_surface(surf, palette) + return surf + + def setUp(self): + pygame.display.init() + + def tearDown(self): + pygame.display.quit() + + def test_surface_to_array_2d(self): + try: + from numpy import empty, dtype + except ImportError: + return + + palette = self.test_palette + alpha_color = (0, 0, 0, 128) + + dst_dims = self.surf_size + destinations = [empty(dst_dims, t) for t in self.dst_types] + if pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN: + swapped_dst = empty(dst_dims, dtype(">u4")) + else: + swapped_dst = empty(dst_dims, dtype("u4")) + else: + swapped_dst = empty(dst_dims, dtype("i", + "!i", + "1i", + "=1i", + "@q", + "q", + "4x", + "8x", + ]: + surface.fill((255, 254, 253)) + exp = Exporter(shape, format=format) + exp._buf[:] = [42] * exp.buflen + array_to_surface(surface, exp) + for x in range(w): + for y in range(h): + self.assertEqual(surface.get_at((x, y)), (42, 42, 42, 255)) + # Some unsupported formats for array_to_surface and a 32 bit surface + for format in ["f", "d", "?", "x", "1x", "2x", "3x", "5x", "6x", "7x", "9x"]: + exp = Exporter(shape, format=format) + self.assertRaises(ValueError, array_to_surface, surface, exp) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/rect_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/rect_test.py new file mode 100644 index 0000000..0d635c8 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/rect_test.py @@ -0,0 +1,2152 @@ +import math +import sys +import unittest +import platform + +from pygame import Rect, Vector2 +from pygame.tests import test_utils + + +IS_PYPY = "PyPy" == platform.python_implementation() + + +class RectTypeTest(unittest.TestCase): + def _assertCountEqual(self, *args, **kwargs): + self.assertCountEqual(*args, **kwargs) + + def testConstructionXYWidthHeight(self): + r = Rect(1, 2, 3, 4) + self.assertEqual(1, r.left) + self.assertEqual(2, r.top) + self.assertEqual(3, r.width) + self.assertEqual(4, r.height) + + def testConstructionTopLeftSize(self): + r = Rect((1, 2), (3, 4)) + self.assertEqual(1, r.left) + self.assertEqual(2, r.top) + self.assertEqual(3, r.width) + self.assertEqual(4, r.height) + + def testCalculatedAttributes(self): + r = Rect(1, 2, 3, 4) + + self.assertEqual(r.left + r.width, r.right) + self.assertEqual(r.top + r.height, r.bottom) + self.assertEqual((r.width, r.height), r.size) + self.assertEqual((r.left, r.top), r.topleft) + self.assertEqual((r.right, r.top), r.topright) + self.assertEqual((r.left, r.bottom), r.bottomleft) + self.assertEqual((r.right, r.bottom), r.bottomright) + + midx = r.left + r.width // 2 + midy = r.top + r.height // 2 + + self.assertEqual(midx, r.centerx) + self.assertEqual(midy, r.centery) + self.assertEqual((r.centerx, r.centery), r.center) + self.assertEqual((r.centerx, r.top), r.midtop) + self.assertEqual((r.centerx, r.bottom), r.midbottom) + self.assertEqual((r.left, r.centery), r.midleft) + self.assertEqual((r.right, r.centery), r.midright) + + def test_normalize(self): + """Ensures normalize works when width and height are both negative.""" + test_rect = Rect((1, 2), (-3, -6)) + expected_normalized_rect = ( + (test_rect.x + test_rect.w, test_rect.y + test_rect.h), + (-test_rect.w, -test_rect.h), + ) + + test_rect.normalize() + + self.assertEqual(test_rect, expected_normalized_rect) + + @unittest.skipIf(IS_PYPY, "fails on pypy sometimes") + def test_normalize__positive_height(self): + """Ensures normalize works with a negative width and a positive height.""" + test_rect = Rect((1, 2), (-3, 6)) + expected_normalized_rect = ( + (test_rect.x + test_rect.w, test_rect.y), + (-test_rect.w, test_rect.h), + ) + + test_rect.normalize() + + self.assertEqual(test_rect, expected_normalized_rect) + + @unittest.skipIf(IS_PYPY, "fails on pypy sometimes") + def test_normalize__positive_width(self): + """Ensures normalize works with a positive width and a negative height.""" + test_rect = Rect((1, 2), (3, -6)) + expected_normalized_rect = ( + (test_rect.x, test_rect.y + test_rect.h), + (test_rect.w, -test_rect.h), + ) + + test_rect.normalize() + + self.assertEqual(test_rect, expected_normalized_rect) + + @unittest.skipIf(IS_PYPY, "fails on pypy sometimes") + def test_normalize__zero_height(self): + """Ensures normalize works with a negative width and a zero height.""" + test_rect = Rect((1, 2), (-3, 0)) + expected_normalized_rect = ( + (test_rect.x + test_rect.w, test_rect.y), + (-test_rect.w, test_rect.h), + ) + + test_rect.normalize() + + self.assertEqual(test_rect, expected_normalized_rect) + + @unittest.skipIf(IS_PYPY, "fails on pypy sometimes") + def test_normalize__zero_width(self): + """Ensures normalize works with a zero width and a negative height.""" + test_rect = Rect((1, 2), (0, -6)) + expected_normalized_rect = ( + (test_rect.x, test_rect.y + test_rect.h), + (test_rect.w, -test_rect.h), + ) + + test_rect.normalize() + + self.assertEqual(test_rect, expected_normalized_rect) + + @unittest.skipIf(IS_PYPY, "fails on pypy") + def test_normalize__non_negative(self): + """Ensures normalize works when width and height are both non-negative. + + Tests combinations of positive and zero values for width and height. + The normalize method has no impact when both width and height are + non-negative. + """ + for size in ((3, 6), (3, 0), (0, 6), (0, 0)): + test_rect = Rect((1, 2), size) + expected_normalized_rect = Rect(test_rect) + + test_rect.normalize() + + self.assertEqual(test_rect, expected_normalized_rect) + + def test_x(self): + """Ensures changing the x attribute moves the rect and does not change + the rect's size. + """ + expected_x = 10 + expected_y = 2 + expected_size = (3, 4) + r = Rect((1, expected_y), expected_size) + + r.x = expected_x + + self.assertEqual(r.x, expected_x) + self.assertEqual(r.x, r.left) + self.assertEqual(r.y, expected_y) + self.assertEqual(r.size, expected_size) + + def test_x__invalid_value(self): + """Ensures the x attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.x = value + + def test_x__del(self): + """Ensures the x attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.x + + def test_y(self): + """Ensures changing the y attribute moves the rect and does not change + the rect's size. + """ + expected_x = 1 + expected_y = 20 + expected_size = (3, 4) + r = Rect((expected_x, 2), expected_size) + + r.y = expected_y + + self.assertEqual(r.y, expected_y) + self.assertEqual(r.y, r.top) + self.assertEqual(r.x, expected_x) + self.assertEqual(r.size, expected_size) + + def test_y__invalid_value(self): + """Ensures the y attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.y = value + + def test_y__del(self): + """Ensures the y attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.y + + def test_left(self): + """Changing the left attribute moves the rect and does not change + the rect's width + """ + r = Rect(1, 2, 3, 4) + new_left = 10 + + r.left = new_left + self.assertEqual(new_left, r.left) + self.assertEqual(Rect(new_left, 2, 3, 4), r) + + def test_left__invalid_value(self): + """Ensures the left attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.left = value + + def test_left__del(self): + """Ensures the left attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.left + + def test_right(self): + """Changing the right attribute moves the rect and does not change + the rect's width + """ + r = Rect(1, 2, 3, 4) + new_right = r.right + 20 + expected_left = r.left + 20 + old_width = r.width + + r.right = new_right + self.assertEqual(new_right, r.right) + self.assertEqual(expected_left, r.left) + self.assertEqual(old_width, r.width) + + def test_right__invalid_value(self): + """Ensures the right attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.right = value + + def test_right__del(self): + """Ensures the right attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.right + + def test_top(self): + """Changing the top attribute moves the rect and does not change + the rect's width + """ + r = Rect(1, 2, 3, 4) + new_top = 10 + + r.top = new_top + self.assertEqual(Rect(1, new_top, 3, 4), r) + self.assertEqual(new_top, r.top) + + def test_top__invalid_value(self): + """Ensures the top attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.top = value + + def test_top__del(self): + """Ensures the top attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.top + + def test_bottom(self): + """Changing the bottom attribute moves the rect and does not change + the rect's height + """ + r = Rect(1, 2, 3, 4) + new_bottom = r.bottom + 20 + expected_top = r.top + 20 + old_height = r.height + + r.bottom = new_bottom + self.assertEqual(new_bottom, r.bottom) + self.assertEqual(expected_top, r.top) + self.assertEqual(old_height, r.height) + + def test_bottom__invalid_value(self): + """Ensures the bottom attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.bottom = value + + def test_bottom__del(self): + """Ensures the bottom attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.bottom + + def test_centerx(self): + """Changing the centerx attribute moves the rect and does not change + the rect's width + """ + r = Rect(1, 2, 3, 4) + new_centerx = r.centerx + 20 + expected_left = r.left + 20 + old_width = r.width + + r.centerx = new_centerx + self.assertEqual(new_centerx, r.centerx) + self.assertEqual(expected_left, r.left) + self.assertEqual(old_width, r.width) + + def test_centerx__invalid_value(self): + """Ensures the centerx attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.centerx = value + + def test_centerx__del(self): + """Ensures the centerx attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.centerx + + def test_centery(self): + """Changing the centery attribute moves the rect and does not change + the rect's width + """ + r = Rect(1, 2, 3, 4) + new_centery = r.centery + 20 + expected_top = r.top + 20 + old_height = r.height + + r.centery = new_centery + self.assertEqual(new_centery, r.centery) + self.assertEqual(expected_top, r.top) + self.assertEqual(old_height, r.height) + + def test_centery__invalid_value(self): + """Ensures the centery attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.centery = value + + def test_centery__del(self): + """Ensures the centery attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.centery + + def test_topleft(self): + """Changing the topleft attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.topleft = new_topleft + self.assertEqual(new_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_topleft__invalid_value(self): + """Ensures the topleft attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.topleft = value + + def test_topleft__del(self): + """Ensures the topleft attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.topleft + + def test_bottomleft(self): + """Changing the bottomleft attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_bottomleft = (r.left + 20, r.bottom + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.bottomleft = new_bottomleft + self.assertEqual(new_bottomleft, r.bottomleft) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_bottomleft__invalid_value(self): + """Ensures the bottomleft attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.bottomleft = value + + def test_bottomleft__del(self): + """Ensures the bottomleft attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.bottomleft + + def test_topright(self): + """Changing the topright attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_topright = (r.right + 20, r.top + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.topright = new_topright + self.assertEqual(new_topright, r.topright) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_topright__invalid_value(self): + """Ensures the topright attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.topright = value + + def test_topright__del(self): + """Ensures the topright attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.topright + + def test_bottomright(self): + """Changing the bottomright attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_bottomright = (r.right + 20, r.bottom + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.bottomright = new_bottomright + self.assertEqual(new_bottomright, r.bottomright) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_bottomright__invalid_value(self): + """Ensures the bottomright attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.bottomright = value + + def test_bottomright__del(self): + """Ensures the bottomright attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.bottomright + + def test_center(self): + """Changing the center attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_center = (r.centerx + 20, r.centery + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.center = new_center + self.assertEqual(new_center, r.center) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_center__invalid_value(self): + """Ensures the center attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.center = value + + def test_center__del(self): + """Ensures the center attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.center + + def test_midleft(self): + """Changing the midleft attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_midleft = (r.left + 20, r.centery + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.midleft = new_midleft + self.assertEqual(new_midleft, r.midleft) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_midleft__invalid_value(self): + """Ensures the midleft attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.midleft = value + + def test_midleft__del(self): + """Ensures the midleft attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.midleft + + def test_midright(self): + """Changing the midright attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_midright = (r.right + 20, r.centery + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.midright = new_midright + self.assertEqual(new_midright, r.midright) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_midright__invalid_value(self): + """Ensures the midright attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.midright = value + + def test_midright__del(self): + """Ensures the midright attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.midright + + def test_midtop(self): + """Changing the midtop attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_midtop = (r.centerx + 20, r.top + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.midtop = new_midtop + self.assertEqual(new_midtop, r.midtop) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_midtop__invalid_value(self): + """Ensures the midtop attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.midtop = value + + def test_midtop__del(self): + """Ensures the midtop attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.midtop + + def test_midbottom(self): + """Changing the midbottom attribute moves the rect and does not change + the rect's size + """ + r = Rect(1, 2, 3, 4) + new_midbottom = (r.centerx + 20, r.bottom + 30) + expected_topleft = (r.left + 20, r.top + 30) + old_size = r.size + + r.midbottom = new_midbottom + self.assertEqual(new_midbottom, r.midbottom) + self.assertEqual(expected_topleft, r.topleft) + self.assertEqual(old_size, r.size) + + def test_midbottom__invalid_value(self): + """Ensures the midbottom attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.midbottom = value + + def test_midbottom__del(self): + """Ensures the midbottom attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.midbottom + + def test_width(self): + """Changing the width resizes the rect from the top-left corner""" + r = Rect(1, 2, 3, 4) + new_width = 10 + old_topleft = r.topleft + old_height = r.height + + r.width = new_width + self.assertEqual(new_width, r.width) + self.assertEqual(old_height, r.height) + self.assertEqual(old_topleft, r.topleft) + + def test_width__invalid_value(self): + """Ensures the width attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.width = value + + def test_width__del(self): + """Ensures the width attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.width + + def test_height(self): + """Changing the height resizes the rect from the top-left corner""" + r = Rect(1, 2, 3, 4) + new_height = 10 + old_topleft = r.topleft + old_width = r.width + + r.height = new_height + self.assertEqual(new_height, r.height) + self.assertEqual(old_width, r.width) + self.assertEqual(old_topleft, r.topleft) + + def test_height__invalid_value(self): + """Ensures the height attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.height = value + + def test_height__del(self): + """Ensures the height attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.height + + def test_size(self): + """Changing the size resizes the rect from the top-left corner""" + r = Rect(1, 2, 3, 4) + new_size = (10, 20) + old_topleft = r.topleft + + r.size = new_size + self.assertEqual(new_size, r.size) + self.assertEqual(old_topleft, r.topleft) + + def test_size__invalid_value(self): + """Ensures the size attribute handles invalid values correctly.""" + r = Rect(0, 0, 1, 1) + + for value in (None, [], "1", 1, (1,), [1, 2, 3]): + with self.assertRaises(TypeError): + r.size = value + + def test_size__del(self): + """Ensures the size attribute can't be deleted.""" + r = Rect(0, 0, 1, 1) + + with self.assertRaises(AttributeError): + del r.size + + def test_contains(self): + r = Rect(1, 2, 3, 4) + + self.assertTrue( + r.contains(Rect(2, 3, 1, 1)), "r does not contain Rect(2, 3, 1, 1)" + ) + self.assertTrue(Rect(2, 3, 1, 1) in r, "r does not contain Rect(2, 3, 1, 1) 2") + self.assertTrue( + r.contains(Rect(r)), "r does not contain the same rect as itself" + ) + self.assertTrue(r in Rect(r), "r does not contain the same rect as itself") + self.assertTrue( + r.contains(Rect(2, 3, 0, 0)), + "r does not contain an empty rect within its bounds", + ) + self.assertTrue( + Rect(2, 3, 0, 0) in r, + "r does not contain an empty rect within its bounds", + ) + self.assertFalse(r.contains(Rect(0, 0, 1, 2)), "r contains Rect(0, 0, 1, 2)") + self.assertFalse(r.contains(Rect(4, 6, 1, 1)), "r contains Rect(4, 6, 1, 1)") + self.assertFalse(r.contains(Rect(4, 6, 0, 0)), "r contains Rect(4, 6, 0, 0)") + self.assertFalse(Rect(0, 0, 1, 2) in r, "r contains Rect(0, 0, 1, 2)") + self.assertFalse(Rect(4, 6, 1, 1) in r, "r contains Rect(4, 6, 1, 1)") + self.assertFalse(Rect(4, 6, 0, 0) in r, "r contains Rect(4, 6, 0, 0)") + self.assertTrue(2 in Rect(0, 0, 1, 2), "r does not contain 2") + self.assertFalse(3 in Rect(0, 0, 1, 2), "r contains 3") + + def test_collidepoint(self): + r = Rect(1, 2, 3, 4) + + self.assertTrue( + r.collidepoint(r.left, r.top), "r does not collide with point (left, top)" + ) + self.assertFalse( + r.collidepoint(r.left - 1, r.top), "r collides with point (left - 1, top)" + ) + self.assertFalse( + r.collidepoint(r.left, r.top - 1), "r collides with point (left, top - 1)" + ) + self.assertFalse( + r.collidepoint(r.left - 1, r.top - 1), + "r collides with point (left - 1, top - 1)", + ) + + self.assertTrue( + r.collidepoint(r.right - 1, r.bottom - 1), + "r does not collide with point (right - 1, bottom - 1)", + ) + self.assertFalse( + r.collidepoint(r.right, r.bottom), "r collides with point (right, bottom)" + ) + self.assertFalse( + r.collidepoint(r.right - 1, r.bottom), + "r collides with point (right - 1, bottom)", + ) + self.assertFalse( + r.collidepoint(r.right, r.bottom - 1), + "r collides with point (right, bottom - 1)", + ) + + def test_inflate__larger(self): + """The inflate method inflates around the center of the rectangle""" + r = Rect(2, 4, 6, 8) + r2 = r.inflate(4, 6) + + self.assertEqual(r.center, r2.center) + self.assertEqual(r.left - 2, r2.left) + self.assertEqual(r.top - 3, r2.top) + self.assertEqual(r.right + 2, r2.right) + self.assertEqual(r.bottom + 3, r2.bottom) + self.assertEqual(r.width + 4, r2.width) + self.assertEqual(r.height + 6, r2.height) + + def test_inflate__smaller(self): + """The inflate method inflates around the center of the rectangle""" + r = Rect(2, 4, 6, 8) + r2 = r.inflate(-4, -6) + + self.assertEqual(r.center, r2.center) + self.assertEqual(r.left + 2, r2.left) + self.assertEqual(r.top + 3, r2.top) + self.assertEqual(r.right - 2, r2.right) + self.assertEqual(r.bottom - 3, r2.bottom) + self.assertEqual(r.width - 4, r2.width) + self.assertEqual(r.height - 6, r2.height) + + def test_inflate_ip__larger(self): + """The inflate_ip method inflates around the center of the rectangle""" + r = Rect(2, 4, 6, 8) + r2 = Rect(r) + r2.inflate_ip(-4, -6) + + self.assertEqual(r.center, r2.center) + self.assertEqual(r.left + 2, r2.left) + self.assertEqual(r.top + 3, r2.top) + self.assertEqual(r.right - 2, r2.right) + self.assertEqual(r.bottom - 3, r2.bottom) + self.assertEqual(r.width - 4, r2.width) + self.assertEqual(r.height - 6, r2.height) + + def test_inflate_ip__smaller(self): + """The inflate method inflates around the center of the rectangle""" + r = Rect(2, 4, 6, 8) + r2 = Rect(r) + r2.inflate_ip(-4, -6) + + self.assertEqual(r.center, r2.center) + self.assertEqual(r.left + 2, r2.left) + self.assertEqual(r.top + 3, r2.top) + self.assertEqual(r.right - 2, r2.right) + self.assertEqual(r.bottom - 3, r2.bottom) + self.assertEqual(r.width - 4, r2.width) + self.assertEqual(r.height - 6, r2.height) + + def test_clamp(self): + r = Rect(10, 10, 10, 10) + c = Rect(19, 12, 5, 5).clamp(r) + self.assertEqual(c.right, r.right) + self.assertEqual(c.top, 12) + c = Rect(1, 2, 3, 4).clamp(r) + self.assertEqual(c.topleft, r.topleft) + c = Rect(5, 500, 22, 33).clamp(r) + self.assertEqual(c.center, r.center) + + def test_clamp_ip(self): + r = Rect(10, 10, 10, 10) + c = Rect(19, 12, 5, 5) + c.clamp_ip(r) + self.assertEqual(c.right, r.right) + self.assertEqual(c.top, 12) + c = Rect(1, 2, 3, 4) + c.clamp_ip(r) + self.assertEqual(c.topleft, r.topleft) + c = Rect(5, 500, 22, 33) + c.clamp_ip(r) + self.assertEqual(c.center, r.center) + + def test_clip(self): + r1 = Rect(1, 2, 3, 4) + self.assertEqual(Rect(1, 2, 2, 2), r1.clip(Rect(0, 0, 3, 4))) + self.assertEqual(Rect(2, 2, 2, 4), r1.clip(Rect(2, 2, 10, 20))) + self.assertEqual(Rect(2, 3, 1, 2), r1.clip(Rect(2, 3, 1, 2))) + self.assertEqual((0, 0), r1.clip(20, 30, 5, 6).size) + self.assertEqual( + r1, r1.clip(Rect(r1)), "r1 does not clip an identical rect to itself" + ) + + def test_clipline(self): + """Ensures clipline handles four int parameters. + + Tests the clipline(x1, y1, x2, y2) format. + """ + rect = Rect((1, 2), (35, 40)) + x1 = 5 + y1 = 6 + x2 = 11 + y2 = 19 + expected_line = ((x1, y1), (x2, y2)) + + clipped_line = rect.clipline(x1, y1, x2, y2) + + self.assertIsInstance(clipped_line, tuple) + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__two_sequences(self): + """Ensures clipline handles a sequence of two sequences. + + Tests the clipline((x1, y1), (x2, y2)) format. + Tests the sequences as different types. + """ + rect = Rect((1, 2), (35, 40)) + pt1 = (5, 6) + pt2 = (11, 19) + + INNER_SEQUENCES = (list, tuple, Vector2) + expected_line = (pt1, pt2) + + for inner_seq1 in INNER_SEQUENCES: + endpt1 = inner_seq1(pt1) + + for inner_seq2 in INNER_SEQUENCES: + clipped_line = rect.clipline((endpt1, inner_seq2(pt2))) + + self.assertIsInstance(clipped_line, tuple) + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__sequence_of_four_ints(self): + """Ensures clipline handles a sequence of four ints. + + Tests the clipline((x1, y1, x2, y2)) format. + Tests the sequence as different types. + """ + rect = Rect((1, 2), (35, 40)) + line = (5, 6, 11, 19) + expected_line = ((line[0], line[1]), (line[2], line[3])) + + for outer_seq in (list, tuple): + clipped_line = rect.clipline(outer_seq(line)) + + self.assertIsInstance(clipped_line, tuple) + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__sequence_of_two_sequences(self): + """Ensures clipline handles a sequence of two sequences. + + Tests the clipline(((x1, y1), (x2, y2))) format. + Tests the sequences as different types. + """ + rect = Rect((1, 2), (35, 40)) + pt1 = (5, 6) + pt2 = (11, 19) + + INNER_SEQUENCES = (list, tuple, Vector2) + expected_line = (pt1, pt2) + + for inner_seq1 in INNER_SEQUENCES: + endpt1 = inner_seq1(pt1) + + for inner_seq2 in INNER_SEQUENCES: + endpt2 = inner_seq2(pt2) + + for outer_seq in (list, tuple): + clipped_line = rect.clipline(outer_seq((endpt1, endpt2))) + + self.assertIsInstance(clipped_line, tuple) + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__floats(self): + """Ensures clipline handles float parameters.""" + rect = Rect((1, 2), (35, 40)) + x1 = 5.9 + y1 = 6.9 + x2 = 11.9 + y2 = 19.9 + + # Floats are truncated. + expected_line = ( + (math.floor(x1), math.floor(y1)), + (math.floor(x2), math.floor(y2)), + ) + + clipped_line = rect.clipline(x1, y1, x2, y2) + + self.assertIsInstance(clipped_line, tuple) + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__no_overlap(self): + """Ensures lines that do not overlap the rect are not clipped.""" + rect = Rect((10, 25), (15, 20)) + # Use a bigger rect to help create test lines. + big_rect = rect.inflate(2, 2) + lines = ( + (big_rect.bottomleft, big_rect.topleft), # Left edge. + (big_rect.topleft, big_rect.topright), # Top edge. + (big_rect.topright, big_rect.bottomright), # Right edge. + (big_rect.bottomright, big_rect.bottomleft), + ) # Bottom edge. + expected_line = () + + # Test lines outside rect. + for line in lines: + clipped_line = rect.clipline(line) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__both_endpoints_outside(self): + """Ensures lines that overlap the rect are clipped. + + Testing lines with both endpoints outside the rect. + """ + rect = Rect((0, 0), (20, 20)) + # Use a bigger rect to help create test lines. + big_rect = rect.inflate(2, 2) + + # Create a dict of lines and expected results. + line_dict = { + (big_rect.midleft, big_rect.midright): ( + rect.midleft, + (rect.midright[0] - 1, rect.midright[1]), + ), + (big_rect.midtop, big_rect.midbottom): ( + rect.midtop, + (rect.midbottom[0], rect.midbottom[1] - 1), + ), + # Diagonals. + (big_rect.topleft, big_rect.bottomright): ( + rect.topleft, + (rect.bottomright[0] - 1, rect.bottomright[1] - 1), + ), + # This line needs a small adjustment to make sure it intersects + # the rect correctly. + ( + (big_rect.topright[0] - 1, big_rect.topright[1]), + (big_rect.bottomleft[0], big_rect.bottomleft[1] - 1), + ): ( + (rect.topright[0] - 1, rect.topright[1]), + (rect.bottomleft[0], rect.bottomleft[1] - 1), + ), + } + + for line, expected_line in line_dict.items(): + clipped_line = rect.clipline(line) + + self.assertTupleEqual(clipped_line, expected_line) + + # Swap endpoints to test for symmetry. + expected_line = (expected_line[1], expected_line[0]) + + clipped_line = rect.clipline((line[1], line[0])) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__both_endpoints_inside(self): + """Ensures lines that overlap the rect are clipped. + + Testing lines with both endpoints inside the rect. + """ + rect = Rect((-10, -5), (20, 20)) + # Use a smaller rect to help create test lines. + small_rect = rect.inflate(-2, -2) + + lines = ( + (small_rect.midleft, small_rect.midright), + (small_rect.midtop, small_rect.midbottom), + # Diagonals. + (small_rect.topleft, small_rect.bottomright), + (small_rect.topright, small_rect.bottomleft), + ) + + for line in lines: + expected_line = line + + clipped_line = rect.clipline(line) + + self.assertTupleEqual(clipped_line, expected_line) + + # Swap endpoints to test for symmetry. + expected_line = (expected_line[1], expected_line[0]) + + clipped_line = rect.clipline((line[1], line[0])) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__endpoints_inside_and_outside(self): + """Ensures lines that overlap the rect are clipped. + + Testing lines with one endpoint outside the rect and the other is + inside the rect. + """ + rect = Rect((0, 0), (21, 21)) + # Use a bigger rect to help create test lines. + big_rect = rect.inflate(2, 2) + + # Create a dict of lines and expected results. + line_dict = { + (big_rect.midleft, rect.center): (rect.midleft, rect.center), + (big_rect.midtop, rect.center): (rect.midtop, rect.center), + (big_rect.midright, rect.center): ( + (rect.midright[0] - 1, rect.midright[1]), + rect.center, + ), + (big_rect.midbottom, rect.center): ( + (rect.midbottom[0], rect.midbottom[1] - 1), + rect.center, + ), + # Diagonals. + (big_rect.topleft, rect.center): (rect.topleft, rect.center), + (big_rect.topright, rect.center): ( + (rect.topright[0] - 1, rect.topright[1]), + rect.center, + ), + (big_rect.bottomright, rect.center): ( + (rect.bottomright[0] - 1, rect.bottomright[1] - 1), + rect.center, + ), + # This line needs a small adjustment to make sure it intersects + # the rect correctly. + ((big_rect.bottomleft[0], big_rect.bottomleft[1] - 1), rect.center): ( + (rect.bottomleft[0], rect.bottomleft[1] - 1), + rect.center, + ), + } + + for line, expected_line in line_dict.items(): + clipped_line = rect.clipline(line) + + self.assertTupleEqual(clipped_line, expected_line) + + # Swap endpoints to test for symmetry. + expected_line = (expected_line[1], expected_line[0]) + + clipped_line = rect.clipline((line[1], line[0])) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__edges(self): + """Ensures clipline properly clips line that are along the rect edges.""" + rect = Rect((10, 25), (15, 20)) + + # Create a dict of edges and expected results. + edge_dict = { + # Left edge. + (rect.bottomleft, rect.topleft): ( + (rect.bottomleft[0], rect.bottomleft[1] - 1), + rect.topleft, + ), + # Top edge. + (rect.topleft, rect.topright): ( + rect.topleft, + (rect.topright[0] - 1, rect.topright[1]), + ), + # Right edge. + (rect.topright, rect.bottomright): (), + # Bottom edge. + (rect.bottomright, rect.bottomleft): (), + } + + for edge, expected_line in edge_dict.items(): + clipped_line = rect.clipline(edge) + + self.assertTupleEqual(clipped_line, expected_line) + + # Swap endpoints to test for symmetry. + if expected_line: + expected_line = (expected_line[1], expected_line[0]) + + clipped_line = rect.clipline((edge[1], edge[0])) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__equal_endpoints_with_overlap(self): + """Ensures clipline handles lines with both endpoints the same. + + Testing lines that overlap the rect. + """ + rect = Rect((10, 25), (15, 20)) + + # Test all the points in and on a rect. + pts = ( + (x, y) + for x in range(rect.left, rect.right) + for y in range(rect.top, rect.bottom) + ) + + for pt in pts: + expected_line = (pt, pt) + + clipped_line = rect.clipline((pt, pt)) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__equal_endpoints_no_overlap(self): + """Ensures clipline handles lines with both endpoints the same. + + Testing lines that do not overlap the rect. + """ + expected_line = () + rect = Rect((10, 25), (15, 20)) + + # Test points outside rect. + for pt in test_utils.rect_perimeter_pts(rect.inflate(2, 2)): + clipped_line = rect.clipline((pt, pt)) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__zero_size_rect(self): + """Ensures clipline handles zero sized rects correctly.""" + expected_line = () + + for size in ((0, 15), (15, 0), (0, 0)): + rect = Rect((10, 25), size) + + clipped_line = rect.clipline(rect.topleft, rect.topleft) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__negative_size_rect(self): + """Ensures clipline handles negative sized rects correctly.""" + expected_line = () + + for size in ((-15, 20), (15, -20), (-15, -20)): + rect = Rect((10, 25), size) + norm_rect = rect.copy() + norm_rect.normalize() + # Use a bigger rect to help create test lines. + big_rect = norm_rect.inflate(2, 2) + + # Create a dict of lines and expected results. Some line have both + # endpoints outside the rect and some have one inside and one + # outside. + line_dict = { + (big_rect.midleft, big_rect.midright): ( + norm_rect.midleft, + (norm_rect.midright[0] - 1, norm_rect.midright[1]), + ), + (big_rect.midtop, big_rect.midbottom): ( + norm_rect.midtop, + (norm_rect.midbottom[0], norm_rect.midbottom[1] - 1), + ), + (big_rect.midleft, norm_rect.center): ( + norm_rect.midleft, + norm_rect.center, + ), + (big_rect.midtop, norm_rect.center): ( + norm_rect.midtop, + norm_rect.center, + ), + (big_rect.midright, norm_rect.center): ( + (norm_rect.midright[0] - 1, norm_rect.midright[1]), + norm_rect.center, + ), + (big_rect.midbottom, norm_rect.center): ( + (norm_rect.midbottom[0], norm_rect.midbottom[1] - 1), + norm_rect.center, + ), + } + + for line, expected_line in line_dict.items(): + clipped_line = rect.clipline(line) + + # Make sure rect wasn't normalized. + self.assertNotEqual(rect, norm_rect) + self.assertTupleEqual(clipped_line, expected_line) + + # Swap endpoints to test for symmetry. + expected_line = (expected_line[1], expected_line[0]) + + clipped_line = rect.clipline((line[1], line[0])) + + self.assertTupleEqual(clipped_line, expected_line) + + def test_clipline__invalid_line(self): + """Ensures clipline handles invalid lines correctly.""" + rect = Rect((0, 0), (10, 20)) + invalid_lines = ( + (), + (1,), + (1, 2), + (1, 2, 3), + (1, 2, 3, 4, 5), + ((1, 2),), + ((1, 2), (3,)), + ((1, 2), 3), + ((1, 2, 5), (3, 4)), + ((1, 2), (3, 4, 5)), + ((1, 2), (3, 4), (5, 6)), + ) + + for line in invalid_lines: + with self.assertRaises(TypeError): + clipped_line = rect.clipline(line) + + with self.assertRaises(TypeError): + clipped_line = rect.clipline(*line) + + @unittest.skipIf(IS_PYPY, "fails on pypy sometimes") + def test_move(self): + r = Rect(1, 2, 3, 4) + move_x = 10 + move_y = 20 + r2 = r.move(move_x, move_y) + expected_r2 = Rect(r.left + move_x, r.top + move_y, r.width, r.height) + self.assertEqual(expected_r2, r2) + + @unittest.skipIf(IS_PYPY, "fails on pypy sometimes") + def test_move_ip(self): + r = Rect(1, 2, 3, 4) + r2 = Rect(r) + move_x = 10 + move_y = 20 + r2.move_ip(move_x, move_y) + expected_r2 = Rect(r.left + move_x, r.top + move_y, r.width, r.height) + self.assertEqual(expected_r2, r2) + + def test_update_XYWidthHeight(self): + """Test update with 4 int values(x, y, w, h)""" + rect = Rect(0, 0, 1, 1) + rect.update(1, 2, 3, 4) + + self.assertEqual(1, rect.left) + self.assertEqual(2, rect.top) + self.assertEqual(3, rect.width) + self.assertEqual(4, rect.height) + + def test_update__TopLeftSize(self): + """Test update with 2 tuples((x, y), (w, h))""" + rect = Rect(0, 0, 1, 1) + rect.update((1, 2), (3, 4)) + + self.assertEqual(1, rect.left) + self.assertEqual(2, rect.top) + self.assertEqual(3, rect.width) + self.assertEqual(4, rect.height) + + def test_update__List(self): + """Test update with list""" + rect = Rect(0, 0, 1, 1) + rect2 = [1, 2, 3, 4] + rect.update(rect2) + + self.assertEqual(1, rect.left) + self.assertEqual(2, rect.top) + self.assertEqual(3, rect.width) + self.assertEqual(4, rect.height) + + def test_update__RectObject(self): + """Test update with other rect object""" + rect = Rect(0, 0, 1, 1) + rect2 = Rect(1, 2, 3, 4) + rect.update(rect2) + + self.assertEqual(1, rect.left) + self.assertEqual(2, rect.top) + self.assertEqual(3, rect.width) + self.assertEqual(4, rect.height) + + def test_union(self): + r1 = Rect(1, 1, 1, 2) + r2 = Rect(-2, -2, 1, 2) + self.assertEqual(Rect(-2, -2, 4, 5), r1.union(r2)) + + def test_union__with_identical_Rect(self): + r1 = Rect(1, 2, 3, 4) + self.assertEqual(r1, r1.union(Rect(r1))) + + def test_union_ip(self): + r1 = Rect(1, 1, 1, 2) + r2 = Rect(-2, -2, 1, 2) + r1.union_ip(r2) + self.assertEqual(Rect(-2, -2, 4, 5), r1) + + def test_unionall(self): + r1 = Rect(0, 0, 1, 1) + r2 = Rect(-2, -2, 1, 1) + r3 = Rect(2, 2, 1, 1) + + r4 = r1.unionall([r2, r3]) + self.assertEqual(Rect(-2, -2, 5, 5), r4) + + def test_unionall__invalid_rect_format(self): + """Ensures unionall correctly handles invalid rect parameters.""" + numbers = [0, 1.2, 2, 3.3] + strs = ["a", "b", "c"] + nones = [None, None] + + for invalid_rects in (numbers, strs, nones): + with self.assertRaises(TypeError): + Rect(0, 0, 1, 1).unionall(invalid_rects) + + def test_unionall_ip(self): + r1 = Rect(0, 0, 1, 1) + r2 = Rect(-2, -2, 1, 1) + r3 = Rect(2, 2, 1, 1) + + r1.unionall_ip([r2, r3]) + self.assertEqual(Rect(-2, -2, 5, 5), r1) + + # Bug for an empty list. Would return a Rect instead of None. + self.assertTrue(r1.unionall_ip([]) is None) + + def test_unionall__invalid_rect_format(self): + """Ensures unionall_ip correctly handles invalid rect parameters.""" + numbers = [0, 1.2, 2, 3.3] + strs = ["a", "b", "c"] + nones = [None, None] + + for invalid_rects in (numbers, strs, nones): + with self.assertRaises(TypeError): + Rect(0, 0, 1, 1).unionall_ip(invalid_rects) + + def test_colliderect(self): + r1 = Rect(1, 2, 3, 4) + self.assertTrue( + r1.colliderect(Rect(0, 0, 2, 3)), + "r1 does not collide with Rect(0, 0, 2, 3)", + ) + self.assertFalse( + r1.colliderect(Rect(0, 0, 1, 2)), "r1 collides with Rect(0, 0, 1, 2)" + ) + self.assertFalse( + r1.colliderect(Rect(r1.right, r1.bottom, 2, 2)), + "r1 collides with Rect(r1.right, r1.bottom, 2, 2)", + ) + self.assertTrue( + r1.colliderect(Rect(r1.left + 1, r1.top + 1, r1.width - 2, r1.height - 2)), + "r1 does not collide with Rect(r1.left + 1, r1.top + 1, " + + "r1.width - 2, r1.height - 2)", + ) + self.assertTrue( + r1.colliderect(Rect(r1.left - 1, r1.top - 1, r1.width + 2, r1.height + 2)), + "r1 does not collide with Rect(r1.left - 1, r1.top - 1, " + + "r1.width + 2, r1.height + 2)", + ) + self.assertTrue( + r1.colliderect(Rect(r1)), "r1 does not collide with an identical rect" + ) + self.assertFalse( + r1.colliderect(Rect(r1.right, r1.bottom, 0, 0)), + "r1 collides with Rect(r1.right, r1.bottom, 0, 0)", + ) + self.assertFalse( + r1.colliderect(Rect(r1.right, r1.bottom, 1, 1)), + "r1 collides with Rect(r1.right, r1.bottom, 1, 1)", + ) + + @unittest.skipIf(IS_PYPY, "fails on pypy3 sometimes") + def testEquals(self): + """check to see how the rect uses __eq__""" + r1 = Rect(1, 2, 3, 4) + r2 = Rect(10, 20, 30, 40) + r3 = (10, 20, 30, 40) + r4 = Rect(10, 20, 30, 40) + + class foo(Rect): + def __eq__(self, other): + return id(self) == id(other) + + def __ne__(self, other): + return id(self) != id(other) + + class foo2(Rect): + pass + + r5 = foo(10, 20, 30, 40) + r6 = foo2(10, 20, 30, 40) + + self.assertNotEqual(r5, r2) + + # because we define equality differently for this subclass. + self.assertEqual(r6, r2) + + rect_list = [r1, r2, r3, r4, r6] + + # see if we can remove 4 of these. + rect_list.remove(r2) + rect_list.remove(r2) + rect_list.remove(r2) + rect_list.remove(r2) + self.assertRaises(ValueError, rect_list.remove, r2) + + def test_collidedict(self): + """Ensures collidedict detects collisions.""" + rect = Rect(1, 1, 10, 10) + + collide_item1 = ("collide 1", rect.copy()) + collide_item2 = ("collide 2", Rect(5, 5, 10, 10)) + no_collide_item1 = ("no collide 1", Rect(60, 60, 10, 10)) + no_collide_item2 = ("no collide 2", Rect(70, 70, 10, 10)) + + # Dict to check collisions with values. + rect_values = dict( + (collide_item1, collide_item2, no_collide_item1, no_collide_item2) + ) + value_collide_items = (collide_item1, collide_item2) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + key_collide_items = tuple((tuple(v), k) for k, v in value_collide_items) + + for use_values in (True, False): + if use_values: + expected_items = value_collide_items + d = rect_values + else: + expected_items = key_collide_items + d = rect_keys + + collide_item = rect.collidedict(d, use_values) + + # The detected collision could be any of the possible items. + self.assertIn(collide_item, expected_items) + + def test_collidedict__no_collision(self): + """Ensures collidedict returns None when no collisions.""" + rect = Rect(1, 1, 10, 10) + + no_collide_item1 = ("no collide 1", Rect(50, 50, 10, 10)) + no_collide_item2 = ("no collide 2", Rect(60, 60, 10, 10)) + no_collide_item3 = ("no collide 3", Rect(70, 70, 10, 10)) + + # Dict to check collisions with values. + rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3)) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + + for use_values in (True, False): + d = rect_values if use_values else rect_keys + + collide_item = rect.collidedict(d, use_values) + + self.assertIsNone(collide_item) + + def test_collidedict__barely_touching(self): + """Ensures collidedict works correctly for rects that barely touch.""" + rect = Rect(1, 1, 10, 10) + # Small rect to test barely touching collisions. + collide_rect = Rect(0, 0, 1, 1) + + collide_item1 = ("collide 1", collide_rect) + no_collide_item1 = ("no collide 1", Rect(50, 50, 10, 10)) + no_collide_item2 = ("no collide 2", Rect(60, 60, 10, 10)) + no_collide_item3 = ("no collide 3", Rect(70, 70, 10, 10)) + + # Dict to check collisions with values. + no_collide_rect_values = dict( + (no_collide_item1, no_collide_item2, no_collide_item3) + ) + + # Dict to check collisions with keys. + no_collide_rect_keys = {tuple(v): k for k, v in no_collide_rect_values.items()} + + # Tests the collide_rect on each of the rect's corners. + for attr in ("topleft", "topright", "bottomright", "bottomleft"): + setattr(collide_rect, attr, getattr(rect, attr)) + + for use_values in (True, False): + if use_values: + expected_item = collide_item1 + d = dict(no_collide_rect_values) + else: + expected_item = (tuple(collide_item1[1]), collide_item1[0]) + d = dict(no_collide_rect_keys) + + d.update((expected_item,)) # Add in the expected item. + + collide_item = rect.collidedict(d, use_values) + + self.assertTupleEqual(collide_item, expected_item) + + def test_collidedict__zero_sized_rects(self): + """Ensures collidedict works correctly with zero sized rects. + + There should be no collisions with zero sized rects. + """ + zero_rect1 = Rect(1, 1, 0, 0) + zero_rect2 = Rect(1, 1, 1, 0) + zero_rect3 = Rect(1, 1, 0, 1) + zero_rect4 = Rect(1, 1, -1, 0) + zero_rect5 = Rect(1, 1, 0, -1) + + no_collide_item1 = ("no collide 1", zero_rect1.copy()) + no_collide_item2 = ("no collide 2", zero_rect2.copy()) + no_collide_item3 = ("no collide 3", zero_rect3.copy()) + no_collide_item4 = ("no collide 4", zero_rect4.copy()) + no_collide_item5 = ("no collide 5", zero_rect5.copy()) + no_collide_item6 = ("no collide 6", Rect(0, 0, 10, 10)) + no_collide_item7 = ("no collide 7", Rect(0, 0, 2, 2)) + + # Dict to check collisions with values. + rect_values = dict( + ( + no_collide_item1, + no_collide_item2, + no_collide_item3, + no_collide_item4, + no_collide_item5, + no_collide_item6, + no_collide_item7, + ) + ) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + + for use_values in (True, False): + d = rect_values if use_values else rect_keys + + for zero_rect in ( + zero_rect1, + zero_rect2, + zero_rect3, + zero_rect4, + zero_rect5, + ): + collide_item = zero_rect.collidedict(d, use_values) + + self.assertIsNone(collide_item) + + def test_collidedict__zero_sized_rects_as_args(self): + """Ensures collidedict works correctly with zero sized rects as args. + + There should be no collisions with zero sized rects. + """ + rect = Rect(0, 0, 10, 10) + + no_collide_item1 = ("no collide 1", Rect(1, 1, 0, 0)) + no_collide_item2 = ("no collide 2", Rect(1, 1, 1, 0)) + no_collide_item3 = ("no collide 3", Rect(1, 1, 0, 1)) + + # Dict to check collisions with values. + rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3)) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + + for use_values in (True, False): + d = rect_values if use_values else rect_keys + + collide_item = rect.collidedict(d, use_values) + + self.assertIsNone(collide_item) + + def test_collidedict__negative_sized_rects(self): + """Ensures collidedict works correctly with negative sized rects.""" + neg_rect = Rect(1, 1, -1, -1) + + collide_item1 = ("collide 1", neg_rect.copy()) + collide_item2 = ("collide 2", Rect(0, 0, 10, 10)) + no_collide_item1 = ("no collide 1", Rect(1, 1, 10, 10)) + + # Dict to check collisions with values. + rect_values = dict((collide_item1, collide_item2, no_collide_item1)) + value_collide_items = (collide_item1, collide_item2) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + key_collide_items = tuple((tuple(v), k) for k, v in value_collide_items) + + for use_values in (True, False): + if use_values: + collide_items = value_collide_items + d = rect_values + else: + collide_items = key_collide_items + d = rect_keys + + collide_item = neg_rect.collidedict(d, use_values) + + # The detected collision could be any of the possible items. + self.assertIn(collide_item, collide_items) + + def test_collidedict__negative_sized_rects_as_args(self): + """Ensures collidedict works correctly with negative sized rect args.""" + rect = Rect(0, 0, 10, 10) + + collide_item1 = ("collide 1", Rect(1, 1, -1, -1)) + no_collide_item1 = ("no collide 1", Rect(1, 1, -1, 0)) + no_collide_item2 = ("no collide 2", Rect(1, 1, 0, -1)) + + # Dict to check collisions with values. + rect_values = dict((collide_item1, no_collide_item1, no_collide_item2)) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + + for use_values in (True, False): + if use_values: + expected_item = collide_item1 + d = rect_values + else: + expected_item = (tuple(collide_item1[1]), collide_item1[0]) + d = rect_keys + + collide_item = rect.collidedict(d, use_values) + + self.assertTupleEqual(collide_item, expected_item) + + def test_collidedict__invalid_dict_format(self): + """Ensures collidedict correctly handles invalid dict parameters.""" + rect = Rect(0, 0, 10, 10) + + invalid_value_dict = ("collide", rect.copy()) + invalid_key_dict = tuple(invalid_value_dict[1]), invalid_value_dict[0] + + for use_values in (True, False): + d = invalid_value_dict if use_values else invalid_key_dict + + with self.assertRaises(TypeError): + collide_item = rect.collidedict(d, use_values) + + def test_collidedict__invalid_dict_value_format(self): + """Ensures collidedict correctly handles dicts with invalid values.""" + rect = Rect(0, 0, 10, 10) + rect_keys = {tuple(rect): "collide"} + + with self.assertRaises(TypeError): + collide_item = rect.collidedict(rect_keys, 1) + + def test_collidedict__invalid_dict_key_format(self): + """Ensures collidedict correctly handles dicts with invalid keys.""" + rect = Rect(0, 0, 10, 10) + rect_values = {"collide": rect.copy()} + + with self.assertRaises(TypeError): + collide_item = rect.collidedict(rect_values) + + def test_collidedict__invalid_use_values_format(self): + """Ensures collidedict correctly handles invalid use_values parameters.""" + rect = Rect(0, 0, 1, 1) + d = {} + + for invalid_param in (None, d, 1.1): + with self.assertRaises(TypeError): + collide_item = rect.collidedict(d, invalid_param) + + def test_collidedictall(self): + """Ensures collidedictall detects collisions.""" + rect = Rect(1, 1, 10, 10) + + collide_item1 = ("collide 1", rect.copy()) + collide_item2 = ("collide 2", Rect(5, 5, 10, 10)) + no_collide_item1 = ("no collide 1", Rect(60, 60, 20, 20)) + no_collide_item2 = ("no collide 2", Rect(70, 70, 20, 20)) + + # Dict to check collisions with values. + rect_values = dict( + (collide_item1, collide_item2, no_collide_item1, no_collide_item2) + ) + value_collide_items = [collide_item1, collide_item2] + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + key_collide_items = [(tuple(v), k) for k, v in value_collide_items] + + for use_values in (True, False): + if use_values: + expected_items = value_collide_items + d = rect_values + else: + expected_items = key_collide_items + d = rect_keys + + collide_items = rect.collidedictall(d, use_values) + + self._assertCountEqual(collide_items, expected_items) + + def test_collidedictall__no_collision(self): + """Ensures collidedictall returns an empty list when no collisions.""" + rect = Rect(1, 1, 10, 10) + + no_collide_item1 = ("no collide 1", Rect(50, 50, 20, 20)) + no_collide_item2 = ("no collide 2", Rect(60, 60, 20, 20)) + no_collide_item3 = ("no collide 3", Rect(70, 70, 20, 20)) + + # Dict to check collisions with values. + rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3)) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + + expected_items = [] + + for use_values in (True, False): + d = rect_values if use_values else rect_keys + + collide_items = rect.collidedictall(d, use_values) + + self._assertCountEqual(collide_items, expected_items) + + def test_collidedictall__barely_touching(self): + """Ensures collidedictall works correctly for rects that barely touch.""" + rect = Rect(1, 1, 10, 10) + # Small rect to test barely touching collisions. + collide_rect = Rect(0, 0, 1, 1) + + collide_item1 = ("collide 1", collide_rect) + no_collide_item1 = ("no collide 1", Rect(50, 50, 20, 20)) + no_collide_item2 = ("no collide 2", Rect(60, 60, 20, 20)) + no_collide_item3 = ("no collide 3", Rect(70, 70, 20, 20)) + + # Dict to check collisions with values. + no_collide_rect_values = dict( + (no_collide_item1, no_collide_item2, no_collide_item3) + ) + + # Dict to check collisions with keys. + no_collide_rect_keys = {tuple(v): k for k, v in no_collide_rect_values.items()} + + # Tests the collide_rect on each of the rect's corners. + for attr in ("topleft", "topright", "bottomright", "bottomleft"): + setattr(collide_rect, attr, getattr(rect, attr)) + + for use_values in (True, False): + if use_values: + expected_items = [collide_item1] + d = dict(no_collide_rect_values) + else: + expected_items = [(tuple(collide_item1[1]), collide_item1[0])] + d = dict(no_collide_rect_keys) + + d.update(expected_items) # Add in the expected items. + + collide_items = rect.collidedictall(d, use_values) + + self._assertCountEqual(collide_items, expected_items) + + def test_collidedictall__zero_sized_rects(self): + """Ensures collidedictall works correctly with zero sized rects. + + There should be no collisions with zero sized rects. + """ + zero_rect1 = Rect(2, 2, 0, 0) + zero_rect2 = Rect(2, 2, 2, 0) + zero_rect3 = Rect(2, 2, 0, 2) + zero_rect4 = Rect(2, 2, -2, 0) + zero_rect5 = Rect(2, 2, 0, -2) + + no_collide_item1 = ("no collide 1", zero_rect1.copy()) + no_collide_item2 = ("no collide 2", zero_rect2.copy()) + no_collide_item3 = ("no collide 3", zero_rect3.copy()) + no_collide_item4 = ("no collide 4", zero_rect4.copy()) + no_collide_item5 = ("no collide 5", zero_rect5.copy()) + no_collide_item6 = ("no collide 6", Rect(0, 0, 10, 10)) + no_collide_item7 = ("no collide 7", Rect(0, 0, 2, 2)) + + # Dict to check collisions with values. + rect_values = dict( + ( + no_collide_item1, + no_collide_item2, + no_collide_item3, + no_collide_item4, + no_collide_item5, + no_collide_item6, + no_collide_item7, + ) + ) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + + expected_items = [] + + for use_values in (True, False): + d = rect_values if use_values else rect_keys + + for zero_rect in ( + zero_rect1, + zero_rect2, + zero_rect3, + zero_rect4, + zero_rect5, + ): + collide_items = zero_rect.collidedictall(d, use_values) + + self._assertCountEqual(collide_items, expected_items) + + def test_collidedictall__zero_sized_rects_as_args(self): + """Ensures collidedictall works correctly with zero sized rects + as args. + + There should be no collisions with zero sized rects. + """ + rect = Rect(0, 0, 20, 20) + + no_collide_item1 = ("no collide 1", Rect(2, 2, 0, 0)) + no_collide_item2 = ("no collide 2", Rect(2, 2, 2, 0)) + no_collide_item3 = ("no collide 3", Rect(2, 2, 0, 2)) + + # Dict to check collisions with values. + rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3)) + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + + expected_items = [] + + for use_values in (True, False): + d = rect_values if use_values else rect_keys + + collide_items = rect.collidedictall(d, use_values) + + self._assertCountEqual(collide_items, expected_items) + + def test_collidedictall__negative_sized_rects(self): + """Ensures collidedictall works correctly with negative sized rects.""" + neg_rect = Rect(2, 2, -2, -2) + + collide_item1 = ("collide 1", neg_rect.copy()) + collide_item2 = ("collide 2", Rect(0, 0, 20, 20)) + no_collide_item1 = ("no collide 1", Rect(2, 2, 20, 20)) + + # Dict to check collisions with values. + rect_values = dict((collide_item1, collide_item2, no_collide_item1)) + value_collide_items = [collide_item1, collide_item2] + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + key_collide_items = [(tuple(v), k) for k, v in value_collide_items] + + for use_values in (True, False): + if use_values: + expected_items = value_collide_items + d = rect_values + else: + expected_items = key_collide_items + d = rect_keys + + collide_items = neg_rect.collidedictall(d, use_values) + + self._assertCountEqual(collide_items, expected_items) + + def test_collidedictall__negative_sized_rects_as_args(self): + """Ensures collidedictall works correctly with negative sized rect + args. + """ + rect = Rect(0, 0, 10, 10) + + collide_item1 = ("collide 1", Rect(1, 1, -1, -1)) + no_collide_item1 = ("no collide 1", Rect(1, 1, -1, 0)) + no_collide_item2 = ("no collide 2", Rect(1, 1, 0, -1)) + + # Dict to check collisions with values. + rect_values = dict((collide_item1, no_collide_item1, no_collide_item2)) + value_collide_items = [collide_item1] + + # Dict to check collisions with keys. + rect_keys = {tuple(v): k for k, v in rect_values.items()} + key_collide_items = [(tuple(v), k) for k, v in value_collide_items] + + for use_values in (True, False): + if use_values: + expected_items = value_collide_items + d = rect_values + else: + expected_items = key_collide_items + d = rect_keys + + collide_items = rect.collidedictall(d, use_values) + + self._assertCountEqual(collide_items, expected_items) + + def test_collidedictall__invalid_dict_format(self): + """Ensures collidedictall correctly handles invalid dict parameters.""" + rect = Rect(0, 0, 10, 10) + + invalid_value_dict = ("collide", rect.copy()) + invalid_key_dict = tuple(invalid_value_dict[1]), invalid_value_dict[0] + + for use_values in (True, False): + d = invalid_value_dict if use_values else invalid_key_dict + + with self.assertRaises(TypeError): + collide_item = rect.collidedictall(d, use_values) + + def test_collidedictall__invalid_dict_value_format(self): + """Ensures collidedictall correctly handles dicts with invalid values.""" + rect = Rect(0, 0, 10, 10) + rect_keys = {tuple(rect): "collide"} + + with self.assertRaises(TypeError): + collide_items = rect.collidedictall(rect_keys, 1) + + def test_collidedictall__invalid_dict_key_format(self): + """Ensures collidedictall correctly handles dicts with invalid keys.""" + rect = Rect(0, 0, 10, 10) + rect_values = {"collide": rect.copy()} + + with self.assertRaises(TypeError): + collide_items = rect.collidedictall(rect_values) + + def test_collidedictall__invalid_use_values_format(self): + """Ensures collidedictall correctly handles invalid use_values + parameters. + """ + rect = Rect(0, 0, 1, 1) + d = {} + + for invalid_param in (None, d, 1.1): + with self.assertRaises(TypeError): + collide_items = rect.collidedictall(d, invalid_param) + + def test_collidelist(self): + + # __doc__ (as of 2008-08-02) for pygame.rect.Rect.collidelist: + + # Rect.collidelist(list): return index + # test if one rectangle in a list intersects + # + # Test whether the rectangle collides with any in a sequence of + # rectangles. The index of the first collision found is returned. If + # no collisions are found an index of -1 is returned. + + r = Rect(1, 1, 10, 10) + l = [Rect(50, 50, 1, 1), Rect(5, 5, 10, 10), Rect(15, 15, 1, 1)] + + self.assertEqual(r.collidelist(l), 1) + + f = [Rect(50, 50, 1, 1), (100, 100, 4, 4)] + self.assertEqual(r.collidelist(f), -1) + + def test_collidelistall(self): + + # __doc__ (as of 2008-08-02) for pygame.rect.Rect.collidelistall: + + # Rect.collidelistall(list): return indices + # test if all rectangles in a list intersect + # + # Returns a list of all the indices that contain rectangles that + # collide with the Rect. If no intersecting rectangles are found, an + # empty list is returned. + + r = Rect(1, 1, 10, 10) + + l = [ + Rect(1, 1, 10, 10), + Rect(5, 5, 10, 10), + Rect(15, 15, 1, 1), + Rect(2, 2, 1, 1), + ] + self.assertEqual(r.collidelistall(l), [0, 1, 3]) + + f = [Rect(50, 50, 1, 1), Rect(20, 20, 5, 5)] + self.assertFalse(r.collidelistall(f)) + + def test_fit(self): + + # __doc__ (as of 2008-08-02) for pygame.rect.Rect.fit: + + # Rect.fit(Rect): return Rect + # resize and move a rectangle with aspect ratio + # + # Returns a new rectangle that is moved and resized to fit another. + # The aspect ratio of the original Rect is preserved, so the new + # rectangle may be smaller than the target in either width or height. + + r = Rect(10, 10, 30, 30) + + r2 = Rect(30, 30, 15, 10) + + f = r.fit(r2) + self.assertTrue(r2.contains(f)) + + f2 = r2.fit(r) + self.assertTrue(r.contains(f2)) + + def test_copy(self): + r = Rect(1, 2, 10, 20) + c = r.copy() + self.assertEqual(c, r) + + def test_subscript(self): + r = Rect(1, 2, 3, 4) + self.assertEqual(r[0], 1) + self.assertEqual(r[1], 2) + self.assertEqual(r[2], 3) + self.assertEqual(r[3], 4) + self.assertEqual(r[-1], 4) + self.assertEqual(r[-2], 3) + self.assertEqual(r[-4], 1) + self.assertRaises(IndexError, r.__getitem__, 5) + self.assertRaises(IndexError, r.__getitem__, -5) + self.assertEqual(r[0:2], [1, 2]) + self.assertEqual(r[0:4], [1, 2, 3, 4]) + self.assertEqual(r[0:-1], [1, 2, 3]) + self.assertEqual(r[:], [1, 2, 3, 4]) + self.assertEqual(r[...], [1, 2, 3, 4]) + self.assertEqual(r[0:4:2], [1, 3]) + self.assertEqual(r[0:4:3], [1, 4]) + self.assertEqual(r[3::-1], [4, 3, 2, 1]) + self.assertRaises(TypeError, r.__getitem__, None) + + def test_ass_subscript(self): + r = Rect(0, 0, 0, 0) + r[...] = 1, 2, 3, 4 + self.assertEqual(r, [1, 2, 3, 4]) + self.assertRaises(TypeError, r.__setitem__, None, 0) + self.assertEqual(r, [1, 2, 3, 4]) + self.assertRaises(TypeError, r.__setitem__, 0, "") + self.assertEqual(r, [1, 2, 3, 4]) + self.assertRaises(IndexError, r.__setitem__, 4, 0) + self.assertEqual(r, [1, 2, 3, 4]) + self.assertRaises(IndexError, r.__setitem__, -5, 0) + self.assertEqual(r, [1, 2, 3, 4]) + r[0] = 10 + self.assertEqual(r, [10, 2, 3, 4]) + r[3] = 40 + self.assertEqual(r, [10, 2, 3, 40]) + r[-1] = 400 + self.assertEqual(r, [10, 2, 3, 400]) + r[-4] = 100 + self.assertEqual(r, [100, 2, 3, 400]) + r[1:3] = 0 + self.assertEqual(r, [100, 0, 0, 400]) + r[...] = 0 + self.assertEqual(r, [0, 0, 0, 0]) + r[:] = 9 + self.assertEqual(r, [9, 9, 9, 9]) + r[:] = 11, 12, 13, 14 + self.assertEqual(r, [11, 12, 13, 14]) + r[::-1] = r + self.assertEqual(r, [14, 13, 12, 11]) + + +@unittest.skipIf(IS_PYPY, "fails on pypy") +class SubclassTest(unittest.TestCase): + class MyRect(Rect): + def __init__(self, *args, **kwds): + super(SubclassTest.MyRect, self).__init__(*args, **kwds) + self.an_attribute = True + + def test_copy(self): + mr1 = self.MyRect(1, 2, 10, 20) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.copy() + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + def test_move(self): + mr1 = self.MyRect(1, 2, 10, 20) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.move(1, 2) + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + def test_inflate(self): + mr1 = self.MyRect(1, 2, 10, 20) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.inflate(2, 4) + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + def test_clamp(self): + mr1 = self.MyRect(19, 12, 5, 5) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.clamp(Rect(10, 10, 10, 10)) + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + def test_clip(self): + mr1 = self.MyRect(1, 2, 3, 4) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.clip(Rect(0, 0, 3, 4)) + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + def test_union(self): + mr1 = self.MyRect(1, 1, 1, 2) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.union(Rect(-2, -2, 1, 2)) + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + def test_unionall(self): + mr1 = self.MyRect(0, 0, 1, 1) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.unionall([Rect(-2, -2, 1, 1), Rect(2, 2, 1, 1)]) + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + def test_fit(self): + mr1 = self.MyRect(10, 10, 30, 30) + self.assertTrue(mr1.an_attribute) + mr2 = mr1.fit(Rect(30, 30, 15, 10)) + self.assertTrue(isinstance(mr2, self.MyRect)) + self.assertRaises(AttributeError, getattr, mr2, "an_attribute") + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f899ca0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__pycache__/run_tests__test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__pycache__/run_tests__test.cpython-38.pyc new file mode 100644 index 0000000..fc69492 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/__pycache__/run_tests__test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ee9cb25 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..08faf46 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_3_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_3_test.cpython-38.pyc new file mode 100644 index 0000000..ea69bd8 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_3_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_4_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_4_test.cpython-38.pyc new file mode 100644 index 0000000..390e2a0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_4_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_5_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_5_test.cpython-38.pyc new file mode 100644 index 0000000..e72aa2a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_5_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_6_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_6_test.cpython-38.pyc new file mode 100644 index 0000000..4aa8d85 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/fake_6_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/no_assertions__ret_code_of_1__test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/no_assertions__ret_code_of_1__test.cpython-38.pyc new file mode 100644 index 0000000..ce6c994 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/no_assertions__ret_code_of_1__test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/zero_tests_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/zero_tests_test.cpython-38.pyc new file mode 100644 index 0000000..c93238c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/__pycache__/zero_tests_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_3_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_3_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_3_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_4_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_4_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_4_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_5_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_5_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_5_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_6_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_6_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/fake_6_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/no_assertions__ret_code_of_1__test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/no_assertions__ret_code_of_1__test.py new file mode 100644 index 0000000..0ba0e94 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/no_assertions__ret_code_of_1__test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + pass + + def test_get_mods(self): + pass + + def test_get_pressed(self): + pass + + def test_name(self): + pass + + def test_set_mods(self): + pass + + def test_set_repeat(self): + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/zero_tests_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/zero_tests_test.py new file mode 100644 index 0000000..649055a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/all_ok/zero_tests_test.py @@ -0,0 +1,23 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d223d88 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..f6261f3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/incomplete_todo_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/incomplete_todo_test.cpython-38.pyc new file mode 100644 index 0000000..12efd74 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/incomplete_todo_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/magic_tag_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/magic_tag_test.cpython-38.pyc new file mode 100644 index 0000000..e94bb57 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/magic_tag_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/sleep_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/sleep_test.cpython-38.pyc new file mode 100644 index 0000000..dc4dcc4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/__pycache__/sleep_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/incomplete_todo_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/incomplete_todo_test.py new file mode 100644 index 0000000..bdd8a3b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/incomplete_todo_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def todo_test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def todo_test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/magic_tag_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/magic_tag_test.py new file mode 100644 index 0000000..126bc2b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/magic_tag_test.py @@ -0,0 +1,38 @@ +__tags__ = ["magic"] + +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/sleep_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/sleep_test.py new file mode 100644 index 0000000..468c75f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/everything/sleep_test.py @@ -0,0 +1,29 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + +import time + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + stop_time = time.time() + 10.0 + while time.time() < stop_time: + time.sleep(1) + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..fd5d556 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..a7050ab Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/invisible_tag_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/invisible_tag_test.cpython-38.pyc new file mode 100644 index 0000000..a73d2bd Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/invisible_tag_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/magic_tag_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/magic_tag_test.cpython-38.pyc new file mode 100644 index 0000000..5de6475 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/__pycache__/magic_tag_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/invisible_tag_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/invisible_tag_test.py new file mode 100644 index 0000000..3ef959a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/invisible_tag_test.py @@ -0,0 +1,41 @@ +__tags__ = ["invisible"] + +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/magic_tag_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/magic_tag_test.py new file mode 100644 index 0000000..126bc2b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/exclude/magic_tag_test.py @@ -0,0 +1,38 @@ +__tags__ = ["magic"] + +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3f54af3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..9c4b3e6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_3_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_3_test.cpython-38.pyc new file mode 100644 index 0000000..e3f08c9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_3_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_4_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_4_test.cpython-38.pyc new file mode 100644 index 0000000..071ca47 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/__pycache__/fake_4_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_3_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_3_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_3_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_4_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_4_test.py new file mode 100644 index 0000000..1e75fea --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/failures1/fake_4_test.py @@ -0,0 +1,41 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(False, "Some Jibberish") + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + if 1: + if 1: + assert False + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d4f1a69 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..824b3c9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/fake_3_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/fake_3_test.cpython-38.pyc new file mode 100644 index 0000000..7d41802 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/__pycache__/fake_3_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/fake_2_test.py new file mode 100644 index 0000000..b88f1ae --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def todo_test_get_pressed(self): + self.fail() + + def test_name(self): + self.assertTrue(True) + + def todo_test_set_mods(self): + self.fail() + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/fake_3_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/fake_3_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete/fake_3_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..afaacc0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..d47c1d2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_3_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_3_test.cpython-38.pyc new file mode 100644 index 0000000..8240798 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/__pycache__/fake_3_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/fake_2_test.py new file mode 100644 index 0000000..bdd8a3b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def todo_test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def todo_test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/fake_3_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/fake_3_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/incomplete_todo/fake_3_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..83ee06f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_1_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_1_test.cpython-38.pyc new file mode 100644 index 0000000..24ec40b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_1_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..d7ea7cc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/fake_1_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/fake_1_test.py new file mode 100644 index 0000000..3e9e936 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/fake_1_test.py @@ -0,0 +1,40 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + while True: + pass + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/infinite_loop/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b08eca2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..574d10b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_3_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_3_test.cpython-38.pyc new file mode 100644 index 0000000..746b5b4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_3_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_4_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_4_test.cpython-38.pyc new file mode 100644 index 0000000..fec8976 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/__pycache__/fake_4_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_3_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_3_test.py new file mode 100644 index 0000000..f59ad40 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_3_test.py @@ -0,0 +1,41 @@ +import sys + +if __name__ == "__main__": + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + sys.stderr.write("jibberish messes things up\n") + self.assertTrue(False) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_4_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_4_test.py new file mode 100644 index 0000000..1e75fea --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stderr/fake_4_test.py @@ -0,0 +1,41 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(False, "Some Jibberish") + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + if 1: + if 1: + assert False + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..09865f0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..8b41d7b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_3_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_3_test.cpython-38.pyc new file mode 100644 index 0000000..06b25d3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_3_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_4_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_4_test.cpython-38.pyc new file mode 100644 index 0000000..62de5e2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/__pycache__/fake_4_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_3_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_3_test.py new file mode 100644 index 0000000..467c725 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_3_test.py @@ -0,0 +1,42 @@ +import sys + +if __name__ == "__main__": + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + sys.stdout.write("jibberish ruins everything\n") + self.assertTrue(False) + + def test_name(self): + sys.stdout.write("forgot to remove debug crap\n") + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_4_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_4_test.py new file mode 100644 index 0000000..1e75fea --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/print_stdout/fake_4_test.py @@ -0,0 +1,41 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(False, "Some Jibberish") + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + if 1: + if 1: + assert False + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/run_tests__test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/run_tests__test.py new file mode 100644 index 0000000..533f7a0 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/run_tests__test.py @@ -0,0 +1,145 @@ +################################################################################ + +import subprocess, os, sys, re, difflib + +################################################################################ + +IGNORE = (".svn", "infinite_loop") +NORMALIZERS = ( + (r"Ran (\d+) tests in (\d+\.\d+)s", "Ran \\1 tests in X.XXXs"), + (r'File ".*?([^/\\.]+\.py)"', 'File "\\1"'), +) + +################################################################################ + + +def norm_result(result): + "normalize differences, such as timing between output" + for normalizer, replacement in NORMALIZERS: + if hasattr(normalizer, "__call__"): + result = normalizer(result) + else: + result = re.sub(normalizer, replacement, result) + + return result + + +def call_proc(cmd, cd=None): + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=cd, + universal_newlines=True, + ) + if proc.wait(): + print("%s %s" % (cmd, proc.wait())) + raise Exception(proc.stdout.read()) + + return proc.stdout.read() + + +################################################################################ + +unnormed_diff = "-u" in sys.argv +verbose = "-v" in sys.argv or unnormed_diff +if "-h" in sys.argv or "--help" in sys.argv: + sys.exit( + "\nCOMPARES OUTPUT OF SINGLE VS SUBPROCESS MODE OF RUN_TESTS.PY\n\n" + "-v, to output diffs even on success\n" + "-u, to output diffs of unnormalized tests\n\n" + "Each line of a Differ delta begins with a two-letter code:\n\n" + " '- ' line unique to sequence 1\n" + " '+ ' line unique to sequence 2\n" + " ' ' line common to both sequences\n" + " '? ' line not present in either input sequence\n" + ) + +main_dir = os.path.split(os.path.abspath(sys.argv[0]))[0] +trunk_dir = os.path.normpath(os.path.join(main_dir, "../../")) + +test_suite_dirs = [ + x + for x in os.listdir(main_dir) + if os.path.isdir(os.path.join(main_dir, x)) and x not in IGNORE +] + + +################################################################################ + + +def assert_on_results(suite, single, sub): + test = globals().get("%s_test" % suite) + if hasattr(test, "__call_"): + test(suite, single, sub) + print("assertions on %s OK" % (suite,)) + + +# Don't modify tests in suites below. These assertions are in place to make sure +# that tests are actually being ran + + +def all_ok_test(uite, *args): + for results in args: + assert "Ran 36 tests" in results # some tests are runing + assert "OK" in results # OK + + +def failures1_test(suite, *args): + for results in args: + assert "FAILED (failures=2)" in results + assert "Ran 18 tests" in results + + +################################################################################ +# Test that output is the same in single process and subprocess modes +# + +base_cmd = [sys.executable, "run_tests.py", "-i"] + +cmd = base_cmd + ["-n", "-f"] +sub_cmd = base_cmd + ["-f"] +time_out_cmd = base_cmd + ["-t", "4", "-f", "infinite_loop"] + +passes = 0 +failed = False + +for suite in test_suite_dirs: + single = call_proc(cmd + [suite], trunk_dir) + subs = call_proc(sub_cmd + [suite], trunk_dir) + + normed_single, normed_subs = map(norm_result, (single, subs)) + + failed = normed_single != normed_subs + if failed: + print("%s suite comparison FAILED\n" % (suite,)) + else: + passes += 1 + print("%s suite comparison OK" % (suite,)) + + assert_on_results(suite, single, subs) + + if verbose or failed: + print("difflib.Differ().compare(single, suprocessed):\n") + print( + "".join( + list( + difflib.Differ().compare( + (unnormed_diff and single or normed_single).splitlines(1), + (unnormed_diff and subs or normed_subs).splitlines(1), + ) + ) + ) + ) + +sys.stdout.write("infinite_loop suite (subprocess mode timeout) ") +loop_test = call_proc(time_out_cmd, trunk_dir) +assert "successfully terminated" in loop_test +passes += 1 +print("OK") + +print("\n%s/%s suites pass" % (passes, len(test_suite_dirs) + 1)) + +print("\n-h for help") + +################################################################################ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..db48a50 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/fake_2_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/fake_2_test.cpython-38.pyc new file mode 100644 index 0000000..d84872c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/fake_2_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/sleep_test.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/sleep_test.cpython-38.pyc new file mode 100644 index 0000000..a409da4 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/__pycache__/sleep_test.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/fake_2_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/fake_2_test.py new file mode 100644 index 0000000..3be92e1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/fake_2_test.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + self.assertTrue(True) + + def test_get_mods(self): + self.assertTrue(True) + + def test_get_pressed(self): + self.assertTrue(True) + + def test_name(self): + self.assertTrue(True) + + def test_set_mods(self): + self.assertTrue(True) + + def test_set_repeat(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/sleep_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/sleep_test.py new file mode 100644 index 0000000..bab528a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/run_tests__tests/timeout/sleep_test.py @@ -0,0 +1,30 @@ +if __name__ == "__main__": + import sys + import os + + pkg_dir = os.path.split( + os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + )[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest + +import time + + +class KeyModuleTest(unittest.TestCase): + def test_get_focused(self): + stop_time = time.time() + 10.0 + while time.time() < stop_time: + time.sleep(1) + + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/rwobject_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/rwobject_test.py new file mode 100644 index 0000000..31723ae --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/rwobject_test.py @@ -0,0 +1,139 @@ +import pathlib +import unittest + +from pygame import encode_string, encode_file_path + + +class RWopsEncodeStringTest(unittest.TestCase): + global getrefcount + + def test_obj_None(self): + encoded_string = encode_string(None) + + self.assertIsNone(encoded_string) + + def test_returns_bytes(self): + u = "Hello" + encoded_string = encode_string(u) + + self.assertIsInstance(encoded_string, bytes) + + def test_obj_bytes(self): + b = b"encyclop\xE6dia" + encoded_string = encode_string(b, "ascii", "strict") + + self.assertIs(encoded_string, b) + + def test_encode_unicode(self): + u = "\u00DEe Olde Komp\u00FCter Shoppe" + b = u.encode("utf-8") + self.assertEqual(encode_string(u, "utf-8"), b) + + def test_error_fowarding(self): + self.assertRaises(SyntaxError, encode_string) + + def test_errors(self): + u = "abc\u0109defg\u011Dh\u0125ij\u0135klmnoprs\u015Dtu\u016Dvz" + b = u.encode("ascii", "ignore") + self.assertEqual(encode_string(u, "ascii", "ignore"), b) + + def test_encoding_error(self): + u = "a\x80b" + encoded_string = encode_string(u, "ascii", "strict") + + self.assertIsNone(encoded_string) + + def test_check_defaults(self): + u = "a\u01F7b" + b = u.encode("unicode_escape", "backslashreplace") + encoded_string = encode_string(u) + + self.assertEqual(encoded_string, b) + + def test_etype(self): + u = "a\x80b" + self.assertRaises(SyntaxError, encode_string, u, "ascii", "strict", SyntaxError) + + def test_etype__invalid(self): + """Ensures invalid etypes are properly handled.""" + + for etype in ("SyntaxError", self): + self.assertRaises(TypeError, encode_string, "test", etype=etype) + + def test_string_with_null_bytes(self): + b = b"a\x00b\x00c" + encoded_string = encode_string(b, etype=SyntaxError) + encoded_decode_string = encode_string(b.decode(), "ascii", "strict") + + self.assertIs(encoded_string, b) + self.assertEqual(encoded_decode_string, b) + + try: + from sys import getrefcount as _g + + getrefcount = _g # This nonsense is for Python 3.x + except ImportError: + pass + else: + + def test_refcount(self): + bpath = b" This is a string that is not cached."[1:] + upath = bpath.decode("ascii") + before = getrefcount(bpath) + bpath = encode_string(bpath) + self.assertEqual(getrefcount(bpath), before) + bpath = encode_string(upath) + self.assertEqual(getrefcount(bpath), before) + + def test_smp(self): + utf_8 = b"a\xF0\x93\x82\xA7b" + u = "a\U000130A7b" + b = encode_string(u, "utf-8", "strict", AssertionError) + self.assertEqual(b, utf_8) + + def test_pathlib_obj(self): + """Test loading string representation of pathlib object""" + """ + We do this because pygame functions internally use pg_EncodeString + to decode the filenames passed to them. So if we test that here, we + can safely assume that all those functions do not have any issues + with pathlib objects + """ + encoded = encode_string(pathlib.PurePath("foo"), "utf-8") + self.assertEqual(encoded, b"foo") + + encoded = encode_string(pathlib.Path("baz")) + self.assertEqual(encoded, b"baz") + + +class RWopsEncodeFilePathTest(unittest.TestCase): + # Most tests can be skipped since RWopsEncodeFilePath wraps + # RWopsEncodeString + def test_encoding(self): + u = "Hello" + encoded_file_path = encode_file_path(u) + + self.assertIsInstance(encoded_file_path, bytes) + + def test_error_fowarding(self): + self.assertRaises(SyntaxError, encode_file_path) + + def test_path_with_null_bytes(self): + b = b"a\x00b\x00c" + encoded_file_path = encode_file_path(b) + + self.assertIsNone(encoded_file_path) + + def test_etype(self): + b = b"a\x00b\x00c" + self.assertRaises(TypeError, encode_file_path, b, TypeError) + + def test_etype__invalid(self): + """Ensures invalid etypes are properly handled.""" + + for etype in ("SyntaxError", self): + self.assertRaises(TypeError, encode_file_path, "test", etype) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/scrap_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/scrap_tags.py new file mode 100644 index 0000000..17a82ff --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/scrap_tags.py @@ -0,0 +1,26 @@ +__tags__ = ["ignore", "subprocess_ignore"] + +# TODO: make scrap_test.py work +# This test used to work only on linux and windows. +# Currently it only work in windows, and in linux it throws: +# `pygame.error: content could not be placed in clipboard.` +# The old test and tags kept here for reference when fixing. + +# import sys +# +# exclude = False +# +# if sys.platform == "win32" or sys.platform.startswith("linux"): +# try: +# import pygame +# +# pygame.scrap._NOT_IMPLEMENTED_ +# except AttributeError: +# pass +# else: +# exclude = True +# else: +# exclude = True +# +# if exclude: +# __tags__.extend(["ignore", "subprocess_ignore"]) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/scrap_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/scrap_test.py new file mode 100644 index 0000000..6b7f6fa --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/scrap_test.py @@ -0,0 +1,301 @@ +import os +import sys + +if os.environ.get("SDL_VIDEODRIVER") == "dummy": + __tags__ = ("ignore", "subprocess_ignore") +import unittest +from pygame.tests.test_utils import trunk_relative_path + +import pygame +from pygame import scrap + + +class ScrapModuleTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + pygame.display.init() + pygame.display.set_mode((1, 1)) + scrap.init() + + @classmethod + def tearDownClass(cls): + # scrap.quit() # Does not exist! + pygame.display.quit() + + def test_init(self): + """Ensures scrap module still initialized after multiple init calls.""" + scrap.init() + scrap.init() + + self.assertTrue(scrap.get_init()) + + def test_init__reinit(self): + """Ensures reinitializing the scrap module doesn't clear its data.""" + data_type = pygame.SCRAP_TEXT + expected_data = b"test_init__reinit" + scrap.put(data_type, expected_data) + + scrap.init() + + self.assertEqual(scrap.get(data_type), expected_data) + + def test_get_init(self): + """Ensures get_init gets the init state.""" + self.assertTrue(scrap.get_init()) + + def todo_test_contains(self): + """Ensures contains works as expected.""" + self.fail() + + def todo_test_get(self): + """Ensures get works as expected.""" + self.fail() + + def test_get__owned_empty_type(self): + """Ensures get works when there is no data of the requested type + in the clipboard and the clipboard is owned by the pygame application. + """ + # Use a unique data type identifier to ensure there is no preexisting + # data. + DATA_TYPE = "test_get__owned_empty_type" + + if scrap.lost(): + # Try to acquire the clipboard. + scrap.put(pygame.SCRAP_TEXT, b"text to clipboard") + + if scrap.lost(): + self.skipTest("requires the pygame application to own the clipboard") + + data = scrap.get(DATA_TYPE) + + self.assertIsNone(data) + + def todo_test_get_types(self): + """Ensures get_types works as expected.""" + self.fail() + + def todo_test_lost(self): + """Ensures lost works as expected.""" + self.fail() + + def test_set_mode(self): + """Ensures set_mode works as expected.""" + scrap.set_mode(pygame.SCRAP_SELECTION) + scrap.set_mode(pygame.SCRAP_CLIPBOARD) + + self.assertRaises(ValueError, scrap.set_mode, 1099) + + def test_put__text(self): + """Ensures put can place text into the clipboard.""" + scrap.put(pygame.SCRAP_TEXT, b"Hello world") + + self.assertEqual(scrap.get(pygame.SCRAP_TEXT), b"Hello world") + + scrap.put(pygame.SCRAP_TEXT, b"Another String") + + self.assertEqual(scrap.get(pygame.SCRAP_TEXT), b"Another String") + + @unittest.skipIf("pygame.image" not in sys.modules, "requires pygame.image module") + def test_put__bmp_image(self): + """Ensures put can place a BMP image into the clipboard.""" + sf = pygame.image.load(trunk_relative_path("examples/data/asprite.bmp")) + expected_string = pygame.image.tostring(sf, "RGBA") + scrap.put(pygame.SCRAP_BMP, expected_string) + + self.assertEqual(scrap.get(pygame.SCRAP_BMP), expected_string) + + def test_put(self): + """Ensures put can place data into the clipboard + when using a user defined type identifier. + """ + DATA_TYPE = "arbitrary buffer" + + scrap.put(DATA_TYPE, b"buf") + r = scrap.get(DATA_TYPE) + + self.assertEqual(r, b"buf") + + +class ScrapModuleClipboardNotOwnedTest(unittest.TestCase): + """Test the scrap module's functionality when the pygame application is + not the current owner of the clipboard. + + A separate class is used to prevent tests that acquire the clipboard from + interfering with these tests. + """ + + @classmethod + def setUpClass(cls): + pygame.display.init() + pygame.display.set_mode((1, 1)) + scrap.init() + + @classmethod + def tearDownClass(cls): + # scrap.quit() # Does not exist! + pygame.quit() + pygame.display.quit() + + def _skip_if_clipboard_owned(self): + # Skip test if the pygame application owns the clipboard. Currently, + # there is no way to give up ownership. + if not scrap.lost(): + self.skipTest("requires the pygame application to not own the clipboard") + + def test_get__not_owned(self): + """Ensures get works when there is no data of the requested type + in the clipboard and the clipboard is not owned by the pygame + application. + """ + self._skip_if_clipboard_owned() + + # Use a unique data type identifier to ensure there is no preexisting + # data. + DATA_TYPE = "test_get__not_owned" + + data = scrap.get(DATA_TYPE) + + self.assertIsNone(data) + + def test_get_types__not_owned(self): + """Ensures get_types works when the clipboard is not owned + by the pygame application. + """ + self._skip_if_clipboard_owned() + + data_types = scrap.get_types() + + self.assertIsInstance(data_types, list) + + def test_contains__not_owned(self): + """Ensures contains works when the clipboard is not owned + by the pygame application. + """ + self._skip_if_clipboard_owned() + + # Use a unique data type identifier to ensure there is no preexisting + # data. + DATA_TYPE = "test_contains__not_owned" + + contains = scrap.contains(DATA_TYPE) + + self.assertFalse(contains) + + def test_lost__not_owned(self): + """Ensures lost works when the clipboard is not owned + by the pygame application. + """ + self._skip_if_clipboard_owned() + + lost = scrap.lost() + + self.assertTrue(lost) + + +class X11InteractiveTest(unittest.TestCase): + __tags__ = ["ignore", "subprocess_ignore"] + try: + pygame.display.init() + except Exception: + pass + else: + if pygame.display.get_driver() == "x11": + __tags__ = ["interactive"] + pygame.display.quit() + + def test_issue_208(self): + """PATCH: pygame.scrap on X11, fix copying into PRIMARY selection + + Copying into theX11 PRIMARY selection (mouse copy/paste) would not + work due to a confusion between content type and clipboard type. + + """ + + from pygame import display, event, freetype + from pygame.locals import SCRAP_SELECTION, SCRAP_TEXT + from pygame.locals import KEYDOWN, K_y, QUIT + + success = False + freetype.init() + font = freetype.Font(None, 24) + display.init() + display.set_caption("Interactive X11 Paste Test") + screen = display.set_mode((600, 200)) + screen.fill(pygame.Color("white")) + text = "Scrap put() succeeded." + msg = ( + "Some text has been placed into the X11 clipboard." + " Please click the center mouse button in an open" + " text window to retrieve it." + '\n\nDid you get "{}"? (y/n)' + ).format(text) + word_wrap(screen, msg, font, 6) + display.flip() + event.pump() + scrap.init() + scrap.set_mode(SCRAP_SELECTION) + scrap.put(SCRAP_TEXT, text.encode("UTF-8")) + while True: + e = event.wait() + if e.type == QUIT: + break + if e.type == KEYDOWN: + success = e.key == K_y + break + pygame.display.quit() + self.assertTrue(success) + + +def word_wrap(surf, text, font, margin=0, color=(0, 0, 0)): + font.origin = True + surf_width, surf_height = surf.get_size() + width = surf_width - 2 * margin + height = surf_height - 2 * margin + line_spacing = int(1.25 * font.get_sized_height()) + x, y = margin, margin + line_spacing + space = font.get_rect(" ") + for word in iwords(text): + if word == "\n": + x, y = margin, y + line_spacing + else: + bounds = font.get_rect(word) + if x + bounds.width + bounds.x >= width: + x, y = margin, y + line_spacing + if x + bounds.width + bounds.x >= width: + raise ValueError("word too wide for the surface") + if y + bounds.height - bounds.y >= height: + raise ValueError("text to long for the surface") + font.render_to(surf, (x, y), None, color) + x += bounds.width + space.width + return x, y + + +def iwords(text): + # r"\n|[^ ]+" + # + head = 0 + tail = head + end = len(text) + while head < end: + if text[head] == " ": + head += 1 + tail = head + 1 + elif text[head] == "\n": + head += 1 + yield "\n" + tail = head + 1 + elif tail == end: + yield text[head:] + head = end + elif text[tail] == "\n": + yield text[head:tail] + head = tail + elif text[tail] == " ": + yield text[head:tail] + head = tail + else: + tail += 1 + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/sndarray_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/sndarray_tags.py new file mode 100644 index 0000000..68fa7a5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/sndarray_tags.py @@ -0,0 +1,12 @@ +__tags__ = ["array"] + +exclude = False + +try: + import pygame.mixer + import numpy +except ImportError: + exclude = True + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/sndarray_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/sndarray_test.py new file mode 100644 index 0000000..afa94ec --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/sndarray_test.py @@ -0,0 +1,155 @@ +import unittest + +from numpy import int8, int16, uint8, uint16, float32, array, alltrue + +import pygame +import pygame.sndarray + + +class SndarrayTest(unittest.TestCase): + array_dtypes = {8: uint8, -8: int8, 16: uint16, -16: int16, 32: float32} + + def _assert_compatible(self, arr, size): + dtype = self.array_dtypes[size] + self.assertEqual(arr.dtype, dtype) + + def test_array(self): + def check_array(size, channels, test_data): + try: + pygame.mixer.init(22050, size, channels, allowedchanges=0) + except pygame.error: + # Not all sizes are supported on all systems. + return + try: + __, sz, __ = pygame.mixer.get_init() + if sz == size: + srcarr = array(test_data, self.array_dtypes[size]) + snd = pygame.sndarray.make_sound(srcarr) + arr = pygame.sndarray.array(snd) + self._assert_compatible(arr, size) + self.assertTrue( + alltrue(arr == srcarr), + "size: %i\n%s\n%s" % (size, arr, test_data), + ) + finally: + pygame.mixer.quit() + + check_array(8, 1, [0, 0x0F, 0xF0, 0xFF]) + check_array(8, 2, [[0, 0x80], [0x2D, 0x41], [0x64, 0xA1], [0xFF, 0x40]]) + check_array(16, 1, [0, 0x00FF, 0xFF00, 0xFFFF]) + check_array( + 16, 2, [[0, 0xFFFF], [0xFFFF, 0], [0x00FF, 0xFF00], [0x0F0F, 0xF0F0]] + ) + check_array(-8, 1, [0, -0x80, 0x7F, 0x64]) + check_array(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0xFF, 0]]) + check_array(-16, 1, [0, 0x7FFF, -0x7FFF, -1]) + check_array(-16, 2, [[0, -0x7FFF], [-0x7FFF, 0], [0x7FFF, 0], [0, 0x7FFF]]) + + def test_get_arraytype(self): + array_type = pygame.sndarray.get_arraytype() + + self.assertEqual(array_type, "numpy", "unknown array type %s" % array_type) + + def test_get_arraytypes(self): + arraytypes = pygame.sndarray.get_arraytypes() + self.assertIn("numpy", arraytypes) + + for atype in arraytypes: + self.assertEqual(atype, "numpy", "unknown array type %s" % atype) + + def test_make_sound(self): + def check_sound(size, channels, test_data): + try: + pygame.mixer.init(22050, size, channels, allowedchanges=0) + except pygame.error: + # Not all sizes are supported on all systems. + return + try: + __, sz, __ = pygame.mixer.get_init() + if sz == size: + srcarr = array(test_data, self.array_dtypes[size]) + snd = pygame.sndarray.make_sound(srcarr) + arr = pygame.sndarray.samples(snd) + self.assertTrue( + alltrue(arr == srcarr), + "size: %i\n%s\n%s" % (size, arr, test_data), + ) + finally: + pygame.mixer.quit() + + check_sound(8, 1, [0, 0x0F, 0xF0, 0xFF]) + check_sound(8, 2, [[0, 0x80], [0x2D, 0x41], [0x64, 0xA1], [0xFF, 0x40]]) + check_sound(16, 1, [0, 0x00FF, 0xFF00, 0xFFFF]) + check_sound( + 16, 2, [[0, 0xFFFF], [0xFFFF, 0], [0x00FF, 0xFF00], [0x0F0F, 0xF0F0]] + ) + check_sound(-8, 1, [0, -0x80, 0x7F, 0x64]) + check_sound(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0xFF, 0]]) + check_sound(-16, 1, [0, 0x7FFF, -0x7FFF, -1]) + check_sound(-16, 2, [[0, -0x7FFF], [-0x7FFF, 0], [0x7FFF, 0], [0, 0x7FFF]]) + check_sound(32, 2, [[0.0, -1.0], [-1.0, 0], [1.0, 0], [0, 1.0]]) + + def test_samples(self): + + null_byte = b"\x00" + + def check_sample(size, channels, test_data): + try: + pygame.mixer.init(22050, size, channels, allowedchanges=0) + except pygame.error: + # Not all sizes are supported on all systems. + return + try: + __, sz, __ = pygame.mixer.get_init() + if sz == size: + zeroed = null_byte * ((abs(size) // 8) * len(test_data) * channels) + snd = pygame.mixer.Sound(buffer=zeroed) + samples = pygame.sndarray.samples(snd) + self._assert_compatible(samples, size) + ##print ('X %s' % (samples.shape,)) + ##print ('Y %s' % (test_data,)) + samples[...] = test_data + arr = pygame.sndarray.array(snd) + self.assertTrue( + alltrue(samples == arr), + "size: %i\n%s\n%s" % (size, arr, test_data), + ) + finally: + pygame.mixer.quit() + + check_sample(8, 1, [0, 0x0F, 0xF0, 0xFF]) + check_sample(8, 2, [[0, 0x80], [0x2D, 0x41], [0x64, 0xA1], [0xFF, 0x40]]) + check_sample(16, 1, [0, 0x00FF, 0xFF00, 0xFFFF]) + check_sample( + 16, 2, [[0, 0xFFFF], [0xFFFF, 0], [0x00FF, 0xFF00], [0x0F0F, 0xF0F0]] + ) + check_sample(-8, 1, [0, -0x80, 0x7F, 0x64]) + check_sample(-8, 2, [[0, -0x80], [-0x64, 0x64], [0x25, -0x50], [0xFF, 0]]) + check_sample(-16, 1, [0, 0x7FFF, -0x7FFF, -1]) + check_sample(-16, 2, [[0, -0x7FFF], [-0x7FFF, 0], [0x7FFF, 0], [0, 0x7FFF]]) + check_sample(32, 2, [[0.0, -1.0], [-1.0, 0], [1.0, 0], [0, 1.0]]) + + def test_use_arraytype(self): + def do_use_arraytype(atype): + pygame.sndarray.use_arraytype(atype) + + pygame.sndarray.use_arraytype("numpy") + self.assertEqual(pygame.sndarray.get_arraytype(), "numpy") + + self.assertRaises(ValueError, do_use_arraytype, "not an option") + + def test_float32(self): + """sized arrays work with Sounds and 32bit float arrays.""" + try: + pygame.mixer.init(22050, 32, 2, allowedchanges=0) + except pygame.error: + # Not all sizes are supported on all systems. + self.skipTest("unsupported mixer configuration") + + arr = array([[0.0, -1.0], [-1.0, 0], [1.0, 0], [0, 1.0]], float32) + newsound = pygame.mixer.Sound(array=arr) + pygame.mixer.quit() + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/sprite_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/sprite_test.py new file mode 100644 index 0000000..b0b099a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/sprite_test.py @@ -0,0 +1,1403 @@ +#################################### IMPORTS ################################### +# -*- encoding: utf-8 -*- + + +import unittest + +import pygame +from pygame import sprite + + +################################# MODULE LEVEL ################################# + + +class SpriteModuleTest(unittest.TestCase): + pass + + +######################### SPRITECOLLIDE FUNCTIONS TEST ######################### + + +class SpriteCollideTest(unittest.TestCase): + def setUp(self): + self.ag = sprite.AbstractGroup() + self.ag2 = sprite.AbstractGroup() + self.s1 = sprite.Sprite(self.ag) + self.s2 = sprite.Sprite(self.ag2) + self.s3 = sprite.Sprite(self.ag2) + + self.s1.image = pygame.Surface((50, 10), pygame.SRCALPHA, 32) + self.s2.image = pygame.Surface((10, 10), pygame.SRCALPHA, 32) + self.s3.image = pygame.Surface((10, 10), pygame.SRCALPHA, 32) + + self.s1.rect = self.s1.image.get_rect() + self.s2.rect = self.s2.image.get_rect() + self.s3.rect = self.s3.image.get_rect() + self.s2.rect.move_ip(40, 0) + self.s3.rect.move_ip(100, 100) + + def test_spritecollide__works_if_collided_cb_is_None(self): + # Test that sprites collide without collided function. + self.assertEqual( + sprite.spritecollide(self.s1, self.ag2, dokill=False, collided=None), + [self.s2], + ) + + def test_spritecollide__works_if_collided_cb_not_passed(self): + # Should also work when collided function isn't passed at all. + self.assertEqual( + sprite.spritecollide(self.s1, self.ag2, dokill=False), [self.s2] + ) + + def test_spritecollide__collided_must_be_a_callable(self): + # Need to pass a callable. + self.assertRaises( + TypeError, sprite.spritecollide, self.s1, self.ag2, dokill=False, collided=1 + ) + + def test_spritecollide__collided_defaults_to_collide_rect(self): + # collide_rect should behave the same as default. + self.assertEqual( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=sprite.collide_rect + ), + [self.s2], + ) + + def test_collide_rect_ratio__ratio_of_one_like_default(self): + # collide_rect_ratio should behave the same as default at a 1.0 ratio. + self.assertEqual( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=sprite.collide_rect_ratio(1.0) + ), + [self.s2], + ) + + def test_collide_rect_ratio__collides_all_at_ratio_of_twenty(self): + # collide_rect_ratio should collide all at a 20.0 ratio. + collided_func = sprite.collide_rect_ratio(20.0) + expected_sprites = sorted(self.ag2.sprites(), key=id) + + collided_sprites = sorted( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=collided_func + ), + key=id, + ) + + self.assertListEqual(collided_sprites, expected_sprites) + + def test_collide_circle__no_radius_set(self): + # collide_circle with no radius set. + self.assertEqual( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=sprite.collide_circle + ), + [self.s2], + ) + + def test_collide_circle_ratio__no_radius_and_ratio_of_one(self): + # collide_circle_ratio with no radius set, at a 1.0 ratio. + self.assertEqual( + sprite.spritecollide( + self.s1, + self.ag2, + dokill=False, + collided=sprite.collide_circle_ratio(1.0), + ), + [self.s2], + ) + + def test_collide_circle_ratio__no_radius_and_ratio_of_twenty(self): + # collide_circle_ratio with no radius set, at a 20.0 ratio. + collided_func = sprite.collide_circle_ratio(20.0) + expected_sprites = sorted(self.ag2.sprites(), key=id) + + collided_sprites = sorted( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=collided_func + ), + key=id, + ) + + self.assertListEqual(expected_sprites, collided_sprites) + + def test_collide_circle__radius_set_by_collide_circle_ratio(self): + # Call collide_circle_ratio with no radius set, at a 20.0 ratio. + # That should return group ag2 AND set the radius attribute of the + # sprites in such a way that collide_circle would give same result as + # if it had been called without the radius being set. + collided_func = sprite.collide_circle_ratio(20.0) + + sprite.spritecollide(self.s1, self.ag2, dokill=False, collided=collided_func) + + self.assertEqual( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=sprite.collide_circle + ), + [self.s2], + ) + + def test_collide_circle_ratio__no_radius_and_ratio_of_two_twice(self): + # collide_circle_ratio with no radius set, at a 2.0 ratio, + # called twice to check if the bug where the calculated radius + # is not stored correctly in the radius attribute of each sprite. + collided_func = sprite.collide_circle_ratio(2.0) + + # Calling collide_circle_ratio will set the radius attribute of the + # sprites. If an incorrect value is stored then we will not get the + # same result next time it is called: + expected_sprites = sorted( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=collided_func + ), + key=id, + ) + collided_sprites = sorted( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=collided_func + ), + key=id, + ) + + self.assertListEqual(expected_sprites, collided_sprites) + + def test_collide_circle__with_radii_set(self): + # collide_circle with a radius set. + self.s1.radius = 50 + self.s2.radius = 10 + self.s3.radius = 400 + collided_func = sprite.collide_circle + expected_sprites = sorted(self.ag2.sprites(), key=id) + + collided_sprites = sorted( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=collided_func + ), + key=id, + ) + + self.assertListEqual(expected_sprites, collided_sprites) + + def test_collide_circle_ratio__with_radii_set(self): + # collide_circle_ratio with a radius set. + self.s1.radius = 50 + self.s2.radius = 10 + self.s3.radius = 400 + collided_func = sprite.collide_circle_ratio(0.5) + expected_sprites = sorted(self.ag2.sprites(), key=id) + + collided_sprites = sorted( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=collided_func + ), + key=id, + ) + + self.assertListEqual(expected_sprites, collided_sprites) + + def test_collide_mask__opaque(self): + # make some fully opaque sprites that will collide with masks. + self.s1.image.fill((255, 255, 255, 255)) + self.s2.image.fill((255, 255, 255, 255)) + self.s3.image.fill((255, 255, 255, 255)) + + # masks should be autogenerated from image if they don't exist. + self.assertEqual( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=sprite.collide_mask + ), + [self.s2], + ) + + self.s1.mask = pygame.mask.from_surface(self.s1.image) + self.s2.mask = pygame.mask.from_surface(self.s2.image) + self.s3.mask = pygame.mask.from_surface(self.s3.image) + + # with set masks. + self.assertEqual( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=sprite.collide_mask + ), + [self.s2], + ) + + def test_collide_mask__transparent(self): + # make some sprites that are fully transparent, so they won't collide. + self.s1.image.fill((255, 255, 255, 0)) + self.s2.image.fill((255, 255, 255, 0)) + self.s3.image.fill((255, 255, 255, 0)) + + self.s1.mask = pygame.mask.from_surface(self.s1.image, 255) + self.s2.mask = pygame.mask.from_surface(self.s2.image, 255) + self.s3.mask = pygame.mask.from_surface(self.s3.image, 255) + + self.assertFalse( + sprite.spritecollide( + self.s1, self.ag2, dokill=False, collided=sprite.collide_mask + ) + ) + + def test_spritecollideany__without_collided_callback(self): + + # pygame.sprite.spritecollideany(sprite, group) -> sprite + # finds any sprites that collide + + # if collided is not passed, all + # sprites must have a "rect" value, which is a + # rectangle of the sprite area, which will be used + # to calculate the collision. + + # s2 in, s3 out + expected_sprite = self.s2 + collided_sprite = sprite.spritecollideany(self.s1, self.ag2) + + self.assertEqual(collided_sprite, expected_sprite) + + # s2 and s3 out + self.s2.rect.move_ip(0, 10) + collided_sprite = sprite.spritecollideany(self.s1, self.ag2) + + self.assertIsNone(collided_sprite) + + # s2 out, s3 in + self.s3.rect.move_ip(-105, -105) + expected_sprite = self.s3 + collided_sprite = sprite.spritecollideany(self.s1, self.ag2) + + self.assertEqual(collided_sprite, expected_sprite) + + # s2 and s3 in + self.s2.rect.move_ip(0, -10) + expected_sprite_choices = self.ag2.sprites() + collided_sprite = sprite.spritecollideany(self.s1, self.ag2) + + self.assertIn(collided_sprite, expected_sprite_choices) + + def test_spritecollideany__with_collided_callback(self): + + # pygame.sprite.spritecollideany(sprite, group) -> sprite + # finds any sprites that collide + + # collided is a callback function used to calculate if + # two sprites are colliding. it should take two sprites + # as values, and return a bool value indicating if + # they are colliding. + + # This collision test can be faster than pygame.sprite.spritecollide() + # since it has less work to do. + + arg_dict_a = {} + arg_dict_b = {} + return_container = [True] + + # This function is configurable using the mutable default arguments! + def collided_callback( + spr_a, + spr_b, + arg_dict_a=arg_dict_a, + arg_dict_b=arg_dict_b, + return_container=return_container, + ): + + count = arg_dict_a.get(spr_a, 0) + arg_dict_a[spr_a] = 1 + count + + count = arg_dict_b.get(spr_b, 0) + arg_dict_b[spr_b] = 1 + count + + return return_container[0] + + # This should return a sprite from self.ag2 because the callback + # function (collided_callback()) currently returns True. + expected_sprite_choices = self.ag2.sprites() + collided_sprite = sprite.spritecollideany(self.s1, self.ag2, collided_callback) + + self.assertIn(collided_sprite, expected_sprite_choices) + + # The callback function should have been called only once, so self.s1 + # should have only been passed as an argument once + self.assertEqual(len(arg_dict_a), 1) + self.assertEqual(arg_dict_a[self.s1], 1) + + # The callback function should have been called only once, so self.s2 + # exclusive-or self.s3 should have only been passed as an argument + # once + self.assertEqual(len(arg_dict_b), 1) + self.assertEqual(list(arg_dict_b.values())[0], 1) + self.assertTrue(self.s2 in arg_dict_b or self.s3 in arg_dict_b) + + arg_dict_a.clear() + arg_dict_b.clear() + return_container[0] = False + + # This should return None because the callback function + # (collided_callback()) currently returns False. + collided_sprite = sprite.spritecollideany(self.s1, self.ag2, collided_callback) + + self.assertIsNone(collided_sprite) + + # The callback function should have been called as many times as + # there are sprites in self.ag2 + self.assertEqual(len(arg_dict_a), 1) + self.assertEqual(arg_dict_a[self.s1], len(self.ag2)) + self.assertEqual(len(arg_dict_b), len(self.ag2)) + + # Each sprite in self.ag2 should be called once. + for s in self.ag2: + self.assertEqual(arg_dict_b[s], 1) + + def test_groupcollide__without_collided_callback(self): + + # pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb) -> dict + # collision detection between group and group + + # test no kill + expected_dict = {self.s1: [self.s2]} + crashed = pygame.sprite.groupcollide(self.ag, self.ag2, False, False) + + self.assertDictEqual(expected_dict, crashed) + + crashed = pygame.sprite.groupcollide(self.ag, self.ag2, False, False) + + self.assertDictEqual(expected_dict, crashed) + + # Test dokill2=True (kill colliding sprites in second group). + crashed = pygame.sprite.groupcollide(self.ag, self.ag2, False, True) + + self.assertDictEqual(expected_dict, crashed) + + expected_dict = {} + crashed = pygame.sprite.groupcollide(self.ag, self.ag2, False, False) + + self.assertDictEqual(expected_dict, crashed) + + # Test dokill1=True (kill colliding sprites in first group). + self.s3.rect.move_ip(-100, -100) + expected_dict = {self.s1: [self.s3]} + crashed = pygame.sprite.groupcollide(self.ag, self.ag2, True, False) + + self.assertDictEqual(expected_dict, crashed) + + expected_dict = {} + crashed = pygame.sprite.groupcollide(self.ag, self.ag2, False, False) + + self.assertDictEqual(expected_dict, crashed) + + def test_groupcollide__with_collided_callback(self): + + collided_callback_true = lambda spr_a, spr_b: True + collided_callback_false = lambda spr_a, spr_b: False + + # test no kill + expected_dict = {} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, False, False, collided_callback_false + ) + + self.assertDictEqual(expected_dict, crashed) + + expected_dict = {self.s1: sorted(self.ag2.sprites(), key=id)} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, False, False, collided_callback_true + ) + for value in crashed.values(): + value.sort(key=id) + + self.assertDictEqual(expected_dict, crashed) + + # expected_dict is the same again for this collide + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, False, False, collided_callback_true + ) + for value in crashed.values(): + value.sort(key=id) + + self.assertDictEqual(expected_dict, crashed) + + # Test dokill2=True (kill colliding sprites in second group). + expected_dict = {} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, False, True, collided_callback_false + ) + + self.assertDictEqual(expected_dict, crashed) + + expected_dict = {self.s1: sorted(self.ag2.sprites(), key=id)} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, False, True, collided_callback_true + ) + for value in crashed.values(): + value.sort(key=id) + + self.assertDictEqual(expected_dict, crashed) + + expected_dict = {} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, False, True, collided_callback_true + ) + + self.assertDictEqual(expected_dict, crashed) + + # Test dokill1=True (kill colliding sprites in first group). + self.ag.add(self.s2) + self.ag2.add(self.s3) + expected_dict = {} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, True, False, collided_callback_false + ) + + self.assertDictEqual(expected_dict, crashed) + + expected_dict = {self.s1: [self.s3], self.s2: [self.s3]} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, True, False, collided_callback_true + ) + + self.assertDictEqual(expected_dict, crashed) + + expected_dict = {} + crashed = pygame.sprite.groupcollide( + self.ag, self.ag2, True, False, collided_callback_true + ) + + self.assertDictEqual(expected_dict, crashed) + + def test_collide_rect(self): + # Test colliding - some edges touching + self.assertTrue(pygame.sprite.collide_rect(self.s1, self.s2)) + self.assertTrue(pygame.sprite.collide_rect(self.s2, self.s1)) + + # Test colliding - all edges touching + self.s2.rect.center = self.s3.rect.center + + self.assertTrue(pygame.sprite.collide_rect(self.s2, self.s3)) + self.assertTrue(pygame.sprite.collide_rect(self.s3, self.s2)) + + # Test colliding - no edges touching + self.s2.rect.inflate_ip(10, 10) + + self.assertTrue(pygame.sprite.collide_rect(self.s2, self.s3)) + self.assertTrue(pygame.sprite.collide_rect(self.s3, self.s2)) + + # Test colliding - some edges intersecting + self.s2.rect.center = (self.s1.rect.right, self.s1.rect.bottom) + + self.assertTrue(pygame.sprite.collide_rect(self.s1, self.s2)) + self.assertTrue(pygame.sprite.collide_rect(self.s2, self.s1)) + + # Test not colliding + self.assertFalse(pygame.sprite.collide_rect(self.s1, self.s3)) + self.assertFalse(pygame.sprite.collide_rect(self.s3, self.s1)) + + +################################################################################ + + +class AbstractGroupTypeTest(unittest.TestCase): + def setUp(self): + self.ag = sprite.AbstractGroup() + self.ag2 = sprite.AbstractGroup() + self.s1 = sprite.Sprite(self.ag) + self.s2 = sprite.Sprite(self.ag) + self.s3 = sprite.Sprite(self.ag2) + self.s4 = sprite.Sprite(self.ag2) + + self.s1.image = pygame.Surface((10, 10)) + self.s1.image.fill(pygame.Color("red")) + self.s1.rect = self.s1.image.get_rect() + + self.s2.image = pygame.Surface((10, 10)) + self.s2.image.fill(pygame.Color("green")) + self.s2.rect = self.s2.image.get_rect() + self.s2.rect.left = 10 + + self.s3.image = pygame.Surface((10, 10)) + self.s3.image.fill(pygame.Color("blue")) + self.s3.rect = self.s3.image.get_rect() + self.s3.rect.top = 10 + + self.s4.image = pygame.Surface((10, 10)) + self.s4.image.fill(pygame.Color("white")) + self.s4.rect = self.s4.image.get_rect() + self.s4.rect.left = 10 + self.s4.rect.top = 10 + + self.bg = pygame.Surface((20, 20)) + self.scr = pygame.Surface((20, 20)) + self.scr.fill(pygame.Color("grey")) + + def test_has(self): + "See if AbstractGroup.has() works as expected." + + self.assertEqual(True, self.s1 in self.ag) + + self.assertEqual(True, self.ag.has(self.s1)) + + self.assertEqual(True, self.ag.has([self.s1, self.s2])) + + # see if one of them not being in there. + self.assertNotEqual(True, self.ag.has([self.s1, self.s2, self.s3])) + self.assertNotEqual(True, self.ag.has(self.s1, self.s2, self.s3)) + self.assertNotEqual(True, self.ag.has(self.s1, sprite.Group(self.s2, self.s3))) + self.assertNotEqual(True, self.ag.has(self.s1, [self.s2, self.s3])) + + # test empty list processing + self.assertFalse(self.ag.has(*[])) + self.assertFalse(self.ag.has([])) + self.assertFalse(self.ag.has([[]])) + + # see if a second AbstractGroup works. + self.assertEqual(True, self.ag2.has(self.s3)) + + def test_add(self): + ag3 = sprite.AbstractGroup() + sprites = (self.s1, self.s2, self.s3, self.s4) + + for s in sprites: + self.assertNotIn(s, ag3) + + ag3.add(self.s1, [self.s2], self.ag2) + + for s in sprites: + self.assertIn(s, ag3) + + def test_add_internal(self): + self.assertNotIn(self.s1, self.ag2) + + self.ag2.add_internal(self.s1) + + self.assertIn(self.s1, self.ag2) + + def test_clear(self): + + self.ag.draw(self.scr) + self.ag.clear(self.scr, self.bg) + self.assertEqual((0, 0, 0, 255), self.scr.get_at((5, 5))) + self.assertEqual((0, 0, 0, 255), self.scr.get_at((15, 5))) + + def test_draw(self): + + self.ag.draw(self.scr) + self.assertEqual((255, 0, 0, 255), self.scr.get_at((5, 5))) + self.assertEqual((0, 255, 0, 255), self.scr.get_at((15, 5))) + + self.assertEqual(self.ag.spritedict[self.s1], pygame.Rect(0, 0, 10, 10)) + self.assertEqual(self.ag.spritedict[self.s2], pygame.Rect(10, 0, 10, 10)) + + def test_empty(self): + + self.ag.empty() + self.assertFalse(self.s1 in self.ag) + self.assertFalse(self.s2 in self.ag) + + def test_has_internal(self): + self.assertTrue(self.ag.has_internal(self.s1)) + self.assertFalse(self.ag.has_internal(self.s3)) + + def test_remove(self): + + # Test removal of 1 sprite + self.ag.remove(self.s1) + self.assertFalse(self.ag in self.s1.groups()) + self.assertFalse(self.ag.has(self.s1)) + + # Test removal of 2 sprites as 2 arguments + self.ag2.remove(self.s3, self.s4) + self.assertFalse(self.ag2 in self.s3.groups()) + self.assertFalse(self.ag2 in self.s4.groups()) + self.assertFalse(self.ag2.has(self.s3, self.s4)) + + # Test removal of 4 sprites as a list containing a sprite and a group + # containing a sprite and another group containing 2 sprites. + self.ag.add(self.s1, self.s3, self.s4) + self.ag2.add(self.s3, self.s4) + g = sprite.Group(self.s2) + self.ag.remove([self.s1, g], self.ag2) + self.assertFalse(self.ag in self.s1.groups()) + self.assertFalse(self.ag in self.s2.groups()) + self.assertFalse(self.ag in self.s3.groups()) + self.assertFalse(self.ag in self.s4.groups()) + self.assertFalse(self.ag.has(self.s1, self.s2, self.s3, self.s4)) + + def test_remove_internal(self): + + self.ag.remove_internal(self.s1) + self.assertFalse(self.ag.has_internal(self.s1)) + + def test_sprites(self): + expected_sprites = sorted((self.s1, self.s2), key=id) + sprite_list = sorted(self.ag.sprites(), key=id) + + self.assertListEqual(sprite_list, expected_sprites) + + def test_update(self): + class test_sprite(pygame.sprite.Sprite): + sink = [] + + def __init__(self, *groups): + pygame.sprite.Sprite.__init__(self, *groups) + + def update(self, *args): + self.sink += args + + s = test_sprite(self.ag) + self.ag.update(1, 2, 3) + + self.assertEqual(test_sprite.sink, [1, 2, 3]) + + def test_update_with_kwargs(self): + class test_sprite(pygame.sprite.Sprite): + sink = [] + sink_kwargs = {} + + def __init__(self, *groups): + pygame.sprite.Sprite.__init__(self, *groups) + + def update(self, *args, **kwargs): + self.sink += args + self.sink_kwargs.update(kwargs) + + s = test_sprite(self.ag) + self.ag.update(1, 2, 3, foo=4, bar=5) + + self.assertEqual(test_sprite.sink, [1, 2, 3]) + self.assertEqual(test_sprite.sink_kwargs, {"foo": 4, "bar": 5}) + + +################################################################################ + +# A base class to share tests between similar classes + + +class LayeredGroupBase: + def test_get_layer_of_sprite(self): + expected_layer = 666 + spr = self.sprite() + self.LG.add(spr, layer=expected_layer) + layer = self.LG.get_layer_of_sprite(spr) + + self.assertEqual(len(self.LG._spritelist), 1) + self.assertEqual(layer, self.LG.get_layer_of_sprite(spr)) + self.assertEqual(layer, expected_layer) + self.assertEqual(layer, self.LG._spritelayers[spr]) + + def test_add(self): + expected_layer = self.LG._default_layer + spr = self.sprite() + self.LG.add(spr) + layer = self.LG.get_layer_of_sprite(spr) + + self.assertEqual(len(self.LG._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__sprite_with_layer_attribute(self): + expected_layer = 100 + spr = self.sprite() + spr._layer = expected_layer + self.LG.add(spr) + layer = self.LG.get_layer_of_sprite(spr) + + self.assertEqual(len(self.LG._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__passing_layer_keyword(self): + expected_layer = 100 + spr = self.sprite() + self.LG.add(spr, layer=expected_layer) + layer = self.LG.get_layer_of_sprite(spr) + + self.assertEqual(len(self.LG._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__overriding_sprite_layer_attr(self): + expected_layer = 200 + spr = self.sprite() + spr._layer = 100 + self.LG.add(spr, layer=expected_layer) + layer = self.LG.get_layer_of_sprite(spr) + + self.assertEqual(len(self.LG._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__adding_sprite_on_init(self): + spr = self.sprite() + lrg2 = sprite.LayeredUpdates(spr) + expected_layer = lrg2._default_layer + layer = lrg2._spritelayers[spr] + + self.assertEqual(len(lrg2._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__sprite_init_layer_attr(self): + expected_layer = 20 + spr = self.sprite() + spr._layer = expected_layer + lrg2 = sprite.LayeredUpdates(spr) + layer = lrg2._spritelayers[spr] + + self.assertEqual(len(lrg2._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__sprite_init_passing_layer(self): + expected_layer = 33 + spr = self.sprite() + lrg2 = sprite.LayeredUpdates(spr, layer=expected_layer) + layer = lrg2._spritelayers[spr] + + self.assertEqual(len(lrg2._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__sprite_init_overiding_layer(self): + expected_layer = 33 + spr = self.sprite() + spr._layer = 55 + lrg2 = sprite.LayeredUpdates(spr, layer=expected_layer) + layer = lrg2._spritelayers[spr] + + self.assertEqual(len(lrg2._spritelist), 1) + self.assertEqual(layer, expected_layer) + + def test_add__spritelist(self): + expected_layer = self.LG._default_layer + sprite_count = 10 + sprites = [self.sprite() for _ in range(sprite_count)] + + self.LG.add(sprites) + + self.assertEqual(len(self.LG._spritelist), sprite_count) + + for i in range(sprite_count): + layer = self.LG.get_layer_of_sprite(sprites[i]) + + self.assertEqual(layer, expected_layer) + + def test_add__spritelist_with_layer_attr(self): + sprites = [] + sprite_and_layer_count = 10 + for i in range(sprite_and_layer_count): + sprites.append(self.sprite()) + sprites[-1]._layer = i + + self.LG.add(sprites) + + self.assertEqual(len(self.LG._spritelist), sprite_and_layer_count) + + for i in range(sprite_and_layer_count): + layer = self.LG.get_layer_of_sprite(sprites[i]) + + self.assertEqual(layer, i) + + def test_add__spritelist_passing_layer(self): + expected_layer = 33 + sprite_count = 10 + sprites = [self.sprite() for _ in range(sprite_count)] + + self.LG.add(sprites, layer=expected_layer) + + self.assertEqual(len(self.LG._spritelist), sprite_count) + + for i in range(sprite_count): + layer = self.LG.get_layer_of_sprite(sprites[i]) + + self.assertEqual(layer, expected_layer) + + def test_add__spritelist_overriding_layer(self): + expected_layer = 33 + sprites = [] + sprite_and_layer_count = 10 + for i in range(sprite_and_layer_count): + sprites.append(self.sprite()) + sprites[-1].layer = i + + self.LG.add(sprites, layer=expected_layer) + + self.assertEqual(len(self.LG._spritelist), sprite_and_layer_count) + + for i in range(sprite_and_layer_count): + layer = self.LG.get_layer_of_sprite(sprites[i]) + + self.assertEqual(layer, expected_layer) + + def test_add__spritelist_init(self): + sprite_count = 10 + sprites = [self.sprite() for _ in range(sprite_count)] + + lrg2 = sprite.LayeredUpdates(sprites) + expected_layer = lrg2._default_layer + + self.assertEqual(len(lrg2._spritelist), sprite_count) + + for i in range(sprite_count): + layer = lrg2.get_layer_of_sprite(sprites[i]) + + self.assertEqual(layer, expected_layer) + + def test_remove__sprite(self): + sprites = [] + sprite_count = 10 + for i in range(sprite_count): + sprites.append(self.sprite()) + sprites[-1].rect = pygame.Rect((0, 0, 0, 0)) + + self.LG.add(sprites) + + self.assertEqual(len(self.LG._spritelist), sprite_count) + + for i in range(sprite_count): + self.LG.remove(sprites[i]) + + self.assertEqual(len(self.LG._spritelist), 0) + + def test_sprites(self): + sprites = [] + sprite_and_layer_count = 10 + for i in range(sprite_and_layer_count, 0, -1): + sprites.append(self.sprite()) + sprites[-1]._layer = i + + self.LG.add(sprites) + + self.assertEqual(len(self.LG._spritelist), sprite_and_layer_count) + + # Sprites should be ordered based on their layer (bottom to top), + # which is the reverse order of the sprites list. + expected_sprites = list(reversed(sprites)) + actual_sprites = self.LG.sprites() + + self.assertListEqual(actual_sprites, expected_sprites) + + def test_layers(self): + sprites = [] + expected_layers = [] + layer_count = 10 + for i in range(layer_count): + expected_layers.append(i) + for j in range(5): + sprites.append(self.sprite()) + sprites[-1]._layer = i + self.LG.add(sprites) + + layers = self.LG.layers() + + self.assertListEqual(layers, expected_layers) + + def test_add__layers_are_correct(self): + layers = [1, 4, 6, 8, 3, 6, 2, 6, 4, 5, 6, 1, 0, 9, 7, 6, 54, 8, 2, 43, 6, 1] + for lay in layers: + self.LG.add(self.sprite(), layer=lay) + layers.sort() + + for idx, spr in enumerate(self.LG.sprites()): + layer = self.LG.get_layer_of_sprite(spr) + + self.assertEqual(layer, layers[idx]) + + def test_change_layer(self): + expected_layer = 99 + spr = self.sprite() + self.LG.add(spr, layer=expected_layer) + + self.assertEqual(self.LG._spritelayers[spr], expected_layer) + + expected_layer = 44 + self.LG.change_layer(spr, expected_layer) + + self.assertEqual(self.LG._spritelayers[spr], expected_layer) + + expected_layer = 77 + spr2 = self.sprite() + spr2.layer = 55 + self.LG.add(spr2) + self.LG.change_layer(spr2, expected_layer) + + self.assertEqual(spr2.layer, expected_layer) + + def test_get_sprites_at(self): + sprites = [] + expected_sprites = [] + for i in range(3): + spr = self.sprite() + spr.rect = pygame.Rect(i * 50, i * 50, 100, 100) + sprites.append(spr) + if i < 2: + expected_sprites.append(spr) + self.LG.add(sprites) + result = self.LG.get_sprites_at((50, 50)) + self.assertEqual(result, expected_sprites) + + def test_get_top_layer(self): + layers = [1, 5, 2, 8, 4, 5, 3, 88, 23, 0] + for i in layers: + self.LG.add(self.sprite(), layer=i) + top_layer = self.LG.get_top_layer() + + self.assertEqual(top_layer, self.LG.get_top_layer()) + self.assertEqual(top_layer, max(layers)) + self.assertEqual(top_layer, max(self.LG._spritelayers.values())) + self.assertEqual(top_layer, self.LG._spritelayers[self.LG._spritelist[-1]]) + + def test_get_bottom_layer(self): + layers = [1, 5, 2, 8, 4, 5, 3, 88, 23, 0] + for i in layers: + self.LG.add(self.sprite(), layer=i) + bottom_layer = self.LG.get_bottom_layer() + + self.assertEqual(bottom_layer, self.LG.get_bottom_layer()) + self.assertEqual(bottom_layer, min(layers)) + self.assertEqual(bottom_layer, min(self.LG._spritelayers.values())) + self.assertEqual(bottom_layer, self.LG._spritelayers[self.LG._spritelist[0]]) + + def test_move_to_front(self): + layers = [1, 5, 2, 8, 4, 5, 3, 88, 23, 0] + for i in layers: + self.LG.add(self.sprite(), layer=i) + spr = self.sprite() + self.LG.add(spr, layer=3) + + self.assertNotEqual(spr, self.LG._spritelist[-1]) + + self.LG.move_to_front(spr) + + self.assertEqual(spr, self.LG._spritelist[-1]) + + def test_move_to_back(self): + layers = [1, 5, 2, 8, 4, 5, 3, 88, 23, 0] + for i in layers: + self.LG.add(self.sprite(), layer=i) + spr = self.sprite() + self.LG.add(spr, layer=55) + + self.assertNotEqual(spr, self.LG._spritelist[0]) + + self.LG.move_to_back(spr) + + self.assertEqual(spr, self.LG._spritelist[0]) + + def test_get_top_sprite(self): + layers = [1, 5, 2, 8, 4, 5, 3, 88, 23, 0] + for i in layers: + self.LG.add(self.sprite(), layer=i) + expected_layer = self.LG.get_top_layer() + layer = self.LG.get_layer_of_sprite(self.LG.get_top_sprite()) + + self.assertEqual(layer, expected_layer) + + def test_get_sprites_from_layer(self): + sprites = {} + layers = [ + 1, + 4, + 5, + 6, + 3, + 7, + 8, + 2, + 1, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 1, + 6, + 5, + 4, + 3, + 2, + ] + for lay in layers: + spr = self.sprite() + spr._layer = lay + self.LG.add(spr) + if lay not in sprites: + sprites[lay] = [] + sprites[lay].append(spr) + + for lay in self.LG.layers(): + for spr in self.LG.get_sprites_from_layer(lay): + self.assertIn(spr, sprites[lay]) + + sprites[lay].remove(spr) + if len(sprites[lay]) == 0: + del sprites[lay] + + self.assertEqual(len(sprites.values()), 0) + + def test_switch_layer(self): + sprites1 = [] + sprites2 = [] + layers = [3, 2, 3, 2, 3, 3, 2, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 2, 2, 3, 2, 3] + for lay in layers: + spr = self.sprite() + spr._layer = lay + self.LG.add(spr) + if lay == 2: + sprites1.append(spr) + else: + sprites2.append(spr) + + sprites1.sort(key=id) + sprites2.sort(key=id) + layer2_sprites = sorted(self.LG.get_sprites_from_layer(2), key=id) + layer3_sprites = sorted(self.LG.get_sprites_from_layer(3), key=id) + + self.assertListEqual(sprites1, layer2_sprites) + self.assertListEqual(sprites2, layer3_sprites) + self.assertEqual(len(self.LG), len(sprites1) + len(sprites2)) + + self.LG.switch_layer(2, 3) + layer2_sprites = sorted(self.LG.get_sprites_from_layer(2), key=id) + layer3_sprites = sorted(self.LG.get_sprites_from_layer(3), key=id) + + self.assertListEqual(sprites1, layer3_sprites) + self.assertListEqual(sprites2, layer2_sprites) + self.assertEqual(len(self.LG), len(sprites1) + len(sprites2)) + + def test_copy(self): + self.LG.add(self.sprite()) + spr = self.LG.sprites()[0] + lg_copy = self.LG.copy() + + self.assertIsInstance(lg_copy, type(self.LG)) + self.assertIn(spr, lg_copy) + self.assertIn(lg_copy, spr.groups()) + + +########################## LAYERED RENDER GROUP TESTS ########################## + + +class LayeredUpdatesTypeTest__SpriteTest(LayeredGroupBase, unittest.TestCase): + sprite = sprite.Sprite + + def setUp(self): + self.LG = sprite.LayeredUpdates() + + +class LayeredUpdatesTypeTest__DirtySprite(LayeredGroupBase, unittest.TestCase): + sprite = sprite.DirtySprite + + def setUp(self): + self.LG = sprite.LayeredUpdates() + + +class LayeredDirtyTypeTest__DirtySprite(LayeredGroupBase, unittest.TestCase): + sprite = sprite.DirtySprite + + def setUp(self): + self.LG = sprite.LayeredDirty() + + def test_repaint_rect(self): + group = self.LG + surface = pygame.Surface((100, 100)) + + group.repaint_rect(pygame.Rect(0, 0, 100, 100)) + group.draw(surface) + + def test_repaint_rect_with_clip(self): + group = self.LG + surface = pygame.Surface((100, 100)) + + group.set_clip(pygame.Rect(0, 0, 100, 100)) + group.repaint_rect(pygame.Rect(0, 0, 100, 100)) + group.draw(surface) + + def _nondirty_intersections_redrawn(self, use_source_rect=False): + # Helper method to ensure non-dirty sprites are redrawn correctly. + # + # Parameters: + # use_source_rect - allows non-dirty sprites to be tested + # with (True) and without (False) a source_rect + # + # This test was written to reproduce the behavior seen in issue #898. + # A non-dirty sprite (using source_rect) was being redrawn incorrectly + # after a dirty sprite intersected with it. + # + # This test does the following. + # 1. Creates a surface filled with white. Also creates an image_source + # with a default fill color of yellow and adds 2 images to it + # (red and blue rectangles). + # 2. Creates 2 DirtySprites (red_sprite and blue_sprite) using the + # image_source and adds them to a LayeredDirty group. + # 3. Moves the red_sprite and calls LayeredDirty.draw(surface) a few + # times. + # 4. Checks to make sure the sprites were redrawn correctly. + RED = pygame.Color("red") + BLUE = pygame.Color("blue") + WHITE = pygame.Color("white") + YELLOW = pygame.Color("yellow") + + surface = pygame.Surface((60, 80)) + surface.fill(WHITE) + start_pos = (10, 10) + + # These rects define each sprite's image area in the image_source. + red_sprite_source = pygame.Rect((45, 0), (5, 4)) + blue_sprite_source = pygame.Rect((0, 40), (20, 10)) + + # Create a source image/surface. + image_source = pygame.Surface((50, 50)) + image_source.fill(YELLOW) + image_source.fill(RED, red_sprite_source) + image_source.fill(BLUE, blue_sprite_source) + + # The blue_sprite is stationary and will not reset its dirty flag. It + # will be the non-dirty sprite in this test. Its values are dependent + # on the use_source_rect flag. + blue_sprite = pygame.sprite.DirtySprite(self.LG) + + if use_source_rect: + blue_sprite.image = image_source + # The rect is a bit smaller than the source_rect to make sure + # LayeredDirty.draw() is using the correct dimensions. + blue_sprite.rect = pygame.Rect( + start_pos, (blue_sprite_source.w - 7, blue_sprite_source.h - 7) + ) + blue_sprite.source_rect = blue_sprite_source + start_x, start_y = blue_sprite.rect.topleft + end_x = start_x + blue_sprite.source_rect.w + end_y = start_y + blue_sprite.source_rect.h + else: + blue_sprite.image = image_source.subsurface(blue_sprite_source) + blue_sprite.rect = pygame.Rect(start_pos, blue_sprite_source.size) + start_x, start_y = blue_sprite.rect.topleft + end_x, end_y = blue_sprite.rect.bottomright + + # The red_sprite is moving and will always be dirty. + red_sprite = pygame.sprite.DirtySprite(self.LG) + red_sprite.image = image_source + red_sprite.rect = pygame.Rect(start_pos, red_sprite_source.size) + red_sprite.source_rect = red_sprite_source + red_sprite.dirty = 2 + + # Draw the red_sprite as it moves a few steps. + for _ in range(4): + red_sprite.rect.move_ip(2, 1) + + # This is the method being tested. + self.LG.draw(surface) + + # Check colors where the blue_sprite is drawn. We expect red where the + # red_sprite is drawn over the blue_sprite, but the rest should be + # blue. + surface.lock() # Lock surface for possible speed up. + try: + for y in range(start_y, end_y): + for x in range(start_x, end_x): + if red_sprite.rect.collidepoint(x, y): + expected_color = RED + else: + expected_color = BLUE + + color = surface.get_at((x, y)) + + self.assertEqual(color, expected_color, "pos=({}, {})".format(x, y)) + finally: + surface.unlock() + + def test_nondirty_intersections_redrawn(self): + """Ensure non-dirty sprites are correctly redrawn + when dirty sprites intersect with them. + """ + self._nondirty_intersections_redrawn() + + def test_nondirty_intersections_redrawn__with_source_rect(self): + """Ensure non-dirty sprites using source_rects are correctly redrawn + when dirty sprites intersect with them. + + Related to issue #898. + """ + self._nondirty_intersections_redrawn(True) + + +############################### SPRITE BASE CLASS ############################## +# +# tests common between sprite classes + + +class SpriteBase: + def setUp(self): + self.groups = [] + for Group in self.Groups: + self.groups.append(Group()) + + self.sprite = self.Sprite() + + def test_add_internal(self): + + for g in self.groups: + self.sprite.add_internal(g) + + for g in self.groups: + self.assertIn(g, self.sprite.groups()) + + def test_remove_internal(self): + + for g in self.groups: + self.sprite.add_internal(g) + + for g in self.groups: + self.sprite.remove_internal(g) + + for g in self.groups: + self.assertFalse(g in self.sprite.groups()) + + def test_update(self): + class test_sprite(pygame.sprite.Sprite): + sink = [] + + def __init__(self, *groups): + pygame.sprite.Sprite.__init__(self, *groups) + + def update(self, *args): + self.sink += args + + s = test_sprite() + s.update(1, 2, 3) + + self.assertEqual(test_sprite.sink, [1, 2, 3]) + + def test_update_with_kwargs(self): + class test_sprite(pygame.sprite.Sprite): + sink = [] + sink_dict = {} + + def __init__(self, *groups): + pygame.sprite.Sprite.__init__(self, *groups) + + def update(self, *args, **kwargs): + self.sink += args + self.sink_dict.update(kwargs) + + s = test_sprite() + s.update(1, 2, 3, foo=4, bar=5) + + self.assertEqual(test_sprite.sink, [1, 2, 3]) + self.assertEqual(test_sprite.sink_dict, {"foo": 4, "bar": 5}) + + def test___init____added_to_groups_passed(self): + expected_groups = sorted(self.groups, key=id) + sprite = self.Sprite(self.groups) + groups = sorted(sprite.groups(), key=id) + + self.assertListEqual(groups, expected_groups) + + def test_add(self): + expected_groups = sorted(self.groups, key=id) + self.sprite.add(self.groups) + groups = sorted(self.sprite.groups(), key=id) + + self.assertListEqual(groups, expected_groups) + + def test_alive(self): + self.assertFalse( + self.sprite.alive(), "Sprite should not be alive if in no groups" + ) + + self.sprite.add(self.groups) + + self.assertTrue(self.sprite.alive()) + + def test_groups(self): + for i, g in enumerate(self.groups): + expected_groups = sorted(self.groups[: i + 1], key=id) + self.sprite.add(g) + groups = sorted(self.sprite.groups(), key=id) + + self.assertListEqual(groups, expected_groups) + + def test_kill(self): + self.sprite.add(self.groups) + + self.assertTrue(self.sprite.alive()) + + self.sprite.kill() + + self.assertListEqual(self.sprite.groups(), []) + self.assertFalse(self.sprite.alive()) + + def test_remove(self): + self.sprite.add(self.groups) + self.sprite.remove(self.groups) + + self.assertListEqual(self.sprite.groups(), []) + + +############################## SPRITE CLASS TESTS ############################## + + +class SpriteTypeTest(SpriteBase, unittest.TestCase): + Sprite = sprite.Sprite + + Groups = [ + sprite.Group, + sprite.LayeredUpdates, + sprite.RenderUpdates, + sprite.OrderedUpdates, + ] + + +class DirtySpriteTypeTest(SpriteBase, unittest.TestCase): + Sprite = sprite.DirtySprite + + Groups = [ + sprite.Group, + sprite.LayeredUpdates, + sprite.RenderUpdates, + sprite.OrderedUpdates, + sprite.LayeredDirty, + ] + + +############################## BUG TESTS ####################################### + + +class SingleGroupBugsTest(unittest.TestCase): + def test_memoryleak_bug(self): + # For memoryleak bug posted to mailing list by Tobias Steinrücken on 16/11/10. + # Fixed in revision 2953. + + import weakref + import gc + + class MySprite(sprite.Sprite): + def __init__(self, *args, **kwargs): + sprite.Sprite.__init__(self, *args, **kwargs) + self.image = pygame.Surface((2, 4), 0, 24) + self.rect = self.image.get_rect() + + g = sprite.GroupSingle() + screen = pygame.Surface((4, 8), 0, 24) + s = MySprite() + r = weakref.ref(s) + g.sprite = s + del s + gc.collect() + + self.assertIsNotNone(r()) + + g.update() + g.draw(screen) + g.sprite = MySprite() + gc.collect() + + self.assertIsNone(r()) + + +################################################################################ + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/surface_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/surface_test.py new file mode 100644 index 0000000..ab9b0e6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/surface_test.py @@ -0,0 +1,4015 @@ +import os +import unittest +from pygame.tests import test_utils +from pygame.tests.test_utils import ( + example_path, + AssertRaisesRegexMixin, + SurfaceSubclass, +) + +try: + from pygame.tests.test_utils.arrinter import * +except (ImportError, NameError): + pass +import pygame +from pygame.locals import * +from pygame.bufferproxy import BufferProxy + +import platform +import gc +import weakref +import ctypes + +IS_PYPY = "PyPy" == platform.python_implementation() + + +class SurfaceTypeTest(AssertRaisesRegexMixin, unittest.TestCase): + def test_surface__pixel_format_as_surface_subclass(self): + """Ensure a subclassed surface can be used for pixel format + when creating a new surface.""" + expected_depth = 16 + expected_flags = SRCALPHA + expected_size = (13, 37) + depth_surface = SurfaceSubclass((11, 21), expected_flags, expected_depth) + + surface = pygame.Surface(expected_size, expected_flags, depth_surface) + + self.assertIsNot(surface, depth_surface) + self.assertIsInstance(surface, pygame.Surface) + self.assertNotIsInstance(surface, SurfaceSubclass) + self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_flags(), expected_flags) + self.assertEqual(surface.get_bitsize(), expected_depth) + + def test_set_clip(self): + """see if surface.set_clip(None) works correctly.""" + s = pygame.Surface((800, 600)) + r = pygame.Rect(10, 10, 10, 10) + s.set_clip(r) + r.move_ip(10, 0) + s.set_clip(None) + res = s.get_clip() + # this was garbled before. + self.assertEqual(res[0], 0) + self.assertEqual(res[2], 800) + + def test_print(self): + surf = pygame.Surface((70, 70), 0, 32) + self.assertEqual(repr(surf), "") + + def test_keyword_arguments(self): + surf = pygame.Surface((70, 70), flags=SRCALPHA, depth=32) + self.assertEqual(surf.get_flags() & SRCALPHA, SRCALPHA) + self.assertEqual(surf.get_bitsize(), 32) + + # sanity check to make sure the check below is valid + surf_16 = pygame.Surface((70, 70), 0, 16) + self.assertEqual(surf_16.get_bytesize(), 2) + + # try again with an argument list + surf_16 = pygame.Surface((70, 70), depth=16) + self.assertEqual(surf_16.get_bytesize(), 2) + + def test_set_at(self): + + # 24bit surfaces + s = pygame.Surface((100, 100), 0, 24) + s.fill((0, 0, 0)) + + # set it with a tuple. + s.set_at((0, 0), (10, 10, 10, 255)) + r = s.get_at((0, 0)) + self.assertIsInstance(r, pygame.Color) + self.assertEqual(r, (10, 10, 10, 255)) + + # try setting a color with a single integer. + s.fill((0, 0, 0, 255)) + s.set_at((10, 1), 0x0000FF) + r = s.get_at((10, 1)) + self.assertEqual(r, (0, 0, 255, 255)) + + def test_set_at__big_endian(self): + """png files are loaded in big endian format (BGR rather than RGB)""" + pygame.display.init() + try: + image = pygame.image.load(example_path(os.path.join("data", "BGR.png"))) + # Check they start red, green and blue + self.assertEqual(image.get_at((10, 10)), pygame.Color(255, 0, 0)) + self.assertEqual(image.get_at((10, 20)), pygame.Color(0, 255, 0)) + self.assertEqual(image.get_at((10, 40)), pygame.Color(0, 0, 255)) + # Set three pixels that are already red, green, blue + # to red, green and, blue with set_at: + image.set_at((10, 10), pygame.Color(255, 0, 0)) + image.set_at((10, 20), pygame.Color(0, 255, 0)) + image.set_at((10, 40), pygame.Color(0, 0, 255)) + + # Check they still are + self.assertEqual(image.get_at((10, 10)), pygame.Color(255, 0, 0)) + self.assertEqual(image.get_at((10, 20)), pygame.Color(0, 255, 0)) + self.assertEqual(image.get_at((10, 40)), pygame.Color(0, 0, 255)) + + finally: + pygame.display.quit() + + def test_SRCALPHA(self): + # has the flag been passed in ok? + surf = pygame.Surface((70, 70), SRCALPHA, 32) + self.assertEqual(surf.get_flags() & SRCALPHA, SRCALPHA) + + # 24bit surfaces can not have SRCALPHA. + self.assertRaises(ValueError, pygame.Surface, (100, 100), pygame.SRCALPHA, 24) + + # if we have a 32 bit surface, the SRCALPHA should have worked too. + surf2 = pygame.Surface((70, 70), SRCALPHA) + if surf2.get_bitsize() == 32: + self.assertEqual(surf2.get_flags() & SRCALPHA, SRCALPHA) + + def test_flags_default0_nodisplay(self): + """is set to zero, and SRCALPH is not set by default with no display initialized.""" + pygame.display.quit() + surf = pygame.Surface((70, 70)) + self.assertEqual(surf.get_flags() & SRCALPHA, 0) + + def test_flags_default0_display(self): + """is set to zero, and SRCALPH is not set by default even when the display is initialized.""" + pygame.display.set_mode((320, 200)) + try: + surf = pygame.Surface((70, 70)) + self.assertEqual(surf.get_flags() & SRCALPHA, 0) + finally: + pygame.display.quit() + + def test_masks(self): + def make_surf(bpp, flags, masks): + pygame.Surface((10, 10), flags, bpp, masks) + + # With some masks SDL_CreateRGBSurface does not work properly. + masks = (0xFF000000, 0xFF0000, 0xFF00, 0) + self.assertEqual(make_surf(32, 0, masks), None) + # For 24 and 32 bit surfaces Pygame assumes no losses. + masks = (0x7F0000, 0xFF00, 0xFF, 0) + self.assertRaises(ValueError, make_surf, 24, 0, masks) + self.assertRaises(ValueError, make_surf, 32, 0, masks) + # What contiguous bits in a mask. + masks = (0x6F0000, 0xFF00, 0xFF, 0) + self.assertRaises(ValueError, make_surf, 32, 0, masks) + + def test_get_bounding_rect(self): + surf = pygame.Surface((70, 70), SRCALPHA, 32) + surf.fill((0, 0, 0, 0)) + bound_rect = surf.get_bounding_rect() + self.assertEqual(bound_rect.width, 0) + self.assertEqual(bound_rect.height, 0) + surf.set_at((30, 30), (255, 255, 255, 1)) + bound_rect = surf.get_bounding_rect() + self.assertEqual(bound_rect.left, 30) + self.assertEqual(bound_rect.top, 30) + self.assertEqual(bound_rect.width, 1) + self.assertEqual(bound_rect.height, 1) + surf.set_at((29, 29), (255, 255, 255, 1)) + bound_rect = surf.get_bounding_rect() + self.assertEqual(bound_rect.left, 29) + self.assertEqual(bound_rect.top, 29) + self.assertEqual(bound_rect.width, 2) + self.assertEqual(bound_rect.height, 2) + + surf = pygame.Surface((70, 70), 0, 24) + surf.fill((0, 0, 0)) + bound_rect = surf.get_bounding_rect() + self.assertEqual(bound_rect.width, surf.get_width()) + self.assertEqual(bound_rect.height, surf.get_height()) + + surf.set_colorkey((0, 0, 0)) + bound_rect = surf.get_bounding_rect() + self.assertEqual(bound_rect.width, 0) + self.assertEqual(bound_rect.height, 0) + surf.set_at((30, 30), (255, 255, 255)) + bound_rect = surf.get_bounding_rect() + self.assertEqual(bound_rect.left, 30) + self.assertEqual(bound_rect.top, 30) + self.assertEqual(bound_rect.width, 1) + self.assertEqual(bound_rect.height, 1) + surf.set_at((60, 60), (255, 255, 255)) + bound_rect = surf.get_bounding_rect() + self.assertEqual(bound_rect.left, 30) + self.assertEqual(bound_rect.top, 30) + self.assertEqual(bound_rect.width, 31) + self.assertEqual(bound_rect.height, 31) + + # Issue #180 + pygame.display.init() + try: + surf = pygame.Surface((4, 1), 0, 8) + surf.fill((255, 255, 255)) + surf.get_bounding_rect() # Segfault. + finally: + pygame.display.quit() + + def test_copy(self): + """Ensure a surface can be copied.""" + color = (25, 25, 25, 25) + s1 = pygame.Surface((32, 32), pygame.SRCALPHA, 32) + s1.fill(color) + + s2 = s1.copy() + + s1rect = s1.get_rect() + s2rect = s2.get_rect() + + self.assertEqual(s1rect.size, s2rect.size) + self.assertEqual(s2.get_at((10, 10)), color) + + def test_fill(self): + """Ensure a surface can be filled.""" + color = (25, 25, 25, 25) + fill_rect = pygame.Rect(0, 0, 16, 16) + s1 = pygame.Surface((32, 32), pygame.SRCALPHA, 32) + s1.fill(color, fill_rect) + + for pt in test_utils.rect_area_pts(fill_rect): + self.assertEqual(s1.get_at(pt), color) + + for pt in test_utils.rect_outer_bounds(fill_rect): + self.assertNotEqual(s1.get_at(pt), color) + + def test_fill_rle(self): + """Test RLEACCEL flag with fill()""" + color = (250, 25, 25, 255) + + surf = pygame.Surface((32, 32)) + blit_surf = pygame.Surface((32, 32)) + + blit_surf.set_colorkey((255, 0, 255), pygame.RLEACCEL) + self.assertTrue(blit_surf.get_flags() & pygame.RLEACCELOK) + surf.blit(blit_surf, (0, 0)) + blit_surf.fill(color) + self.assertEqual( + blit_surf.mustlock(), (blit_surf.get_flags() & pygame.RLEACCEL) != 0 + ) + self.assertTrue(blit_surf.get_flags() & pygame.RLEACCEL) + + def test_mustlock_rle(self): + """Test RLEACCEL flag with mustlock()""" + surf = pygame.Surface((100, 100)) + blit_surf = pygame.Surface((100, 100)) + blit_surf.set_colorkey((0, 0, 255), pygame.RLEACCEL) + self.assertTrue(blit_surf.get_flags() & pygame.RLEACCELOK) + surf.blit(blit_surf, (0, 0)) + self.assertTrue(blit_surf.get_flags() & pygame.RLEACCEL) + self.assertTrue(blit_surf.mustlock()) + + def test_mustlock_surf_alpha_rle(self): + """Test RLEACCEL flag with mustlock() on a surface + with per pixel alpha - new feature in SDL2""" + surf = pygame.Surface((100, 100)) + blit_surf = pygame.Surface((100, 100), depth=32, flags=pygame.SRCALPHA) + blit_surf.set_colorkey((192, 191, 192, 255), pygame.RLEACCEL) + self.assertTrue(blit_surf.get_flags() & pygame.RLEACCELOK) + surf.blit(blit_surf, (0, 0)) + self.assertTrue(blit_surf.get_flags() & pygame.RLEACCEL) + self.assertTrue(blit_surf.get_flags() & pygame.SRCALPHA) + self.assertTrue(blit_surf.mustlock()) + + def test_copy_rle(self): + """Test copying a surface set to use run length encoding""" + s1 = pygame.Surface((32, 32), 24) + s1.set_colorkey((255, 0, 255), pygame.RLEACCEL) + self.assertTrue(s1.get_flags() & pygame.RLEACCELOK) + + newsurf = s1.copy() + self.assertTrue(s1.get_flags() & pygame.RLEACCELOK) + self.assertTrue(newsurf.get_flags() & pygame.RLEACCELOK) + + def test_subsurface_rle(self): + """Ensure an RLE sub-surface works independently of its parent.""" + color = (250, 25, 25, 255) + color2 = (200, 200, 250, 255) + sub_rect = pygame.Rect(16, 16, 16, 16) + s0 = pygame.Surface((32, 32), 24) + s1 = pygame.Surface((32, 32), 24) + s1.set_colorkey((255, 0, 255), pygame.RLEACCEL) + s1.fill(color) + s2 = s1.subsurface(sub_rect) + s2.fill(color2) + s0.blit(s1, (0, 0)) + self.assertTrue(s1.get_flags() & pygame.RLEACCEL) + self.assertTrue(not s2.get_flags() & pygame.RLEACCEL) + + def test_subsurface_rle2(self): + """Ensure an RLE sub-surface works independently of its parent.""" + color = (250, 25, 25, 255) + color2 = (200, 200, 250, 255) + sub_rect = pygame.Rect(16, 16, 16, 16) + + s0 = pygame.Surface((32, 32), 24) + s1 = pygame.Surface((32, 32), 24) + s1.set_colorkey((255, 0, 255), pygame.RLEACCEL) + s1.fill(color) + s2 = s1.subsurface(sub_rect) + s2.fill(color2) + s0.blit(s2, (0, 0)) + self.assertTrue(s1.get_flags() & pygame.RLEACCELOK) + self.assertTrue(not s2.get_flags() & pygame.RLEACCELOK) + + def test_solarwolf_rle_usage(self): + """Test for error/crash when calling set_colorkey() followed + by convert twice in succession. Code originally taken + from solarwolf.""" + + def optimize(img): + clear = img.get_colorkey() + img.set_colorkey(clear, RLEACCEL) + self.assertEqual(img.get_colorkey(), clear) + return img.convert() + + pygame.display.init() + try: + pygame.display.set_mode((640, 480)) + + image = pygame.image.load(example_path(os.path.join("data", "alien1.png"))) + image = image.convert() + orig_colorkey = image.get_colorkey() + + image = optimize(image) + image = optimize(image) + self.assertTrue(image.get_flags() & pygame.RLEACCELOK) + self.assertTrue(not image.get_flags() & pygame.RLEACCEL) + self.assertEqual(image.get_colorkey(), orig_colorkey) + self.assertTrue(isinstance(image, pygame.Surface)) + finally: + pygame.display.quit() + + def test_solarwolf_rle_usage_2(self): + """Test for RLE status after setting alpha""" + + pygame.display.init() + try: + pygame.display.set_mode((640, 480), depth=32) + blit_to_surf = pygame.Surface((100, 100)) + + image = pygame.image.load(example_path(os.path.join("data", "alien1.png"))) + image = image.convert() + orig_colorkey = image.get_colorkey() + + # set the colorkey with RLEACCEL, should add the RLEACCELOK flag + image.set_colorkey(orig_colorkey, RLEACCEL) + self.assertTrue(image.get_flags() & pygame.RLEACCELOK) + self.assertTrue(not image.get_flags() & pygame.RLEACCEL) + + # now blit the surface - should add the RLEACCEL flag + blit_to_surf.blit(image, (0, 0)) + self.assertTrue(image.get_flags() & pygame.RLEACCELOK) + self.assertTrue(image.get_flags() & pygame.RLEACCEL) + + # Now set the alpha, without RLE acceleration - should strip all + # RLE flags + image.set_alpha(90) + self.assertTrue(not image.get_flags() & pygame.RLEACCELOK) + self.assertTrue(not image.get_flags() & pygame.RLEACCEL) + + finally: + pygame.display.quit() + + def test_set_alpha__set_colorkey_rle(self): + pygame.display.init() + try: + pygame.display.set_mode((640, 480)) + blit_to_surf = pygame.Surface((80, 71)) + blit_to_surf.fill((255, 255, 255)) + + image = pygame.image.load(example_path(os.path.join("data", "alien1.png"))) + image = image.convert() + orig_colorkey = image.get_colorkey() + + # Add the RLE flag while setting alpha for the whole surface + image.set_alpha(90, RLEACCEL) + blit_to_surf.blit(image, (0, 0)) + sample_pixel_rle = blit_to_surf.get_at((50, 50)) + + # Now reset the colorkey to the original value with RLE + self.assertEqual(image.get_colorkey(), orig_colorkey) + image.set_colorkey(orig_colorkey, RLEACCEL) + blit_to_surf.fill((255, 255, 255)) + blit_to_surf.blit(image, (0, 0)) + sample_pixel_no_rle = blit_to_surf.get_at((50, 50)) + + self.assertAlmostEqual(sample_pixel_rle.r, sample_pixel_no_rle.r, delta=2) + self.assertAlmostEqual(sample_pixel_rle.g, sample_pixel_no_rle.g, delta=2) + self.assertAlmostEqual(sample_pixel_rle.b, sample_pixel_no_rle.b, delta=2) + + finally: + pygame.display.quit() + + def test_fill_negative_coordinates(self): + + # negative coordinates should be clipped by fill, and not draw outside the surface. + color = (25, 25, 25, 25) + color2 = (20, 20, 20, 25) + fill_rect = pygame.Rect(-10, -10, 16, 16) + + s1 = pygame.Surface((32, 32), pygame.SRCALPHA, 32) + r1 = s1.fill(color, fill_rect) + c = s1.get_at((0, 0)) + self.assertEqual(c, color) + + # make subsurface in the middle to test it doesn't over write. + s2 = s1.subsurface((5, 5, 5, 5)) + r2 = s2.fill(color2, (-3, -3, 5, 5)) + c2 = s1.get_at((4, 4)) + self.assertEqual(c, color) + + # rect returns the area we actually fill. + r3 = s2.fill(color2, (-30, -30, 5, 5)) + # since we are using negative coords, it should be an zero sized rect. + self.assertEqual(tuple(r3), (0, 0, 0, 0)) + + def test_fill_keyword_args(self): + """Ensure fill() accepts keyword arguments.""" + color = (1, 2, 3, 255) + area = (1, 1, 2, 2) + s1 = pygame.Surface((4, 4), 0, 32) + s1.fill(special_flags=pygame.BLEND_ADD, color=color, rect=area) + + self.assertEqual(s1.get_at((0, 0)), (0, 0, 0, 255)) + self.assertEqual(s1.get_at((1, 1)), color) + + ######################################################################## + + def test_get_alpha(self): + """Ensure a surface's alpha value can be retrieved.""" + s1 = pygame.Surface((32, 32), pygame.SRCALPHA, 32) + + self.assertEqual(s1.get_alpha(), 255) + + for alpha in (0, 32, 127, 255): + s1.set_alpha(alpha) + for t in range(4): + s1.set_alpha(s1.get_alpha()) + + self.assertEqual(s1.get_alpha(), alpha) + + ######################################################################## + + def test_get_bytesize(self): + """Ensure a surface's bit and byte sizes can be retrieved.""" + pygame.display.init() + try: + depth = 32 + depth_bytes = 4 + s1 = pygame.Surface((32, 32), pygame.SRCALPHA, depth) + + self.assertEqual(s1.get_bytesize(), depth_bytes) + self.assertEqual(s1.get_bitsize(), depth) + + depth = 15 + depth_bytes = 2 + s1 = pygame.Surface((32, 32), 0, depth) + + self.assertEqual(s1.get_bytesize(), depth_bytes) + self.assertEqual(s1.get_bitsize(), depth) + + depth = 12 + depth_bytes = 2 + s1 = pygame.Surface((32, 32), 0, depth) + + self.assertEqual(s1.get_bytesize(), depth_bytes) + self.assertEqual(s1.get_bitsize(), depth) + + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode() + pygame.display.quit() + surface.get_bytesize() + finally: + pygame.display.quit() + + ######################################################################## + + def test_get_flags(self): + """Ensure a surface's flags can be retrieved.""" + s1 = pygame.Surface((32, 32), pygame.SRCALPHA, 32) + + self.assertEqual(s1.get_flags(), pygame.SRCALPHA) + + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == "dummy", + 'requires a non-"dummy" SDL_VIDEODRIVER', + ) + def test_get_flags__display_surf(self): + pygame.display.init() + try: + # FULLSCREEN + screen_surf = pygame.display.set_mode((600, 400), flags=0) + self.assertFalse(screen_surf.get_flags() & pygame.FULLSCREEN) + + screen_surf = pygame.display.set_mode((600, 400), flags=pygame.FULLSCREEN) + self.assertTrue(screen_surf.get_flags() & pygame.FULLSCREEN) + + # NOFRAME + screen_surf = pygame.display.set_mode((600, 400), flags=0) + self.assertFalse(screen_surf.get_flags() & pygame.NOFRAME) + + screen_surf = pygame.display.set_mode((600, 400), flags=pygame.NOFRAME) + self.assertTrue(screen_surf.get_flags() & pygame.NOFRAME) + + # RESIZABLE + screen_surf = pygame.display.set_mode((600, 400), flags=0) + self.assertFalse(screen_surf.get_flags() & pygame.RESIZABLE) + + screen_surf = pygame.display.set_mode((600, 400), flags=pygame.RESIZABLE) + self.assertTrue(screen_surf.get_flags() & pygame.RESIZABLE) + + # OPENGL + screen_surf = pygame.display.set_mode((600, 400), flags=0) + # it can have an OPENGL flag by default on Macos? + if not (screen_surf.get_flags() & pygame.OPENGL): + self.assertFalse(screen_surf.get_flags() & pygame.OPENGL) + + try: + pygame.display.set_mode((200, 200), pygame.OPENGL, 32) + except pygame.error: + pass # If we can't create OPENGL surface don't try this test + else: + self.assertTrue(screen_surf.get_flags() & pygame.OPENGL) + finally: + pygame.display.quit() + + ######################################################################## + + def test_get_parent(self): + """Ensure a surface's parent can be retrieved.""" + pygame.display.init() + try: + parent = pygame.Surface((16, 16)) + child = parent.subsurface((0, 0, 5, 5)) + + self.assertIs(child.get_parent(), parent) + + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode() + pygame.display.quit() + surface.get_parent() + finally: + pygame.display.quit() + + ######################################################################## + + def test_get_rect(self): + """Ensure a surface's rect can be retrieved.""" + size = (16, 16) + surf = pygame.Surface(size) + rect = surf.get_rect() + + self.assertEqual(rect.size, size) + + ######################################################################## + + def test_get_width__size_and_height(self): + """Ensure a surface's size, width and height can be retrieved.""" + for w in range(0, 255, 32): + for h in range(0, 127, 15): + s = pygame.Surface((w, h)) + self.assertEqual(s.get_width(), w) + self.assertEqual(s.get_height(), h) + self.assertEqual(s.get_size(), (w, h)) + + def test_get_view(self): + """Ensure a buffer view of the surface's pixels can be retrieved.""" + # Check that BufferProxys are returned when array depth is supported, + # ValueErrors returned otherwise. + Error = ValueError + s = pygame.Surface((5, 7), 0, 8) + v2 = s.get_view("2") + + self.assertRaises(Error, s.get_view, "0") + self.assertRaises(Error, s.get_view, "1") + self.assertIsInstance(v2, BufferProxy) + self.assertRaises(Error, s.get_view, "3") + + s = pygame.Surface((8, 7), 0, 8) + length = s.get_bytesize() * s.get_width() * s.get_height() + v0 = s.get_view("0") + v1 = s.get_view("1") + + self.assertIsInstance(v0, BufferProxy) + self.assertEqual(v0.length, length) + self.assertIsInstance(v1, BufferProxy) + self.assertEqual(v1.length, length) + + s = pygame.Surface((5, 7), 0, 16) + v2 = s.get_view("2") + + self.assertRaises(Error, s.get_view, "0") + self.assertRaises(Error, s.get_view, "1") + self.assertIsInstance(v2, BufferProxy) + self.assertRaises(Error, s.get_view, "3") + + s = pygame.Surface((8, 7), 0, 16) + length = s.get_bytesize() * s.get_width() * s.get_height() + v0 = s.get_view("0") + v1 = s.get_view("1") + + self.assertIsInstance(v0, BufferProxy) + self.assertEqual(v0.length, length) + self.assertIsInstance(v1, BufferProxy) + self.assertEqual(v1.length, length) + + s = pygame.Surface((5, 7), pygame.SRCALPHA, 16) + v2 = s.get_view("2") + + self.assertIsInstance(v2, BufferProxy) + self.assertRaises(Error, s.get_view, "3") + + s = pygame.Surface((5, 7), 0, 24) + v2 = s.get_view("2") + v3 = s.get_view("3") + + self.assertRaises(Error, s.get_view, "0") + self.assertRaises(Error, s.get_view, "1") + self.assertIsInstance(v2, BufferProxy) + self.assertIsInstance(v3, BufferProxy) + + s = pygame.Surface((8, 7), 0, 24) + length = s.get_bytesize() * s.get_width() * s.get_height() + v0 = s.get_view("0") + v1 = s.get_view("1") + + self.assertIsInstance(v0, BufferProxy) + self.assertEqual(v0.length, length) + self.assertIsInstance(v1, BufferProxy) + self.assertEqual(v1.length, length) + + s = pygame.Surface((5, 7), 0, 32) + length = s.get_bytesize() * s.get_width() * s.get_height() + v0 = s.get_view("0") + v1 = s.get_view("1") + v2 = s.get_view("2") + v3 = s.get_view("3") + + self.assertIsInstance(v0, BufferProxy) + self.assertEqual(v0.length, length) + self.assertIsInstance(v1, BufferProxy) + self.assertEqual(v1.length, length) + self.assertIsInstance(v2, BufferProxy) + self.assertIsInstance(v3, BufferProxy) + + s2 = s.subsurface((0, 0, 4, 7)) + + self.assertRaises(Error, s2.get_view, "0") + self.assertRaises(Error, s2.get_view, "1") + + s2 = None + s = pygame.Surface((5, 7), pygame.SRCALPHA, 32) + + for kind in ("2", "3", "a", "A", "r", "R", "g", "G", "b", "B"): + self.assertIsInstance(s.get_view(kind), BufferProxy) + + # Check default argument value: '2' + s = pygame.Surface((2, 4), 0, 32) + v = s.get_view() + if not IS_PYPY: + ai = ArrayInterface(v) + self.assertEqual(ai.nd, 2) + + # Check locking. + s = pygame.Surface((2, 4), 0, 32) + + self.assertFalse(s.get_locked()) + + v = s.get_view("2") + + self.assertFalse(s.get_locked()) + + c = v.__array_interface__ + + self.assertTrue(s.get_locked()) + + c = None + gc.collect() + + self.assertTrue(s.get_locked()) + + v = None + gc.collect() + + self.assertFalse(s.get_locked()) + + # Check invalid view kind values. + s = pygame.Surface((2, 4), pygame.SRCALPHA, 32) + self.assertRaises(TypeError, s.get_view, "") + self.assertRaises(TypeError, s.get_view, "9") + self.assertRaises(TypeError, s.get_view, "RGBA") + self.assertRaises(TypeError, s.get_view, 2) + + # Both unicode and bytes strings are allowed for kind. + s = pygame.Surface((2, 4), 0, 32) + s.get_view("2") + s.get_view(b"2") + + # Garbage collection + s = pygame.Surface((2, 4), 0, 32) + weak_s = weakref.ref(s) + v = s.get_view("3") + weak_v = weakref.ref(v) + gc.collect() + self.assertTrue(weak_s() is s) + self.assertTrue(weak_v() is v) + del v + gc.collect() + self.assertTrue(weak_s() is s) + self.assertTrue(weak_v() is None) + del s + gc.collect() + self.assertTrue(weak_s() is None) + + def test_get_buffer(self): + # Check that get_buffer works for all pixel sizes and for a subsurface. + + # Check for all pixel sizes + for bitsize in [8, 16, 24, 32]: + s = pygame.Surface((5, 7), 0, bitsize) + length = s.get_pitch() * s.get_height() + v = s.get_buffer() + + self.assertIsInstance(v, BufferProxy) + self.assertEqual(v.length, length) + self.assertEqual(repr(v), "") + + # Check for a subsurface (not contiguous) + s = pygame.Surface((7, 10), 0, 32) + s2 = s.subsurface((1, 2, 5, 7)) + length = s2.get_pitch() * s2.get_height() + v = s2.get_buffer() + + self.assertIsInstance(v, BufferProxy) + self.assertEqual(v.length, length) + + # Check locking. + s = pygame.Surface((2, 4), 0, 32) + v = s.get_buffer() + self.assertTrue(s.get_locked()) + v = None + gc.collect() + self.assertFalse(s.get_locked()) + + OLDBUF = hasattr(pygame.bufferproxy, "get_segcount") + + @unittest.skipIf(not OLDBUF, "old buffer not available") + def test_get_buffer_oldbuf(self): + from pygame.bufferproxy import get_segcount, get_write_buffer + + s = pygame.Surface((2, 4), pygame.SRCALPHA, 32) + v = s.get_buffer() + segcount, buflen = get_segcount(v) + self.assertEqual(segcount, 1) + self.assertEqual(buflen, s.get_pitch() * s.get_height()) + seglen, segaddr = get_write_buffer(v, 0) + self.assertEqual(segaddr, s._pixels_address) + self.assertEqual(seglen, buflen) + + @unittest.skipIf(not OLDBUF, "old buffer not available") + def test_get_view_oldbuf(self): + from pygame.bufferproxy import get_segcount, get_write_buffer + + s = pygame.Surface((2, 4), pygame.SRCALPHA, 32) + v = s.get_view("1") + segcount, buflen = get_segcount(v) + self.assertEqual(segcount, 8) + self.assertEqual(buflen, s.get_pitch() * s.get_height()) + seglen, segaddr = get_write_buffer(v, 7) + self.assertEqual(segaddr, s._pixels_address + s.get_bytesize() * 7) + self.assertEqual(seglen, s.get_bytesize()) + + def test_set_colorkey(self): + + # __doc__ (as of 2008-06-25) for pygame.surface.Surface.set_colorkey: + + # Surface.set_colorkey(Color, flags=0): return None + # Surface.set_colorkey(None): return None + # Set the transparent colorkey + + s = pygame.Surface((16, 16), pygame.SRCALPHA, 32) + + colorkeys = ((20, 189, 20, 255), (128, 50, 50, 255), (23, 21, 255, 255)) + + for colorkey in colorkeys: + s.set_colorkey(colorkey) + + for t in range(4): + s.set_colorkey(s.get_colorkey()) + + self.assertEqual(s.get_colorkey(), colorkey) + + def test_set_masks(self): + s = pygame.Surface((32, 32)) + r, g, b, a = s.get_masks() + self.assertRaises(TypeError, s.set_masks, (b, g, r, a)) + + def test_set_shifts(self): + s = pygame.Surface((32, 32)) + r, g, b, a = s.get_shifts() + self.assertRaises(TypeError, s.set_shifts, (b, g, r, a)) + + def test_blit_keyword_args(self): + color = (1, 2, 3, 255) + s1 = pygame.Surface((4, 4), 0, 32) + s2 = pygame.Surface((2, 2), 0, 32) + s2.fill((1, 2, 3)) + s1.blit(special_flags=BLEND_ADD, source=s2, dest=(1, 1), area=s2.get_rect()) + self.assertEqual(s1.get_at((0, 0)), (0, 0, 0, 255)) + self.assertEqual(s1.get_at((1, 1)), color) + + def test_blit_big_rects(self): + """SDL2 can have more than 16 bits for x, y, width, height.""" + big_surf = pygame.Surface((100, 68000), 0, 32) + big_surf_color = (255, 0, 0) + big_surf.fill(big_surf_color) + + background = pygame.Surface((500, 500), 0, 32) + background_color = (0, 255, 0) + background.fill(background_color) + + # copy parts of the big_surf using more than 16bit parts. + background.blit(big_surf, (100, 100), area=(0, 16000, 100, 100)) + background.blit(big_surf, (200, 200), area=(0, 32000, 100, 100)) + background.blit(big_surf, (300, 300), area=(0, 66000, 100, 100)) + + # check that all three areas are drawn. + self.assertEqual(background.get_at((101, 101)), big_surf_color) + self.assertEqual(background.get_at((201, 201)), big_surf_color) + self.assertEqual(background.get_at((301, 301)), big_surf_color) + + # areas outside the 3 blitted areas not covered by those blits. + self.assertEqual(background.get_at((400, 301)), background_color) + self.assertEqual(background.get_at((400, 201)), background_color) + self.assertEqual(background.get_at((100, 201)), background_color) + self.assertEqual(background.get_at((99, 99)), background_color) + self.assertEqual(background.get_at((450, 450)), background_color) + + +class TestSurfaceBlit(unittest.TestCase): + """Tests basic blitting functionality and options.""" + + # __doc__ (as of 2008-08-02) for pygame.surface.Surface.blit: + + # Surface.blit(source, dest, area=None, special_flags = 0): return Rect + # draw one image onto another + # + # Draws a source Surface onto this Surface. The draw can be positioned + # with the dest argument. Dest can either be pair of coordinates + # representing the upper left corner of the source. A Rect can also be + # passed as the destination and the topleft corner of the rectangle + # will be used as the position for the blit. The size of the + # destination rectangle does not effect the blit. + # + # An optional area rectangle can be passed as well. This represents a + # smaller portion of the source Surface to draw. + # + # An optional special flags is for passing in new in 1.8.0: BLEND_ADD, + # BLEND_SUB, BLEND_MULT, BLEND_MIN, BLEND_MAX new in 1.8.1: + # BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, + # BLEND_RGBA_MAX BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, + # BLEND_RGB_MIN, BLEND_RGB_MAX With other special blitting flags + # perhaps added in the future. + # + # The return rectangle is the area of the affected pixels, excluding + # any pixels outside the destination Surface, or outside the clipping + # area. + # + # Pixel alphas will be ignored when blitting to an 8 bit Surface. + # special_flags new in pygame 1.8. + + def setUp(self): + """Resets starting surfaces.""" + self.src_surface = pygame.Surface((256, 256), 32) + self.src_surface.fill(pygame.Color(255, 255, 255)) + self.dst_surface = pygame.Surface((64, 64), 32) + self.dst_surface.fill(pygame.Color(0, 0, 0)) + + def test_blit_overflow_coord(self): + """Full coverage w/ overflow, specified with Coordinate""" + result = self.dst_surface.blit(self.src_surface, (0, 0)) + self.assertIsInstance(result, pygame.Rect) + self.assertEqual(result.size, (64, 64)) + for k in [(x, x) for x in range(64)]: + self.assertEqual(self.dst_surface.get_at(k), (255, 255, 255)) + + def test_blit_overflow_rect(self): + """Full coverage w/ overflow, specified with a Rect""" + result = self.dst_surface.blit(self.src_surface, pygame.Rect(-1, -1, 300, 300)) + self.assertIsInstance(result, pygame.Rect) + self.assertEqual(result.size, (64, 64)) + for k in [(x, x) for x in range(64)]: + self.assertEqual(self.dst_surface.get_at(k), (255, 255, 255)) + + def test_blit_overflow_nonorigin(self): + """Test Rectange Dest, with overflow but with starting rect with top-left at (1,1)""" + result = self.dst_surface.blit(self.src_surface, dest=pygame.Rect((1, 1, 1, 1))) + self.assertIsInstance(result, pygame.Rect) + self.assertEqual(result.size, (63, 63)) + self.assertEqual(self.dst_surface.get_at((0, 0)), (0, 0, 0)) + self.assertEqual(self.dst_surface.get_at((63, 0)), (0, 0, 0)) + self.assertEqual(self.dst_surface.get_at((0, 63)), (0, 0, 0)) + self.assertEqual(self.dst_surface.get_at((1, 1)), (255, 255, 255)) + self.assertEqual(self.dst_surface.get_at((63, 63)), (255, 255, 255)) + + def test_blit_area_contraint(self): + """Testing area constraint""" + result = self.dst_surface.blit( + self.src_surface, + dest=pygame.Rect((1, 1, 1, 1)), + area=pygame.Rect((2, 2, 2, 2)), + ) + self.assertIsInstance(result, pygame.Rect) + self.assertEqual(result.size, (2, 2)) + self.assertEqual(self.dst_surface.get_at((0, 0)), (0, 0, 0)) # Corners + self.assertEqual(self.dst_surface.get_at((63, 0)), (0, 0, 0)) + self.assertEqual(self.dst_surface.get_at((0, 63)), (0, 0, 0)) + self.assertEqual(self.dst_surface.get_at((63, 63)), (0, 0, 0)) + self.assertEqual( + self.dst_surface.get_at((1, 1)), (255, 255, 255) + ) # Blitted Area + self.assertEqual(self.dst_surface.get_at((2, 2)), (255, 255, 255)) + self.assertEqual(self.dst_surface.get_at((3, 3)), (0, 0, 0)) + # Should stop short of filling in (3,3) + + def test_blit_zero_overlap(self): + """Testing zero-overlap condition.""" + result = self.dst_surface.blit( + self.src_surface, + dest=pygame.Rect((-256, -256, 1, 1)), + area=pygame.Rect((2, 2, 256, 256)), + ) + self.assertIsInstance(result, pygame.Rect) + self.assertEqual(result.size, (0, 0)) # No blitting expected + for k in [(x, x) for x in range(64)]: + self.assertEqual(self.dst_surface.get_at(k), (0, 0, 0)) # Diagonal + self.assertEqual( + self.dst_surface.get_at((63, 0)), (0, 0, 0) + ) # Remaining corners + self.assertEqual(self.dst_surface.get_at((0, 63)), (0, 0, 0)) + + def test_blit__SRCALPHA_opaque_source(self): + src = pygame.Surface((256, 256), SRCALPHA, 32) + dst = src.copy() + + for i, j in test_utils.rect_area_pts(src.get_rect()): + dst.set_at((i, j), (i, 0, 0, j)) + src.set_at((i, j), (0, i, 0, 255)) + + dst.blit(src, (0, 0)) + + for pt in test_utils.rect_area_pts(src.get_rect()): + self.assertEqual(dst.get_at(pt)[1], src.get_at(pt)[1]) + + def test_blit__blit_to_self(self): + """Test that blit operation works on self, alpha value is + correct, and that no RGB distortion occurs.""" + test_surface = pygame.Surface((128, 128), SRCALPHA, 32) + area = test_surface.get_rect() + + for pt, test_color in test_utils.gradient(area.width, area.height): + test_surface.set_at(pt, test_color) + + reference_surface = test_surface.copy() + + test_surface.blit(test_surface, (0, 0)) + + for x in range(area.width): + for y in range(area.height): + (r, g, b, a) = reference_color = reference_surface.get_at((x, y)) + expected_color = (r, g, b, (a + (a * ((256 - a) // 256)))) + self.assertEqual(reference_color, expected_color) + + self.assertEqual(reference_surface.get_rect(), test_surface.get_rect()) + + def test_blit__SRCALPHA_to_SRCALPHA_non_zero(self): + """Tests blitting a nonzero alpha surface to another nonzero alpha surface + both straight alpha compositing method. Test is fuzzy (+/- 1/256) to account for + different implementations in SDL1 and SDL2. + """ + + size = (32, 32) + + def check_color_diff(color1, color2): + """Returns True if two colors are within (1, 1, 1, 1) of each other.""" + for val in color1 - color2: + if abs(val) > 1: + return False + return True + + def high_a_onto_low(high, low): + """Tests straight alpha case. Source is low alpha, destination is high alpha""" + high_alpha_surface = pygame.Surface(size, pygame.SRCALPHA, 32) + low_alpha_surface = high_alpha_surface.copy() + high_alpha_color = Color( + (high, high, low, high) + ) # Injecting some RGB variance. + low_alpha_color = Color((high, low, low, low)) + high_alpha_surface.fill(high_alpha_color) + low_alpha_surface.fill(low_alpha_color) + + high_alpha_surface.blit(low_alpha_surface, (0, 0)) + + expected_color = low_alpha_color + Color( + tuple( + ((x * (255 - low_alpha_color.a)) // 255) for x in high_alpha_color + ) + ) + self.assertTrue( + check_color_diff(high_alpha_surface.get_at((0, 0)), expected_color) + ) + + def low_a_onto_high(high, low): + """Tests straight alpha case. Source is high alpha, destination is low alpha""" + high_alpha_surface = pygame.Surface(size, pygame.SRCALPHA, 32) + low_alpha_surface = high_alpha_surface.copy() + high_alpha_color = Color( + (high, high, low, high) + ) # Injecting some RGB variance. + low_alpha_color = Color((high, low, low, low)) + high_alpha_surface.fill(high_alpha_color) + low_alpha_surface.fill(low_alpha_color) + + low_alpha_surface.blit(high_alpha_surface, (0, 0)) + + expected_color = high_alpha_color + Color( + tuple( + ((x * (255 - high_alpha_color.a)) // 255) for x in low_alpha_color + ) + ) + self.assertTrue( + check_color_diff(low_alpha_surface.get_at((0, 0)), expected_color) + ) + + for low_a in range(0, 128): + for high_a in range(128, 256): + high_a_onto_low(high_a, low_a) + low_a_onto_high(high_a, low_a) + + def test_blit__SRCALPHA32_to_8(self): + # Bug: fatal + # SDL_DisplayConvert segfaults when video is uninitialized. + target = pygame.Surface((11, 8), 0, 8) + test_color = target.get_palette_at(2) + source = pygame.Surface((1, 1), pygame.SRCALPHA, 32) + source.set_at((0, 0), test_color) + target.blit(source, (0, 0)) + + +class GeneralSurfaceTests(AssertRaisesRegexMixin, unittest.TestCase): + @unittest.skipIf( + os.environ.get("SDL_VIDEODRIVER") == "dummy", + 'requires a non-"dummy" SDL_VIDEODRIVER', + ) + def test_image_convert_bug_131(self): + # Bitbucket bug #131: Unable to Surface.convert(32) some 1-bit images. + # https://bitbucket.org/pygame/pygame/issue/131/unable-to-surfaceconvert-32-some-1-bit + + pygame.display.init() + try: + pygame.display.set_mode((640, 480)) + + im = pygame.image.load(example_path(os.path.join("data", "city.png"))) + im2 = pygame.image.load(example_path(os.path.join("data", "brick.png"))) + + self.assertEqual(im.get_palette(), ((0, 0, 0, 255), (255, 255, 255, 255))) + self.assertEqual(im2.get_palette(), ((0, 0, 0, 255), (0, 0, 0, 255))) + + self.assertEqual(repr(im.convert(32)), "") + self.assertEqual(repr(im2.convert(32)), "") + + # Ensure a palette format to palette format works. + im3 = im.convert(8) + self.assertEqual(repr(im3), "") + self.assertEqual(im3.get_palette(), im.get_palette()) + + finally: + pygame.display.quit() + + def test_convert_init(self): + """Ensure initialization exceptions are raised + for surf.convert().""" + pygame.display.quit() + surf = pygame.Surface((1, 1)) + + self.assertRaisesRegex(pygame.error, "display initialized", surf.convert) + + pygame.display.init() + try: + if os.environ.get("SDL_VIDEODRIVER") != "dummy": + try: + surf.convert(32) + surf.convert(pygame.Surface((1, 1))) + except pygame.error: + self.fail("convert() should not raise an exception here.") + + self.assertRaisesRegex(pygame.error, "No video mode", surf.convert) + + pygame.display.set_mode((640, 480)) + try: + surf.convert() + except pygame.error: + self.fail("convert() should not raise an exception here.") + finally: + pygame.display.quit() + + def test_convert_alpha_init(self): + """Ensure initialization exceptions are raised + for surf.convert_alpha().""" + pygame.display.quit() + surf = pygame.Surface((1, 1)) + + self.assertRaisesRegex(pygame.error, "display initialized", surf.convert_alpha) + + pygame.display.init() + try: + self.assertRaisesRegex(pygame.error, "No video mode", surf.convert_alpha) + + pygame.display.set_mode((640, 480)) + try: + surf.convert_alpha() + except pygame.error: + self.fail("convert_alpha() should not raise an exception here.") + finally: + pygame.display.quit() + + def test_convert_alpha_SRCALPHA(self): + """Ensure that the surface returned by surf.convert_alpha() + has alpha blending enabled""" + pygame.display.init() + try: + pygame.display.set_mode((640, 480)) + + s1 = pygame.Surface((100, 100), 0, 32) + # s2=pygame.Surface((100,100), pygame.SRCALPHA, 32) + s1_alpha = s1.convert_alpha() + self.assertEqual(s1_alpha.get_flags() & SRCALPHA, SRCALPHA) + self.assertEqual(s1_alpha.get_alpha(), 255) + finally: + pygame.display.quit() + + def test_src_alpha_issue_1289(self): + """blit should be white.""" + surf1 = pygame.Surface((1, 1), pygame.SRCALPHA, 32) + surf1.fill((255, 255, 255, 100)) + + surf2 = pygame.Surface((1, 1), pygame.SRCALPHA, 32) + self.assertEqual(surf2.get_at((0, 0)), (0, 0, 0, 0)) + surf2.blit(surf1, (0, 0)) + + self.assertEqual(surf1.get_at((0, 0)), (255, 255, 255, 100)) + self.assertEqual(surf2.get_at((0, 0)), (255, 255, 255, 100)) + + def test_src_alpha_compatible(self): + """ "What pygame 1.9.x did". Is the alpha blitter as before?""" + + # The table below was generated with the SDL1 blit. + # def print_table(): + # nums = [0, 1, 65, 126, 127, 199, 254, 255] + # results = {} + # for dest_r, dest_b, dest_a in zip(nums, reversed(nums), reversed(nums)): + # for src_r, src_b, src_a in zip(nums, reversed(nums), nums): + # src_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + # src_surf.fill((src_r, 255, src_b, src_a)) + # dest_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + # dest_surf.fill((dest_r, 255, dest_b, dest_a)) + # dest_surf.blit(src_surf, (0, 0)) + # key = ((dest_r, dest_b, dest_a), (src_r, src_b, src_a)) + # results[key] = dest_surf.get_at((65, 33)) + # print("(dest_r, dest_b, dest_a), (src_r, src_b, src_a): color") + # pprint(results) + + results_expected = { + ((0, 255, 255), (0, 255, 0)): (0, 255, 255, 255), + ((0, 255, 255), (1, 254, 1)): (0, 255, 255, 255), + ((0, 255, 255), (65, 199, 65)): (16, 255, 241, 255), + ((0, 255, 255), (126, 127, 126)): (62, 255, 192, 255), + ((0, 255, 255), (127, 126, 127)): (63, 255, 191, 255), + ((0, 255, 255), (199, 65, 199)): (155, 255, 107, 255), + ((0, 255, 255), (254, 1, 254)): (253, 255, 2, 255), + ((0, 255, 255), (255, 0, 255)): (255, 255, 0, 255), + ((1, 254, 254), (0, 255, 0)): (1, 255, 254, 254), + ((1, 254, 254), (1, 254, 1)): (1, 255, 254, 255), + ((1, 254, 254), (65, 199, 65)): (17, 255, 240, 255), + ((1, 254, 254), (126, 127, 126)): (63, 255, 191, 255), + ((1, 254, 254), (127, 126, 127)): (64, 255, 190, 255), + ((1, 254, 254), (199, 65, 199)): (155, 255, 107, 255), + ((1, 254, 254), (254, 1, 254)): (253, 255, 2, 255), + ((1, 254, 254), (255, 0, 255)): (255, 255, 0, 255), + ((65, 199, 199), (0, 255, 0)): (65, 255, 199, 199), + ((65, 199, 199), (1, 254, 1)): (64, 255, 200, 200), + ((65, 199, 199), (65, 199, 65)): (65, 255, 199, 214), + ((65, 199, 199), (126, 127, 126)): (95, 255, 164, 227), + ((65, 199, 199), (127, 126, 127)): (96, 255, 163, 227), + ((65, 199, 199), (199, 65, 199)): (169, 255, 95, 243), + ((65, 199, 199), (254, 1, 254)): (253, 255, 2, 255), + ((65, 199, 199), (255, 0, 255)): (255, 255, 0, 255), + ((126, 127, 127), (0, 255, 0)): (126, 255, 127, 127), + ((126, 127, 127), (1, 254, 1)): (125, 255, 128, 128), + ((126, 127, 127), (65, 199, 65)): (110, 255, 146, 160), + ((126, 127, 127), (126, 127, 126)): (126, 255, 127, 191), + ((126, 127, 127), (127, 126, 127)): (126, 255, 126, 191), + ((126, 127, 127), (199, 65, 199)): (183, 255, 79, 227), + ((126, 127, 127), (254, 1, 254)): (253, 255, 1, 255), + ((126, 127, 127), (255, 0, 255)): (255, 255, 0, 255), + ((127, 126, 126), (0, 255, 0)): (127, 255, 126, 126), + ((127, 126, 126), (1, 254, 1)): (126, 255, 127, 127), + ((127, 126, 126), (65, 199, 65)): (111, 255, 145, 159), + ((127, 126, 126), (126, 127, 126)): (127, 255, 126, 190), + ((127, 126, 126), (127, 126, 127)): (127, 255, 126, 191), + ((127, 126, 126), (199, 65, 199)): (183, 255, 78, 227), + ((127, 126, 126), (254, 1, 254)): (254, 255, 1, 255), + ((127, 126, 126), (255, 0, 255)): (255, 255, 0, 255), + ((199, 65, 65), (0, 255, 0)): (199, 255, 65, 65), + ((199, 65, 65), (1, 254, 1)): (198, 255, 66, 66), + ((199, 65, 65), (65, 199, 65)): (165, 255, 99, 114), + ((199, 65, 65), (126, 127, 126)): (163, 255, 96, 159), + ((199, 65, 65), (127, 126, 127)): (163, 255, 95, 160), + ((199, 65, 65), (199, 65, 199)): (199, 255, 65, 214), + ((199, 65, 65), (254, 1, 254)): (254, 255, 1, 255), + ((199, 65, 65), (255, 0, 255)): (255, 255, 0, 255), + ((254, 1, 1), (0, 255, 0)): (254, 255, 1, 1), + ((254, 1, 1), (1, 254, 1)): (253, 255, 2, 2), + ((254, 1, 1), (65, 199, 65)): (206, 255, 52, 66), + ((254, 1, 1), (126, 127, 126)): (191, 255, 63, 127), + ((254, 1, 1), (127, 126, 127)): (191, 255, 63, 128), + ((254, 1, 1), (199, 65, 199)): (212, 255, 51, 200), + ((254, 1, 1), (254, 1, 254)): (254, 255, 1, 255), + ((254, 1, 1), (255, 0, 255)): (255, 255, 0, 255), + ((255, 0, 0), (0, 255, 0)): (0, 255, 255, 0), + ((255, 0, 0), (1, 254, 1)): (1, 255, 254, 1), + ((255, 0, 0), (65, 199, 65)): (65, 255, 199, 65), + ((255, 0, 0), (126, 127, 126)): (126, 255, 127, 126), + ((255, 0, 0), (127, 126, 127)): (127, 255, 126, 127), + ((255, 0, 0), (199, 65, 199)): (199, 255, 65, 199), + ((255, 0, 0), (254, 1, 254)): (254, 255, 1, 254), + ((255, 0, 0), (255, 0, 255)): (255, 255, 0, 255), + } + + # chosen because they contain edge cases. + nums = [0, 1, 65, 126, 127, 199, 254, 255] + results = {} + for dst_r, dst_b, dst_a in zip(nums, reversed(nums), reversed(nums)): + for src_r, src_b, src_a in zip(nums, reversed(nums), nums): + with self.subTest( + src_r=src_r, + src_b=src_b, + src_a=src_a, + dest_r=dst_r, + dest_b=dst_b, + dest_a=dst_a, + ): + src_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + src_surf.fill((src_r, 255, src_b, src_a)) + dest_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + dest_surf.fill((dst_r, 255, dst_b, dst_a)) + + dest_surf.blit(src_surf, (0, 0)) + key = ((dst_r, dst_b, dst_a), (src_r, src_b, src_a)) + results[key] = dest_surf.get_at((65, 33)) + self.assertEqual(results[key], results_expected[key]) + + self.assertEqual(results, results_expected) + + def test_src_alpha_compatible_16bit(self): + """ "What pygame 1.9.x did". Is the alpha blitter as before?""" + + # The table below was generated with the SDL1 blit. + # def print_table(): + # nums = [0, 1, 65, 126, 127, 199, 254, 255] + # results = {} + # for dest_r, dest_b, dest_a in zip(nums, reversed(nums), reversed(nums)): + # for src_r, src_b, src_a in zip(nums, reversed(nums), nums): + # src_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 16) + # src_surf.fill((src_r, 255, src_b, src_a)) + # dest_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 16) + # dest_surf.fill((dest_r, 255, dest_b, dest_a)) + # dest_surf.blit(src_surf, (0, 0)) + # key = ((dest_r, dest_b, dest_a), (src_r, src_b, src_a)) + # results[key] = dest_surf.get_at((65, 33)) + # print("(dest_r, dest_b, dest_a), (src_r, src_b, src_a): color") + # pprint(results) + + results_expected = { + ((0, 255, 255), (0, 255, 0)): (0, 255, 255, 255), + ((0, 255, 255), (1, 254, 1)): (0, 255, 255, 255), + ((0, 255, 255), (65, 199, 65)): (17, 255, 255, 255), + ((0, 255, 255), (126, 127, 126)): (51, 255, 204, 255), + ((0, 255, 255), (127, 126, 127)): (51, 255, 204, 255), + ((0, 255, 255), (199, 65, 199)): (170, 255, 102, 255), + ((0, 255, 255), (254, 1, 254)): (255, 255, 0, 255), + ((0, 255, 255), (255, 0, 255)): (255, 255, 0, 255), + ((1, 254, 254), (0, 255, 0)): (0, 255, 255, 255), + ((1, 254, 254), (1, 254, 1)): (0, 255, 255, 255), + ((1, 254, 254), (65, 199, 65)): (17, 255, 255, 255), + ((1, 254, 254), (126, 127, 126)): (51, 255, 204, 255), + ((1, 254, 254), (127, 126, 127)): (51, 255, 204, 255), + ((1, 254, 254), (199, 65, 199)): (170, 255, 102, 255), + ((1, 254, 254), (254, 1, 254)): (255, 255, 0, 255), + ((1, 254, 254), (255, 0, 255)): (255, 255, 0, 255), + ((65, 199, 199), (0, 255, 0)): (68, 255, 204, 204), + ((65, 199, 199), (1, 254, 1)): (68, 255, 204, 204), + ((65, 199, 199), (65, 199, 65)): (68, 255, 204, 221), + ((65, 199, 199), (126, 127, 126)): (85, 255, 170, 238), + ((65, 199, 199), (127, 126, 127)): (85, 255, 170, 238), + ((65, 199, 199), (199, 65, 199)): (187, 255, 85, 255), + ((65, 199, 199), (254, 1, 254)): (255, 255, 0, 255), + ((65, 199, 199), (255, 0, 255)): (255, 255, 0, 255), + ((126, 127, 127), (0, 255, 0)): (119, 255, 119, 119), + ((126, 127, 127), (1, 254, 1)): (119, 255, 119, 119), + ((126, 127, 127), (65, 199, 65)): (102, 255, 136, 153), + ((126, 127, 127), (126, 127, 126)): (119, 255, 119, 187), + ((126, 127, 127), (127, 126, 127)): (119, 255, 119, 187), + ((126, 127, 127), (199, 65, 199)): (187, 255, 68, 238), + ((126, 127, 127), (254, 1, 254)): (255, 255, 0, 255), + ((126, 127, 127), (255, 0, 255)): (255, 255, 0, 255), + ((127, 126, 126), (0, 255, 0)): (119, 255, 119, 119), + ((127, 126, 126), (1, 254, 1)): (119, 255, 119, 119), + ((127, 126, 126), (65, 199, 65)): (102, 255, 136, 153), + ((127, 126, 126), (126, 127, 126)): (119, 255, 119, 187), + ((127, 126, 126), (127, 126, 127)): (119, 255, 119, 187), + ((127, 126, 126), (199, 65, 199)): (187, 255, 68, 238), + ((127, 126, 126), (254, 1, 254)): (255, 255, 0, 255), + ((127, 126, 126), (255, 0, 255)): (255, 255, 0, 255), + ((199, 65, 65), (0, 255, 0)): (204, 255, 68, 68), + ((199, 65, 65), (1, 254, 1)): (204, 255, 68, 68), + ((199, 65, 65), (65, 199, 65)): (170, 255, 102, 119), + ((199, 65, 65), (126, 127, 126)): (170, 255, 85, 153), + ((199, 65, 65), (127, 126, 127)): (170, 255, 85, 153), + ((199, 65, 65), (199, 65, 199)): (204, 255, 68, 221), + ((199, 65, 65), (254, 1, 254)): (255, 255, 0, 255), + ((199, 65, 65), (255, 0, 255)): (255, 255, 0, 255), + ((254, 1, 1), (0, 255, 0)): (0, 255, 255, 0), + ((254, 1, 1), (1, 254, 1)): (0, 255, 255, 0), + ((254, 1, 1), (65, 199, 65)): (68, 255, 204, 68), + ((254, 1, 1), (126, 127, 126)): (119, 255, 119, 119), + ((254, 1, 1), (127, 126, 127)): (119, 255, 119, 119), + ((254, 1, 1), (199, 65, 199)): (204, 255, 68, 204), + ((254, 1, 1), (254, 1, 254)): (255, 255, 0, 255), + ((254, 1, 1), (255, 0, 255)): (255, 255, 0, 255), + ((255, 0, 0), (0, 255, 0)): (0, 255, 255, 0), + ((255, 0, 0), (1, 254, 1)): (0, 255, 255, 0), + ((255, 0, 0), (65, 199, 65)): (68, 255, 204, 68), + ((255, 0, 0), (126, 127, 126)): (119, 255, 119, 119), + ((255, 0, 0), (127, 126, 127)): (119, 255, 119, 119), + ((255, 0, 0), (199, 65, 199)): (204, 255, 68, 204), + ((255, 0, 0), (254, 1, 254)): (255, 255, 0, 255), + ((255, 0, 0), (255, 0, 255)): (255, 255, 0, 255), + } + + # chosen because they contain edge cases. + nums = [0, 1, 65, 126, 127, 199, 254, 255] + results = {} + for dst_r, dst_b, dst_a in zip(nums, reversed(nums), reversed(nums)): + for src_r, src_b, src_a in zip(nums, reversed(nums), nums): + with self.subTest( + src_r=src_r, + src_b=src_b, + src_a=src_a, + dest_r=dst_r, + dest_b=dst_b, + dest_a=dst_a, + ): + src_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 16) + src_surf.fill((src_r, 255, src_b, src_a)) + dest_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 16) + dest_surf.fill((dst_r, 255, dst_b, dst_a)) + + dest_surf.blit(src_surf, (0, 0)) + key = ((dst_r, dst_b, dst_a), (src_r, src_b, src_a)) + results[key] = dest_surf.get_at((65, 33)) + self.assertEqual(results[key], results_expected[key]) + + self.assertEqual(results, results_expected) + + def test_sdl1_mimic_blitter_with_set_alpha(self): + """does the SDL 1 style blitter in pygame 2 work with set_alpha(), + this feature only exists in pygame 2/SDL2 SDL1 did not support + combining surface and pixel alpha""" + + results_expected = { + ((0, 255, 255), (0, 255, 0)): (0, 255, 255, 255), + ((0, 255, 255), (1, 254, 1)): (0, 255, 255, 255), + ((0, 255, 255), (65, 199, 65)): (16, 255, 241, 255), + ((0, 255, 255), (126, 127, 126)): (62, 255, 192, 255), + ((0, 255, 255), (127, 126, 127)): (63, 255, 191, 255), + ((0, 255, 255), (199, 65, 199)): (155, 255, 107, 255), + ((0, 255, 255), (254, 1, 254)): (253, 255, 2, 255), + ((0, 255, 255), (255, 0, 255)): (255, 255, 0, 255), + ((1, 254, 254), (0, 255, 0)): (1, 255, 254, 254), + ((1, 254, 254), (1, 254, 1)): (1, 255, 254, 255), + ((1, 254, 254), (65, 199, 65)): (17, 255, 240, 255), + ((1, 254, 254), (126, 127, 126)): (63, 255, 191, 255), + ((1, 254, 254), (127, 126, 127)): (64, 255, 190, 255), + ((1, 254, 254), (199, 65, 199)): (155, 255, 107, 255), + ((1, 254, 254), (254, 1, 254)): (253, 255, 2, 255), + ((1, 254, 254), (255, 0, 255)): (255, 255, 0, 255), + ((65, 199, 199), (0, 255, 0)): (65, 255, 199, 199), + ((65, 199, 199), (1, 254, 1)): (64, 255, 200, 200), + ((65, 199, 199), (65, 199, 65)): (65, 255, 199, 214), + ((65, 199, 199), (126, 127, 126)): (95, 255, 164, 227), + ((65, 199, 199), (127, 126, 127)): (96, 255, 163, 227), + ((65, 199, 199), (199, 65, 199)): (169, 255, 95, 243), + ((65, 199, 199), (254, 1, 254)): (253, 255, 2, 255), + ((65, 199, 199), (255, 0, 255)): (255, 255, 0, 255), + ((126, 127, 127), (0, 255, 0)): (126, 255, 127, 127), + ((126, 127, 127), (1, 254, 1)): (125, 255, 128, 128), + ((126, 127, 127), (65, 199, 65)): (110, 255, 146, 160), + ((126, 127, 127), (126, 127, 126)): (126, 255, 127, 191), + ((126, 127, 127), (127, 126, 127)): (126, 255, 126, 191), + ((126, 127, 127), (199, 65, 199)): (183, 255, 79, 227), + ((126, 127, 127), (254, 1, 254)): (253, 255, 1, 255), + ((126, 127, 127), (255, 0, 255)): (255, 255, 0, 255), + ((127, 126, 126), (0, 255, 0)): (127, 255, 126, 126), + ((127, 126, 126), (1, 254, 1)): (126, 255, 127, 127), + ((127, 126, 126), (65, 199, 65)): (111, 255, 145, 159), + ((127, 126, 126), (126, 127, 126)): (127, 255, 126, 190), + ((127, 126, 126), (127, 126, 127)): (127, 255, 126, 191), + ((127, 126, 126), (199, 65, 199)): (183, 255, 78, 227), + ((127, 126, 126), (254, 1, 254)): (254, 255, 1, 255), + ((127, 126, 126), (255, 0, 255)): (255, 255, 0, 255), + ((199, 65, 65), (0, 255, 0)): (199, 255, 65, 65), + ((199, 65, 65), (1, 254, 1)): (198, 255, 66, 66), + ((199, 65, 65), (65, 199, 65)): (165, 255, 99, 114), + ((199, 65, 65), (126, 127, 126)): (163, 255, 96, 159), + ((199, 65, 65), (127, 126, 127)): (163, 255, 95, 160), + ((199, 65, 65), (199, 65, 199)): (199, 255, 65, 214), + ((199, 65, 65), (254, 1, 254)): (254, 255, 1, 255), + ((199, 65, 65), (255, 0, 255)): (255, 255, 0, 255), + ((254, 1, 1), (0, 255, 0)): (254, 255, 1, 1), + ((254, 1, 1), (1, 254, 1)): (253, 255, 2, 2), + ((254, 1, 1), (65, 199, 65)): (206, 255, 52, 66), + ((254, 1, 1), (126, 127, 126)): (191, 255, 63, 127), + ((254, 1, 1), (127, 126, 127)): (191, 255, 63, 128), + ((254, 1, 1), (199, 65, 199)): (212, 255, 51, 200), + ((254, 1, 1), (254, 1, 254)): (254, 255, 1, 255), + ((254, 1, 1), (255, 0, 255)): (255, 255, 0, 255), + ((255, 0, 0), (0, 255, 0)): (0, 255, 255, 0), + ((255, 0, 0), (1, 254, 1)): (1, 255, 254, 1), + ((255, 0, 0), (65, 199, 65)): (65, 255, 199, 65), + ((255, 0, 0), (126, 127, 126)): (126, 255, 127, 126), + ((255, 0, 0), (127, 126, 127)): (127, 255, 126, 127), + ((255, 0, 0), (199, 65, 199)): (199, 255, 65, 199), + ((255, 0, 0), (254, 1, 254)): (254, 255, 1, 254), + ((255, 0, 0), (255, 0, 255)): (255, 255, 0, 255), + } + + # chosen because they contain edge cases. + nums = [0, 1, 65, 126, 127, 199, 254, 255] + results = {} + for dst_r, dst_b, dst_a in zip(nums, reversed(nums), reversed(nums)): + for src_r, src_b, src_a in zip(nums, reversed(nums), nums): + with self.subTest( + src_r=src_r, + src_b=src_b, + src_a=src_a, + dest_r=dst_r, + dest_b=dst_b, + dest_a=dst_a, + ): + src_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + src_surf.fill((src_r, 255, src_b, 255)) + src_surf.set_alpha(src_a) + dest_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + dest_surf.fill((dst_r, 255, dst_b, dst_a)) + + dest_surf.blit(src_surf, (0, 0)) + key = ((dst_r, dst_b, dst_a), (src_r, src_b, src_a)) + results[key] = dest_surf.get_at((65, 33)) + self.assertEqual(results[key], results_expected[key]) + + self.assertEqual(results, results_expected) + + @unittest.skipIf( + "arm" in platform.machine() or "aarch64" in platform.machine(), + "sdl2 blitter produces different results on arm", + ) + def test_src_alpha_sdl2_blitter(self): + """Checking that the BLEND_ALPHA_SDL2 flag works - this feature + only exists when using SDL2""" + + results_expected = { + ((0, 255, 255), (0, 255, 0)): (0, 255, 255, 255), + ((0, 255, 255), (1, 254, 1)): (0, 253, 253, 253), + ((0, 255, 255), (65, 199, 65)): (16, 253, 239, 253), + ((0, 255, 255), (126, 127, 126)): (62, 253, 190, 253), + ((0, 255, 255), (127, 126, 127)): (63, 253, 189, 253), + ((0, 255, 255), (199, 65, 199)): (154, 253, 105, 253), + ((0, 255, 255), (254, 1, 254)): (252, 253, 0, 253), + ((0, 255, 255), (255, 0, 255)): (255, 255, 0, 255), + ((1, 254, 254), (0, 255, 0)): (1, 255, 254, 254), + ((1, 254, 254), (1, 254, 1)): (0, 253, 252, 252), + ((1, 254, 254), (65, 199, 65)): (16, 253, 238, 252), + ((1, 254, 254), (126, 127, 126)): (62, 253, 189, 252), + ((1, 254, 254), (127, 126, 127)): (63, 253, 189, 253), + ((1, 254, 254), (199, 65, 199)): (154, 253, 105, 253), + ((1, 254, 254), (254, 1, 254)): (252, 253, 0, 253), + ((1, 254, 254), (255, 0, 255)): (255, 255, 0, 255), + ((65, 199, 199), (0, 255, 0)): (65, 255, 199, 199), + ((65, 199, 199), (1, 254, 1)): (64, 253, 197, 197), + ((65, 199, 199), (65, 199, 65)): (64, 253, 197, 211), + ((65, 199, 199), (126, 127, 126)): (94, 253, 162, 225), + ((65, 199, 199), (127, 126, 127)): (95, 253, 161, 225), + ((65, 199, 199), (199, 65, 199)): (168, 253, 93, 241), + ((65, 199, 199), (254, 1, 254)): (252, 253, 0, 253), + ((65, 199, 199), (255, 0, 255)): (255, 255, 0, 255), + ((126, 127, 127), (0, 255, 0)): (126, 255, 127, 127), + ((126, 127, 127), (1, 254, 1)): (125, 253, 126, 126), + ((126, 127, 127), (65, 199, 65)): (109, 253, 144, 158), + ((126, 127, 127), (126, 127, 126)): (125, 253, 125, 188), + ((126, 127, 127), (127, 126, 127)): (126, 253, 125, 189), + ((126, 127, 127), (199, 65, 199)): (181, 253, 77, 225), + ((126, 127, 127), (254, 1, 254)): (252, 253, 0, 253), + ((126, 127, 127), (255, 0, 255)): (255, 255, 0, 255), + ((127, 126, 126), (0, 255, 0)): (127, 255, 126, 126), + ((127, 126, 126), (1, 254, 1)): (126, 253, 125, 125), + ((127, 126, 126), (65, 199, 65)): (110, 253, 143, 157), + ((127, 126, 126), (126, 127, 126)): (125, 253, 125, 188), + ((127, 126, 126), (127, 126, 127)): (126, 253, 125, 189), + ((127, 126, 126), (199, 65, 199)): (181, 253, 77, 225), + ((127, 126, 126), (254, 1, 254)): (252, 253, 0, 253), + ((127, 126, 126), (255, 0, 255)): (255, 255, 0, 255), + ((199, 65, 65), (0, 255, 0)): (199, 255, 65, 65), + ((199, 65, 65), (1, 254, 1)): (197, 253, 64, 64), + ((199, 65, 65), (65, 199, 65)): (163, 253, 98, 112), + ((199, 65, 65), (126, 127, 126)): (162, 253, 94, 157), + ((199, 65, 65), (127, 126, 127)): (162, 253, 94, 158), + ((199, 65, 65), (199, 65, 199)): (197, 253, 64, 212), + ((199, 65, 65), (254, 1, 254)): (252, 253, 0, 253), + ((199, 65, 65), (255, 0, 255)): (255, 255, 0, 255), + ((254, 1, 1), (0, 255, 0)): (254, 255, 1, 1), + ((254, 1, 1), (1, 254, 1)): (252, 253, 0, 0), + ((254, 1, 1), (65, 199, 65)): (204, 253, 50, 64), + ((254, 1, 1), (126, 127, 126)): (189, 253, 62, 125), + ((254, 1, 1), (127, 126, 127)): (190, 253, 62, 126), + ((254, 1, 1), (199, 65, 199)): (209, 253, 50, 198), + ((254, 1, 1), (254, 1, 254)): (252, 253, 0, 253), + ((254, 1, 1), (255, 0, 255)): (255, 255, 0, 255), + ((255, 0, 0), (0, 255, 0)): (255, 255, 0, 0), + ((255, 0, 0), (1, 254, 1)): (253, 253, 0, 0), + ((255, 0, 0), (65, 199, 65)): (205, 253, 50, 64), + ((255, 0, 0), (126, 127, 126)): (190, 253, 62, 125), + ((255, 0, 0), (127, 126, 127)): (190, 253, 62, 126), + ((255, 0, 0), (199, 65, 199)): (209, 253, 50, 198), + ((255, 0, 0), (254, 1, 254)): (252, 253, 0, 253), + ((255, 0, 0), (255, 0, 255)): (255, 255, 0, 255), + } + + # chosen because they contain edge cases. + nums = [0, 1, 65, 126, 127, 199, 254, 255] + results = {} + for dst_r, dst_b, dst_a in zip(nums, reversed(nums), reversed(nums)): + for src_r, src_b, src_a in zip(nums, reversed(nums), nums): + with self.subTest( + src_r=src_r, + src_b=src_b, + src_a=src_a, + dest_r=dst_r, + dest_b=dst_b, + dest_a=dst_a, + ): + src_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + src_surf.fill((src_r, 255, src_b, src_a)) + dest_surf = pygame.Surface((66, 66), pygame.SRCALPHA, 32) + dest_surf.fill((dst_r, 255, dst_b, dst_a)) + + dest_surf.blit( + src_surf, (0, 0), special_flags=pygame.BLEND_ALPHA_SDL2 + ) + key = ((dst_r, dst_b, dst_a), (src_r, src_b, src_a)) + results[key] = dest_surf.get_at((65, 33)) + self.assertEqual(results[key], results_expected[key]) + + # print("(dest_r, dest_b, dest_a), (src_r, src_b, src_a): color") + # pprint(results) + self.assertEqual(results, results_expected) + + def test_opaque_destination_blit_with_set_alpha(self): + # no set_alpha() + src_surf = pygame.Surface((32, 32), pygame.SRCALPHA, 32) + src_surf.fill((255, 255, 255, 200)) + dest_surf = pygame.Surface((32, 32)) + dest_surf.fill((100, 100, 100)) + + dest_surf.blit(src_surf, (0, 0)) + + no_surf_alpha_col = dest_surf.get_at((0, 0)) + + dest_surf.fill((100, 100, 100)) + dest_surf.set_alpha(200) + dest_surf.blit(src_surf, (0, 0)) + + surf_alpha_col = dest_surf.get_at((0, 0)) + + self.assertEqual(no_surf_alpha_col, surf_alpha_col) + + def todo_test_convert(self): + + # __doc__ (as of 2008-08-02) for pygame.surface.Surface.convert: + + # Surface.convert(Surface): return Surface + # Surface.convert(depth, flags=0): return Surface + # Surface.convert(masks, flags=0): return Surface + # Surface.convert(): return Surface + # change the pixel format of an image + # + # Creates a new copy of the Surface with the pixel format changed. The + # new pixel format can be determined from another existing Surface. + # Otherwise depth, flags, and masks arguments can be used, similar to + # the pygame.Surface() call. + # + # If no arguments are passed the new Surface will have the same pixel + # format as the display Surface. This is always the fastest format for + # blitting. It is a good idea to convert all Surfaces before they are + # blitted many times. + # + # The converted Surface will have no pixel alphas. They will be + # stripped if the original had them. See Surface.convert_alpha() for + # preserving or creating per-pixel alphas. + # + + self.fail() + + def test_convert__pixel_format_as_surface_subclass(self): + """Ensure convert accepts a Surface subclass argument.""" + expected_size = (23, 17) + convert_surface = SurfaceSubclass(expected_size, 0, 32) + depth_surface = SurfaceSubclass((31, 61), 0, 32) + + pygame.display.init() + try: + surface = convert_surface.convert(depth_surface) + + self.assertIsNot(surface, depth_surface) + self.assertIsNot(surface, convert_surface) + self.assertIsInstance(surface, pygame.Surface) + self.assertIsInstance(surface, SurfaceSubclass) + self.assertEqual(surface.get_size(), expected_size) + finally: + pygame.display.quit() + + def test_convert_alpha(self): + """Ensure the surface returned by surf.convert_alpha + has alpha values added""" + pygame.display.init() + try: + pygame.display.set_mode((640, 480)) + + s1 = pygame.Surface((100, 100), 0, 32) + s1_alpha = pygame.Surface.convert_alpha(s1) + + s2 = pygame.Surface((100, 100), 0, 32) + s2_alpha = s2.convert_alpha() + + s3 = pygame.Surface((100, 100), 0, 8) + s3_alpha = s3.convert_alpha() + + s4 = pygame.Surface((100, 100), 0, 12) + s4_alpha = s4.convert_alpha() + + s5 = pygame.Surface((100, 100), 0, 15) + s5_alpha = s5.convert_alpha() + + s6 = pygame.Surface((100, 100), 0, 16) + s6_alpha = s6.convert_alpha() + + s7 = pygame.Surface((100, 100), 0, 24) + s7_alpha = s7.convert_alpha() + + self.assertEqual(s1_alpha.get_alpha(), 255) + self.assertEqual(s2_alpha.get_alpha(), 255) + self.assertEqual(s3_alpha.get_alpha(), 255) + self.assertEqual(s4_alpha.get_alpha(), 255) + self.assertEqual(s5_alpha.get_alpha(), 255) + self.assertEqual(s6_alpha.get_alpha(), 255) + self.assertEqual(s7_alpha.get_alpha(), 255) + + self.assertEqual(s1_alpha.get_bitsize(), 32) + self.assertEqual(s2_alpha.get_bitsize(), 32) + self.assertEqual(s3_alpha.get_bitsize(), 32) + self.assertEqual(s4_alpha.get_bitsize(), 32) + self.assertEqual(s5_alpha.get_bitsize(), 32) + self.assertEqual(s6_alpha.get_bitsize(), 32) + self.assertEqual(s6_alpha.get_bitsize(), 32) + + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode() + pygame.display.quit() + surface.convert_alpha() + + finally: + pygame.display.quit() + + def test_convert_alpha__pixel_format_as_surface_subclass(self): + """Ensure convert_alpha accepts a Surface subclass argument.""" + expected_size = (23, 17) + convert_surface = SurfaceSubclass(expected_size, SRCALPHA, 32) + depth_surface = SurfaceSubclass((31, 57), SRCALPHA, 32) + + pygame.display.init() + try: + pygame.display.set_mode((60, 60)) + + # This is accepted as an argument, but its values are ignored. + # See issue #599. + surface = convert_surface.convert_alpha(depth_surface) + + self.assertIsNot(surface, depth_surface) + self.assertIsNot(surface, convert_surface) + self.assertIsInstance(surface, pygame.Surface) + self.assertIsInstance(surface, SurfaceSubclass) + self.assertEqual(surface.get_size(), expected_size) + finally: + pygame.display.quit() + + def test_get_abs_offset(self): + pygame.display.init() + try: + parent = pygame.Surface((64, 64), SRCALPHA, 32) + + # Stack bunch of subsurfaces + sub_level_1 = parent.subsurface((2, 2), (34, 37)) + sub_level_2 = sub_level_1.subsurface((0, 0), (30, 29)) + sub_level_3 = sub_level_2.subsurface((3, 7), (20, 21)) + sub_level_4 = sub_level_3.subsurface((6, 1), (14, 14)) + sub_level_5 = sub_level_4.subsurface((5, 6), (3, 4)) + + # Parent is always (0, 0) + self.assertEqual(parent.get_abs_offset(), (0, 0)) + # Total offset: (0+2, 0+2) = (2, 2) + self.assertEqual(sub_level_1.get_abs_offset(), (2, 2)) + # Total offset: (0+2+0, 0+2+0) = (2, 2) + self.assertEqual(sub_level_2.get_abs_offset(), (2, 2)) + # Total offset: (0+2+0+3, 0+2+0+7) = (5, 9) + self.assertEqual(sub_level_3.get_abs_offset(), (5, 9)) + # Total offset: (0+2+0+3+6, 0+2+0+7+1) = (11, 10) + self.assertEqual(sub_level_4.get_abs_offset(), (11, 10)) + # Total offset: (0+2+0+3+6+5, 0+2+0+7+1+6) = (16, 16) + self.assertEqual(sub_level_5.get_abs_offset(), (16, 16)) + + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode() + pygame.display.quit() + surface.get_abs_offset() + finally: + pygame.display.quit() + + def test_get_abs_parent(self): + pygame.display.init() + try: + parent = pygame.Surface((32, 32), SRCALPHA, 32) + + # Stack bunch of subsurfaces + sub_level_1 = parent.subsurface((1, 1), (15, 15)) + sub_level_2 = sub_level_1.subsurface((1, 1), (12, 12)) + sub_level_3 = sub_level_2.subsurface((1, 1), (9, 9)) + sub_level_4 = sub_level_3.subsurface((1, 1), (8, 8)) + sub_level_5 = sub_level_4.subsurface((2, 2), (3, 4)) + sub_level_6 = sub_level_5.subsurface((0, 0), (2, 1)) + + # Can't have subsurfaces bigger than parents + self.assertRaises(ValueError, parent.subsurface, (5, 5), (100, 100)) + self.assertRaises(ValueError, sub_level_3.subsurface, (0, 0), (11, 5)) + self.assertRaises(ValueError, sub_level_6.subsurface, (0, 0), (5, 5)) + + # Calling get_abs_parent on parent should return itself + self.assertEqual(parent.get_abs_parent(), parent) + + # On subclass "depth" of 1, get_abs_parent and get_parent should return the same + self.assertEqual(sub_level_1.get_abs_parent(), sub_level_1.get_parent()) + self.assertEqual(sub_level_2.get_abs_parent(), parent) + self.assertEqual(sub_level_3.get_abs_parent(), parent) + self.assertEqual(sub_level_4.get_abs_parent(), parent) + self.assertEqual(sub_level_5.get_abs_parent(), parent) + self.assertEqual( + sub_level_6.get_abs_parent(), sub_level_6.get_parent().get_abs_parent() + ) + + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode() + pygame.display.quit() + surface.get_abs_parent() + finally: + pygame.display.quit() + + def test_get_at(self): + surf = pygame.Surface((2, 2), 0, 24) + c00 = pygame.Color(1, 2, 3) + c01 = pygame.Color(5, 10, 15) + c10 = pygame.Color(100, 50, 0) + c11 = pygame.Color(4, 5, 6) + surf.set_at((0, 0), c00) + surf.set_at((0, 1), c01) + surf.set_at((1, 0), c10) + surf.set_at((1, 1), c11) + c = surf.get_at((0, 0)) + self.assertIsInstance(c, pygame.Color) + self.assertEqual(c, c00) + self.assertEqual(surf.get_at((0, 1)), c01) + self.assertEqual(surf.get_at((1, 0)), c10) + self.assertEqual(surf.get_at((1, 1)), c11) + for p in [(-1, 0), (0, -1), (2, 0), (0, 2)]: + self.assertRaises(IndexError, surf.get_at, p) + + def test_get_at_mapped(self): + color = pygame.Color(10, 20, 30) + for bitsize in [8, 16, 24, 32]: + surf = pygame.Surface((2, 2), 0, bitsize) + surf.fill(color) + pixel = surf.get_at_mapped((0, 0)) + self.assertEqual( + pixel, + surf.map_rgb(color), + "%i != %i, bitsize: %i" % (pixel, surf.map_rgb(color), bitsize), + ) + + def test_get_bitsize(self): + pygame.display.init() + try: + expected_size = (11, 21) + + # Check that get_bitsize returns passed depth + expected_depth = 32 + surface = pygame.Surface(expected_size, pygame.SRCALPHA, expected_depth) + self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_bitsize(), expected_depth) + + expected_depth = 16 + surface = pygame.Surface(expected_size, pygame.SRCALPHA, expected_depth) + self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_bitsize(), expected_depth) + + expected_depth = 15 + surface = pygame.Surface(expected_size, 0, expected_depth) + self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_bitsize(), expected_depth) + # Check for invalid depths + expected_depth = -1 + self.assertRaises( + ValueError, pygame.Surface, expected_size, 0, expected_depth + ) + expected_depth = 11 + self.assertRaises( + ValueError, pygame.Surface, expected_size, 0, expected_depth + ) + expected_depth = 1024 + self.assertRaises( + ValueError, pygame.Surface, expected_size, 0, expected_depth + ) + + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode() + pygame.display.quit() + surface.get_bitsize() + finally: + pygame.display.quit() + + def test_get_clip(self): + s = pygame.Surface((800, 600)) + rectangle = s.get_clip() + self.assertEqual(rectangle, (0, 0, 800, 600)) + + def test_get_colorkey(self): + pygame.display.init() + try: + # if set_colorkey is not used + s = pygame.Surface((800, 600), 0, 32) + self.assertIsNone(s.get_colorkey()) + + # if set_colorkey is used + s.set_colorkey(None) + self.assertIsNone(s.get_colorkey()) + + # setting up remainder of tests... + r, g, b, a = 20, 40, 60, 12 + colorkey = pygame.Color(r, g, b) + s.set_colorkey(colorkey) + + # test for ideal case + self.assertEqual(s.get_colorkey(), (r, g, b, 255)) + + # test for if the color_key is set using pygame.RLEACCEL + s.set_colorkey(colorkey, pygame.RLEACCEL) + self.assertEqual(s.get_colorkey(), (r, g, b, 255)) + + # test for if the color key is not what's expected + s.set_colorkey(pygame.Color(r + 1, g + 1, b + 1)) + self.assertNotEqual(s.get_colorkey(), (r, g, b, 255)) + + s.set_colorkey(pygame.Color(r, g, b, a)) + # regardless of whether alpha is not 255 + # colorkey returned from surface is always 255 + self.assertEqual(s.get_colorkey(), (r, g, b, 255)) + finally: + # test for using method after display.quit() is called... + s = pygame.display.set_mode((200, 200), 0, 32) + pygame.display.quit() + with self.assertRaises(pygame.error): + s.get_colorkey() + + def test_get_height(self): + sizes = ((1, 1), (119, 10), (10, 119), (1, 1000), (1000, 1), (1000, 1000)) + for width, height in sizes: + surf = pygame.Surface((width, height)) + found_height = surf.get_height() + self.assertEqual(height, found_height) + + def test_get_locked(self): + def blit_locked_test(surface): + newSurf = pygame.Surface((10, 10)) + try: + newSurf.blit(surface, (0, 0)) + except pygame.error: + return True + else: + return False + + surf = pygame.Surface((100, 100)) + + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Unlocked + # Surface should lock + surf.lock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Locked + # Surface should unlock + surf.unlock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Unlocked + + # Check multiple locks + surf = pygame.Surface((100, 100)) + surf.lock() + surf.lock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Locked + surf.unlock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Locked + surf.unlock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Unlocked + + # Check many locks + surf = pygame.Surface((100, 100)) + for i in range(1000): + surf.lock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Locked + for i in range(1000): + surf.unlock() + self.assertFalse(surf.get_locked()) # Unlocked + + # Unlocking an unlocked surface + surf = pygame.Surface((100, 100)) + surf.unlock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Unlocked + surf.unlock() + self.assertIs(surf.get_locked(), blit_locked_test(surf)) # Unlocked + + def test_get_locks(self): + + # __doc__ (as of 2008-08-02) for pygame.surface.Surface.get_locks: + + # Surface.get_locks(): return tuple + # Gets the locks for the Surface + # + # Returns the currently existing locks for the Surface. + + # test on a surface that is not initially locked + surface = pygame.Surface((100, 100)) + self.assertEqual(surface.get_locks(), ()) + + # test on the same surface after it has been locked + surface.lock() + self.assertEqual(surface.get_locks(), (surface,)) + + # test on the same surface after it has been unlocked + surface.unlock() + self.assertEqual(surface.get_locks(), ()) + + # test with PixelArray initialization: locks surface + pxarray = pygame.PixelArray(surface) + self.assertNotEqual(surface.get_locks(), ()) + + # closing the PixelArray releases the surface lock + pxarray.close() + self.assertEqual(surface.get_locks(), ()) + + # AttributeError raised when called on invalid object type (i.e. not a pygame.Surface object) + with self.assertRaises(AttributeError): + "DUMMY".get_locks() + + # test multiple locks and unlocks on the same surface + surface.lock() + surface.lock() + surface.lock() + self.assertEqual(surface.get_locks(), (surface, surface, surface)) + + surface.unlock() + surface.unlock() + self.assertEqual(surface.get_locks(), (surface,)) + surface.unlock() + self.assertEqual(surface.get_locks(), ()) + + def test_get_losses(self): + """Ensure a surface's losses can be retrieved""" + pygame.display.init() + try: + # Masks for different color component configurations + mask8 = (224, 28, 3, 0) + mask15 = (31744, 992, 31, 0) + mask16 = (63488, 2016, 31, 0) + mask24 = (4278190080, 16711680, 65280, 0) + mask32 = (4278190080, 16711680, 65280, 255) + + # Surfaces with standard depths and masks + display_surf = pygame.display.set_mode((100, 100)) + surf = pygame.Surface((100, 100)) + surf_8bit = pygame.Surface((100, 100), depth=8, masks=mask8) + surf_15bit = pygame.Surface((100, 100), depth=15, masks=mask15) + surf_16bit = pygame.Surface((100, 100), depth=16, masks=mask16) + surf_24bit = pygame.Surface((100, 100), depth=24, masks=mask24) + surf_32bit = pygame.Surface((100, 100), depth=32, masks=mask32) + + # Test output is correct type, length, and value range + losses = surf.get_losses() + self.assertIsInstance(losses, tuple) + self.assertEqual(len(losses), 4) + for loss in losses: + self.assertIsInstance(loss, int) + self.assertGreaterEqual(loss, 0) + self.assertLessEqual(loss, 8) + + # Test each surface for correct losses + # Display surface losses gives idea of default surface losses + if display_surf.get_losses() == (0, 0, 0, 8): + self.assertEqual(losses, (0, 0, 0, 8)) + elif display_surf.get_losses() == (8, 8, 8, 8): + self.assertEqual(losses, (8, 8, 8, 8)) + + self.assertEqual(surf_8bit.get_losses(), (5, 5, 6, 8)) + self.assertEqual(surf_15bit.get_losses(), (3, 3, 3, 8)) + self.assertEqual(surf_16bit.get_losses(), (3, 2, 3, 8)) + self.assertEqual(surf_24bit.get_losses(), (0, 0, 0, 8)) + self.assertEqual(surf_32bit.get_losses(), (0, 0, 0, 0)) + + # Method should fail when display is not initialized + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode((100, 100)) + pygame.display.quit() + surface.get_losses() + finally: + pygame.display.quit() + + def test_get_masks__rgba(self): + """ + Ensure that get_mask can return RGBA mask. + """ + masks = [ + (0x0F00, 0x00F0, 0x000F, 0xF000), + (0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000), + ] + depths = [16, 32] + for expected, depth in list(zip(masks, depths)): + surface = pygame.Surface((10, 10), pygame.SRCALPHA, depth) + self.assertEqual(expected, surface.get_masks()) + + def test_get_masks__rgb(self): + """ + Ensure that get_mask can return RGB mask. + """ + masks = [ + (0x60, 0x1C, 0x03, 0x00), + (0xF00, 0x0F0, 0x00F, 0x000), + (0x7C00, 0x03E0, 0x001F, 0x0000), + (0xF800, 0x07E0, 0x001F, 0x0000), + (0xFF0000, 0x00FF00, 0x0000FF, 0x000000), + (0xFF0000, 0x00FF00, 0x0000FF, 0x000000), + ] + depths = [8, 12, 15, 16, 24, 32] + for expected, depth in list(zip(masks, depths)): + surface = pygame.Surface((10, 10), 0, depth) + if depth == 8: + expected = (0x00, 0x00, 0x00, 0x00) + self.assertEqual(expected, surface.get_masks()) + + def test_get_masks__no_surface(self): + """ + Ensure that after display.quit, calling get_masks raises pygame.error. + """ + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode((10, 10)) + pygame.display.quit() + surface.get_masks() + + def test_get_offset(self): + """get_offset returns the (0,0) if surface is not a child + returns the position of child subsurface inside of parent + """ + pygame.display.init() + try: + surf = pygame.Surface((100, 100)) + self.assertEqual(surf.get_offset(), (0, 0)) + + # subsurface offset test + subsurf = surf.subsurface(1, 1, 10, 10) + self.assertEqual(subsurf.get_offset(), (1, 1)) + + with self.assertRaises(pygame.error): + surface = pygame.display.set_mode() + pygame.display.quit() + surface.get_offset() + finally: + pygame.display.quit() + + def test_get_palette(self): + pygame.display.init() + try: + palette = [Color(i, i, i) for i in range(256)] + pygame.display.set_mode((100, 50)) + surf = pygame.Surface((2, 2), 0, 8) + surf.set_palette(palette) + palette2 = surf.get_palette() + r, g, b = palette2[0] + + self.assertEqual(len(palette2), len(palette)) + for c2, c in zip(palette2, palette): + self.assertEqual(c2, c) + for c in palette2: + self.assertIsInstance(c, pygame.Color) + finally: + pygame.display.quit() + + def test_get_palette_at(self): + # See also test_get_palette + pygame.display.init() + try: + pygame.display.set_mode((100, 50)) + surf = pygame.Surface((2, 2), 0, 8) + color = pygame.Color(1, 2, 3, 255) + surf.set_palette_at(0, color) + color2 = surf.get_palette_at(0) + self.assertIsInstance(color2, pygame.Color) + self.assertEqual(color2, color) + self.assertRaises(IndexError, surf.get_palette_at, -1) + self.assertRaises(IndexError, surf.get_palette_at, 256) + finally: + pygame.display.quit() + + def test_get_pitch(self): + # Test get_pitch() on several surfaces of varying size/depth + sizes = ((2, 2), (7, 33), (33, 7), (2, 734), (734, 2), (734, 734)) + depths = [8, 24, 32] + for width, height in sizes: + for depth in depths: + # Test get_pitch() on parent surface + surf = pygame.Surface((width, height), depth=depth) + buff = surf.get_buffer() + pitch = buff.length / surf.get_height() + test_pitch = surf.get_pitch() + self.assertEqual(pitch, test_pitch) + # Test get_pitch() on subsurface with same rect as parent + rect1 = surf.get_rect() + subsurf1 = surf.subsurface(rect1) + sub_buff1 = subsurf1.get_buffer() + sub_pitch1 = sub_buff1.length / subsurf1.get_height() + test_sub_pitch1 = subsurf1.get_pitch() + self.assertEqual(sub_pitch1, test_sub_pitch1) + # Test get_pitch on subsurface with modified rect + rect2 = rect1.inflate(-width / 2, -height / 2) + subsurf2 = surf.subsurface(rect2) + sub_buff2 = subsurf2.get_buffer() + sub_pitch2 = sub_buff2.length / float(subsurf2.get_height()) + test_sub_pitch2 = subsurf2.get_pitch() + self.assertEqual(sub_pitch2, test_sub_pitch2) + + def test_get_shifts(self): + """ + Tests whether Surface.get_shifts returns proper + RGBA shifts under various conditions. + """ + # __doc__ (as of 2008-08-02) for pygame.surface.Surface.get_shifts: + # Surface.get_shifts(): return (R, G, B, A) + # the bit shifts needed to convert between color and mapped integer. + # Returns the pixel shifts need to convert between each color and a + # mapped integer. + # This value is not needed for normal Pygame usage. + + # Test for SDL2 on surfaces with various depths and alpha on/off + depths = [8, 24, 32] + alpha = 128 + off = None + for bit_depth in depths: + surface = pygame.Surface((32, 32), depth=bit_depth) + surface.set_alpha(alpha) + r1, g1, b1, a1 = surface.get_shifts() + surface.set_alpha(off) + r2, g2, b2, a2 = surface.get_shifts() + self.assertEqual((r1, g1, b1, a1), (r2, g2, b2, a2)) + + def test_get_size(self): + sizes = ((1, 1), (119, 10), (1000, 1000), (1, 5000), (1221, 1), (99, 999)) + for width, height in sizes: + surf = pygame.Surface((width, height)) + found_size = surf.get_size() + self.assertEqual((width, height), found_size) + + def test_lock(self): + + # __doc__ (as of 2008-08-02) for pygame.surface.Surface.lock: + + # Surface.lock(): return None + # lock the Surface memory for pixel access + # + # Lock the pixel data of a Surface for access. On accelerated + # Surfaces, the pixel data may be stored in volatile video memory or + # nonlinear compressed forms. When a Surface is locked the pixel + # memory becomes available to access by regular software. Code that + # reads or writes pixel values will need the Surface to be locked. + # + # Surfaces should not remain locked for more than necessary. A locked + # Surface can often not be displayed or managed by Pygame. + # + # Not all Surfaces require locking. The Surface.mustlock() method can + # determine if it is actually required. There is no performance + # penalty for locking and unlocking a Surface that does not need it. + # + # All pygame functions will automatically lock and unlock the Surface + # data as needed. If a section of code is going to make calls that + # will repeatedly lock and unlock the Surface many times, it can be + # helpful to wrap the block inside a lock and unlock pair. + # + # It is safe to nest locking and unlocking calls. The surface will + # only be unlocked after the final lock is released. + # + + # Basic + surf = pygame.Surface((100, 100)) + surf.lock() + self.assertTrue(surf.get_locked()) + + # Nested + surf = pygame.Surface((100, 100)) + surf.lock() + surf.lock() + surf.unlock() + self.assertTrue(surf.get_locked()) + surf.unlock() + surf.lock() + surf.lock() + self.assertTrue(surf.get_locked()) + surf.unlock() + self.assertTrue(surf.get_locked()) + surf.unlock() + self.assertFalse(surf.get_locked()) + + # Already Locked + surf = pygame.Surface((100, 100)) + surf.lock() + surf.lock() + self.assertTrue(surf.get_locked()) + surf.unlock() + self.assertTrue(surf.get_locked()) + surf.unlock() + self.assertFalse(surf.get_locked()) + + def test_map_rgb(self): + color = Color(0, 128, 255, 64) + surf = pygame.Surface((5, 5), SRCALPHA, 32) + c = surf.map_rgb(color) + self.assertEqual(surf.unmap_rgb(c), color) + + self.assertEqual(surf.get_at((0, 0)), (0, 0, 0, 0)) + surf.fill(c) + self.assertEqual(surf.get_at((0, 0)), color) + + surf.fill((0, 0, 0, 0)) + self.assertEqual(surf.get_at((0, 0)), (0, 0, 0, 0)) + surf.set_at((0, 0), c) + self.assertEqual(surf.get_at((0, 0)), color) + + def test_mustlock(self): + # Test that subsurfaces mustlock + surf = pygame.Surface((1024, 1024)) + subsurf = surf.subsurface((0, 0, 1024, 1024)) + self.assertTrue(subsurf.mustlock()) + self.assertFalse(surf.mustlock()) + # Tests nested subsurfaces + rects = ((0, 0, 512, 512), (0, 0, 256, 256), (0, 0, 128, 128)) + surf_stack = [] + surf_stack.append(surf) + surf_stack.append(subsurf) + for rect in rects: + surf_stack.append(surf_stack[-1].subsurface(rect)) + self.assertTrue(surf_stack[-1].mustlock()) + self.assertTrue(surf_stack[-2].mustlock()) + + def test_set_alpha_none(self): + """surf.set_alpha(None) disables blending""" + s = pygame.Surface((1, 1), SRCALPHA, 32) + s.fill((0, 255, 0, 128)) + s.set_alpha(None) + self.assertEqual(None, s.get_alpha()) + + s2 = pygame.Surface((1, 1), SRCALPHA, 32) + s2.fill((255, 0, 0, 255)) + s2.blit(s, (0, 0)) + self.assertEqual(s2.get_at((0, 0))[0], 0, "the red component should be 0") + + def test_set_alpha_value(self): + """surf.set_alpha(x), where x != None, enables blending""" + s = pygame.Surface((1, 1), SRCALPHA, 32) + s.fill((0, 255, 0, 128)) + s.set_alpha(255) + + s2 = pygame.Surface((1, 1), SRCALPHA, 32) + s2.fill((255, 0, 0, 255)) + s2.blit(s, (0, 0)) + self.assertGreater( + s2.get_at((0, 0))[0], 0, "the red component should be above 0" + ) + + def test_palette_colorkey(self): + """test bug discovered by robertpfeiffer + https://github.com/pygame/pygame/issues/721 + """ + surf = pygame.image.load(example_path(os.path.join("data", "alien2.png"))) + key = surf.get_colorkey() + self.assertEqual(surf.get_palette()[surf.map_rgb(key)], key) + + def test_palette_colorkey_set_px(self): + surf = pygame.image.load(example_path(os.path.join("data", "alien2.png"))) + key = surf.get_colorkey() + surf.set_at((0, 0), key) + self.assertEqual(surf.get_at((0, 0)), key) + + def test_palette_colorkey_fill(self): + surf = pygame.image.load(example_path(os.path.join("data", "alien2.png"))) + key = surf.get_colorkey() + surf.fill(key) + self.assertEqual(surf.get_at((0, 0)), key) + + def test_set_palette(self): + palette = [pygame.Color(i, i, i) for i in range(256)] + palette[10] = tuple(palette[10]) # 4 element tuple + palette[11] = tuple(palette[11])[0:3] # 3 element tuple + + surf = pygame.Surface((2, 2), 0, 8) + pygame.display.init() + try: + pygame.display.set_mode((100, 50)) + surf.set_palette(palette) + for i in range(256): + self.assertEqual(surf.map_rgb(palette[i]), i, "palette color %i" % (i,)) + c = palette[i] + surf.fill(c) + self.assertEqual(surf.get_at((0, 0)), c, "palette color %i" % (i,)) + for i in range(10): + palette[i] = pygame.Color(255 - i, 0, 0) + surf.set_palette(palette[0:10]) + for i in range(256): + self.assertEqual(surf.map_rgb(palette[i]), i, "palette color %i" % (i,)) + c = palette[i] + surf.fill(c) + self.assertEqual(surf.get_at((0, 0)), c, "palette color %i" % (i,)) + self.assertRaises(ValueError, surf.set_palette, [Color(1, 2, 3, 254)]) + self.assertRaises(ValueError, surf.set_palette, (1, 2, 3, 254)) + finally: + pygame.display.quit() + + def test_set_palette__fail(self): + pygame.init() + palette = 256 * [(10, 20, 30)] + surf = pygame.Surface((2, 2), 0, 32) + self.assertRaises(pygame.error, surf.set_palette, palette) + pygame.quit() + + def test_set_palette_at(self): + pygame.display.init() + try: + pygame.display.set_mode((100, 50)) + surf = pygame.Surface((2, 2), 0, 8) + original = surf.get_palette_at(10) + replacement = Color(1, 1, 1, 255) + if replacement == original: + replacement = Color(2, 2, 2, 255) + surf.set_palette_at(10, replacement) + self.assertEqual(surf.get_palette_at(10), replacement) + next = tuple(original) + surf.set_palette_at(10, next) + self.assertEqual(surf.get_palette_at(10), next) + next = tuple(original)[0:3] + surf.set_palette_at(10, next) + self.assertEqual(surf.get_palette_at(10), next) + self.assertRaises(IndexError, surf.set_palette_at, 256, replacement) + self.assertRaises(IndexError, surf.set_palette_at, -1, replacement) + finally: + pygame.display.quit() + + def test_subsurface(self): + + # __doc__ (as of 2008-08-02) for pygame.surface.Surface.subsurface: + + # Surface.subsurface(Rect): return Surface + # create a new surface that references its parent + # + # Returns a new Surface that shares its pixels with its new parent. + # The new Surface is considered a child of the original. Modifications + # to either Surface pixels will effect each other. Surface information + # like clipping area and color keys are unique to each Surface. + # + # The new Surface will inherit the palette, color key, and alpha + # settings from its parent. + # + # It is possible to have any number of subsurfaces and subsubsurfaces + # on the parent. It is also possible to subsurface the display Surface + # if the display mode is not hardware accelerated. + # + # See the Surface.get_offset(), Surface.get_parent() to learn more + # about the state of a subsurface. + # + + surf = pygame.Surface((16, 16)) + s = surf.subsurface(0, 0, 1, 1) + s = surf.subsurface((0, 0, 1, 1)) + + # s = surf.subsurface((0,0,1,1), 1) + # This form is not acceptable. + # s = surf.subsurface(0,0,10,10, 1) + + self.assertRaises(ValueError, surf.subsurface, (0, 0, 1, 1, 666)) + + self.assertEqual(s.get_shifts(), surf.get_shifts()) + self.assertEqual(s.get_masks(), surf.get_masks()) + self.assertEqual(s.get_losses(), surf.get_losses()) + + # Issue 2 at Bitbucket.org/pygame/pygame + surf = pygame.Surface.__new__(pygame.Surface) + self.assertRaises(pygame.error, surf.subsurface, (0, 0, 0, 0)) + + def test_unlock(self): + # Basic + surf = pygame.Surface((100, 100)) + surf.lock() + surf.unlock() + self.assertFalse(surf.get_locked()) + + # Nested + surf = pygame.Surface((100, 100)) + surf.lock() + surf.lock() + surf.unlock() + self.assertTrue(surf.get_locked()) + surf.unlock() + self.assertFalse(surf.get_locked()) + + # Already Unlocked + surf = pygame.Surface((100, 100)) + surf.unlock() + self.assertFalse(surf.get_locked()) + surf.unlock() + self.assertFalse(surf.get_locked()) + + # Surface can be relocked + surf = pygame.Surface((100, 100)) + surf.lock() + surf.unlock() + self.assertFalse(surf.get_locked()) + surf.lock() + surf.unlock() + self.assertFalse(surf.get_locked()) + + def test_unmap_rgb(self): + # Special case, 8 bit-per-pixel surface (has a palette). + surf = pygame.Surface((2, 2), 0, 8) + c = (1, 1, 1) # Unlikely to be in a default palette. + i = 67 + pygame.display.init() + try: + pygame.display.set_mode((100, 50)) + surf.set_palette_at(i, c) + unmapped_c = surf.unmap_rgb(i) + self.assertEqual(unmapped_c, c) + # Confirm it is a Color instance + self.assertIsInstance(unmapped_c, pygame.Color) + finally: + pygame.display.quit() + + # Remaining, non-pallete, cases. + c = (128, 64, 12, 255) + formats = [(0, 16), (0, 24), (0, 32), (SRCALPHA, 16), (SRCALPHA, 32)] + for flags, bitsize in formats: + surf = pygame.Surface((2, 2), flags, bitsize) + unmapped_c = surf.unmap_rgb(surf.map_rgb(c)) + surf.fill(c) + comparison_c = surf.get_at((0, 0)) + self.assertEqual( + unmapped_c, + comparison_c, + "%s != %s, flags: %i, bitsize: %i" + % (unmapped_c, comparison_c, flags, bitsize), + ) + # Confirm it is a Color instance + self.assertIsInstance(unmapped_c, pygame.Color) + + def test_scroll(self): + scrolls = [ + (8, 2, 3), + (16, 2, 3), + (24, 2, 3), + (32, 2, 3), + (32, -1, -3), + (32, 0, 0), + (32, 11, 0), + (32, 0, 11), + (32, -11, 0), + (32, 0, -11), + (32, -11, 2), + (32, 2, -11), + ] + for bitsize, dx, dy in scrolls: + surf = pygame.Surface((10, 10), 0, bitsize) + surf.fill((255, 0, 0)) + surf.fill((0, 255, 0), (2, 2, 2, 2)) + comp = surf.copy() + comp.blit(surf, (dx, dy)) + surf.scroll(dx, dy) + w, h = surf.get_size() + for x in range(w): + for y in range(h): + with self.subTest(x=x, y=y): + self.assertEqual( + surf.get_at((x, y)), + comp.get_at((x, y)), + "%s != %s, bpp:, %i, x: %i, y: %i" + % ( + surf.get_at((x, y)), + comp.get_at((x, y)), + bitsize, + dx, + dy, + ), + ) + # Confirm clip rect containment + surf = pygame.Surface((20, 13), 0, 32) + surf.fill((255, 0, 0)) + surf.fill((0, 255, 0), (7, 1, 6, 6)) + comp = surf.copy() + clip = Rect(3, 1, 8, 14) + surf.set_clip(clip) + comp.set_clip(clip) + comp.blit(surf, (clip.x + 2, clip.y + 3), surf.get_clip()) + surf.scroll(2, 3) + w, h = surf.get_size() + for x in range(w): + for y in range(h): + self.assertEqual(surf.get_at((x, y)), comp.get_at((x, y))) + # Confirm keyword arguments and per-pixel alpha + spot_color = (0, 255, 0, 128) + surf = pygame.Surface((4, 4), pygame.SRCALPHA, 32) + surf.fill((255, 0, 0, 255)) + surf.set_at((1, 1), spot_color) + surf.scroll(dx=1) + self.assertEqual(surf.get_at((2, 1)), spot_color) + surf.scroll(dy=1) + self.assertEqual(surf.get_at((2, 2)), spot_color) + surf.scroll(dy=1, dx=1) + self.assertEqual(surf.get_at((3, 3)), spot_color) + surf.scroll(dx=-3, dy=-3) + self.assertEqual(surf.get_at((0, 0)), spot_color) + + +class SurfaceSubtypeTest(unittest.TestCase): + """Issue #280: Methods that return a new Surface preserve subclasses""" + + def setUp(self): + pygame.display.init() + + def tearDown(self): + pygame.display.quit() + + def test_copy(self): + """Ensure method copy() preserves the surface's class + + When Surface is subclassed, the inherited copy() method will return + instances of the subclass. Non Surface fields are uncopied, however. + This includes instance attributes. + """ + expected_size = (32, 32) + ms1 = SurfaceSubclass(expected_size, SRCALPHA, 32) + ms2 = ms1.copy() + + self.assertIsNot(ms1, ms2) + self.assertIsInstance(ms1, pygame.Surface) + self.assertIsInstance(ms2, pygame.Surface) + self.assertIsInstance(ms1, SurfaceSubclass) + self.assertIsInstance(ms2, SurfaceSubclass) + self.assertTrue(ms1.test_attribute) + self.assertRaises(AttributeError, getattr, ms2, "test_attribute") + self.assertEqual(ms2.get_size(), expected_size) + + def test_convert(self): + """Ensure method convert() preserves the surface's class + + When Surface is subclassed, the inherited convert() method will return + instances of the subclass. Non Surface fields are omitted, however. + This includes instance attributes. + """ + expected_size = (32, 32) + ms1 = SurfaceSubclass(expected_size, 0, 24) + ms2 = ms1.convert(24) + + self.assertIsNot(ms1, ms2) + self.assertIsInstance(ms1, pygame.Surface) + self.assertIsInstance(ms2, pygame.Surface) + self.assertIsInstance(ms1, SurfaceSubclass) + self.assertIsInstance(ms2, SurfaceSubclass) + self.assertTrue(ms1.test_attribute) + self.assertRaises(AttributeError, getattr, ms2, "test_attribute") + self.assertEqual(ms2.get_size(), expected_size) + + def test_convert_alpha(self): + """Ensure method convert_alpha() preserves the surface's class + + When Surface is subclassed, the inherited convert_alpha() method will + return instances of the subclass. Non Surface fields are omitted, + however. This includes instance attributes. + """ + pygame.display.set_mode((40, 40)) + expected_size = (32, 32) + s = pygame.Surface(expected_size, SRCALPHA, 16) + ms1 = SurfaceSubclass(expected_size, SRCALPHA, 32) + ms2 = ms1.convert_alpha(s) + + self.assertIsNot(ms1, ms2) + self.assertIsInstance(ms1, pygame.Surface) + self.assertIsInstance(ms2, pygame.Surface) + self.assertIsInstance(ms1, SurfaceSubclass) + self.assertIsInstance(ms2, SurfaceSubclass) + self.assertTrue(ms1.test_attribute) + self.assertRaises(AttributeError, getattr, ms2, "test_attribute") + self.assertEqual(ms2.get_size(), expected_size) + + def test_subsurface(self): + """Ensure method subsurface() preserves the surface's class + + When Surface is subclassed, the inherited subsurface() method will + return instances of the subclass. Non Surface fields are uncopied, + however. This includes instance attributes. + """ + expected_size = (10, 12) + ms1 = SurfaceSubclass((32, 32), SRCALPHA, 32) + ms2 = ms1.subsurface((4, 5), expected_size) + + self.assertIsNot(ms1, ms2) + self.assertIsInstance(ms1, pygame.Surface) + self.assertIsInstance(ms2, pygame.Surface) + self.assertIsInstance(ms1, SurfaceSubclass) + self.assertIsInstance(ms2, SurfaceSubclass) + self.assertTrue(ms1.test_attribute) + self.assertRaises(AttributeError, getattr, ms2, "test_attribute") + self.assertEqual(ms2.get_size(), expected_size) + + +class SurfaceGetBufferTest(unittest.TestCase): + # These tests requires ctypes. They are disabled if ctypes + # is not installed. + try: + ArrayInterface + except NameError: + __tags__ = ("ignore", "subprocess_ignore") + + lilendian = pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN + + def _check_interface_2D(self, s): + s_w, s_h = s.get_size() + s_bytesize = s.get_bytesize() + s_pitch = s.get_pitch() + s_pixels = s._pixels_address + + # check the array interface structure fields. + v = s.get_view("2") + if not IS_PYPY: + flags = PAI_ALIGNED | PAI_NOTSWAPPED | PAI_WRITEABLE + if s.get_pitch() == s_w * s_bytesize: + flags |= PAI_FORTRAN + + inter = ArrayInterface(v) + + self.assertEqual(inter.two, 2) + self.assertEqual(inter.nd, 2) + self.assertEqual(inter.typekind, "u") + self.assertEqual(inter.itemsize, s_bytesize) + self.assertEqual(inter.shape[0], s_w) + self.assertEqual(inter.shape[1], s_h) + self.assertEqual(inter.strides[0], s_bytesize) + self.assertEqual(inter.strides[1], s_pitch) + self.assertEqual(inter.flags, flags) + self.assertEqual(inter.data, s_pixels) + + def _check_interface_3D(self, s): + s_w, s_h = s.get_size() + s_bytesize = s.get_bytesize() + s_pitch = s.get_pitch() + s_pixels = s._pixels_address + s_shifts = list(s.get_shifts()) + + # Check for RGB or BGR surface. + if s_shifts[0:3] == [0, 8, 16]: + if self.lilendian: + # RGB + offset = 0 + step = 1 + else: + # BGR + offset = s_bytesize - 1 + step = -1 + elif s_shifts[0:3] == [8, 16, 24]: + if self.lilendian: + # xRGB + offset = 1 + step = 1 + else: + # BGRx + offset = s_bytesize - 2 + step = -1 + elif s_shifts[0:3] == [16, 8, 0]: + if self.lilendian: + # BGR + offset = 2 + step = -1 + else: + # RGB + offset = s_bytesize - 3 + step = 1 + elif s_shifts[0:3] == [24, 16, 8]: + if self.lilendian: + # BGRx + offset = 2 + step = -1 + else: + # RGBx + offset = s_bytesize - 4 + step = -1 + else: + return + + # check the array interface structure fields. + v = s.get_view("3") + if not IS_PYPY: + inter = ArrayInterface(v) + flags = PAI_ALIGNED | PAI_NOTSWAPPED | PAI_WRITEABLE + self.assertEqual(inter.two, 2) + self.assertEqual(inter.nd, 3) + self.assertEqual(inter.typekind, "u") + self.assertEqual(inter.itemsize, 1) + self.assertEqual(inter.shape[0], s_w) + self.assertEqual(inter.shape[1], s_h) + self.assertEqual(inter.shape[2], 3) + self.assertEqual(inter.strides[0], s_bytesize) + self.assertEqual(inter.strides[1], s_pitch) + self.assertEqual(inter.strides[2], step) + self.assertEqual(inter.flags, flags) + self.assertEqual(inter.data, s_pixels + offset) + + def _check_interface_rgba(self, s, plane): + s_w, s_h = s.get_size() + s_bytesize = s.get_bytesize() + s_pitch = s.get_pitch() + s_pixels = s._pixels_address + s_shifts = s.get_shifts() + s_masks = s.get_masks() + + # Find the color plane position within the pixel. + if not s_masks[plane]: + return + alpha_shift = s_shifts[plane] + offset = alpha_shift // 8 + if not self.lilendian: + offset = s_bytesize - offset - 1 + + # check the array interface structure fields. + v = s.get_view("rgba"[plane]) + if not IS_PYPY: + inter = ArrayInterface(v) + flags = PAI_ALIGNED | PAI_NOTSWAPPED | PAI_WRITEABLE + self.assertEqual(inter.two, 2) + self.assertEqual(inter.nd, 2) + self.assertEqual(inter.typekind, "u") + self.assertEqual(inter.itemsize, 1) + self.assertEqual(inter.shape[0], s_w) + self.assertEqual(inter.shape[1], s_h) + self.assertEqual(inter.strides[0], s_bytesize) + self.assertEqual(inter.strides[1], s_pitch) + self.assertEqual(inter.flags, flags) + self.assertEqual(inter.data, s_pixels + offset) + + def test_array_interface(self): + self._check_interface_2D(pygame.Surface((5, 7), 0, 8)) + self._check_interface_2D(pygame.Surface((5, 7), 0, 16)) + self._check_interface_2D(pygame.Surface((5, 7), pygame.SRCALPHA, 16)) + self._check_interface_3D(pygame.Surface((5, 7), 0, 24)) + self._check_interface_3D(pygame.Surface((8, 4), 0, 24)) # No gaps + self._check_interface_2D(pygame.Surface((5, 7), 0, 32)) + self._check_interface_3D(pygame.Surface((5, 7), 0, 32)) + self._check_interface_2D(pygame.Surface((5, 7), pygame.SRCALPHA, 32)) + self._check_interface_3D(pygame.Surface((5, 7), pygame.SRCALPHA, 32)) + + def test_array_interface_masks(self): + """Test non-default color byte orders on 3D views""" + + sz = (5, 7) + # Reversed RGB byte order + s = pygame.Surface(sz, 0, 32) + s_masks = list(s.get_masks()) + masks = [0xFF, 0xFF00, 0xFF0000] + if s_masks[0:3] == masks or s_masks[0:3] == masks[::-1]: + masks = s_masks[2::-1] + s_masks[3:4] + self._check_interface_3D(pygame.Surface(sz, 0, 32, masks)) + s = pygame.Surface(sz, 0, 24) + s_masks = list(s.get_masks()) + masks = [0xFF, 0xFF00, 0xFF0000] + if s_masks[0:3] == masks or s_masks[0:3] == masks[::-1]: + masks = s_masks[2::-1] + s_masks[3:4] + self._check_interface_3D(pygame.Surface(sz, 0, 24, masks)) + + masks = [0xFF00, 0xFF0000, 0xFF000000, 0] + self._check_interface_3D(pygame.Surface(sz, 0, 32, masks)) + + def test_array_interface_alpha(self): + for shifts in [[0, 8, 16, 24], [8, 16, 24, 0], [24, 16, 8, 0], [16, 8, 0, 24]]: + masks = [0xFF << s for s in shifts] + s = pygame.Surface((4, 2), pygame.SRCALPHA, 32, masks) + self._check_interface_rgba(s, 3) + + def test_array_interface_rgb(self): + for shifts in [[0, 8, 16, 24], [8, 16, 24, 0], [24, 16, 8, 0], [16, 8, 0, 24]]: + masks = [0xFF << s for s in shifts] + masks[3] = 0 + for plane in range(3): + s = pygame.Surface((4, 2), 0, 24) + self._check_interface_rgba(s, plane) + s = pygame.Surface((4, 2), 0, 32) + self._check_interface_rgba(s, plane) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_newbuf_PyBUF_flags_bytes(self): + from pygame.tests.test_utils import buftools + + Importer = buftools.Importer + s = pygame.Surface((10, 6), 0, 32) + a = s.get_buffer() + b = Importer(a, buftools.PyBUF_SIMPLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 1) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address) + b = Importer(a, buftools.PyBUF_WRITABLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertFalse(b.readonly) + b = Importer(a, buftools.PyBUF_FORMAT) + self.assertEqual(b.ndim, 0) + self.assertEqual(b.format, "B") + b = Importer(a, buftools.PyBUF_ND) + self.assertEqual(b.ndim, 1) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 1) + self.assertEqual(b.shape, (a.length,)) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address) + b = Importer(a, buftools.PyBUF_STRIDES) + self.assertEqual(b.ndim, 1) + self.assertTrue(b.format is None) + self.assertEqual(b.strides, (1,)) + s2 = s.subsurface((1, 1, 7, 4)) # Not contiguous + a = s2.get_buffer() + b = Importer(a, buftools.PyBUF_SIMPLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 1) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s2._pixels_address) + b = Importer(a, buftools.PyBUF_C_CONTIGUOUS) + self.assertEqual(b.ndim, 1) + self.assertEqual(b.strides, (1,)) + b = Importer(a, buftools.PyBUF_F_CONTIGUOUS) + self.assertEqual(b.ndim, 1) + self.assertEqual(b.strides, (1,)) + b = Importer(a, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertEqual(b.ndim, 1) + self.assertEqual(b.strides, (1,)) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_newbuf_PyBUF_flags_0D(self): + # This is the same handler as used by get_buffer(), so just + # confirm that it succeeds for one case. + from pygame.tests.test_utils import buftools + + Importer = buftools.Importer + s = pygame.Surface((10, 6), 0, 32) + a = s.get_view("0") + b = Importer(a, buftools.PyBUF_SIMPLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 1) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_newbuf_PyBUF_flags_1D(self): + from pygame.tests.test_utils import buftools + + Importer = buftools.Importer + s = pygame.Surface((10, 6), 0, 32) + a = s.get_view("1") + b = Importer(a, buftools.PyBUF_SIMPLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, s.get_bytesize()) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address) + b = Importer(a, buftools.PyBUF_WRITABLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertFalse(b.readonly) + b = Importer(a, buftools.PyBUF_FORMAT) + self.assertEqual(b.ndim, 0) + self.assertEqual(b.format, "=I") + b = Importer(a, buftools.PyBUF_ND) + self.assertEqual(b.ndim, 1) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, s.get_bytesize()) + self.assertEqual(b.shape, (s.get_width() * s.get_height(),)) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address) + b = Importer(a, buftools.PyBUF_STRIDES) + self.assertEqual(b.ndim, 1) + self.assertTrue(b.format is None) + self.assertEqual(b.strides, (s.get_bytesize(),)) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_newbuf_PyBUF_flags_2D(self): + from pygame.tests.test_utils import buftools + + Importer = buftools.Importer + s = pygame.Surface((10, 6), 0, 32) + a = s.get_view("2") + # Non dimensional requests, no PyDEF_ND, are handled by the + # 1D surface buffer code, so only need to confirm a success. + b = Importer(a, buftools.PyBUF_SIMPLE) + self.assertEqual(b.ndim, 0) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, s.get_bytesize()) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address) + # Uniquely 2D + b = Importer(a, buftools.PyBUF_STRIDES) + self.assertEqual(b.ndim, 2) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, s.get_bytesize()) + self.assertEqual(b.shape, s.get_size()) + self.assertEqual(b.strides, (s.get_bytesize(), s.get_pitch())) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address) + b = Importer(a, buftools.PyBUF_RECORDS_RO) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, "=I") + self.assertEqual(b.strides, (s.get_bytesize(), s.get_pitch())) + b = Importer(a, buftools.PyBUF_RECORDS) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, "=I") + self.assertEqual(b.strides, (s.get_bytesize(), s.get_pitch())) + b = Importer(a, buftools.PyBUF_F_CONTIGUOUS) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, None) + self.assertEqual(b.strides, (s.get_bytesize(), s.get_pitch())) + b = Importer(a, buftools.PyBUF_ANY_CONTIGUOUS) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, None) + self.assertEqual(b.strides, (s.get_bytesize(), s.get_pitch())) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + s2 = s.subsurface((1, 1, 7, 4)) # Not contiguous + a = s2.get_view("2") + b = Importer(a, buftools.PyBUF_STRIDES) + self.assertEqual(b.ndim, 2) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, s2.get_bytesize()) + self.assertEqual(b.shape, s2.get_size()) + self.assertEqual(b.strides, (s2.get_bytesize(), s.get_pitch())) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s2._pixels_address) + b = Importer(a, buftools.PyBUF_RECORDS) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, "=I") + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_FORMAT) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ANY_CONTIGUOUS) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_newbuf_PyBUF_flags_3D(self): + from pygame.tests.test_utils import buftools + + Importer = buftools.Importer + s = pygame.Surface((12, 6), 0, 24) + rmask, gmask, bmask, amask = s.get_masks() + if self.lilendian: + if rmask == 0x0000FF: + color_step = 1 + addr_offset = 0 + else: + color_step = -1 + addr_offset = 2 + else: + if rmask == 0xFF0000: + color_step = 1 + addr_offset = 0 + else: + color_step = -1 + addr_offset = 2 + a = s.get_view("3") + b = Importer(a, buftools.PyBUF_STRIDES) + w, h = s.get_size() + shape = w, h, 3 + strides = 3, s.get_pitch(), color_step + self.assertEqual(b.ndim, 3) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 1) + self.assertEqual(b.shape, shape) + self.assertEqual(b.strides, strides) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address + addr_offset) + b = Importer(a, buftools.PyBUF_RECORDS_RO) + self.assertEqual(b.ndim, 3) + self.assertEqual(b.format, "B") + self.assertEqual(b.strides, strides) + b = Importer(a, buftools.PyBUF_RECORDS) + self.assertEqual(b.ndim, 3) + self.assertEqual(b.format, "B") + self.assertEqual(b.strides, strides) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_FORMAT) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ANY_CONTIGUOUS) + + @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") + def test_newbuf_PyBUF_flags_rgba(self): + # All color plane views are handled by the same routine, + # so only one plane need be checked. + from pygame.tests.test_utils import buftools + + Importer = buftools.Importer + s = pygame.Surface((12, 6), 0, 24) + rmask, gmask, bmask, amask = s.get_masks() + if self.lilendian: + if rmask == 0x0000FF: + addr_offset = 0 + else: + addr_offset = 2 + else: + if rmask == 0xFF0000: + addr_offset = 0 + else: + addr_offset = 2 + a = s.get_view("R") + b = Importer(a, buftools.PyBUF_STRIDES) + w, h = s.get_size() + shape = w, h + strides = s.get_bytesize(), s.get_pitch() + self.assertEqual(b.ndim, 2) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.length) + self.assertEqual(b.itemsize, 1) + self.assertEqual(b.shape, shape) + self.assertEqual(b.strides, strides) + self.assertTrue(b.suboffsets is None) + self.assertFalse(b.readonly) + self.assertEqual(b.buf, s._pixels_address + addr_offset) + b = Importer(a, buftools.PyBUF_RECORDS_RO) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, "B") + self.assertEqual(b.strides, strides) + b = Importer(a, buftools.PyBUF_RECORDS) + self.assertEqual(b.ndim, 2) + self.assertEqual(b.format, "B") + self.assertEqual(b.strides, strides) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_FORMAT) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ND) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, buftools.PyBUF_ANY_CONTIGUOUS) + + +class SurfaceBlendTest(unittest.TestCase): + def setUp(self): + # Needed for 8 bits-per-pixel color palette surface tests. + pygame.display.init() + + def tearDown(self): + pygame.display.quit() + + _test_palette = [ + (0, 0, 0, 255), + (10, 30, 60, 0), + (25, 75, 100, 128), + (200, 150, 100, 200), + (0, 100, 200, 255), + ] + surf_size = (10, 12) + _test_points = [ + ((0, 0), 1), + ((4, 5), 1), + ((9, 0), 2), + ((5, 5), 2), + ((0, 11), 3), + ((4, 6), 3), + ((9, 11), 4), + ((5, 6), 4), + ] + + def _make_surface(self, bitsize, srcalpha=False, palette=None): + if palette is None: + palette = self._test_palette + flags = 0 + if srcalpha: + flags |= SRCALPHA + surf = pygame.Surface(self.surf_size, flags, bitsize) + if bitsize == 8: + surf.set_palette([c[:3] for c in palette]) + return surf + + def _fill_surface(self, surf, palette=None): + if palette is None: + palette = self._test_palette + surf.fill(palette[1], (0, 0, 5, 6)) + surf.fill(palette[2], (5, 0, 5, 6)) + surf.fill(palette[3], (0, 6, 5, 6)) + surf.fill(palette[4], (5, 6, 5, 6)) + + def _make_src_surface(self, bitsize, srcalpha=False, palette=None): + surf = self._make_surface(bitsize, srcalpha, palette) + self._fill_surface(surf, palette) + return surf + + def _assert_surface(self, surf, palette=None, msg=""): + if palette is None: + palette = self._test_palette + if surf.get_bitsize() == 16: + palette = [surf.unmap_rgb(surf.map_rgb(c)) for c in palette] + for posn, i in self._test_points: + self.assertEqual( + surf.get_at(posn), + palette[i], + "%s != %s: flags: %i, bpp: %i, posn: %s%s" + % ( + surf.get_at(posn), + palette[i], + surf.get_flags(), + surf.get_bitsize(), + posn, + msg, + ), + ) + + def test_blit_blend(self): + sources = [ + self._make_src_surface(8), + self._make_src_surface(16), + self._make_src_surface(16, srcalpha=True), + self._make_src_surface(24), + self._make_src_surface(32), + self._make_src_surface(32, srcalpha=True), + ] + destinations = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + blend = [ + ("BLEND_ADD", (0, 25, 100, 255), lambda a, b: min(a + b, 255)), + ("BLEND_SUB", (100, 25, 0, 100), lambda a, b: max(a - b, 0)), + ("BLEND_MULT", (100, 200, 0, 0), lambda a, b: (a * b) // 256), + ("BLEND_MIN", (255, 0, 0, 255), min), + ("BLEND_MAX", (0, 255, 0, 255), max), + ] + + for src in sources: + src_palette = [src.unmap_rgb(src.map_rgb(c)) for c in self._test_palette] + for dst in destinations: + for blend_name, dst_color, op in blend: + dc = dst.unmap_rgb(dst.map_rgb(dst_color)) + p = [] + for sc in src_palette: + c = [op(dc[i], sc[i]) for i in range(3)] + if dst.get_masks()[3]: + c.append(dc[3]) + else: + c.append(255) + c = dst.unmap_rgb(dst.map_rgb(c)) + p.append(c) + dst.fill(dst_color) + dst.blit(src, (0, 0), special_flags=getattr(pygame, blend_name)) + self._assert_surface( + dst, + p, + ( + ", op: %s, src bpp: %i" + ", src flags: %i" + % (blend_name, src.get_bitsize(), src.get_flags()) + ), + ) + + src = self._make_src_surface(32) + masks = src.get_masks() + dst = pygame.Surface( + src.get_size(), 0, 32, [masks[2], masks[1], masks[0], masks[3]] + ) + for blend_name, dst_color, op in blend: + p = [] + for src_color in self._test_palette: + c = [op(dst_color[i], src_color[i]) for i in range(3)] + c.append(255) + p.append(tuple(c)) + dst.fill(dst_color) + dst.blit(src, (0, 0), special_flags=getattr(pygame, blend_name)) + self._assert_surface(dst, p, ", %s" % blend_name) + + # Blend blits are special cased for 32 to 32 bit surfaces. + # + # Confirm that it works when the rgb bytes are not the + # least significant bytes. + pat = self._make_src_surface(32) + masks = pat.get_masks() + if min(masks) == 0xFF000000: + masks = [m >> 8 for m in masks] + else: + masks = [m << 8 for m in masks] + src = pygame.Surface(pat.get_size(), 0, 32, masks) + self._fill_surface(src) + dst = pygame.Surface(src.get_size(), 0, 32, masks) + for blend_name, dst_color, op in blend: + p = [] + for src_color in self._test_palette: + c = [op(dst_color[i], src_color[i]) for i in range(3)] + c.append(255) + p.append(tuple(c)) + dst.fill(dst_color) + dst.blit(src, (0, 0), special_flags=getattr(pygame, blend_name)) + self._assert_surface(dst, p, ", %s" % blend_name) + + def test_blit_blend_rgba(self): + sources = [ + self._make_src_surface(8), + self._make_src_surface(16), + self._make_src_surface(16, srcalpha=True), + self._make_src_surface(24), + self._make_src_surface(32), + self._make_src_surface(32, srcalpha=True), + ] + destinations = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + blend = [ + ("BLEND_RGBA_ADD", (0, 25, 100, 255), lambda a, b: min(a + b, 255)), + ("BLEND_RGBA_SUB", (0, 25, 100, 255), lambda a, b: max(a - b, 0)), + ("BLEND_RGBA_MULT", (0, 7, 100, 255), lambda a, b: (a * b) // 256), + ("BLEND_RGBA_MIN", (0, 255, 0, 255), min), + ("BLEND_RGBA_MAX", (0, 255, 0, 255), max), + ] + + for src in sources: + src_palette = [src.unmap_rgb(src.map_rgb(c)) for c in self._test_palette] + for dst in destinations: + for blend_name, dst_color, op in blend: + dc = dst.unmap_rgb(dst.map_rgb(dst_color)) + p = [] + for sc in src_palette: + c = [op(dc[i], sc[i]) for i in range(4)] + if not dst.get_masks()[3]: + c[3] = 255 + c = dst.unmap_rgb(dst.map_rgb(c)) + p.append(c) + dst.fill(dst_color) + dst.blit(src, (0, 0), special_flags=getattr(pygame, blend_name)) + self._assert_surface( + dst, + p, + ( + ", op: %s, src bpp: %i" + ", src flags: %i" + % (blend_name, src.get_bitsize(), src.get_flags()) + ), + ) + + # Blend blits are special cased for 32 to 32 bit surfaces + # with per-pixel alpha. + # + # Confirm the general case is used instead when the formats differ. + src = self._make_src_surface(32, srcalpha=True) + masks = src.get_masks() + dst = pygame.Surface( + src.get_size(), SRCALPHA, 32, (masks[2], masks[1], masks[0], masks[3]) + ) + for blend_name, dst_color, op in blend: + p = [ + tuple([op(dst_color[i], src_color[i]) for i in range(4)]) + for src_color in self._test_palette + ] + dst.fill(dst_color) + dst.blit(src, (0, 0), special_flags=getattr(pygame, blend_name)) + self._assert_surface(dst, p, ", %s" % blend_name) + + # Confirm this special case handles subsurfaces. + src = pygame.Surface((8, 10), SRCALPHA, 32) + dst = pygame.Surface((8, 10), SRCALPHA, 32) + tst = pygame.Surface((8, 10), SRCALPHA, 32) + src.fill((1, 2, 3, 4)) + dst.fill((40, 30, 20, 10)) + subsrc = src.subsurface((2, 3, 4, 4)) + subdst = dst.subsurface((2, 3, 4, 4)) + subdst.blit(subsrc, (0, 0), special_flags=BLEND_RGBA_ADD) + tst.fill((40, 30, 20, 10)) + tst.fill((41, 32, 23, 14), (2, 3, 4, 4)) + for x in range(8): + for y in range(10): + self.assertEqual( + dst.get_at((x, y)), + tst.get_at((x, y)), + "%s != %s at (%i, %i)" + % (dst.get_at((x, y)), tst.get_at((x, y)), x, y), + ) + + def test_blit_blend_premultiplied(self): + def test_premul_surf( + src_col, + dst_col, + src_size=(16, 16), + dst_size=(16, 16), + src_bit_depth=32, + dst_bit_depth=32, + src_has_alpha=True, + dst_has_alpha=True, + ): + if src_bit_depth == 8: + src = pygame.Surface(src_size, 0, src_bit_depth) + palette = [src_col, dst_col] + src.set_palette(palette) + src.fill(palette[0]) + elif src_has_alpha: + src = pygame.Surface(src_size, SRCALPHA, src_bit_depth) + src.fill(src_col) + else: + src = pygame.Surface(src_size, 0, src_bit_depth) + src.fill(src_col) + + if dst_bit_depth == 8: + dst = pygame.Surface(dst_size, 0, dst_bit_depth) + palette = [src_col, dst_col] + dst.set_palette(palette) + dst.fill(palette[1]) + elif dst_has_alpha: + dst = pygame.Surface(dst_size, SRCALPHA, dst_bit_depth) + dst.fill(dst_col) + else: + dst = pygame.Surface(dst_size, 0, dst_bit_depth) + dst.fill(dst_col) + + dst.blit(src, (0, 0), special_flags=BLEND_PREMULTIPLIED) + + actual_col = dst.get_at( + (int(float(src_size[0] / 2.0)), int(float(src_size[0] / 2.0))) + ) + + # This is the blend pre-multiplied formula + if src_col.a == 0: + expected_col = dst_col + elif src_col.a == 255: + expected_col = src_col + else: + # sC + dC - (((dC + 1) * sA >> 8) + expected_col = pygame.Color( + (src_col.r + dst_col.r - ((dst_col.r + 1) * src_col.a >> 8)), + (src_col.g + dst_col.g - ((dst_col.g + 1) * src_col.a >> 8)), + (src_col.b + dst_col.b - ((dst_col.b + 1) * src_col.a >> 8)), + (src_col.a + dst_col.a - ((dst_col.a + 1) * src_col.a >> 8)), + ) + if not dst_has_alpha: + expected_col.a = 255 + + return (expected_col, actual_col) + + # # Colour Tests + self.assertEqual( + *test_premul_surf(pygame.Color(40, 20, 0, 51), pygame.Color(40, 20, 0, 51)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(0, 0, 0, 0), pygame.Color(40, 20, 0, 51)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(40, 20, 0, 51), pygame.Color(0, 0, 0, 0)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(0, 0, 0, 0), pygame.Color(0, 0, 0, 0)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(2, 2, 2, 2), pygame.Color(40, 20, 0, 51)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(40, 20, 0, 51), pygame.Color(2, 2, 2, 2)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(2, 2, 2, 2), pygame.Color(2, 2, 2, 2)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(9, 9, 9, 9), pygame.Color(40, 20, 0, 51)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(40, 20, 0, 51), pygame.Color(9, 9, 9, 9)) + ) + + self.assertEqual( + *test_premul_surf(pygame.Color(9, 9, 9, 9), pygame.Color(9, 9, 9, 9)) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(127, 127, 127, 127), pygame.Color(40, 20, 0, 51) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(40, 20, 0, 51), pygame.Color(127, 127, 127, 127) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(127, 127, 127, 127), pygame.Color(127, 127, 127, 127) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(200, 200, 200, 200), pygame.Color(40, 20, 0, 51) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(40, 20, 0, 51), pygame.Color(200, 200, 200, 200) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(200, 200, 200, 200), pygame.Color(200, 200, 200, 200) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(255, 255, 255, 255), pygame.Color(40, 20, 0, 51) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(40, 20, 0, 51), pygame.Color(255, 255, 255, 255) + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(255, 255, 255, 255), pygame.Color(255, 255, 255, 255) + ) + ) + + # Surface format tests + self.assertRaises( + IndexError, + test_premul_surf, + pygame.Color(255, 255, 255, 255), + pygame.Color(255, 255, 255, 255), + src_size=(0, 0), + dst_size=(0, 0), + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(40, 20, 0, 51), + pygame.Color(30, 20, 0, 51), + src_size=(4, 4), + dst_size=(9, 9), + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 51), + pygame.Color(40, 20, 0, 51), + src_size=(17, 67), + dst_size=(69, 69), + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 51), + src_size=(17, 67), + dst_size=(69, 69), + src_has_alpha=True, + ) + ) + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 51), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + dst_has_alpha=False, + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + src_has_alpha=False, + dst_has_alpha=False, + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + dst_bit_depth=24, + src_has_alpha=True, + dst_has_alpha=False, + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + src_bit_depth=24, + src_has_alpha=False, + dst_has_alpha=True, + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + src_bit_depth=24, + dst_bit_depth=24, + src_has_alpha=False, + dst_has_alpha=False, + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + src_bit_depth=8, + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + dst_bit_depth=8, + ) + ) + + self.assertEqual( + *test_premul_surf( + pygame.Color(30, 20, 0, 255), + pygame.Color(40, 20, 0, 255), + src_size=(17, 67), + dst_size=(69, 69), + src_bit_depth=8, + dst_bit_depth=8, + ) + ) + + def test_blit_blend_big_rect(self): + """test that an oversized rect works ok.""" + color = (1, 2, 3, 255) + area = (1, 1, 30, 30) + s1 = pygame.Surface((4, 4), 0, 32) + r = s1.fill(special_flags=pygame.BLEND_ADD, color=color, rect=area) + + self.assertEqual(pygame.Rect((1, 1, 3, 3)), r) + self.assertEqual(s1.get_at((0, 0)), (0, 0, 0, 255)) + self.assertEqual(s1.get_at((1, 1)), color) + + black = pygame.Color("black") + red = pygame.Color("red") + self.assertNotEqual(black, red) + + surf = pygame.Surface((10, 10), 0, 32) + surf.fill(black) + subsurf = surf.subsurface(pygame.Rect(0, 1, 10, 8)) + self.assertEqual(surf.get_at((0, 0)), black) + self.assertEqual(surf.get_at((0, 9)), black) + + subsurf.fill(red, (0, -1, 10, 1), pygame.BLEND_RGB_ADD) + self.assertEqual(surf.get_at((0, 0)), black) + self.assertEqual(surf.get_at((0, 9)), black) + + subsurf.fill(red, (0, 8, 10, 1), pygame.BLEND_RGB_ADD) + self.assertEqual(surf.get_at((0, 0)), black) + self.assertEqual(surf.get_at((0, 9)), black) + + def test_GET_PIXELVALS(self): + # surface.h GET_PIXELVALS bug regarding whether of not + # a surface has per-pixel alpha. Looking at the Amask + # is not enough. The surface's SRCALPHA flag must also + # be considered. Fix rev. 1923. + src = self._make_surface(32, srcalpha=True) + src.fill((0, 0, 0, 128)) + src.set_alpha(None) # Clear SRCALPHA flag. + dst = self._make_surface(32, srcalpha=True) + dst.blit(src, (0, 0), special_flags=BLEND_RGBA_ADD) + self.assertEqual(dst.get_at((0, 0)), (0, 0, 0, 255)) + + def test_fill_blend(self): + destinations = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + blend = [ + ("BLEND_ADD", (0, 25, 100, 255), lambda a, b: min(a + b, 255)), + ("BLEND_SUB", (0, 25, 100, 255), lambda a, b: max(a - b, 0)), + ("BLEND_MULT", (0, 7, 100, 255), lambda a, b: (a * b) // 256), + ("BLEND_MIN", (0, 255, 0, 255), min), + ("BLEND_MAX", (0, 255, 0, 255), max), + ] + + for dst in destinations: + dst_palette = [dst.unmap_rgb(dst.map_rgb(c)) for c in self._test_palette] + for blend_name, fill_color, op in blend: + fc = dst.unmap_rgb(dst.map_rgb(fill_color)) + self._fill_surface(dst) + p = [] + for dc in dst_palette: + c = [op(dc[i], fc[i]) for i in range(3)] + if dst.get_masks()[3]: + c.append(dc[3]) + else: + c.append(255) + c = dst.unmap_rgb(dst.map_rgb(c)) + p.append(c) + dst.fill(fill_color, special_flags=getattr(pygame, blend_name)) + self._assert_surface(dst, p, ", %s" % blend_name) + + def test_fill_blend_rgba(self): + destinations = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + blend = [ + ("BLEND_RGBA_ADD", (0, 25, 100, 255), lambda a, b: min(a + b, 255)), + ("BLEND_RGBA_SUB", (0, 25, 100, 255), lambda a, b: max(a - b, 0)), + ("BLEND_RGBA_MULT", (0, 7, 100, 255), lambda a, b: (a * b) // 256), + ("BLEND_RGBA_MIN", (0, 255, 0, 255), min), + ("BLEND_RGBA_MAX", (0, 255, 0, 255), max), + ] + + for dst in destinations: + dst_palette = [dst.unmap_rgb(dst.map_rgb(c)) for c in self._test_palette] + for blend_name, fill_color, op in blend: + fc = dst.unmap_rgb(dst.map_rgb(fill_color)) + self._fill_surface(dst) + p = [] + for dc in dst_palette: + c = [op(dc[i], fc[i]) for i in range(4)] + if not dst.get_masks()[3]: + c[3] = 255 + c = dst.unmap_rgb(dst.map_rgb(c)) + p.append(c) + dst.fill(fill_color, special_flags=getattr(pygame, blend_name)) + self._assert_surface(dst, p, ", %s" % blend_name) + + +class SurfaceSelfBlitTest(unittest.TestCase): + """Blit to self tests. + + This test case is in response to MotherHamster Bugzilla Bug 19. + """ + + def setUp(self): + # Needed for 8 bits-per-pixel color palette surface tests. + pygame.display.init() + + def tearDown(self): + pygame.display.quit() + + _test_palette = [(0, 0, 0, 255), (255, 0, 0, 0), (0, 255, 0, 255)] + surf_size = (9, 6) + + def _fill_surface(self, surf, palette=None): + if palette is None: + palette = self._test_palette + surf.fill(palette[1]) + surf.fill(palette[2], (1, 2, 1, 2)) + + def _make_surface(self, bitsize, srcalpha=False, palette=None): + if palette is None: + palette = self._test_palette + flags = 0 + if srcalpha: + flags |= SRCALPHA + surf = pygame.Surface(self.surf_size, flags, bitsize) + if bitsize == 8: + surf.set_palette([c[:3] for c in palette]) + self._fill_surface(surf, palette) + return surf + + def _assert_same(self, a, b): + w, h = a.get_size() + for x in range(w): + for y in range(h): + self.assertEqual( + a.get_at((x, y)), + b.get_at((x, y)), + ( + "%s != %s, bpp: %i" + % (a.get_at((x, y)), b.get_at((x, y)), a.get_bitsize()) + ), + ) + + def test_overlap_check(self): + # Ensure overlapping blits are properly detected. There are two + # places where this is done, within SoftBlitPyGame() in alphablit.c + # and PySurface_Blit() in surface.c. SoftBlitPyGame should catch the + # per-pixel alpha surface, PySurface_Blit the colorkey and blanket + # alpha surface. per-pixel alpha and blanket alpha self blits are + # not properly handled by SDL 1.2.13, so Pygame does them. + bgc = (0, 0, 0, 255) + rectc_left = (128, 64, 32, 255) + rectc_right = (255, 255, 255, 255) + colors = [(255, 255, 255, 255), (128, 64, 32, 255)] + overlaps = [ + (0, 0, 1, 0, (50, 0)), + (0, 0, 49, 1, (98, 2)), + (0, 0, 49, 49, (98, 98)), + (49, 0, 0, 1, (0, 2)), + (49, 0, 0, 49, (0, 98)), + ] + surfs = [pygame.Surface((100, 100), SRCALPHA, 32)] + surf = pygame.Surface((100, 100), 0, 32) + surf.set_alpha(255) + surfs.append(surf) + surf = pygame.Surface((100, 100), 0, 32) + surf.set_colorkey((0, 1, 0)) + surfs.append(surf) + for surf in surfs: + for s_x, s_y, d_x, d_y, test_posn in overlaps: + surf.fill(bgc) + surf.fill(rectc_right, (25, 0, 25, 50)) + surf.fill(rectc_left, (0, 0, 25, 50)) + surf.blit(surf, (d_x, d_y), (s_x, s_y, 50, 50)) + self.assertEqual(surf.get_at(test_posn), rectc_right) + + # https://github.com/pygame/pygame/issues/370#issuecomment-364625291 + @unittest.skipIf("ppc64le" in platform.uname(), "known ppc64le issue") + def test_colorkey(self): + # Check a workaround for an SDL 1.2.13 surface self-blit problem + # (MotherHamster Bugzilla bug 19). + pygame.display.set_mode((100, 50)) # Needed for 8bit surface + bitsizes = [8, 16, 24, 32] + for bitsize in bitsizes: + surf = self._make_surface(bitsize) + surf.set_colorkey(self._test_palette[1]) + surf.blit(surf, (3, 0)) + p = [] + for c in self._test_palette: + c = surf.unmap_rgb(surf.map_rgb(c)) + p.append(c) + p[1] = (p[1][0], p[1][1], p[1][2], 0) + tmp = self._make_surface(32, srcalpha=True, palette=p) + tmp.blit(tmp, (3, 0)) + tmp.set_alpha(None) + comp = self._make_surface(bitsize) + comp.blit(tmp, (0, 0)) + self._assert_same(surf, comp) + + # https://github.com/pygame/pygame/issues/370#issuecomment-364625291 + @unittest.skipIf("ppc64le" in platform.uname(), "known ppc64le issue") + def test_blanket_alpha(self): + # Check a workaround for an SDL 1.2.13 surface self-blit problem + # (MotherHamster Bugzilla bug 19). + pygame.display.set_mode((100, 50)) # Needed for 8bit surface + bitsizes = [8, 16, 24, 32] + for bitsize in bitsizes: + surf = self._make_surface(bitsize) + surf.set_alpha(128) + surf.blit(surf, (3, 0)) + p = [] + for c in self._test_palette: + c = surf.unmap_rgb(surf.map_rgb(c)) + p.append((c[0], c[1], c[2], 128)) + tmp = self._make_surface(32, srcalpha=True, palette=p) + tmp.blit(tmp, (3, 0)) + tmp.set_alpha(None) + comp = self._make_surface(bitsize) + comp.blit(tmp, (0, 0)) + self._assert_same(surf, comp) + + def test_pixel_alpha(self): + bitsizes = [16, 32] + for bitsize in bitsizes: + surf = self._make_surface(bitsize, srcalpha=True) + comp = self._make_surface(bitsize, srcalpha=True) + comp.blit(surf, (3, 0)) + surf.blit(surf, (3, 0)) + self._assert_same(surf, comp) + + def test_blend(self): + bitsizes = [8, 16, 24, 32] + blends = ["BLEND_ADD", "BLEND_SUB", "BLEND_MULT", "BLEND_MIN", "BLEND_MAX"] + for bitsize in bitsizes: + surf = self._make_surface(bitsize) + comp = self._make_surface(bitsize) + for blend in blends: + self._fill_surface(surf) + self._fill_surface(comp) + comp.blit(surf, (3, 0), special_flags=getattr(pygame, blend)) + surf.blit(surf, (3, 0), special_flags=getattr(pygame, blend)) + self._assert_same(surf, comp) + + def test_blend_rgba(self): + bitsizes = [16, 32] + blends = [ + "BLEND_RGBA_ADD", + "BLEND_RGBA_SUB", + "BLEND_RGBA_MULT", + "BLEND_RGBA_MIN", + "BLEND_RGBA_MAX", + ] + for bitsize in bitsizes: + surf = self._make_surface(bitsize, srcalpha=True) + comp = self._make_surface(bitsize, srcalpha=True) + for blend in blends: + self._fill_surface(surf) + self._fill_surface(comp) + comp.blit(surf, (3, 0), special_flags=getattr(pygame, blend)) + surf.blit(surf, (3, 0), special_flags=getattr(pygame, blend)) + self._assert_same(surf, comp) + + def test_subsurface(self): + # Blitting a surface to its subsurface is allowed. + surf = self._make_surface(32, srcalpha=True) + comp = surf.copy() + comp.blit(surf, (3, 0)) + sub = surf.subsurface((3, 0, 6, 6)) + sub.blit(surf, (0, 0)) + del sub + self._assert_same(surf, comp) + + # Blitting a subsurface to its owner is forbidden because of + # lock conficts. This limitation allows the overlap check + # in PySurface_Blit of alphablit.c to be simplified. + def do_blit(d, s): + d.blit(s, (0, 0)) + + sub = surf.subsurface((1, 1, 2, 2)) + self.assertRaises(pygame.error, do_blit, surf, sub) + + def test_copy_alpha(self): + """issue 581: alpha of surface copy with SRCALPHA is set to 0.""" + surf = pygame.Surface((16, 16), pygame.SRCALPHA, 32) + self.assertEqual(surf.get_alpha(), 255) + surf2 = surf.copy() + self.assertEqual(surf2.get_alpha(), 255) + + +class SurfaceFillTest(unittest.TestCase): + def setUp(self): + pygame.display.init() + + def tearDown(self): + pygame.display.quit() + + def test_fill(self): + screen = pygame.display.set_mode((640, 480)) + + # Green and blue test pattern + screen.fill((0, 255, 0), (0, 0, 320, 240)) + screen.fill((0, 255, 0), (320, 240, 320, 240)) + screen.fill((0, 0, 255), (320, 0, 320, 240)) + screen.fill((0, 0, 255), (0, 240, 320, 240)) + + # Now apply a clip rect, such that only the left side of the + # screen should be effected by blit operations. + screen.set_clip((0, 0, 320, 480)) + + # Test fills with each special flag, and additionally without any. + screen.fill((255, 0, 0, 127), (160, 0, 320, 30), 0) + screen.fill((255, 0, 0, 127), (160, 30, 320, 30), pygame.BLEND_ADD) + screen.fill((0, 127, 127, 127), (160, 60, 320, 30), pygame.BLEND_SUB) + screen.fill((0, 63, 63, 127), (160, 90, 320, 30), pygame.BLEND_MULT) + screen.fill((0, 127, 127, 127), (160, 120, 320, 30), pygame.BLEND_MIN) + screen.fill((127, 0, 0, 127), (160, 150, 320, 30), pygame.BLEND_MAX) + screen.fill((255, 0, 0, 127), (160, 180, 320, 30), pygame.BLEND_RGBA_ADD) + screen.fill((0, 127, 127, 127), (160, 210, 320, 30), pygame.BLEND_RGBA_SUB) + screen.fill((0, 63, 63, 127), (160, 240, 320, 30), pygame.BLEND_RGBA_MULT) + screen.fill((0, 127, 127, 127), (160, 270, 320, 30), pygame.BLEND_RGBA_MIN) + screen.fill((127, 0, 0, 127), (160, 300, 320, 30), pygame.BLEND_RGBA_MAX) + screen.fill((255, 0, 0, 127), (160, 330, 320, 30), pygame.BLEND_RGB_ADD) + screen.fill((0, 127, 127, 127), (160, 360, 320, 30), pygame.BLEND_RGB_SUB) + screen.fill((0, 63, 63, 127), (160, 390, 320, 30), pygame.BLEND_RGB_MULT) + screen.fill((0, 127, 127, 127), (160, 420, 320, 30), pygame.BLEND_RGB_MIN) + screen.fill((255, 0, 0, 127), (160, 450, 320, 30), pygame.BLEND_RGB_MAX) + + # Update the display so we can see the results + pygame.display.flip() + + # Compare colors on both sides of window + for y in range(5, 480, 10): + self.assertEqual(screen.get_at((10, y)), screen.get_at((330, 480 - y))) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/surfarray_tags.py b/.venv/lib/python3.8/site-packages/pygame/tests/surfarray_tags.py new file mode 100644 index 0000000..baa535c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/surfarray_tags.py @@ -0,0 +1,16 @@ +__tags__ = ["array"] + +exclude = False + +try: + import numpy +except ImportError: + exclude = True +else: + try: + import pygame.pixelcopy + except ImportError: + exclude = True + +if exclude: + __tags__.extend(("ignore", "subprocess_ignore")) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/surfarray_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/surfarray_test.py new file mode 100644 index 0000000..246da82 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/surfarray_test.py @@ -0,0 +1,754 @@ +import unittest +import platform + +from numpy import ( + uint8, + uint16, + uint32, + uint64, + zeros, + float32, + float64, + alltrue, + rint, + arange, +) + +import pygame +from pygame.locals import * + +import pygame.surfarray + + +IS_PYPY = "PyPy" == platform.python_implementation() + + +@unittest.skipIf(IS_PYPY, "pypy skip known failure") # TODO +class SurfarrayModuleTest(unittest.TestCase): + pixels2d = {8: True, 16: True, 24: False, 32: True} + pixels3d = {8: False, 16: False, 24: True, 32: True} + array2d = {8: True, 16: True, 24: True, 32: True} + array3d = {8: False, 16: False, 24: True, 32: True} + + test_palette = [ + (0, 0, 0, 255), + (10, 30, 60, 255), + (25, 75, 100, 255), + (100, 150, 200, 255), + (0, 100, 200, 255), + ] + surf_size = (10, 12) + test_points = [ + ((0, 0), 1), + ((4, 5), 1), + ((9, 0), 2), + ((5, 5), 2), + ((0, 11), 3), + ((4, 6), 3), + ((9, 11), 4), + ((5, 6), 4), + ] + + @classmethod + def setUpClass(cls): + # Needed for 8 bits-per-pixel color palette surface tests. + pygame.init() + + @classmethod + def tearDownClass(cls): + pygame.quit() + + def setUp(cls): + # This makes sure pygame is always initialized before each test (in + # case a test calls pygame.quit()). + if not pygame.get_init(): + pygame.init() + + def _make_surface(self, bitsize, srcalpha=False, palette=None): + if palette is None: + palette = self.test_palette + flags = 0 + if srcalpha: + flags |= SRCALPHA + surf = pygame.Surface(self.surf_size, flags, bitsize) + if bitsize == 8: + surf.set_palette([c[:3] for c in palette]) + return surf + + def _fill_surface(self, surf, palette=None): + if palette is None: + palette = self.test_palette + surf.fill(palette[1], (0, 0, 5, 6)) + surf.fill(palette[2], (5, 0, 5, 6)) + surf.fill(palette[3], (0, 6, 5, 6)) + surf.fill(palette[4], (5, 6, 5, 6)) + + def _make_src_surface(self, bitsize, srcalpha=False, palette=None): + surf = self._make_surface(bitsize, srcalpha, palette) + self._fill_surface(surf, palette) + return surf + + def _assert_surface(self, surf, palette=None, msg=""): + if palette is None: + palette = self.test_palette + if surf.get_bitsize() == 16: + palette = [surf.unmap_rgb(surf.map_rgb(c)) for c in palette] + for posn, i in self.test_points: + self.assertEqual( + surf.get_at(posn), + palette[i], + "%s != %s: flags: %i, bpp: %i, posn: %s%s" + % ( + surf.get_at(posn), + palette[i], + surf.get_flags(), + surf.get_bitsize(), + posn, + msg, + ), + ) + + def _make_array3d(self, dtype): + return zeros((self.surf_size[0], self.surf_size[1], 3), dtype) + + def _fill_array2d(self, arr, surf): + palette = self.test_palette + arr[:5, :6] = surf.map_rgb(palette[1]) + arr[5:, :6] = surf.map_rgb(palette[2]) + arr[:5, 6:] = surf.map_rgb(palette[3]) + arr[5:, 6:] = surf.map_rgb(palette[4]) + + def _fill_array3d(self, arr): + palette = self.test_palette + arr[:5, :6] = palette[1][:3] + arr[5:, :6] = palette[2][:3] + arr[:5, 6:] = palette[3][:3] + arr[5:, 6:] = palette[4][:3] + + def _make_src_array3d(self, dtype): + arr = self._make_array3d(dtype) + self._fill_array3d(arr) + return arr + + def _make_array2d(self, dtype): + return zeros(self.surf_size, dtype) + + def test_array2d(self): + + sources = [ + self._make_src_surface(8), + self._make_src_surface(16), + self._make_src_surface(16, srcalpha=True), + self._make_src_surface(24), + self._make_src_surface(32), + self._make_src_surface(32, srcalpha=True), + ] + palette = self.test_palette + alpha_color = (0, 0, 0, 128) + + for surf in sources: + arr = pygame.surfarray.array2d(surf) + for posn, i in self.test_points: + self.assertEqual( + arr[posn], + surf.get_at_mapped(posn), + "%s != %s: flags: %i, bpp: %i, posn: %s" + % ( + arr[posn], + surf.get_at_mapped(posn), + surf.get_flags(), + surf.get_bitsize(), + posn, + ), + ) + + if surf.get_masks()[3]: + surf.fill(alpha_color) + arr = pygame.surfarray.array2d(surf) + posn = (0, 0) + self.assertEqual( + arr[posn], + surf.get_at_mapped(posn), + "%s != %s: bpp: %i" + % (arr[posn], surf.get_at_mapped(posn), surf.get_bitsize()), + ) + + def test_array3d(self): + + sources = [ + self._make_src_surface(16), + self._make_src_surface(16, srcalpha=True), + self._make_src_surface(24), + self._make_src_surface(32), + self._make_src_surface(32, srcalpha=True), + ] + palette = self.test_palette + + for surf in sources: + arr = pygame.surfarray.array3d(surf) + + def same_color(ac, sc): + return ac[0] == sc[0] and ac[1] == sc[1] and ac[2] == sc[2] + + for posn, i in self.test_points: + self.assertTrue( + same_color(arr[posn], surf.get_at(posn)), + "%s != %s: flags: %i, bpp: %i, posn: %s" + % ( + tuple(arr[posn]), + surf.get_at(posn), + surf.get_flags(), + surf.get_bitsize(), + posn, + ), + ) + + def test_array_alpha(self): + + palette = [ + (0, 0, 0, 0), + (10, 50, 100, 255), + (60, 120, 240, 130), + (64, 128, 255, 0), + (255, 128, 0, 65), + ] + targets = [ + self._make_src_surface(8, palette=palette), + self._make_src_surface(16, palette=palette), + self._make_src_surface(16, palette=palette, srcalpha=True), + self._make_src_surface(24, palette=palette), + self._make_src_surface(32, palette=palette), + self._make_src_surface(32, palette=palette, srcalpha=True), + ] + + for surf in targets: + p = palette + if surf.get_bitsize() == 16: + p = [surf.unmap_rgb(surf.map_rgb(c)) for c in p] + arr = pygame.surfarray.array_alpha(surf) + if surf.get_masks()[3]: + for (x, y), i in self.test_points: + self.assertEqual( + arr[x, y], + p[i][3], + ( + "%i != %i, posn: (%i, %i), " + "bitsize: %i" + % (arr[x, y], p[i][3], x, y, surf.get_bitsize()) + ), + ) + else: + self.assertTrue(alltrue(arr == 255)) + + # No per-pixel alpha when blanket alpha is None. + for surf in targets: + blanket_alpha = surf.get_alpha() + surf.set_alpha(None) + arr = pygame.surfarray.array_alpha(surf) + self.assertTrue( + alltrue(arr == 255), + "All alpha values should be 255 when" + " surf.set_alpha(None) has been set." + " bitsize: %i, flags: %i" % (surf.get_bitsize(), surf.get_flags()), + ) + surf.set_alpha(blanket_alpha) + + # Bug for per-pixel alpha surface when blanket alpha 0. + for surf in targets: + blanket_alpha = surf.get_alpha() + surf.set_alpha(0) + arr = pygame.surfarray.array_alpha(surf) + if surf.get_masks()[3]: + self.assertFalse( + alltrue(arr == 255), + "bitsize: %i, flags: %i" % (surf.get_bitsize(), surf.get_flags()), + ) + else: + self.assertTrue( + alltrue(arr == 255), + "bitsize: %i, flags: %i" % (surf.get_bitsize(), surf.get_flags()), + ) + surf.set_alpha(blanket_alpha) + + def test_array_colorkey(self): + + palette = [ + (0, 0, 0, 0), + (10, 50, 100, 255), + (60, 120, 240, 130), + (64, 128, 255, 0), + (255, 128, 0, 65), + ] + targets = [ + self._make_src_surface(8, palette=palette), + self._make_src_surface(16, palette=palette), + self._make_src_surface(16, palette=palette, srcalpha=True), + self._make_src_surface(24, palette=palette), + self._make_src_surface(32, palette=palette), + self._make_src_surface(32, palette=palette, srcalpha=True), + ] + + for surf in targets: + p = palette + if surf.get_bitsize() == 16: + p = [surf.unmap_rgb(surf.map_rgb(c)) for c in p] + surf.set_colorkey(None) + arr = pygame.surfarray.array_colorkey(surf) + self.assertTrue(alltrue(arr == 255)) + + for i in range(1, len(palette)): + surf.set_colorkey(p[i]) + alphas = [255] * len(p) + alphas[i] = 0 + arr = pygame.surfarray.array_colorkey(surf) + for (x, y), j in self.test_points: + self.assertEqual( + arr[x, y], + alphas[j], + ( + "%i != %i, posn: (%i, %i), " + "bitsize: %i" + % (arr[x, y], alphas[j], x, y, surf.get_bitsize()) + ), + ) + + def test_array_red(self): + self._test_array_rgb("red", 0) + + def test_array_green(self): + self._test_array_rgb("green", 1) + + def test_array_blue(self): + self._test_array_rgb("blue", 2) + + def _test_array_rgb(self, operation, mask_posn): + method_name = "array_" + operation + + array_rgb = getattr(pygame.surfarray, method_name) + palette = [ + (0, 0, 0, 255), + (5, 13, 23, 255), + (29, 31, 37, 255), + (131, 157, 167, 255), + (179, 191, 251, 255), + ] + plane = [c[mask_posn] for c in palette] + + targets = [ + self._make_src_surface(24, palette=palette), + self._make_src_surface(32, palette=palette), + self._make_src_surface(32, palette=palette, srcalpha=True), + ] + + for surf in targets: + self.assertFalse(surf.get_locked()) + for (x, y), i in self.test_points: + surf.fill(palette[i]) + arr = array_rgb(surf) + self.assertEqual(arr[x, y], plane[i]) + surf.fill((100, 100, 100, 250)) + self.assertEqual(arr[x, y], plane[i]) + self.assertFalse(surf.get_locked()) + del arr + + def test_blit_array(self): + + s = pygame.Surface((10, 10), 0, 24) + a = pygame.surfarray.array3d(s) + pygame.surfarray.blit_array(s, a) + + # target surfaces + targets = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + + # source arrays + arrays3d = [] + dtypes = [(8, uint8), (16, uint16), (32, uint32)] + try: + dtypes.append((64, uint64)) + except NameError: + pass + arrays3d = [(self._make_src_array3d(dtype), None) for __, dtype in dtypes] + for bitsize in [8, 16, 24, 32]: + palette = None + if bitsize == 16: + s = pygame.Surface((1, 1), 0, 16) + palette = [s.unmap_rgb(s.map_rgb(c)) for c in self.test_palette] + if self.pixels3d[bitsize]: + surf = self._make_src_surface(bitsize) + arr = pygame.surfarray.pixels3d(surf) + arrays3d.append((arr, palette)) + if self.array3d[bitsize]: + surf = self._make_src_surface(bitsize) + arr = pygame.surfarray.array3d(surf) + arrays3d.append((arr, palette)) + for sz, dtype in dtypes: + arrays3d.append((arr.astype(dtype), palette)) + + # tests on arrays + def do_blit(surf, arr): + pygame.surfarray.blit_array(surf, arr) + + for surf in targets: + bitsize = surf.get_bitsize() + for arr, palette in arrays3d: + surf.fill((0, 0, 0, 0)) + if bitsize == 8: + self.assertRaises(ValueError, do_blit, surf, arr) + else: + pygame.surfarray.blit_array(surf, arr) + self._assert_surface(surf, palette) + + if self.pixels2d[bitsize]: + surf.fill((0, 0, 0, 0)) + s = self._make_src_surface(bitsize, surf.get_flags() & SRCALPHA) + arr = pygame.surfarray.pixels2d(s) + pygame.surfarray.blit_array(surf, arr) + self._assert_surface(surf) + + if self.array2d[bitsize]: + s = self._make_src_surface(bitsize, surf.get_flags() & SRCALPHA) + arr = pygame.surfarray.array2d(s) + for sz, dtype in dtypes: + surf.fill((0, 0, 0, 0)) + if sz >= bitsize: + pygame.surfarray.blit_array(surf, arr.astype(dtype)) + self._assert_surface(surf) + else: + self.assertRaises( + ValueError, do_blit, surf, self._make_array2d(dtype) + ) + + # Check alpha for 2D arrays + surf = self._make_surface(16, srcalpha=True) + arr = zeros(surf.get_size(), uint16) + arr[...] = surf.map_rgb((0, 128, 255, 64)) + color = surf.unmap_rgb(arr[0, 0]) + pygame.surfarray.blit_array(surf, arr) + self.assertEqual(surf.get_at((5, 5)), color) + + surf = self._make_surface(32, srcalpha=True) + arr = zeros(surf.get_size(), uint32) + color = (0, 111, 255, 63) + arr[...] = surf.map_rgb(color) + pygame.surfarray.blit_array(surf, arr) + self.assertEqual(surf.get_at((5, 5)), color) + + # Check shifts + arr3d = self._make_src_array3d(uint8) + + shift_tests = [ + (16, [12, 0, 8, 4], [0xF000, 0xF, 0xF00, 0xF0]), + (24, [16, 0, 8, 0], [0xFF0000, 0xFF, 0xFF00, 0]), + (32, [0, 16, 24, 8], [0xFF, 0xFF0000, 0xFF000000, 0xFF00]), + ] + + for bitsize, shifts, masks in shift_tests: + surf = self._make_surface(bitsize, srcalpha=(shifts[3] != 0)) + palette = None + if bitsize == 16: + palette = [surf.unmap_rgb(surf.map_rgb(c)) for c in self.test_palette] + + self.assertRaises(TypeError, surf.set_shifts, shifts) + self.assertRaises(TypeError, surf.set_masks, masks) + + # Invalid arrays + surf = pygame.Surface((1, 1), 0, 32) + t = "abcd" + self.assertRaises(ValueError, do_blit, surf, t) + + surf_size = self.surf_size + surf = pygame.Surface(surf_size, 0, 32) + arr = zeros([surf_size[0], surf_size[1] + 1, 3], uint32) + self.assertRaises(ValueError, do_blit, surf, arr) + arr = zeros([surf_size[0] + 1, surf_size[1], 3], uint32) + self.assertRaises(ValueError, do_blit, surf, arr) + + surf = pygame.Surface((1, 4), 0, 32) + arr = zeros((4,), uint32) + self.assertRaises(ValueError, do_blit, surf, arr) + arr.shape = (1, 1, 1, 4) + self.assertRaises(ValueError, do_blit, surf, arr) + + # Issue #81: round from float to int + try: + rint + except NameError: + pass + else: + surf = pygame.Surface((10, 10), pygame.SRCALPHA, 32) + w, h = surf.get_size() + length = w * h + for dtype in [float32, float64]: + surf.fill((255, 255, 255, 0)) + farr = arange(0, length, dtype=dtype) + farr.shape = w, h + pygame.surfarray.blit_array(surf, farr) + for x in range(w): + for y in range(h): + self.assertEqual( + surf.get_at_mapped((x, y)), int(rint(farr[x, y])) + ) + + # this test should be removed soon, when the function is deleted + def test_get_arraytype(self): + array_type = pygame.surfarray.get_arraytype() + + self.assertEqual(array_type, "numpy", "unknown array type %s" % array_type) + + # this test should be removed soon, when the function is deleted + def test_get_arraytypes(self): + + arraytypes = pygame.surfarray.get_arraytypes() + self.assertIn("numpy", arraytypes) + + for atype in arraytypes: + self.assertEqual(atype, "numpy", "unknown array type %s" % atype) + + def test_make_surface(self): + + # How does one properly test this with 2d arrays. It makes no sense + # since the pixel format is not entirely dependent on element size. + # Just make sure the surface pixel size is at least as large as the + # array element size I guess. + # + for bitsize, dtype in [(8, uint8), (16, uint16), (24, uint32)]: + ## Even this simple assertion fails for 2d arrays. Where's the problem? + ## surf = pygame.surfarray.make_surface(self._make_array2d(dtype)) + ## self.assertGreaterEqual(surf.get_bitsize(), bitsize, + ## "not %i >= %i)" % (surf.get_bitsize(), bitsize)) + ## + surf = pygame.surfarray.make_surface(self._make_src_array3d(dtype)) + self._assert_surface(surf) + + # Issue #81: round from float to int + try: + rint + except NameError: + pass + else: + w = 9 + h = 11 + length = w * h + for dtype in [float32, float64]: + farr = arange(0, length, dtype=dtype) + farr.shape = w, h + surf = pygame.surfarray.make_surface(farr) + for x in range(w): + for y in range(h): + self.assertEqual( + surf.get_at_mapped((x, y)), int(rint(farr[x, y])) + ) + + def test_map_array(self): + + arr3d = self._make_src_array3d(uint8) + targets = [ + self._make_surface(8), + self._make_surface(16), + self._make_surface(16, srcalpha=True), + self._make_surface(24), + self._make_surface(32), + self._make_surface(32, srcalpha=True), + ] + palette = self.test_palette + + for surf in targets: + arr2d = pygame.surfarray.map_array(surf, arr3d) + for posn, i in self.test_points: + self.assertEqual( + arr2d[posn], + surf.map_rgb(palette[i]), + "%i != %i, bitsize: %i, flags: %i" + % ( + arr2d[posn], + surf.map_rgb(palette[i]), + surf.get_bitsize(), + surf.get_flags(), + ), + ) + + # Exception checks + self.assertRaises( + ValueError, + pygame.surfarray.map_array, + self._make_surface(32), + self._make_array2d(uint8), + ) + + def test_pixels2d(self): + + sources = [ + self._make_surface(8), + self._make_surface(16, srcalpha=True), + self._make_surface(32, srcalpha=True), + ] + + for surf in sources: + self.assertFalse(surf.get_locked()) + arr = pygame.surfarray.pixels2d(surf) + self.assertTrue(surf.get_locked()) + self._fill_array2d(arr, surf) + surf.unlock() + self.assertTrue(surf.get_locked()) + del arr + self.assertFalse(surf.get_locked()) + self.assertEqual(surf.get_locks(), ()) + self._assert_surface(surf) + + # Error checks + self.assertRaises(ValueError, pygame.surfarray.pixels2d, self._make_surface(24)) + + def test_pixels3d(self): + + sources = [self._make_surface(24), self._make_surface(32)] + + for surf in sources: + self.assertFalse(surf.get_locked()) + arr = pygame.surfarray.pixels3d(surf) + self.assertTrue(surf.get_locked()) + self._fill_array3d(arr) + surf.unlock() + self.assertTrue(surf.get_locked()) + del arr + self.assertFalse(surf.get_locked()) + self.assertEqual(surf.get_locks(), ()) + self._assert_surface(surf) + + # Alpha check + color = (1, 2, 3, 0) + surf = self._make_surface(32, srcalpha=True) + arr = pygame.surfarray.pixels3d(surf) + arr[0, 0] = color[:3] + self.assertEqual(surf.get_at((0, 0)), color) + + # Error checks + def do_pixels3d(surf): + pygame.surfarray.pixels3d(surf) + + self.assertRaises(ValueError, do_pixels3d, self._make_surface(8)) + self.assertRaises(ValueError, do_pixels3d, self._make_surface(16)) + + def test_pixels_alpha(self): + + palette = [ + (0, 0, 0, 0), + (127, 127, 127, 0), + (127, 127, 127, 85), + (127, 127, 127, 170), + (127, 127, 127, 255), + ] + alphas = [0, 45, 86, 99, 180] + + surf = self._make_src_surface(32, srcalpha=True, palette=palette) + + self.assertFalse(surf.get_locked()) + arr = pygame.surfarray.pixels_alpha(surf) + self.assertTrue(surf.get_locked()) + surf.unlock() + self.assertTrue(surf.get_locked()) + + for (x, y), i in self.test_points: + self.assertEqual(arr[x, y], palette[i][3]) + + for (x, y), i in self.test_points: + alpha = alphas[i] + arr[x, y] = alpha + color = (127, 127, 127, alpha) + self.assertEqual(surf.get_at((x, y)), color, "posn: (%i, %i)" % (x, y)) + + del arr + self.assertFalse(surf.get_locked()) + self.assertEqual(surf.get_locks(), ()) + + # Check exceptions. + def do_pixels_alpha(surf): + pygame.surfarray.pixels_alpha(surf) + + targets = [(8, False), (16, False), (16, True), (24, False), (32, False)] + + for bitsize, srcalpha in targets: + self.assertRaises( + ValueError, do_pixels_alpha, self._make_surface(bitsize, srcalpha) + ) + + def test_pixels_red(self): + self._test_pixels_rgb("red", 0) + + def test_pixels_green(self): + self._test_pixels_rgb("green", 1) + + def test_pixels_blue(self): + self._test_pixels_rgb("blue", 2) + + def _test_pixels_rgb(self, operation, mask_posn): + method_name = "pixels_" + operation + + pixels_rgb = getattr(pygame.surfarray, method_name) + palette = [ + (0, 0, 0, 255), + (5, 13, 23, 255), + (29, 31, 37, 255), + (131, 157, 167, 255), + (179, 191, 251, 255), + ] + plane = [c[mask_posn] for c in palette] + + surf24 = self._make_src_surface(24, srcalpha=False, palette=palette) + surf32 = self._make_src_surface(32, srcalpha=False, palette=palette) + surf32a = self._make_src_surface(32, srcalpha=True, palette=palette) + + for surf in [surf24, surf32, surf32a]: + self.assertFalse(surf.get_locked()) + arr = pixels_rgb(surf) + self.assertTrue(surf.get_locked()) + surf.unlock() + self.assertTrue(surf.get_locked()) + + for (x, y), i in self.test_points: + self.assertEqual(arr[x, y], plane[i]) + + del arr + self.assertFalse(surf.get_locked()) + self.assertEqual(surf.get_locks(), ()) + + # Check exceptions. + targets = [(8, False), (16, False), (16, True)] + + for bitsize, srcalpha in targets: + self.assertRaises( + ValueError, pixels_rgb, self._make_surface(bitsize, srcalpha) + ) + + def test_use_arraytype(self): + def do_use_arraytype(atype): + pygame.surfarray.use_arraytype(atype) + + pygame.surfarray.use_arraytype("numpy") + self.assertEqual(pygame.surfarray.get_arraytype(), "numpy") + self.assertRaises(ValueError, do_use_arraytype, "not an option") + + def test_surf_lock(self): + sf = pygame.Surface((5, 5), 0, 32) + for atype in pygame.surfarray.get_arraytypes(): + pygame.surfarray.use_arraytype(atype) + + ar = pygame.surfarray.pixels2d(sf) + self.assertTrue(sf.get_locked()) + + sf.unlock() + self.assertTrue(sf.get_locked()) + + del ar + self.assertFalse(sf.get_locked()) + self.assertEqual(sf.get_locks(), ()) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/surflock_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/surflock_test.py new file mode 100644 index 0000000..19f354b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/surflock_test.py @@ -0,0 +1,144 @@ +import unittest +import sys +import platform + +import pygame + +IS_PYPY = "PyPy" == platform.python_implementation() + + +@unittest.skipIf(IS_PYPY, "pypy skip known failure") # TODO +class SurfaceLockTest(unittest.TestCase): + def test_lock(self): + sf = pygame.Surface((5, 5)) + + sf.lock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (sf,)) + + sf.lock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (sf, sf)) + + sf.unlock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (sf,)) + + sf.unlock() + self.assertEqual(sf.get_locked(), False) + self.assertEqual(sf.get_locks(), ()) + + def test_subsurface_lock(self): + sf = pygame.Surface((5, 5)) + subsf = sf.subsurface((1, 1, 2, 2)) + sf2 = pygame.Surface((5, 5)) + + # Simple blits, nothing should happen here. + sf2.blit(subsf, (0, 0)) + sf2.blit(sf, (0, 0)) + + # Test blitting on self: + self.assertRaises(pygame.error, sf.blit, subsf, (0, 0)) + # self.assertRaises(pygame.error, subsf.blit, sf, (0, 0)) + # ^ Fails although it should not in my opinion. If I cannot + # blit the subsurface to the surface, it should not be allowed + # the other way around as well. + + # Test additional locks. + sf.lock() + sf2.blit(subsf, (0, 0)) + self.assertRaises(pygame.error, sf2.blit, sf, (0, 0)) + + subsf.lock() + self.assertRaises(pygame.error, sf2.blit, subsf, (0, 0)) + self.assertRaises(pygame.error, sf2.blit, sf, (0, 0)) + + # sf and subsf are now explicitly locked. Unlock sf, so we can + # (assume) to blit it. + # It will fail though as the subsurface still has a lock around, + # which is okay and correct behaviour. + sf.unlock() + self.assertRaises(pygame.error, sf2.blit, subsf, (0, 0)) + self.assertRaises(pygame.error, sf2.blit, sf, (0, 0)) + + # Run a second unlock on the surface. This should ideally have + # no effect as the subsurface is the locking reason! + sf.unlock() + self.assertRaises(pygame.error, sf2.blit, sf, (0, 0)) + self.assertRaises(pygame.error, sf2.blit, subsf, (0, 0)) + subsf.unlock() + + sf.lock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (sf,)) + self.assertEqual(subsf.get_locked(), False) + self.assertEqual(subsf.get_locks(), ()) + + subsf.lock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (sf, subsf)) + self.assertEqual(subsf.get_locked(), True) + self.assertEqual(subsf.get_locks(), (subsf,)) + + sf.unlock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (subsf,)) + self.assertEqual(subsf.get_locked(), True) + self.assertEqual(subsf.get_locks(), (subsf,)) + + subsf.unlock() + self.assertEqual(sf.get_locked(), False) + self.assertEqual(sf.get_locks(), ()) + self.assertEqual(subsf.get_locked(), False) + self.assertEqual(subsf.get_locks(), ()) + + subsf.lock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (subsf,)) + self.assertEqual(subsf.get_locked(), True) + self.assertEqual(subsf.get_locks(), (subsf,)) + + subsf.lock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (subsf, subsf)) + self.assertEqual(subsf.get_locked(), True) + self.assertEqual(subsf.get_locks(), (subsf, subsf)) + + def test_pxarray_ref(self): + sf = pygame.Surface((5, 5)) + ar = pygame.PixelArray(sf) + ar2 = pygame.PixelArray(sf) + + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (ar, ar2)) + + del ar + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (ar2,)) + + ar = ar2[:] + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (ar2,)) + + del ar + self.assertEqual(sf.get_locked(), True) + self.assertEqual(len(sf.get_locks()), 1) + + def test_buffer(self): + sf = pygame.Surface((5, 5)) + buf = sf.get_buffer() + + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (buf,)) + + sf.unlock() + self.assertEqual(sf.get_locked(), True) + self.assertEqual(sf.get_locks(), (buf,)) + + del buf + self.assertEqual(sf.get_locked(), False) + self.assertEqual(sf.get_locks(), ()) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/sysfont_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/sysfont_test.py new file mode 100644 index 0000000..0ae380a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/sysfont_test.py @@ -0,0 +1,51 @@ +import unittest +import platform + + +class SysfontModuleTest(unittest.TestCase): + def test_create_aliases(self): + import pygame.sysfont + + pygame.sysfont.initsysfonts() + pygame.sysfont.create_aliases() + self.assertTrue(len(pygame.sysfont.Sysalias) > 0) + + def test_initsysfonts(self): + import pygame.sysfont + + pygame.sysfont.initsysfonts() + self.assertTrue(len(pygame.sysfont.get_fonts()) > 0) + + @unittest.skipIf("Darwin" not in platform.platform(), "Not mac we skip.") + def test_initsysfonts_darwin(self): + import pygame.sysfont + + self.assertTrue(len(pygame.sysfont.get_fonts()) > 10) + + def test_sysfont(self): + import pygame.font + + pygame.font.init() + arial = pygame.font.SysFont("Arial", 40) + self.assertTrue(isinstance(arial, pygame.font.Font)) + + @unittest.skipIf( + ("Darwin" in platform.platform() or "Windows" in platform.platform()), + "Not unix we skip.", + ) + def test_initsysfonts_unix(self): + import pygame.sysfont + + self.assertTrue(len(pygame.sysfont.get_fonts()) > 0) + + @unittest.skipIf("Windows" not in platform.platform(), "Not windows we skip.") + def test_initsysfonts_win32(self): + import pygame.sysfont + + self.assertTrue(len(pygame.sysfont.get_fonts()) > 10) + + +############################################################################### + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_test_.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_test_.py new file mode 100644 index 0000000..0880e7e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_test_.py @@ -0,0 +1,2 @@ +while True: + pass diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__init__.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__init__.py new file mode 100644 index 0000000..f8013ca --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__init__.py @@ -0,0 +1,227 @@ +import os +import pygame +import sys +import tempfile +import time + +is_pygame_pkg = __name__.startswith("pygame.tests.") + +############################################################################### + + +def tostring(row): + """Convert row of bytes to string. Expects `row` to be an + ``array``. + """ + return row.tobytes() + + +def geterror(): + return sys.exc_info()[1] + + +class AssertRaisesRegexMixin(object): + """Provides a way to prevent DeprecationWarnings in python >= 3.2. + + For this mixin to override correctly it needs to be before the + unittest.TestCase in the multiple inheritance hierarchy. + e.g. class TestClass(AssertRaisesRegexMixin, unittest.TestCase) + + This class/mixin and its usage can be removed when pygame no longer + supports python < 3.2. + """ + + def assertRaisesRegex(self, *args, **kwargs): + try: + return super(AssertRaisesRegexMixin, self).assertRaisesRegex( + *args, **kwargs + ) + except AttributeError: + try: + return super(AssertRaisesRegexMixin, self).assertRaisesRegexp( + *args, **kwargs + ) + except AttributeError: + self.skipTest("No assertRaisesRegex/assertRaisesRegexp method") + + +############################################################################### + +this_dir = os.path.dirname(os.path.abspath(__file__)) +trunk_dir = os.path.split(os.path.split(this_dir)[0])[0] +if is_pygame_pkg: + test_module = "tests" +else: + test_module = "test" + + +def trunk_relative_path(relative): + return os.path.normpath(os.path.join(trunk_dir, relative)) + + +def fixture_path(path): + return trunk_relative_path(os.path.join(test_module, "fixtures", path)) + + +def example_path(path): + return trunk_relative_path(os.path.join("examples", path)) + + +sys.path.insert(0, trunk_relative_path(".")) + + +################################## TEMP FILES ################################# + + +def get_tmp_dir(): + return tempfile.mkdtemp() + + +############################################################################### + + +def question(q): + return input("\n%s (y/n): " % q.rstrip(" ")).lower().strip() == "y" + + +def prompt(p): + return input("\n%s (press enter to continue): " % p.rstrip(" ")) + + +#################################### HELPERS ################################## + + +def rgba_between(value, minimum=0, maximum=255): + if value < minimum: + return minimum + elif value > maximum: + return maximum + else: + return value + + +def combinations(seqs): + """ + + Recipe 496807 from ActiveState Python CookBook + + Non recursive technique for getting all possible combinations of a sequence + of sequences. + + """ + + r = [[]] + for x in seqs: + r = [i + [y] for y in x for i in r] + return r + + +def gradient(width, height): + """ + + Yields a pt and corresponding RGBA tuple, for every (width, height) combo. + Useful for generating gradients. + + Actual gradient may be changed, no tests rely on specific values. + + Used in transform.rotate lossless tests to generate a fixture. + + """ + + for l in range(width): + for t in range(height): + yield (l, t), tuple(map(rgba_between, (l, t, l, l + t))) + + +def rect_area_pts(rect): + for l in range(rect.left, rect.right): + for t in range(rect.top, rect.bottom): + yield l, t + + +def rect_perimeter_pts(rect): + """ + + Returns pts ((L, T) tuples) encompassing the perimeter of a rect. + + The order is clockwise: + + topleft to topright + topright to bottomright + bottomright to bottomleft + bottomleft to topleft + + Duplicate pts are not returned + + """ + clock_wise_from_top_left = ( + [(l, rect.top) for l in range(rect.left, rect.right)], + [(rect.right - 1, t) for t in range(rect.top + 1, rect.bottom)], + [(l, rect.bottom - 1) for l in range(rect.right - 2, rect.left - 1, -1)], + [(rect.left, t) for t in range(rect.bottom - 2, rect.top, -1)], + ) + + for line in clock_wise_from_top_left: + for pt in line: + yield pt + + +def rect_outer_bounds(rect): + """ + + Returns topleft outerbound if possible and then the other pts, that are + "exclusive" bounds of the rect + + ?------O + |RECT| ?|0)uterbound + |----| + O O + + """ + return ([(rect.left - 1, rect.top)] if rect.left else []) + [ + rect.topright, + rect.bottomleft, + rect.bottomright, + ] + + +def import_submodule(module): + m = __import__(module) + for n in module.split(".")[1:]: + m = getattr(m, n) + return m + + +class SurfaceSubclass(pygame.Surface): + """A subclassed Surface to test inheritance.""" + + def __init__(self, *args, **kwargs): + super(SurfaceSubclass, self).__init__(*args, **kwargs) + self.test_attribute = True + + +def test(): + """ + + Lightweight test for helpers + + """ + + r = pygame.Rect(0, 0, 10, 10) + assert rect_outer_bounds(r) == [(10, 0), (0, 10), (10, 10)] # tr # bl # br + + assert len(list(rect_area_pts(r))) == 100 + + r = pygame.Rect(0, 0, 3, 3) + assert list(rect_perimeter_pts(r)) == [ + (0, 0), + (1, 0), + (2, 0), # tl -> tr + (2, 1), + (2, 2), # tr -> br + (1, 2), + (0, 2), # br -> bl + (0, 1), # bl -> tl + ] + + print("Tests: OK") diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..332b736 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/arrinter.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/arrinter.cpython-38.pyc new file mode 100644 index 0000000..bb22235 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/arrinter.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/async_sub.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/async_sub.cpython-38.pyc new file mode 100644 index 0000000..b3508d6 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/async_sub.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/buftools.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/buftools.cpython-38.pyc new file mode 100644 index 0000000..b321199 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/buftools.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/endian.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/endian.cpython-38.pyc new file mode 100644 index 0000000..041833f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/endian.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/png.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/png.cpython-38.pyc new file mode 100644 index 0000000..ffe37d3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/png.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/run_tests.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/run_tests.cpython-38.pyc new file mode 100644 index 0000000..a27e85e Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/run_tests.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/test_machinery.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/test_machinery.cpython-38.pyc new file mode 100644 index 0000000..4d68bad Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/test_machinery.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/test_runner.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/test_runner.cpython-38.pyc new file mode 100644 index 0000000..f9bcf59 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/__pycache__/test_runner.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/arrinter.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/arrinter.py new file mode 100644 index 0000000..3883b45 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/arrinter.py @@ -0,0 +1,441 @@ +import sys +import ctypes +from ctypes import * +import unittest + +__all__ = [ + "PAI_CONTIGUOUS", + "PAI_FORTRAN", + "PAI_ALIGNED", + "PAI_NOTSWAPPED", + "PAI_WRITEABLE", + "PAI_ARR_HAS_DESCR", + "ArrayInterface", +] + +try: + c_ssize_t # Undefined in early Python versions +except NameError: + if sizeof(c_uint) == sizeof(c_void_p): + c_size_t = c_uint + c_ssize_t = c_int + elif sizeof(c_ulong) == sizeof(c_void_p): + c_size_t = c_ulong + c_ssize_t = c_long + elif sizeof(c_ulonglong) == sizeof(c_void_p): + c_size_t = c_ulonglong + c_ssize_t = c_longlong + + +SIZEOF_VOID_P = sizeof(c_void_p) +if SIZEOF_VOID_P <= sizeof(c_int): + Py_intptr_t = c_int +elif SIZEOF_VOID_P <= sizeof(c_long): + Py_intptr_t = c_long +elif "c_longlong" in globals() and SIZEOF_VOID_P <= sizeof(c_longlong): + Py_intptr_t = c_longlong +else: + raise RuntimeError("Unrecognized pointer size %i" % (pointer_size,)) + + +class PyArrayInterface(Structure): + _fields_ = [ + ("two", c_int), + ("nd", c_int), + ("typekind", c_char), + ("itemsize", c_int), + ("flags", c_int), + ("shape", POINTER(Py_intptr_t)), + ("strides", POINTER(Py_intptr_t)), + ("data", c_void_p), + ("descr", py_object), + ] + + +PAI_Ptr = POINTER(PyArrayInterface) + +try: + PyCObject_AsVoidPtr = pythonapi.PyCObject_AsVoidPtr +except AttributeError: + + def PyCObject_AsVoidPtr(o): + raise TypeError("Not available") + +else: + PyCObject_AsVoidPtr.restype = c_void_p + PyCObject_AsVoidPtr.argtypes = [py_object] + PyCObject_GetDesc = pythonapi.PyCObject_GetDesc + PyCObject_GetDesc.restype = c_void_p + PyCObject_GetDesc.argtypes = [py_object] + +try: + PyCapsule_IsValid = pythonapi.PyCapsule_IsValid +except AttributeError: + + def PyCapsule_IsValid(capsule, name): + return 0 + +else: + PyCapsule_IsValid.restype = c_int + PyCapsule_IsValid.argtypes = [py_object, c_char_p] + PyCapsule_GetPointer = pythonapi.PyCapsule_GetPointer + PyCapsule_GetPointer.restype = c_void_p + PyCapsule_GetPointer.argtypes = [py_object, c_char_p] + PyCapsule_GetContext = pythonapi.PyCapsule_GetContext + PyCapsule_GetContext.restype = c_void_p + PyCapsule_GetContext.argtypes = [py_object] + +PyCapsule_Destructor = CFUNCTYPE(None, py_object) +PyCapsule_New = pythonapi.PyCapsule_New +PyCapsule_New.restype = py_object +PyCapsule_New.argtypes = [c_void_p, c_char_p, POINTER(PyCapsule_Destructor)] + + +def capsule_new(p): + return PyCapsule_New(addressof(p), None, None) + + +PAI_CONTIGUOUS = 0x01 +PAI_FORTRAN = 0x02 +PAI_ALIGNED = 0x100 +PAI_NOTSWAPPED = 0x200 +PAI_WRITEABLE = 0x400 +PAI_ARR_HAS_DESCR = 0x800 + + +class ArrayInterface(object): + def __init__(self, arr): + try: + self._cobj = arr.__array_struct__ + except AttributeError: + raise TypeError("The array object lacks an array structure") + if not self._cobj: + raise TypeError("The array object has a NULL array structure value") + try: + vp = PyCObject_AsVoidPtr(self._cobj) + except TypeError: + if PyCapsule_IsValid(self._cobj, None): + vp = PyCapsule_GetPointer(self._cobj, None) + else: + raise TypeError("The array object has an invalid array structure") + self.desc = PyCapsule_GetContext(self._cobj) + else: + self.desc = PyCObject_GetDesc(self._cobj) + self._inter = cast(vp, PAI_Ptr)[0] + + def __getattr__(self, name): + if name == "typekind": + return self._inter.typekind.decode("latin-1") + return getattr(self._inter, name) + + def __str__(self): + if isinstance(self.desc, tuple): + ver = self.desc[0] + else: + ver = "N/A" + return ( + "nd: %i\n" + "typekind: %s\n" + "itemsize: %i\n" + "flags: %s\n" + "shape: %s\n" + "strides: %s\n" + "ver: %s\n" + % ( + self.nd, + self.typekind, + self.itemsize, + format_flags(self.flags), + format_shape(self.nd, self.shape), + format_strides(self.nd, self.strides), + ver, + ) + ) + + +def format_flags(flags): + names = [] + for flag, name in [ + (PAI_CONTIGUOUS, "CONTIGUOUS"), + (PAI_FORTRAN, "FORTRAN"), + (PAI_ALIGNED, "ALIGNED"), + (PAI_NOTSWAPPED, "NOTSWAPPED"), + (PAI_WRITEABLE, "WRITEABLE"), + (PAI_ARR_HAS_DESCR, "ARR_HAS_DESCR"), + ]: + if flag & flags: + names.append(name) + return ", ".join(names) + + +def format_shape(nd, shape): + return ", ".join([str(shape[i]) for i in range(nd)]) + + +def format_strides(nd, strides): + return ", ".join([str(strides[i]) for i in range(nd)]) + + +class Exporter(object): + def __init__( + self, shape, typekind=None, itemsize=None, strides=None, descr=None, flags=None + ): + if typekind is None: + typekind = "u" + if itemsize is None: + itemsize = 1 + if flags is None: + flags = PAI_WRITEABLE | PAI_ALIGNED | PAI_NOTSWAPPED + if descr is not None: + flags |= PAI_ARR_HAS_DESCR + if len(typekind) != 1: + raise ValueError("Argument 'typekind' must be length 1 string") + nd = len(shape) + self.typekind = typekind + self.itemsize = itemsize + self.nd = nd + self.shape = tuple(shape) + self._shape = (c_ssize_t * self.nd)(*self.shape) + if strides is None: + self._strides = (c_ssize_t * self.nd)() + self._strides[self.nd - 1] = self.itemsize + for i in range(self.nd - 1, 0, -1): + self._strides[i - 1] = self.shape[i] * self._strides[i] + strides = tuple(self._strides) + self.strides = strides + elif len(strides) == nd: + self.strides = tuple(strides) + self._strides = (c_ssize_t * self.nd)(*self.strides) + else: + raise ValueError("Mismatch in length of strides and shape") + self.descr = descr + if self.is_contiguous("C"): + flags |= PAI_CONTIGUOUS + if self.is_contiguous("F"): + flags |= PAI_FORTRAN + self.flags = flags + sz = max(shape[i] * strides[i] for i in range(nd)) + self._data = (c_ubyte * sz)() + self.data = addressof(self._data) + self._inter = PyArrayInterface( + 2, + nd, + typekind.encode("latin_1"), + itemsize, + flags, + self._shape, + self._strides, + self.data, + descr, + ) + self.len = itemsize + for i in range(nd): + self.len *= self.shape[i] + + __array_struct__ = property(lambda self: capsule_new(self._inter)) + + def is_contiguous(self, fortran): + if fortran in "CA": + if self.strides[-1] == self.itemsize: + for i in range(self.nd - 1, 0, -1): + if self.strides[i - 1] != self.shape[i] * self.strides[i]: + break + else: + return True + if fortran in "FA": + if self.strides[0] == self.itemsize: + for i in range(0, self.nd - 1): + if self.strides[i + 1] != self.shape[i] * self.strides[i]: + break + else: + return True + return False + + +class Array(Exporter): + _ctypes = { + ("u", 1): c_uint8, + ("u", 2): c_uint16, + ("u", 4): c_uint32, + ("u", 8): c_uint64, + ("i", 1): c_int8, + ("i", 2): c_int16, + ("i", 4): c_int32, + ("i", 8): c_int64, + } + + def __init__(self, *args, **kwds): + super(Array, self).__init__(*args, **kwds) + try: + if self.flags & PAI_NOTSWAPPED: + ct = self._ctypes[self.typekind, self.itemsize] + elif c_int.__ctype_le__ is c_int: + ct = self._ctypes[self.typekind, self.itemsize].__ctype_be__ + else: + ct = self._ctypes[self.typekind, self.itemsize].__ctype_le__ + except KeyError: + ct = c_uint8 * self.itemsize + self._ctype = ct + self._ctype_p = POINTER(ct) + + def __getitem__(self, key): + return cast(self._addr_at(key), self._ctype_p)[0] + + def __setitem__(self, key, value): + cast(self._addr_at(key), self._ctype_p)[0] = value + + def _addr_at(self, key): + if not isinstance(key, tuple): + key = (key,) + if len(key) != self.nd: + raise ValueError("wrong number of indexes") + for i in range(self.nd): + if not (0 <= key[i] < self.shape[i]): + raise IndexError("index {} out of range".format(i)) + return self.data + sum(i * s for i, s in zip(key, self.strides)) + + +class ExporterTest(unittest.TestCase): + def test_strides(self): + self.check_args(0, (10,), "u", (2,), 20, 20, 2) + self.check_args(0, (5, 3), "u", (6, 2), 30, 30, 2) + self.check_args(0, (7, 3, 5), "u", (30, 10, 2), 210, 210, 2) + self.check_args(0, (13, 5, 11, 3), "u", (330, 66, 6, 2), 4290, 4290, 2) + self.check_args(3, (7, 3, 5), "i", (2, 14, 42), 210, 210, 2) + self.check_args(3, (7, 3, 5), "x", (2, 16, 48), 210, 240, 2) + self.check_args(3, (13, 5, 11, 3), "%", (440, 88, 8, 2), 4290, 5720, 2) + self.check_args(3, (7, 5), "-", (15, 3), 105, 105, 3) + self.check_args(3, (7, 5), "*", (3, 21), 105, 105, 3) + self.check_args(3, (7, 5), " ", (3, 24), 105, 120, 3) + + def test_is_contiguous(self): + a = Exporter((10,), itemsize=2) + self.assertTrue(a.is_contiguous("C")) + self.assertTrue(a.is_contiguous("F")) + self.assertTrue(a.is_contiguous("A")) + a = Exporter((10, 4), itemsize=2) + self.assertTrue(a.is_contiguous("C")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("F")) + a = Exporter((13, 5, 11, 3), itemsize=2, strides=(330, 66, 6, 2)) + self.assertTrue(a.is_contiguous("C")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("F")) + a = Exporter((10, 4), itemsize=2, strides=(2, 20)) + self.assertTrue(a.is_contiguous("F")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("C")) + a = Exporter((13, 5, 11, 3), itemsize=2, strides=(2, 26, 130, 1430)) + self.assertTrue(a.is_contiguous("F")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("C")) + a = Exporter((2, 11, 6, 4), itemsize=2, strides=(576, 48, 8, 2)) + self.assertFalse(a.is_contiguous("A")) + a = Exporter((2, 11, 6, 4), itemsize=2, strides=(2, 4, 48, 288)) + self.assertFalse(a.is_contiguous("A")) + a = Exporter((3, 2, 2), itemsize=2, strides=(16, 8, 4)) + self.assertFalse(a.is_contiguous("A")) + a = Exporter((3, 2, 2), itemsize=2, strides=(4, 12, 24)) + self.assertFalse(a.is_contiguous("A")) + + def check_args( + self, call_flags, shape, typekind, strides, length, bufsize, itemsize, offset=0 + ): + if call_flags & 1: + typekind_arg = typekind + else: + typekind_arg = None + if call_flags & 2: + strides_arg = strides + else: + strides_arg = None + a = Exporter(shape, itemsize=itemsize, strides=strides_arg) + self.assertEqual(sizeof(a._data), bufsize) + self.assertEqual(a.data, ctypes.addressof(a._data) + offset) + m = ArrayInterface(a) + self.assertEqual(m.data, a.data) + self.assertEqual(m.itemsize, itemsize) + self.assertEqual(tuple(m.shape[0 : m.nd]), shape) + self.assertEqual(tuple(m.strides[0 : m.nd]), strides) + + +class ArrayTest(unittest.TestCase): + def __init__(self, *args, **kwds): + unittest.TestCase.__init__(self, *args, **kwds) + self.a = Array((20, 15), "i", 4) + + def setUp(self): + # Every test starts with a zeroed array. + memset(self.a.data, 0, sizeof(self.a._data)) + + def test__addr_at(self): + a = self.a + self.assertEqual(a._addr_at((0, 0)), a.data) + self.assertEqual(a._addr_at((0, 1)), a.data + 4) + self.assertEqual(a._addr_at((1, 0)), a.data + 60) + self.assertEqual(a._addr_at((1, 1)), a.data + 64) + + def test_indices(self): + a = self.a + self.assertEqual(a[0, 0], 0) + self.assertEqual(a[19, 0], 0) + self.assertEqual(a[0, 14], 0) + self.assertEqual(a[19, 14], 0) + self.assertEqual(a[5, 8], 0) + a[0, 0] = 12 + a[5, 8] = 99 + self.assertEqual(a[0, 0], 12) + self.assertEqual(a[5, 8], 99) + self.assertRaises(IndexError, a.__getitem__, (-1, 0)) + self.assertRaises(IndexError, a.__getitem__, (0, -1)) + self.assertRaises(IndexError, a.__getitem__, (20, 0)) + self.assertRaises(IndexError, a.__getitem__, (0, 15)) + self.assertRaises(ValueError, a.__getitem__, 0) + self.assertRaises(ValueError, a.__getitem__, (0, 0, 0)) + a = Array((3,), "i", 4) + a[1] = 333 + self.assertEqual(a[1], 333) + + def test_typekind(self): + a = Array((1,), "i", 4) + self.assertTrue(a._ctype is c_int32) + self.assertTrue(a._ctype_p is POINTER(c_int32)) + a = Array((1,), "u", 4) + self.assertTrue(a._ctype is c_uint32) + self.assertTrue(a._ctype_p is POINTER(c_uint32)) + a = Array((1,), "f", 4) # float types unsupported: size system dependent + ct = a._ctype + self.assertTrue(issubclass(ct, ctypes.Array)) + self.assertEqual(sizeof(ct), 4) + + def test_itemsize(self): + for size in [1, 2, 4, 8]: + a = Array((1,), "i", size) + ct = a._ctype + self.assertTrue(issubclass(ct, ctypes._SimpleCData)) + self.assertEqual(sizeof(ct), size) + + def test_oddball_itemsize(self): + for size in [3, 5, 6, 7, 9]: + a = Array((1,), "i", size) + ct = a._ctype + self.assertTrue(issubclass(ct, ctypes.Array)) + self.assertEqual(sizeof(ct), size) + + def test_byteswapped(self): + a = Array((1,), "u", 4, flags=(PAI_ALIGNED | PAI_WRITEABLE)) + ct = a._ctype + self.assertTrue(ct is not c_uint32) + if sys.byteorder == "little": + self.assertTrue(ct is c_uint32.__ctype_be__) + else: + self.assertTrue(ct is c_uint32.__ctype_le__) + i = 0xA0B0C0D + n = c_uint32(i) + a[0] = i + self.assertEqual(a[0], i) + self.assertEqual(a._data[0:4], cast(addressof(n), POINTER(c_uint8))[3:-1:-1]) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/async_sub.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/async_sub.py new file mode 100644 index 0000000..4adc760 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/async_sub.py @@ -0,0 +1,301 @@ +################################################################################ +""" + +Modification of http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554 + +""" + +#################################### IMPORTS ################################### + +import os +import platform +import subprocess +import errno +import time +import sys +import unittest +import tempfile + + +def geterror(): + return sys.exc_info()[1] + + +null_byte = "\x00".encode("ascii") + +if platform.system() == "Windows": + + def encode(s): + return s.encode("ascii") + + def decode(b): + return b.decode("ascii") + + try: + import ctypes + from ctypes.wintypes import DWORD + + kernel32 = ctypes.windll.kernel32 + TerminateProcess = ctypes.windll.kernel32.TerminateProcess + + def WriteFile(handle, data, ol=None): + c_written = DWORD() + success = ctypes.windll.kernel32.WriteFile( + handle, + ctypes.create_string_buffer(encode(data)), + len(data), + ctypes.byref(c_written), + ol, + ) + return ctypes.windll.kernel32.GetLastError(), c_written.value + + def ReadFile(handle, desired_bytes, ol=None): + c_read = DWORD() + buffer = ctypes.create_string_buffer(desired_bytes + 1) + success = ctypes.windll.kernel32.ReadFile( + handle, buffer, desired_bytes, ctypes.byref(c_read), ol + ) + buffer[c_read.value] = null_byte + return ctypes.windll.kernel32.GetLastError(), decode(buffer.value) + + def PeekNamedPipe(handle, desired_bytes): + c_avail = DWORD() + c_message = DWORD() + if desired_bytes > 0: + c_read = DWORD() + buffer = ctypes.create_string_buffer(desired_bytes + 1) + success = ctypes.windll.kernel32.PeekNamedPipe( + handle, + buffer, + desired_bytes, + ctypes.byref(c_read), + ctypes.byref(c_avail), + ctypes.byref(c_message), + ) + buffer[c_read.value] = null_byte + return decode(buffer.value), c_avail.value, c_message.value + else: + success = ctypes.windll.kernel32.PeekNamedPipe( + handle, + None, + desired_bytes, + None, + ctypes.byref(c_avail), + ctypes.byref(c_message), + ) + return "", c_avail.value, c_message.value + + except ImportError: + from win32file import ReadFile, WriteFile + from win32pipe import PeekNamedPipe + from win32api import TerminateProcess + import msvcrt + +else: + from signal import SIGINT, SIGTERM, SIGKILL + import select + import fcntl + +################################### CONSTANTS ################################## + +PIPE = subprocess.PIPE + +################################################################################ + + +class Popen(subprocess.Popen): + def recv(self, maxsize=None): + return self._recv("stdout", maxsize) + + def recv_err(self, maxsize=None): + return self._recv("stderr", maxsize) + + def send_recv(self, input="", maxsize=None): + return self.send(input), self.recv(maxsize), self.recv_err(maxsize) + + def read_async(self, wait=0.1, e=1, tr=5, stderr=0): + if tr < 1: + tr = 1 + x = time.time() + wait + y = [] + r = "" + pr = self.recv + if stderr: + pr = self.recv_err + while time.time() < x or r: + r = pr() + if r is None: + if e: + raise Exception("Other end disconnected!") + else: + break + elif r: + y.append(r) + else: + time.sleep(max((x - time.time()) / tr, 0)) + return "".join(y) + + def send_all(self, data): + while len(data): + sent = self.send(data) + if sent is None: + raise Exception("Other end disconnected!") + data = buffer(data, sent) + + def get_conn_maxsize(self, which, maxsize): + if maxsize is None: + maxsize = 1024 + elif maxsize < 1: + maxsize = 1 + return getattr(self, which), maxsize + + def _close(self, which): + getattr(self, which).close() + setattr(self, which, None) + + if platform.system() == "Windows": + + def kill(self): + # Recipes + # http://me.in-berlin.de/doc/python/faq/windows.html#how-do-i-emulate-os-kill-in-windows + # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/347462 + + """kill function for Win32""" + TerminateProcess(int(self._handle), 0) # returns None + + def send(self, input): + if not self.stdin: + return None + + try: + x = msvcrt.get_osfhandle(self.stdin.fileno()) + (errCode, written) = WriteFile(x, input) + except ValueError: + return self._close("stdin") + except (subprocess.pywintypes.error, Exception): + if geterror()[0] in (109, errno.ESHUTDOWN): + return self._close("stdin") + raise + + return written + + def _recv(self, which, maxsize): + conn, maxsize = self.get_conn_maxsize(which, maxsize) + if conn is None: + return None + + try: + x = msvcrt.get_osfhandle(conn.fileno()) + (read, nAvail, nMessage) = PeekNamedPipe(x, 0) + if maxsize < nAvail: + nAvail = maxsize + if nAvail > 0: + (errCode, read) = ReadFile(x, nAvail, None) + except ValueError: + return self._close(which) + except (subprocess.pywintypes.error, Exception): + if geterror()[0] in (109, errno.ESHUTDOWN): + return self._close(which) + raise + + if self.universal_newlines: + # Translate newlines. For Python 3.x assume read is text. + # If bytes then another solution is needed. + read = read.replace("\r\n", "\n").replace("\r", "\n") + return read + + else: + + def kill(self): + for i, sig in enumerate([SIGTERM, SIGKILL] * 2): + if i % 2 == 0: + os.kill(self.pid, sig) + time.sleep((i * (i % 2) / 5.0) + 0.01) + + killed_pid, stat = os.waitpid(self.pid, os.WNOHANG) + if killed_pid != 0: + return + + def send(self, input): + if not self.stdin: + return None + + if not select.select([], [self.stdin], [], 0)[1]: + return 0 + + try: + written = os.write(self.stdin.fileno(), input) + except OSError: + if geterror()[0] == errno.EPIPE: # broken pipe + return self._close("stdin") + raise + + return written + + def _recv(self, which, maxsize): + conn, maxsize = self.get_conn_maxsize(which, maxsize) + if conn is None: + return None + + if not select.select([conn], [], [], 0)[0]: + return "" + + r = conn.read(maxsize) + if not r: + return self._close(which) + + if self.universal_newlines: + r = r.replace("\r\n", "\n").replace("\r", "\n") + return r + + +################################################################################ + + +def proc_in_time_or_kill(cmd, time_out, wd=None, env=None): + proc = Popen( + cmd, + cwd=wd, + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=1, + ) + + ret_code = None + response = [] + + t = time.time() + while ret_code is None and ((time.time() - t) < time_out): + ret_code = proc.poll() + response += [proc.read_async(wait=0.1, e=0)] + + if ret_code is None: + ret_code = '"Process timed out (time_out = %s secs) ' % time_out + try: + proc.kill() + ret_code += 'and was successfully terminated"' + except Exception: + ret_code += 'and termination failed (exception: %s)"' % (geterror(),) + + return ret_code, "".join(response) + + +################################################################################ + + +class AsyncTest(unittest.TestCase): + def test_proc_in_time_or_kill(self): + ret_code, response = proc_in_time_or_kill( + [sys.executable, "-c", "while 1: pass"], time_out=1 + ) + + self.assertIn("rocess timed out", ret_code) + self.assertIn("successfully terminated", ret_code) + + +################################################################################ + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/buftools.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/buftools.py new file mode 100644 index 0000000..2d2112a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/buftools.py @@ -0,0 +1,613 @@ +"""Module pygame.tests.test_utils.array + +Export the Exporter and Importer classes. + +Class Exporter has configurable shape and strides. Exporter objects +provide a convient target for unit tests on Pygame objects and functions that +import a new buffer interface. + +Class Importer imports a buffer interface with the given PyBUF_* flags. +It returns NULL Py_buffer fields as None. The shape, strides, and suboffsets +arrays are returned as tuples of ints. All Py_buffer field properties are +read-only. This class is useful in comparing exported buffer interfaces +with the actual request. The simular Python builtin memoryview currently +does not support configurable PyBUF_* flags. + +This module contains its own unit tests. When Pygame is installed, these tests +can be run with the following command line statement: + +python -m pygame.tests.test_utils.array + +""" +import pygame + +if not pygame.HAVE_NEWBUF: + emsg = "This Pygame build does not support the new buffer protocol" + raise ImportError(emsg) +import pygame.newbuffer +from pygame.newbuffer import ( + PyBUF_SIMPLE, + PyBUF_FORMAT, + PyBUF_ND, + PyBUF_WRITABLE, + PyBUF_STRIDES, + PyBUF_C_CONTIGUOUS, + PyBUF_F_CONTIGUOUS, + PyBUF_ANY_CONTIGUOUS, + PyBUF_INDIRECT, + PyBUF_STRIDED, + PyBUF_STRIDED_RO, + PyBUF_RECORDS, + PyBUF_RECORDS_RO, + PyBUF_FULL, + PyBUF_FULL_RO, + PyBUF_CONTIG, + PyBUF_CONTIG_RO, +) + +import unittest +import sys +import ctypes +import operator + +try: + reduce +except NameError: + from functools import reduce + +__all__ = ["Exporter", "Importer"] + +try: + ctypes.c_ssize_t +except AttributeError: + void_p_sz = ctypes.sizeof(ctypes.c_void_p) + if ctypes.sizeof(ctypes.c_short) == void_p_sz: + ctypes.c_ssize_t = ctypes.c_short + elif ctypes.sizeof(ctypes.c_int) == void_p_sz: + ctypes.c_ssize_t = ctypes.c_int + elif ctypes.sizeof(ctypes.c_long) == void_p_sz: + ctypes.c_ssize_t = ctypes.c_long + elif ctypes.sizeof(ctypes.c_longlong) == void_p_sz: + ctypes.c_ssize_t = ctypes.c_longlong + else: + raise RuntimeError("Cannot set c_ssize_t: sizeof(void *) is %i" % void_p_sz) + + +def _prop_get(fn): + return property(fn) + + +class Exporter(pygame.newbuffer.BufferMixin): + """An object that exports a multi-dimension new buffer interface + + The only array operation this type supports is to export a buffer. + """ + + prefixes = { + "@": "", + "=": "=", + "<": "=", + ">": "=", + "!": "=", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9", + } + types = { + "c": ctypes.c_char, + "b": ctypes.c_byte, + "B": ctypes.c_ubyte, + "=c": ctypes.c_int8, + "=b": ctypes.c_int8, + "=B": ctypes.c_uint8, + "?": ctypes.c_bool, + "=?": ctypes.c_int8, + "h": ctypes.c_short, + "H": ctypes.c_ushort, + "=h": ctypes.c_int16, + "=H": ctypes.c_uint16, + "i": ctypes.c_int, + "I": ctypes.c_uint, + "=i": ctypes.c_int32, + "=I": ctypes.c_uint32, + "l": ctypes.c_long, + "L": ctypes.c_ulong, + "=l": ctypes.c_int32, + "=L": ctypes.c_uint32, + "q": ctypes.c_longlong, + "Q": ctypes.c_ulonglong, + "=q": ctypes.c_int64, + "=Q": ctypes.c_uint64, + "f": ctypes.c_float, + "d": ctypes.c_double, + "P": ctypes.c_void_p, + "x": ctypes.c_ubyte * 1, + "2x": ctypes.c_ubyte * 2, + "3x": ctypes.c_ubyte * 3, + "4x": ctypes.c_ubyte * 4, + "5x": ctypes.c_ubyte * 5, + "6x": ctypes.c_ubyte * 6, + "7x": ctypes.c_ubyte * 7, + "8x": ctypes.c_ubyte * 8, + "9x": ctypes.c_ubyte * 9, + } + + def __init__(self, shape, format=None, strides=None, readonly=None, itemsize=None): + if format is None: + format = "B" + if readonly is None: + readonly = False + prefix = "" + typecode = "" + i = 0 + if i < len(format): + try: + prefix = self.prefixes[format[i]] + i += 1 + except LookupError: + pass + if i < len(format) and format[i] == "1": + i += 1 + if i == len(format) - 1: + typecode = format[i] + if itemsize is None: + try: + itemsize = ctypes.sizeof(self.types[prefix + typecode]) + except KeyError: + raise ValueError("Unknown item format '" + format + "'") + self.readonly = bool(readonly) + self.format = format + self._format = ctypes.create_string_buffer(format.encode("latin_1")) + self.ndim = len(shape) + self.itemsize = itemsize + self.len = reduce(operator.mul, shape, 1) * self.itemsize + self.shape = tuple(shape) + self._shape = (ctypes.c_ssize_t * self.ndim)(*self.shape) + if strides is None: + self._strides = (ctypes.c_ssize_t * self.ndim)() + self._strides[self.ndim - 1] = itemsize + for i in range(self.ndim - 1, 0, -1): + self._strides[i - 1] = self.shape[i] * self._strides[i] + self.strides = tuple(self._strides) + elif len(strides) == self.ndim: + self.strides = tuple(strides) + self._strides = (ctypes.c_ssize_t * self.ndim)(*self.strides) + else: + raise ValueError("Mismatch in length of strides and shape") + buflen = max(d * abs(s) for d, s in zip(self.shape, self.strides)) + self.buflen = buflen + self._buf = (ctypes.c_ubyte * buflen)() + offset = sum( + (d - 1) * abs(s) for d, s in zip(self.shape, self.strides) if s < 0 + ) + self.buf = ctypes.addressof(self._buf) + offset + + def buffer_info(self): + return (addressof(self.buffer), self.shape[0]) + + def tobytes(self): + return cast(self.buffer, POINTER(c_char))[0 : self._len] + + def __len__(self): + return self.shape[0] + + def _get_buffer(self, view, flags): + from ctypes import addressof + + if (flags & PyBUF_WRITABLE) == PyBUF_WRITABLE and self.readonly: + raise BufferError("buffer is read-only") + if ( + flags & PyBUF_C_CONTIGUOUS + ) == PyBUF_C_CONTIGUOUS and not self.is_contiguous("C"): + raise BufferError("data is not C contiguous") + if ( + flags & PyBUF_F_CONTIGUOUS + ) == PyBUF_F_CONTIGUOUS and not self.is_contiguous("F"): + raise BufferError("data is not F contiguous") + if ( + flags & PyBUF_ANY_CONTIGUOUS + ) == PyBUF_ANY_CONTIGUOUS and not self.is_contiguous("A"): + raise BufferError("data is not contiguous") + view.buf = self.buf + view.readonly = self.readonly + view.len = self.len + if flags | PyBUF_WRITABLE == PyBUF_WRITABLE: + view.ndim = 0 + else: + view.ndim = self.ndim + view.itemsize = self.itemsize + if (flags & PyBUF_FORMAT) == PyBUF_FORMAT: + view.format = addressof(self._format) + else: + view.format = None + if (flags & PyBUF_ND) == PyBUF_ND: + view.shape = addressof(self._shape) + elif self.is_contiguous("C"): + view.shape = None + else: + raise BufferError( + "shape required for {} dimensional data".format(self.ndim) + ) + if (flags & PyBUF_STRIDES) == PyBUF_STRIDES: + view.strides = ctypes.addressof(self._strides) + elif view.shape is None or self.is_contiguous("C"): + view.strides = None + else: + raise BufferError("strides required for none C contiguous data") + view.suboffsets = None + view.internal = None + view.obj = self + + def is_contiguous(self, fortran): + if fortran in "CA": + if self.strides[-1] == self.itemsize: + for i in range(self.ndim - 1, 0, -1): + if self.strides[i - 1] != self.shape[i] * self.strides[i]: + break + else: + return True + if fortran in "FA": + if self.strides[0] == self.itemsize: + for i in range(0, self.ndim - 1): + if self.strides[i + 1] != self.shape[i] * self.strides[i]: + break + else: + return True + return False + + +class Importer(object): + """An object that imports a new buffer interface + + The fields of the Py_buffer C struct are exposed by identically + named Importer read-only properties. + """ + + def __init__(self, obj, flags): + self._view = pygame.newbuffer.Py_buffer() + self._view.get_buffer(obj, flags) + + @property + def obj(self): + """return object or None for NULL field""" + return self._view.obj + + @property + def buf(self): + """return int or None for NULL field""" + return self._view.buf + + @property + def len(self): + """return int""" + return self._view.len + + @property + def readonly(self): + """return bool""" + return self._view.readonly + + @property + def format(self): + """return bytes or None for NULL field""" + format_addr = self._view.format + if format_addr is None: + return None + return ctypes.cast(format_addr, ctypes.c_char_p).value.decode("ascii") + + @property + def itemsize(self): + """return int""" + return self._view.itemsize + + @property + def ndim(self): + """return int""" + return self._view.ndim + + @property + def shape(self): + """return int tuple or None for NULL field""" + return self._to_ssize_tuple(self._view.shape) + + @property + def strides(self): + """return int tuple or None for NULL field""" + return self._to_ssize_tuple(self._view.strides) + + @property + def suboffsets(self): + """return int tuple or None for NULL field""" + return self._to_ssize_tuple(self._view.suboffsets) + + @property + def internal(self): + """return int or None for NULL field""" + return self._view.internal + + def _to_ssize_tuple(self, addr): + from ctypes import cast, POINTER, c_ssize_t + + if addr is None: + return None + return tuple(cast(addr, POINTER(c_ssize_t))[0 : self._view.ndim]) + + +class ExporterTest(unittest.TestCase): + """Class Exporter unit tests""" + + def test_formats(self): + char_sz = ctypes.sizeof(ctypes.c_char) + short_sz = ctypes.sizeof(ctypes.c_short) + int_sz = ctypes.sizeof(ctypes.c_int) + long_sz = ctypes.sizeof(ctypes.c_long) + longlong_sz = ctypes.sizeof(ctypes.c_longlong) + float_sz = ctypes.sizeof(ctypes.c_float) + double_sz = ctypes.sizeof(ctypes.c_double) + voidp_sz = ctypes.sizeof(ctypes.c_void_p) + bool_sz = ctypes.sizeof(ctypes.c_bool) + + self.check_args(0, (1,), "B", (1,), 1, 1, 1) + self.check_args(1, (1,), "b", (1,), 1, 1, 1) + self.check_args(1, (1,), "B", (1,), 1, 1, 1) + self.check_args(1, (1,), "c", (char_sz,), char_sz, char_sz, char_sz) + self.check_args(1, (1,), "h", (short_sz,), short_sz, short_sz, short_sz) + self.check_args(1, (1,), "H", (short_sz,), short_sz, short_sz, short_sz) + self.check_args(1, (1,), "i", (int_sz,), int_sz, int_sz, int_sz) + self.check_args(1, (1,), "I", (int_sz,), int_sz, int_sz, int_sz) + self.check_args(1, (1,), "l", (long_sz,), long_sz, long_sz, long_sz) + self.check_args(1, (1,), "L", (long_sz,), long_sz, long_sz, long_sz) + self.check_args( + 1, (1,), "q", (longlong_sz,), longlong_sz, longlong_sz, longlong_sz + ) + self.check_args( + 1, (1,), "Q", (longlong_sz,), longlong_sz, longlong_sz, longlong_sz + ) + self.check_args(1, (1,), "f", (float_sz,), float_sz, float_sz, float_sz) + self.check_args(1, (1,), "d", (double_sz,), double_sz, double_sz, double_sz) + self.check_args(1, (1,), "x", (1,), 1, 1, 1) + self.check_args(1, (1,), "P", (voidp_sz,), voidp_sz, voidp_sz, voidp_sz) + self.check_args(1, (1,), "?", (bool_sz,), bool_sz, bool_sz, bool_sz) + self.check_args(1, (1,), "@b", (1,), 1, 1, 1) + self.check_args(1, (1,), "@B", (1,), 1, 1, 1) + self.check_args(1, (1,), "@c", (char_sz,), char_sz, char_sz, char_sz) + self.check_args(1, (1,), "@h", (short_sz,), short_sz, short_sz, short_sz) + self.check_args(1, (1,), "@H", (short_sz,), short_sz, short_sz, short_sz) + self.check_args(1, (1,), "@i", (int_sz,), int_sz, int_sz, int_sz) + self.check_args(1, (1,), "@I", (int_sz,), int_sz, int_sz, int_sz) + self.check_args(1, (1,), "@l", (long_sz,), long_sz, long_sz, long_sz) + self.check_args(1, (1,), "@L", (long_sz,), long_sz, long_sz, long_sz) + self.check_args( + 1, (1,), "@q", (longlong_sz,), longlong_sz, longlong_sz, longlong_sz + ) + self.check_args( + 1, (1,), "@Q", (longlong_sz,), longlong_sz, longlong_sz, longlong_sz + ) + self.check_args(1, (1,), "@f", (float_sz,), float_sz, float_sz, float_sz) + self.check_args(1, (1,), "@d", (double_sz,), double_sz, double_sz, double_sz) + self.check_args(1, (1,), "@?", (bool_sz,), bool_sz, bool_sz, bool_sz) + self.check_args(1, (1,), "=b", (1,), 1, 1, 1) + self.check_args(1, (1,), "=B", (1,), 1, 1, 1) + self.check_args(1, (1,), "=c", (1,), 1, 1, 1) + self.check_args(1, (1,), "=h", (2,), 2, 2, 2) + self.check_args(1, (1,), "=H", (2,), 2, 2, 2) + self.check_args(1, (1,), "=i", (4,), 4, 4, 4) + self.check_args(1, (1,), "=I", (4,), 4, 4, 4) + self.check_args(1, (1,), "=l", (4,), 4, 4, 4) + self.check_args(1, (1,), "=L", (4,), 4, 4, 4) + self.check_args(1, (1,), "=q", (8,), 8, 8, 8) + self.check_args(1, (1,), "=Q", (8,), 8, 8, 8) + self.check_args(1, (1,), "=?", (1,), 1, 1, 1) + self.check_args(1, (1,), "h", (2,), 2, 2, 2) + self.check_args(1, (1,), "!h", (2,), 2, 2, 2) + self.check_args(1, (1,), "q", (8,), 8, 8, 8) + self.check_args(1, (1,), "!q", (8,), 8, 8, 8) + self.check_args(1, (1,), "1x", (1,), 1, 1, 1) + self.check_args(1, (1,), "2x", (2,), 2, 2, 2) + self.check_args(1, (1,), "3x", (3,), 3, 3, 3) + self.check_args(1, (1,), "4x", (4,), 4, 4, 4) + self.check_args(1, (1,), "5x", (5,), 5, 5, 5) + self.check_args(1, (1,), "6x", (6,), 6, 6, 6) + self.check_args(1, (1,), "7x", (7,), 7, 7, 7) + self.check_args(1, (1,), "8x", (8,), 8, 8, 8) + self.check_args(1, (1,), "9x", (9,), 9, 9, 9) + self.check_args(1, (1,), "1h", (2,), 2, 2, 2) + self.check_args(1, (1,), "=1h", (2,), 2, 2, 2) + self.assertRaises(ValueError, Exporter, (2, 1), "") + self.assertRaises(ValueError, Exporter, (2, 1), "W") + self.assertRaises(ValueError, Exporter, (2, 1), "^Q") + self.assertRaises(ValueError, Exporter, (2, 1), "=W") + self.assertRaises(ValueError, Exporter, (2, 1), "=f") + self.assertRaises(ValueError, Exporter, (2, 1), "=d") + self.assertRaises(ValueError, Exporter, (2, 1), "f") + self.assertRaises(ValueError, Exporter, (2, 1), ">d") + self.assertRaises(ValueError, Exporter, (2, 1), "!f") + self.assertRaises(ValueError, Exporter, (2, 1), "!d") + self.assertRaises(ValueError, Exporter, (2, 1), "0x") + self.assertRaises(ValueError, Exporter, (2, 1), "11x") + self.assertRaises(ValueError, Exporter, (2, 1), "BB") + + def test_strides(self): + self.check_args(1, (10,), "=h", (2,), 20, 20, 2) + self.check_args(1, (5, 3), "=h", (6, 2), 30, 30, 2) + self.check_args(1, (7, 3, 5), "=h", (30, 10, 2), 210, 210, 2) + self.check_args(1, (13, 5, 11, 3), "=h", (330, 66, 6, 2), 4290, 4290, 2) + self.check_args(3, (7, 3, 5), "=h", (2, 14, 42), 210, 210, 2) + self.check_args(3, (7, 3, 5), "=h", (2, 16, 48), 210, 240, 2) + self.check_args(3, (13, 5, 11, 3), "=h", (440, 88, 8, 2), 4290, 5720, 2) + self.check_args(3, (7, 5), "3x", (15, 3), 105, 105, 3) + self.check_args(3, (7, 5), "3x", (3, 21), 105, 105, 3) + self.check_args(3, (7, 5), "3x", (3, 24), 105, 120, 3) + + def test_readonly(self): + a = Exporter((2,), "h", readonly=True) + self.assertTrue(a.readonly) + b = Importer(a, PyBUF_STRIDED_RO) + self.assertRaises(BufferError, Importer, a, PyBUF_STRIDED) + b = Importer(a, PyBUF_STRIDED_RO) + + def test_is_contiguous(self): + a = Exporter((10,), "=h") + self.assertTrue(a.is_contiguous("C")) + self.assertTrue(a.is_contiguous("F")) + self.assertTrue(a.is_contiguous("A")) + a = Exporter((10, 4), "=h") + self.assertTrue(a.is_contiguous("C")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("F")) + a = Exporter((13, 5, 11, 3), "=h", (330, 66, 6, 2)) + self.assertTrue(a.is_contiguous("C")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("F")) + a = Exporter((10, 4), "=h", (2, 20)) + self.assertTrue(a.is_contiguous("F")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("C")) + a = Exporter((13, 5, 11, 3), "=h", (2, 26, 130, 1430)) + self.assertTrue(a.is_contiguous("F")) + self.assertTrue(a.is_contiguous("A")) + self.assertFalse(a.is_contiguous("C")) + a = Exporter((2, 11, 6, 4), "=h", (576, 48, 8, 2)) + self.assertFalse(a.is_contiguous("A")) + a = Exporter((2, 11, 6, 4), "=h", (2, 4, 48, 288)) + self.assertFalse(a.is_contiguous("A")) + a = Exporter((3, 2, 2), "=h", (16, 8, 4)) + self.assertFalse(a.is_contiguous("A")) + a = Exporter((3, 2, 2), "=h", (4, 12, 24)) + self.assertFalse(a.is_contiguous("A")) + + def test_PyBUF_flags(self): + a = Exporter((10, 2), "d") + b = Importer(a, PyBUF_SIMPLE) + self.assertTrue(b.obj is a) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.len) + self.assertEqual(b.itemsize, a.itemsize) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertTrue(b.internal is None) + self.assertFalse(b.readonly) + b = Importer(a, PyBUF_WRITABLE) + self.assertTrue(b.obj is a) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.len) + self.assertEqual(b.itemsize, a.itemsize) + self.assertTrue(b.shape is None) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertTrue(b.internal is None) + self.assertFalse(b.readonly) + b = Importer(a, PyBUF_ND) + self.assertTrue(b.obj is a) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.len) + self.assertEqual(b.itemsize, a.itemsize) + self.assertEqual(b.shape, a.shape) + self.assertTrue(b.strides is None) + self.assertTrue(b.suboffsets is None) + self.assertTrue(b.internal is None) + self.assertFalse(b.readonly) + a = Exporter((5, 10), "=h", (24, 2)) + b = Importer(a, PyBUF_STRIDES) + self.assertTrue(b.obj is a) + self.assertTrue(b.format is None) + self.assertEqual(b.len, a.len) + self.assertEqual(b.itemsize, a.itemsize) + self.assertEqual(b.shape, a.shape) + self.assertEqual(b.strides, a.strides) + self.assertTrue(b.suboffsets is None) + self.assertTrue(b.internal is None) + self.assertFalse(b.readonly) + b = Importer(a, PyBUF_FULL) + self.assertTrue(b.obj is a) + self.assertEqual(b.format, "=h") + self.assertEqual(b.len, a.len) + self.assertEqual(b.itemsize, a.itemsize) + self.assertEqual(b.shape, a.shape) + self.assertEqual(b.strides, a.strides) + self.assertTrue(b.suboffsets is None) + self.assertTrue(b.internal is None) + self.assertFalse(b.readonly) + self.assertRaises(BufferError, Importer, a, PyBUF_SIMPLE) + self.assertRaises(BufferError, Importer, a, PyBUF_WRITABLE) + self.assertRaises(BufferError, Importer, a, PyBUF_ND) + self.assertRaises(BufferError, Importer, a, PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, PyBUF_ANY_CONTIGUOUS) + self.assertRaises(BufferError, Importer, a, PyBUF_CONTIG) + + def test_negative_strides(self): + self.check_args(3, (3, 5, 4), "B", (20, 4, -1), 60, 60, 1, 3) + self.check_args(3, (3, 5, 3), "B", (20, 4, -1), 45, 60, 1, 2) + self.check_args(3, (3, 5, 4), "B", (20, -4, 1), 60, 60, 1, 16) + self.check_args(3, (3, 5, 4), "B", (-20, -4, -1), 60, 60, 1, 59) + self.check_args(3, (3, 5, 3), "B", (-20, -4, -1), 45, 60, 1, 58) + + def test_attributes(self): + a = Exporter((13, 5, 11, 3), "=h", (440, 88, 8, 2)) + self.assertEqual(a.ndim, 4) + self.assertEqual(a.itemsize, 2) + self.assertFalse(a.readonly) + self.assertEqual(a.shape, (13, 5, 11, 3)) + self.assertEqual(a.format, "=h") + self.assertEqual(a.strides, (440, 88, 8, 2)) + self.assertEqual(a.len, 4290) + self.assertEqual(a.buflen, 5720) + self.assertEqual(a.buf, ctypes.addressof(a._buf)) + a = Exporter((8,)) + self.assertEqual(a.ndim, 1) + self.assertEqual(a.itemsize, 1) + self.assertFalse(a.readonly) + self.assertEqual(a.shape, (8,)) + self.assertEqual(a.format, "B") + self.assertTrue(isinstance(a.strides, tuple)) + self.assertEqual(a.strides, (1,)) + self.assertEqual(a.len, 8) + self.assertEqual(a.buflen, 8) + a = Exporter([13, 5, 11, 3], "=h", [440, 88, 8, 2]) + self.assertTrue(isinstance(a.shape, tuple)) + self.assertTrue(isinstance(a.strides, tuple)) + self.assertEqual(a.shape, (13, 5, 11, 3)) + self.assertEqual(a.strides, (440, 88, 8, 2)) + + def test_itemsize(self): + exp = Exporter((4, 5), format="B", itemsize=8) + imp = Importer(exp, PyBUF_RECORDS) + self.assertEqual(imp.itemsize, 8) + self.assertEqual(imp.format, "B") + self.assertEqual(imp.strides, (40, 8)) + exp = Exporter((4, 5), format="weird", itemsize=5) + imp = Importer(exp, PyBUF_RECORDS) + self.assertEqual(imp.itemsize, 5) + self.assertEqual(imp.format, "weird") + self.assertEqual(imp.strides, (25, 5)) + + def check_args( + self, call_flags, shape, format, strides, length, bufsize, itemsize, offset=0 + ): + format_arg = format if call_flags & 1 else None + strides_arg = strides if call_flags & 2 else None + a = Exporter(shape, format_arg, strides_arg) + self.assertEqual(a.buflen, bufsize) + self.assertEqual(a.buf, ctypes.addressof(a._buf) + offset) + m = Importer(a, PyBUF_RECORDS_RO) + self.assertEqual(m.buf, a.buf) + self.assertEqual(m.len, length) + self.assertEqual(m.format, format) + self.assertEqual(m.itemsize, itemsize) + self.assertEqual(m.shape, shape) + self.assertEqual(m.strides, strides) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/endian.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/endian.py new file mode 100644 index 0000000..64ba1b3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/endian.py @@ -0,0 +1,20 @@ +# Module pygame.tests.test_utils.endian +# +# Machine independent conversion to little-endian and big-endian Python +# integer values. + +import struct + + +def little_endian_uint32(i): + """Return the 32 bit unsigned integer little-endian representation of i""" + + s = struct.pack("I", i) + return struct.unpack("=I", s)[0] diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/png.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/png.py new file mode 100644 index 0000000..74df9fd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/png.py @@ -0,0 +1,4001 @@ +#!/usr/bin/env python + +# $URL: http://pypng.googlecode.com/svn/trunk/code/png.py $ +# $Rev: 228 $ + +# png.py - PNG encoder/decoder in pure Python +# +# Modified for Pygame in Oct., 2012 to work with Python 3.x. +# +# Copyright (C) 2006 Johann C. Rocholl +# Portions Copyright (C) 2009 David Jones +# And probably portions Copyright (C) 2006 Nicko van Someren +# +# Original concept by Johann C. Rocholl. +# +# LICENSE (The MIT License) +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Changelog (recent first): +# 2009-03-11 David: interlaced bit depth < 8 (writing). +# 2009-03-10 David: interlaced bit depth < 8 (reading). +# 2009-03-04 David: Flat and Boxed pixel formats. +# 2009-02-26 David: Palette support (writing). +# 2009-02-23 David: Bit-depths < 8; better PNM support. +# 2006-06-17 Nicko: Reworked into a class, faster interlacing. +# 2006-06-17 Johann: Very simple prototype PNG decoder. +# 2006-06-17 Nicko: Test suite with various image generators. +# 2006-06-17 Nicko: Alpha-channel, grey-scale, 16-bit/plane support. +# 2006-06-15 Johann: Scanline iterator interface for large input files. +# 2006-06-09 Johann: Very simple prototype PNG encoder. + +# Incorporated into Bangai-O Development Tools by drj on 2009-02-11 from +# http://trac.browsershots.org/browser/trunk/pypng/lib/png.py?rev=2885 + +# Incorporated into pypng by drj on 2009-03-12 from +# //depot/prj/bangaio/master/code/png.py#67 + + +""" +Pure Python PNG Reader/Writer + +This Python module implements support for PNG images (see PNG +specification at http://www.w3.org/TR/2003/REC-PNG-20031110/ ). It reads +and writes PNG files with all allowable bit depths (1/2/4/8/16/24/32/48/64 +bits per pixel) and colour combinations: greyscale (1/2/4/8/16 bit); RGB, +RGBA, LA (greyscale with alpha) with 8/16 bits per channel; colour mapped +images (1/2/4/8 bit). Adam7 interlacing is supported for reading and +writing. A number of optional chunks can be specified (when writing) +and understood (when reading): ``tRNS``, ``bKGD``, ``gAMA``. + +For help, type ``import png; help(png)`` in your python interpreter. + +A good place to start is the :class:`Reader` and :class:`Writer` classes. + +This file can also be used as a command-line utility to convert +`Netpbm `_ PNM files to PNG, and the reverse conversion from PNG to +PNM. The interface is similar to that of the ``pnmtopng`` program from +Netpbm. Type ``python png.py --help`` at the shell prompt +for usage and a list of options. + +A note on spelling and terminology +---------------------------------- + +Generally British English spelling is used in the documentation. So +that's "greyscale" and "colour". This not only matches the author's +native language, it's also used by the PNG specification. + +The major colour models supported by PNG (and hence by PyPNG) are: +greyscale, RGB, greyscale--alpha, RGB--alpha. These are sometimes +referred to using the abbreviations: L, RGB, LA, RGBA. In this case +each letter abbreviates a single channel: *L* is for Luminance or Luma or +Lightness which is the channel used in greyscale images; *R*, *G*, *B* stand +for Red, Green, Blue, the components of a colour image; *A* stands for +Alpha, the opacity channel (used for transparency effects, but higher +values are more opaque, so it makes sense to call it opacity). + +A note on formats +----------------- + +When getting pixel data out of this module (reading) and presenting +data to this module (writing) there are a number of ways the data could +be represented as a Python value. Generally this module uses one of +three formats called "flat row flat pixel", "boxed row flat pixel", and +"boxed row boxed pixel". Basically the concern is whether each pixel +and each row comes in its own little tuple (box), or not. + +Consider an image that is 3 pixels wide by 2 pixels high, and each pixel +has RGB components: + +Boxed row flat pixel:: + + list([R,G,B, R,G,B, R,G,B], + [R,G,B, R,G,B, R,G,B]) + +Each row appears as its own list, but the pixels are flattened so that +three values for one pixel simply follow the three values for the previous +pixel. This is the most common format used, because it provides a good +compromise between space and convenience. PyPNG regards itself as +at liberty to replace any sequence type with any sufficiently compatible +other sequence type; in practice each row is an array (from the array +module), and the outer list is sometimes an iterator rather than an +explicit list (so that streaming is possible). + +Flat row flat pixel:: + + [R,G,B, R,G,B, R,G,B, + R,G,B, R,G,B, R,G,B] + +The entire image is one single giant sequence of colour values. +Generally an array will be used (to save space), not a list. + +Boxed row boxed pixel:: + + list([ (R,G,B), (R,G,B), (R,G,B) ], + [ (R,G,B), (R,G,B), (R,G,B) ]) + +Each row appears in its own list, but each pixel also appears in its own +tuple. A serious memory burn in Python. + +In all cases the top row comes first, and for each row the pixels are +ordered from left-to-right. Within a pixel the values appear in the +order, R-G-B-A (or L-A for greyscale--alpha). + +There is a fourth format, mentioned because it is used internally, +is close to what lies inside a PNG file itself, and has some support +from the public API. This format is called packed. When packed, +each row is a sequence of bytes (integers from 0 to 255), just as +it is before PNG scanline filtering is applied. When the bit depth +is 8 this is essentially the same as boxed row flat pixel; when the +bit depth is less than 8, several pixels are packed into each byte; +when the bit depth is 16 (the only value more than 8 that is supported +by the PNG image format) each pixel value is decomposed into 2 bytes +(and `packed` is a misnomer). This format is used by the +:meth:`Writer.write_packed` method. It isn't usually a convenient +format, but may be just right if the source data for the PNG image +comes from something that uses a similar format (for example, 1-bit +BMPs, or another PNG file). + +And now, my famous members +-------------------------- +""" + +__version__ = "$URL: http://pypng.googlecode.com/svn/trunk/code/png.py $ $Rev: 228 $" + +from array import array +from pygame.tests.test_utils import tostring +import itertools +import math +import operator +import struct +import sys +import zlib +import warnings + +__all__ = ["Image", "Reader", "Writer", "write_chunks", "from_array"] + + +# The PNG signature. +# http://www.w3.org/TR/PNG/#5PNG-file-signature +_signature = struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10) + +_adam7 = ( + (0, 0, 8, 8), + (4, 0, 8, 8), + (0, 4, 4, 8), + (2, 0, 4, 4), + (0, 2, 2, 4), + (1, 0, 2, 2), + (0, 1, 1, 2), +) + + +def group(s, n): + # See + # http://www.python.org/doc/2.6/library/functions.html#zip + return zip(*[iter(s)] * n) + + +def isarray(x): + """Same as ``isinstance(x, array)``.""" + return isinstance(x, array) + + +# Conditionally convert to bytes. Works on Python 2 and Python 3. +try: + bytes("", "ascii") + + def strtobytes(x): + return bytes(x, "iso8859-1") + + def bytestostr(x): + return str(x, "iso8859-1") + +except: + strtobytes = str + bytestostr = str + + +def interleave_planes(ipixels, apixels, ipsize, apsize): + """ + Interleave (colour) planes, e.g. RGB + A = RGBA. + + Return an array of pixels consisting of the `ipsize` elements of data + from each pixel in `ipixels` followed by the `apsize` elements of data + from each pixel in `apixels`. Conventionally `ipixels` and + `apixels` are byte arrays so the sizes are bytes, but it actually + works with any arrays of the same type. The returned array is the + same type as the input arrays which should be the same type as each other. + """ + + itotal = len(ipixels) + atotal = len(apixels) + newtotal = itotal + atotal + newpsize = ipsize + apsize + # Set up the output buffer + # See http://www.python.org/doc/2.4.4/lib/module-array.html#l2h-1356 + out = array(ipixels.typecode) + # It's annoying that there is no cheap way to set the array size :-( + out.extend(ipixels) + out.extend(apixels) + # Interleave in the pixel data + for i in range(ipsize): + out[i:newtotal:newpsize] = ipixels[i:itotal:ipsize] + for i in range(apsize): + out[i + ipsize : newtotal : newpsize] = apixels[i:atotal:apsize] + return out + + +def check_palette(palette): + """Check a palette argument (to the :class:`Writer` class) for validity. + Returns the palette as a list if okay; raises an exception otherwise. + """ + + # None is the default and is allowed. + if palette is None: + return None + + p = list(palette) + if not (0 < len(p) <= 256): + raise ValueError("a palette must have between 1 and 256 entries") + seen_triple = False + for i, t in enumerate(p): + if len(t) not in (3, 4): + raise ValueError("palette entry %d: entries must be 3- or 4-tuples." % i) + if len(t) == 3: + seen_triple = True + if seen_triple and len(t) == 4: + raise ValueError( + "palette entry %d: all 4-tuples must precede all 3-tuples" % i + ) + for x in t: + if int(x) != x or not (0 <= x <= 255): + raise ValueError( + "palette entry %d: values must be integer: 0 <= x <= 255" % i + ) + return p + + +class Error(Exception): + prefix = "Error" + + def __str__(self): + return self.prefix + ": " + " ".join(self.args) + + +class FormatError(Error): + """Problem with input file format. In other words, PNG file does + not conform to the specification in some way and is invalid. + """ + + prefix = "FormatError" + + +class ChunkError(FormatError): + prefix = "ChunkError" + + +class Writer: + """ + PNG encoder in pure Python. + """ + + def __init__( + self, + width=None, + height=None, + size=None, + greyscale=False, + alpha=False, + bitdepth=8, + palette=None, + transparent=None, + background=None, + gamma=None, + compression=None, + interlace=False, + bytes_per_sample=None, # deprecated + planes=None, + colormap=None, + maxval=None, + chunk_limit=2 ** 20, + ): + """ + Create a PNG encoder object. + + Arguments: + + width, height + Image size in pixels, as two separate arguments. + size + Image size (w,h) in pixels, as single argument. + greyscale + Input data is greyscale, not RGB. + alpha + Input data has alpha channel (RGBA or LA). + bitdepth + Bit depth: from 1 to 16. + palette + Create a palette for a colour mapped image (colour type 3). + transparent + Specify a transparent colour (create a ``tRNS`` chunk). + background + Specify a default background colour (create a ``bKGD`` chunk). + gamma + Specify a gamma value (create a ``gAMA`` chunk). + compression + zlib compression level (1-9). + interlace + Create an interlaced image. + chunk_limit + Write multiple ``IDAT`` chunks to save memory. + + The image size (in pixels) can be specified either by using the + `width` and `height` arguments, or with the single `size` + argument. If `size` is used it should be a pair (*width*, + *height*). + + `greyscale` and `alpha` are booleans that specify whether + an image is greyscale (or colour), and whether it has an + alpha channel (or not). + + `bitdepth` specifies the bit depth of the source pixel values. + Each source pixel value must be an integer between 0 and + ``2**bitdepth-1``. For example, 8-bit images have values + between 0 and 255. PNG only stores images with bit depths of + 1,2,4,8, or 16. When `bitdepth` is not one of these values, + the next highest valid bit depth is selected, and an ``sBIT`` + (significant bits) chunk is generated that specifies the original + precision of the source image. In this case the supplied pixel + values will be rescaled to fit the range of the selected bit depth. + + The details of which bit depth / colour model combinations the + PNG file format supports directly, are somewhat arcane + (refer to the PNG specification for full details). Briefly: + "small" bit depths (1,2,4) are only allowed with greyscale and + colour mapped images; colour mapped images cannot have bit depth + 16. + + For colour mapped images (in other words, when the `palette` + argument is specified) the `bitdepth` argument must match one of + the valid PNG bit depths: 1, 2, 4, or 8. (It is valid to have a + PNG image with a palette and an ``sBIT`` chunk, but the meaning + is slightly different; it would be awkward to press the + `bitdepth` argument into service for this.) + + The `palette` option, when specified, causes a colour mapped image + to be created: the PNG colour type is set to 3; greyscale + must not be set; alpha must not be set; transparent must + not be set; the bit depth must be 1,2,4, or 8. When a colour + mapped image is created, the pixel values are palette indexes + and the `bitdepth` argument specifies the size of these indexes + (not the size of the colour values in the palette). + + The palette argument value should be a sequence of 3- or + 4-tuples. 3-tuples specify RGB palette entries; 4-tuples + specify RGBA palette entries. If both 4-tuples and 3-tuples + appear in the sequence then all the 4-tuples must come + before all the 3-tuples. A ``PLTE`` chunk is created; if there + are 4-tuples then a ``tRNS`` chunk is created as well. The + ``PLTE`` chunk will contain all the RGB triples in the same + sequence; the ``tRNS`` chunk will contain the alpha channel for + all the 4-tuples, in the same sequence. Palette entries + are always 8-bit. + + If specified, the `transparent` and `background` parameters must + be a tuple with three integer values for red, green, blue, or + a simple integer (or singleton tuple) for a greyscale image. + + If specified, the `gamma` parameter must be a positive number + (generally, a float). A ``gAMA`` chunk will be created. Note that + this will not change the values of the pixels as they appear in + the PNG file, they are assumed to have already been converted + appropriately for the gamma specified. + + The `compression` argument specifies the compression level + to be used by the ``zlib`` module. Higher values are likely + to compress better, but will be slower to compress. The + default for this argument is ``None``; this does not mean + no compression, rather it means that the default from the + ``zlib`` module is used (which is generally acceptable). + + If `interlace` is true then an interlaced image is created + (using PNG's so far only interlace method, *Adam7*). This does not + affect how the pixels should be presented to the encoder, rather + it changes how they are arranged into the PNG file. On slow + connexions interlaced images can be partially decoded by the + browser to give a rough view of the image that is successively + refined as more image data appears. + + .. note :: + + Enabling the `interlace` option requires the entire image + to be processed in working memory. + + `chunk_limit` is used to limit the amount of memory used whilst + compressing the image. In order to avoid using large amounts of + memory, multiple ``IDAT`` chunks may be created. + """ + + # At the moment the `planes` argument is ignored; + # its purpose is to act as a dummy so that + # ``Writer(x, y, **info)`` works, where `info` is a dictionary + # returned by Reader.read and friends. + # Ditto for `colormap`. + + # A couple of helper functions come first. Best skipped if you + # are reading through. + + def isinteger(x): + try: + return int(x) == x + except: + return False + + def check_color(c, which): + """Checks that a colour argument for transparent or + background options is the right form. Also "corrects" bare + integers to 1-tuples. + """ + + if c is None: + return c + if greyscale: + try: + l = len(c) + except TypeError: + c = (c,) + if len(c) != 1: + raise ValueError("%s for greyscale must be 1-tuple" % which) + if not isinteger(c[0]): + raise ValueError("%s colour for greyscale must be integer" % which) + else: + if not ( + len(c) == 3 + and isinteger(c[0]) + and isinteger(c[1]) + and isinteger(c[2]) + ): + raise ValueError("%s colour must be a triple of integers" % which) + return c + + if size: + if len(size) != 2: + raise ValueError("size argument should be a pair (width, height)") + if width is not None and width != size[0]: + raise ValueError( + "size[0] (%r) and width (%r) should match when both are used." + % (size[0], width) + ) + if height is not None and height != size[1]: + raise ValueError( + "size[1] (%r) and height (%r) should match when both are used." + % (size[1], height) + ) + width, height = size + del size + + if width <= 0 or height <= 0: + raise ValueError("width and height must be greater than zero") + if not isinteger(width) or not isinteger(height): + raise ValueError("width and height must be integers") + # http://www.w3.org/TR/PNG/#7Integers-and-byte-order + if width > 2 ** 32 - 1 or height > 2 ** 32 - 1: + raise ValueError("width and height cannot exceed 2**32-1") + + if alpha and transparent is not None: + raise ValueError("transparent colour not allowed with alpha channel") + + if bytes_per_sample is not None: + warnings.warn( + "please use bitdepth instead of bytes_per_sample", DeprecationWarning + ) + if bytes_per_sample not in (0.125, 0.25, 0.5, 1, 2): + raise ValueError("bytes per sample must be .125, .25, .5, 1, or 2") + bitdepth = int(8 * bytes_per_sample) + del bytes_per_sample + if not isinteger(bitdepth) or bitdepth < 1 or 16 < bitdepth: + raise ValueError( + "bitdepth (%r) must be a positive integer <= 16" % bitdepth + ) + + self.rescale = None + if palette: + if bitdepth not in (1, 2, 4, 8): + raise ValueError("with palette, bitdepth must be 1, 2, 4, or 8") + if transparent is not None: + raise ValueError("transparent and palette not compatible") + if alpha: + raise ValueError("alpha and palette not compatible") + if greyscale: + raise ValueError("greyscale and palette not compatible") + else: + # No palette, check for sBIT chunk generation. + if alpha or not greyscale: + if bitdepth not in (8, 16): + targetbitdepth = (8, 16)[bitdepth > 8] + self.rescale = (bitdepth, targetbitdepth) + bitdepth = targetbitdepth + del targetbitdepth + else: + assert greyscale + assert not alpha + if bitdepth not in (1, 2, 4, 8, 16): + if bitdepth > 8: + targetbitdepth = 16 + elif bitdepth == 3: + targetbitdepth = 4 + else: + assert bitdepth in (5, 6, 7) + targetbitdepth = 8 + self.rescale = (bitdepth, targetbitdepth) + bitdepth = targetbitdepth + del targetbitdepth + + if bitdepth < 8 and (alpha or not greyscale and not palette): + raise ValueError("bitdepth < 8 only permitted with greyscale or palette") + if bitdepth > 8 and palette: + raise ValueError("bit depth must be 8 or less for images with palette") + + transparent = check_color(transparent, "transparent") + background = check_color(background, "background") + + # It's important that the true boolean values (greyscale, alpha, + # colormap, interlace) are converted to bool because Iverson's + # convention is relied upon later on. + self.width = width + self.height = height + self.transparent = transparent + self.background = background + self.gamma = gamma + self.greyscale = bool(greyscale) + self.alpha = bool(alpha) + self.colormap = bool(palette) + self.bitdepth = int(bitdepth) + self.compression = compression + self.chunk_limit = chunk_limit + self.interlace = bool(interlace) + self.palette = check_palette(palette) + + self.color_type = 4 * self.alpha + 2 * (not greyscale) + 1 * self.colormap + assert self.color_type in (0, 2, 3, 4, 6) + + self.color_planes = (3, 1)[self.greyscale or self.colormap] + self.planes = self.color_planes + self.alpha + # :todo: fix for bitdepth < 8 + self.psize = (self.bitdepth / 8) * self.planes + + def make_palette(self): + """Create the byte sequences for a ``PLTE`` and if necessary a + ``tRNS`` chunk. Returned as a pair (*p*, *t*). *t* will be + ``None`` if no ``tRNS`` chunk is necessary. + """ + + p = array("B") + t = array("B") + + for x in self.palette: + p.extend(x[0:3]) + if len(x) > 3: + t.append(x[3]) + p = tostring(p) + t = tostring(t) + if t: + return p, t + return p, None + + def write(self, outfile, rows): + """Write a PNG image to the output file. `rows` should be + an iterable that yields each row in boxed row flat pixel format. + The rows should be the rows of the original image, so there + should be ``self.height`` rows of ``self.width * self.planes`` values. + If `interlace` is specified (when creating the instance), then + an interlaced PNG file will be written. Supply the rows in the + normal image order; the interlacing is carried out internally. + + .. note :: + + Interlacing will require the entire image to be in working memory. + """ + + if self.interlace: + fmt = "BH"[self.bitdepth > 8] + a = array(fmt, itertools.chain(*rows)) + return self.write_array(outfile, a) + else: + nrows = self.write_passes(outfile, rows) + if nrows != self.height: + raise ValueError( + "rows supplied (%d) does not match height (%d)" + % (nrows, self.height) + ) + + def write_passes(self, outfile, rows, packed=False): + """ + Write a PNG image to the output file. + + Most users are expected to find the :meth:`write` or + :meth:`write_array` method more convenient. + + The rows should be given to this method in the order that + they appear in the output file. For straightlaced images, + this is the usual top to bottom ordering, but for interlaced + images the rows should have already been interlaced before + passing them to this function. + + `rows` should be an iterable that yields each row. When + `packed` is ``False`` the rows should be in boxed row flat pixel + format; when `packed` is ``True`` each row should be a packed + sequence of bytes. + + """ + + # http://www.w3.org/TR/PNG/#5PNG-file-signature + outfile.write(_signature) + + # http://www.w3.org/TR/PNG/#11IHDR + write_chunk( + outfile, + "IHDR", + struct.pack( + "!2I5B", + self.width, + self.height, + self.bitdepth, + self.color_type, + 0, + 0, + self.interlace, + ), + ) + + # See :chunk:order + # http://www.w3.org/TR/PNG/#11gAMA + if self.gamma is not None: + write_chunk( + outfile, "gAMA", struct.pack("!L", int(round(self.gamma * 1e5))) + ) + + # See :chunk:order + # http://www.w3.org/TR/PNG/#11sBIT + if self.rescale: + write_chunk( + outfile, + "sBIT", + struct.pack("%dB" % self.planes, *[self.rescale[0]] * self.planes), + ) + + # :chunk:order: Without a palette (PLTE chunk), ordering is + # relatively relaxed. With one, gAMA chunk must precede PLTE + # chunk which must precede tRNS and bKGD. + # See http://www.w3.org/TR/PNG/#5ChunkOrdering + if self.palette: + p, t = self.make_palette() + write_chunk(outfile, "PLTE", p) + if t: + # tRNS chunk is optional. Only needed if palette entries + # have alpha. + write_chunk(outfile, "tRNS", t) + + # http://www.w3.org/TR/PNG/#11tRNS + if self.transparent is not None: + if self.greyscale: + write_chunk(outfile, "tRNS", struct.pack("!1H", *self.transparent)) + else: + write_chunk(outfile, "tRNS", struct.pack("!3H", *self.transparent)) + + # http://www.w3.org/TR/PNG/#11bKGD + if self.background is not None: + if self.greyscale: + write_chunk(outfile, "bKGD", struct.pack("!1H", *self.background)) + else: + write_chunk(outfile, "bKGD", struct.pack("!3H", *self.background)) + + # http://www.w3.org/TR/PNG/#11IDAT + if self.compression is not None: + compressor = zlib.compressobj(self.compression) + else: + compressor = zlib.compressobj() + + # Choose an extend function based on the bitdepth. The extend + # function packs/decomposes the pixel values into bytes and + # stuffs them onto the data array. + data = array("B") + if self.bitdepth == 8 or packed: + extend = data.extend + elif self.bitdepth == 16: + # Decompose into bytes + def extend(sl): + fmt = "!%dH" % len(sl) + data.extend(array("B", struct.pack(fmt, *sl))) + + else: + # Pack into bytes + assert self.bitdepth < 8 + # samples per byte + spb = int(8 / self.bitdepth) + + def extend(sl): + a = array("B", sl) + # Adding padding bytes so we can group into a whole + # number of spb-tuples. + l = float(len(a)) + extra = math.ceil(l / float(spb)) * spb - l + a.extend([0] * int(extra)) + # Pack into bytes + l = group(a, spb) + l = map(lambda e: reduce(lambda x, y: (x << self.bitdepth) + y, e), l) + data.extend(l) + + if self.rescale: + oldextend = extend + factor = float(2 ** self.rescale[1] - 1) / float(2 ** self.rescale[0] - 1) + + def extend(sl): + oldextend(map(lambda x: int(round(factor * x)), sl)) + + # Build the first row, testing mostly to see if we need to + # changed the extend function to cope with NumPy integer types + # (they cause our ordinary definition of extend to fail, so we + # wrap it). See + # http://code.google.com/p/pypng/issues/detail?id=44 + enumrows = enumerate(rows) + del rows + + # First row's filter type. + data.append(0) + # :todo: Certain exceptions in the call to ``.next()`` or the + # following try would indicate no row data supplied. + # Should catch. + i, row = next(enumrows) + try: + # If this fails... + extend(row) + except: + # ... try a version that converts the values to int first. + # Not only does this work for the (slightly broken) NumPy + # types, there are probably lots of other, unknown, "nearly" + # int types it works for. + def wrapmapint(f): + return lambda sl: f(map(int, sl)) + + extend = wrapmapint(extend) + del wrapmapint + extend(row) + + for i, row in enumrows: + # Add "None" filter type. Currently, it's essential that + # this filter type be used for every scanline as we do not + # mark the first row of a reduced pass image; that means we + # could accidentally compute the wrong filtered scanline if + # we used "up", "average", or "paeth" on such a line. + data.append(0) + extend(row) + if len(data) > self.chunk_limit: + compressed = compressor.compress(tostring(data)) + if len(compressed): + # print >> sys.stderr, len(data), len(compressed) + write_chunk(outfile, "IDAT", compressed) + # Because of our very witty definition of ``extend``, + # above, we must re-use the same ``data`` object. Hence + # we use ``del`` to empty this one, rather than create a + # fresh one (which would be my natural FP instinct). + del data[:] + if len(data): + compressed = compressor.compress(tostring(data)) + else: + compressed = "" + flushed = compressor.flush() + if len(compressed) or len(flushed): + # print >> sys.stderr, len(data), len(compressed), len(flushed) + write_chunk(outfile, "IDAT", compressed + flushed) + # http://www.w3.org/TR/PNG/#11IEND + write_chunk(outfile, "IEND") + return i + 1 + + def write_array(self, outfile, pixels): + """ + Write an array in flat row flat pixel format as a PNG file on + the output file. See also :meth:`write` method. + """ + + if self.interlace: + self.write_passes(outfile, self.array_scanlines_interlace(pixels)) + else: + self.write_passes(outfile, self.array_scanlines(pixels)) + + def write_packed(self, outfile, rows): + """ + Write PNG file to `outfile`. The pixel data comes from `rows` + which should be in boxed row packed format. Each row should be + a sequence of packed bytes. + + Technically, this method does work for interlaced images but it + is best avoided. For interlaced images, the rows should be + presented in the order that they appear in the file. + + This method should not be used when the source image bit depth + is not one naturally supported by PNG; the bit depth should be + 1, 2, 4, 8, or 16. + """ + + if self.rescale: + raise Error( + "write_packed method not suitable for bit depth %d" % self.rescale[0] + ) + return self.write_passes(outfile, rows, packed=True) + + def convert_pnm(self, infile, outfile): + """ + Convert a PNM file containing raw pixel data into a PNG file + with the parameters set in the writer object. Works for + (binary) PGM, PPM, and PAM formats. + """ + + if self.interlace: + pixels = array("B") + pixels.fromfile( + infile, + (self.bitdepth / 8) * self.color_planes * self.width * self.height, + ) + self.write_passes(outfile, self.array_scanlines_interlace(pixels)) + else: + self.write_passes(outfile, self.file_scanlines(infile)) + + def convert_ppm_and_pgm(self, ppmfile, pgmfile, outfile): + """ + Convert a PPM and PGM file containing raw pixel data into a + PNG outfile with the parameters set in the writer object. + """ + pixels = array("B") + pixels.fromfile( + ppmfile, (self.bitdepth / 8) * self.color_planes * self.width * self.height + ) + apixels = array("B") + apixels.fromfile(pgmfile, (self.bitdepth / 8) * self.width * self.height) + pixels = interleave_planes( + pixels, + apixels, + (self.bitdepth / 8) * self.color_planes, + (self.bitdepth / 8), + ) + if self.interlace: + self.write_passes(outfile, self.array_scanlines_interlace(pixels)) + else: + self.write_passes(outfile, self.array_scanlines(pixels)) + + def file_scanlines(self, infile): + """ + Generates boxed rows in flat pixel format, from the input file + `infile`. It assumes that the input file is in a "Netpbm-like" + binary format, and is positioned at the beginning of the first + pixel. The number of pixels to read is taken from the image + dimensions (`width`, `height`, `planes`) and the number of bytes + per value is implied by the image `bitdepth`. + """ + + # Values per row + vpr = self.width * self.planes + row_bytes = vpr + if self.bitdepth > 8: + assert self.bitdepth == 16 + row_bytes *= 2 + fmt = ">%dH" % vpr + + def line(): + return array("H", struct.unpack(fmt, infile.read(row_bytes))) + + else: + + def line(): + scanline = array("B", infile.read(row_bytes)) + return scanline + + for y in range(self.height): + yield line() + + def array_scanlines(self, pixels): + """ + Generates boxed rows (flat pixels) from flat rows (flat pixels) + in an array. + """ + + # Values per row + vpr = self.width * self.planes + stop = 0 + for y in range(self.height): + start = stop + stop = start + vpr + yield pixels[start:stop] + + def array_scanlines_interlace(self, pixels): + """ + Generator for interlaced scanlines from an array. `pixels` is + the full source image in flat row flat pixel format. The + generator yields each scanline of the reduced passes in turn, in + boxed row flat pixel format. + """ + + # http://www.w3.org/TR/PNG/#8InterlaceMethods + # Array type. + fmt = "BH"[self.bitdepth > 8] + # Value per row + vpr = self.width * self.planes + for xstart, ystart, xstep, ystep in _adam7: + if xstart >= self.width: + continue + # Pixels per row (of reduced image) + ppr = int(math.ceil((self.width - xstart) / float(xstep))) + # number of values in reduced image row. + row_len = ppr * self.planes + for y in range(ystart, self.height, ystep): + if xstep == 1: + offset = y * vpr + yield pixels[offset : offset + vpr] + else: + row = array(fmt) + # There's no easier way to set the length of an array + row.extend(pixels[0:row_len]) + offset = y * vpr + xstart * self.planes + end_offset = (y + 1) * vpr + skip = self.planes * xstep + for i in range(self.planes): + row[i :: self.planes] = pixels[offset + i : end_offset : skip] + yield row + + +def write_chunk(outfile, tag, data=strtobytes("")): + """ + Write a PNG chunk to the output file, including length and + checksum. + """ + + # http://www.w3.org/TR/PNG/#5Chunk-layout + outfile.write(struct.pack("!I", len(data))) + tag = strtobytes(tag) + outfile.write(tag) + outfile.write(data) + checksum = zlib.crc32(tag) + checksum = zlib.crc32(data, checksum) + checksum &= 2 ** 32 - 1 + outfile.write(struct.pack("!I", checksum)) + + +def write_chunks(out, chunks): + """Create a PNG file by writing out the chunks.""" + + out.write(_signature) + for chunk in chunks: + write_chunk(out, *chunk) + + +def filter_scanline(type, line, fo, prev=None): + """Apply a scanline filter to a scanline. `type` specifies the + filter type (0 to 4); `line` specifies the current (unfiltered) + scanline as a sequence of bytes; `prev` specifies the previous + (unfiltered) scanline as a sequence of bytes. `fo` specifies the + filter offset; normally this is size of a pixel in bytes (the number + of bytes per sample times the number of channels), but when this is + < 1 (for bit depths < 8) then the filter offset is 1. + """ + + assert 0 <= type < 5 + + # The output array. Which, pathetically, we extend one-byte at a + # time (fortunately this is linear). + out = array("B", [type]) + + def sub(): + ai = -fo + for x in line: + if ai >= 0: + x = (x - line[ai]) & 0xFF + out.append(x) + ai += 1 + + def up(): + for i, x in enumerate(line): + x = (x - prev[i]) & 0xFF + out.append(x) + + def average(): + ai = -fo + for i, x in enumerate(line): + if ai >= 0: + x = (x - ((line[ai] + prev[i]) >> 1)) & 0xFF + else: + x = (x - (prev[i] >> 1)) & 0xFF + out.append(x) + ai += 1 + + def paeth(): + # http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth + ai = -fo # also used for ci + for i, x in enumerate(line): + a = 0 + b = prev[i] + c = 0 + + if ai >= 0: + a = line[ai] + c = prev[ai] + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + Pr = a + elif pb <= pc: + Pr = b + else: + Pr = c + + x = (x - Pr) & 0xFF + out.append(x) + ai += 1 + + if not prev: + # We're on the first line. Some of the filters can be reduced + # to simpler cases which makes handling the line "off the top" + # of the image simpler. "up" becomes "none"; "paeth" becomes + # "left" (non-trivial, but true). "average" needs to be handled + # specially. + if type == 2: # "up" + return line # type = 0 + elif type == 3: + prev = [0] * len(line) + elif type == 4: # "paeth" + type = 1 + if type == 0: + out.extend(line) + elif type == 1: + sub() + elif type == 2: + up() + elif type == 3: + average() + else: # type == 4 + paeth() + return out + + +def from_array(a, mode=None, info={}): + """Create a PNG :class:`Image` object from a 2- or 3-dimensional array. + One application of this function is easy PIL-style saving: + ``png.from_array(pixels, 'L').save('foo.png')``. + + .. note : + + The use of the term *3-dimensional* is for marketing purposes + only. It doesn't actually work. Please bear with us. Meanwhile + enjoy the complimentary snacks (on request) and please use a + 2-dimensional array. + + Unless they are specified using the *info* parameter, the PNG's + height and width are taken from the array size. For a 3 dimensional + array the first axis is the height; the second axis is the width; + and the third axis is the channel number. Thus an RGB image that is + 16 pixels high and 8 wide will use an array that is 16x8x3. For 2 + dimensional arrays the first axis is the height, but the second axis + is ``width*channels``, so an RGB image that is 16 pixels high and 8 + wide will use a 2-dimensional array that is 16x24 (each row will be + 8*3==24 sample values). + + *mode* is a string that specifies the image colour format in a + PIL-style mode. It can be: + + ``'L'`` + greyscale (1 channel) + ``'LA'`` + greyscale with alpha (2 channel) + ``'RGB'`` + colour image (3 channel) + ``'RGBA'`` + colour image with alpha (4 channel) + + The mode string can also specify the bit depth (overriding how this + function normally derives the bit depth, see below). Appending + ``';16'`` to the mode will cause the PNG to be 16 bits per channel; + any decimal from 1 to 16 can be used to specify the bit depth. + + When a 2-dimensional array is used *mode* determines how many + channels the image has, and so allows the width to be derived from + the second array dimension. + + The array is expected to be a ``numpy`` array, but it can be any + suitable Python sequence. For example, a list of lists can be used: + ``png.from_array([[0, 255, 0], [255, 0, 255]], 'L')``. The exact + rules are: ``len(a)`` gives the first dimension, height; + ``len(a[0])`` gives the second dimension; ``len(a[0][0])`` gives the + third dimension, unless an exception is raised in which case a + 2-dimensional array is assumed. It's slightly more complicated than + that because an iterator of rows can be used, and it all still + works. Using an iterator allows data to be streamed efficiently. + + The bit depth of the PNG is normally taken from the array element's + datatype (but if *mode* specifies a bitdepth then that is used + instead). The array element's datatype is determined in a way which + is supposed to work both for ``numpy`` arrays and for Python + ``array.array`` objects. A 1 byte datatype will give a bit depth of + 8, a 2 byte datatype will give a bit depth of 16. If the datatype + does not have an implicit size, for example it is a plain Python + list of lists, as above, then a default of 8 is used. + + The *info* parameter is a dictionary that can be used to specify + metadata (in the same style as the arguments to the + :class:``png.Writer`` class). For this function the keys that are + useful are: + + height + overrides the height derived from the array dimensions and allows + *a* to be an iterable. + width + overrides the width derived from the array dimensions. + bitdepth + overrides the bit depth derived from the element datatype (but + must match *mode* if that also specifies a bit depth). + + Generally anything specified in the + *info* dictionary will override any implicit choices that this + function would otherwise make, but must match any explicit ones. + For example, if the *info* dictionary has a ``greyscale`` key then + this must be true when mode is ``'L'`` or ``'LA'`` and false when + mode is ``'RGB'`` or ``'RGBA'``. + """ + + # We abuse the *info* parameter by modifying it. Take a copy here. + # (Also typechecks *info* to some extent). + info = dict(info) + + # Syntax check mode string. + bitdepth = None + try: + mode = mode.split(";") + if len(mode) not in (1, 2): + raise Error() + if mode[0] not in ("L", "LA", "RGB", "RGBA"): + raise Error() + if len(mode) == 2: + try: + bitdepth = int(mode[1]) + except: + raise Error() + except Error: + raise Error("mode string should be 'RGB' or 'L;16' or similar.") + mode = mode[0] + + # Get bitdepth from *mode* if possible. + if bitdepth: + if info.get("bitdepth") and bitdepth != info["bitdepth"]: + raise Error( + "mode bitdepth (%d) should match info bitdepth (%d)." + % (bitdepth, info["bitdepth"]) + ) + info["bitdepth"] = bitdepth + + # Fill in and/or check entries in *info*. + # Dimensions. + if "size" in info: + # Check width, height, size all match where used. + for dimension, axis in [("width", 0), ("height", 1)]: + if dimension in info: + if info[dimension] != info["size"][axis]: + raise Error( + "info[%r] should match info['size'][%r]." % (dimension, axis) + ) + info["width"], info["height"] = info["size"] + if "height" not in info: + try: + l = len(a) + except: + raise Error("len(a) does not work, supply info['height'] instead.") + info["height"] = l + # Colour format. + if "greyscale" in info: + if bool(info["greyscale"]) != ("L" in mode): + raise Error("info['greyscale'] should match mode.") + info["greyscale"] = "L" in mode + if "alpha" in info: + if bool(info["alpha"]) != ("A" in mode): + raise Error("info['alpha'] should match mode.") + info["alpha"] = "A" in mode + + planes = len(mode) + if "planes" in info: + if info["planes"] != planes: + raise Error("info['planes'] should match mode.") + + # In order to work out whether we the array is 2D or 3D we need its + # first row, which requires that we take a copy of its iterator. + # We may also need the first row to derive width and bitdepth. + a, t = itertools.tee(a) + row = next(t) + del t + try: + row[0][0] + threed = True + testelement = row[0] + except: + threed = False + testelement = row + if "width" not in info: + if threed: + width = len(row) + else: + width = len(row) // planes + info["width"] = width + + # Not implemented yet + assert not threed + + if "bitdepth" not in info: + try: + dtype = testelement.dtype + # goto the "else:" clause. Sorry. + except: + try: + # Try a Python array.array. + bitdepth = 8 * testelement.itemsize + except: + # We can't determine it from the array element's + # datatype, use a default of 8. + bitdepth = 8 + else: + # If we got here without exception, we now assume that + # the array is a numpy array. + if dtype.kind == "b": + bitdepth = 1 + else: + bitdepth = 8 * dtype.itemsize + info["bitdepth"] = bitdepth + + for thing in "width height bitdepth greyscale alpha".split(): + assert thing in info + return Image(a, info) + + +# So that refugee's from PIL feel more at home. Not documented. +fromarray = from_array + + +class Image: + """A PNG image. + You can create an :class:`Image` object from an array of pixels by calling + :meth:`png.from_array`. It can be saved to disk with the + :meth:`save` method.""" + + def __init__(self, rows, info): + """ + .. note :: + + The constructor is not public. Please do not call it. + """ + + self.rows = rows + self.info = info + + def save(self, file): + """Save the image to *file*. If *file* looks like an open file + descriptor then it is used, otherwise it is treated as a + filename and a fresh file is opened. + + In general, you can only call this method once; after it has + been called the first time and the PNG image has been saved, the + source data will have been streamed, and cannot be streamed + again. + """ + + w = Writer(**self.info) + + try: + file.write + + def close(): + pass + + except: + file = open(file, "wb") + + def close(): + file.close() + + try: + w.write(file, self.rows) + finally: + close() + + +class _readable: + """ + A simple file-like interface for strings and arrays. + """ + + def __init__(self, buf): + self.buf = buf + self.offset = 0 + + def read(self, n): + r = self.buf[self.offset : self.offset + n] + if isarray(r): + r = tostring(r) + self.offset += n + return r + + +class Reader: + """ + PNG decoder in pure Python. + """ + + def __init__(self, _guess=None, **kw): + """ + Create a PNG decoder object. + + The constructor expects exactly one keyword argument. If you + supply a positional argument instead, it will guess the input + type. You can choose among the following keyword arguments: + + filename + Name of input file (a PNG file). + file + A file-like object (object with a read() method). + bytes + ``array`` or ``string`` with PNG data. + + """ + if (_guess is not None and len(kw) != 0) or (_guess is None and len(kw) != 1): + raise TypeError("Reader() takes exactly 1 argument") + + # Will be the first 8 bytes, later on. See validate_signature. + self.signature = None + self.transparent = None + # A pair of (len,type) if a chunk has been read but its data and + # checksum have not (in other words the file position is just + # past the 4 bytes that specify the chunk type). See preamble + # method for how this is used. + self.atchunk = None + + if _guess is not None: + if isarray(_guess): + kw["bytes"] = _guess + elif isinstance(_guess, str): + kw["filename"] = _guess + elif isinstance(_guess, file): + kw["file"] = _guess + + if "filename" in kw: + self.file = open(kw["filename"], "rb") + elif "file" in kw: + self.file = kw["file"] + elif "bytes" in kw: + self.file = _readable(kw["bytes"]) + else: + raise TypeError("expecting filename, file or bytes array") + + def chunk(self, seek=None): + """ + Read the next PNG chunk from the input file; returns a + (*type*,*data*) tuple. *type* is the chunk's type as a string + (all PNG chunk types are 4 characters long). *data* is the + chunk's data content, as a string. + + If the optional `seek` argument is + specified then it will keep reading chunks until it either runs + out of file or finds the type specified by the argument. Note + that in general the order of chunks in PNGs is unspecified, so + using `seek` can cause you to miss chunks. + """ + + self.validate_signature() + + while True: + # http://www.w3.org/TR/PNG/#5Chunk-layout + if not self.atchunk: + self.atchunk = self.chunklentype() + length, type = self.atchunk + self.atchunk = None + data = self.file.read(length) + if len(data) != length: + raise ChunkError( + "Chunk %s too short for required %i octets." % (type, length) + ) + checksum = self.file.read(4) + if len(checksum) != 4: + raise ValueError("Chunk %s too short for checksum.", tag) + if seek and type != seek: + continue + verify = zlib.crc32(strtobytes(type)) + verify = zlib.crc32(data, verify) + # Whether the output from zlib.crc32 is signed or not varies + # according to hideous implementation details, see + # http://bugs.python.org/issue1202 . + # We coerce it to be positive here (in a way which works on + # Python 2.3 and older). + verify &= 2 ** 32 - 1 + verify = struct.pack("!I", verify) + if checksum != verify: + # print repr(checksum) + (a,) = struct.unpack("!I", checksum) + (b,) = struct.unpack("!I", verify) + raise ChunkError( + "Checksum error in %s chunk: 0x%08X != 0x%08X." % (type, a, b) + ) + return type, data + + def chunks(self): + """Return an iterator that will yield each chunk as a + (*chunktype*, *content*) pair. + """ + + while True: + t, v = self.chunk() + yield t, v + if t == "IEND": + break + + def undo_filter(self, filter_type, scanline, previous): + """Undo the filter for a scanline. `scanline` is a sequence of + bytes that does not include the initial filter type byte. + `previous` is decoded previous scanline (for straightlaced + images this is the previous pixel row, but for interlaced + images, it is the previous scanline in the reduced image, which + in general is not the previous pixel row in the final image). + When there is no previous scanline (the first row of a + straightlaced image, or the first row in one of the passes in an + interlaced image), then this argument should be ``None``. + + The scanline will have the effects of filtering removed, and the + result will be returned as a fresh sequence of bytes. + """ + + # :todo: Would it be better to update scanline in place? + + # Create the result byte array. It seems that the best way to + # create the array to be the right size is to copy from an + # existing sequence. *sigh* + # If we fill the result with scanline, then this allows a + # micro-optimisation in the "null" and "sub" cases. + result = array("B", scanline) + + if filter_type == 0: + # And here, we _rely_ on filling the result with scanline, + # above. + return result + + if filter_type not in (1, 2, 3, 4): + raise FormatError( + "Invalid PNG Filter Type." + " See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ." + ) + + # Filter unit. The stride from one pixel to the corresponding + # byte from the previous previous. Normally this is the pixel + # size in bytes, but when this is smaller than 1, the previous + # byte is used instead. + fu = max(1, self.psize) + + # For the first line of a pass, synthesize a dummy previous + # line. An alternative approach would be to observe that on the + # first line 'up' is the same as 'null', 'paeth' is the same + # as 'sub', with only 'average' requiring any special case. + if not previous: + previous = array("B", [0] * len(scanline)) + + def sub(): + """Undo sub filter.""" + + ai = 0 + # Loops starts at index fu. Observe that the initial part + # of the result is already filled in correctly with + # scanline. + for i in range(fu, len(result)): + x = scanline[i] + a = result[ai] + result[i] = (x + a) & 0xFF + ai += 1 + + def up(): + """Undo up filter.""" + for i in range(len(result)): # pylint: disable=consider-using-enumerate + x = scanline[i] + b = previous[i] + result[i] = (x + b) & 0xFF + + def average(): + """Undo average filter.""" + + ai = -fu + for i in range(len(result)): # pylint: disable=consider-using-enumerate + x = scanline[i] + if ai < 0: + a = 0 + else: + a = result[ai] + b = previous[i] + result[i] = (x + ((a + b) >> 1)) & 0xFF + ai += 1 + + def paeth(): + """Undo Paeth filter.""" + + # Also used for ci. + ai = -fu + for i in range(len(result)): # pylint: disable=consider-using-enumerate + x = scanline[i] + if ai < 0: + a = c = 0 + else: + a = result[ai] + c = previous[ai] + b = previous[i] + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + pr = a + elif pb <= pc: + pr = b + else: + pr = c + result[i] = (x + pr) & 0xFF + ai += 1 + + # Call appropriate filter algorithm. Note that 0 has already + # been dealt with. + (None, sub, up, average, paeth)[filter_type]() + return result + + def deinterlace(self, raw): + """ + Read raw pixel data, undo filters, deinterlace, and flatten. + Return in flat row flat pixel format. + """ + + # print >> sys.stderr, ("Reading interlaced, w=%s, r=%s, planes=%s," + + # " bpp=%s") % (self.width, self.height, self.planes, self.bps) + # Values per row (of the target image) + vpr = self.width * self.planes + + # Make a result array, and make it big enough. Interleaving + # writes to the output array randomly (well, not quite), so the + # entire output array must be in memory. + fmt = "BH"[self.bitdepth > 8] + a = array(fmt, [0] * vpr * self.height) + source_offset = 0 + + for xstart, ystart, xstep, ystep in _adam7: + # print >> sys.stderr, "Adam7: start=%s,%s step=%s,%s" % ( + # xstart, ystart, xstep, ystep) + if xstart >= self.width: + continue + # The previous (reconstructed) scanline. None at the + # beginning of a pass to indicate that there is no previous + # line. + recon = None + # Pixels per row (reduced pass image) + ppr = int(math.ceil((self.width - xstart) / float(xstep))) + # Row size in bytes for this pass. + row_size = int(math.ceil(self.psize * ppr)) + for y in range(ystart, self.height, ystep): + filter_type = raw[source_offset] + source_offset += 1 + scanline = raw[source_offset : source_offset + row_size] + source_offset += row_size + recon = self.undo_filter(filter_type, scanline, recon) + # Convert so that there is one element per pixel value + flat = self.serialtoflat(recon, ppr) + if xstep == 1: + assert xstart == 0 + offset = y * vpr + a[offset : offset + vpr] = flat + else: + offset = y * vpr + xstart * self.planes + end_offset = (y + 1) * vpr + skip = self.planes * xstep + for i in range(self.planes): + a[offset + i : end_offset : skip] = flat[i :: self.planes] + return a + + def iterboxed(self, rows): + """Iterator that yields each scanline in boxed row flat pixel + format. `rows` should be an iterator that yields the bytes of + each row in turn. + """ + + def asvalues(raw): + """Convert a row of raw bytes into a flat row. Result may + or may not share with argument""" + + if self.bitdepth == 8: + return raw + if self.bitdepth == 16: + raw = tostring(raw) + return array("H", struct.unpack("!%dH" % (len(raw) // 2), raw)) + assert self.bitdepth < 8 + width = self.width + # Samples per byte + spb = 8 // self.bitdepth + out = array("B") + mask = 2 ** self.bitdepth - 1 + shifts = map(self.bitdepth.__mul__, reversed(range(spb))) + for o in raw: + out.extend(map(lambda i: mask & (o >> i), shifts)) + return out[:width] + + return map(asvalues, rows) + + def serialtoflat(self, bytes, width=None): + """Convert serial format (byte stream) pixel data to flat row + flat pixel. + """ + + if self.bitdepth == 8: + return bytes + if self.bitdepth == 16: + bytes = tostring(bytes) + return array("H", struct.unpack("!%dH" % (len(bytes) // 2), bytes)) + assert self.bitdepth < 8 + if width is None: + width = self.width + # Samples per byte + spb = 8 // self.bitdepth + out = array("B") + mask = 2 ** self.bitdepth - 1 + shifts = map(self.bitdepth.__mul__, reversed(range(spb))) + l = width + for o in bytes: + out.extend([(mask & (o >> s)) for s in shifts][:l]) + l -= spb + if l <= 0: + l = width + return out + + def iterstraight(self, raw): + """Iterator that undoes the effect of filtering, and yields each + row in serialised format (as a sequence of bytes). Assumes input + is straightlaced. `raw` should be an iterable that yields the + raw bytes in chunks of arbitrary size.""" + + # length of row, in bytes + rb = self.row_bytes + a = array("B") + # The previous (reconstructed) scanline. None indicates first + # line of image. + recon = None + for some in raw: + a.extend(some) + while len(a) >= rb + 1: + filter_type = a[0] + scanline = a[1 : rb + 1] + del a[: rb + 1] + recon = self.undo_filter(filter_type, scanline, recon) + yield recon + if len(a) != 0: + # :file:format We get here with a file format error: when the + # available bytes (after decompressing) do not pack into exact + # rows. + raise FormatError("Wrong size for decompressed IDAT chunk.") + assert len(a) == 0 + + def validate_signature(self): + """If signature (header) has not been read then read and + validate it; otherwise do nothing. + """ + + if self.signature: + return + self.signature = self.file.read(8) + if self.signature != _signature: + raise FormatError("PNG file has invalid signature.") + + def preamble(self): + """ + Extract the image metadata by reading the initial part of the PNG + file up to the start of the ``IDAT`` chunk. All the chunks that + precede the ``IDAT`` chunk are read and either processed for + metadata or discarded. + """ + + self.validate_signature() + + while True: + if not self.atchunk: + self.atchunk = self.chunklentype() + if self.atchunk is None: + raise FormatError("This PNG file has no IDAT chunks.") + if self.atchunk[1] == "IDAT": + return + self.process_chunk() + + def chunklentype(self): + """Reads just enough of the input to determine the next + chunk's length and type, returned as a (*length*, *type*) pair + where *type* is a string. If there are no more chunks, ``None`` + is returned. + """ + + x = self.file.read(8) + if not x: + return None + if len(x) != 8: + raise FormatError("End of file whilst reading chunk length and type.") + length, type = struct.unpack("!I4s", x) + type = bytestostr(type) + if length > 2 ** 31 - 1: + raise FormatError("Chunk %s is too large: %d." % (type, length)) + return length, type + + def process_chunk(self): + """Process the next chunk and its data. This only processes the + following chunk types, all others are ignored: ``IHDR``, + ``PLTE``, ``bKGD``, ``tRNS``, ``gAMA``, ``sBIT``. + """ + + type, data = self.chunk() + if type == "IHDR": + # http://www.w3.org/TR/PNG/#11IHDR + if len(data) != 13: + raise FormatError("IHDR chunk has incorrect length.") + ( + self.width, + self.height, + self.bitdepth, + self.color_type, + self.compression, + self.filter, + self.interlace, + ) = struct.unpack("!2I5B", data) + + # Check that the header specifies only valid combinations. + if self.bitdepth not in (1, 2, 4, 8, 16): + raise Error("invalid bit depth %d" % self.bitdepth) + if self.color_type not in (0, 2, 3, 4, 6): + raise Error("invalid colour type %d" % self.color_type) + # Check indexed (palettized) images have 8 or fewer bits + # per pixel; check only indexed or greyscale images have + # fewer than 8 bits per pixel. + if (self.color_type & 1 and self.bitdepth > 8) or ( + self.bitdepth < 8 and self.color_type not in (0, 3) + ): + raise FormatError( + "Illegal combination of bit depth (%d)" + " and colour type (%d)." + " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." + % (self.bitdepth, self.color_type) + ) + if self.compression != 0: + raise Error("unknown compression method %d" % self.compression) + if self.filter != 0: + raise FormatError( + "Unknown filter method %d," + " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ." + % self.filter + ) + if self.interlace not in (0, 1): + raise FormatError( + "Unknown interlace method %d," + " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ." + % self.interlace + ) + + # Derived values + # http://www.w3.org/TR/PNG/#6Colour-values + colormap = bool(self.color_type & 1) + greyscale = not (self.color_type & 2) + alpha = bool(self.color_type & 4) + color_planes = (3, 1)[greyscale or colormap] + planes = color_planes + alpha + + self.colormap = colormap + self.greyscale = greyscale + self.alpha = alpha + self.color_planes = color_planes + self.planes = planes + self.psize = float(self.bitdepth) / float(8) * planes + if int(self.psize) == self.psize: + self.psize = int(self.psize) + self.row_bytes = int(math.ceil(self.width * self.psize)) + # Stores PLTE chunk if present, and is used to check + # chunk ordering constraints. + self.plte = None + # Stores tRNS chunk if present, and is used to check chunk + # ordering constraints. + self.trns = None + # Stores sbit chunk if present. + self.sbit = None + elif type == "PLTE": + # http://www.w3.org/TR/PNG/#11PLTE + if self.plte: + warnings.warn("Multiple PLTE chunks present.") + self.plte = data + if len(data) % 3 != 0: + raise FormatError("PLTE chunk's length should be a multiple of 3.") + if len(data) > (2 ** self.bitdepth) * 3: + raise FormatError("PLTE chunk is too long.") + if len(data) == 0: + raise FormatError("Empty PLTE is not allowed.") + elif type == "bKGD": + try: + if self.colormap: + if not self.plte: + warnings.warn("PLTE chunk is required before bKGD chunk.") + self.background = struct.unpack("B", data) + else: + self.background = struct.unpack("!%dH" % self.color_planes, data) + except struct.error: + raise FormatError("bKGD chunk has incorrect length.") + elif type == "tRNS": + # http://www.w3.org/TR/PNG/#11tRNS + self.trns = data + if self.colormap: + if not self.plte: + warnings.warn("PLTE chunk is required before tRNS chunk.") + else: + if len(data) > len(self.plte) / 3: + # Was warning, but promoted to Error as it + # would otherwise cause pain later on. + raise FormatError("tRNS chunk is too long.") + else: + if self.alpha: + raise FormatError( + "tRNS chunk is not valid with colour type %d." % self.color_type + ) + try: + self.transparent = struct.unpack("!%dH" % self.color_planes, data) + except struct.error: + raise FormatError("tRNS chunk has incorrect length.") + elif type == "gAMA": + try: + self.gamma = struct.unpack("!L", data)[0] / 100000.0 + except struct.error: + raise FormatError("gAMA chunk has incorrect length.") + elif type == "sBIT": + self.sbit = data + if ( + self.colormap + and len(data) != 3 + or not self.colormap + and len(data) != self.planes + ): + raise FormatError("sBIT chunk has incorrect length.") + + def read(self): + """ + Read the PNG file and decode it. Returns (`width`, `height`, + `pixels`, `metadata`). + + May use excessive memory. + + `pixels` are returned in boxed row flat pixel format. + """ + + def iteridat(): + """Iterator that yields all the ``IDAT`` chunks as strings.""" + while True: + try: + type, data = self.chunk() + except ValueError as e: + raise ChunkError(e.args[0]) + if type == "IEND": + # http://www.w3.org/TR/PNG/#11IEND + break + if type != "IDAT": + continue + # type == 'IDAT' + # http://www.w3.org/TR/PNG/#11IDAT + if self.colormap and not self.plte: + warnings.warn("PLTE chunk is required before IDAT chunk") + yield data + + def iterdecomp(idat): + """Iterator that yields decompressed strings. `idat` should + be an iterator that yields the ``IDAT`` chunk data. + """ + + # Currently, with no max_length parameter to decompress, this + # routine will do one yield per IDAT chunk. So not very + # incremental. + d = zlib.decompressobj() + # Each IDAT chunk is passed to the decompressor, then any + # remaining state is decompressed out. + for data in idat: + # :todo: add a max_length argument here to limit output + # size. + yield array("B", d.decompress(data)) + yield array("B", d.flush()) + + self.preamble() + raw = iterdecomp(iteridat()) + + if self.interlace: + raw = array("B", itertools.chain(*raw)) + arraycode = "BH"[self.bitdepth > 8] + # Like :meth:`group` but producing an array.array object for + # each row. + pixels = map( + lambda *row: array(arraycode, row), + *[iter(self.deinterlace(raw))] * self.width * self.planes + ) + else: + pixels = self.iterboxed(self.iterstraight(raw)) + meta = dict() + for attr in "greyscale alpha planes bitdepth interlace".split(): + meta[attr] = getattr(self, attr) + meta["size"] = (self.width, self.height) + for attr in "gamma transparent background".split(): + a = getattr(self, attr, None) + if a is not None: + meta[attr] = a + return self.width, self.height, pixels, meta + + def read_flat(self): + """ + Read a PNG file and decode it into flat row flat pixel format. + Returns (*width*, *height*, *pixels*, *metadata*). + + May use excessive memory. + + `pixels` are returned in flat row flat pixel format. + + See also the :meth:`read` method which returns pixels in the + more stream-friendly boxed row flat pixel format. + """ + + x, y, pixel, meta = self.read() + arraycode = "BH"[meta["bitdepth"] > 8] + pixel = array(arraycode, itertools.chain(*pixel)) + return x, y, pixel, meta + + def palette(self, alpha="natural"): + """Returns a palette that is a sequence of 3-tuples or 4-tuples, + synthesizing it from the ``PLTE`` and ``tRNS`` chunks. These + chunks should have already been processed (for example, by + calling the :meth:`preamble` method). All the tuples are the + same size: 3-tuples if there is no ``tRNS`` chunk, 4-tuples when + there is a ``tRNS`` chunk. Assumes that the image is colour type + 3 and therefore a ``PLTE`` chunk is required. + + If the `alpha` argument is ``'force'`` then an alpha channel is + always added, forcing the result to be a sequence of 4-tuples. + """ + + if not self.plte: + raise FormatError("Required PLTE chunk is missing in colour type 3 image.") + plte = group(array("B", self.plte), 3) + if self.trns or alpha == "force": + trns = array("B", self.trns or "") + trns.extend([255] * (len(plte) - len(trns))) + plte = map(operator.add, plte, group(trns, 1)) + return plte + + def asDirect(self): + """Returns the image data as a direct representation of an + ``x * y * planes`` array. This method is intended to remove the + need for callers to deal with palettes and transparency + themselves. Images with a palette (colour type 3) + are converted to RGB or RGBA; images with transparency (a + ``tRNS`` chunk) are converted to LA or RGBA as appropriate. + When returned in this format the pixel values represent the + colour value directly without needing to refer to palettes or + transparency information. + + Like the :meth:`read` method this method returns a 4-tuple: + + (*width*, *height*, *pixels*, *meta*) + + This method normally returns pixel values with the bit depth + they have in the source image, but when the source PNG has an + ``sBIT`` chunk it is inspected and can reduce the bit depth of + the result pixels; pixel values will be reduced according to + the bit depth specified in the ``sBIT`` chunk (PNG nerds should + note a single result bit depth is used for all channels; the + maximum of the ones specified in the ``sBIT`` chunk. An RGB565 + image will be rescaled to 6-bit RGB666). + + The *meta* dictionary that is returned reflects the `direct` + format and not the original source image. For example, an RGB + source image with a ``tRNS`` chunk to represent a transparent + colour, will have ``planes=3`` and ``alpha=False`` for the + source image, but the *meta* dictionary returned by this method + will have ``planes=4`` and ``alpha=True`` because an alpha + channel is synthesized and added. + + *pixels* is the pixel data in boxed row flat pixel format (just + like the :meth:`read` method). + + All the other aspects of the image data are not changed. + """ + + self.preamble() + + # Simple case, no conversion necessary. + if not self.colormap and not self.trns and not self.sbit: + return self.read() + + x, y, pixels, meta = self.read() + + if self.colormap: + meta["colormap"] = False + meta["alpha"] = bool(self.trns) + meta["bitdepth"] = 8 + meta["planes"] = 3 + bool(self.trns) + plte = self.palette() + + def iterpal(pixels): + for row in pixels: + row = map(plte.__getitem__, row) + yield array("B", itertools.chain(*row)) + + pixels = iterpal(pixels) + elif self.trns: + # It would be nice if there was some reasonable way of doing + # this without generating a whole load of intermediate tuples. + # But tuples does seem like the easiest way, with no other way + # clearly much simpler or much faster. (Actually, the L to LA + # conversion could perhaps go faster (all those 1-tuples!), but + # I still wonder whether the code proliferation is worth it) + it = self.transparent + maxval = 2 ** meta["bitdepth"] - 1 + planes = meta["planes"] + meta["alpha"] = True + meta["planes"] += 1 + typecode = "BH"[meta["bitdepth"] > 8] + + def itertrns(pixels): + for row in pixels: + # For each row we group it into pixels, then form a + # characterisation vector that says whether each pixel + # is opaque or not. Then we convert True/False to + # 0/maxval (by multiplication), and add it as the extra + # channel. + row = group(row, planes) + opa = map(it.__ne__, row) + opa = map(maxval.__mul__, opa) + opa = zip(opa) # convert to 1-tuples + yield array(typecode, itertools.chain(*map(operator.add, row, opa))) + + pixels = itertrns(pixels) + targetbitdepth = None + if self.sbit: + sbit = struct.unpack("%dB" % len(self.sbit), self.sbit) + targetbitdepth = max(sbit) + if targetbitdepth > meta["bitdepth"]: + raise Error("sBIT chunk %r exceeds bitdepth %d" % (sbit, self.bitdepth)) + if min(sbit) <= 0: + raise Error("sBIT chunk %r has a 0-entry" % sbit) + if targetbitdepth == meta["bitdepth"]: + targetbitdepth = None + if targetbitdepth: + shift = meta["bitdepth"] - targetbitdepth + meta["bitdepth"] = targetbitdepth + + def itershift(pixels): + for row in pixels: + yield map(shift.__rrshift__, row) + + pixels = itershift(pixels) + return x, y, pixels, meta + + def asFloat(self, maxval=1.0): + """Return image pixels as per :meth:`asDirect` method, but scale + all pixel values to be floating point values between 0.0 and + *maxval*. + """ + + x, y, pixels, info = self.asDirect() + sourcemaxval = 2 ** info["bitdepth"] - 1 + del info["bitdepth"] + info["maxval"] = float(maxval) + factor = float(maxval) / float(sourcemaxval) + + def iterfloat(): + for row in pixels: + yield map(factor.__mul__, row) + + return x, y, iterfloat(), info + + def _as_rescale(self, get, targetbitdepth): + """Helper used by :meth:`asRGB8` and :meth:`asRGBA8`.""" + + width, height, pixels, meta = get() + maxval = 2 ** meta["bitdepth"] - 1 + targetmaxval = 2 ** targetbitdepth - 1 + factor = float(targetmaxval) / float(maxval) + meta["bitdepth"] = targetbitdepth + + def iterscale(): + for row in pixels: + yield map(lambda x: int(round(x * factor)), row) + + return width, height, iterscale(), meta + + def asRGB8(self): + """Return the image data as an RGB pixels with 8-bits per + sample. This is like the :meth:`asRGB` method except that + this method additionally rescales the values so that they + are all between 0 and 255 (8-bit). In the case where the + source image has a bit depth < 8 the transformation preserves + all the information; where the source image has bit depth + > 8, then rescaling to 8-bit values loses precision. No + dithering is performed. Like :meth:`asRGB`, an alpha channel + in the source image will raise an exception. + + This function returns a 4-tuple: + (*width*, *height*, *pixels*, *metadata*). + *width*, *height*, *metadata* are as per the :meth:`read` method. + + *pixels* is the pixel data in boxed row flat pixel format. + """ + + return self._as_rescale(self.asRGB, 8) + + def asRGBA8(self): + """Return the image data as RGBA pixels with 8-bits per + sample. This method is similar to :meth:`asRGB8` and + :meth:`asRGBA`: The result pixels have an alpha channel, *and* + values are rescaled to the range 0 to 255. The alpha channel is + synthesized if necessary (with a small speed penalty). + """ + + return self._as_rescale(self.asRGBA, 8) + + def asRGB(self): + """Return image as RGB pixels. RGB colour images are passed + through unchanged; greyscales are expanded into RGB + triplets (there is a small speed overhead for doing this). + + An alpha channel in the source image will raise an + exception. + + The return values are as for the :meth:`read` method + except that the *metadata* reflect the returned pixels, not the + source image. In particular, for this method + ``metadata['greyscale']`` will be ``False``. + """ + + width, height, pixels, meta = self.asDirect() + if meta["alpha"]: + raise Error("will not convert image with alpha channel to RGB") + if not meta["greyscale"]: + return width, height, pixels, meta + meta["greyscale"] = False + typecode = "BH"[meta["bitdepth"] > 8] + + def iterrgb(): + for row in pixels: + a = array(typecode, [0]) * 3 * width + for i in range(3): + a[i::3] = row + yield a + + return width, height, iterrgb(), meta + + def asRGBA(self): + """Return image as RGBA pixels. Greyscales are expanded into + RGB triplets; an alpha channel is synthesized if necessary. + The return values are as for the :meth:`read` method + except that the *metadata* reflect the returned pixels, not the + source image. In particular, for this method + ``metadata['greyscale']`` will be ``False``, and + ``metadata['alpha']`` will be ``True``. + """ + + width, height, pixels, meta = self.asDirect() + if meta["alpha"] and not meta["greyscale"]: + return width, height, pixels, meta + typecode = "BH"[meta["bitdepth"] > 8] + maxval = 2 ** meta["bitdepth"] - 1 + + def newarray(): + return array(typecode, [0]) * 4 * width + + if meta["alpha"] and meta["greyscale"]: + # LA to RGBA + def convert(): + for row in pixels: + # Create a fresh target row, then copy L channel + # into first three target channels, and A channel + # into fourth channel. + a = newarray() + for i in range(3): + a[i::4] = row[0::2] + a[3::4] = row[1::2] + yield a + + elif meta["greyscale"]: + # L to RGBA + def convert(): + for row in pixels: + a = newarray() + for i in range(3): + a[i::4] = row + a[3::4] = array(typecode, [maxval]) * width + yield a + + else: + assert not meta["alpha"] and not meta["greyscale"] + # RGB to RGBA + def convert(): + for row in pixels: + a = newarray() + for i in range(3): + a[i::4] = row[i::3] + a[3::4] = array(typecode, [maxval]) * width + yield a + + meta["alpha"] = True + meta["greyscale"] = False + return width, height, convert(), meta + + +# === Internal Test Support === + +# This section comprises the tests that are internally validated (as +# opposed to tests which produce output files that are externally +# validated). Primarily they are unittests. + +# Note that it is difficult to internally validate the results of +# writing a PNG file. The only thing we can do is read it back in +# again, which merely checks consistency, not that the PNG file we +# produce is valid. + +# Run the tests from the command line: +# python -c 'import png;png.test()' + +# (For an in-memory binary file IO object) We use BytesIO where +# available, otherwise we use StringIO, but name it BytesIO. +try: + from io import BytesIO +except: + from StringIO import StringIO as BytesIO +import tempfile +import unittest + + +def test(): + unittest.main(__name__) + + +def topngbytes(name, rows, x, y, **k): + """Convenience function for creating a PNG file "in memory" as a + string. Creates a :class:`Writer` instance using the keyword arguments, + then passes `rows` to its :meth:`Writer.write` method. The resulting + PNG file is returned as a string. `name` is used to identify the file for + debugging. + """ + + import os + + print(name) + f = BytesIO() + w = Writer(x, y, **k) + w.write(f, rows) + if os.environ.get("PYPNG_TEST_TMP"): + w = open(name, "wb") + w.write(f.getvalue()) + w.close() + return f.getvalue() + + +def testWithIO(inp, out, f): + """Calls the function `f` with ``sys.stdin`` changed to `inp` + and ``sys.stdout`` changed to `out`. They are restored when `f` + returns. This function returns whatever `f` returns. + """ + + import os + + try: + oldin, sys.stdin = sys.stdin, inp + oldout, sys.stdout = sys.stdout, out + x = f() + finally: + sys.stdin = oldin + sys.stdout = oldout + if os.environ.get("PYPNG_TEST_TMP") and hasattr(out, "getvalue"): + name = mycallersname() + if name: + w = open(name + ".png", "wb") + w.write(out.getvalue()) + w.close() + return x + + +def mycallersname(): + """Returns the name of the caller of the caller of this function + (hence the name of the caller of the function in which + "mycallersname()" textually appears). Returns None if this cannot + be determined.""" + + # http://docs.python.org/library/inspect.html#the-interpreter-stack + import inspect + + frame = inspect.currentframe() + if not frame: + return None + frame_, filename_, lineno_, funname, linelist_, listi_ = inspect.getouterframes( + frame + )[2] + return funname + + +def seqtobytes(s): + """Convert a sequence of integers to a *bytes* instance. Good for + plastering over Python 2 / Python 3 cracks. + """ + + return strtobytes("".join(chr(x) for x in s)) + + +class Test(unittest.TestCase): + # This member is used by the superclass. If we don't define a new + # class here then when we use self.assertRaises() and the PyPNG code + # raises an assertion then we get no proper traceback. I can't work + # out why, but defining a new class here means we get a proper + # traceback. + class failureException(Exception): + pass + + def helperLN(self, n): + mask = (1 << n) - 1 + # Use small chunk_limit so that multiple chunk writing is + # tested. Making it a test for Issue 20. + w = Writer(15, 17, greyscale=True, bitdepth=n, chunk_limit=99) + f = BytesIO() + w.write_array(f, array("B", map(mask.__and__, range(1, 256)))) + r = Reader(bytes=f.getvalue()) + x, y, pixels, meta = r.read() + self.assertEqual(x, 15) + self.assertEqual(y, 17) + self.assertEqual( + list(itertools.chain(*pixels)), map(mask.__and__, range(1, 256)) + ) + + def testL8(self): + return self.helperLN(8) + + def testL4(self): + return self.helperLN(4) + + def testL2(self): + "Also tests asRGB8." + w = Writer(1, 4, greyscale=True, bitdepth=2) + f = BytesIO() + w.write_array(f, array("B", range(4))) + r = Reader(bytes=f.getvalue()) + x, y, pixels, meta = r.asRGB8() + self.assertEqual(x, 1) + self.assertEqual(y, 4) + for i, row in enumerate(pixels): + self.assertEqual(len(row), 3) + self.assertEqual(list(row), [0x55 * i] * 3) + + def testP2(self): + "2-bit palette." + a = (255, 255, 255) + b = (200, 120, 120) + c = (50, 99, 50) + w = Writer(1, 4, bitdepth=2, palette=[a, b, c]) + f = BytesIO() + w.write_array(f, array("B", (0, 1, 1, 2))) + r = Reader(bytes=f.getvalue()) + x, y, pixels, meta = r.asRGB8() + self.assertEqual(x, 1) + self.assertEqual(y, 4) + self.assertEqual(list(pixels), map(list, [a, b, b, c])) + + def testPtrns(self): + "Test colour type 3 and tRNS chunk (and 4-bit palette)." + a = (50, 99, 50, 50) + b = (200, 120, 120, 80) + c = (255, 255, 255) + d = (200, 120, 120) + e = (50, 99, 50) + w = Writer(3, 3, bitdepth=4, palette=[a, b, c, d, e]) + f = BytesIO() + w.write_array(f, array("B", (4, 3, 2, 3, 2, 0, 2, 0, 1))) + r = Reader(bytes=f.getvalue()) + x, y, pixels, meta = r.asRGBA8() + self.assertEqual(x, 3) + self.assertEqual(y, 3) + c = c + (255,) + d = d + (255,) + e = e + (255,) + boxed = [(e, d, c), (d, c, a), (c, a, b)] + flat = map(lambda row: itertools.chain(*row), boxed) + self.assertEqual(map(list, pixels), map(list, flat)) + + def testRGBtoRGBA(self): + "asRGBA8() on colour type 2 source." "" + # Test for Issue 26 + r = Reader(bytes=_pngsuite["basn2c08"]) + x, y, pixels, meta = r.asRGBA8() + # Test the pixels at row 9 columns 0 and 1. + row9 = list(pixels)[9] + self.assertEqual(row9[0:8], [0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF]) + + def testLtoRGBA(self): + "asRGBA() on grey source." "" + # Test for Issue 60 + r = Reader(bytes=_pngsuite["basi0g08"]) + x, y, pixels, meta = r.asRGBA() + row9 = list(list(pixels)[9]) + self.assertEqual(row9[0:8], [222, 222, 222, 255, 221, 221, 221, 255]) + + def testCtrns(self): + "Test colour type 2 and tRNS chunk." + # Test for Issue 25 + r = Reader(bytes=_pngsuite["tbrn2c08"]) + x, y, pixels, meta = r.asRGBA8() + # I just happen to know that the first pixel is transparent. + # In particular it should be #7f7f7f00 + row0 = list(pixels)[0] + self.assertEqual(tuple(row0[0:4]), (0x7F, 0x7F, 0x7F, 0x00)) + + def testAdam7read(self): + """Adam7 interlace reading. + Specifically, test that for images in the PngSuite that + have both an interlaced and straightlaced pair that both + images from the pair produce the same array of pixels.""" + for candidate in _pngsuite: + if not candidate.startswith("basn"): + continue + candi = candidate.replace("n", "i") + if candi not in _pngsuite: + continue + print("adam7 read %s" % (candidate,)) + straight = Reader(bytes=_pngsuite[candidate]) + adam7 = Reader(bytes=_pngsuite[candi]) + # Just compare the pixels. Ignore x,y (because they're + # likely to be correct?); metadata is ignored because the + # "interlace" member differs. Lame. + straight = straight.read()[2] + adam7 = adam7.read()[2] + self.assertEqual(map(list, straight), map(list, adam7)) + + def testAdam7write(self): + """Adam7 interlace writing. + For each test image in the PngSuite, write an interlaced + and a straightlaced version. Decode both, and compare results. + """ + # Not such a great test, because the only way we can check what + # we have written is to read it back again. + + for name, bytes in _pngsuite.items(): + # Only certain colour types supported for this test. + if name[3:5] not in ["n0", "n2", "n4", "n6"]: + continue + it = Reader(bytes=bytes) + x, y, pixels, meta = it.read() + pngi = topngbytes( + "adam7wn" + name + ".png", + pixels, + x=x, + y=y, + bitdepth=it.bitdepth, + greyscale=it.greyscale, + alpha=it.alpha, + transparent=it.transparent, + interlace=False, + ) + x, y, ps, meta = Reader(bytes=pngi).read() + it = Reader(bytes=bytes) + x, y, pixels, meta = it.read() + pngs = topngbytes( + "adam7wi" + name + ".png", + pixels, + x=x, + y=y, + bitdepth=it.bitdepth, + greyscale=it.greyscale, + alpha=it.alpha, + transparent=it.transparent, + interlace=True, + ) + x, y, pi, meta = Reader(bytes=pngs).read() + self.assertEqual(map(list, ps), map(list, pi)) + + def testPGMin(self): + """Test that the command line tool can read PGM files.""" + + def do(): + return _main(["testPGMin"]) + + s = BytesIO() + s.write(strtobytes("P5 2 2 3\n")) + s.write(strtobytes("\x00\x01\x02\x03")) + s.flush() + s.seek(0) + o = BytesIO() + testWithIO(s, o, do) + r = Reader(bytes=o.getvalue()) + x, y, pixels, meta = r.read() + self.assertTrue(r.greyscale) + self.assertEqual(r.bitdepth, 2) + + def testPAMin(self): + """Test that the command line tool can read PAM file.""" + + def do(): + return _main(["testPAMin"]) + + s = BytesIO() + s.write( + strtobytes( + "P7\nWIDTH 3\nHEIGHT 1\nDEPTH 4\nMAXVAL 255\n" + "TUPLTYPE RGB_ALPHA\nENDHDR\n" + ) + ) + # The pixels in flat row flat pixel format + flat = [255, 0, 0, 255, 0, 255, 0, 120, 0, 0, 255, 30] + asbytes = seqtobytes(flat) + s.write(asbytes) + s.flush() + s.seek(0) + o = BytesIO() + testWithIO(s, o, do) + r = Reader(bytes=o.getvalue()) + x, y, pixels, meta = r.read() + self.assertTrue(r.alpha) + self.assertTrue(not r.greyscale) + self.assertEqual(list(itertools.chain(*pixels)), flat) + + def testLA4(self): + """Create an LA image with bitdepth 4.""" + bytes = topngbytes( + "la4.png", [[5, 12]], 1, 1, greyscale=True, alpha=True, bitdepth=4 + ) + sbit = Reader(bytes=bytes).chunk("sBIT")[1] + self.assertEqual(sbit, strtobytes("\x04\x04")) + + def testPNMsbit(self): + """Test that PNM files can generates sBIT chunk.""" + + def do(): + return _main(["testPNMsbit"]) + + s = BytesIO() + s.write(strtobytes("P6 8 1 1\n")) + for pixel in range(8): + s.write(struct.pack(" 255: + a = array("H") + else: + a = array("B") + fw = float(width) + fh = float(height) + pfun = test_patterns[pattern] + for y in range(height): + fy = float(y) / fh + for x in range(width): + a.append(int(round(pfun(float(x) / fw, fy) * maxval))) + return a + + def test_rgba(size=256, bitdepth=8, red="GTB", green="GLR", blue="RTL", alpha=None): + """ + Create a test image. Each channel is generated from the + specified pattern; any channel apart from red can be set to + None, which will cause it not to be in the image. It + is possible to create all PNG channel types (L, RGB, LA, RGBA), + as well as non PNG channel types (RGA, and so on). + """ + + i = test_pattern(size, size, bitdepth, red) + psize = 1 + for channel in (green, blue, alpha): + if channel: + c = test_pattern(size, size, bitdepth, channel) + i = interleave_planes(i, c, psize, 1) + psize += 1 + return i + + def pngsuite_image(name): + """ + Create a test image by reading an internal copy of the files + from the PngSuite. Returned in flat row flat pixel format. + """ + + if name not in _pngsuite: + raise NotImplementedError( + "cannot find PngSuite file %s (use -L for a list)" % name + ) + r = Reader(bytes=_pngsuite[name]) + w, h, pixels, meta = r.asDirect() + assert w == h + # LAn for n < 8 is a special case for which we need to rescale + # the data. + if meta["greyscale"] and meta["alpha"] and meta["bitdepth"] < 8: + factor = 255 // (2 ** meta["bitdepth"] - 1) + + def rescale(data): + for row in data: + yield map(factor.__mul__, row) + + pixels = rescale(pixels) + meta["bitdepth"] = 8 + arraycode = "BH"[meta["bitdepth"] > 8] + return w, array(arraycode, itertools.chain(*pixels)), meta + + # The body of test_suite() + size = 256 + if options.test_size: + size = options.test_size + options.bitdepth = options.test_depth + options.greyscale = bool(options.test_black) + + kwargs = {} + if options.test_red: + kwargs["red"] = options.test_red + if options.test_green: + kwargs["green"] = options.test_green + if options.test_blue: + kwargs["blue"] = options.test_blue + if options.test_alpha: + kwargs["alpha"] = options.test_alpha + if options.greyscale: + if options.test_red or options.test_green or options.test_blue: + raise ValueError( + "cannot specify colours (R, G, B) when greyscale image (black channel, K) is specified" + ) + kwargs["red"] = options.test_black + kwargs["green"] = None + kwargs["blue"] = None + options.alpha = bool(options.test_alpha) + if not args: + pixels = test_rgba(size, options.bitdepth, **kwargs) + else: + size, pixels, meta = pngsuite_image(args[0]) + for k in ["bitdepth", "alpha", "greyscale"]: + setattr(options, k, meta[k]) + + writer = Writer( + size, + size, + bitdepth=options.bitdepth, + transparent=options.transparent, + background=options.background, + gamma=options.gamma, + greyscale=options.greyscale, + alpha=options.alpha, + compression=options.compression, + interlace=options.interlace, + ) + writer.write_array(sys.stdout, pixels) + + +def read_pam_header(infile): + """ + Read (the rest of a) PAM header. `infile` should be positioned + immediately after the initial 'P7' line (at the beginning of the + second line). Returns are as for `read_pnm_header`. + """ + + # Unlike PBM, PGM, and PPM, we can read the header a line at a time. + header = dict() + while True: + l = infile.readline().strip() + if l == strtobytes("ENDHDR"): + break + if not l: + raise EOFError("PAM ended prematurely") + if l[0] == strtobytes("#"): + continue + l = l.split(None, 1) + if l[0] not in header: + header[l[0]] = l[1] + else: + header[l[0]] += strtobytes(" ") + l[1] + + required = ["WIDTH", "HEIGHT", "DEPTH", "MAXVAL"] + required = [strtobytes(x) for x in required] + WIDTH, HEIGHT, DEPTH, MAXVAL = required + present = [x for x in required if x in header] + if len(present) != len(required): + raise Error("PAM file must specify WIDTH, HEIGHT, DEPTH, and MAXVAL") + width = int(header[WIDTH]) + height = int(header[HEIGHT]) + depth = int(header[DEPTH]) + maxval = int(header[MAXVAL]) + if width <= 0 or height <= 0 or depth <= 0 or maxval <= 0: + raise Error("WIDTH, HEIGHT, DEPTH, MAXVAL must all be positive integers") + return "P7", width, height, depth, maxval + + +def read_pnm_header(infile, supported=("P5", "P6")): + """ + Read a PNM header, returning (format,width,height,depth,maxval). + `width` and `height` are in pixels. `depth` is the number of + channels in the image; for PBM and PGM it is synthesized as 1, for + PPM as 3; for PAM images it is read from the header. `maxval` is + synthesized (as 1) for PBM images. + """ + + # Generally, see http://netpbm.sourceforge.net/doc/ppm.html + # and http://netpbm.sourceforge.net/doc/pam.html + + supported = [strtobytes(x) for x in supported] + + # Technically 'P7' must be followed by a newline, so by using + # rstrip() we are being liberal in what we accept. I think this + # is acceptable. + type = infile.read(3).rstrip() + if type not in supported: + raise NotImplementedError("file format %s not supported" % type) + if type == strtobytes("P7"): + # PAM header parsing is completely different. + return read_pam_header(infile) + # Expected number of tokens in header (3 for P4, 4 for P6) + expected = 4 + pbm = ("P1", "P4") + if type in pbm: + expected = 3 + header = [type] + + # We have to read the rest of the header byte by byte because the + # final whitespace character (immediately following the MAXVAL in + # the case of P6) may not be a newline. Of course all PNM files in + # the wild use a newline at this point, so it's tempting to use + # readline; but it would be wrong. + def getc(): + c = infile.read(1) + if not c: + raise Error("premature EOF reading PNM header") + return c + + c = getc() + while True: + # Skip whitespace that precedes a token. + while c.isspace(): + c = getc() + # Skip comments. + while c == "#": + while c not in "\n\r": + c = getc() + if not c.isdigit(): + raise Error("unexpected character %s found in header" % c) + # According to the specification it is legal to have comments + # that appear in the middle of a token. + # This is bonkers; I've never seen it; and it's a bit awkward to + # code good lexers in Python (no goto). So we break on such + # cases. + token = strtobytes("") + while c.isdigit(): + token += c + c = getc() + # Slight hack. All "tokens" are decimal integers, so convert + # them here. + header.append(int(token)) + if len(header) == expected: + break + # Skip comments (again) + while c == "#": + while c not in "\n\r": + c = getc() + if not c.isspace(): + raise Error("expected header to end with whitespace, not %s" % c) + + if type in pbm: + # synthesize a MAXVAL + header.append(1) + depth = (1, 3)[type == strtobytes("P6")] + return header[0], header[1], header[2], depth, header[3] + + +def write_pnm(file, width, height, pixels, meta): + """Write a Netpbm PNM/PAM file.""" + + bitdepth = meta["bitdepth"] + maxval = 2 ** bitdepth - 1 + # Rudely, the number of image planes can be used to determine + # whether we are L (PGM), LA (PAM), RGB (PPM), or RGBA (PAM). + planes = meta["planes"] + # Can be an assert as long as we assume that pixels and meta came + # from a PNG file. + assert planes in (1, 2, 3, 4) + if planes in (1, 3): + if 1 == planes: + # PGM + # Could generate PBM if maxval is 1, but we don't (for one + # thing, we'd have to convert the data, not just blat it + # out). + fmt = "P5" + else: + # PPM + fmt = "P6" + file.write("%s %d %d %d\n" % (fmt, width, height, maxval)) + if planes in (2, 4): + # PAM + # See http://netpbm.sourceforge.net/doc/pam.html + if 2 == planes: + tupltype = "GRAYSCALE_ALPHA" + else: + tupltype = "RGB_ALPHA" + file.write( + "P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL %d\n" + "TUPLTYPE %s\nENDHDR\n" % (width, height, planes, maxval, tupltype) + ) + # Values per row + vpr = planes * width + # struct format + fmt = ">%d" % vpr + if maxval > 0xFF: + fmt = fmt + "H" + else: + fmt = fmt + "B" + for row in pixels: + file.write(struct.pack(fmt, *row)) + file.flush() + + +def color_triple(color): + """ + Convert a command line colour value to a RGB triple of integers. + FIXME: Somewhere we need support for greyscale backgrounds etc. + """ + if color.startswith("#") and len(color) == 4: + return (int(color[1], 16), int(color[2], 16), int(color[3], 16)) + if color.startswith("#") and len(color) == 7: + return (int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)) + elif color.startswith("#") and len(color) == 13: + return (int(color[1:5], 16), int(color[5:9], 16), int(color[9:13], 16)) + + +def _main(argv): + """ + Run the PNG encoder with options from the command line. + """ + + # Parse command line arguments + from optparse import OptionParser + import re + + version = "%prog " + re.sub(r"( ?\$|URL: |Rev:)", "", __version__) + parser = OptionParser(version=version) + parser.set_usage("%prog [options] [imagefile]") + parser.add_option( + "-r", + "--read-png", + default=False, + action="store_true", + help="Read PNG, write PNM", + ) + parser.add_option( + "-i", + "--interlace", + default=False, + action="store_true", + help="create an interlaced PNG file (Adam7)", + ) + parser.add_option( + "-t", + "--transparent", + action="store", + type="string", + metavar="color", + help="mark the specified colour (#RRGGBB) as transparent", + ) + parser.add_option( + "-b", + "--background", + action="store", + type="string", + metavar="color", + help="save the specified background colour", + ) + parser.add_option( + "-a", + "--alpha", + action="store", + type="string", + metavar="pgmfile", + help="alpha channel transparency (RGBA)", + ) + parser.add_option( + "-g", + "--gamma", + action="store", + type="float", + metavar="value", + help="save the specified gamma value", + ) + parser.add_option( + "-c", + "--compression", + action="store", + type="int", + metavar="level", + help="zlib compression level (0-9)", + ) + parser.add_option( + "-T", + "--test", + default=False, + action="store_true", + help="create a test image (a named PngSuite image if an argument is supplied)", + ) + parser.add_option( + "-L", + "--list", + default=False, + action="store_true", + help="print list of named test images", + ) + parser.add_option( + "-R", + "--test-red", + action="store", + type="string", + metavar="pattern", + help="test pattern for the red image layer", + ) + parser.add_option( + "-G", + "--test-green", + action="store", + type="string", + metavar="pattern", + help="test pattern for the green image layer", + ) + parser.add_option( + "-B", + "--test-blue", + action="store", + type="string", + metavar="pattern", + help="test pattern for the blue image layer", + ) + parser.add_option( + "-A", + "--test-alpha", + action="store", + type="string", + metavar="pattern", + help="test pattern for the alpha image layer", + ) + parser.add_option( + "-K", + "--test-black", + action="store", + type="string", + metavar="pattern", + help="test pattern for greyscale image", + ) + parser.add_option( + "-d", + "--test-depth", + default=8, + action="store", + type="int", + metavar="NBITS", + help="create test PNGs that are NBITS bits per channel", + ) + parser.add_option( + "-S", + "--test-size", + action="store", + type="int", + metavar="size", + help="width and height of the test image", + ) + (options, args) = parser.parse_args(args=argv[1:]) + + # Convert options + if options.transparent is not None: + options.transparent = color_triple(options.transparent) + if options.background is not None: + options.background = color_triple(options.background) + + if options.list: + names = list(_pngsuite) + names.sort() + for name in names: + print(name) + return + + # Run regression tests + if options.test: + return test_suite(options, args) + + # Prepare input and output files + if len(args) == 0: + infilename = "-" + infile = sys.stdin + elif len(args) == 1: + infilename = args[0] + infile = open(infilename, "rb") + else: + parser.error("more than one input file") + outfile = sys.stdout + + if options.read_png: + # Encode PNG to PPM + png = Reader(file=infile) + width, height, pixels, meta = png.asDirect() + write_pnm(outfile, width, height, pixels, meta) + else: + # Encode PNM to PNG + format, width, height, depth, maxval = read_pnm_header( + infile, ("P5", "P6", "P7") + ) + # When it comes to the variety of input formats, we do something + # rather rude. Observe that L, LA, RGB, RGBA are the 4 colour + # types supported by PNG and that they correspond to 1, 2, 3, 4 + # channels respectively. So we use the number of channels in + # the source image to determine which one we have. We do not + # care about TUPLTYPE. + greyscale = depth <= 2 + pamalpha = depth in (2, 4) + supported = map(lambda x: 2 ** x - 1, range(1, 17)) + try: + mi = supported.index(maxval) + except ValueError: + raise NotImplementedError( + "your maxval (%s) not in supported list %s" % (maxval, str(supported)) + ) + bitdepth = mi + 1 + writer = Writer( + width, + height, + greyscale=greyscale, + bitdepth=bitdepth, + interlace=options.interlace, + transparent=options.transparent, + background=options.background, + alpha=bool(pamalpha or options.alpha), + gamma=options.gamma, + compression=options.compression, + ) + if options.alpha: + pgmfile = open(options.alpha, "rb") + format, awidth, aheight, adepth, amaxval = read_pnm_header(pgmfile, "P5") + if amaxval != "255": + raise NotImplementedError( + "maxval %s not supported for alpha channel" % amaxval + ) + if (awidth, aheight) != (width, height): + raise ValueError( + "alpha channel image size mismatch" + " (%s has %sx%s but %s has %sx%s)" + % (infilename, width, height, options.alpha, awidth, aheight) + ) + writer.convert_ppm_and_pgm(infile, pgmfile, outfile) + else: + writer.convert_pnm(infile, outfile) + + +if __name__ == "__main__": + try: + _main(sys.argv) + except Error as e: + sys.stderr.write("%s\n" % (e,)) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/run_tests.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/run_tests.py new file mode 100644 index 0000000..a193e23 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/run_tests.py @@ -0,0 +1,350 @@ +import sys + +if __name__ == "__main__": + sys.exit("This module is for import only") + +test_pkg_name = ".".join(__name__.split(".")[0:-2]) +is_pygame_pkg = test_pkg_name == "pygame.tests" +test_runner_mod = test_pkg_name + ".test_utils.test_runner" + +if is_pygame_pkg: + from pygame.tests.test_utils import import_submodule + from pygame.tests.test_utils.test_runner import ( + prepare_test_env, + run_test, + combine_results, + get_test_results, + TEST_RESULTS_START, + ) +else: + from test.test_utils import import_submodule + from test.test_utils.test_runner import ( + prepare_test_env, + run_test, + combine_results, + get_test_results, + TEST_RESULTS_START, + ) +import pygame +import pygame.threads + +import os +import re +import shutil +import tempfile +import time +import random +from pprint import pformat + +was_run = False + + +def run(*args, **kwds): + """Run the Pygame unit test suite and return (total tests run, fails dict) + + Positional arguments (optional): + The names of tests to include. If omitted then all tests are run. Test + names need not include the trailing '_test'. + + Keyword arguments: + incomplete - fail incomplete tests (default False) + usesubprocess - run all test suites in the current process + (default False, use separate subprocesses) + dump - dump failures/errors as dict ready to eval (default False) + file - if provided, the name of a file into which to dump failures/errors + timings - if provided, the number of times to run each individual test to + get an average run time (default is run each test once) + exclude - A list of TAG names to exclude from the run. The items may be + comma or space separated. + show_output - show silenced stderr/stdout on errors (default False) + all - dump all results, not just errors (default False) + randomize - randomize order of tests (default False) + seed - if provided, a seed randomizer integer + multi_thread - if provided, the number of THREADS in which to run + subprocessed tests + time_out - if subprocess is True then the time limit in seconds before + killing a test (default 30) + fake - if provided, the name of the fake tests package in the + run_tests__tests subpackage to run instead of the normal + Pygame tests + python - the path to a python executable to run subprocessed tests + (default sys.executable) + interative - allow tests tagged 'interative'. + + Return value: + A tuple of total number of tests run, dictionary of error information. The + dictionary is empty if no errors were recorded. + + By default individual test modules are run in separate subprocesses. This + recreates normal Pygame usage where pygame.init() and pygame.quit() are + called only once per program execution, and avoids unfortunate + interactions between test modules. Also, a time limit is placed on test + execution, so frozen tests are killed when there time allotment expired. + Use the single process option if threading is not working properly or if + tests are taking too long. It is not guaranteed that all tests will pass + in single process mode. + + Tests are run in a randomized order if the randomize argument is True or a + seed argument is provided. If no seed integer is provided then the system + time is used. + + Individual test modules may have a corresponding *_tags.py module, + defining a __tags__ attribute, a list of tag strings used to selectively + omit modules from a run. By default only the 'interactive', 'ignore', and + 'subprocess_ignore' tags are ignored. 'interactive' is for modules that + take user input, like cdrom_test.py. 'ignore' and 'subprocess_ignore' for + for disabling modules for foreground and subprocess modes respectively. + These are for disabling tests on optional modules or for experimental + modules with known problems. These modules can be run from the console as + a Python program. + + This function can only be called once per Python session. It is not + reentrant. + + """ + + global was_run + + if was_run: + raise RuntimeError("run() was already called this session") + was_run = True + + options = kwds.copy() + option_usesubprocess = options.get("usesubprocess", False) + option_dump = options.pop("dump", False) + option_file = options.pop("file", None) + option_randomize = options.get("randomize", False) + option_seed = options.get("seed", None) + option_multi_thread = options.pop("multi_thread", 1) + option_time_out = options.pop("time_out", 120) + option_fake = options.pop("fake", None) + option_python = options.pop("python", sys.executable) + option_exclude = options.pop("exclude", ()) + option_interactive = options.pop("interactive", False) + + if not option_interactive and "interactive" not in option_exclude: + option_exclude += ("interactive",) + if option_usesubprocess and "subprocess_ignore" not in option_exclude: + option_exclude += ("subprocess_ignore",) + elif "ignore" not in option_exclude: + option_exclude += ("ignore",) + + option_exclude += ("python3_ignore",) + option_exclude += ("SDL2_ignore",) + + main_dir, test_subdir, fake_test_subdir = prepare_test_env() + + ########################################################################### + # Compile a list of test modules. If fake, then compile list of fake + # xxxx_test.py from run_tests__tests + + TEST_MODULE_RE = re.compile(r"^(.+_test)\.py$") + + test_mods_pkg_name = test_pkg_name + + working_dir_temp = tempfile.mkdtemp() + + if option_fake is not None: + test_mods_pkg_name = ".".join( + [test_mods_pkg_name, "run_tests__tests", option_fake] + ) + test_subdir = os.path.join(fake_test_subdir, option_fake) + working_dir = test_subdir + else: + working_dir = working_dir_temp + + # Added in because some machines will need os.environ else there will be + # false failures in subprocess mode. Same issue as python2.6. Needs some + # env vars. + + test_env = os.environ + + fmt1 = "%s.%%s" % test_mods_pkg_name + fmt2 = "%s.%%s_test" % test_mods_pkg_name + if args: + test_modules = [m.endswith("_test") and (fmt1 % m) or (fmt2 % m) for m in args] + else: + test_modules = [] + for f in sorted(os.listdir(test_subdir)): + for match in TEST_MODULE_RE.findall(f): + test_modules.append(fmt1 % (match,)) + + ########################################################################### + # Remove modules to be excluded. + + tmp = test_modules + test_modules = [] + for name in tmp: + tag_module_name = "%s_tags" % (name[0:-5],) + try: + tag_module = import_submodule(tag_module_name) + except ImportError: + test_modules.append(name) + else: + try: + tags = tag_module.__tags__ + except AttributeError: + print("%s has no tags: ignoring" % (tag_module_name,)) + test_modules.append(name) + else: + for tag in tags: + if tag in option_exclude: + print("skipping %s (tag '%s')" % (name, tag)) + break + else: + test_modules.append(name) + del tmp, tag_module_name, name + + ########################################################################### + # Meta results + + results = {} + meta_results = {"__meta__": {}} + meta = meta_results["__meta__"] + + ########################################################################### + # Randomization + + if option_randomize or option_seed is not None: + if option_seed is None: + option_seed = time.time() + meta["random_seed"] = option_seed + print("\nRANDOM SEED USED: %s\n" % option_seed) + random.seed(option_seed) + random.shuffle(test_modules) + + ########################################################################### + # Single process mode + + if not option_usesubprocess: + options["exclude"] = option_exclude + t = time.time() + for module in test_modules: + results.update(run_test(module, **options)) + t = time.time() - t + + ########################################################################### + # Subprocess mode + # + + else: + if is_pygame_pkg: + from pygame.tests.test_utils.async_sub import proc_in_time_or_kill + else: + from test.test_utils.async_sub import proc_in_time_or_kill + + pass_on_args = ["--exclude", ",".join(option_exclude)] + for field in ["randomize", "incomplete", "unbuffered", "verbosity"]: + if kwds.get(field, False): + pass_on_args.append("--" + field) + + def sub_test(module): + print("loading %s" % module) + + cmd = [option_python, "-m", test_runner_mod, module] + pass_on_args + + return ( + module, + (cmd, test_env, working_dir), + proc_in_time_or_kill( + cmd, option_time_out, env=test_env, wd=working_dir + ), + ) + + if option_multi_thread > 1: + + def tmap(f, args): + return pygame.threads.tmap( + f, args, stop_on_error=False, num_workers=option_multi_thread + ) + + else: + tmap = map + + t = time.time() + + for module, cmd, (return_code, raw_return) in tmap(sub_test, test_modules): + test_file = "%s.py" % os.path.join(test_subdir, module) + cmd, test_env, working_dir = cmd + + test_results = get_test_results(raw_return) + if test_results: + results.update(test_results) + else: + results[module] = {} + + results[module].update( + dict( + return_code=return_code, + raw_return=raw_return, + cmd=cmd, + test_file=test_file, + test_env=test_env, + working_dir=working_dir, + module=module, + ) + ) + + t = time.time() - t + + ########################################################################### + # Output Results + # + + untrusty_total, combined = combine_results(results, t) + total, n_errors, n_failures = count_results(results) + + meta["total_tests"] = total + meta["combined"] = combined + meta["total_errors"] = n_errors + meta["total_failures"] = n_failures + results.update(meta_results) + + if not option_usesubprocess and total != untrusty_total: + raise AssertionError( + "Something went wrong in the Test Machinery:\n" + "total: %d != untrusty_total: %d" % (total, untrusty_total) + ) + + if not option_dump: + print(combined) + else: + print(TEST_RESULTS_START) + print(pformat(results)) + + if option_file is not None: + results_file = open(option_file, "w") + try: + results_file.write(pformat(results)) + finally: + results_file.close() + + shutil.rmtree(working_dir_temp) + + return total, n_errors + n_failures + + +def count_results(results): + total = errors = failures = 0 + for result in results.values(): + if result.get("return_code", 0): + total += 1 + errors += 1 + else: + total += result["num_tests"] + errors += result["num_errors"] + failures += result["num_failures"] + + return total, errors, failures + + +def run_and_exit(*args, **kwargs): + """Run the tests, and if there are failures, exit with a return code of 1. + + This is needed for various buildbots to recognise that the tests have + failed. + """ + total, fails = run(*args, **kwargs) + if fails: + sys.exit(1) + sys.exit(0) diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/test_machinery.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/test_machinery.py new file mode 100644 index 0000000..114c281 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/test_machinery.py @@ -0,0 +1,89 @@ +import inspect +import random +import re +import unittest + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +from . import import_submodule + + +class PygameTestLoader(unittest.TestLoader): + def __init__( + self, randomize_tests=False, include_incomplete=False, exclude=("interactive",) + ): + super(PygameTestLoader, self).__init__() + self.randomize_tests = randomize_tests + + if exclude is None: + self.exclude = set() + else: + self.exclude = set(exclude) + + if include_incomplete: + self.testMethodPrefix = ("test", "todo_") + + def getTestCaseNames(self, testCaseClass): + res = [] + for name in super(PygameTestLoader, self).getTestCaseNames(testCaseClass): + tags = get_tags(testCaseClass, getattr(testCaseClass, name)) + if self.exclude.isdisjoint(tags): + res.append(name) + + if self.randomize_tests: + random.shuffle(res) + + return res + + +# Exclude by tags: + +TAGS_RE = re.compile(r"\|[tT]ags:(-?[ a-zA-Z,0-9_\n]+)\|", re.M) + + +class TestTags: + def __init__(self): + self.memoized = {} + self.parent_modules = {} + + def get_parent_module(self, class_): + if class_ not in self.parent_modules: + self.parent_modules[class_] = import_submodule(class_.__module__) + return self.parent_modules[class_] + + def __call__(self, parent_class, meth): + key = (parent_class, meth.__name__) + if key not in self.memoized: + parent_module = self.get_parent_module(parent_class) + + module_tags = getattr(parent_module, "__tags__", []) + class_tags = getattr(parent_class, "__tags__", []) + + tags = TAGS_RE.search(inspect.getdoc(meth) or "") + if tags: + test_tags = [t.strip() for t in tags.group(1).split(",")] + else: + test_tags = [] + + combined = set() + for tags in (module_tags, class_tags, test_tags): + if not tags: + continue + + add = set([t for t in tags if not t.startswith("-")]) + remove = set([t[1:] for t in tags if t not in add]) + + if add: + combined.update(add) + if remove: + combined.difference_update(remove) + + self.memoized[key] = combined + + return self.memoized[key] + + +get_tags = TestTags() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/test_runner.py b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/test_runner.py new file mode 100644 index 0000000..4c35221 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/test_utils/test_runner.py @@ -0,0 +1,330 @@ +import sys +import os + +if __name__ == "__main__": + pkg_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = pkg_name == "tests" and os.path.split(parent_dir)[1] == "pygame" + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith("pygame.tests.") + +import unittest +from .test_machinery import PygameTestLoader + +import re + +try: + import StringIO +except ImportError: + import io as StringIO + +import optparse +from pprint import pformat + + +def prepare_test_env(): + test_subdir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + main_dir = os.path.split(test_subdir)[0] + sys.path.insert(0, test_subdir) + fake_test_subdir = os.path.join(test_subdir, "run_tests__tests") + return main_dir, test_subdir, fake_test_subdir + + +main_dir, test_subdir, fake_test_subdir = prepare_test_env() + +################################################################################ +# Set the command line options +# +# options are shared with run_tests.py so make sure not to conflict +# in time more will be added here + +TAG_PAT = r"-?[a-zA-Z0-9_]+" +TAG_RE = re.compile(TAG_PAT) +EXCLUDE_RE = re.compile(r"(%s,?\s*)+$" % (TAG_PAT,)) + + +def exclude_callback(option, opt, value, parser): + if EXCLUDE_RE.match(value) is None: + raise optparse.OptionValueError("%s argument has invalid value" % (opt,)) + parser.values.exclude = TAG_RE.findall(value) + + +opt_parser = optparse.OptionParser() + +opt_parser.add_option( + "-i", "--incomplete", action="store_true", help="fail incomplete tests" +) + +opt_parser.add_option( + "-s", + "--usesubprocess", + action="store_true", + help="run everything in a single process " " (default: use no subprocesses)", +) + +opt_parser.add_option( + "-e", + "--exclude", + action="callback", + type="string", + help="exclude tests containing any of TAGS", + callback=exclude_callback, +) + +opt_parser.add_option( + "-u", + "--unbuffered", + action="store_true", + help="Show stdout/stderr as tests run, rather than storing it and showing on failures", +) + +opt_parser.add_option( + "-v", + "--verbose", + dest="verbosity", + action="store_const", + const=2, + help="Verbose output", +) +opt_parser.add_option( + "-q", + "--quiet", + dest="verbosity", + action="store_const", + const=0, + help="Quiet output", +) + +opt_parser.add_option( + "-r", "--randomize", action="store_true", help="randomize order of tests" +) + +################################################################################ +# If an xxxx_test.py takes longer than TIME_OUT seconds it will be killed +# This is only the default, can be over-ridden on command line + +TIME_OUT = 30 + +# DEFAULTS + +################################################################################ +# Human readable output +# + +COMPLETE_FAILURE_TEMPLATE = """ +====================================================================== +ERROR: all_tests_for (%(module)s.AllTestCases) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "test/%(module)s.py", line 1, in all_tests_for +subprocess completely failed with return code of %(return_code)s +cmd: %(cmd)s +test_env: %(test_env)s +working_dir: %(working_dir)s +return (first 10 and last 10 lines): +%(raw_return)s + +""" # Leave that last empty line else build page regex won't match +# Text also needs to be vertically compressed + + +RAN_TESTS_DIV = (70 * "-") + "\nRan" + +DOTS = re.compile("^([FE.sux]*)$", re.MULTILINE) + + +def extract_tracebacks(output): + """from test runner output return the tracebacks.""" + verbose_mode = " ..." in output + + if verbose_mode: + if "ERROR" in output or "FAILURE" in output: + return "\n\n==".join(output.split("\n\n==")[1:]) + else: + dots = DOTS.search(output).group(1) + if "E" in dots or "F" in dots: + return output[len(dots) + 1 :].split(RAN_TESTS_DIV)[0] + return "" + + +def output_into_dots(output): + """convert the test runner output into dots.""" + # verbose_mode = ") ..." in output + verbose_mode = " ..." in output + + if verbose_mode: + # a map from the verbose output to the dots output. + reasons = { + "... ERROR": "E", + "... unexpected success": "u", + "... skipped": "s", + "... expected failure": "x", + "... ok": ".", + "... FAIL": "F", + } + results = output.split("\n\n==")[0] + lines = [l for l in results.split("\n") if l and "..." in l] + dotlist = [] + for l in lines: + found = False + for reason in reasons: + if reason in l: + dotlist.append(reasons[reason]) + found = True + break + if not found: + raise ValueError("Not sure what this is. Add to reasons. :%s" % l) + + return "".join(dotlist) + dots = DOTS.search(output).group(1) + return dots + + +def combine_results(all_results, t): + """ + + Return pieced together results in a form fit for human consumption. Don't + rely on results if piecing together subprocessed results (single process + mode is fine). Was originally meant for that purpose but was found to be + unreliable. See the dump option for reliable results. + + """ + + all_dots = "" + failures = [] + + for module, results in sorted(all_results.items()): + output, return_code, raw_return = map( + results.get, ("output", "return_code", "raw_return") + ) + + if not output or (return_code and RAN_TESTS_DIV not in output): + # would this effect the original dict? TODO + output_lines = raw_return.splitlines() + if len(output_lines) > 20: + results["raw_return"] = "\n".join( + output_lines[:10] + ["..."] + output_lines[-10:] + ) + failures.append(COMPLETE_FAILURE_TEMPLATE % results) + all_dots += "E" + continue + + dots = output_into_dots(output) + all_dots += dots + tracebacks = extract_tracebacks(output) + if tracebacks: + failures.append(tracebacks) + + total_fails, total_errors = map(all_dots.count, "FE") + total_tests = len(all_dots) + + combined = [all_dots] + if failures: + combined += ["".join(failures).lstrip("\n")[:-1]] + combined += ["%s %s tests in %.3fs\n" % (RAN_TESTS_DIV, total_tests, t)] + + if failures: + infos = (["failures=%s" % total_fails] if total_fails else []) + ( + ["errors=%s" % total_errors] if total_errors else [] + ) + combined += ["FAILED (%s)\n" % ", ".join(infos)] + else: + combined += ["OK\n"] + + return total_tests, "\n".join(combined) + + +################################################################################ + +TEST_RESULTS_START = "<--!! TEST RESULTS START HERE !!-->" +TEST_RESULTS_END = "<--!! TEST RESULTS END HERE !!-->" +_test_re_str = "%s\n(.*)%s" % (TEST_RESULTS_START, TEST_RESULTS_END) +TEST_RESULTS_RE = re.compile(_test_re_str, re.DOTALL | re.M) + + +def get_test_results(raw_return): + test_results = TEST_RESULTS_RE.search(raw_return) + if test_results: + try: + return eval(test_results.group(1)) + except: + print("BUGGY TEST RESULTS EVAL:\n %s" % test_results.group(1)) + raise + + +################################################################################ + + +def run_test( + module, + incomplete=False, + usesubprocess=True, + randomize=False, + exclude=("interactive",), + buffer=True, + unbuffered=None, + verbosity=1, +): + """Run a unit test module""" + suite = unittest.TestSuite() + + if verbosity is None: + verbosity = 1 + + if verbosity: + print("loading %s" % module) + + loader = PygameTestLoader( + randomize_tests=randomize, include_incomplete=incomplete, exclude=exclude + ) + suite.addTest(loader.loadTestsFromName(module)) + + output = StringIO.StringIO() + runner = unittest.TextTestRunner(stream=output, buffer=buffer, verbosity=verbosity) + results = runner.run(suite) + + if verbosity == 2: + output.seek(0) + print(output.read()) + output.seek(0) + + results = { + module: { + "output": output.getvalue(), + "num_tests": results.testsRun, + "num_errors": len(results.errors), + "num_failures": len(results.failures), + } + } + + if usesubprocess: + print(TEST_RESULTS_START) + print(pformat(results)) + print(TEST_RESULTS_END) + else: + return results + + +################################################################################ + +if __name__ == "__main__": + options, args = opt_parser.parse_args() + if not args: + + if is_pygame_pkg: + run_from = "pygame.tests.go" + else: + run_from = os.path.join(main_dir, "run_tests.py") + sys.exit("No test module provided; consider using %s instead" % run_from) + run_test( + args[0], + incomplete=options.incomplete, + usesubprocess=options.usesubprocess, + randomize=options.randomize, + exclude=options.exclude, + buffer=(not options.unbuffered), + ) + +################################################################################ diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/threads_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/threads_test.py new file mode 100644 index 0000000..5f55eac --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/threads_test.py @@ -0,0 +1,240 @@ +import unittest +from pygame.threads import FuncResult, tmap, WorkerQueue, Empty, STOP +from pygame import threads, Surface, transform + + +import time + + +class WorkerQueueTypeTest(unittest.TestCase): + def test_usage_with_different_functions(self): + def f(x): + return x + 1 + + def f2(x): + return x + 2 + + wq = WorkerQueue() + fr = FuncResult(f) + fr2 = FuncResult(f2) + wq.do(fr, 1) + wq.do(fr2, 1) + wq.wait() + wq.stop() + + self.assertEqual(fr.result, 2) + self.assertEqual(fr2.result, 3) + + def test_do(self): + """Tests function placement on queue and execution after blocking function completion.""" + # __doc__ (as of 2008-06-28) for pygame.threads.WorkerQueue.do: + + # puts a function on a queue for running _later_. + + # TODO: This tests needs refactoring to avoid sleep. + # sleep is slow and unreliable (especially on VMs). + + # def sleep_test(): + # time.sleep(0.5) + + # def calc_test(x): + # return x + 1 + + # worker_queue = WorkerQueue(num_workers=1) + # sleep_return = FuncResult(sleep_test) + # calc_return = FuncResult(calc_test) + # init_time = time.time() + # worker_queue.do(sleep_return) + # worker_queue.do(calc_return, 1) + # worker_queue.wait() + # worker_queue.stop() + # time_diff = time.time() - init_time + + # self.assertEqual(sleep_return.result, None) + # self.assertEqual(calc_return.result, 2) + # self.assertGreaterEqual(time_diff, 0.5) + + def test_stop(self): + """Ensure stop() stops the worker queue""" + wq = WorkerQueue() + + self.assertGreater(len(wq.pool), 0) + + for t in wq.pool: + self.assertTrue(t.is_alive()) + + for i in range(200): + wq.do(lambda x: x + 1, i) + + wq.stop() + + for t in wq.pool: + self.assertFalse(t.is_alive()) + + self.assertIs(wq.queue.get(), STOP) + + def test_threadloop(self): + + # __doc__ (as of 2008-06-28) for pygame.threads.WorkerQueue.threadloop: + + # Loops until all of the tasks are finished. + + # Make a worker queue with only one thread + wq = WorkerQueue(1) + + # Ocuppy the one worker with the threadloop + # wq threads are just threadloop, so this makes an embedded threadloop + wq.do(wq.threadloop) + + # Make sure wq can still do work + # If wq can still do work, threadloop works + l = [] + wq.do(l.append, 1) + # Wait won't work because the primary thread is in an infinite loop + time.sleep(0.5) + self.assertEqual(l[0], 1) + + # Kill the embedded threadloop by sending stop onto the stack + # Threadloop puts STOP back onto the queue when it STOPs so this kills both loops + wq.stop() + + # Make sure wq has stopped + self.assertFalse(wq.pool[0].is_alive()) + + def test_wait(self): + + # __doc__ (as of 2008-06-28) for pygame.threads.WorkerQueue.wait: + + # waits until all tasks are complete. + + wq = WorkerQueue() + + for i in range(2000): + wq.do(lambda x: x + 1, i) + wq.wait() + + self.assertRaises(Empty, wq.queue.get_nowait) + + wq.stop() + + +class ThreadsModuleTest(unittest.TestCase): + def test_benchmark_workers(self): + """Ensure benchmark_workers performance measure functions properly with both default and specified inputs""" + "tags:long_running" + + # __doc__ (as of 2008-06-28) for pygame.threads.benchmark_workers: + + # does a little test to see if workers are at all faster. + # Returns the number of workers which works best. + # Takes a little bit of time to run, so you should only really call + # it once. + # You can pass in benchmark data, and functions if you want. + # a_bench_func - f(data) + # the_data - data to work on. + optimal_workers = threads.benchmark_workers() + self.assertIsInstance(optimal_workers, int) + self.assertTrue(0 <= optimal_workers < 64) + + # Test passing benchmark data and function explicitly + def smooth_scale_bench(data): + transform.smoothscale(data, (128, 128)) + + surf_data = [Surface((x, x), 0, 32) for x in range(12, 64, 12)] + best_num_workers = threads.benchmark_workers(smooth_scale_bench, surf_data) + self.assertIsInstance(best_num_workers, int) + + def test_init(self): + """Ensure init() sets up the worker queue""" + threads.init(8) + + self.assertIsInstance(threads._wq, WorkerQueue) + + threads.quit() + + def test_quit(self): + """Ensure quit() cleans up the worker queue""" + threads.init(8) + threads.quit() + + self.assertIsNone(threads._wq) + + def test_tmap(self): + # __doc__ (as of 2008-06-28) for pygame.threads.tmap: + + # like map, but uses a thread pool to execute. + # num_workers - the number of worker threads that will be used. If pool + # is passed in, then the num_workers arg is ignored. + # worker_queue - you can optionally pass in an existing WorkerQueue. + # wait - True means that the results are returned when everything is finished. + # False means that we return the [worker_queue, results] right away instead. + # results, is returned as a list of FuncResult instances. + # stop_on_error - + + ## test that the outcomes of map and tmap are the same + func, data = lambda x: x + 1, range(100) + + tmapped = list(tmap(func, data)) + mapped = list(map(func, data)) + + self.assertEqual(tmapped, mapped) + + ## Test that setting tmap to not stop on errors produces the expected result + data2 = range(100) + always_excepts = lambda x: 1 / 0 + + tmapped2 = list(tmap(always_excepts, data2, stop_on_error=False)) + + # Use list comprehension to check all entries are None as all function + # calls made by tmap will have thrown an exception (ZeroDivisionError) + # Condense to single bool with `all`, which will return true if all + # entries are true + self.assertTrue(all([x is None for x in tmapped2])) + + def todo_test_tmap__None_func_and_multiple_sequences(self): + """Using a None as func and multiple sequences""" + self.fail() + + res = tmap(None, [1, 2, 3, 4]) + res2 = tmap(None, [1, 2, 3, 4], [22, 33, 44, 55]) + res3 = tmap(None, [1, 2, 3, 4], [22, 33, 44, 55, 66]) + res4 = tmap(None, [1, 2, 3, 4, 5], [22, 33, 44, 55]) + + self.assertEqual([1, 2, 3, 4], res) + self.assertEqual([(1, 22), (2, 33), (3, 44), (4, 55)], res2) + self.assertEqual([(1, 22), (2, 33), (3, 44), (4, 55), (None, 66)], res3) + self.assertEqual([(1, 22), (2, 33), (3, 44), (4, 55), (5, None)], res4) + + def test_tmap__wait(self): + r = range(1000) + wq, results = tmap(lambda x: x, r, num_workers=5, wait=False) + wq.wait() + r2 = map(lambda x: x.result, results) + self.assertEqual(list(r), list(r2)) + + def test_FuncResult(self): + """Ensure FuncResult sets its result and exception attributes""" + # Results are stored in result attribute + fr = FuncResult(lambda x: x + 1) + fr(2) + + self.assertEqual(fr.result, 3) + + # Exceptions are store in exception attribute + self.assertIsNone(fr.exception, "no exception should be raised") + + exception = ValueError("rast") + + def x(sdf): + raise exception + + fr = FuncResult(x) + fr(None) + + self.assertIs(fr.exception, exception) + + +################################################################################ + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/time_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/time_test.py new file mode 100644 index 0000000..e428508 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/time_test.py @@ -0,0 +1,392 @@ +import unittest +import pygame +import time + +Clock = pygame.time.Clock + + +class ClockTypeTest(unittest.TestCase): + __tags__ = ["timing"] + + def test_construction(self): + """Ensure a Clock object can be created""" + c = Clock() + + self.assertTrue(c, "Clock cannot be constructed") + + def test_get_fps(self): + """test_get_fps tests pygame.time.get_fps()""" + # Initialization check, first call should return 0 fps + c = Clock() + self.assertEqual(c.get_fps(), 0) + # Type check get_fps should return float + self.assertTrue(type(c.get_fps()) == float) + # Allowable margin of error in percentage + delta = 0.30 + # Test fps correctness for 100, 60 and 30 fps + self._fps_test(c, 100, delta) + self._fps_test(c, 60, delta) + self._fps_test(c, 30, delta) + + def _fps_test(self, clock, fps, delta): + """ticks fps times each second, hence get_fps() should return fps""" + delay_per_frame = 1.0 / fps + for f in range(fps): # For one second tick and sleep + clock.tick() + time.sleep(delay_per_frame) + # We should get around fps (+- fps*delta -- delta % of fps) + self.assertAlmostEqual(clock.get_fps(), fps, delta=fps * delta) + + def test_get_rawtime(self): + + iterations = 10 + delay = 0.1 + delay_miliseconds = delay * (10 ** 3) # actual time difference between ticks + framerate_limit = 5 + delta = 50 # allowable error in milliseconds + + # Testing Clock Initialization + c = Clock() + self.assertEqual(c.get_rawtime(), 0) + + # Testing Raw Time with Frame Delay + for f in range(iterations): + time.sleep(delay) + c.tick(framerate_limit) + c1 = c.get_rawtime() + self.assertAlmostEqual(delay_miliseconds, c1, delta=delta) + + # Testing get_rawtime() = get_time() + for f in range(iterations): + time.sleep(delay) + c.tick() + c1 = c.get_rawtime() + c2 = c.get_time() + self.assertAlmostEqual(c1, c2, delta=delta) + + def test_get_time(self): + # Testing parameters + delay = 0.1 # seconds + delay_miliseconds = delay * (10 ** 3) + iterations = 10 + delta = 50 # milliseconds + + # Testing Clock Initialization + c = Clock() + self.assertEqual(c.get_time(), 0) + + # Testing within delay parameter range + for i in range(iterations): + time.sleep(delay) + c.tick() + c1 = c.get_time() + self.assertAlmostEqual(delay_miliseconds, c1, delta=delta) + + # Comparing get_time() results with the 'time' module + for i in range(iterations): + t0 = time.time() + time.sleep(delay) + c.tick() + t1 = time.time() + c1 = c.get_time() # elapsed time in milliseconds + d0 = (t1 - t0) * ( + 10 ** 3 + ) #'time' module elapsed time converted to milliseconds + self.assertAlmostEqual(d0, c1, delta=delta) + + def test_tick(self): + """Tests time.Clock.tick()""" + """ + Loops with a set delay a few times then checks what tick reports to + verify its accuracy. Then calls tick with a desired frame-rate and + verifies it is not faster than the desired frame-rate nor is it taking + a dramatically long time to complete + """ + + # Adjust this value to increase the acceptable sleep jitter + epsilon = 1.5 + # Adjust this value to increase the acceptable locked frame-rate jitter + epsilon2 = 0.3 + # adjust this value to increase the acceptable frame-rate margin + epsilon3 = 20 + testing_framerate = 60 + milliseconds = 5.0 + + collection = [] + c = Clock() + + # verify time.Clock.tick() will measure the time correctly + c.tick() + for i in range(100): + time.sleep(milliseconds / 1000) # convert to seconds + collection.append(c.tick()) + + # removes the first highest and lowest value + for outlier in [min(collection), max(collection)]: + if outlier != milliseconds: + collection.remove(outlier) + + average_time = float(sum(collection)) / len(collection) + + # assert the deviation from the intended frame-rate is within the + # acceptable amount (the delay is not taking a dramatically long time) + self.assertAlmostEqual(average_time, milliseconds, delta=epsilon) + + # verify tick will control the frame-rate + + c = Clock() + collection = [] + + start = time.time() + + for i in range(testing_framerate): + collection.append(c.tick(testing_framerate)) + + # remove the highest and lowest outliers + for outlier in [min(collection), max(collection)]: + if outlier != round(1000 / testing_framerate): + collection.remove(outlier) + + end = time.time() + + # Since calling tick with a desired fps will prevent the program from + # running at greater than the given fps, 100 iterations at 100 fps + # should last no less than 1 second + self.assertAlmostEqual(end - start, 1, delta=epsilon2) + + average_tick_time = float(sum(collection)) / len(collection) + self.assertAlmostEqual( + 1000 / average_tick_time, testing_framerate, delta=epsilon3 + ) + + def test_tick_busy_loop(self): + """Test tick_busy_loop""" + + c = Clock() + + # Test whether the return value of tick_busy_loop is equal to + # (FPS is accurate) or greater than (slower than the set FPS) + # with a small margin for error based on differences in how this + # test runs in practise - it either sometimes runs slightly fast + # or seems to based on a rounding error. + second_length = 1000 + shortfall_tolerance = 1 # (ms) The amount of time a tick is allowed to run short of, to account for underlying rounding errors + sample_fps = 40 + + self.assertGreaterEqual( + c.tick_busy_loop(sample_fps), + (second_length / sample_fps) - shortfall_tolerance, + ) + pygame.time.wait(10) # incur delay between ticks that's faster than sample_fps + self.assertGreaterEqual( + c.tick_busy_loop(sample_fps), + (second_length / sample_fps) - shortfall_tolerance, + ) + pygame.time.wait(200) # incur delay between ticks that's slower than sample_fps + self.assertGreaterEqual( + c.tick_busy_loop(sample_fps), + (second_length / sample_fps) - shortfall_tolerance, + ) + + high_fps = 500 + self.assertGreaterEqual( + c.tick_busy_loop(high_fps), (second_length / high_fps) - shortfall_tolerance + ) + + low_fps = 1 + self.assertGreaterEqual( + c.tick_busy_loop(low_fps), (second_length / low_fps) - shortfall_tolerance + ) + + low_non_factor_fps = 35 # 1000/35 makes 28.5714285714 + frame_length_without_decimal_places = int( + second_length / low_non_factor_fps + ) # Same result as math.floor + self.assertGreaterEqual( + c.tick_busy_loop(low_non_factor_fps), + frame_length_without_decimal_places - shortfall_tolerance, + ) + + high_non_factor_fps = 750 # 1000/750 makes 1.3333... + frame_length_without_decimal_places_2 = int( + second_length / high_non_factor_fps + ) # Same result as math.floor + self.assertGreaterEqual( + c.tick_busy_loop(high_non_factor_fps), + frame_length_without_decimal_places_2 - shortfall_tolerance, + ) + + zero_fps = 0 + self.assertEqual(c.tick_busy_loop(zero_fps), 0) + + # Check behaviour of unexpected values + + negative_fps = -1 + self.assertEqual(c.tick_busy_loop(negative_fps), 0) + + fractional_fps = 32.75 + frame_length_without_decimal_places_3 = int(second_length / fractional_fps) + self.assertGreaterEqual( + c.tick_busy_loop(fractional_fps), + frame_length_without_decimal_places_3 - shortfall_tolerance, + ) + + bool_fps = True + self.assertGreaterEqual( + c.tick_busy_loop(bool_fps), (second_length / bool_fps) - shortfall_tolerance + ) + + +class TimeModuleTest(unittest.TestCase): + __tags__ = ["timing"] + + def test_delay(self): + """Tests time.delay() function.""" + millis = 50 # millisecond to wait on each iteration + iterations = 20 # number of iterations + delta = 150 # Represents acceptable margin of error for wait in ms + # Call checking function + self._wait_delay_check(pygame.time.delay, millis, iterations, delta) + # After timing behaviour, check argument type exceptions + self._type_error_checks(pygame.time.delay) + + def test_get_ticks(self): + """Tests time.get_ticks()""" + """ + Iterates and delays for arbitrary amount of time for each iteration, + check get_ticks to equal correct gap time + """ + iterations = 20 + millis = 50 + delta = 15 # Acceptable margin of error in ms + # Assert return type to be int + self.assertTrue(type(pygame.time.get_ticks()) == int) + for i in range(iterations): + curr_ticks = pygame.time.get_ticks() # Save current tick count + curr_time = time.time() # Save current time + pygame.time.delay(millis) # Delay for millis + # Time and Ticks difference from start of the iteration + time_diff = round((time.time() - curr_time) * 1000) + ticks_diff = pygame.time.get_ticks() - curr_ticks + # Assert almost equality of the ticking time and time difference + self.assertAlmostEqual(ticks_diff, time_diff, delta=delta) + + def test_set_timer(self): + """Tests time.set_timer()""" + """ + Tests if a timer will post the correct amount of eventid events in + the specified delay. Test is posting event objects work. + Also tests if setting milliseconds to 0 stops the timer and if + the once argument and repeat arguments work. + """ + pygame.init() + TIMER_EVENT_TYPE = pygame.event.custom_type() + timer_event = pygame.event.Event(TIMER_EVENT_TYPE) + delta = 50 + timer_delay = 100 + test_number = 8 # Number of events to read for the test + events = 0 # Events read + + pygame.event.clear() + pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay) + + # Test that 'test_number' events are posted in the right amount of time + t1 = pygame.time.get_ticks() + max_test_time = t1 + timer_delay * test_number + delta + while events < test_number: + for event in pygame.event.get(): + if event == timer_event: + events += 1 + + # The test takes too much time + if pygame.time.get_ticks() > max_test_time: + break + + pygame.time.set_timer(TIMER_EVENT_TYPE, 0) + t2 = pygame.time.get_ticks() + # Is the number ef events and the timing right? + self.assertEqual(events, test_number) + self.assertAlmostEqual(timer_delay * test_number, t2 - t1, delta=delta) + + # Test that the timer stopped when set with 0ms delay. + pygame.time.delay(200) + self.assertNotIn(timer_event, pygame.event.get()) + + # Test that the old timer for an event is deleted when a new timer is set + pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay) + pygame.time.delay(int(timer_delay * 3.5)) + self.assertEqual(pygame.event.get().count(timer_event), 3) + pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay * 10) # long wait time + pygame.time.delay(timer_delay * 5) + self.assertNotIn(timer_event, pygame.event.get()) + pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay * 3) + pygame.time.delay(timer_delay * 7) + self.assertEqual(pygame.event.get().count(timer_event), 2) + pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay) + pygame.time.delay(int(timer_delay * 5.5)) + self.assertEqual(pygame.event.get().count(timer_event), 5) + + # Test that the loops=True works + pygame.time.set_timer(TIMER_EVENT_TYPE, 10, True) + pygame.time.delay(40) + self.assertEqual(pygame.event.get().count(timer_event), 1) + + # Test a variety of event objects, test loops argument + events_to_test = [ + pygame.event.Event(TIMER_EVENT_TYPE), + pygame.event.Event( + TIMER_EVENT_TYPE, foo="9gwz5", baz=12, lol=[124, (34, "")] + ), + pygame.event.Event(pygame.KEYDOWN, key=pygame.K_a, unicode="a"), + ] + repeat = 3 + millis = 50 + for e in events_to_test: + pygame.time.set_timer(e, millis, loops=repeat) + pygame.time.delay(2 * millis * repeat) + self.assertEqual(pygame.event.get().count(e), repeat) + pygame.quit() + + def test_wait(self): + """Tests time.wait() function.""" + millis = 100 # millisecond to wait on each iteration + iterations = 10 # number of iterations + delta = 50 # Represents acceptable margin of error for wait in ms + # Call checking function + self._wait_delay_check(pygame.time.wait, millis, iterations, delta) + # After timing behaviour, check argument type exceptions + self._type_error_checks(pygame.time.wait) + + def _wait_delay_check(self, func_to_check, millis, iterations, delta): + """ " + call func_to_check(millis) "iterations" times and check each time if + function "waited" for given millisecond (+- delta). At the end, take + average time for each call (whole_duration/iterations), which should + be equal to millis (+- delta - acceptable margin of error). + *Created to avoid code duplication during delay and wait tests + """ + # take starting time for duration calculation + start_time = time.time() + for i in range(iterations): + wait_time = func_to_check(millis) + # Check equality of wait_time and millis with margin of error delta + self.assertAlmostEqual(wait_time, millis, delta=delta) + stop_time = time.time() + # Cycle duration in millisecond + duration = round((stop_time - start_time) * 1000) + # Duration/Iterations should be (almost) equal to predefined millis + self.assertAlmostEqual(duration / iterations, millis, delta=delta) + + def _type_error_checks(self, func_to_check): + """Checks 3 TypeError (float, tuple, string) for the func_to_check""" + """Intended for time.delay and time.wait functions""" + # Those methods throw no exceptions on negative integers + self.assertRaises(TypeError, func_to_check, 0.1) # check float + self.assertRaises(TypeError, pygame.time.delay, (0, 1)) # check tuple + self.assertRaises(TypeError, pygame.time.delay, "10") # check string + + +############################################################################### + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/touch_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/touch_test.py new file mode 100644 index 0000000..3f63cae --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/touch_test.py @@ -0,0 +1,98 @@ +import unittest +import os +import pygame +from pygame._sdl2 import touch +from pygame.tests.test_utils import question + + +has_touchdevice = touch.get_num_devices() > 0 + + +class TouchTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + pygame.display.init() + + @classmethod + def tearDownClass(cls): + pygame.display.quit() + + def test_num_devices(self): + touch.get_num_devices() + + @unittest.skipIf(not has_touchdevice, "no touch devices found") + def test_get_device(self): + touch.get_device(0) + + def test_num_fingers__invalid(self): + self.assertRaises(pygame.error, touch.get_device, -1234) + self.assertRaises(TypeError, touch.get_device, "test") + + @unittest.skipIf(not has_touchdevice, "no touch devices found") + def test_num_fingers(self): + touch.get_num_fingers(touch.get_device(0)) + + def test_num_fingers__invalid(self): + self.assertRaises(TypeError, touch.get_num_fingers, "test") + self.assertRaises(pygame.error, touch.get_num_fingers, -1234) + + +class TouchInteractiveTest(unittest.TestCase): + + __tags__ = ["interactive"] + + @unittest.skipIf(not has_touchdevice, "no touch devices found") + def test_get_finger(self): + """ask for touch input and check the dict""" + + pygame.display.init() + pygame.font.init() + + os.environ["SDL_VIDEO_WINDOW_POS"] = "50,50" + screen = pygame.display.set_mode((800, 600)) + screen.fill((255, 255, 255)) + + font = pygame.font.Font(None, 32) + instructions_str_1 = "Please place some fingers on your touch device" + instructions_str_2 = ( + "Close the window when finished, " "and answer the question" + ) + inst_1_render = font.render(instructions_str_1, True, pygame.Color("#000000")) + inst_2_render = font.render(instructions_str_2, True, pygame.Color("#000000")) + + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + finger_data_renders = [] + num_devices = pygame._sdl2.touch.get_num_devices() + if num_devices > 0: + first_device = pygame._sdl2.touch.get_device(0) + num_fingers = pygame._sdl2.touch.get_num_fingers(first_device) + if num_fingers > 0: + for finger_index in range(0, num_fingers): + data = pygame._sdl2.touch.get_finger(first_device, finger_index) + render = font.render( + "finger - " + str(data), True, pygame.Color("#000000") + ) + + finger_data_renders.append(render) + + screen.fill((255, 255, 255)) + screen.blit(inst_1_render, (5, 5)) + screen.blit(inst_2_render, (5, 40)) + for index, finger in enumerate(finger_data_renders): + screen.blit(finger, (5, 80 + (index * 40))) + + pygame.display.update() + + response = question("Does the finger data seem correct?") + self.assertTrue(response) + + pygame.display.quit() + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/transform_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/transform_test.py new file mode 100644 index 0000000..1484817 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/transform_test.py @@ -0,0 +1,1316 @@ +import unittest +import os +import platform + +from pygame.tests import test_utils +from pygame.tests.test_utils import example_path + +import pygame +import pygame.transform +from pygame.locals import * + + +def show_image(s, images=[]): + # pygame.display.init() + size = s.get_rect()[2:] + screen = pygame.display.set_mode(size) + screen.blit(s, (0, 0)) + pygame.display.flip() + pygame.event.pump() + going = True + idx = 0 + while going: + events = pygame.event.get() + for e in events: + if e.type == QUIT: + going = False + if e.type == KEYDOWN: + if e.key in [K_s, K_a]: + if e.key == K_s: + idx += 1 + if e.key == K_a: + idx -= 1 + s = images[idx] + screen.blit(s, (0, 0)) + pygame.display.flip() + pygame.event.pump() + elif e.key in [K_ESCAPE]: + going = False + pygame.display.quit() + pygame.display.init() + + +def threshold( + return_surf, + surf, + color, + threshold=(0, 0, 0), + diff_color=(0, 0, 0), + change_return=True, +): + """given the color it makes return_surf only have areas with the given colour.""" + + width, height = surf.get_width(), surf.get_height() + + if change_return: + return_surf.fill(diff_color) + + try: + r, g, b = color + except ValueError: + r, g, b, a = color + + try: + tr, tg, tb = color + except ValueError: + tr, tg, tb, ta = color + + similar = 0 + for y in range(height): + for x in range(width): + c1 = surf.get_at((x, y)) + + if (abs(c1[0] - r) < tr) & (abs(c1[1] - g) < tg) & (abs(c1[2] - b) < tb): + # this pixel is within the threshold. + if change_return: + return_surf.set_at((x, y), c1) + similar += 1 + # else: + # print c1, c2 + + return similar + + +class TransformModuleTest(unittest.TestCase): + def test_scale__alpha(self): + """see if set_alpha information is kept.""" + + s = pygame.Surface((32, 32)) + s.set_alpha(55) + self.assertEqual(s.get_alpha(), 55) + + s = pygame.Surface((32, 32)) + s.set_alpha(55) + s2 = pygame.transform.scale(s, (64, 64)) + s3 = s.copy() + self.assertEqual(s.get_alpha(), s3.get_alpha()) + self.assertEqual(s.get_alpha(), s2.get_alpha()) + + def test_scale__destination(self): + """see if the destination surface can be passed in to use.""" + + s = pygame.Surface((32, 32)) + s2 = pygame.transform.scale(s, (64, 64)) + s3 = s2.copy() + + # Also validate keyword arguments + s3 = pygame.transform.scale(surface=s, size=(64, 64), dest_surface=s3) + pygame.transform.scale(s, (64, 64), s2) + + # the wrong size surface is past in. Should raise an error. + self.assertRaises(ValueError, pygame.transform.scale, s, (33, 64), s3) + + s = pygame.Surface((32, 32)) + s2 = pygame.transform.smoothscale(s, (64, 64)) + s3 = s2.copy() + + # Also validate keyword arguments + s3 = pygame.transform.smoothscale(surface=s, size=(64, 64), dest_surface=s3) + + # the wrong size surface is past in. Should raise an error. + self.assertRaises(ValueError, pygame.transform.smoothscale, s, (33, 64), s3) + + def test_scale__vector2(self): + s = pygame.Surface((32, 32)) + s2 = pygame.transform.scale(s, pygame.Vector2(64, 64)) + s3 = pygame.transform.smoothscale(s, pygame.Vector2(64, 64)) + + self.assertEqual((64, 64), s2.get_size()) + self.assertEqual((64, 64), s3.get_size()) + + def test_scale__zero_surface_transform(self): + tmp_surface = pygame.transform.scale(pygame.Surface((128, 128)), (0, 0)) + self.assertEqual(tmp_surface.get_size(), (0, 0)) + tmp_surface = pygame.transform.scale(tmp_surface, (128, 128)) + self.assertEqual(tmp_surface.get_size(), (128, 128)) + + def test_threshold__honors_third_surface(self): + # __doc__ for threshold as of Tue 07/15/2008 + + # pygame.transform.threshold(DestSurface, Surface, color, threshold = + # (0,0,0,0), diff_color = (0,0,0,0), change_return = True, Surface = + # None): return num_threshold_pixels + + # When given the optional third + # surface, it would use the colors in that rather than the "color" + # specified in the function to check against. + + # New in pygame 1.8 + + ################################################################ + # Sizes + (w, h) = size = (32, 32) + + # the original_color is within the threshold of the threshold_color + threshold = (20, 20, 20, 20) + + original_color = (25, 25, 25, 25) + threshold_color = (10, 10, 10, 10) + + # Surfaces + original_surface = pygame.Surface(size, pygame.SRCALPHA, 32) + dest_surface = pygame.Surface(size, pygame.SRCALPHA, 32) + + # Third surface is used in lieu of 3rd position arg color + third_surface = pygame.Surface(size, pygame.SRCALPHA, 32) + + # Color filling + original_surface.fill(original_color) + third_surface.fill(threshold_color) + + ################################################################ + # All pixels for color should be within threshold + # + pixels_within_threshold = pygame.transform.threshold( + dest_surface=None, + surface=original_surface, + search_color=threshold_color, + threshold=threshold, + set_color=None, + set_behavior=0, + ) + + self.assertEqual(w * h, pixels_within_threshold) + + ################################################################ + # This should respect third_surface colors in place of 3rd arg + # color Should be the same as: surface.fill(threshold_color) + # all within threshold + + pixels_within_threshold = pygame.transform.threshold( + dest_surface=None, + surface=original_surface, + search_color=None, + threshold=threshold, + set_color=None, + set_behavior=0, + search_surf=third_surface, + ) + self.assertEqual(w * h, pixels_within_threshold) + + def test_threshold_dest_surf_not_change(self): + """the pixels within the threshold. + + All pixels not within threshold are changed to set_color. + So there should be none changed in this test. + """ + (w, h) = size = (32, 32) + threshold = (20, 20, 20, 20) + original_color = (25, 25, 25, 25) + original_dest_color = (65, 65, 65, 55) + threshold_color = (10, 10, 10, 10) + set_color = (255, 10, 10, 10) + + surf = pygame.Surface(size, pygame.SRCALPHA, 32) + dest_surf = pygame.Surface(size, pygame.SRCALPHA, 32) + search_surf = pygame.Surface(size, pygame.SRCALPHA, 32) + + surf.fill(original_color) + search_surf.fill(threshold_color) + dest_surf.fill(original_dest_color) + + # set_behavior=1, set dest_surface from set_color. + # all within threshold of third_surface, so no color is set. + + THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR = 1 + pixels_within_threshold = pygame.transform.threshold( + dest_surface=dest_surf, + surface=surf, + search_color=None, + threshold=threshold, + set_color=set_color, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR, + search_surf=search_surf, + ) + + # # Return, of pixels within threshold is correct + self.assertEqual(w * h, pixels_within_threshold) + + # # Size of dest surface is correct + dest_rect = dest_surf.get_rect() + dest_size = dest_rect.size + self.assertEqual(size, dest_size) + + # The color is not the change_color specified for every pixel As all + # pixels are within threshold + + for pt in test_utils.rect_area_pts(dest_rect): + self.assertNotEqual(dest_surf.get_at(pt), set_color) + self.assertEqual(dest_surf.get_at(pt), original_dest_color) + + def test_threshold_dest_surf_all_changed(self): + """Lowering the threshold, expecting changed surface""" + + (w, h) = size = (32, 32) + threshold = (20, 20, 20, 20) + original_color = (25, 25, 25, 25) + original_dest_color = (65, 65, 65, 55) + threshold_color = (10, 10, 10, 10) + set_color = (255, 10, 10, 10) + + surf = pygame.Surface(size, pygame.SRCALPHA, 32) + dest_surf = pygame.Surface(size, pygame.SRCALPHA, 32) + search_surf = pygame.Surface(size, pygame.SRCALPHA, 32) + + surf.fill(original_color) + search_surf.fill(threshold_color) + dest_surf.fill(original_dest_color) + + THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR = 1 + pixels_within_threshold = pygame.transform.threshold( + dest_surf, + surf, + search_color=None, + set_color=set_color, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR, + search_surf=search_surf, + ) + + self.assertEqual(0, pixels_within_threshold) + + dest_rect = dest_surf.get_rect() + dest_size = dest_rect.size + self.assertEqual(size, dest_size) + + # The color is the set_color specified for every pixel As all + # pixels are not within threshold + for pt in test_utils.rect_area_pts(dest_rect): + self.assertEqual(dest_surf.get_at(pt), set_color) + + def test_threshold_count(self): + """counts the colors, and not changes them.""" + surf_size = (32, 32) + surf = pygame.Surface(surf_size, pygame.SRCALPHA, 32) + search_surf = pygame.Surface(surf_size, pygame.SRCALPHA, 32) + search_color = (55, 55, 55, 255) + original_color = (10, 10, 10, 255) + + surf.fill(original_color) + # set 2 pixels to the color we are searching for. + surf.set_at((0, 0), search_color) + surf.set_at((12, 5), search_color) + + # There is no destination surface, but we ask to change it. + # This should be an error. + self.assertRaises( + TypeError, pygame.transform.threshold, None, surf, search_color + ) + # from pygame.transform import THRESHOLD_BEHAVIOR_COUNT + THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF = 2 + self.assertRaises( + TypeError, + pygame.transform.threshold, + None, + surf, + search_color, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + ) + + THRESHOLD_BEHAVIOR_COUNT = 0 + num_threshold_pixels = pygame.transform.threshold( + dest_surface=None, + surface=surf, + search_color=search_color, + set_behavior=THRESHOLD_BEHAVIOR_COUNT, + ) + self.assertEqual(num_threshold_pixels, 2) + + def test_threshold_search_surf(self): + surf_size = (32, 32) + surf = pygame.Surface(surf_size, pygame.SRCALPHA, 32) + search_surf = pygame.Surface(surf_size, pygame.SRCALPHA, 32) + dest_surf = pygame.Surface(surf_size, pygame.SRCALPHA, 32) + + original_color = (10, 10, 10, 255) + search_color = (55, 55, 55, 255) + + surf.fill(original_color) + dest_surf.fill(original_color) + # set 2 pixels to the color we are searching for. + surf.set_at((0, 0), search_color) + surf.set_at((12, 5), search_color) + + search_surf.fill(search_color) + + # We look in the other surface for matching colors. + # Change it in dest_surf + THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF = 2 + + # TypeError: if search_surf is used, search_color should be None + self.assertRaises( + TypeError, + pygame.transform.threshold, + dest_surf, + surf, + search_color, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + search_surf=search_surf, + ) + + # surf, dest_surf, and search_surf should all be the same size. + # Check surface sizes are the same size. + different_sized_surf = pygame.Surface((22, 33), pygame.SRCALPHA, 32) + self.assertRaises( + TypeError, + pygame.transform.threshold, + different_sized_surf, + surf, + search_color=None, + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + search_surf=search_surf, + ) + + self.assertRaises( + TypeError, + pygame.transform.threshold, + dest_surf, + surf, + search_color=None, + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + search_surf=different_sized_surf, + ) + + # We look to see if colors in search_surf are in surf. + num_threshold_pixels = pygame.transform.threshold( + dest_surface=dest_surf, + surface=surf, + search_color=None, + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + search_surf=search_surf, + ) + + num_pixels_within = 2 + self.assertEqual(num_threshold_pixels, num_pixels_within) + + dest_surf.fill(original_color) + num_threshold_pixels = pygame.transform.threshold( + dest_surf, + surf, + search_color=None, + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + search_surf=search_surf, + inverse_set=True, + ) + + self.assertEqual(num_threshold_pixels, 2) + + def test_threshold_inverse_set(self): + """changes the pixels within the threshold, and not outside.""" + surf_size = (32, 32) + _dest_surf = pygame.Surface(surf_size, pygame.SRCALPHA, 32) + _surf = pygame.Surface(surf_size, pygame.SRCALPHA, 32) + + dest_surf = _dest_surf # surface we are changing. + surf = _surf # surface we are looking at + search_color = (55, 55, 55, 255) # color we are searching for. + threshold = (0, 0, 0, 0) # within this distance from search_color. + set_color = (245, 245, 245, 255) # color we set. + inverse_set = 1 # pixels within threshold are changed to 'set_color' + + original_color = (10, 10, 10, 255) + surf.fill(original_color) + # set 2 pixels to the color we are searching for. + surf.set_at((0, 0), search_color) + surf.set_at((12, 5), search_color) + + dest_surf.fill(original_color) + # set 2 pixels to the color we are searching for. + dest_surf.set_at((0, 0), search_color) + dest_surf.set_at((12, 5), search_color) + + THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR = 1 + num_threshold_pixels = pygame.transform.threshold( + dest_surf, + surf, + search_color=search_color, + threshold=threshold, + set_color=set_color, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR, + inverse_set=1, + ) + + self.assertEqual(num_threshold_pixels, 2) + # only two pixels changed to diff_color. + self.assertEqual(dest_surf.get_at((0, 0)), set_color) + self.assertEqual(dest_surf.get_at((12, 5)), set_color) + + # other pixels should be the same as they were before. + # We just check one other pixel, not all of them. + self.assertEqual(dest_surf.get_at((2, 2)), original_color) + + # XXX + def test_threshold_non_src_alpha(self): + + result = pygame.Surface((10, 10)) + s1 = pygame.Surface((10, 10)) + s2 = pygame.Surface((10, 10)) + s3 = pygame.Surface((10, 10)) + s4 = pygame.Surface((10, 10)) + + x = s1.fill((0, 0, 0)) + s1.set_at((0, 0), (32, 20, 0)) + + x = s2.fill((0, 20, 0)) + x = s3.fill((0, 0, 0)) + x = s4.fill((0, 0, 0)) + s2.set_at((0, 0), (33, 21, 0)) + s2.set_at((3, 0), (63, 61, 0)) + s3.set_at((0, 0), (112, 31, 0)) + s4.set_at((0, 0), (11, 31, 0)) + s4.set_at((1, 1), (12, 31, 0)) + + self.assertEqual(s1.get_at((0, 0)), (32, 20, 0, 255)) + self.assertEqual(s2.get_at((0, 0)), (33, 21, 0, 255)) + self.assertEqual((0, 0), (s1.get_flags(), s2.get_flags())) + + similar_color = (255, 255, 255, 255) + diff_color = (222, 0, 0, 255) + threshold_color = (20, 20, 20, 255) + + THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR = 1 + num_threshold_pixels = pygame.transform.threshold( + dest_surface=result, + surface=s1, + search_color=similar_color, + threshold=threshold_color, + set_color=diff_color, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR, + ) + self.assertEqual(num_threshold_pixels, 0) + + num_threshold_pixels = pygame.transform.threshold( + dest_surface=result, + surface=s1, + search_color=(40, 40, 0), + threshold=threshold_color, + set_color=diff_color, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_COLOR, + ) + self.assertEqual(num_threshold_pixels, 1) + + self.assertEqual(result.get_at((0, 0)), diff_color) + + def test_threshold__uneven_colors(self): + (w, h) = size = (16, 16) + + original_surface = pygame.Surface(size, pygame.SRCALPHA, 32) + dest_surface = pygame.Surface(size, pygame.SRCALPHA, 32) + + original_surface.fill(0) + + threshold_color_template = [5, 5, 5, 5] + threshold_template = [6, 6, 6, 6] + + ################################################################ + + for pos in range(len("rgb")): + threshold_color = threshold_color_template[:] + threshold = threshold_template[:] + + threshold_color[pos] = 45 + threshold[pos] = 50 + + pixels_within_threshold = pygame.transform.threshold( + None, + original_surface, + threshold_color, + threshold, + set_color=None, + set_behavior=0, + ) + + self.assertEqual(w * h, pixels_within_threshold) + + ################################################################ + + def test_threshold_set_behavior2(self): + """raises an error when set_behavior=2 and set_color is not None.""" + from pygame.transform import threshold + + s1 = pygame.Surface((32, 32), SRCALPHA, 32) + s2 = pygame.Surface((32, 32), SRCALPHA, 32) + THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF = 2 + self.assertRaises( + TypeError, + threshold, + dest_surface=s2, + surface=s1, + search_color=(30, 30, 30), + threshold=(11, 11, 11), + set_color=(255, 0, 0), + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + ) + + def test_threshold_set_behavior0(self): + """raises an error when set_behavior=1 + and set_color is not None, + and dest_surf is not None. + """ + from pygame.transform import threshold + + s1 = pygame.Surface((32, 32), SRCALPHA, 32) + s2 = pygame.Surface((32, 32), SRCALPHA, 32) + THRESHOLD_BEHAVIOR_COUNT = 0 + + self.assertRaises( + TypeError, + threshold, + dest_surface=None, + surface=s2, + search_color=(30, 30, 30), + threshold=(11, 11, 11), + set_color=(0, 0, 0), + set_behavior=THRESHOLD_BEHAVIOR_COUNT, + ) + + self.assertRaises( + TypeError, + threshold, + dest_surface=s1, + surface=s2, + search_color=(30, 30, 30), + threshold=(11, 11, 11), + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_COUNT, + ) + + threshold( + dest_surface=None, + surface=s2, + search_color=(30, 30, 30), + threshold=(11, 11, 11), + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_COUNT, + ) + + def test_threshold_from_surface(self): + """Set similar pixels in 'dest_surf' to color in the 'surf'.""" + from pygame.transform import threshold + + surf = pygame.Surface((32, 32), SRCALPHA, 32) + dest_surf = pygame.Surface((32, 32), SRCALPHA, 32) + surf_color = (40, 40, 40, 255) + dest_color = (255, 255, 255) + surf.fill(surf_color) + dest_surf.fill(dest_color) + THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF = 2 + + num_threshold_pixels = threshold( + dest_surface=dest_surf, + surface=surf, + search_color=(30, 30, 30), + threshold=(11, 11, 11), + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF, + inverse_set=1, + ) + + self.assertEqual( + num_threshold_pixels, dest_surf.get_height() * dest_surf.get_width() + ) + self.assertEqual(dest_surf.get_at((0, 0)), surf_color) + + def test_threshold__surface(self): + """ """ + from pygame.transform import threshold + + s1 = pygame.Surface((32, 32), SRCALPHA, 32) + s2 = pygame.Surface((32, 32), SRCALPHA, 32) + s3 = pygame.Surface((1, 1), SRCALPHA, 32) + THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF = 2 + + # # only one pixel should not be changed. + # s1.fill((40,40,40)) + # s2.fill((255,255,255)) + # s1.set_at( (0,0), (170, 170, 170) ) + # # set the similar pixels in destination surface to the color + # # in the first surface. + # num_threshold_pixels = threshold( + # dest_surface=s2, + # surface=s1, + # search_color=(30,30,30), + # threshold=(11,11,11), + # set_color=None, + # set_behavior=THRESHOLD_BEHAVIOR_FROM_SEARCH_SURF) + + # #num_threshold_pixels = threshold(s2, s1, (30,30,30)) + # self.assertEqual(num_threshold_pixels, (s1.get_height() * s1.get_width()) -1) + # self.assertEqual(s2.get_at((0,0)), (0,0,0, 255)) + # self.assertEqual(s2.get_at((0,1)), (40, 40, 40, 255)) + # self.assertEqual(s2.get_at((17,1)), (40, 40, 40, 255)) + + # # abs(40 - 255) < 100 + # #(abs(c1[0] - r) < tr) + + # s1.fill((160,160,160)) + # s2.fill((255,255,255)) + # num_threshold_pixels = threshold(s2, s1, (255,255,255), (100,100,100), (0,0,0), True) + + # self.assertEqual(num_threshold_pixels, (s1.get_height() * s1.get_width())) + + # only one pixel should not be changed. + s1.fill((40, 40, 40)) + s1.set_at((0, 0), (170, 170, 170)) + THRESHOLD_BEHAVIOR_COUNT = 0 + + num_threshold_pixels = threshold( + dest_surface=None, + surface=s1, + search_color=(30, 30, 30), + threshold=(11, 11, 11), + set_color=None, + set_behavior=THRESHOLD_BEHAVIOR_COUNT, + ) + + # num_threshold_pixels = threshold(s2, s1, (30,30,30)) + self.assertEqual(num_threshold_pixels, (s1.get_height() * s1.get_width()) - 1) + + # test end markers. 0, and 255 + + # the pixels are different by 1. + s1.fill((254, 254, 254)) + s2.fill((255, 255, 255)) + s3.fill((255, 255, 255)) + s1.set_at((0, 0), (170, 170, 170)) + num_threshold_pixels = threshold( + None, s1, (254, 254, 254), (1, 1, 1), None, THRESHOLD_BEHAVIOR_COUNT + ) + self.assertEqual(num_threshold_pixels, (s1.get_height() * s1.get_width()) - 1) + + # compare the two surfaces. Should be all but one matching. + num_threshold_pixels = threshold( + None, s1, None, (1, 1, 1), None, THRESHOLD_BEHAVIOR_COUNT, s2 + ) + self.assertEqual(num_threshold_pixels, (s1.get_height() * s1.get_width()) - 1) + + # within (0,0,0) threshold? Should match no pixels. + num_threshold_pixels = threshold( + None, s1, (253, 253, 253), (0, 0, 0), None, THRESHOLD_BEHAVIOR_COUNT + ) + self.assertEqual(num_threshold_pixels, 0) + + # other surface within (0,0,0) threshold? Should match no pixels. + num_threshold_pixels = threshold( + None, s1, None, (0, 0, 0), None, THRESHOLD_BEHAVIOR_COUNT, s2 + ) + self.assertEqual(num_threshold_pixels, 0) + + def test_threshold__subclassed_surface(self): + """Ensure threshold accepts subclassed surfaces.""" + expected_size = (13, 11) + expected_flags = 0 + expected_depth = 32 + expected_color = (90, 80, 70, 255) + expected_count = 0 + surface = test_utils.SurfaceSubclass( + expected_size, expected_flags, expected_depth + ) + dest_surface = test_utils.SurfaceSubclass( + expected_size, expected_flags, expected_depth + ) + search_surface = test_utils.SurfaceSubclass( + expected_size, expected_flags, expected_depth + ) + surface.fill((10, 10, 10)) + dest_surface.fill((255, 255, 255)) + search_surface.fill((20, 20, 20)) + + count = pygame.transform.threshold( + dest_surface=dest_surface, + surface=surface, + threshold=(1, 1, 1), + set_color=expected_color, + search_color=None, + search_surf=search_surface, + ) + + self.assertIsInstance(dest_surface, pygame.Surface) + self.assertIsInstance(dest_surface, test_utils.SurfaceSubclass) + self.assertEqual(count, expected_count) + self.assertEqual(dest_surface.get_at((0, 0)), expected_color) + self.assertEqual(dest_surface.get_bitsize(), expected_depth) + self.assertEqual(dest_surface.get_size(), expected_size) + self.assertEqual(dest_surface.get_flags(), expected_flags) + + def test_laplacian(self): + """ """ + + SIZE = 32 + s1 = pygame.Surface((SIZE, SIZE)) + s2 = pygame.Surface((SIZE, SIZE)) + s1.fill((10, 10, 70)) + pygame.draw.line(s1, (255, 0, 0), (3, 10), (20, 20)) + + # a line at the last row of the image. + pygame.draw.line(s1, (255, 0, 0), (0, 31), (31, 31)) + + pygame.transform.laplacian(s1, s2) + + # show_image(s1) + # show_image(s2) + + self.assertEqual(s2.get_at((0, 0)), (0, 0, 0, 255)) + self.assertEqual(s2.get_at((3, 10)), (255, 0, 0, 255)) + self.assertEqual(s2.get_at((0, 31)), (255, 0, 0, 255)) + self.assertEqual(s2.get_at((31, 31)), (255, 0, 0, 255)) + + # here we create the return surface. + s2 = pygame.transform.laplacian(s1) + + self.assertEqual(s2.get_at((0, 0)), (0, 0, 0, 255)) + self.assertEqual(s2.get_at((3, 10)), (255, 0, 0, 255)) + self.assertEqual(s2.get_at((0, 31)), (255, 0, 0, 255)) + self.assertEqual(s2.get_at((31, 31)), (255, 0, 0, 255)) + + def test_laplacian__24_big_endian(self): + """ """ + pygame.display.init() + try: + surf_1 = pygame.image.load( + example_path(os.path.join("data", "laplacian.png")) + ) + SIZE = 32 + surf_2 = pygame.Surface((SIZE, SIZE), 0, 24) + # s1.fill((10, 10, 70)) + # pygame.draw.line(s1, (255, 0, 0), (3, 10), (20, 20)) + + # a line at the last row of the image. + # pygame.draw.line(s1, (255, 0, 0), (0, 31), (31, 31)) + + # Also validate keyword arguments + pygame.transform.laplacian(surface=surf_1, dest_surface=surf_2) + + # show_image(s1) + # show_image(s2) + + self.assertEqual(surf_2.get_at((0, 0)), (0, 0, 0, 255)) + self.assertEqual(surf_2.get_at((3, 10)), (255, 0, 0, 255)) + self.assertEqual(surf_2.get_at((0, 31)), (255, 0, 0, 255)) + self.assertEqual(surf_2.get_at((31, 31)), (255, 0, 0, 255)) + + # here we create the return surface. + surf_2 = pygame.transform.laplacian(surf_1) + + self.assertEqual(surf_2.get_at((0, 0)), (0, 0, 0, 255)) + self.assertEqual(surf_2.get_at((3, 10)), (255, 0, 0, 255)) + self.assertEqual(surf_2.get_at((0, 31)), (255, 0, 0, 255)) + self.assertEqual(surf_2.get_at((31, 31)), (255, 0, 0, 255)) + finally: + pygame.display.quit() + + def test_average_surfaces(self): + """ """ + + SIZE = 32 + s1 = pygame.Surface((SIZE, SIZE)) + s2 = pygame.Surface((SIZE, SIZE)) + s3 = pygame.Surface((SIZE, SIZE)) + s1.fill((10, 10, 70)) + s2.fill((10, 20, 70)) + s3.fill((10, 130, 10)) + + surfaces = [s1, s2, s3] + surfaces = [s1, s2] + sr = pygame.transform.average_surfaces(surfaces) + + self.assertEqual(sr.get_at((0, 0)), (10, 15, 70, 255)) + + self.assertRaises(TypeError, pygame.transform.average_surfaces, 1) + self.assertRaises(TypeError, pygame.transform.average_surfaces, []) + + self.assertRaises(TypeError, pygame.transform.average_surfaces, [1]) + self.assertRaises(TypeError, pygame.transform.average_surfaces, [s1, 1]) + self.assertRaises(TypeError, pygame.transform.average_surfaces, [1, s1]) + self.assertRaises(TypeError, pygame.transform.average_surfaces, [s1, s2, 1]) + + self.assertRaises( + TypeError, pygame.transform.average_surfaces, (s for s in [s1, s2, s3]) + ) + + def test_average_surfaces__24(self): + + SIZE = 32 + depth = 24 + s1 = pygame.Surface((SIZE, SIZE), 0, depth) + s2 = pygame.Surface((SIZE, SIZE), 0, depth) + s3 = pygame.Surface((SIZE, SIZE), 0, depth) + s1.fill((10, 10, 70, 255)) + s2.fill((10, 20, 70, 255)) + s3.fill((10, 130, 10, 255)) + + surfaces = [s1, s2, s3] + sr = pygame.transform.average_surfaces(surfaces) + self.assertEqual(sr.get_masks(), s1.get_masks()) + self.assertEqual(sr.get_flags(), s1.get_flags()) + self.assertEqual(sr.get_losses(), s1.get_losses()) + + if 0: + print(sr, s1) + print(sr.get_masks(), s1.get_masks()) + print(sr.get_flags(), s1.get_flags()) + print(sr.get_losses(), s1.get_losses()) + print(sr.get_shifts(), s1.get_shifts()) + + self.assertEqual(sr.get_at((0, 0)), (10, 53, 50, 255)) + + def test_average_surfaces__24_big_endian(self): + pygame.display.init() + try: + surf_1 = pygame.image.load(example_path(os.path.join("data", "BGR.png"))) + + surf_2 = surf_1.copy() + + surfaces = [surf_1, surf_2] + self.assertEqual(surf_1.get_at((0, 0)), (255, 0, 0, 255)) + self.assertEqual(surf_2.get_at((0, 0)), (255, 0, 0, 255)) + + surf_av = pygame.transform.average_surfaces(surfaces) + self.assertEqual(surf_av.get_masks(), surf_1.get_masks()) + self.assertEqual(surf_av.get_flags(), surf_1.get_flags()) + self.assertEqual(surf_av.get_losses(), surf_1.get_losses()) + + self.assertEqual(surf_av.get_at((0, 0)), (255, 0, 0, 255)) + finally: + pygame.display.quit() + + def test_average_surfaces__subclassed_surfaces(self): + """Ensure average_surfaces accepts subclassed surfaces.""" + expected_size = (23, 17) + expected_flags = 0 + expected_depth = 32 + expected_color = (50, 50, 50, 255) + surfaces = [] + + for color in ((40, 60, 40), (60, 40, 60)): + s = test_utils.SurfaceSubclass( + expected_size, expected_flags, expected_depth + ) + s.fill(color) + surfaces.append(s) + + surface = pygame.transform.average_surfaces(surfaces) + + self.assertIsInstance(surface, pygame.Surface) + self.assertNotIsInstance(surface, test_utils.SurfaceSubclass) + self.assertEqual(surface.get_at((0, 0)), expected_color) + self.assertEqual(surface.get_bitsize(), expected_depth) + self.assertEqual(surface.get_size(), expected_size) + self.assertEqual(surface.get_flags(), expected_flags) + + def test_average_surfaces__subclassed_destination_surface(self): + """Ensure average_surfaces accepts a destination subclassed surface.""" + expected_size = (13, 27) + expected_flags = 0 + expected_depth = 32 + expected_color = (15, 15, 15, 255) + surfaces = [] + + for color in ((10, 10, 20), (20, 20, 10), (30, 30, 30)): + s = test_utils.SurfaceSubclass( + expected_size, expected_flags, expected_depth + ) + s.fill(color) + surfaces.append(s) + expected_dest_surface = surfaces.pop() + + # Also validate keyword arguments + dest_surface = pygame.transform.average_surfaces( + surfaces=surfaces, dest_surface=expected_dest_surface + ) + + self.assertIsInstance(dest_surface, pygame.Surface) + self.assertIsInstance(dest_surface, test_utils.SurfaceSubclass) + self.assertIs(dest_surface, expected_dest_surface) + self.assertEqual(dest_surface.get_at((0, 0)), expected_color) + self.assertEqual(dest_surface.get_bitsize(), expected_depth) + self.assertEqual(dest_surface.get_size(), expected_size) + self.assertEqual(dest_surface.get_flags(), expected_flags) + + def test_average_color(self): + """ """ + for i in (24, 32): + with self.subTest(f"Testing {i}-bit surface"): + s = pygame.Surface((32, 32), 0, i) + s.fill((0, 100, 200)) + s.fill((10, 50, 100), (0, 0, 16, 32)) + + self.assertEqual(pygame.transform.average_color(s), (5, 75, 150, 0)) + + # Also validate keyword arguments + avg_color = pygame.transform.average_color( + surface=s, rect=(16, 0, 16, 32) + ) + self.assertEqual(avg_color, (0, 100, 200, 0)) + + def test_rotate(self): + # setting colors and canvas + blue = (0, 0, 255, 255) + red = (255, 0, 0, 255) + black = (0, 0, 0) + canvas = pygame.Surface((3, 3)) + rotation = 0 + + canvas.set_at((2, 0), blue) + canvas.set_at((0, 2), red) + + self.assertEqual(canvas.get_at((0, 0)), black) + self.assertEqual(canvas.get_at((2, 0)), blue) + self.assertEqual(canvas.get_at((0, 2)), red) + + for i in range(0, 4): + if i % 2 == 0: + self.assertEqual(canvas.get_at((0, 0)), black) + elif i == 1: + self.assertEqual(canvas.get_at((0, 0)), blue) + elif i == 3: + self.assertEqual(canvas.get_at((0, 0)), red) + + rotation += 90 + # Also validate keyword arguments + canvas = pygame.transform.rotate(surface=canvas, angle=90) + + self.assertEqual(canvas.get_at((0, 0)), black) + + def test_rotate_of_0_sized_surface(self): + # This function just tests possible Segmentation Fault + canvas1 = pygame.Surface((0, 1)) + canvas2 = pygame.Surface((1, 0)) + pygame.transform.rotate(canvas1, 42) + pygame.transform.rotate(canvas2, 42) + + def test_rotate__lossless_at_90_degrees(self): + w, h = 32, 32 + s = pygame.Surface((w, h), pygame.SRCALPHA) + + gradient = list(test_utils.gradient(w, h)) + + for pt, color in gradient: + s.set_at(pt, color) + + for rotation in (90, -90): + s = pygame.transform.rotate(s, rotation) + + for pt, color in gradient: + self.assertTrue(s.get_at(pt) == color) + + def test_scale2x(self): + + # __doc__ (as of 2008-06-25) for pygame.transform.scale2x: + + # pygame.transform.scale2x(Surface, DestSurface = None): Surface + # specialized image doubler + + w, h = 32, 32 + s = pygame.Surface((w, h), pygame.SRCALPHA, 32) + + # s.set_at((0,0), (20, 20, 20, 255)) + + s1 = pygame.transform.scale2x(s) + # Also validate keyword arguments + s2 = pygame.transform.scale2x(surface=s) + self.assertEqual(s1.get_rect().size, (64, 64)) + self.assertEqual(s2.get_rect().size, (64, 64)) + + def test_scale2xraw(self): + w, h = 32, 32 + s = pygame.Surface((w, h), pygame.SRCALPHA, 32) + s.fill((0, 0, 0)) + pygame.draw.circle(s, (255, 0, 0), (w // 2, h // 2), (w // 3)) + + s2 = pygame.transform.scale(s, (w * 2, h * 2)) + s2_2 = pygame.transform.scale(s2, (w * 4, h * 4)) + s4 = pygame.transform.scale(s, (w * 4, h * 4)) + + self.assertEqual(s2_2.get_rect().size, (128, 128)) + + for pt in test_utils.rect_area_pts(s2_2.get_rect()): + self.assertEqual(s2_2.get_at(pt), s4.get_at(pt)) + + def test_get_smoothscale_backend(self): + filter_type = pygame.transform.get_smoothscale_backend() + self.assertTrue(filter_type in ["GENERIC", "MMX", "SSE"]) + # It would be nice to test if a non-generic type corresponds to an x86 + # processor. But there is no simple test for this. platform.machine() + # returns process version specific information, like 'i686'. + + def test_set_smoothscale_backend(self): + # All machines should allow 'GENERIC'. + original_type = pygame.transform.get_smoothscale_backend() + pygame.transform.set_smoothscale_backend("GENERIC") + filter_type = pygame.transform.get_smoothscale_backend() + self.assertEqual(filter_type, "GENERIC") + # All machines should allow returning to original value. + # Also check that keyword argument works. + pygame.transform.set_smoothscale_backend(backend=original_type) + # Something invalid. + def change(): + pygame.transform.set_smoothscale_backend("mmx") + + self.assertRaises(ValueError, change) + # Invalid argument keyword. + def change(): + pygame.transform.set_smoothscale_backend(t="GENERIC") + + self.assertRaises(TypeError, change) + # Invalid argument type. + def change(): + pygame.transform.set_smoothscale_backend(1) + + self.assertRaises(TypeError, change) + # Unsupported type, if possible. + if original_type != "SSE": + + def change(): + pygame.transform.set_smoothscale_backend("SSE") + + self.assertRaises(ValueError, change) + # Should be back where we started. + filter_type = pygame.transform.get_smoothscale_backend() + self.assertEqual(filter_type, original_type) + + def test_chop(self): + original_surface = pygame.Surface((20, 20)) + pygame.draw.rect(original_surface, (255, 0, 0), (0, 0, 10, 10)) + pygame.draw.rect(original_surface, (0, 255, 0), (0, 10, 10, 10)) + pygame.draw.rect(original_surface, (0, 0, 255), (10, 0, 10, 10)) + pygame.draw.rect(original_surface, (255, 255, 0), (10, 10, 10, 10)) + # Test chopping the corner of image + rect = pygame.Rect(0, 0, 5, 15) + test_surface = pygame.transform.chop(original_surface, rect) + # Check the size of chopped image + self.assertEqual(test_surface.get_size(), (15, 5)) + # Check if the colors of the chopped image are correct + for x in range(15): + for y in range(5): + if x < 5: + self.assertEqual(test_surface.get_at((x, y)), (0, 255, 0)) + else: + self.assertEqual(test_surface.get_at((x, y)), (255, 255, 0)) + # Check if the original image stayed the same + self.assertEqual(original_surface.get_size(), (20, 20)) + for x in range(20): + for y in range(20): + if x < 10 and y < 10: + self.assertEqual(original_surface.get_at((x, y)), (255, 0, 0)) + if x < 10 < y: + self.assertEqual(original_surface.get_at((x, y)), (0, 255, 0)) + if x > 10 > y: + self.assertEqual(original_surface.get_at((x, y)), (0, 0, 255)) + if x > 10 and y > 10: + self.assertEqual(original_surface.get_at((x, y)), (255, 255, 0)) + # Test chopping the center of the surface: + rect = pygame.Rect(0, 0, 10, 10) + rect.center = original_surface.get_rect().center + # Also validate keyword arguments + test_surface = pygame.transform.chop(surface=original_surface, rect=rect) + self.assertEqual(test_surface.get_size(), (10, 10)) + for x in range(10): + for y in range(10): + if x < 5 and y < 5: + self.assertEqual(test_surface.get_at((x, y)), (255, 0, 0)) + if x < 5 < y: + self.assertEqual(test_surface.get_at((x, y)), (0, 255, 0)) + if x > 5 > y: + self.assertEqual(test_surface.get_at((x, y)), (0, 0, 255)) + if x > 5 and y > 5: + self.assertEqual(test_surface.get_at((x, y)), (255, 255, 0)) + # Test chopping with the empty rect + rect = pygame.Rect(10, 10, 0, 0) + test_surface = pygame.transform.chop(original_surface, rect) + self.assertEqual(test_surface.get_size(), (20, 20)) + # Test chopping the entire surface + rect = pygame.Rect(0, 0, 20, 20) + test_surface = pygame.transform.chop(original_surface, rect) + self.assertEqual(test_surface.get_size(), (0, 0)) + # Test chopping outside of surface + rect = pygame.Rect(5, 15, 20, 20) + test_surface = pygame.transform.chop(original_surface, rect) + self.assertEqual(test_surface.get_size(), (5, 15)) + rect = pygame.Rect(400, 400, 10, 10) + test_surface = pygame.transform.chop(original_surface, rect) + self.assertEqual(test_surface.get_size(), (20, 20)) + + def test_rotozoom(self): + + # __doc__ (as of 2008-08-02) for pygame.transform.rotozoom: + + # pygame.transform.rotozoom(Surface, angle, scale): return Surface + # filtered scale and rotation + # + # This is a combined scale and rotation transform. The resulting + # Surface will be a filtered 32-bit Surface. The scale argument is a + # floating point value that will be multiplied by the current + # resolution. The angle argument is a floating point value that + # represents the counterclockwise degrees to rotate. A negative + # rotation angle will rotate clockwise. + + s = pygame.Surface((10, 0)) + pygame.transform.scale(s, (10, 2)) + s1 = pygame.transform.rotozoom(s, 30, 1) + # Also validate keyword arguments + s2 = pygame.transform.rotozoom(surface=s, angle=30, scale=1) + + self.assertEqual(s1.get_rect(), pygame.Rect(0, 0, 0, 0)) + self.assertEqual(s2.get_rect(), pygame.Rect(0, 0, 0, 0)) + + def test_smoothscale(self): + """Tests the stated boundaries, sizing, and color blending of smoothscale function""" + # __doc__ (as of 2008-08-02) for pygame.transform.smoothscale: + + # pygame.transform.smoothscale(Surface, (width, height), DestSurface = + # None): return Surface + # + # scale a surface to an arbitrary size smoothly + # + # Uses one of two different algorithms for scaling each dimension of + # the input surface as required. For shrinkage, the output pixels are + # area averages of the colors they cover. For expansion, a bilinear + # filter is used. For the amd64 and i686 architectures, optimized MMX + # routines are included and will run much faster than other machine + # types. The size is a 2 number sequence for (width, height). This + # function only works for 24-bit or 32-bit surfaces. An exception + # will be thrown if the input surface bit depth is less than 24. + # + # New in pygame 1.8 + + # check stated exceptions + def smoothscale_low_bpp(): + starting_surface = pygame.Surface((20, 20), depth=12) + smoothscaled_surface = pygame.transform.smoothscale( + starting_surface, (10, 10) + ) + + self.assertRaises(ValueError, smoothscale_low_bpp) + + def smoothscale_high_bpp(): + starting_surface = pygame.Surface((20, 20), depth=48) + smoothscaled_surface = pygame.transform.smoothscale( + starting_surface, (10, 10) + ) + + self.assertRaises(ValueError, smoothscale_high_bpp) + + def smoothscale_invalid_scale(): + starting_surface = pygame.Surface((20, 20), depth=32) + smoothscaled_surface = pygame.transform.smoothscale( + starting_surface, (-1, -1) + ) + + self.assertRaises(ValueError, smoothscale_invalid_scale) + + # Test Color Blending Scaling-Up + two_pixel_surface = pygame.Surface((2, 1), depth=32) + two_pixel_surface.fill(pygame.Color(0, 0, 0), pygame.Rect(0, 0, 1, 1)) + two_pixel_surface.fill(pygame.Color(255, 255, 255), pygame.Rect(1, 0, 1, 1)) + for k in [2 ** x for x in range(5, 8)]: # Enlarge to targets 32, 64...256 + bigger_surface = pygame.transform.smoothscale(two_pixel_surface, (k, 1)) + self.assertEqual( + bigger_surface.get_at((k // 2, 0)), pygame.Color(127, 127, 127) + ) + self.assertEqual(bigger_surface.get_size(), (k, 1)) + # Test Color Blending Scaling-Down + two_five_six_surf = pygame.Surface((256, 1), depth=32) + two_five_six_surf.fill(pygame.Color(0, 0, 0), pygame.Rect(0, 0, 128, 1)) + two_five_six_surf.fill(pygame.Color(255, 255, 255), pygame.Rect(128, 0, 128, 1)) + for k in range(3, 11, 2): # Shrink to targets 3, 5...11 pixels wide + smaller_surface = pygame.transform.smoothscale(two_five_six_surf, (k, 1)) + self.assertEqual( + smaller_surface.get_at(((k // 2), 0)), pygame.Color(127, 127, 127) + ) + self.assertEqual(smaller_surface.get_size(), (k, 1)) + + +class TransformDisplayModuleTest(unittest.TestCase): + def setUp(self): + pygame.display.init() + pygame.display.set_mode((320, 200)) + + def tearDown(self): + pygame.display.quit() + + def test_flip(self): + """honors the set_color key on the returned surface from flip.""" + image_loaded = pygame.image.load(example_path("data/chimp.png")) + + image = pygame.Surface(image_loaded.get_size(), 0, 32) + image.blit(image_loaded, (0, 0)) + + image_converted = image_loaded.convert() + + self.assertFalse(image.get_flags() & pygame.SRCALPHA) + self.assertFalse(image_converted.get_flags() & pygame.SRCALPHA) + + surf = pygame.Surface(image.get_size(), 0, 32) + surf2 = pygame.Surface(image.get_size(), 0, 32) + + surf.fill((255, 255, 255)) + surf2.fill((255, 255, 255)) + + colorkey = image.get_at((0, 0)) + image.set_colorkey(colorkey, RLEACCEL) + timage = pygame.transform.flip(image, 1, 0) + + colorkey = image_converted.get_at((0, 0)) + image_converted.set_colorkey(colorkey, RLEACCEL) + # Also validate keyword arguments + timage_converted = pygame.transform.flip( + surface=image_converted, flip_x=1, flip_y=0 + ) + + # blit the flipped surface, and non flipped surface. + surf.blit(timage, (0, 0)) + surf2.blit(image, (0, 0)) + + # the results should be the same. + self.assertEqual(surf.get_at((0, 0)), surf2.get_at((0, 0))) + self.assertEqual(surf2.get_at((0, 0)), (255, 255, 255, 255)) + + # now we test the convert() ed image also works. + surf.fill((255, 255, 255)) + surf2.fill((255, 255, 255)) + surf.blit(timage_converted, (0, 0)) + surf2.blit(image_converted, (0, 0)) + self.assertEqual(surf.get_at((0, 0)), surf2.get_at((0, 0))) + + def test_flip_alpha(self): + """returns a surface with the same properties as the input.""" + image_loaded = pygame.image.load(example_path("data/chimp.png")) + + image_alpha = pygame.Surface(image_loaded.get_size(), pygame.SRCALPHA, 32) + image_alpha.blit(image_loaded, (0, 0)) + + surf = pygame.Surface(image_loaded.get_size(), 0, 32) + surf2 = pygame.Surface(image_loaded.get_size(), 0, 32) + + colorkey = image_alpha.get_at((0, 0)) + image_alpha.set_colorkey(colorkey, RLEACCEL) + timage_alpha = pygame.transform.flip(image_alpha, 1, 0) + + self.assertTrue(image_alpha.get_flags() & pygame.SRCALPHA) + self.assertTrue(timage_alpha.get_flags() & pygame.SRCALPHA) + + # now we test the alpha image works. + surf.fill((255, 255, 255)) + surf2.fill((255, 255, 255)) + surf.blit(timage_alpha, (0, 0)) + surf2.blit(image_alpha, (0, 0)) + self.assertEqual(surf.get_at((0, 0)), surf2.get_at((0, 0))) + self.assertEqual(surf2.get_at((0, 0)), (255, 0, 0, 255)) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/version_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/version_test.py new file mode 100644 index 0000000..ba0bb3d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/version_test.py @@ -0,0 +1,48 @@ +import os +import unittest + + +pg_header = os.path.join("src_c", "include", "_pygame.h") + + +class VersionTest(unittest.TestCase): + @unittest.skipIf( + not os.path.isfile(pg_header), "Skipping because we cannot find _pygame.h" + ) + def test_pg_version_consistency(self): + from pygame import version + + pgh_major = -1 + pgh_minor = -1 + pgh_patch = -1 + import re + + major_exp_search = re.compile(r"define\s+PG_MAJOR_VERSION\s+([0-9]+)").search + minor_exp_search = re.compile(r"define\s+PG_MINOR_VERSION\s+([0-9]+)").search + patch_exp_search = re.compile(r"define\s+PG_PATCH_VERSION\s+([0-9]+)").search + with open(pg_header) as f: + for line in f: + if pgh_major == -1: + m = major_exp_search(line) + if m: + pgh_major = int(m.group(1)) + if pgh_minor == -1: + m = minor_exp_search(line) + if m: + pgh_minor = int(m.group(1)) + if pgh_patch == -1: + m = patch_exp_search(line) + if m: + pgh_patch = int(m.group(1)) + self.assertEqual(pgh_major, version.vernum[0]) + self.assertEqual(pgh_minor, version.vernum[1]) + self.assertEqual(pgh_patch, version.vernum[2]) + + def test_sdl_version(self): + from pygame import version + + self.assertEqual(len(version.SDL), 3) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/tests/video_test.py b/.venv/lib/python3.8/site-packages/pygame/tests/video_test.py new file mode 100644 index 0000000..a6a6473 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/tests/video_test.py @@ -0,0 +1,26 @@ +import unittest +import sys +import pygame + +from pygame._sdl2 import video + + +class VideoModuleTest(unittest.TestCase): + default_caption = "pygame window" + + @unittest.skipIf( + not (sys.maxsize > 2 ** 32), + "32 bit SDL 2.0.16 has an issue.", + ) + def test_renderer_set_viewport(self): + """works.""" + window = video.Window(title=self.default_caption, size=(800, 600)) + renderer = video.Renderer(window=window) + renderer.logical_size = (1920, 1080) + rect = pygame.Rect(0, 0, 1920, 1080) + renderer.set_viewport(rect) + self.assertEqual(renderer.get_viewport(), (0, 0, 1920, 1080)) + + +if __name__ == "__main__": + unittest.main() diff --git a/.venv/lib/python3.8/site-packages/pygame/threads/__init__.py b/.venv/lib/python3.8/site-packages/pygame/threads/__init__.py new file mode 100644 index 0000000..89db392 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/threads/__init__.py @@ -0,0 +1,272 @@ +""" +* Experimental * + +Like the map function, but can use a pool of threads. + +Really easy to use threads. eg. tmap(f, alist) + +If you know how to use the map function, you can use threads. +""" + +__author__ = "Rene Dudfield" +__version__ = "0.3.0" +__license__ = "Python license" + +from queue import Queue, Empty +import threading + + +Thread = threading.Thread + +STOP = object() +FINISH = object() + +# DONE_ONE = object() +# DONE_TWO = object() + +# a default worker queue. +_wq = None + +# if we are using threads or not. This is the number of workers. +_use_workers = 0 + +# Set this to the maximum for the amount of Cores/CPUs +# Note, that the tests early out. +# So it should only test the best number of workers +2 +MAX_WORKERS_TO_TEST = 64 + + +def init(number_of_workers=0): + """Does a little test to see if threading is worth it. + Sets up a global worker queue if it's worth it. + + Calling init() is not required, but is generally better to do. + """ + global _wq, _use_workers + + if number_of_workers: + _use_workers = number_of_workers + else: + _use_workers = benchmark_workers() + + # if it is best to use zero workers, then use that. + _wq = WorkerQueue(_use_workers) + + +def quit(): + """cleans up everything.""" + global _wq, _use_workers + _wq.stop() + _wq = None + _use_workers = False + + +def benchmark_workers(a_bench_func=None, the_data=None): + """does a little test to see if workers are at all faster. + Returns the number of workers which works best. + Takes a little bit of time to run, so you should only really call + it once. + You can pass in benchmark data, and functions if you want. + a_bench_func - f(data) + the_data - data to work on. + """ + # TODO: try and make this scale better with slower/faster cpus. + # first find some variables so that using 0 workers takes about 1.0 seconds. + # then go from there. + + # note, this will only work with pygame 1.8rc3+ + # replace the doit() and the_data with something that releases the GIL + + import pygame + import pygame.transform + import time + + if not a_bench_func: + + def doit(x): + return pygame.transform.scale(x, (544, 576)) + + else: + doit = a_bench_func + + if not the_data: + thedata = [pygame.Surface((155, 155), 0, 32) for x in range(10)] + else: + thedata = the_data + + best = time.time() + 100000000 + best_number = 0 + # last_best = -1 + + for num_workers in range(0, MAX_WORKERS_TO_TEST): + + wq = WorkerQueue(num_workers) + t1 = time.time() + for _ in range(20): + print(f"active count:{threading.activeCount()}") + tmap(doit, thedata, worker_queue=wq) + t2 = time.time() + + wq.stop() + + total_time = t2 - t1 + print(f"total time num_workers:{num_workers}: time:{total_time}:") + + if total_time < best: + # last_best = best_number + best_number = num_workers + best = total_time + + if num_workers - best_number > 1: + # We tried to add more, but it didn't like it. + # so we stop with testing at this number. + break + + return best_number + + +class WorkerQueue(object): + def __init__(self, num_workers=20): + self.queue = Queue() + self.pool = [] + self._setup_workers(num_workers) + + def _setup_workers(self, num_workers): + """Sets up the worker threads + NOTE: undefined behaviour if you call this again. + """ + self.pool = [] + + for _ in range(num_workers): + self.pool.append(Thread(target=self.threadloop)) + + for a_thread in self.pool: + a_thread.setDaemon(True) + a_thread.start() + + def do(self, f, *args, **kwArgs): + """puts a function on a queue for running later.""" + self.queue.put((f, args, kwArgs)) + + def stop(self): + """Stops the WorkerQueue, waits for all of the threads to finish up.""" + self.queue.put(STOP) + for thread in self.pool: + thread.join() + + def threadloop(self): # , finish=False): + """Loops until all of the tasks are finished.""" + while True: + args = self.queue.get() + if args is STOP: + self.queue.put(STOP) + self.queue.task_done() + break + try: + args[0](*args[1], **args[2]) + finally: + # clean up the queue, raise the exception. + self.queue.task_done() + # raise + + def wait(self): + """waits until all tasks are complete.""" + self.queue.join() + + +class FuncResult: + """Used for wrapping up a function call so that the results are stored + inside the instances result attribute. + """ + + def __init__(self, f, callback=None, errback=None): + """f - is the function we that we call + callback(result) - this is called when the function(f) returns + errback(exception) - this is called when the function(f) raises + an exception. + """ + self.f = f + self.exception = None + self.result = None + self.callback = callback + self.errback = errback + + def __call__(self, *args, **kwargs): + # we try to call the function here. If it fails we store the exception. + try: + self.result = self.f(*args, **kwargs) + if self.callback: + self.callback(self.result) + except Exception as e: + self.exception = e + if self.errback: + self.errback(self.exception) + + +def tmap(f, seq_args, num_workers=20, worker_queue=None, wait=True, stop_on_error=True): + """like map, but uses a thread pool to execute. + num_workers - the number of worker threads that will be used. If pool + is passed in, then the num_workers arg is ignored. + worker_queue - you can optionally pass in an existing WorkerQueue. + wait - True means that the results are returned when everything is finished. + False means that we return the [worker_queue, results] right away instead. + results, is returned as a list of FuncResult instances. + stop_on_error - + """ + + if worker_queue: + wq = worker_queue + else: + # see if we have a global queue to work with. + if _wq: + wq = _wq + else: + if num_workers == 0: + return map(f, seq_args) + + wq = WorkerQueue(num_workers) + + # we short cut it here if the number of workers is 0. + # normal map should be faster in this case. + if len(wq.pool) == 0: + return map(f, seq_args) + + # print ("queue size:%s" % wq.queue.qsize()) + + # TODO: divide the data (seq_args) into even chunks and + # then pass each thread a map(f, equal_part(seq_args)) + # That way there should be less locking, and overhead. + + results = [] + for sa in seq_args: + results.append(FuncResult(f)) + wq.do(results[-1], sa) + + # wq.stop() + + if wait: + # print ("wait") + wq.wait() + # print ("after wait") + # print ("queue size:%s" % wq.queue.qsize()) + if wq.queue.qsize(): + raise Exception("buggy threadmap") + # if we created a worker queue, we need to stop it. + if not worker_queue and not _wq: + # print ("stoping") + wq.stop() + if wq.queue.qsize(): + um = wq.queue.get() + if not um is STOP: + raise Exception("buggy threadmap") + + # see if there were any errors. If so raise the first one. This matches map behaviour. + # TODO: the traceback doesn't show up nicely. + # NOTE: TODO: we might want to return the results anyway? This should be an option. + if stop_on_error: + error_ones = list(filter(lambda x: x.exception, results)) + if error_ones: + raise error_ones[0].exception + + return map(lambda x: x.result, results) + return [wq, results] diff --git a/.venv/lib/python3.8/site-packages/pygame/threads/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygame/threads/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..5f54563 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/threads/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygame/time.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/time.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..ebd45fa Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/time.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/time.pyi b/.venv/lib/python3.8/site-packages/pygame/time.pyi new file mode 100644 index 0000000..f938442 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/time.pyi @@ -0,0 +1,15 @@ +from typing import Union + +from pygame.event import Event + +def get_ticks() -> int: ... +def wait(milliseconds: int) -> int: ... +def delay(milliseconds: int) -> int: ... +def set_timer(event: Union[int, Event], millis: int, loops: int = 0) -> None: ... + +class Clock: + def tick(self, framerate: int = 0) -> int: ... + def tick_busy_loop(self, framerate: int = 0) -> int: ... + def get_time(self) -> int: ... + def get_rawtime(self) -> int: ... + def get_fps(self) -> float: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/transform.cpython-38-x86_64-linux-gnu.so b/.venv/lib/python3.8/site-packages/pygame/transform.cpython-38-x86_64-linux-gnu.so new file mode 100755 index 0000000..6e586eb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygame/transform.cpython-38-x86_64-linux-gnu.so differ diff --git a/.venv/lib/python3.8/site-packages/pygame/transform.pyi b/.venv/lib/python3.8/site-packages/pygame/transform.pyi new file mode 100644 index 0000000..4fe8e91 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/transform.pyi @@ -0,0 +1,41 @@ +from typing import Optional, Sequence, Union + +from pygame.color import Color +from pygame.surface import Surface + +from ._common import _ColorValue, _Coordinate, _RectValue + +def flip(surface: Surface, flip_x: bool, flip_y: bool) -> Surface: ... +def scale( + surface: Surface, + size: _Coordinate, + dest_surface: Optional[Surface] = None, +) -> Surface: ... +def rotate(surface: Surface, angle: float) -> Surface: ... +def rotozoom(surface: Surface, angle: float, scale: float) -> Surface: ... +def scale2x(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ... +def smoothscale( + surface: Surface, + size: _Coordinate, + dest_surface: Optional[Surface] = None, +) -> Surface: ... +def get_smoothscale_backend() -> str: ... +def set_smoothscale_backend(backend: str) -> None: ... +def chop(surface: Surface, rect: _RectValue) -> Surface: ... +def laplacian(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ... +def average_surfaces( + surfaces: Sequence[Surface], + dest_surface: Optional[Surface] = None, + palette_colors: Union[bool, int] = 1, +) -> Surface: ... +def average_color(surface: Surface, rect: Optional[_RectValue] = None) -> Color: ... +def threshold( + dest_surface: Optional[Surface], + surface: Surface, + search_color: Optional[_ColorValue], + threshold: Optional[_ColorValue] = (0, 0, 0, 0), + set_color: Optional[_ColorValue] = (0, 0, 0, 0), + set_behavior: Optional[int] = 1, + search_surf: Optional[Surface] = None, + inverse_set: Optional[bool] = False, +) -> int: ... diff --git a/.venv/lib/python3.8/site-packages/pygame/version.py b/.venv/lib/python3.8/site-packages/pygame/version.py new file mode 100644 index 0000000..b287dba --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/version.py @@ -0,0 +1,72 @@ +## pygame - Python Game Library +## Copyright (C) 2000-2003 Pete Shinners +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Library General Public +## License as published by the Free Software Foundation; either +## version 2 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Library General Public License for more details. +## +## You should have received a copy of the GNU Library General Public +## License along with this library; if not, write to the Free +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## +## Pete Shinners +## pete@shinners.org + +"""Simply the current installed pygame version. The version information is +stored in the regular pygame module as 'pygame.ver'. Keeping the version +information also available in a separate module allows you to test the +pygame version without importing the main pygame module. + +The python version information should always compare greater than any previous +releases. (hmm, until we get to versions > 10) +""" +from pygame.base import get_sdl_version + +############### +# This file is generated with version.py.in +## + +class SoftwareVersion(tuple): + """ + A class for storing data about software versions. + """ + __slots__ = () + fields = "major", "minor", "patch" + + def __new__(cls, major, minor, patch): + return tuple.__new__(cls, (major, minor, patch)) + + def __repr__(self): + fields = (f"{fld}={val}" for fld, val in zip(self.fields, self)) + return f"{str(self.__class__.__name__)}({', '.join(fields)})" + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + major = property(lambda self: self[0]) + minor = property(lambda self: self[1]) + patch = property(lambda self: self[2]) + +class PygameVersion(SoftwareVersion): + """ + Pygame Version class. + """ + +class SDLVersion(SoftwareVersion): + """ + SDL Version class. + """ + +_sdl_tuple = get_sdl_version() +SDL = SDLVersion(_sdl_tuple[0], _sdl_tuple[1], _sdl_tuple[2]) +ver = "2.1.2" # pylint: disable=invalid-name +vernum = PygameVersion(2, 1, 2) +rev = "" # pylint: disable=invalid-name + +__all__ = ["SDL", "ver", "vernum", "rev"] diff --git a/.venv/lib/python3.8/site-packages/pygame/version.pyi b/.venv/lib/python3.8/site-packages/pygame/version.pyi new file mode 100644 index 0000000..41f2132 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygame/version.pyi @@ -0,0 +1,17 @@ +from typing import Tuple + +class SoftwareVersion(Tuple[int, int, int]): + def __new__(cls, major: int, minor: int, patch: int) -> PygameVersion: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + major: int + minor: int + patch: int + +class PygameVersion(SoftwareVersion): ... +class SDLVersion(SoftwareVersion): ... + +SDL: SDLVersion +ver: str +vernum: PygameVersion +rev: str diff --git a/.venv/lib/python3.8/site-packages/pygments/__init__.py b/.venv/lib/python3.8/site-packages/pygments/__init__.py new file mode 100644 index 0000000..22c50b3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/__init__.py @@ -0,0 +1,83 @@ +""" + Pygments + ~~~~~~~~ + + Pygments is a syntax highlighting package written in Python. + + It is a generic syntax highlighter for general use in all kinds of software + such as forum systems, wikis or other applications that need to prettify + source code. Highlights are: + + * a wide range of common languages and markup formats is supported + * special attention is paid to details, increasing quality by a fair amount + * support for new languages and formats are added easily + * a number of output formats, presently HTML, LaTeX, RTF, SVG, all image + formats that PIL supports, and ANSI sequences + * it is usable as a command-line tool and as a library + * ... and it highlights even Brainfuck! + + The `Pygments master branch`_ is installable with ``easy_install Pygments==dev``. + + .. _Pygments master branch: + https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +from io import StringIO, BytesIO + +__version__ = '2.11.2' +__docformat__ = 'restructuredtext' + +__all__ = ['lex', 'format', 'highlight'] + + +def lex(code, lexer): + """ + Lex ``code`` with ``lexer`` and return an iterable of tokens. + """ + try: + return lexer.get_tokens(code) + except TypeError as err: + if (isinstance(err.args[0], str) and + ('unbound method get_tokens' in err.args[0] or + 'missing 1 required positional argument' in err.args[0])): + raise TypeError('lex() argument must be a lexer instance, ' + 'not a class') + raise + + +def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builtin + """ + Format a tokenlist ``tokens`` with the formatter ``formatter``. + + If ``outfile`` is given and a valid file object (an object + with a ``write`` method), the result will be written to it, otherwise + it is returned as a string. + """ + try: + if not outfile: + realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO() + formatter.format(tokens, realoutfile) + return realoutfile.getvalue() + else: + formatter.format(tokens, outfile) + except TypeError as err: + if (isinstance(err.args[0], str) and + ('unbound method format' in err.args[0] or + 'missing 1 required positional argument' in err.args[0])): + raise TypeError('format() argument must be a formatter instance, ' + 'not a class') + raise + + +def highlight(code, lexer, formatter, outfile=None): + """ + Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``. + + If ``outfile`` is given and a valid file object (an object + with a ``write`` method), the result will be written to it, otherwise + it is returned as a string. + """ + return format(lex(code, lexer), formatter, outfile) + diff --git a/.venv/lib/python3.8/site-packages/pygments/__main__.py b/.venv/lib/python3.8/site-packages/pygments/__main__.py new file mode 100644 index 0000000..c6e2517 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/__main__.py @@ -0,0 +1,17 @@ +""" + pygments.__main__ + ~~~~~~~~~~~~~~~~~ + + Main entry point for ``python -m pygments``. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import sys +import pygments.cmdline + +try: + sys.exit(pygments.cmdline.main(sys.argv)) +except KeyboardInterrupt: + sys.exit(1) diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f3f1014 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/__main__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..822f8a3 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/__main__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/cmdline.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/cmdline.cpython-38.pyc new file mode 100644 index 0000000..581b813 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/cmdline.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/console.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/console.cpython-38.pyc new file mode 100644 index 0000000..cb1f6f0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/console.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/filter.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/filter.cpython-38.pyc new file mode 100644 index 0000000..33501bc Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/filter.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/formatter.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/formatter.cpython-38.pyc new file mode 100644 index 0000000..3209b7a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/formatter.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/lexer.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/lexer.cpython-38.pyc new file mode 100644 index 0000000..f4e93d0 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/lexer.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/modeline.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/modeline.cpython-38.pyc new file mode 100644 index 0000000..ee1790b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/modeline.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/plugin.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/plugin.cpython-38.pyc new file mode 100644 index 0000000..8d8a144 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/plugin.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/regexopt.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/regexopt.cpython-38.pyc new file mode 100644 index 0000000..165fb4f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/regexopt.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/scanner.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/scanner.cpython-38.pyc new file mode 100644 index 0000000..d145ee5 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/scanner.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/sphinxext.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/sphinxext.cpython-38.pyc new file mode 100644 index 0000000..83bfd1b Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/sphinxext.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/style.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/style.cpython-38.pyc new file mode 100644 index 0000000..7a70ef7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/style.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/token.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/token.cpython-38.pyc new file mode 100644 index 0000000..f492e2c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/token.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/unistring.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/unistring.cpython-38.pyc new file mode 100644 index 0000000..7538640 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/unistring.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/__pycache__/util.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/__pycache__/util.cpython-38.pyc new file mode 100644 index 0000000..d2b4df2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/__pycache__/util.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/cmdline.py b/.venv/lib/python3.8/site-packages/pygments/cmdline.py new file mode 100644 index 0000000..4f688c7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/cmdline.py @@ -0,0 +1,663 @@ +""" + pygments.cmdline + ~~~~~~~~~~~~~~~~ + + Command line interface. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +import sys +import shutil +import argparse +from textwrap import dedent + +from pygments import __version__, highlight +from pygments.util import ClassNotFound, OptionError, docstring_headline, \ + guess_decode, guess_decode_from_terminal, terminal_encoding, \ + UnclosingTextIOWrapper +from pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \ + load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename +from pygments.lexers.special import TextLexer +from pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter +from pygments.formatters import get_all_formatters, get_formatter_by_name, \ + load_formatter_from_file, get_formatter_for_filename, find_formatter_class +from pygments.formatters.terminal import TerminalFormatter +from pygments.formatters.terminal256 import Terminal256Formatter +from pygments.filters import get_all_filters, find_filter_class +from pygments.styles import get_all_styles, get_style_by_name + + +def _parse_options(o_strs): + opts = {} + if not o_strs: + return opts + for o_str in o_strs: + if not o_str.strip(): + continue + o_args = o_str.split(',') + for o_arg in o_args: + o_arg = o_arg.strip() + try: + o_key, o_val = o_arg.split('=', 1) + o_key = o_key.strip() + o_val = o_val.strip() + except ValueError: + opts[o_arg] = True + else: + opts[o_key] = o_val + return opts + + +def _parse_filters(f_strs): + filters = [] + if not f_strs: + return filters + for f_str in f_strs: + if ':' in f_str: + fname, fopts = f_str.split(':', 1) + filters.append((fname, _parse_options([fopts]))) + else: + filters.append((f_str, {})) + return filters + + +def _print_help(what, name): + try: + if what == 'lexer': + cls = get_lexer_by_name(name) + print("Help on the %s lexer:" % cls.name) + print(dedent(cls.__doc__)) + elif what == 'formatter': + cls = find_formatter_class(name) + print("Help on the %s formatter:" % cls.name) + print(dedent(cls.__doc__)) + elif what == 'filter': + cls = find_filter_class(name) + print("Help on the %s filter:" % name) + print(dedent(cls.__doc__)) + return 0 + except (AttributeError, ValueError): + print("%s not found!" % what, file=sys.stderr) + return 1 + + +def _print_list(what): + if what == 'lexer': + print() + print("Lexers:") + print("~~~~~~~") + + info = [] + for fullname, names, exts, _ in get_all_lexers(): + tup = (', '.join(names)+':', fullname, + exts and '(filenames ' + ', '.join(exts) + ')' or '') + info.append(tup) + info.sort() + for i in info: + print(('* %s\n %s %s') % i) + + elif what == 'formatter': + print() + print("Formatters:") + print("~~~~~~~~~~~") + + info = [] + for cls in get_all_formatters(): + doc = docstring_headline(cls) + tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and + '(filenames ' + ', '.join(cls.filenames) + ')' or '') + info.append(tup) + info.sort() + for i in info: + print(('* %s\n %s %s') % i) + + elif what == 'filter': + print() + print("Filters:") + print("~~~~~~~~") + + for name in get_all_filters(): + cls = find_filter_class(name) + print("* " + name + ':') + print(" %s" % docstring_headline(cls)) + + elif what == 'style': + print() + print("Styles:") + print("~~~~~~~") + + for name in get_all_styles(): + cls = get_style_by_name(name) + print("* " + name + ':') + print(" %s" % docstring_headline(cls)) + + +def _print_list_as_json(requested_items): + import json + result = {} + if 'lexer' in requested_items: + info = {} + for fullname, names, filenames, mimetypes in get_all_lexers(): + info[fullname] = { + 'aliases': names, + 'filenames': filenames, + 'mimetypes': mimetypes + } + result['lexers'] = info + + if 'formatter' in requested_items: + info = {} + for cls in get_all_formatters(): + doc = docstring_headline(cls) + info[cls.name] = { + 'aliases': cls.aliases, + 'filenames': cls.filenames, + 'doc': doc + } + result['formatters'] = info + + if 'filter' in requested_items: + info = {} + for name in get_all_filters(): + cls = find_filter_class(name) + info[name] = { + 'doc': docstring_headline(cls) + } + result['filters'] = info + + if 'style' in requested_items: + info = {} + for name in get_all_styles(): + cls = get_style_by_name(name) + info[name] = { + 'doc': docstring_headline(cls) + } + result['styles'] = info + + json.dump(result, sys.stdout) + +def main_inner(parser, argns): + if argns.help: + parser.print_help() + return 0 + + if argns.V: + print('Pygments version %s, (c) 2006-2021 by Georg Brandl, Matthäus ' + 'Chajdas and contributors.' % __version__) + return 0 + + def is_only_option(opt): + return not any(v for (k, v) in vars(argns).items() if k != opt) + + # handle ``pygmentize -L`` + if argns.L is not None: + arg_set = set() + for k, v in vars(argns).items(): + if v: + arg_set.add(k) + + arg_set.discard('L') + arg_set.discard('json') + + if arg_set: + parser.print_help(sys.stderr) + return 2 + + # print version + if not argns.json: + main(['', '-V']) + allowed_types = {'lexer', 'formatter', 'filter', 'style'} + largs = [arg.rstrip('s') for arg in argns.L] + if any(arg not in allowed_types for arg in largs): + parser.print_help(sys.stderr) + return 0 + if not largs: + largs = allowed_types + if not argns.json: + for arg in largs: + _print_list(arg) + else: + _print_list_as_json(largs) + return 0 + + # handle ``pygmentize -H`` + if argns.H: + if not is_only_option('H'): + parser.print_help(sys.stderr) + return 2 + what, name = argns.H + if what not in ('lexer', 'formatter', 'filter'): + parser.print_help(sys.stderr) + return 2 + return _print_help(what, name) + + # parse -O options + parsed_opts = _parse_options(argns.O or []) + + # parse -P options + for p_opt in argns.P or []: + try: + name, value = p_opt.split('=', 1) + except ValueError: + parsed_opts[p_opt] = True + else: + parsed_opts[name] = value + + # encodings + inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding')) + outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding')) + + # handle ``pygmentize -N`` + if argns.N: + lexer = find_lexer_class_for_filename(argns.N) + if lexer is None: + lexer = TextLexer + + print(lexer.aliases[0]) + return 0 + + # handle ``pygmentize -C`` + if argns.C: + inp = sys.stdin.buffer.read() + try: + lexer = guess_lexer(inp, inencoding=inencoding) + except ClassNotFound: + lexer = TextLexer + + print(lexer.aliases[0]) + return 0 + + # handle ``pygmentize -S`` + S_opt = argns.S + a_opt = argns.a + if S_opt is not None: + f_opt = argns.f + if not f_opt: + parser.print_help(sys.stderr) + return 2 + if argns.l or argns.INPUTFILE: + parser.print_help(sys.stderr) + return 2 + + try: + parsed_opts['style'] = S_opt + fmter = get_formatter_by_name(f_opt, **parsed_opts) + except ClassNotFound as err: + print(err, file=sys.stderr) + return 1 + + print(fmter.get_style_defs(a_opt or '')) + return 0 + + # if no -S is given, -a is not allowed + if argns.a is not None: + parser.print_help(sys.stderr) + return 2 + + # parse -F options + F_opts = _parse_filters(argns.F or []) + + # -x: allow custom (eXternal) lexers and formatters + allow_custom_lexer_formatter = bool(argns.x) + + # select lexer + lexer = None + + # given by name? + lexername = argns.l + if lexername: + # custom lexer, located relative to user's cwd + if allow_custom_lexer_formatter and '.py' in lexername: + try: + filename = None + name = None + if ':' in lexername: + filename, name = lexername.rsplit(':', 1) + + if '.py' in name: + # This can happen on Windows: If the lexername is + # C:\lexer.py -- return to normal load path in that case + name = None + + if filename and name: + lexer = load_lexer_from_file(filename, name, + **parsed_opts) + else: + lexer = load_lexer_from_file(lexername, **parsed_opts) + except ClassNotFound as err: + print('Error:', err, file=sys.stderr) + return 1 + else: + try: + lexer = get_lexer_by_name(lexername, **parsed_opts) + except (OptionError, ClassNotFound) as err: + print('Error:', err, file=sys.stderr) + return 1 + + # read input code + code = None + + if argns.INPUTFILE: + if argns.s: + print('Error: -s option not usable when input file specified', + file=sys.stderr) + return 2 + + infn = argns.INPUTFILE + try: + with open(infn, 'rb') as infp: + code = infp.read() + except Exception as err: + print('Error: cannot read infile:', err, file=sys.stderr) + return 1 + if not inencoding: + code, inencoding = guess_decode(code) + + # do we have to guess the lexer? + if not lexer: + try: + lexer = get_lexer_for_filename(infn, code, **parsed_opts) + except ClassNotFound as err: + if argns.g: + try: + lexer = guess_lexer(code, **parsed_opts) + except ClassNotFound: + lexer = TextLexer(**parsed_opts) + else: + print('Error:', err, file=sys.stderr) + return 1 + except OptionError as err: + print('Error:', err, file=sys.stderr) + return 1 + + elif not argns.s: # treat stdin as full file (-s support is later) + # read code from terminal, always in binary mode since we want to + # decode ourselves and be tolerant with it + code = sys.stdin.buffer.read() # use .buffer to get a binary stream + if not inencoding: + code, inencoding = guess_decode_from_terminal(code, sys.stdin) + # else the lexer will do the decoding + if not lexer: + try: + lexer = guess_lexer(code, **parsed_opts) + except ClassNotFound: + lexer = TextLexer(**parsed_opts) + + else: # -s option needs a lexer with -l + if not lexer: + print('Error: when using -s a lexer has to be selected with -l', + file=sys.stderr) + return 2 + + # process filters + for fname, fopts in F_opts: + try: + lexer.add_filter(fname, **fopts) + except ClassNotFound as err: + print('Error:', err, file=sys.stderr) + return 1 + + # select formatter + outfn = argns.o + fmter = argns.f + if fmter: + # custom formatter, located relative to user's cwd + if allow_custom_lexer_formatter and '.py' in fmter: + try: + filename = None + name = None + if ':' in fmter: + # Same logic as above for custom lexer + filename, name = fmter.rsplit(':', 1) + + if '.py' in name: + name = None + + if filename and name: + fmter = load_formatter_from_file(filename, name, + **parsed_opts) + else: + fmter = load_formatter_from_file(fmter, **parsed_opts) + except ClassNotFound as err: + print('Error:', err, file=sys.stderr) + return 1 + else: + try: + fmter = get_formatter_by_name(fmter, **parsed_opts) + except (OptionError, ClassNotFound) as err: + print('Error:', err, file=sys.stderr) + return 1 + + if outfn: + if not fmter: + try: + fmter = get_formatter_for_filename(outfn, **parsed_opts) + except (OptionError, ClassNotFound) as err: + print('Error:', err, file=sys.stderr) + return 1 + try: + outfile = open(outfn, 'wb') + except Exception as err: + print('Error: cannot open outfile:', err, file=sys.stderr) + return 1 + else: + if not fmter: + if '256' in os.environ.get('TERM', ''): + fmter = Terminal256Formatter(**parsed_opts) + else: + fmter = TerminalFormatter(**parsed_opts) + outfile = sys.stdout.buffer + + # determine output encoding if not explicitly selected + if not outencoding: + if outfn: + # output file? use lexer encoding for now (can still be None) + fmter.encoding = inencoding + else: + # else use terminal encoding + fmter.encoding = terminal_encoding(sys.stdout) + + # provide coloring under Windows, if possible + if not outfn and sys.platform in ('win32', 'cygwin') and \ + fmter.name in ('Terminal', 'Terminal256'): # pragma: no cover + # unfortunately colorama doesn't support binary streams on Py3 + outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding) + fmter.encoding = None + try: + import colorama.initialise + except ImportError: + pass + else: + outfile = colorama.initialise.wrap_stream( + outfile, convert=None, strip=None, autoreset=False, wrap=True) + + # When using the LaTeX formatter and the option `escapeinside` is + # specified, we need a special lexer which collects escaped text + # before running the chosen language lexer. + escapeinside = parsed_opts.get('escapeinside', '') + if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter): + left = escapeinside[0] + right = escapeinside[1] + lexer = LatexEmbeddedLexer(left, right, lexer) + + # ... and do it! + if not argns.s: + # process whole input as per normal... + try: + highlight(code, lexer, fmter, outfile) + finally: + if outfn: + outfile.close() + return 0 + else: + # line by line processing of stdin (eg: for 'tail -f')... + try: + while 1: + line = sys.stdin.buffer.readline() + if not line: + break + if not inencoding: + line = guess_decode_from_terminal(line, sys.stdin)[0] + highlight(line, lexer, fmter, outfile) + if hasattr(outfile, 'flush'): + outfile.flush() + return 0 + except KeyboardInterrupt: # pragma: no cover + return 0 + finally: + if outfn: + outfile.close() + + +class HelpFormatter(argparse.HelpFormatter): + def __init__(self, prog, indent_increment=2, max_help_position=16, width=None): + if width is None: + try: + width = shutil.get_terminal_size().columns - 2 + except Exception: + pass + argparse.HelpFormatter.__init__(self, prog, indent_increment, + max_help_position, width) + + +def main(args=sys.argv): + """ + Main command line entry point. + """ + desc = "Highlight an input file and write the result to an output file." + parser = argparse.ArgumentParser(description=desc, add_help=False, + formatter_class=HelpFormatter) + + operation = parser.add_argument_group('Main operation') + lexersel = operation.add_mutually_exclusive_group() + lexersel.add_argument( + '-l', metavar='LEXER', + help='Specify the lexer to use. (Query names with -L.) If not ' + 'given and -g is not present, the lexer is guessed from the filename.') + lexersel.add_argument( + '-g', action='store_true', + help='Guess the lexer from the file contents, or pass through ' + 'as plain text if nothing can be guessed.') + operation.add_argument( + '-F', metavar='FILTER[:options]', action='append', + help='Add a filter to the token stream. (Query names with -L.) ' + 'Filter options are given after a colon if necessary.') + operation.add_argument( + '-f', metavar='FORMATTER', + help='Specify the formatter to use. (Query names with -L.) ' + 'If not given, the formatter is guessed from the output filename, ' + 'and defaults to the terminal formatter if the output is to the ' + 'terminal or an unknown file extension.') + operation.add_argument( + '-O', metavar='OPTION=value[,OPTION=value,...]', action='append', + help='Give options to the lexer and formatter as a comma-separated ' + 'list of key-value pairs. ' + 'Example: `-O bg=light,python=cool`.') + operation.add_argument( + '-P', metavar='OPTION=value', action='append', + help='Give a single option to the lexer and formatter - with this ' + 'you can pass options whose value contains commas and equal signs. ' + 'Example: `-P "heading=Pygments, the Python highlighter"`.') + operation.add_argument( + '-o', metavar='OUTPUTFILE', + help='Where to write the output. Defaults to standard output.') + + operation.add_argument( + 'INPUTFILE', nargs='?', + help='Where to read the input. Defaults to standard input.') + + flags = parser.add_argument_group('Operation flags') + flags.add_argument( + '-v', action='store_true', + help='Print a detailed traceback on unhandled exceptions, which ' + 'is useful for debugging and bug reports.') + flags.add_argument( + '-s', action='store_true', + help='Process lines one at a time until EOF, rather than waiting to ' + 'process the entire file. This only works for stdin, only for lexers ' + 'with no line-spanning constructs, and is intended for streaming ' + 'input such as you get from `tail -f`. ' + 'Example usage: `tail -f sql.log | pygmentize -s -l sql`.') + flags.add_argument( + '-x', action='store_true', + help='Allow custom lexers and formatters to be loaded from a .py file ' + 'relative to the current working directory. For example, ' + '`-l ./customlexer.py -x`. By default, this option expects a file ' + 'with a class named CustomLexer or CustomFormatter; you can also ' + 'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). ' + 'Users should be very careful not to use this option with untrusted ' + 'files, because it will import and run them.') + flags.add_argument('--json', help='Output as JSON. This can ' + 'be only used in conjunction with -L.', + default=False, + action='store_true') + + special_modes_group = parser.add_argument_group( + 'Special modes - do not do any highlighting') + special_modes = special_modes_group.add_mutually_exclusive_group() + special_modes.add_argument( + '-S', metavar='STYLE -f formatter', + help='Print style definitions for STYLE for a formatter ' + 'given with -f. The argument given by -a is formatter ' + 'dependent.') + special_modes.add_argument( + '-L', nargs='*', metavar='WHAT', + help='List lexers, formatters, styles or filters -- ' + 'give additional arguments for the thing(s) you want to list ' + '(e.g. "styles"), or omit them to list everything.') + special_modes.add_argument( + '-N', metavar='FILENAME', + help='Guess and print out a lexer name based solely on the given ' + 'filename. Does not take input or highlight anything. If no specific ' + 'lexer can be determined, "text" is printed.') + special_modes.add_argument( + '-C', action='store_true', + help='Like -N, but print out a lexer name based solely on ' + 'a given content from standard input.') + special_modes.add_argument( + '-H', action='store', nargs=2, metavar=('NAME', 'TYPE'), + help='Print detailed help for the object of type , ' + 'where is one of "lexer", "formatter" or "filter".') + special_modes.add_argument( + '-V', action='store_true', + help='Print the package version.') + special_modes.add_argument( + '-h', '--help', action='store_true', + help='Print this help.') + special_modes_group.add_argument( + '-a', metavar='ARG', + help='Formatter-specific additional argument for the -S (print ' + 'style sheet) mode.') + + argns = parser.parse_args(args[1:]) + + try: + return main_inner(parser, argns) + except Exception: + if argns.v: + print(file=sys.stderr) + print('*' * 65, file=sys.stderr) + print('An unhandled exception occurred while highlighting.', + file=sys.stderr) + print('Please report the whole traceback to the issue tracker at', + file=sys.stderr) + print('.', + file=sys.stderr) + print('*' * 65, file=sys.stderr) + print(file=sys.stderr) + raise + import traceback + info = traceback.format_exception(*sys.exc_info()) + msg = info[-1].strip() + if len(info) >= 3: + # extract relevant file and position info + msg += '\n (f%s)' % info[-2].split('\n')[0].strip()[1:] + print(file=sys.stderr) + print('*** Error while highlighting:', file=sys.stderr) + print(msg, file=sys.stderr) + print('*** If this is a bug you want to report, please rerun with -v.', + file=sys.stderr) + return 1 diff --git a/.venv/lib/python3.8/site-packages/pygments/console.py b/.venv/lib/python3.8/site-packages/pygments/console.py new file mode 100644 index 0000000..8dd08ab --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/console.py @@ -0,0 +1,70 @@ +""" + pygments.console + ~~~~~~~~~~~~~~~~ + + Format colored console output. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +esc = "\x1b[" + +codes = {} +codes[""] = "" +codes["reset"] = esc + "39;49;00m" + +codes["bold"] = esc + "01m" +codes["faint"] = esc + "02m" +codes["standout"] = esc + "03m" +codes["underline"] = esc + "04m" +codes["blink"] = esc + "05m" +codes["overline"] = esc + "06m" + +dark_colors = ["black", "red", "green", "yellow", "blue", + "magenta", "cyan", "gray"] +light_colors = ["brightblack", "brightred", "brightgreen", "brightyellow", "brightblue", + "brightmagenta", "brightcyan", "white"] + +x = 30 +for d, l in zip(dark_colors, light_colors): + codes[d] = esc + "%im" % x + codes[l] = esc + "%im" % (60 + x) + x += 1 + +del d, l, x + +codes["white"] = codes["bold"] + + +def reset_color(): + return codes["reset"] + + +def colorize(color_key, text): + return codes[color_key] + text + codes["reset"] + + +def ansiformat(attr, text): + """ + Format ``text`` with a color and/or some attributes:: + + color normal color + *color* bold color + _color_ underlined color + +color+ blinking color + """ + result = [] + if attr[:1] == attr[-1:] == '+': + result.append(codes['blink']) + attr = attr[1:-1] + if attr[:1] == attr[-1:] == '*': + result.append(codes['bold']) + attr = attr[1:-1] + if attr[:1] == attr[-1:] == '_': + result.append(codes['underline']) + attr = attr[1:-1] + result.append(codes[attr]) + result.append(text) + result.append(codes['reset']) + return ''.join(result) diff --git a/.venv/lib/python3.8/site-packages/pygments/filter.py b/.venv/lib/python3.8/site-packages/pygments/filter.py new file mode 100644 index 0000000..85b4829 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/filter.py @@ -0,0 +1,71 @@ +""" + pygments.filter + ~~~~~~~~~~~~~~~ + + Module that implements the default filter. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + + +def apply_filters(stream, filters, lexer=None): + """ + Use this method to apply an iterable of filters to + a stream. If lexer is given it's forwarded to the + filter, otherwise the filter receives `None`. + """ + def _apply(filter_, stream): + yield from filter_.filter(lexer, stream) + for filter_ in filters: + stream = _apply(filter_, stream) + return stream + + +def simplefilter(f): + """ + Decorator that converts a function into a filter:: + + @simplefilter + def lowercase(self, lexer, stream, options): + for ttype, value in stream: + yield ttype, value.lower() + """ + return type(f.__name__, (FunctionFilter,), { + '__module__': getattr(f, '__module__'), + '__doc__': f.__doc__, + 'function': f, + }) + + +class Filter: + """ + Default filter. Subclass this class or use the `simplefilter` + decorator to create own filters. + """ + + def __init__(self, **options): + self.options = options + + def filter(self, lexer, stream): + raise NotImplementedError() + + +class FunctionFilter(Filter): + """ + Abstract class used by `simplefilter` to create simple + function filters on the fly. The `simplefilter` decorator + automatically creates subclasses of this class for + functions passed to it. + """ + function = None + + def __init__(self, **options): + if not hasattr(self, 'function'): + raise TypeError('%r used without bound function' % + self.__class__.__name__) + Filter.__init__(self, **options) + + def filter(self, lexer, stream): + # pylint: disable=not-callable + yield from self.function(lexer, stream, self.options) diff --git a/.venv/lib/python3.8/site-packages/pygments/filters/__init__.py b/.venv/lib/python3.8/site-packages/pygments/filters/__init__.py new file mode 100644 index 0000000..930ff64 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/filters/__init__.py @@ -0,0 +1,937 @@ +""" + pygments.filters + ~~~~~~~~~~~~~~~~ + + Module containing filter lookup functions and default + filters. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.token import String, Comment, Keyword, Name, Error, Whitespace, \ + string_to_tokentype +from pygments.filter import Filter +from pygments.util import get_list_opt, get_int_opt, get_bool_opt, \ + get_choice_opt, ClassNotFound, OptionError +from pygments.plugin import find_plugin_filters + + +def find_filter_class(filtername): + """Lookup a filter by name. Return None if not found.""" + if filtername in FILTERS: + return FILTERS[filtername] + for name, cls in find_plugin_filters(): + if name == filtername: + return cls + return None + + +def get_filter_by_name(filtername, **options): + """Return an instantiated filter. + + Options are passed to the filter initializer if wanted. + Raise a ClassNotFound if not found. + """ + cls = find_filter_class(filtername) + if cls: + return cls(**options) + else: + raise ClassNotFound('filter %r not found' % filtername) + + +def get_all_filters(): + """Return a generator of all filter names.""" + yield from FILTERS + for name, _ in find_plugin_filters(): + yield name + + +def _replace_special(ttype, value, regex, specialttype, + replacefunc=lambda x: x): + last = 0 + for match in regex.finditer(value): + start, end = match.start(), match.end() + if start != last: + yield ttype, value[last:start] + yield specialttype, replacefunc(value[start:end]) + last = end + if last != len(value): + yield ttype, value[last:] + + +class CodeTagFilter(Filter): + """Highlight special code tags in comments and docstrings. + + Options accepted: + + `codetags` : list of strings + A list of strings that are flagged as code tags. The default is to + highlight ``XXX``, ``TODO``, ``BUG`` and ``NOTE``. + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + tags = get_list_opt(options, 'codetags', + ['XXX', 'TODO', 'BUG', 'NOTE']) + self.tag_re = re.compile(r'\b(%s)\b' % '|'.join([ + re.escape(tag) for tag in tags if tag + ])) + + def filter(self, lexer, stream): + regex = self.tag_re + for ttype, value in stream: + if ttype in String.Doc or \ + ttype in Comment and \ + ttype not in Comment.Preproc: + yield from _replace_special(ttype, value, regex, Comment.Special) + else: + yield ttype, value + + +class SymbolFilter(Filter): + """Convert mathematical symbols such as \\ in Isabelle + or \\longrightarrow in LaTeX into Unicode characters. + + This is mostly useful for HTML or console output when you want to + approximate the source rendering you'd see in an IDE. + + Options accepted: + + `lang` : string + The symbol language. Must be one of ``'isabelle'`` or + ``'latex'``. The default is ``'isabelle'``. + """ + + latex_symbols = { + '\\alpha' : '\U000003b1', + '\\beta' : '\U000003b2', + '\\gamma' : '\U000003b3', + '\\delta' : '\U000003b4', + '\\varepsilon' : '\U000003b5', + '\\zeta' : '\U000003b6', + '\\eta' : '\U000003b7', + '\\vartheta' : '\U000003b8', + '\\iota' : '\U000003b9', + '\\kappa' : '\U000003ba', + '\\lambda' : '\U000003bb', + '\\mu' : '\U000003bc', + '\\nu' : '\U000003bd', + '\\xi' : '\U000003be', + '\\pi' : '\U000003c0', + '\\varrho' : '\U000003c1', + '\\sigma' : '\U000003c3', + '\\tau' : '\U000003c4', + '\\upsilon' : '\U000003c5', + '\\varphi' : '\U000003c6', + '\\chi' : '\U000003c7', + '\\psi' : '\U000003c8', + '\\omega' : '\U000003c9', + '\\Gamma' : '\U00000393', + '\\Delta' : '\U00000394', + '\\Theta' : '\U00000398', + '\\Lambda' : '\U0000039b', + '\\Xi' : '\U0000039e', + '\\Pi' : '\U000003a0', + '\\Sigma' : '\U000003a3', + '\\Upsilon' : '\U000003a5', + '\\Phi' : '\U000003a6', + '\\Psi' : '\U000003a8', + '\\Omega' : '\U000003a9', + '\\leftarrow' : '\U00002190', + '\\longleftarrow' : '\U000027f5', + '\\rightarrow' : '\U00002192', + '\\longrightarrow' : '\U000027f6', + '\\Leftarrow' : '\U000021d0', + '\\Longleftarrow' : '\U000027f8', + '\\Rightarrow' : '\U000021d2', + '\\Longrightarrow' : '\U000027f9', + '\\leftrightarrow' : '\U00002194', + '\\longleftrightarrow' : '\U000027f7', + '\\Leftrightarrow' : '\U000021d4', + '\\Longleftrightarrow' : '\U000027fa', + '\\mapsto' : '\U000021a6', + '\\longmapsto' : '\U000027fc', + '\\relbar' : '\U00002500', + '\\Relbar' : '\U00002550', + '\\hookleftarrow' : '\U000021a9', + '\\hookrightarrow' : '\U000021aa', + '\\leftharpoondown' : '\U000021bd', + '\\rightharpoondown' : '\U000021c1', + '\\leftharpoonup' : '\U000021bc', + '\\rightharpoonup' : '\U000021c0', + '\\rightleftharpoons' : '\U000021cc', + '\\leadsto' : '\U0000219d', + '\\downharpoonleft' : '\U000021c3', + '\\downharpoonright' : '\U000021c2', + '\\upharpoonleft' : '\U000021bf', + '\\upharpoonright' : '\U000021be', + '\\restriction' : '\U000021be', + '\\uparrow' : '\U00002191', + '\\Uparrow' : '\U000021d1', + '\\downarrow' : '\U00002193', + '\\Downarrow' : '\U000021d3', + '\\updownarrow' : '\U00002195', + '\\Updownarrow' : '\U000021d5', + '\\langle' : '\U000027e8', + '\\rangle' : '\U000027e9', + '\\lceil' : '\U00002308', + '\\rceil' : '\U00002309', + '\\lfloor' : '\U0000230a', + '\\rfloor' : '\U0000230b', + '\\flqq' : '\U000000ab', + '\\frqq' : '\U000000bb', + '\\bot' : '\U000022a5', + '\\top' : '\U000022a4', + '\\wedge' : '\U00002227', + '\\bigwedge' : '\U000022c0', + '\\vee' : '\U00002228', + '\\bigvee' : '\U000022c1', + '\\forall' : '\U00002200', + '\\exists' : '\U00002203', + '\\nexists' : '\U00002204', + '\\neg' : '\U000000ac', + '\\Box' : '\U000025a1', + '\\Diamond' : '\U000025c7', + '\\vdash' : '\U000022a2', + '\\models' : '\U000022a8', + '\\dashv' : '\U000022a3', + '\\surd' : '\U0000221a', + '\\le' : '\U00002264', + '\\ge' : '\U00002265', + '\\ll' : '\U0000226a', + '\\gg' : '\U0000226b', + '\\lesssim' : '\U00002272', + '\\gtrsim' : '\U00002273', + '\\lessapprox' : '\U00002a85', + '\\gtrapprox' : '\U00002a86', + '\\in' : '\U00002208', + '\\notin' : '\U00002209', + '\\subset' : '\U00002282', + '\\supset' : '\U00002283', + '\\subseteq' : '\U00002286', + '\\supseteq' : '\U00002287', + '\\sqsubset' : '\U0000228f', + '\\sqsupset' : '\U00002290', + '\\sqsubseteq' : '\U00002291', + '\\sqsupseteq' : '\U00002292', + '\\cap' : '\U00002229', + '\\bigcap' : '\U000022c2', + '\\cup' : '\U0000222a', + '\\bigcup' : '\U000022c3', + '\\sqcup' : '\U00002294', + '\\bigsqcup' : '\U00002a06', + '\\sqcap' : '\U00002293', + '\\Bigsqcap' : '\U00002a05', + '\\setminus' : '\U00002216', + '\\propto' : '\U0000221d', + '\\uplus' : '\U0000228e', + '\\bigplus' : '\U00002a04', + '\\sim' : '\U0000223c', + '\\doteq' : '\U00002250', + '\\simeq' : '\U00002243', + '\\approx' : '\U00002248', + '\\asymp' : '\U0000224d', + '\\cong' : '\U00002245', + '\\equiv' : '\U00002261', + '\\Join' : '\U000022c8', + '\\bowtie' : '\U00002a1d', + '\\prec' : '\U0000227a', + '\\succ' : '\U0000227b', + '\\preceq' : '\U0000227c', + '\\succeq' : '\U0000227d', + '\\parallel' : '\U00002225', + '\\mid' : '\U000000a6', + '\\pm' : '\U000000b1', + '\\mp' : '\U00002213', + '\\times' : '\U000000d7', + '\\div' : '\U000000f7', + '\\cdot' : '\U000022c5', + '\\star' : '\U000022c6', + '\\circ' : '\U00002218', + '\\dagger' : '\U00002020', + '\\ddagger' : '\U00002021', + '\\lhd' : '\U000022b2', + '\\rhd' : '\U000022b3', + '\\unlhd' : '\U000022b4', + '\\unrhd' : '\U000022b5', + '\\triangleleft' : '\U000025c3', + '\\triangleright' : '\U000025b9', + '\\triangle' : '\U000025b3', + '\\triangleq' : '\U0000225c', + '\\oplus' : '\U00002295', + '\\bigoplus' : '\U00002a01', + '\\otimes' : '\U00002297', + '\\bigotimes' : '\U00002a02', + '\\odot' : '\U00002299', + '\\bigodot' : '\U00002a00', + '\\ominus' : '\U00002296', + '\\oslash' : '\U00002298', + '\\dots' : '\U00002026', + '\\cdots' : '\U000022ef', + '\\sum' : '\U00002211', + '\\prod' : '\U0000220f', + '\\coprod' : '\U00002210', + '\\infty' : '\U0000221e', + '\\int' : '\U0000222b', + '\\oint' : '\U0000222e', + '\\clubsuit' : '\U00002663', + '\\diamondsuit' : '\U00002662', + '\\heartsuit' : '\U00002661', + '\\spadesuit' : '\U00002660', + '\\aleph' : '\U00002135', + '\\emptyset' : '\U00002205', + '\\nabla' : '\U00002207', + '\\partial' : '\U00002202', + '\\flat' : '\U0000266d', + '\\natural' : '\U0000266e', + '\\sharp' : '\U0000266f', + '\\angle' : '\U00002220', + '\\copyright' : '\U000000a9', + '\\textregistered' : '\U000000ae', + '\\textonequarter' : '\U000000bc', + '\\textonehalf' : '\U000000bd', + '\\textthreequarters' : '\U000000be', + '\\textordfeminine' : '\U000000aa', + '\\textordmasculine' : '\U000000ba', + '\\euro' : '\U000020ac', + '\\pounds' : '\U000000a3', + '\\yen' : '\U000000a5', + '\\textcent' : '\U000000a2', + '\\textcurrency' : '\U000000a4', + '\\textdegree' : '\U000000b0', + } + + isabelle_symbols = { + '\\' : '\U0001d7ec', + '\\' : '\U0001d7ed', + '\\' : '\U0001d7ee', + '\\' : '\U0001d7ef', + '\\' : '\U0001d7f0', + '\\' : '\U0001d7f1', + '\\' : '\U0001d7f2', + '\\' : '\U0001d7f3', + '\\' : '\U0001d7f4', + '\\' : '\U0001d7f5', + '\\' : '\U0001d49c', + '\\' : '\U0000212c', + '\\' : '\U0001d49e', + '\\' : '\U0001d49f', + '\\' : '\U00002130', + '\\' : '\U00002131', + '\\' : '\U0001d4a2', + '\\' : '\U0000210b', + '\\' : '\U00002110', + '\\' : '\U0001d4a5', + '\\' : '\U0001d4a6', + '\\' : '\U00002112', + '\\' : '\U00002133', + '\\' : '\U0001d4a9', + '\\' : '\U0001d4aa', + '\\

' : '\U0001d5c9', + '\\' : '\U0001d5ca', + '\\' : '\U0001d5cb', + '\\' : '\U0001d5cc', + '\\' : '\U0001d5cd', + '\\' : '\U0001d5ce', + '\\' : '\U0001d5cf', + '\\' : '\U0001d5d0', + '\\' : '\U0001d5d1', + '\\' : '\U0001d5d2', + '\\' : '\U0001d5d3', + '\\' : '\U0001d504', + '\\' : '\U0001d505', + '\\' : '\U0000212d', + '\\

' : '\U0001d507', + '\\' : '\U0001d508', + '\\' : '\U0001d509', + '\\' : '\U0001d50a', + '\\' : '\U0000210c', + '\\' : '\U00002111', + '\\' : '\U0001d50d', + '\\' : '\U0001d50e', + '\\' : '\U0001d50f', + '\\' : '\U0001d510', + '\\' : '\U0001d511', + '\\' : '\U0001d512', + '\\' : '\U0001d513', + '\\' : '\U0001d514', + '\\' : '\U0000211c', + '\\' : '\U0001d516', + '\\' : '\U0001d517', + '\\' : '\U0001d518', + '\\' : '\U0001d519', + '\\' : '\U0001d51a', + '\\' : '\U0001d51b', + '\\' : '\U0001d51c', + '\\' : '\U00002128', + '\\' : '\U0001d51e', + '\\' : '\U0001d51f', + '\\' : '\U0001d520', + '\\
' : '\U0001d521', + '\\' : '\U0001d522', + '\\' : '\U0001d523', + '\\' : '\U0001d524', + '\\' : '\U0001d525', + '\\' : '\U0001d526', + '\\' : '\U0001d527', + '\\' : '\U0001d528', + '\\' : '\U0001d529', + '\\' : '\U0001d52a', + '\\' : '\U0001d52b', + '\\' : '\U0001d52c', + '\\' : '\U0001d52d', + '\\' : '\U0001d52e', + '\\' : '\U0001d52f', + '\\' : '\U0001d530', + '\\' : '\U0001d531', + '\\' : '\U0001d532', + '\\' : '\U0001d533', + '\\' : '\U0001d534', + '\\' : '\U0001d535', + '\\' : '\U0001d536', + '\\' : '\U0001d537', + '\\' : '\U000003b1', + '\\' : '\U000003b2', + '\\' : '\U000003b3', + '\\' : '\U000003b4', + '\\' : '\U000003b5', + '\\' : '\U000003b6', + '\\' : '\U000003b7', + '\\' : '\U000003b8', + '\\' : '\U000003b9', + '\\' : '\U000003ba', + '\\' : '\U000003bb', + '\\' : '\U000003bc', + '\\' : '\U000003bd', + '\\' : '\U000003be', + '\\' : '\U000003c0', + '\\' : '\U000003c1', + '\\' : '\U000003c3', + '\\' : '\U000003c4', + '\\' : '\U000003c5', + '\\' : '\U000003c6', + '\\' : '\U000003c7', + '\\' : '\U000003c8', + '\\' : '\U000003c9', + '\\' : '\U00000393', + '\\' : '\U00000394', + '\\' : '\U00000398', + '\\' : '\U0000039b', + '\\' : '\U0000039e', + '\\' : '\U000003a0', + '\\' : '\U000003a3', + '\\' : '\U000003a5', + '\\' : '\U000003a6', + '\\' : '\U000003a8', + '\\' : '\U000003a9', + '\\' : '\U0001d539', + '\\' : '\U00002102', + '\\' : '\U00002115', + '\\' : '\U0000211a', + '\\' : '\U0000211d', + '\\' : '\U00002124', + '\\' : '\U00002190', + '\\' : '\U000027f5', + '\\' : '\U00002192', + '\\' : '\U000027f6', + '\\' : '\U000021d0', + '\\' : '\U000027f8', + '\\' : '\U000021d2', + '\\' : '\U000027f9', + '\\' : '\U00002194', + '\\' : '\U000027f7', + '\\' : '\U000021d4', + '\\' : '\U000027fa', + '\\' : '\U000021a6', + '\\' : '\U000027fc', + '\\' : '\U00002500', + '\\' : '\U00002550', + '\\' : '\U000021a9', + '\\' : '\U000021aa', + '\\' : '\U000021bd', + '\\' : '\U000021c1', + '\\' : '\U000021bc', + '\\' : '\U000021c0', + '\\' : '\U000021cc', + '\\' : '\U0000219d', + '\\' : '\U000021c3', + '\\' : '\U000021c2', + '\\' : '\U000021bf', + '\\' : '\U000021be', + '\\' : '\U000021be', + '\\' : '\U00002237', + '\\' : '\U00002191', + '\\' : '\U000021d1', + '\\' : '\U00002193', + '\\' : '\U000021d3', + '\\' : '\U00002195', + '\\' : '\U000021d5', + '\\' : '\U000027e8', + '\\' : '\U000027e9', + '\\' : '\U00002308', + '\\' : '\U00002309', + '\\' : '\U0000230a', + '\\' : '\U0000230b', + '\\' : '\U00002987', + '\\' : '\U00002988', + '\\' : '\U000027e6', + '\\' : '\U000027e7', + '\\' : '\U00002983', + '\\' : '\U00002984', + '\\' : '\U000000ab', + '\\' : '\U000000bb', + '\\' : '\U000022a5', + '\\' : '\U000022a4', + '\\' : '\U00002227', + '\\' : '\U000022c0', + '\\' : '\U00002228', + '\\' : '\U000022c1', + '\\' : '\U00002200', + '\\' : '\U00002203', + '\\' : '\U00002204', + '\\' : '\U000000ac', + '\\' : '\U000025a1', + '\\' : '\U000025c7', + '\\' : '\U000022a2', + '\\' : '\U000022a8', + '\\' : '\U000022a9', + '\\' : '\U000022ab', + '\\' : '\U000022a3', + '\\' : '\U0000221a', + '\\' : '\U00002264', + '\\' : '\U00002265', + '\\' : '\U0000226a', + '\\' : '\U0000226b', + '\\' : '\U00002272', + '\\' : '\U00002273', + '\\' : '\U00002a85', + '\\' : '\U00002a86', + '\\' : '\U00002208', + '\\' : '\U00002209', + '\\' : '\U00002282', + '\\' : '\U00002283', + '\\' : '\U00002286', + '\\' : '\U00002287', + '\\' : '\U0000228f', + '\\' : '\U00002290', + '\\' : '\U00002291', + '\\' : '\U00002292', + '\\' : '\U00002229', + '\\' : '\U000022c2', + '\\' : '\U0000222a', + '\\' : '\U000022c3', + '\\' : '\U00002294', + '\\' : '\U00002a06', + '\\' : '\U00002293', + '\\' : '\U00002a05', + '\\' : '\U00002216', + '\\' : '\U0000221d', + '\\' : '\U0000228e', + '\\' : '\U00002a04', + '\\' : '\U00002260', + '\\' : '\U0000223c', + '\\' : '\U00002250', + '\\' : '\U00002243', + '\\' : '\U00002248', + '\\' : '\U0000224d', + '\\' : '\U00002245', + '\\' : '\U00002323', + '\\' : '\U00002261', + '\\' : '\U00002322', + '\\' : '\U000022c8', + '\\' : '\U00002a1d', + '\\' : '\U0000227a', + '\\' : '\U0000227b', + '\\' : '\U0000227c', + '\\' : '\U0000227d', + '\\' : '\U00002225', + '\\' : '\U000000a6', + '\\' : '\U000000b1', + '\\' : '\U00002213', + '\\' : '\U000000d7', + '\\
' : '\U000000f7', + '\\' : '\U000022c5', + '\\' : '\U000022c6', + '\\' : '\U00002219', + '\\' : '\U00002218', + '\\' : '\U00002020', + '\\' : '\U00002021', + '\\' : '\U000022b2', + '\\' : '\U000022b3', + '\\' : '\U000022b4', + '\\' : '\U000022b5', + '\\' : '\U000025c3', + '\\' : '\U000025b9', + '\\' : '\U000025b3', + '\\' : '\U0000225c', + '\\' : '\U00002295', + '\\' : '\U00002a01', + '\\' : '\U00002297', + '\\' : '\U00002a02', + '\\' : '\U00002299', + '\\' : '\U00002a00', + '\\' : '\U00002296', + '\\' : '\U00002298', + '\\' : '\U00002026', + '\\' : '\U000022ef', + '\\' : '\U00002211', + '\\' : '\U0000220f', + '\\' : '\U00002210', + '\\' : '\U0000221e', + '\\' : '\U0000222b', + '\\' : '\U0000222e', + '\\' : '\U00002663', + '\\' : '\U00002662', + '\\' : '\U00002661', + '\\' : '\U00002660', + '\\' : '\U00002135', + '\\' : '\U00002205', + '\\' : '\U00002207', + '\\' : '\U00002202', + '\\' : '\U0000266d', + '\\' : '\U0000266e', + '\\' : '\U0000266f', + '\\' : '\U00002220', + '\\' : '\U000000a9', + '\\' : '\U000000ae', + '\\' : '\U000000ad', + '\\' : '\U000000af', + '\\' : '\U000000bc', + '\\' : '\U000000bd', + '\\' : '\U000000be', + '\\' : '\U000000aa', + '\\' : '\U000000ba', + '\\
' : '\U000000a7', + '\\' : '\U000000b6', + '\\' : '\U000000a1', + '\\' : '\U000000bf', + '\\' : '\U000020ac', + '\\' : '\U000000a3', + '\\' : '\U000000a5', + '\\' : '\U000000a2', + '\\' : '\U000000a4', + '\\' : '\U000000b0', + '\\' : '\U00002a3f', + '\\' : '\U00002127', + '\\' : '\U000025ca', + '\\' : '\U00002118', + '\\' : '\U00002240', + '\\' : '\U000022c4', + '\\' : '\U000000b4', + '\\' : '\U00000131', + '\\' : '\U000000a8', + '\\' : '\U000000b8', + '\\' : '\U000002dd', + '\\' : '\U000003f5', + '\\' : '\U000023ce', + '\\' : '\U00002039', + '\\' : '\U0000203a', + '\\' : '\U00002302', + '\\<^sub>' : '\U000021e9', + '\\<^sup>' : '\U000021e7', + '\\<^bold>' : '\U00002759', + '\\<^bsub>' : '\U000021d8', + '\\<^esub>' : '\U000021d9', + '\\<^bsup>' : '\U000021d7', + '\\<^esup>' : '\U000021d6', + } + + lang_map = {'isabelle' : isabelle_symbols, 'latex' : latex_symbols} + + def __init__(self, **options): + Filter.__init__(self, **options) + lang = get_choice_opt(options, 'lang', + ['isabelle', 'latex'], 'isabelle') + self.symbols = self.lang_map[lang] + + def filter(self, lexer, stream): + for ttype, value in stream: + if value in self.symbols: + yield ttype, self.symbols[value] + else: + yield ttype, value + + +class KeywordCaseFilter(Filter): + """Convert keywords to lowercase or uppercase or capitalize them, which + means first letter uppercase, rest lowercase. + + This can be useful e.g. if you highlight Pascal code and want to adapt the + code to your styleguide. + + Options accepted: + + `case` : string + The casing to convert keywords to. Must be one of ``'lower'``, + ``'upper'`` or ``'capitalize'``. The default is ``'lower'``. + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + case = get_choice_opt(options, 'case', + ['lower', 'upper', 'capitalize'], 'lower') + self.convert = getattr(str, case) + + def filter(self, lexer, stream): + for ttype, value in stream: + if ttype in Keyword: + yield ttype, self.convert(value) + else: + yield ttype, value + + +class NameHighlightFilter(Filter): + """Highlight a normal Name (and Name.*) token with a different token type. + + Example:: + + filter = NameHighlightFilter( + names=['foo', 'bar', 'baz'], + tokentype=Name.Function, + ) + + This would highlight the names "foo", "bar" and "baz" + as functions. `Name.Function` is the default token type. + + Options accepted: + + `names` : list of strings + A list of names that should be given the different token type. + There is no default. + `tokentype` : TokenType or string + A token type or a string containing a token type name that is + used for highlighting the strings in `names`. The default is + `Name.Function`. + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + self.names = set(get_list_opt(options, 'names', [])) + tokentype = options.get('tokentype') + if tokentype: + self.tokentype = string_to_tokentype(tokentype) + else: + self.tokentype = Name.Function + + def filter(self, lexer, stream): + for ttype, value in stream: + if ttype in Name and value in self.names: + yield self.tokentype, value + else: + yield ttype, value + + +class ErrorToken(Exception): + pass + + +class RaiseOnErrorTokenFilter(Filter): + """Raise an exception when the lexer generates an error token. + + Options accepted: + + `excclass` : Exception class + The exception class to raise. + The default is `pygments.filters.ErrorToken`. + + .. versionadded:: 0.8 + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + self.exception = options.get('excclass', ErrorToken) + try: + # issubclass() will raise TypeError if first argument is not a class + if not issubclass(self.exception, Exception): + raise TypeError + except TypeError: + raise OptionError('excclass option is not an exception class') + + def filter(self, lexer, stream): + for ttype, value in stream: + if ttype is Error: + raise self.exception(value) + yield ttype, value + + +class VisibleWhitespaceFilter(Filter): + """Convert tabs, newlines and/or spaces to visible characters. + + Options accepted: + + `spaces` : string or bool + If this is a one-character string, spaces will be replaces by this string. + If it is another true value, spaces will be replaced by ``·`` (unicode + MIDDLE DOT). If it is a false value, spaces will not be replaced. The + default is ``False``. + `tabs` : string or bool + The same as for `spaces`, but the default replacement character is ``»`` + (unicode RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK). The default value + is ``False``. Note: this will not work if the `tabsize` option for the + lexer is nonzero, as tabs will already have been expanded then. + `tabsize` : int + If tabs are to be replaced by this filter (see the `tabs` option), this + is the total number of characters that a tab should be expanded to. + The default is ``8``. + `newlines` : string or bool + The same as for `spaces`, but the default replacement character is ``¶`` + (unicode PILCROW SIGN). The default value is ``False``. + `wstokentype` : bool + If true, give whitespace the special `Whitespace` token type. This allows + styling the visible whitespace differently (e.g. greyed out), but it can + disrupt background colors. The default is ``True``. + + .. versionadded:: 0.8 + """ + + def __init__(self, **options): + Filter.__init__(self, **options) + for name, default in [('spaces', '·'), + ('tabs', '»'), + ('newlines', '¶')]: + opt = options.get(name, False) + if isinstance(opt, str) and len(opt) == 1: + setattr(self, name, opt) + else: + setattr(self, name, (opt and default or '')) + tabsize = get_int_opt(options, 'tabsize', 8) + if self.tabs: + self.tabs += ' ' * (tabsize - 1) + if self.newlines: + self.newlines += '\n' + self.wstt = get_bool_opt(options, 'wstokentype', True) + + def filter(self, lexer, stream): + if self.wstt: + spaces = self.spaces or ' ' + tabs = self.tabs or '\t' + newlines = self.newlines or '\n' + regex = re.compile(r'\s') + + def replacefunc(wschar): + if wschar == ' ': + return spaces + elif wschar == '\t': + return tabs + elif wschar == '\n': + return newlines + return wschar + + for ttype, value in stream: + yield from _replace_special(ttype, value, regex, Whitespace, + replacefunc) + else: + spaces, tabs, newlines = self.spaces, self.tabs, self.newlines + # simpler processing + for ttype, value in stream: + if spaces: + value = value.replace(' ', spaces) + if tabs: + value = value.replace('\t', tabs) + if newlines: + value = value.replace('\n', newlines) + yield ttype, value + + +class GobbleFilter(Filter): + """Gobbles source code lines (eats initial characters). + + This filter drops the first ``n`` characters off every line of code. This + may be useful when the source code fed to the lexer is indented by a fixed + amount of space that isn't desired in the output. + + Options accepted: + + `n` : int + The number of characters to gobble. + + .. versionadded:: 1.2 + """ + def __init__(self, **options): + Filter.__init__(self, **options) + self.n = get_int_opt(options, 'n', 0) + + def gobble(self, value, left): + if left < len(value): + return value[left:], 0 + else: + return '', left - len(value) + + def filter(self, lexer, stream): + n = self.n + left = n # How many characters left to gobble. + for ttype, value in stream: + # Remove ``left`` tokens from first line, ``n`` from all others. + parts = value.split('\n') + (parts[0], left) = self.gobble(parts[0], left) + for i in range(1, len(parts)): + (parts[i], left) = self.gobble(parts[i], n) + value = '\n'.join(parts) + + if value != '': + yield ttype, value + + +class TokenMergeFilter(Filter): + """Merges consecutive tokens with the same token type in the output + stream of a lexer. + + .. versionadded:: 1.2 + """ + def __init__(self, **options): + Filter.__init__(self, **options) + + def filter(self, lexer, stream): + current_type = None + current_value = None + for ttype, value in stream: + if ttype is current_type: + current_value += value + else: + if current_type is not None: + yield current_type, current_value + current_type = ttype + current_value = value + if current_type is not None: + yield current_type, current_value + + +FILTERS = { + 'codetagify': CodeTagFilter, + 'keywordcase': KeywordCaseFilter, + 'highlight': NameHighlightFilter, + 'raiseonerror': RaiseOnErrorTokenFilter, + 'whitespace': VisibleWhitespaceFilter, + 'gobble': GobbleFilter, + 'tokenmerge': TokenMergeFilter, + 'symbols': SymbolFilter, +} diff --git a/.venv/lib/python3.8/site-packages/pygments/filters/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/filters/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b581c99 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/filters/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatter.py b/.venv/lib/python3.8/site-packages/pygments/formatter.py new file mode 100644 index 0000000..c3fe68d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/formatter.py @@ -0,0 +1,94 @@ +""" + pygments.formatter + ~~~~~~~~~~~~~~~~~~ + + Base formatter class. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import codecs + +from pygments.util import get_bool_opt +from pygments.styles import get_style_by_name + +__all__ = ['Formatter'] + + +def _lookup_style(style): + if isinstance(style, str): + return get_style_by_name(style) + return style + + +class Formatter: + """ + Converts a token stream to text. + + Options accepted: + + ``style`` + The style to use, can be a string or a Style subclass + (default: "default"). Not used by e.g. the + TerminalFormatter. + ``full`` + Tells the formatter to output a "full" document, i.e. + a complete self-contained document. This doesn't have + any effect for some formatters (default: false). + ``title`` + If ``full`` is true, the title that should be used to + caption the document (default: ''). + ``encoding`` + If given, must be an encoding name. This will be used to + convert the Unicode token strings to byte strings in the + output. If it is "" or None, Unicode strings will be written + to the output file, which most file-like objects do not + support (default: None). + ``outencoding`` + Overrides ``encoding`` if given. + """ + + #: Name of the formatter + name = None + + #: Shortcuts for the formatter + aliases = [] + + #: fn match rules + filenames = [] + + #: If True, this formatter outputs Unicode strings when no encoding + #: option is given. + unicodeoutput = True + + def __init__(self, **options): + self.style = _lookup_style(options.get('style', 'default')) + self.full = get_bool_opt(options, 'full', False) + self.title = options.get('title', '') + self.encoding = options.get('encoding', None) or None + if self.encoding in ('guess', 'chardet'): + # can happen for e.g. pygmentize -O encoding=guess + self.encoding = 'utf-8' + self.encoding = options.get('outencoding') or self.encoding + self.options = options + + def get_style_defs(self, arg=''): + """ + Return the style definitions for the current style as a string. + + ``arg`` is an additional argument whose meaning depends on the + formatter used. Note that ``arg`` can also be a list or tuple + for some formatters like the html formatter. + """ + return '' + + def format(self, tokensource, outfile): + """ + Format ``tokensource``, an iterable of ``(tokentype, tokenstring)`` + tuples and write it into ``outfile``. + """ + if self.encoding: + # wrap the outfile in a StreamWriter + outfile = codecs.lookup(self.encoding)[3](outfile) + return self.format_unencoded(tokensource, outfile) diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__init__.py b/.venv/lib/python3.8/site-packages/pygments/formatters/__init__.py new file mode 100644 index 0000000..66c9e9d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/formatters/__init__.py @@ -0,0 +1,153 @@ +""" + pygments.formatters + ~~~~~~~~~~~~~~~~~~~ + + Pygments formatters. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import sys +import types +import fnmatch +from os.path import basename + +from pygments.formatters._mapping import FORMATTERS +from pygments.plugin import find_plugin_formatters +from pygments.util import ClassNotFound + +__all__ = ['get_formatter_by_name', 'get_formatter_for_filename', + 'get_all_formatters', 'load_formatter_from_file'] + list(FORMATTERS) + +_formatter_cache = {} # classes by name +_pattern_cache = {} + + +def _fn_matches(fn, glob): + """Return whether the supplied file name fn matches pattern filename.""" + if glob not in _pattern_cache: + pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob)) + return pattern.match(fn) + return _pattern_cache[glob].match(fn) + + +def _load_formatters(module_name): + """Load a formatter (and all others in the module too).""" + mod = __import__(module_name, None, None, ['__all__']) + for formatter_name in mod.__all__: + cls = getattr(mod, formatter_name) + _formatter_cache[cls.name] = cls + + +def get_all_formatters(): + """Return a generator for all formatter classes.""" + # NB: this returns formatter classes, not info like get_all_lexers(). + for info in FORMATTERS.values(): + if info[1] not in _formatter_cache: + _load_formatters(info[0]) + yield _formatter_cache[info[1]] + for _, formatter in find_plugin_formatters(): + yield formatter + + +def find_formatter_class(alias): + """Lookup a formatter by alias. + + Returns None if not found. + """ + for module_name, name, aliases, _, _ in FORMATTERS.values(): + if alias in aliases: + if name not in _formatter_cache: + _load_formatters(module_name) + return _formatter_cache[name] + for _, cls in find_plugin_formatters(): + if alias in cls.aliases: + return cls + + +def get_formatter_by_name(_alias, **options): + """Lookup and instantiate a formatter by alias. + + Raises ClassNotFound if not found. + """ + cls = find_formatter_class(_alias) + if cls is None: + raise ClassNotFound("no formatter found for name %r" % _alias) + return cls(**options) + + +def load_formatter_from_file(filename, formattername="CustomFormatter", + **options): + """Load a formatter from a file. + + This method expects a file located relative to the current working + directory, which contains a class named CustomFormatter. By default, + it expects the Formatter to be named CustomFormatter; you can specify + your own class name as the second argument to this function. + + Users should be very careful with the input, because this method + is equivalent to running eval on the input file. + + Raises ClassNotFound if there are any problems importing the Formatter. + + .. versionadded:: 2.2 + """ + try: + # This empty dict will contain the namespace for the exec'd file + custom_namespace = {} + with open(filename, 'rb') as f: + exec(f.read(), custom_namespace) + # Retrieve the class `formattername` from that namespace + if formattername not in custom_namespace: + raise ClassNotFound('no valid %s class found in %s' % + (formattername, filename)) + formatter_class = custom_namespace[formattername] + # And finally instantiate it with the options + return formatter_class(**options) + except OSError as err: + raise ClassNotFound('cannot read %s: %s' % (filename, err)) + except ClassNotFound: + raise + except Exception as err: + raise ClassNotFound('error when loading custom formatter: %s' % err) + + +def get_formatter_for_filename(fn, **options): + """Lookup and instantiate a formatter by filename pattern. + + Raises ClassNotFound if not found. + """ + fn = basename(fn) + for modname, name, _, filenames, _ in FORMATTERS.values(): + for filename in filenames: + if _fn_matches(fn, filename): + if name not in _formatter_cache: + _load_formatters(modname) + return _formatter_cache[name](**options) + for cls in find_plugin_formatters(): + for filename in cls.filenames: + if _fn_matches(fn, filename): + return cls(**options) + raise ClassNotFound("no formatter found for file name %r" % fn) + + +class _automodule(types.ModuleType): + """Automatically import formatters.""" + + def __getattr__(self, name): + info = FORMATTERS.get(name) + if info: + _load_formatters(info[0]) + cls = _formatter_cache[info[1]] + setattr(self, name, cls) + return cls + raise AttributeError(name) + + +oldmod = sys.modules[__name__] +newmod = _automodule(__name__) +newmod.__dict__.update(oldmod.__dict__) +sys.modules[__name__] = newmod +del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/__init__.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ec9c63d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/__init__.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/_mapping.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/_mapping.cpython-38.pyc new file mode 100644 index 0000000..9746437 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/_mapping.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/bbcode.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/bbcode.cpython-38.pyc new file mode 100644 index 0000000..d673c38 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/bbcode.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/groff.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/groff.cpython-38.pyc new file mode 100644 index 0000000..06522bb Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/groff.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/html.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/html.cpython-38.pyc new file mode 100644 index 0000000..7b5b832 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/html.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/img.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/img.cpython-38.pyc new file mode 100644 index 0000000..b40c529 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/img.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/irc.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/irc.cpython-38.pyc new file mode 100644 index 0000000..5ba3ab2 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/irc.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/latex.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/latex.cpython-38.pyc new file mode 100644 index 0000000..35db48d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/latex.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/other.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/other.cpython-38.pyc new file mode 100644 index 0000000..8c1c32a Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/other.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/pangomarkup.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/pangomarkup.cpython-38.pyc new file mode 100644 index 0000000..830bfb9 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/pangomarkup.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/rtf.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/rtf.cpython-38.pyc new file mode 100644 index 0000000..366eb2c Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/rtf.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/svg.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/svg.cpython-38.pyc new file mode 100644 index 0000000..2b9f70d Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/svg.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/terminal.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/terminal.cpython-38.pyc new file mode 100644 index 0000000..a26c1c7 Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/terminal.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/terminal256.cpython-38.pyc b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/terminal256.cpython-38.pyc new file mode 100644 index 0000000..8ae089f Binary files /dev/null and b/.venv/lib/python3.8/site-packages/pygments/formatters/__pycache__/terminal256.cpython-38.pyc differ diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/_mapping.py b/.venv/lib/python3.8/site-packages/pygments/formatters/_mapping.py new file mode 100644 index 0000000..8b5e478 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/formatters/_mapping.py @@ -0,0 +1,84 @@ +""" + pygments.formatters._mapping + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Formatter mapping definitions. This file is generated by itself. Everytime + you change something on a builtin formatter definition, run this script from + the formatters folder to update it. + + Do not alter the FORMATTERS dictionary by hand. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +FORMATTERS = { + 'BBCodeFormatter': ('pygments.formatters.bbcode', 'BBCode', ('bbcode', 'bb'), (), 'Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there.'), + 'BmpImageFormatter': ('pygments.formatters.img', 'img_bmp', ('bmp', 'bitmap'), ('*.bmp',), 'Create a bitmap image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'), + 'GifImageFormatter': ('pygments.formatters.img', 'img_gif', ('gif',), ('*.gif',), 'Create a GIF image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'), + 'GroffFormatter': ('pygments.formatters.groff', 'groff', ('groff', 'troff', 'roff'), (), 'Format tokens with groff escapes to change their color and font style.'), + 'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "Format tokens as HTML 4 ```` tags within a ``
`` tag, wrapped in a ``
`` tag. The ``
``'s CSS class can be set by the `cssclass` option."), + 'IRCFormatter': ('pygments.formatters.irc', 'IRC', ('irc', 'IRC'), (), 'Format tokens with IRC color sequences'), + 'ImageFormatter': ('pygments.formatters.img', 'img', ('img', 'IMG', 'png'), ('*.png',), 'Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'), + 'JpgImageFormatter': ('pygments.formatters.img', 'img_jpg', ('jpg', 'jpeg'), ('*.jpg',), 'Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'), + 'LatexFormatter': ('pygments.formatters.latex', 'LaTeX', ('latex', 'tex'), ('*.tex',), 'Format tokens as LaTeX code. This needs the `fancyvrb` and `color` standard packages.'), + 'NullFormatter': ('pygments.formatters.other', 'Text only', ('text', 'null'), ('*.txt',), 'Output the text unchanged without any formatting.'), + 'PangoMarkupFormatter': ('pygments.formatters.pangomarkup', 'Pango Markup', ('pango', 'pangomarkup'), (), 'Format tokens as Pango Markup code. It can then be rendered to an SVG.'), + 'RawTokenFormatter': ('pygments.formatters.other', 'Raw tokens', ('raw', 'tokens'), ('*.raw',), 'Format tokens as a raw representation for storing token streams.'), + 'RtfFormatter': ('pygments.formatters.rtf', 'RTF', ('rtf',), ('*.rtf',), 'Format tokens as RTF markup. This formatter automatically outputs full RTF documents with color information and other useful stuff. Perfect for Copy and Paste into Microsoft(R) Word(R) documents.'), + 'SvgFormatter': ('pygments.formatters.svg', 'SVG', ('svg',), ('*.svg',), 'Format tokens as an SVG graphics file. This formatter is still experimental. Each line of code is a ```` element with explicit ``x`` and ``y`` coordinates containing ```` elements with the individual token styles.'), + 'Terminal256Formatter': ('pygments.formatters.terminal256', 'Terminal256', ('terminal256', 'console256', '256'), (), 'Format tokens with ANSI color sequences, for output in a 256-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'), + 'TerminalFormatter': ('pygments.formatters.terminal', 'Terminal', ('terminal', 'console'), (), 'Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly.'), + 'TerminalTrueColorFormatter': ('pygments.formatters.terminal256', 'TerminalTrueColor', ('terminal16m', 'console16m', '16m'), (), 'Format tokens with ANSI color sequences, for output in a true-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'), + 'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.') +} + +if __name__ == '__main__': # pragma: no cover + import sys + import os + + # lookup formatters + found_formatters = [] + imports = [] + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + from pygments.util import docstring_headline + + for root, dirs, files in os.walk('.'): + for filename in files: + if filename.endswith('.py') and not filename.startswith('_'): + module_name = 'pygments.formatters%s.%s' % ( + root[1:].replace('/', '.'), filename[:-3]) + print(module_name) + module = __import__(module_name, None, None, ['']) + for formatter_name in module.__all__: + formatter = getattr(module, formatter_name) + found_formatters.append( + '%r: %r' % (formatter_name, + (module_name, + formatter.name, + tuple(formatter.aliases), + tuple(formatter.filenames), + docstring_headline(formatter)))) + # sort them to make the diff minimal + found_formatters.sort() + + # extract useful sourcecode from this file + with open(__file__) as fp: + content = fp.read() + # replace crnl to nl for Windows. + # + # Note that, originally, contributers should keep nl of master + # repository, for example by using some kind of automatic + # management EOL, like `EolExtension + # `. + content = content.replace("\r\n", "\n") + header = content[:content.find('FORMATTERS = {')] + footer = content[content.find("if __name__ == '__main__':"):] + + # write new file + with open(__file__, 'w') as fp: + fp.write(header) + fp.write('FORMATTERS = {\n %s\n}\n\n' % ',\n '.join(found_formatters)) + fp.write(footer) + + print ('=== %d formatters processed.' % len(found_formatters)) diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/bbcode.py b/.venv/lib/python3.8/site-packages/pygments/formatters/bbcode.py new file mode 100644 index 0000000..586a892 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/formatters/bbcode.py @@ -0,0 +1,108 @@ +""" + pygments.formatters.bbcode + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + BBcode formatter. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + + +from pygments.formatter import Formatter +from pygments.util import get_bool_opt + +__all__ = ['BBCodeFormatter'] + + +class BBCodeFormatter(Formatter): + """ + Format tokens with BBcodes. These formatting codes are used by many + bulletin boards, so you can highlight your sourcecode with pygments before + posting it there. + + This formatter has no support for background colors and borders, as there + are no common BBcode tags for that. + + Some board systems (e.g. phpBB) don't support colors in their [code] tag, + so you can't use the highlighting together with that tag. + Text in a [code] tag usually is shown with a monospace font (which this + formatter can do with the ``monofont`` option) and no spaces (which you + need for indentation) are removed. + + Additional options accepted: + + `style` + The style to use, can be a string or a Style subclass (default: + ``'default'``). + + `codetag` + If set to true, put the output into ``[code]`` tags (default: + ``false``) + + `monofont` + If set to true, add a tag to show the code with a monospace font + (default: ``false``). + """ + name = 'BBCode' + aliases = ['bbcode', 'bb'] + filenames = [] + + def __init__(self, **options): + Formatter.__init__(self, **options) + self._code = get_bool_opt(options, 'codetag', False) + self._mono = get_bool_opt(options, 'monofont', False) + + self.styles = {} + self._make_styles() + + def _make_styles(self): + for ttype, ndef in self.style: + start = end = '' + if ndef['color']: + start += '[color=#%s]' % ndef['color'] + end = '[/color]' + end + if ndef['bold']: + start += '[b]' + end = '[/b]' + end + if ndef['italic']: + start += '[i]' + end = '[/i]' + end + if ndef['underline']: + start += '[u]' + end = '[/u]' + end + # there are no common BBcodes for background-color and border + + self.styles[ttype] = start, end + + def format_unencoded(self, tokensource, outfile): + if self._code: + outfile.write('[code]') + if self._mono: + outfile.write('[font=monospace]') + + lastval = '' + lasttype = None + + for ttype, value in tokensource: + while ttype not in self.styles: + ttype = ttype.parent + if ttype == lasttype: + lastval += value + else: + if lastval: + start, end = self.styles[lasttype] + outfile.write(''.join((start, lastval, end))) + lastval = value + lasttype = ttype + + if lastval: + start, end = self.styles[lasttype] + outfile.write(''.join((start, lastval, end))) + + if self._mono: + outfile.write('[/font]') + if self._code: + outfile.write('[/code]') + if self._code or self._mono: + outfile.write('\n') diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/groff.py b/.venv/lib/python3.8/site-packages/pygments/formatters/groff.py new file mode 100644 index 0000000..5aafe6d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/formatters/groff.py @@ -0,0 +1,168 @@ +""" + pygments.formatters.groff + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Formatter for groff output. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import math +from pygments.formatter import Formatter +from pygments.util import get_bool_opt, get_int_opt + +__all__ = ['GroffFormatter'] + + +class GroffFormatter(Formatter): + """ + Format tokens with groff escapes to change their color and font style. + + .. versionadded:: 2.11 + + Additional options accepted: + + `style` + The style to use, can be a string or a Style subclass (default: + ``'default'``). + + `monospaced` + If set to true, monospace font will be used (default: ``true``). + + `linenos` + If set to true, print the line numbers (default: ``false``). + + `wrap` + Wrap lines to the specified number of characters. Disabled if set to 0 + (default: ``0``). + """ + + name = 'groff' + aliases = ['groff','troff','roff'] + filenames = [] + + def __init__(self, **options): + Formatter.__init__(self, **options) + + self.monospaced = get_bool_opt(options, 'monospaced', True) + self.linenos = get_bool_opt(options, 'linenos', False) + self._lineno = 0 + self.wrap = get_int_opt(options, 'wrap', 0) + self._linelen = 0 + + self.styles = {} + self._make_styles() + + + def _make_styles(self): + regular = '\\f[CR]' if self.monospaced else '\\f[R]' + bold = '\\f[CB]' if self.monospaced else '\\f[B]' + italic = '\\f[CI]' if self.monospaced else '\\f[I]' + + for ttype, ndef in self.style: + start = end = '' + if ndef['color']: + start += '\\m[%s]' % ndef['color'] + end = '\\m[]' + end + if ndef['bold']: + start += bold + end = regular + end + if ndef['italic']: + start += italic + end = regular + end + if ndef['bgcolor']: + start += '\\M[%s]' % ndef['bgcolor'] + end = '\\M[]' + end + + self.styles[ttype] = start, end + + + def _define_colors(self, outfile): + colors = set() + for _, ndef in self.style: + if ndef['color'] is not None: + colors.add(ndef['color']) + + for color in colors: + outfile.write('.defcolor ' + color + ' rgb #' + color + '\n') + + + def _write_lineno(self, outfile): + self._lineno += 1 + outfile.write("%s% 4d " % (self._lineno != 1 and '\n' or '', self._lineno)) + + + def _wrap_line(self, line): + length = len(line.rstrip('\n')) + space = ' ' if self.linenos else '' + newline = '' + + if length > self.wrap: + for i in range(0, math.floor(length / self.wrap)): + chunk = line[i*self.wrap:i*self.wrap+self.wrap] + newline += (chunk + '\n' + space) + remainder = length % self.wrap + if remainder > 0: + newline += line[-remainder-1:] + self._linelen = remainder + elif self._linelen + length > self.wrap: + newline = ('\n' + space) + line + self._linelen = length + else: + newline = line + self._linelen += length + + return newline + + + def _escape_chars(self, text): + text = text.replace('\\', '\\[u005C]'). \ + replace('.', '\\[char46]'). \ + replace('\'', '\\[u0027]'). \ + replace('`', '\\[u0060]'). \ + replace('~', '\\[u007E]') + copy = text + + for char in copy: + if len(char) != len(char.encode()): + uni = char.encode('unicode_escape') \ + .decode()[1:] \ + .replace('x', 'u00') \ + .upper() + text = text.replace(char, '\\[u' + uni[1:] + ']') + + return text + + + def format_unencoded(self, tokensource, outfile): + self._define_colors(outfile) + + outfile.write('.nf\n\\f[CR]\n') + + if self.linenos: + self._write_lineno(outfile) + + for ttype, value in tokensource: + start, end = self.styles[ttype] + + for line in value.splitlines(True): + if self.wrap > 0: + line = self._wrap_line(line) + + if start and end: + text = self._escape_chars(line.rstrip('\n')) + if text != '': + outfile.write(''.join((start, text, end))) + else: + outfile.write(self._escape_chars(line.rstrip('\n'))) + + if line.endswith('\n'): + if self.linenos: + self._write_lineno(outfile) + self._linelen = 0 + else: + outfile.write('\n') + self._linelen = 0 + + outfile.write('\n.fi') diff --git a/.venv/lib/python3.8/site-packages/pygments/formatters/html.py b/.venv/lib/python3.8/site-packages/pygments/formatters/html.py new file mode 100644 index 0000000..f3a77a2 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/formatters/html.py @@ -0,0 +1,983 @@ +""" + pygments.formatters.html + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Formatter for HTML output. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import functools +import os +import sys +import os.path +from io import StringIO + +from pygments.formatter import Formatter +from pygments.token import Token, Text, STANDARD_TYPES +from pygments.util import get_bool_opt, get_int_opt, get_list_opt + +try: + import ctags +except ImportError: + ctags = None + +__all__ = ['HtmlFormatter'] + + +_escape_html_table = { + ord('&'): '&', + ord('<'): '<', + ord('>'): '>', + ord('"'): '"', + ord("'"): ''', +} + + +def escape_html(text, table=_escape_html_table): + """Escape &, <, > as well as single and double quotes for HTML.""" + return text.translate(table) + + +def webify(color): + if color.startswith('calc') or color.startswith('var'): + return color + else: + return '#' + color + + +def _get_ttype_class(ttype): + fname = STANDARD_TYPES.get(ttype) + if fname: + return fname + aname = '' + while fname is None: + aname = '-' + ttype[-1] + aname + ttype = ttype.parent + fname = STANDARD_TYPES.get(ttype) + return fname + aname + + +CSSFILE_TEMPLATE = '''\ +/* +generated by Pygments +Copyright 2006-2021 by the Pygments team. +Licensed under the BSD license, see LICENSE for details. +*/ +%(styledefs)s +''' + +DOC_HEADER = '''\ + + + + + %(title)s + + + + +

%(title)s

+ +''' + +DOC_HEADER_EXTERNALCSS = '''\ + + + + + %(title)s + + + + +

%(title)s

+ +''' + +DOC_FOOTER = '''\ + + +''' + + +class HtmlFormatter(Formatter): + r""" + Format tokens as HTML 4 ```` tags within a ``
`` tag, wrapped
+    in a ``
`` tag. The ``
``'s CSS class can be set by the `cssclass` + option. + + If the `linenos` option is set to ``"table"``, the ``
`` is
+    additionally wrapped inside a ```` which has one row and two
+    cells: one containing the line numbers and one containing the code.
+    Example:
+
+    .. sourcecode:: html
+
+        
+
+ + +
+
1
+            2
+
+
def foo(bar):
+              pass
+            
+
+ + (whitespace added to improve clarity). + + Wrapping can be disabled using the `nowrap` option. + + A list of lines can be specified using the `hl_lines` option to make these + lines highlighted (as of Pygments 0.11). + + With the `full` option, a complete HTML 4 document is output, including + the style definitions inside a ``$)', _handle_cssblock), + + include('keywords'), + include('inline'), + ], + 'keywords': [ + (words(( + '\\define', '\\end', 'caption', 'created', 'modified', 'tags', + 'title', 'type'), prefix=r'^', suffix=r'\b'), + Keyword), + ], + 'inline': [ + # escape + (r'\\.', Text), + # created or modified date + (r'\d{17}', Number.Integer), + # italics + (r'(\s)(//[^/]+//)((?=\W|\n))', + bygroups(Text, Generic.Emph, Text)), + # superscript + (r'(\s)(\^\^[^\^]+\^\^)', bygroups(Text, Generic.Emph)), + # subscript + (r'(\s)(,,[^,]+,,)', bygroups(Text, Generic.Emph)), + # underscore + (r'(\s)(__[^_]+__)', bygroups(Text, Generic.Strong)), + # bold + (r"(\s)(''[^']+'')((?=\W|\n))", + bygroups(Text, Generic.Strong, Text)), + # strikethrough + (r'(\s)(~~[^~]+~~)((?=\W|\n))', + bygroups(Text, Generic.Deleted, Text)), + # TiddlyWiki variables + (r'<<[^>]+>>', Name.Tag), + (r'\$\$[^$]+\$\$', Name.Tag), + (r'\$\([^)]+\)\$', Name.Tag), + # TiddlyWiki style or class + (r'^@@.*$', Name.Tag), + # HTML tags + (r']+>', Name.Tag), + # inline code + (r'`[^`]+`', String.Backtick), + # HTML escaped symbols + (r'&\S*?;', String.Regex), + # Wiki links + (r'(\[{2})([^]\|]+)(\]{2})', bygroups(Text, Name.Tag, Text)), + # External links + (r'(\[{2})([^]\|]+)(\|)([^]\|]+)(\]{2})', + bygroups(Text, Name.Tag, Text, Name.Attribute, Text)), + # Transclusion + (r'(\{{2})([^}]+)(\}{2})', bygroups(Text, Name.Tag, Text)), + # URLs + (r'(\b.?.?tps?://[^\s"]+)', bygroups(Name.Attribute)), + + # general text, must come last! + (r'[\w]+', Text), + (r'.', Text) + ], + } + + def __init__(self, **options): + self.handlecodeblocks = get_bool_opt(options, 'handlecodeblocks', True) + RegexLexer.__init__(self, **options) diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/math.py b/.venv/lib/python3.8/site-packages/pygments/lexers/math.py new file mode 100644 index 0000000..88f810e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/math.py @@ -0,0 +1,20 @@ +""" + pygments.lexers.math + ~~~~~~~~~~~~~~~~~~~~ + + Just export lexers that were contained in this module. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexers.python import NumPyLexer +from pygments.lexers.matlab import MatlabLexer, MatlabSessionLexer, \ + OctaveLexer, ScilabLexer +from pygments.lexers.julia import JuliaLexer, JuliaConsoleLexer +from pygments.lexers.r import RConsoleLexer, SLexer, RdLexer +from pygments.lexers.modeling import BugsLexer, JagsLexer, StanLexer +from pygments.lexers.idl import IDLLexer +from pygments.lexers.algebra import MuPADLexer + +__all__ = [] diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/matlab.py b/.venv/lib/python3.8/site-packages/pygments/lexers/matlab.py new file mode 100644 index 0000000..4450639 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/matlab.py @@ -0,0 +1,3294 @@ +""" + pygments.lexers.matlab + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Matlab and related languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import Lexer, RegexLexer, bygroups, default, words, \ + do_insertions, include +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Generic, Whitespace + +from pygments.lexers import _scilab_builtins + +__all__ = ['MatlabLexer', 'MatlabSessionLexer', 'OctaveLexer', 'ScilabLexer'] + + +class MatlabLexer(RegexLexer): + """ + For Matlab source code. + + .. versionadded:: 0.10 + """ + name = 'Matlab' + aliases = ['matlab'] + filenames = ['*.m'] + mimetypes = ['text/matlab'] + + _operators = r'-|==|~=|<=|>=|<|>|&&|&|~|\|\|?|\.\*|\*|\+|\.\^|\.\\|\./|/|\\' + + tokens = { + 'expressions': [ + # operators: + (_operators, Operator), + + # numbers (must come before punctuation to handle `.5`; cannot use + # `\b` due to e.g. `5. + .5`). The negative lookahead on operators + # avoids including the dot in `1./x` (the dot is part of `./`). + (r'(? and then + # (equal | open-parenthesis | | ). + (r'(?:^|(?<=;))(\s*)(\w+)(\s+)(?!=|\(|%s\s|\s)' % _operators, + bygroups(Whitespace, Name, Whitespace), 'commandargs'), + + include('expressions') + ], + 'blockcomment': [ + (r'^\s*%\}', Comment.Multiline, '#pop'), + (r'^.*\n', Comment.Multiline), + (r'.', Comment.Multiline), + ], + 'deffunc': [ + (r'(\s*)(?:(\S+)(\s*)(=)(\s*))?(.+)(\()(.*)(\))(\s*)', + bygroups(Whitespace, Text, Whitespace, Punctuation, + Whitespace, Name.Function, Punctuation, Text, + Punctuation, Whitespace), '#pop'), + # function with no args + (r'(\s*)([a-zA-Z_]\w*)', + bygroups(Whitespace, Name.Function), '#pop'), + ], + 'propattrs': [ + (r'(\w+)(\s*)(=)(\s*)(\d+)', + bygroups(Name.Builtin, Whitespace, Punctuation, Whitespace, + Number)), + (r'(\w+)(\s*)(=)(\s*)([a-zA-Z]\w*)', + bygroups(Name.Builtin, Whitespace, Punctuation, Whitespace, + Keyword)), + (r',', Punctuation), + (r'\)', Punctuation, '#pop'), + (r'\s+', Whitespace), + (r'.', Text), + ], + 'defprops': [ + (r'%\{\s*\n', Comment.Multiline, 'blockcomment'), + (r'%.*$', Comment), + (r'(?. + + .. versionadded:: 0.10 + """ + name = 'Matlab session' + aliases = ['matlabsession'] + + def get_tokens_unprocessed(self, text): + mlexer = MatlabLexer(**self.options) + + curcode = '' + insertions = [] + continuation = False + + for match in line_re.finditer(text): + line = match.group() + + if line.startswith('>> '): + insertions.append((len(curcode), + [(0, Generic.Prompt, line[:3])])) + curcode += line[3:] + + elif line.startswith('>>'): + insertions.append((len(curcode), + [(0, Generic.Prompt, line[:2])])) + curcode += line[2:] + + elif line.startswith('???'): + + idx = len(curcode) + + # without is showing error on same line as before...? + # line = "\n" + line + token = (0, Generic.Traceback, line) + insertions.append((idx, [token])) + elif continuation: + # line_start is the length of the most recent prompt symbol + line_start = len(insertions[-1][-1][-1]) + # Set leading spaces with the length of the prompt to be a generic prompt + # This keeps code aligned when prompts are removed, say with some Javascript + if line.startswith(' '*line_start): + insertions.append( + (len(curcode), [(0, Generic.Prompt, line[:line_start])])) + curcode += line[line_start:] + else: + curcode += line + else: + if curcode: + yield from do_insertions( + insertions, mlexer.get_tokens_unprocessed(curcode)) + curcode = '' + insertions = [] + + yield match.start(), Generic.Output, line + + # Does not allow continuation if a comment is included after the ellipses. + # Continues any line that ends with ..., even comments (lines that start with %) + if line.strip().endswith('...'): + continuation = True + else: + continuation = False + + if curcode: # or item: + yield from do_insertions( + insertions, mlexer.get_tokens_unprocessed(curcode)) + + +class OctaveLexer(RegexLexer): + """ + For GNU Octave source code. + + .. versionadded:: 1.5 + """ + name = 'Octave' + aliases = ['octave'] + filenames = ['*.m'] + mimetypes = ['text/octave'] + + # These lists are generated automatically. + # Run the following in bash shell: + # + # First dump all of the Octave manual into a plain text file: + # + # $ info octave --subnodes -o octave-manual + # + # Now grep through it: + + # for i in \ + # "Built-in Function" "Command" "Function File" \ + # "Loadable Function" "Mapping Function"; + # do + # perl -e '@name = qw('"$i"'); + # print lc($name[0]),"_kw = [\n"'; + # + # perl -n -e 'print "\"$1\",\n" if /-- '"$i"': .* (\w*) \(/;' \ + # octave-manual | sort | uniq ; + # echo "]" ; + # echo; + # done + + # taken from Octave Mercurial changeset 8cc154f45e37 (30-jan-2011) + + builtin_kw = ( + "addlistener", "addpath", "addproperty", "all", + "and", "any", "argnames", "argv", "assignin", + "atexit", "autoload", + "available_graphics_toolkits", "beep_on_error", + "bitand", "bitmax", "bitor", "bitshift", "bitxor", + "cat", "cell", "cellstr", "char", "class", "clc", + "columns", "command_line_path", + "completion_append_char", "completion_matches", + "complex", "confirm_recursive_rmdir", "cputime", + "crash_dumps_octave_core", "ctranspose", "cumprod", + "cumsum", "debug_on_error", "debug_on_interrupt", + "debug_on_warning", "default_save_options", + "dellistener", "diag", "diff", "disp", + "doc_cache_file", "do_string_escapes", "double", + "drawnow", "e", "echo_executing_commands", "eps", + "eq", "errno", "errno_list", "error", "eval", + "evalin", "exec", "exist", "exit", "eye", "false", + "fclear", "fclose", "fcntl", "fdisp", "feof", + "ferror", "feval", "fflush", "fgetl", "fgets", + "fieldnames", "file_in_loadpath", "file_in_path", + "filemarker", "filesep", "find_dir_in_path", + "fixed_point_format", "fnmatch", "fopen", "fork", + "formula", "fprintf", "fputs", "fread", "freport", + "frewind", "fscanf", "fseek", "fskipl", "ftell", + "functions", "fwrite", "ge", "genpath", "get", + "getegid", "getenv", "geteuid", "getgid", + "getpgrp", "getpid", "getppid", "getuid", "glob", + "gt", "gui_mode", "history_control", + "history_file", "history_size", + "history_timestamp_format_string", "home", + "horzcat", "hypot", "ifelse", + "ignore_function_time_stamp", "inferiorto", + "info_file", "info_program", "inline", "input", + "intmax", "intmin", "ipermute", + "is_absolute_filename", "isargout", "isbool", + "iscell", "iscellstr", "ischar", "iscomplex", + "isempty", "isfield", "isfloat", "isglobal", + "ishandle", "isieee", "isindex", "isinteger", + "islogical", "ismatrix", "ismethod", "isnull", + "isnumeric", "isobject", "isreal", + "is_rooted_relative_filename", "issorted", + "isstruct", "isvarname", "kbhit", "keyboard", + "kill", "lasterr", "lasterror", "lastwarn", + "ldivide", "le", "length", "link", "linspace", + "logical", "lstat", "lt", "make_absolute_filename", + "makeinfo_program", "max_recursion_depth", "merge", + "methods", "mfilename", "minus", "mislocked", + "mkdir", "mkfifo", "mkstemp", "mldivide", "mlock", + "mouse_wheel_zoom", "mpower", "mrdivide", "mtimes", + "munlock", "nargin", "nargout", + "native_float_format", "ndims", "ne", "nfields", + "nnz", "norm", "not", "numel", "nzmax", + "octave_config_info", "octave_core_file_limit", + "octave_core_file_name", + "octave_core_file_options", "ones", "or", + "output_max_field_width", "output_precision", + "page_output_immediately", "page_screen_output", + "path", "pathsep", "pause", "pclose", "permute", + "pi", "pipe", "plus", "popen", "power", + "print_empty_dimensions", "printf", + "print_struct_array_contents", "prod", + "program_invocation_name", "program_name", + "putenv", "puts", "pwd", "quit", "rats", "rdivide", + "readdir", "readlink", "read_readline_init_file", + "realmax", "realmin", "rehash", "rename", + "repelems", "re_read_readline_init_file", "reset", + "reshape", "resize", "restoredefaultpath", + "rethrow", "rmdir", "rmfield", "rmpath", "rows", + "save_header_format_string", "save_precision", + "saving_history", "scanf", "set", "setenv", + "shell_cmd", "sighup_dumps_octave_core", + "sigterm_dumps_octave_core", "silent_functions", + "single", "size", "size_equal", "sizemax", + "sizeof", "sleep", "source", "sparse_auto_mutate", + "split_long_rows", "sprintf", "squeeze", "sscanf", + "stat", "stderr", "stdin", "stdout", "strcmp", + "strcmpi", "string_fill_char", "strncmp", + "strncmpi", "struct", "struct_levels_to_print", + "strvcat", "subsasgn", "subsref", "sum", "sumsq", + "superiorto", "suppress_verbose_help_message", + "symlink", "system", "tic", "tilde_expand", + "times", "tmpfile", "tmpnam", "toc", "toupper", + "transpose", "true", "typeinfo", "umask", "uminus", + "uname", "undo_string_escapes", "unlink", "uplus", + "upper", "usage", "usleep", "vec", "vectorize", + "vertcat", "waitpid", "warning", "warranty", + "whos_line_format", "yes_or_no", "zeros", + "inf", "Inf", "nan", "NaN") + + command_kw = ("close", "load", "who", "whos") + + function_kw = ( + "accumarray", "accumdim", "acosd", "acotd", + "acscd", "addtodate", "allchild", "ancestor", + "anova", "arch_fit", "arch_rnd", "arch_test", + "area", "arma_rnd", "arrayfun", "ascii", "asctime", + "asecd", "asind", "assert", "atand", + "autoreg_matrix", "autumn", "axes", "axis", "bar", + "barh", "bartlett", "bartlett_test", "beep", + "betacdf", "betainv", "betapdf", "betarnd", + "bicgstab", "bicubic", "binary", "binocdf", + "binoinv", "binopdf", "binornd", "bitcmp", + "bitget", "bitset", "blackman", "blanks", + "blkdiag", "bone", "box", "brighten", "calendar", + "cast", "cauchy_cdf", "cauchy_inv", "cauchy_pdf", + "cauchy_rnd", "caxis", "celldisp", "center", "cgs", + "chisquare_test_homogeneity", + "chisquare_test_independence", "circshift", "cla", + "clabel", "clf", "clock", "cloglog", "closereq", + "colon", "colorbar", "colormap", "colperm", + "comet", "common_size", "commutation_matrix", + "compan", "compare_versions", "compass", + "computer", "cond", "condest", "contour", + "contourc", "contourf", "contrast", "conv", + "convhull", "cool", "copper", "copyfile", "cor", + "corrcoef", "cor_test", "cosd", "cotd", "cov", + "cplxpair", "cross", "cscd", "cstrcat", "csvread", + "csvwrite", "ctime", "cumtrapz", "curl", "cut", + "cylinder", "date", "datenum", "datestr", + "datetick", "datevec", "dblquad", "deal", + "deblank", "deconv", "delaunay", "delaunayn", + "delete", "demo", "detrend", "diffpara", "diffuse", + "dir", "discrete_cdf", "discrete_inv", + "discrete_pdf", "discrete_rnd", "display", + "divergence", "dlmwrite", "dos", "dsearch", + "dsearchn", "duplication_matrix", "durbinlevinson", + "ellipsoid", "empirical_cdf", "empirical_inv", + "empirical_pdf", "empirical_rnd", "eomday", + "errorbar", "etime", "etreeplot", "example", + "expcdf", "expinv", "expm", "exppdf", "exprnd", + "ezcontour", "ezcontourf", "ezmesh", "ezmeshc", + "ezplot", "ezpolar", "ezsurf", "ezsurfc", "factor", + "factorial", "fail", "fcdf", "feather", "fftconv", + "fftfilt", "fftshift", "figure", "fileattrib", + "fileparts", "fill", "findall", "findobj", + "findstr", "finv", "flag", "flipdim", "fliplr", + "flipud", "fpdf", "fplot", "fractdiff", "freqz", + "freqz_plot", "frnd", "fsolve", + "f_test_regression", "ftp", "fullfile", "fzero", + "gamcdf", "gaminv", "gampdf", "gamrnd", "gca", + "gcbf", "gcbo", "gcf", "genvarname", "geocdf", + "geoinv", "geopdf", "geornd", "getfield", "ginput", + "glpk", "gls", "gplot", "gradient", + "graphics_toolkit", "gray", "grid", "griddata", + "griddatan", "gtext", "gunzip", "gzip", "hadamard", + "hamming", "hankel", "hanning", "hggroup", + "hidden", "hilb", "hist", "histc", "hold", "hot", + "hotelling_test", "housh", "hsv", "hurst", + "hygecdf", "hygeinv", "hygepdf", "hygernd", + "idivide", "ifftshift", "image", "imagesc", + "imfinfo", "imread", "imshow", "imwrite", "index", + "info", "inpolygon", "inputname", "interpft", + "interpn", "intersect", "invhilb", "iqr", "isa", + "isdefinite", "isdir", "is_duplicate_entry", + "isequal", "isequalwithequalnans", "isfigure", + "ishermitian", "ishghandle", "is_leap_year", + "isletter", "ismac", "ismember", "ispc", "isprime", + "isprop", "isscalar", "issquare", "isstrprop", + "issymmetric", "isunix", "is_valid_file_id", + "isvector", "jet", "kendall", + "kolmogorov_smirnov_cdf", + "kolmogorov_smirnov_test", "kruskal_wallis_test", + "krylov", "kurtosis", "laplace_cdf", "laplace_inv", + "laplace_pdf", "laplace_rnd", "legend", "legendre", + "license", "line", "linkprop", "list_primes", + "loadaudio", "loadobj", "logistic_cdf", + "logistic_inv", "logistic_pdf", "logistic_rnd", + "logit", "loglog", "loglogerr", "logm", "logncdf", + "logninv", "lognpdf", "lognrnd", "logspace", + "lookfor", "ls_command", "lsqnonneg", "magic", + "mahalanobis", "manova", "matlabroot", + "mcnemar_test", "mean", "meansq", "median", "menu", + "mesh", "meshc", "meshgrid", "meshz", "mexext", + "mget", "mkpp", "mode", "moment", "movefile", + "mpoles", "mput", "namelengthmax", "nargchk", + "nargoutchk", "nbincdf", "nbininv", "nbinpdf", + "nbinrnd", "nchoosek", "ndgrid", "newplot", "news", + "nonzeros", "normcdf", "normest", "norminv", + "normpdf", "normrnd", "now", "nthroot", "null", + "ocean", "ols", "onenormest", "optimget", + "optimset", "orderfields", "orient", "orth", + "pack", "pareto", "parseparams", "pascal", "patch", + "pathdef", "pcg", "pchip", "pcolor", "pcr", + "peaks", "periodogram", "perl", "perms", "pie", + "pink", "planerot", "playaudio", "plot", + "plotmatrix", "plotyy", "poisscdf", "poissinv", + "poisspdf", "poissrnd", "polar", "poly", + "polyaffine", "polyarea", "polyderiv", "polyfit", + "polygcd", "polyint", "polyout", "polyreduce", + "polyval", "polyvalm", "postpad", "powerset", + "ppder", "ppint", "ppjumps", "ppplot", "ppval", + "pqpnonneg", "prepad", "primes", "print", + "print_usage", "prism", "probit", "qp", "qqplot", + "quadcc", "quadgk", "quadl", "quadv", "quiver", + "qzhess", "rainbow", "randi", "range", "rank", + "ranks", "rat", "reallog", "realpow", "realsqrt", + "record", "rectangle_lw", "rectangle_sw", + "rectint", "refresh", "refreshdata", + "regexptranslate", "repmat", "residue", "ribbon", + "rindex", "roots", "rose", "rosser", "rotdim", + "rref", "run", "run_count", "rundemos", "run_test", + "runtests", "saveas", "saveaudio", "saveobj", + "savepath", "scatter", "secd", "semilogx", + "semilogxerr", "semilogy", "semilogyerr", + "setaudio", "setdiff", "setfield", "setxor", + "shading", "shift", "shiftdim", "sign_test", + "sinc", "sind", "sinetone", "sinewave", "skewness", + "slice", "sombrero", "sortrows", "spaugment", + "spconvert", "spdiags", "spearman", "spectral_adf", + "spectral_xdf", "specular", "speed", "spencer", + "speye", "spfun", "sphere", "spinmap", "spline", + "spones", "sprand", "sprandn", "sprandsym", + "spring", "spstats", "spy", "sqp", "stairs", + "statistics", "std", "stdnormal_cdf", + "stdnormal_inv", "stdnormal_pdf", "stdnormal_rnd", + "stem", "stft", "strcat", "strchr", "strjust", + "strmatch", "strread", "strsplit", "strtok", + "strtrim", "strtrunc", "structfun", "studentize", + "subplot", "subsindex", "subspace", "substr", + "substruct", "summer", "surf", "surface", "surfc", + "surfl", "surfnorm", "svds", "swapbytes", + "sylvester_matrix", "symvar", "synthesis", "table", + "tand", "tar", "tcdf", "tempdir", "tempname", + "test", "text", "textread", "textscan", "tinv", + "title", "toeplitz", "tpdf", "trace", "trapz", + "treelayout", "treeplot", "triangle_lw", + "triangle_sw", "tril", "trimesh", "triplequad", + "triplot", "trisurf", "triu", "trnd", "tsearchn", + "t_test", "t_test_regression", "type", "unidcdf", + "unidinv", "unidpdf", "unidrnd", "unifcdf", + "unifinv", "unifpdf", "unifrnd", "union", "unique", + "unix", "unmkpp", "unpack", "untabify", "untar", + "unwrap", "unzip", "u_test", "validatestring", + "vander", "var", "var_test", "vech", "ver", + "version", "view", "voronoi", "voronoin", + "waitforbuttonpress", "wavread", "wavwrite", + "wblcdf", "wblinv", "wblpdf", "wblrnd", "weekday", + "welch_test", "what", "white", "whitebg", + "wienrnd", "wilcoxon_test", "wilkinson", "winter", + "xlabel", "xlim", "ylabel", "yulewalker", "zip", + "zlabel", "z_test") + + loadable_kw = ( + "airy", "amd", "balance", "besselh", "besseli", + "besselj", "besselk", "bessely", "bitpack", + "bsxfun", "builtin", "ccolamd", "cellfun", + "cellslices", "chol", "choldelete", "cholinsert", + "cholinv", "cholshift", "cholupdate", "colamd", + "colloc", "convhulln", "convn", "csymamd", + "cummax", "cummin", "daspk", "daspk_options", + "dasrt", "dasrt_options", "dassl", "dassl_options", + "dbclear", "dbdown", "dbstack", "dbstatus", + "dbstop", "dbtype", "dbup", "dbwhere", "det", + "dlmread", "dmperm", "dot", "eig", "eigs", + "endgrent", "endpwent", "etree", "fft", "fftn", + "fftw", "filter", "find", "full", "gcd", + "getgrent", "getgrgid", "getgrnam", "getpwent", + "getpwnam", "getpwuid", "getrusage", "givens", + "gmtime", "gnuplot_binary", "hess", "ifft", + "ifftn", "inv", "isdebugmode", "issparse", "kron", + "localtime", "lookup", "lsode", "lsode_options", + "lu", "luinc", "luupdate", "matrix_type", "max", + "min", "mktime", "pinv", "qr", "qrdelete", + "qrinsert", "qrshift", "qrupdate", "quad", + "quad_options", "qz", "rand", "rande", "randg", + "randn", "randp", "randperm", "rcond", "regexp", + "regexpi", "regexprep", "schur", "setgrent", + "setpwent", "sort", "spalloc", "sparse", "spparms", + "sprank", "sqrtm", "strfind", "strftime", + "strptime", "strrep", "svd", "svd_driver", "syl", + "symamd", "symbfact", "symrcm", "time", "tsearch", + "typecast", "urlread", "urlwrite") + + mapping_kw = ( + "abs", "acos", "acosh", "acot", "acoth", "acsc", + "acsch", "angle", "arg", "asec", "asech", "asin", + "asinh", "atan", "atanh", "beta", "betainc", + "betaln", "bincoeff", "cbrt", "ceil", "conj", "cos", + "cosh", "cot", "coth", "csc", "csch", "erf", "erfc", + "erfcx", "erfinv", "exp", "finite", "fix", "floor", + "fmod", "gamma", "gammainc", "gammaln", "imag", + "isalnum", "isalpha", "isascii", "iscntrl", + "isdigit", "isfinite", "isgraph", "isinf", + "islower", "isna", "isnan", "isprint", "ispunct", + "isspace", "isupper", "isxdigit", "lcm", "lgamma", + "log", "lower", "mod", "real", "rem", "round", + "roundb", "sec", "sech", "sign", "sin", "sinh", + "sqrt", "tan", "tanh", "toascii", "tolower", "xor") + + builtin_consts = ( + "EDITOR", "EXEC_PATH", "I", "IMAGE_PATH", "NA", + "OCTAVE_HOME", "OCTAVE_VERSION", "PAGER", + "PAGER_FLAGS", "SEEK_CUR", "SEEK_END", "SEEK_SET", + "SIG", "S_ISBLK", "S_ISCHR", "S_ISDIR", "S_ISFIFO", + "S_ISLNK", "S_ISREG", "S_ISSOCK", "WCONTINUE", + "WCOREDUMP", "WEXITSTATUS", "WIFCONTINUED", + "WIFEXITED", "WIFSIGNALED", "WIFSTOPPED", "WNOHANG", + "WSTOPSIG", "WTERMSIG", "WUNTRACED") + + tokens = { + 'root': [ + (r'%\{\s*\n', Comment.Multiline, 'percentblockcomment'), + (r'#\{\s*\n', Comment.Multiline, 'hashblockcomment'), + (r'[%#].*$', Comment), + (r'^\s*function\b', Keyword, 'deffunc'), + + # from 'iskeyword' on hg changeset 8cc154f45e37 + (words(( + '__FILE__', '__LINE__', 'break', 'case', 'catch', 'classdef', + 'continue', 'do', 'else', 'elseif', 'end', 'end_try_catch', + 'end_unwind_protect', 'endclassdef', 'endevents', 'endfor', + 'endfunction', 'endif', 'endmethods', 'endproperties', 'endswitch', + 'endwhile', 'events', 'for', 'function', 'get', 'global', 'if', + 'methods', 'otherwise', 'persistent', 'properties', 'return', + 'set', 'static', 'switch', 'try', 'until', 'unwind_protect', + 'unwind_protect_cleanup', 'while'), suffix=r'\b'), + Keyword), + + (words(builtin_kw + command_kw + function_kw + loadable_kw + mapping_kw, + suffix=r'\b'), Name.Builtin), + + (words(builtin_consts, suffix=r'\b'), Name.Constant), + + # operators in Octave but not Matlab: + (r'-=|!=|!|/=|--', Operator), + # operators: + (r'-|==|~=|<|>|<=|>=|&&|&|~|\|\|?', Operator), + # operators in Octave but not Matlab requiring escape for re: + (r'\*=|\+=|\^=|\/=|\\=|\*\*|\+\+|\.\*\*', Operator), + # operators requiring escape for re: + (r'\.\*|\*|\+|\.\^|\.\\|\.\/|\/|\\', Operator), + + + # punctuation: + (r'[\[\](){}:@.,]', Punctuation), + (r'=|:|;', Punctuation), + + (r'"[^"]*"', String), + + (r'(\d+\.\d*|\d*\.\d+)([eEf][+-]?[0-9]+)?', Number.Float), + (r'\d+[eEf][+-]?[0-9]+', Number.Float), + (r'\d+', Number.Integer), + + # quote can be transpose, instead of string: + # (not great, but handles common cases...) + (r'(?<=[\w)\].])\'+', Operator), + (r'(?|<=|>=|&&|&|~|\|\|?', Operator), + # operators requiring escape for re: + (r'\.\*|\*|\+|\.\^|\.\\|\.\/|\/|\\', Operator), + + # punctuation: + (r'[\[\](){}@.,=:;]+', Punctuation), + + (r'"[^"]*"', String), + + # quote can be transpose, instead of string: + # (not great, but handles common cases...) + (r'(?<=[\w)\].])\'+', Operator), + (r'(?`_ lexer. + Derived from pygments.lexers.MuPADLexer. + + .. versionadded:: 2.11 + """ + name = 'Maxima' + aliases = ['maxima', 'macsyma'] + filenames = ['*.mac', '*.max'] + + keywords = ('if', 'then', 'else', 'elseif', + 'do', 'while', 'repeat', 'until', + 'for', 'from', 'to', 'downto', 'step', 'thru') + + constants = ('%pi', '%e', '%phi', '%gamma', '%i', + 'und', 'ind', 'infinity', 'inf', 'minf', + 'true', 'false', 'unknown', 'done') + + operators = (r'.', r':', r'=', r'#', + r'+', r'-', r'*', r'/', r'^', + r'@', r'>', r'<', r'|', r'!', r"'") + + operator_words = ('and', 'or', 'not') + + tokens = { + 'root': [ + (r'/\*', Comment.Multiline, 'comment'), + (r'"(?:[^"\\]|\\.)*"', String), + (r'\(|\)|\[|\]|\{|\}', Punctuation), + (r'[,;$]', Punctuation), + (words (constants), Name.Constant), + (words (keywords), Keyword), + (words (operators), Operator), + (words (operator_words), Operator.Word), + (r'''(?x) + ((?:[a-zA-Z_#][\w#]*|`[^`]*`) + (?:::[a-zA-Z_#][\w#]*|`[^`]*`)*)(\s*)([(])''', + bygroups(Name.Function, Text.Whitespace, Punctuation)), + (r'''(?x) + (?:[a-zA-Z_#%][\w#%]*|`[^`]*`) + (?:::[a-zA-Z_#%][\w#%]*|`[^`]*`)*''', Name.Variable), + (r'[-+]?(\d*\.\d+([bdefls][-+]?\d+)?|\d+(\.\d*)?[bdefls][-+]?\d+)', Number.Float), + (r'[-+]?\d+', Number.Integer), + (r'\s+', Text.Whitespace), + (r'.', Text) + ], + 'comment': [ + (r'[^*/]+', Comment.Multiline), + (r'/\*', Comment.Multiline, '#push'), + (r'\*/', Comment.Multiline, '#pop'), + (r'[*/]', Comment.Multiline) + ] + } + + def analyse_text (text): + strength = 0.0 + # Input expression terminator. + if re.search (r'\$\s*$', text, re.MULTILINE): + strength += 0.05 + # Function definition operator. + if ':=' in text: + strength += 0.02 + return strength diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/meson.py b/.venv/lib/python3.8/site-packages/pygments/lexers/meson.py new file mode 100644 index 0000000..47db014 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/meson.py @@ -0,0 +1,155 @@ +""" + pygments.lexers.meson + ~~~~~~~~~~~~~~~~~~~~~ + + Pygments lexer for the Meson build system + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import ( + RegexLexer, + words, + include, +) +from pygments.token import ( + Comment, + Name, + Number, + Punctuation, + Operator, + Keyword, + String, + Whitespace, +) + +__all__ = ['MesonLexer'] + + +class MesonLexer(RegexLexer): + """ + `meson `_ language lexer. + The grammar definition use to transcribe the syntax was retrieved from + https://mesonbuild.com/Syntax.html#grammar for version 0.58 + Some of those definitions are improperly transcribed so the Meson++ + implementation was also checked: https://github.com/dcbaker/meson-plus-plus + + .. versionadded:: 2.10 + """ + + # TODO String interpolation @VARNAME@ inner matches + # TODO keyword_arg: value inner matches + + name = 'Meson' + aliases = ['meson', 'meson.build'] + filenames = ['meson.build', 'meson_options.txt'] + mimetypes = ['text/x-meson'] + + flags = re.MULTILINE | re.UNICODE + + tokens = { + 'root': [ + (r'#.*?$', Comment), + (r"'''.*'''", String.Single), + (r'[1-9][0-9]*', Number.Integer), + (r'0o[0-7]+', Number.Oct), + (r'0x[a-fA-F0-9]+', Number.Hex), + include('string'), + include('keywords'), + include('expr'), + (r'[a-zA-Z_][a-zA-Z_0-9]*', Name), + (r'\s+', Whitespace), + ], + 'string': [ + (r"[']{3}([']{0,2}([^\\']|\\(.|\n)))*[']{3}", String), + (r"'.*?(?', '->', '#', + # Modules + ':>', + } + + nonid_reserved = {'(', ')', '[', ']', '{', '}', ',', ';', '...', '_'} + + alphanumid_re = r"[a-zA-Z][\w']*" + symbolicid_re = r"[!%&$#+\-/:<=>?@\\~`^|*]+" + + # A character constant is a sequence of the form #s, where s is a string + # constant denoting a string of size one character. This setup just parses + # the entire string as either a String.Double or a String.Char (depending + # on the argument), even if the String.Char is an erronous + # multiple-character string. + def stringy(whatkind): + return [ + (r'[^"\\]', whatkind), + (r'\\[\\"abtnvfr]', String.Escape), + # Control-character notation is used for codes < 32, + # where \^@ == \000 + (r'\\\^[\x40-\x5e]', String.Escape), + # Docs say 'decimal digits' + (r'\\[0-9]{3}', String.Escape), + (r'\\u[0-9a-fA-F]{4}', String.Escape), + (r'\\\s+\\', String.Interpol), + (r'"', whatkind, '#pop'), + ] + + # Callbacks for distinguishing tokens and reserved words + def long_id_callback(self, match): + if match.group(1) in self.alphanumid_reserved: + token = Error + else: + token = Name.Namespace + yield match.start(1), token, match.group(1) + yield match.start(2), Punctuation, match.group(2) + + def end_id_callback(self, match): + if match.group(1) in self.alphanumid_reserved: + token = Error + elif match.group(1) in self.symbolicid_reserved: + token = Error + else: + token = Name + yield match.start(1), token, match.group(1) + + def id_callback(self, match): + str = match.group(1) + if str in self.alphanumid_reserved: + token = Keyword.Reserved + elif str in self.symbolicid_reserved: + token = Punctuation + else: + token = Name + yield match.start(1), token, str + + tokens = { + # Whitespace and comments are (almost) everywhere + 'whitespace': [ + (r'\s+', Text), + (r'\(\*', Comment.Multiline, 'comment'), + ], + + 'delimiters': [ + # This lexer treats these delimiters specially: + # Delimiters define scopes, and the scope is how the meaning of + # the `|' is resolved - is it a case/handle expression, or function + # definition by cases? (This is not how the Definition works, but + # it's how MLton behaves, see http://mlton.org/SMLNJDeviations) + (r'\(|\[|\{', Punctuation, 'main'), + (r'\)|\]|\}', Punctuation, '#pop'), + (r'\b(let|if|local)\b(?!\')', Keyword.Reserved, ('main', 'main')), + (r'\b(struct|sig|while)\b(?!\')', Keyword.Reserved, 'main'), + (r'\b(do|else|end|in|then)\b(?!\')', Keyword.Reserved, '#pop'), + ], + + 'core': [ + # Punctuation that doesn't overlap symbolic identifiers + (r'(%s)' % '|'.join(re.escape(z) for z in nonid_reserved), + Punctuation), + + # Special constants: strings, floats, numbers in decimal and hex + (r'#"', String.Char, 'char'), + (r'"', String.Double, 'string'), + (r'~?0x[0-9a-fA-F]+', Number.Hex), + (r'0wx[0-9a-fA-F]+', Number.Hex), + (r'0w\d+', Number.Integer), + (r'~?\d+\.\d+[eE]~?\d+', Number.Float), + (r'~?\d+\.\d+', Number.Float), + (r'~?\d+[eE]~?\d+', Number.Float), + (r'~?\d+', Number.Integer), + + # Labels + (r'#\s*[1-9][0-9]*', Name.Label), + (r'#\s*(%s)' % alphanumid_re, Name.Label), + (r'#\s+(%s)' % symbolicid_re, Name.Label), + # Some reserved words trigger a special, local lexer state change + (r'\b(datatype|abstype)\b(?!\')', Keyword.Reserved, 'dname'), + (r'\b(exception)\b(?!\')', Keyword.Reserved, 'ename'), + (r'\b(functor|include|open|signature|structure)\b(?!\')', + Keyword.Reserved, 'sname'), + (r'\b(type|eqtype)\b(?!\')', Keyword.Reserved, 'tname'), + + # Regular identifiers, long and otherwise + (r'\'[\w\']*', Name.Decorator), + (r'(%s)(\.)' % alphanumid_re, long_id_callback, "dotted"), + (r'(%s)' % alphanumid_re, id_callback), + (r'(%s)' % symbolicid_re, id_callback), + ], + 'dotted': [ + (r'(%s)(\.)' % alphanumid_re, long_id_callback), + (r'(%s)' % alphanumid_re, end_id_callback, "#pop"), + (r'(%s)' % symbolicid_re, end_id_callback, "#pop"), + (r'\s+', Error), + (r'\S+', Error), + ], + + + # Main parser (prevents errors in files that have scoping errors) + 'root': [ + default('main') + ], + + # In this scope, I expect '|' to not be followed by a function name, + # and I expect 'and' to be followed by a binding site + 'main': [ + include('whitespace'), + + # Special behavior of val/and/fun + (r'\b(val|and)\b(?!\')', Keyword.Reserved, 'vname'), + (r'\b(fun)\b(?!\')', Keyword.Reserved, + ('#pop', 'main-fun', 'fname')), + + include('delimiters'), + include('core'), + (r'\S+', Error), + ], + + # In this scope, I expect '|' and 'and' to be followed by a function + 'main-fun': [ + include('whitespace'), + + (r'\s', Text), + (r'\(\*', Comment.Multiline, 'comment'), + + # Special behavior of val/and/fun + (r'\b(fun|and)\b(?!\')', Keyword.Reserved, 'fname'), + (r'\b(val)\b(?!\')', Keyword.Reserved, + ('#pop', 'main', 'vname')), + + # Special behavior of '|' and '|'-manipulating keywords + (r'\|', Punctuation, 'fname'), + (r'\b(case|handle)\b(?!\')', Keyword.Reserved, + ('#pop', 'main')), + + include('delimiters'), + include('core'), + (r'\S+', Error), + ], + + # Character and string parsers + 'char': stringy(String.Char), + 'string': stringy(String.Double), + + 'breakout': [ + (r'(?=\b(%s)\b(?!\'))' % '|'.join(alphanumid_reserved), Text, '#pop'), + ], + + # Dealing with what comes after module system keywords + 'sname': [ + include('whitespace'), + include('breakout'), + + (r'(%s)' % alphanumid_re, Name.Namespace), + default('#pop'), + ], + + # Dealing with what comes after the 'fun' (or 'and' or '|') keyword + 'fname': [ + include('whitespace'), + (r'\'[\w\']*', Name.Decorator), + (r'\(', Punctuation, 'tyvarseq'), + + (r'(%s)' % alphanumid_re, Name.Function, '#pop'), + (r'(%s)' % symbolicid_re, Name.Function, '#pop'), + + # Ignore interesting function declarations like "fun (x + y) = ..." + default('#pop'), + ], + + # Dealing with what comes after the 'val' (or 'and') keyword + 'vname': [ + include('whitespace'), + (r'\'[\w\']*', Name.Decorator), + (r'\(', Punctuation, 'tyvarseq'), + + (r'(%s)(\s*)(=(?!%s))' % (alphanumid_re, symbolicid_re), + bygroups(Name.Variable, Text, Punctuation), '#pop'), + (r'(%s)(\s*)(=(?!%s))' % (symbolicid_re, symbolicid_re), + bygroups(Name.Variable, Text, Punctuation), '#pop'), + (r'(%s)' % alphanumid_re, Name.Variable, '#pop'), + (r'(%s)' % symbolicid_re, Name.Variable, '#pop'), + + # Ignore interesting patterns like 'val (x, y)' + default('#pop'), + ], + + # Dealing with what comes after the 'type' (or 'and') keyword + 'tname': [ + include('whitespace'), + include('breakout'), + + (r'\'[\w\']*', Name.Decorator), + (r'\(', Punctuation, 'tyvarseq'), + (r'=(?!%s)' % symbolicid_re, Punctuation, ('#pop', 'typbind')), + + (r'(%s)' % alphanumid_re, Keyword.Type), + (r'(%s)' % symbolicid_re, Keyword.Type), + (r'\S+', Error, '#pop'), + ], + + # A type binding includes most identifiers + 'typbind': [ + include('whitespace'), + + (r'\b(and)\b(?!\')', Keyword.Reserved, ('#pop', 'tname')), + + include('breakout'), + include('core'), + (r'\S+', Error, '#pop'), + ], + + # Dealing with what comes after the 'datatype' (or 'and') keyword + 'dname': [ + include('whitespace'), + include('breakout'), + + (r'\'[\w\']*', Name.Decorator), + (r'\(', Punctuation, 'tyvarseq'), + (r'(=)(\s*)(datatype)', + bygroups(Punctuation, Text, Keyword.Reserved), '#pop'), + (r'=(?!%s)' % symbolicid_re, Punctuation, + ('#pop', 'datbind', 'datcon')), + + (r'(%s)' % alphanumid_re, Keyword.Type), + (r'(%s)' % symbolicid_re, Keyword.Type), + (r'\S+', Error, '#pop'), + ], + + # common case - A | B | C of int + 'datbind': [ + include('whitespace'), + + (r'\b(and)\b(?!\')', Keyword.Reserved, ('#pop', 'dname')), + (r'\b(withtype)\b(?!\')', Keyword.Reserved, ('#pop', 'tname')), + (r'\b(of)\b(?!\')', Keyword.Reserved), + + (r'(\|)(\s*)(%s)' % alphanumid_re, + bygroups(Punctuation, Text, Name.Class)), + (r'(\|)(\s+)(%s)' % symbolicid_re, + bygroups(Punctuation, Text, Name.Class)), + + include('breakout'), + include('core'), + (r'\S+', Error), + ], + + # Dealing with what comes after an exception + 'ename': [ + include('whitespace'), + + (r'(and\b)(\s+)(%s)' % alphanumid_re, + bygroups(Keyword.Reserved, Text, Name.Class)), + (r'(and\b)(\s*)(%s)' % symbolicid_re, + bygroups(Keyword.Reserved, Text, Name.Class)), + (r'\b(of)\b(?!\')', Keyword.Reserved), + (r'(%s)|(%s)' % (alphanumid_re, symbolicid_re), Name.Class), + + default('#pop'), + ], + + 'datcon': [ + include('whitespace'), + (r'(%s)' % alphanumid_re, Name.Class, '#pop'), + (r'(%s)' % symbolicid_re, Name.Class, '#pop'), + (r'\S+', Error, '#pop'), + ], + + # Series of type variables + 'tyvarseq': [ + (r'\s', Text), + (r'\(\*', Comment.Multiline, 'comment'), + + (r'\'[\w\']*', Name.Decorator), + (alphanumid_re, Name), + (r',', Punctuation), + (r'\)', Punctuation, '#pop'), + (symbolicid_re, Name), + ], + + 'comment': [ + (r'[^(*)]', Comment.Multiline), + (r'\(\*', Comment.Multiline, '#push'), + (r'\*\)', Comment.Multiline, '#pop'), + (r'[(*)]', Comment.Multiline), + ], + } + + +class OcamlLexer(RegexLexer): + """ + For the OCaml language. + + .. versionadded:: 0.7 + """ + + name = 'OCaml' + aliases = ['ocaml'] + filenames = ['*.ml', '*.mli', '*.mll', '*.mly'] + mimetypes = ['text/x-ocaml'] + + keywords = ( + 'as', 'assert', 'begin', 'class', 'constraint', 'do', 'done', + 'downto', 'else', 'end', 'exception', 'external', 'false', + 'for', 'fun', 'function', 'functor', 'if', 'in', 'include', + 'inherit', 'initializer', 'lazy', 'let', 'match', 'method', + 'module', 'mutable', 'new', 'object', 'of', 'open', 'private', + 'raise', 'rec', 'sig', 'struct', 'then', 'to', 'true', 'try', + 'type', 'value', 'val', 'virtual', 'when', 'while', 'with', + ) + keyopts = ( + '!=', '#', '&', '&&', r'\(', r'\)', r'\*', r'\+', ',', '-', + r'-\.', '->', r'\.', r'\.\.', ':', '::', ':=', ':>', ';', ';;', '<', + '<-', '=', '>', '>]', r'>\}', r'\?', r'\?\?', r'\[', r'\[<', r'\[>', + r'\[\|', ']', '_', '`', r'\{', r'\{<', r'\|', r'\|]', r'\}', '~' + ) + + operators = r'[!$%&*+\./:<=>?@^|~-]' + word_operators = ('and', 'asr', 'land', 'lor', 'lsl', 'lxor', 'mod', 'or') + prefix_syms = r'[!?~]' + infix_syms = r'[=<>@^|&+\*/$%-]' + primitives = ('unit', 'int', 'float', 'bool', 'string', 'char', 'list', 'array') + + tokens = { + 'escape-sequence': [ + (r'\\[\\"\'ntbr]', String.Escape), + (r'\\[0-9]{3}', String.Escape), + (r'\\x[0-9a-fA-F]{2}', String.Escape), + ], + 'root': [ + (r'\s+', Text), + (r'false|true|\(\)|\[\]', Name.Builtin.Pseudo), + (r'\b([A-Z][\w\']*)(?=\s*\.)', Name.Namespace, 'dotted'), + (r'\b([A-Z][\w\']*)', Name.Class), + (r'\(\*(?![)])', Comment, 'comment'), + (r'\b(%s)\b' % '|'.join(keywords), Keyword), + (r'(%s)' % '|'.join(keyopts[::-1]), Operator), + (r'(%s|%s)?%s' % (infix_syms, prefix_syms, operators), Operator), + (r'\b(%s)\b' % '|'.join(word_operators), Operator.Word), + (r'\b(%s)\b' % '|'.join(primitives), Keyword.Type), + + (r"[^\W\d][\w']*", Name), + + (r'-?\d[\d_]*(.[\d_]*)?([eE][+\-]?\d[\d_]*)', Number.Float), + (r'0[xX][\da-fA-F][\da-fA-F_]*', Number.Hex), + (r'0[oO][0-7][0-7_]*', Number.Oct), + (r'0[bB][01][01_]*', Number.Bin), + (r'\d[\d_]*', Number.Integer), + + (r"'(?:(\\[\\\"'ntbr ])|(\\[0-9]{3})|(\\x[0-9a-fA-F]{2}))'", + String.Char), + (r"'.'", String.Char), + (r"'", Keyword), # a stray quote is another syntax element + + (r'"', String.Double, 'string'), + + (r'[~?][a-z][\w\']*:', Name.Variable), + ], + 'comment': [ + (r'[^(*)]+', Comment), + (r'\(\*', Comment, '#push'), + (r'\*\)', Comment, '#pop'), + (r'[(*)]', Comment), + ], + 'string': [ + (r'[^\\"]+', String.Double), + include('escape-sequence'), + (r'\\\n', String.Double), + (r'"', String.Double, '#pop'), + ], + 'dotted': [ + (r'\s+', Text), + (r'\.', Punctuation), + (r'[A-Z][\w\']*(?=\s*\.)', Name.Namespace), + (r'[A-Z][\w\']*', Name.Class, '#pop'), + (r'[a-z_][\w\']*', Name, '#pop'), + default('#pop'), + ], + } + + +class OpaLexer(RegexLexer): + """ + Lexer for the Opa language (http://opalang.org). + + .. versionadded:: 1.5 + """ + + name = 'Opa' + aliases = ['opa'] + filenames = ['*.opa'] + mimetypes = ['text/x-opa'] + + # most of these aren't strictly keywords + # but if you color only real keywords, you might just + # as well not color anything + keywords = ( + 'and', 'as', 'begin', 'case', 'client', 'css', 'database', 'db', 'do', + 'else', 'end', 'external', 'forall', 'function', 'if', 'import', + 'match', 'module', 'or', 'package', 'parser', 'rec', 'server', 'then', + 'type', 'val', 'with', 'xml_parser', + ) + + # matches both stuff and `stuff` + ident_re = r'(([a-zA-Z_]\w*)|(`[^`]*`))' + + op_re = r'[.=\-<>,@~%/+?*&^!]' + punc_re = r'[()\[\],;|]' # '{' and '}' are treated elsewhere + # because they are also used for inserts + + tokens = { + # copied from the caml lexer, should be adapted + 'escape-sequence': [ + (r'\\[\\"\'ntr}]', String.Escape), + (r'\\[0-9]{3}', String.Escape), + (r'\\x[0-9a-fA-F]{2}', String.Escape), + ], + + # factorizing these rules, because they are inserted many times + 'comments': [ + (r'/\*', Comment, 'nested-comment'), + (r'//.*?$', Comment), + ], + 'comments-and-spaces': [ + include('comments'), + (r'\s+', Text), + ], + + 'root': [ + include('comments-and-spaces'), + # keywords + (words(keywords, prefix=r'\b', suffix=r'\b'), Keyword), + # directives + # we could parse the actual set of directives instead of anything + # starting with @, but this is troublesome + # because it needs to be adjusted all the time + # and assuming we parse only sources that compile, it is useless + (r'@' + ident_re + r'\b', Name.Builtin.Pseudo), + + # number literals + (r'-?.[\d]+([eE][+\-]?\d+)', Number.Float), + (r'-?\d+.\d*([eE][+\-]?\d+)', Number.Float), + (r'-?\d+[eE][+\-]?\d+', Number.Float), + (r'0[xX][\da-fA-F]+', Number.Hex), + (r'0[oO][0-7]+', Number.Oct), + (r'0[bB][01]+', Number.Bin), + (r'\d+', Number.Integer), + # color literals + (r'#[\da-fA-F]{3,6}', Number.Integer), + + # string literals + (r'"', String.Double, 'string'), + # char literal, should be checked because this is the regexp from + # the caml lexer + (r"'(?:(\\[\\\"'ntbr ])|(\\[0-9]{3})|(\\x[0-9a-fA-F]{2})|.)'", + String.Char), + + # this is meant to deal with embedded exprs in strings + # every time we find a '}' we pop a state so that if we were + # inside a string, we are back in the string state + # as a consequence, we must also push a state every time we find a + # '{' or else we will have errors when parsing {} for instance + (r'\{', Operator, '#push'), + (r'\}', Operator, '#pop'), + + # html literals + # this is a much more strict that the actual parser, + # since a])', String.Single, 'html-open-tag'), + + # db path + # matching the '[_]' in '/a[_]' because it is a part + # of the syntax of the db path definition + # unfortunately, i don't know how to match the ']' in + # /a[1], so this is somewhat inconsistent + (r'[@?!]?(/\w+)+(\[_\])?', Name.Variable), + # putting the same color on <- as on db path, since + # it can be used only to mean Db.write + (r'<-(?!'+op_re+r')', Name.Variable), + + # 'modules' + # although modules are not distinguished by their names as in caml + # the standard library seems to follow the convention that modules + # only area capitalized + (r'\b([A-Z]\w*)(?=\.)', Name.Namespace), + + # operators + # = has a special role because this is the only + # way to syntactic distinguish binding constructions + # unfortunately, this colors the equal in {x=2} too + (r'=(?!'+op_re+r')', Keyword), + (r'(%s)+' % op_re, Operator), + (r'(%s)+' % punc_re, Operator), + + # coercions + (r':', Operator, 'type'), + # type variables + # we need this rule because we don't parse specially type + # definitions so in "type t('a) = ...", "'a" is parsed by 'root' + ("'"+ident_re, Keyword.Type), + + # id literal, #something, or #{expr} + (r'#'+ident_re, String.Single), + (r'#(?=\{)', String.Single), + + # identifiers + # this avoids to color '2' in 'a2' as an integer + (ident_re, Text), + + # default, not sure if that is needed or not + # (r'.', Text), + ], + + # it is quite painful to have to parse types to know where they end + # this is the general rule for a type + # a type is either: + # * -> ty + # * type-with-slash + # * type-with-slash -> ty + # * type-with-slash (, type-with-slash)+ -> ty + # + # the code is pretty funky in here, but this code would roughly + # translate in caml to: + # let rec type stream = + # match stream with + # | [< "->"; stream >] -> type stream + # | [< ""; stream >] -> + # type_with_slash stream + # type_lhs_1 stream; + # and type_1 stream = ... + 'type': [ + include('comments-and-spaces'), + (r'->', Keyword.Type), + default(('#pop', 'type-lhs-1', 'type-with-slash')), + ], + + # parses all the atomic or closed constructions in the syntax of type + # expressions: record types, tuple types, type constructors, basic type + # and type variables + 'type-1': [ + include('comments-and-spaces'), + (r'\(', Keyword.Type, ('#pop', 'type-tuple')), + (r'~?\{', Keyword.Type, ('#pop', 'type-record')), + (ident_re+r'\(', Keyword.Type, ('#pop', 'type-tuple')), + (ident_re, Keyword.Type, '#pop'), + ("'"+ident_re, Keyword.Type), + # this case is not in the syntax but sometimes + # we think we are parsing types when in fact we are parsing + # some css, so we just pop the states until we get back into + # the root state + default('#pop'), + ], + + # type-with-slash is either: + # * type-1 + # * type-1 (/ type-1)+ + 'type-with-slash': [ + include('comments-and-spaces'), + default(('#pop', 'slash-type-1', 'type-1')), + ], + 'slash-type-1': [ + include('comments-and-spaces'), + ('/', Keyword.Type, ('#pop', 'type-1')), + # same remark as above + default('#pop'), + ], + + # we go in this state after having parsed a type-with-slash + # while trying to parse a type + # and at this point we must determine if we are parsing an arrow + # type (in which case we must continue parsing) or not (in which + # case we stop) + 'type-lhs-1': [ + include('comments-and-spaces'), + (r'->', Keyword.Type, ('#pop', 'type')), + (r'(?=,)', Keyword.Type, ('#pop', 'type-arrow')), + default('#pop'), + ], + 'type-arrow': [ + include('comments-and-spaces'), + # the look ahead here allows to parse f(x : int, y : float -> truc) + # correctly + (r',(?=[^:]*?->)', Keyword.Type, 'type-with-slash'), + (r'->', Keyword.Type, ('#pop', 'type')), + # same remark as above + default('#pop'), + ], + + # no need to do precise parsing for tuples and records + # because they are closed constructions, so we can simply + # find the closing delimiter + # note that this function would be not work if the source + # contained identifiers like `{)` (although it could be patched + # to support it) + 'type-tuple': [ + include('comments-and-spaces'), + (r'[^()/*]+', Keyword.Type), + (r'[/*]', Keyword.Type), + (r'\(', Keyword.Type, '#push'), + (r'\)', Keyword.Type, '#pop'), + ], + 'type-record': [ + include('comments-and-spaces'), + (r'[^{}/*]+', Keyword.Type), + (r'[/*]', Keyword.Type), + (r'\{', Keyword.Type, '#push'), + (r'\}', Keyword.Type, '#pop'), + ], + + # 'type-tuple': [ + # include('comments-and-spaces'), + # (r'\)', Keyword.Type, '#pop'), + # default(('#pop', 'type-tuple-1', 'type-1')), + # ], + # 'type-tuple-1': [ + # include('comments-and-spaces'), + # (r',?\s*\)', Keyword.Type, '#pop'), # ,) is a valid end of tuple, in (1,) + # (r',', Keyword.Type, 'type-1'), + # ], + # 'type-record':[ + # include('comments-and-spaces'), + # (r'\}', Keyword.Type, '#pop'), + # (r'~?(?:\w+|`[^`]*`)', Keyword.Type, 'type-record-field-expr'), + # ], + # 'type-record-field-expr': [ + # + # ], + + 'nested-comment': [ + (r'[^/*]+', Comment), + (r'/\*', Comment, '#push'), + (r'\*/', Comment, '#pop'), + (r'[/*]', Comment), + ], + + # the copy pasting between string and single-string + # is kinda sad. Is there a way to avoid that?? + 'string': [ + (r'[^\\"{]+', String.Double), + (r'"', String.Double, '#pop'), + (r'\{', Operator, 'root'), + include('escape-sequence'), + ], + 'single-string': [ + (r'[^\\\'{]+', String.Double), + (r'\'', String.Double, '#pop'), + (r'\{', Operator, 'root'), + include('escape-sequence'), + ], + + # all the html stuff + # can't really reuse some existing html parser + # because we must be able to parse embedded expressions + + # we are in this state after someone parsed the '<' that + # started the html literal + 'html-open-tag': [ + (r'[\w\-:]+', String.Single, ('#pop', 'html-attr')), + (r'>', String.Single, ('#pop', 'html-content')), + ], + + # we are in this state after someone parsed the ' is allowed + (r'[\w\-:]*>', String.Single, '#pop'), + ], + + # we are in this state after having parsed '', String.Single, '#pop'), + (r'>', String.Single, ('#pop', 'html-content')), + ], + + 'html-attr-value': [ + (r"'", String.Single, ('#pop', 'single-string')), + (r'"', String.Single, ('#pop', 'string')), + (r'#'+ident_re, String.Single, '#pop'), + (r'#(?=\{)', String.Single, ('#pop', 'root')), + (r'[^"\'{`=<>]+', String.Single, '#pop'), + (r'\{', Operator, ('#pop', 'root')), # this is a tail call! + ], + + # we should probably deal with '\' escapes here + 'html-content': [ + (r'', Comment, '#pop'), + (r'[^\-]+|-', Comment), + ], + } + + +class ReasonLexer(RegexLexer): + """ + For the ReasonML language (https://reasonml.github.io/). + + .. versionadded:: 2.6 + """ + + name = 'ReasonML' + aliases = ['reasonml', 'reason'] + filenames = ['*.re', '*.rei'] + mimetypes = ['text/x-reasonml'] + + keywords = ( + 'as', 'assert', 'begin', 'class', 'constraint', 'do', 'done', 'downto', + 'else', 'end', 'exception', 'external', 'false', 'for', 'fun', 'esfun', + 'function', 'functor', 'if', 'in', 'include', 'inherit', 'initializer', 'lazy', + 'let', 'switch', 'module', 'pub', 'mutable', 'new', 'nonrec', 'object', 'of', + 'open', 'pri', 'rec', 'sig', 'struct', 'then', 'to', 'true', 'try', + 'type', 'val', 'virtual', 'when', 'while', 'with', + ) + keyopts = ( + '!=', '#', '&', '&&', r'\(', r'\)', r'\*', r'\+', ',', '-', + r'-\.', '=>', r'\.', r'\.\.', r'\.\.\.', ':', '::', ':=', ':>', ';', ';;', '<', + '<-', '=', '>', '>]', r'>\}', r'\?', r'\?\?', r'\[', r'\[<', r'\[>', + r'\[\|', ']', '_', '`', r'\{', r'\{<', r'\|', r'\|\|', r'\|]', r'\}', '~' + ) + + operators = r'[!$%&*+\./:<=>?@^|~-]' + word_operators = ('and', 'asr', 'land', 'lor', 'lsl', 'lsr', 'lxor', 'mod', 'or') + prefix_syms = r'[!?~]' + infix_syms = r'[=<>@^|&+\*/$%-]' + primitives = ('unit', 'int', 'float', 'bool', 'string', 'char', 'list', 'array') + + tokens = { + 'escape-sequence': [ + (r'\\[\\"\'ntbr]', String.Escape), + (r'\\[0-9]{3}', String.Escape), + (r'\\x[0-9a-fA-F]{2}', String.Escape), + ], + 'root': [ + (r'\s+', Text), + (r'false|true|\(\)|\[\]', Name.Builtin.Pseudo), + (r'\b([A-Z][\w\']*)(?=\s*\.)', Name.Namespace, 'dotted'), + (r'\b([A-Z][\w\']*)', Name.Class), + (r'//.*?\n', Comment.Single), + (r'\/\*(?!/)', Comment.Multiline, 'comment'), + (r'\b(%s)\b' % '|'.join(keywords), Keyword), + (r'(%s)' % '|'.join(keyopts[::-1]), Operator.Word), + (r'(%s|%s)?%s' % (infix_syms, prefix_syms, operators), Operator), + (r'\b(%s)\b' % '|'.join(word_operators), Operator.Word), + (r'\b(%s)\b' % '|'.join(primitives), Keyword.Type), + + (r"[^\W\d][\w']*", Name), + + (r'-?\d[\d_]*(.[\d_]*)?([eE][+\-]?\d[\d_]*)', Number.Float), + (r'0[xX][\da-fA-F][\da-fA-F_]*', Number.Hex), + (r'0[oO][0-7][0-7_]*', Number.Oct), + (r'0[bB][01][01_]*', Number.Bin), + (r'\d[\d_]*', Number.Integer), + + (r"'(?:(\\[\\\"'ntbr ])|(\\[0-9]{3})|(\\x[0-9a-fA-F]{2}))'", + String.Char), + (r"'.'", String.Char), + (r"'", Keyword), + + (r'"', String.Double, 'string'), + + (r'[~?][a-z][\w\']*:', Name.Variable), + ], + 'comment': [ + (r'[^/*]+', Comment.Multiline), + (r'\/\*', Comment.Multiline, '#push'), + (r'\*\/', Comment.Multiline, '#pop'), + (r'\*', Comment.Multiline), + ], + 'string': [ + (r'[^\\"]+', String.Double), + include('escape-sequence'), + (r'\\\n', String.Double), + (r'"', String.Double, '#pop'), + ], + 'dotted': [ + (r'\s+', Text), + (r'\.', Punctuation), + (r'[A-Z][\w\']*(?=\s*\.)', Name.Namespace), + (r'[A-Z][\w\']*', Name.Class, '#pop'), + (r'[a-z_][\w\']*', Name, '#pop'), + default('#pop'), + ], + } + + +class FStarLexer(RegexLexer): + """ + For the F* language (https://www.fstar-lang.org/). + .. versionadded:: 2.7 + """ + + name = 'FStar' + aliases = ['fstar'] + filenames = ['*.fst', '*.fsti'] + mimetypes = ['text/x-fstar'] + + keywords = ( + 'abstract', 'attributes', 'noeq', 'unopteq', 'and' + 'begin', 'by', 'default', 'effect', 'else', 'end', 'ensures', + 'exception', 'exists', 'false', 'forall', 'fun', 'function', 'if', + 'in', 'include', 'inline', 'inline_for_extraction', 'irreducible', + 'logic', 'match', 'module', 'mutable', 'new', 'new_effect', 'noextract', + 'of', 'open', 'opaque', 'private', 'range_of', 'reifiable', + 'reify', 'reflectable', 'requires', 'set_range_of', 'sub_effect', + 'synth', 'then', 'total', 'true', 'try', 'type', 'unfold', 'unfoldable', + 'val', 'when', 'with', 'not' + ) + decl_keywords = ('let', 'rec') + assume_keywords = ('assume', 'admit', 'assert', 'calc') + keyopts = ( + r'~', r'-', r'/\\', r'\\/', r'<:', r'<@', r'\(\|', r'\|\)', r'#', r'u#', + r'&', r'\(', r'\)', r'\(\)', r',', r'~>', r'->', r'<-', r'<--', r'<==>', + r'==>', r'\.', r'\?', r'\?\.', r'\.\[', r'\.\(', r'\.\(\|', r'\.\[\|', + r'\{:pattern', r':', r'::', r':=', r';', r';;', r'=', r'%\[', r'!\{', + r'\[', r'\[@', r'\[\|', r'\|>', r'\]', r'\|\]', r'\{', r'\|', r'\}', r'\$' + ) + + operators = r'[!$%&*+\./:<=>?@^|~-]' + prefix_syms = r'[!?~]' + infix_syms = r'[=<>@^|&+\*/$%-]' + primitives = ('unit', 'int', 'float', 'bool', 'string', 'char', 'list', 'array') + + tokens = { + 'escape-sequence': [ + (r'\\[\\"\'ntbr]', String.Escape), + (r'\\[0-9]{3}', String.Escape), + (r'\\x[0-9a-fA-F]{2}', String.Escape), + ], + 'root': [ + (r'\s+', Text), + (r'false|true|False|True|\(\)|\[\]', Name.Builtin.Pseudo), + (r'\b([A-Z][\w\']*)(?=\s*\.)', Name.Namespace, 'dotted'), + (r'\b([A-Z][\w\']*)', Name.Class), + (r'\(\*(?![)])', Comment, 'comment'), + (r'^\/\/.+$', Comment), + (r'\b(%s)\b' % '|'.join(keywords), Keyword), + (r'\b(%s)\b' % '|'.join(assume_keywords), Name.Exception), + (r'\b(%s)\b' % '|'.join(decl_keywords), Keyword.Declaration), + (r'(%s)' % '|'.join(keyopts[::-1]), Operator), + (r'(%s|%s)?%s' % (infix_syms, prefix_syms, operators), Operator), + (r'\b(%s)\b' % '|'.join(primitives), Keyword.Type), + + (r"[^\W\d][\w']*", Name), + + (r'-?\d[\d_]*(.[\d_]*)?([eE][+\-]?\d[\d_]*)', Number.Float), + (r'0[xX][\da-fA-F][\da-fA-F_]*', Number.Hex), + (r'0[oO][0-7][0-7_]*', Number.Oct), + (r'0[bB][01][01_]*', Number.Bin), + (r'\d[\d_]*', Number.Integer), + + (r"'(?:(\\[\\\"'ntbr ])|(\\[0-9]{3})|(\\x[0-9a-fA-F]{2}))'", + String.Char), + (r"'.'", String.Char), + (r"'", Keyword), # a stray quote is another syntax element + (r"\`([\w\'.]+)\`", Operator.Word), # for infix applications + (r"\`", Keyword), # for quoting + (r'"', String.Double, 'string'), + + (r'[~?][a-z][\w\']*:', Name.Variable), + ], + 'comment': [ + (r'[^(*)]+', Comment), + (r'\(\*', Comment, '#push'), + (r'\*\)', Comment, '#pop'), + (r'[(*)]', Comment), + ], + 'string': [ + (r'[^\\"]+', String.Double), + include('escape-sequence'), + (r'\\\n', String.Double), + (r'"', String.Double, '#pop'), + ], + 'dotted': [ + (r'\s+', Text), + (r'\.', Punctuation), + (r'[A-Z][\w\']*(?=\s*\.)', Name.Namespace), + (r'[A-Z][\w\']*', Name.Class, '#pop'), + (r'[a-z_][\w\']*', Name, '#pop'), + default('#pop'), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/modeling.py b/.venv/lib/python3.8/site-packages/pygments/lexers/modeling.py new file mode 100644 index 0000000..b00a7f1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/modeling.py @@ -0,0 +1,365 @@ +""" + pygments.lexers.modeling + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for modeling languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, bygroups, using, default +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Whitespace + +from pygments.lexers.html import HtmlLexer +from pygments.lexers import _stan_builtins + +__all__ = ['ModelicaLexer', 'BugsLexer', 'JagsLexer', 'StanLexer'] + + +class ModelicaLexer(RegexLexer): + """ + For `Modelica `_ source code. + + .. versionadded:: 1.1 + """ + name = 'Modelica' + aliases = ['modelica'] + filenames = ['*.mo'] + mimetypes = ['text/x-modelica'] + + flags = re.DOTALL | re.MULTILINE + + _name = r"(?:'(?:[^\\']|\\.)+'|[a-zA-Z_]\w*)" + + tokens = { + 'whitespace': [ + (r'[\s\ufeff]+', Text), + (r'//[^\n]*\n?', Comment.Single), + (r'/\*.*?\*/', Comment.Multiline) + ], + 'root': [ + include('whitespace'), + (r'"', String.Double, 'string'), + (r'[()\[\]{},;]+', Punctuation), + (r'\.?[*^/+-]|\.|<>|[<>:=]=?', Operator), + (r'\d+(\.?\d*[eE][-+]?\d+|\.\d*)', Number.Float), + (r'\d+', Number.Integer), + (r'(abs|acos|actualStream|array|asin|assert|AssertionLevel|atan|' + r'atan2|backSample|Boolean|cardinality|cat|ceil|change|Clock|' + r'Connections|cos|cosh|cross|delay|diagonal|div|edge|exp|' + r'ExternalObject|fill|floor|getInstanceName|hold|homotopy|' + r'identity|inStream|integer|Integer|interval|inverse|isPresent|' + r'linspace|log|log10|matrix|max|min|mod|ndims|noClock|noEvent|' + r'ones|outerProduct|pre|previous|product|Real|reinit|rem|rooted|' + r'sample|scalar|semiLinear|shiftSample|sign|sin|sinh|size|skew|' + r'smooth|spatialDistribution|sqrt|StateSelect|String|subSample|' + r'sum|superSample|symmetric|tan|tanh|terminal|terminate|time|' + r'transpose|vector|zeros)\b', Name.Builtin), + (r'(algorithm|annotation|break|connect|constant|constrainedby|der|' + r'discrete|each|else|elseif|elsewhen|encapsulated|enumeration|' + r'equation|exit|expandable|extends|external|firstTick|final|flow|for|if|' + r'import|impure|in|initial|inner|input|interval|loop|nondiscrete|outer|' + r'output|parameter|partial|protected|public|pure|redeclare|' + r'replaceable|return|stream|then|when|while)\b', + Keyword.Reserved), + (r'(and|not|or)\b', Operator.Word), + (r'(block|class|connector|end|function|model|operator|package|' + r'record|type)\b', Keyword.Reserved, 'class'), + (r'(false|true)\b', Keyword.Constant), + (r'within\b', Keyword.Reserved, 'package-prefix'), + (_name, Name) + ], + 'class': [ + include('whitespace'), + (r'(function|record)\b', Keyword.Reserved), + (r'(if|for|when|while)\b', Keyword.Reserved, '#pop'), + (_name, Name.Class, '#pop'), + default('#pop') + ], + 'package-prefix': [ + include('whitespace'), + (_name, Name.Namespace, '#pop'), + default('#pop') + ], + 'string': [ + (r'"', String.Double, '#pop'), + (r'\\[\'"?\\abfnrtv]', String.Escape), + (r'(?i)<\s*html\s*>([^\\"]|\\.)+?(<\s*/\s*html\s*>|(?="))', + using(HtmlLexer)), + (r'<|\\?[^"\\<]+', String.Double) + ] + } + + +class BugsLexer(RegexLexer): + """ + Pygments Lexer for `OpenBugs `_ and WinBugs + models. + + .. versionadded:: 1.6 + """ + + name = 'BUGS' + aliases = ['bugs', 'winbugs', 'openbugs'] + filenames = ['*.bug'] + + _FUNCTIONS = ( + # Scalar functions + 'abs', 'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctanh', + 'cloglog', 'cos', 'cosh', 'cumulative', 'cut', 'density', 'deviance', + 'equals', 'expr', 'gammap', 'ilogit', 'icloglog', 'integral', 'log', + 'logfact', 'loggam', 'logit', 'max', 'min', 'phi', 'post.p.value', + 'pow', 'prior.p.value', 'probit', 'replicate.post', 'replicate.prior', + 'round', 'sin', 'sinh', 'solution', 'sqrt', 'step', 'tan', 'tanh', + 'trunc', + # Vector functions + 'inprod', 'interp.lin', 'inverse', 'logdet', 'mean', 'eigen.vals', + 'ode', 'prod', 'p.valueM', 'rank', 'ranked', 'replicate.postM', + 'sd', 'sort', 'sum', + # Special + 'D', 'I', 'F', 'T', 'C') + """ OpenBUGS built-in functions + + From http://www.openbugs.info/Manuals/ModelSpecification.html#ContentsAII + + This also includes + + - T, C, I : Truncation and censoring. + ``T`` and ``C`` are in OpenBUGS. ``I`` in WinBUGS. + - D : ODE + - F : Functional http://www.openbugs.info/Examples/Functionals.html + + """ + + _DISTRIBUTIONS = ('dbern', 'dbin', 'dcat', 'dnegbin', 'dpois', + 'dhyper', 'dbeta', 'dchisqr', 'ddexp', 'dexp', + 'dflat', 'dgamma', 'dgev', 'df', 'dggamma', 'dgpar', + 'dloglik', 'dlnorm', 'dlogis', 'dnorm', 'dpar', + 'dt', 'dunif', 'dweib', 'dmulti', 'ddirch', 'dmnorm', + 'dmt', 'dwish') + """ OpenBUGS built-in distributions + + Functions from + http://www.openbugs.info/Manuals/ModelSpecification.html#ContentsAI + """ + + tokens = { + 'whitespace': [ + (r"\s+", Text), + ], + 'comments': [ + # Comments + (r'#.*$', Comment.Single), + ], + 'root': [ + # Comments + include('comments'), + include('whitespace'), + # Block start + (r'(model)(\s+)(\{)', + bygroups(Keyword.Namespace, Text, Punctuation)), + # Reserved Words + (r'(for|in)(?![\w.])', Keyword.Reserved), + # Built-in Functions + (r'(%s)(?=\s*\()' + % r'|'.join(_FUNCTIONS + _DISTRIBUTIONS), + Name.Builtin), + # Regular variable names + (r'[A-Za-z][\w.]*', Name), + # Number Literals + (r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?', Number), + # Punctuation + (r'\[|\]|\(|\)|:|,|;', Punctuation), + # Assignment operators + # SLexer makes these tokens Operators. + (r'<-|~', Operator), + # Infix and prefix operators + (r'\+|-|\*|/', Operator), + # Block + (r'[{}]', Punctuation), + ] + } + + def analyse_text(text): + if re.search(r"^\s*model\s*{", text, re.M): + return 0.7 + else: + return 0.0 + + +class JagsLexer(RegexLexer): + """ + Pygments Lexer for JAGS. + + .. versionadded:: 1.6 + """ + + name = 'JAGS' + aliases = ['jags'] + filenames = ['*.jag', '*.bug'] + + # JAGS + _FUNCTIONS = ( + 'abs', 'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctanh', + 'cos', 'cosh', 'cloglog', + 'equals', 'exp', 'icloglog', 'ifelse', 'ilogit', 'log', 'logfact', + 'loggam', 'logit', 'phi', 'pow', 'probit', 'round', 'sin', 'sinh', + 'sqrt', 'step', 'tan', 'tanh', 'trunc', 'inprod', 'interp.lin', + 'logdet', 'max', 'mean', 'min', 'prod', 'sum', 'sd', 'inverse', + 'rank', 'sort', 't', 'acos', 'acosh', 'asin', 'asinh', 'atan', + # Truncation/Censoring (should I include) + 'T', 'I') + # Distributions with density, probability and quartile functions + _DISTRIBUTIONS = tuple('[dpq]%s' % x for x in + ('bern', 'beta', 'dchiqsqr', 'ddexp', 'dexp', + 'df', 'gamma', 'gen.gamma', 'logis', 'lnorm', + 'negbin', 'nchisqr', 'norm', 'par', 'pois', 'weib')) + # Other distributions without density and probability + _OTHER_DISTRIBUTIONS = ( + 'dt', 'dunif', 'dbetabin', 'dbern', 'dbin', 'dcat', 'dhyper', + 'ddirch', 'dmnorm', 'dwish', 'dmt', 'dmulti', 'dbinom', 'dchisq', + 'dnbinom', 'dweibull', 'ddirich') + + tokens = { + 'whitespace': [ + (r"\s+", Text), + ], + 'names': [ + # Regular variable names + (r'[a-zA-Z][\w.]*\b', Name), + ], + 'comments': [ + # do not use stateful comments + (r'(?s)/\*.*?\*/', Comment.Multiline), + # Comments + (r'#.*$', Comment.Single), + ], + 'root': [ + # Comments + include('comments'), + include('whitespace'), + # Block start + (r'(model|data)(\s+)(\{)', + bygroups(Keyword.Namespace, Text, Punctuation)), + (r'var(?![\w.])', Keyword.Declaration), + # Reserved Words + (r'(for|in)(?![\w.])', Keyword.Reserved), + # Builtins + # Need to use lookahead because . is a valid char + (r'(%s)(?=\s*\()' % r'|'.join(_FUNCTIONS + + _DISTRIBUTIONS + + _OTHER_DISTRIBUTIONS), + Name.Builtin), + # Names + include('names'), + # Number Literals + (r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?', Number), + (r'\[|\]|\(|\)|:|,|;', Punctuation), + # Assignment operators + (r'<-|~', Operator), + # # JAGS includes many more than OpenBUGS + (r'\+|-|\*|\/|\|\|[&]{2}|[<>=]=?|\^|%.*?%', Operator), + (r'[{}]', Punctuation), + ] + } + + def analyse_text(text): + if re.search(r'^\s*model\s*\{', text, re.M): + if re.search(r'^\s*data\s*\{', text, re.M): + return 0.9 + elif re.search(r'^\s*var', text, re.M): + return 0.9 + else: + return 0.3 + else: + return 0 + + +class StanLexer(RegexLexer): + """Pygments Lexer for Stan models. + + The Stan modeling language is specified in the *Stan Modeling Language + User's Guide and Reference Manual, v2.17.0*, + `pdf `__. + + .. versionadded:: 1.6 + """ + + name = 'Stan' + aliases = ['stan'] + filenames = ['*.stan'] + + tokens = { + 'whitespace': [ + (r"\s+", Text), + ], + 'comments': [ + (r'(?s)/\*.*?\*/', Comment.Multiline), + # Comments + (r'(//|#).*$', Comment.Single), + ], + 'root': [ + # Stan is more restrictive on strings than this regex + (r'"[^"]*"', String), + # Comments + include('comments'), + # block start + include('whitespace'), + # Block start + (r'(%s)(\s*)(\{)' % + r'|'.join(('functions', 'data', r'transformed\s+?data', + 'parameters', r'transformed\s+parameters', + 'model', r'generated\s+quantities')), + bygroups(Keyword.Namespace, Text, Punctuation)), + # target keyword + (r'target\s*\+=', Keyword), + # Reserved Words + (r'(%s)\b' % r'|'.join(_stan_builtins.KEYWORDS), Keyword), + # Truncation + (r'T(?=\s*\[)', Keyword), + # Data types + (r'(%s)\b' % r'|'.join(_stan_builtins.TYPES), Keyword.Type), + # < should be punctuation, but elsewhere I can't tell if it is in + # a range constraint + (r'(<)(\s*)(upper|lower)(\s*)(=)', + bygroups(Operator, Whitespace, Keyword, Whitespace, Punctuation)), + (r'(,)(\s*)(upper)(\s*)(=)', + bygroups(Punctuation, Whitespace, Keyword, Whitespace, Punctuation)), + # Punctuation + (r"[;,\[\]()]", Punctuation), + # Builtin + (r'(%s)(?=\s*\()' % '|'.join(_stan_builtins.FUNCTIONS), Name.Builtin), + (r'(~)(\s*)(%s)(?=\s*\()' % '|'.join(_stan_builtins.DISTRIBUTIONS), + bygroups(Operator, Whitespace, Name.Builtin)), + # Special names ending in __, like lp__ + (r'[A-Za-z]\w*__\b', Name.Builtin.Pseudo), + (r'(%s)\b' % r'|'.join(_stan_builtins.RESERVED), Keyword.Reserved), + # user-defined functions + (r'[A-Za-z]\w*(?=\s*\()]', Name.Function), + # Regular variable names + (r'[A-Za-z]\w*\b', Name), + # Real Literals + (r'[0-9]+(\.[0-9]*)?([eE][+-]?[0-9]+)?', Number.Float), + (r'\.[0-9]+([eE][+-]?[0-9]+)?', Number.Float), + # Integer Literals + (r'[0-9]+', Number.Integer), + # Assignment operators + (r'<-|(?:\+|-|\.?/|\.?\*|=)?=|~', Operator), + # Infix, prefix and postfix operators (and = ) + (r"\+|-|\.?\*|\.?/|\\|'|\^|!=?|<=?|>=?|\|\||&&|%|\?|:", Operator), + # Block delimiters + (r'[{}]', Punctuation), + # Distribution | + (r'\|', Punctuation) + ] + } + + def analyse_text(text): + if re.search(r'^\s*parameters\s*\{', text, re.M): + return 1.0 + else: + return 0.0 diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/modula2.py b/.venv/lib/python3.8/site-packages/pygments/lexers/modula2.py new file mode 100644 index 0000000..cad2f4f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/modula2.py @@ -0,0 +1,1579 @@ +""" + pygments.lexers.modula2 + ~~~~~~~~~~~~~~~~~~~~~~~ + + Multi-Dialect Lexer for Modula-2. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include +from pygments.util import get_bool_opt, get_list_opt +from pygments.token import Text, Comment, Operator, Keyword, Name, \ + String, Number, Punctuation, Error + +__all__ = ['Modula2Lexer'] + + +# Multi-Dialect Modula-2 Lexer +class Modula2Lexer(RegexLexer): + """ + For `Modula-2 `_ source code. + + The Modula-2 lexer supports several dialects. By default, it operates in + fallback mode, recognising the *combined* literals, punctuation symbols + and operators of all supported dialects, and the *combined* reserved words + and builtins of PIM Modula-2, ISO Modula-2 and Modula-2 R10, while not + differentiating between library defined identifiers. + + To select a specific dialect, a dialect option may be passed + or a dialect tag may be embedded into a source file. + + Dialect Options: + + `m2pim` + Select PIM Modula-2 dialect. + `m2iso` + Select ISO Modula-2 dialect. + `m2r10` + Select Modula-2 R10 dialect. + `objm2` + Select Objective Modula-2 dialect. + + The PIM and ISO dialect options may be qualified with a language extension. + + Language Extensions: + + `+aglet` + Select Aglet Modula-2 extensions, available with m2iso. + `+gm2` + Select GNU Modula-2 extensions, available with m2pim. + `+p1` + Select p1 Modula-2 extensions, available with m2iso. + `+xds` + Select XDS Modula-2 extensions, available with m2iso. + + + Passing a Dialect Option via Unix Commandline Interface + + Dialect options may be passed to the lexer using the `dialect` key. + Only one such option should be passed. If multiple dialect options are + passed, the first valid option is used, any subsequent options are ignored. + + Examples: + + `$ pygmentize -O full,dialect=m2iso -f html -o /path/to/output /path/to/input` + Use ISO dialect to render input to HTML output + `$ pygmentize -O full,dialect=m2iso+p1 -f rtf -o /path/to/output /path/to/input` + Use ISO dialect with p1 extensions to render input to RTF output + + + Embedding a Dialect Option within a source file + + A dialect option may be embedded in a source file in form of a dialect + tag, a specially formatted comment that specifies a dialect option. + + Dialect Tag EBNF:: + + dialectTag : + OpeningCommentDelim Prefix dialectOption ClosingCommentDelim ; + + dialectOption : + 'm2pim' | 'm2iso' | 'm2r10' | 'objm2' | + 'm2iso+aglet' | 'm2pim+gm2' | 'm2iso+p1' | 'm2iso+xds' ; + + Prefix : '!' ; + + OpeningCommentDelim : '(*' ; + + ClosingCommentDelim : '*)' ; + + No whitespace is permitted between the tokens of a dialect tag. + + In the event that a source file contains multiple dialect tags, the first + tag that contains a valid dialect option will be used and any subsequent + dialect tags will be ignored. Ideally, a dialect tag should be placed + at the beginning of a source file. + + An embedded dialect tag overrides a dialect option set via command line. + + Examples: + + ``(*!m2r10*) DEFINITION MODULE Foobar; ...`` + Use Modula2 R10 dialect to render this source file. + ``(*!m2pim+gm2*) DEFINITION MODULE Bazbam; ...`` + Use PIM dialect with GNU extensions to render this source file. + + + Algol Publication Mode: + + In Algol publication mode, source text is rendered for publication of + algorithms in scientific papers and academic texts, following the format + of the Revised Algol-60 Language Report. It is activated by passing + one of two corresponding styles as an option: + + `algol` + render reserved words lowercase underline boldface + and builtins lowercase boldface italic + `algol_nu` + render reserved words lowercase boldface (no underlining) + and builtins lowercase boldface italic + + The lexer automatically performs the required lowercase conversion when + this mode is activated. + + Example: + + ``$ pygmentize -O full,style=algol -f latex -o /path/to/output /path/to/input`` + Render input file in Algol publication mode to LaTeX output. + + + Rendering Mode of First Class ADT Identifiers: + + The rendering of standard library first class ADT identifiers is controlled + by option flag "treat_stdlib_adts_as_builtins". + + When this option is turned on, standard library ADT identifiers are rendered + as builtins. When it is turned off, they are rendered as ordinary library + identifiers. + + `treat_stdlib_adts_as_builtins` (default: On) + + The option is useful for dialects that support ADTs as first class objects + and provide ADTs in the standard library that would otherwise be built-in. + + At present, only Modula-2 R10 supports library ADTs as first class objects + and therefore, no ADT identifiers are defined for any other dialects. + + Example: + + ``$ pygmentize -O full,dialect=m2r10,treat_stdlib_adts_as_builtins=Off ...`` + Render standard library ADTs as ordinary library types. + + .. versionadded:: 1.3 + + .. versionchanged:: 2.1 + Added multi-dialect support. + """ + name = 'Modula-2' + aliases = ['modula2', 'm2'] + filenames = ['*.def', '*.mod'] + mimetypes = ['text/x-modula2'] + + flags = re.MULTILINE | re.DOTALL + + tokens = { + 'whitespace': [ + (r'\n+', Text), # blank lines + (r'\s+', Text), # whitespace + ], + 'dialecttags': [ + # PIM Dialect Tag + (r'\(\*!m2pim\*\)', Comment.Special), + # ISO Dialect Tag + (r'\(\*!m2iso\*\)', Comment.Special), + # M2R10 Dialect Tag + (r'\(\*!m2r10\*\)', Comment.Special), + # ObjM2 Dialect Tag + (r'\(\*!objm2\*\)', Comment.Special), + # Aglet Extensions Dialect Tag + (r'\(\*!m2iso\+aglet\*\)', Comment.Special), + # GNU Extensions Dialect Tag + (r'\(\*!m2pim\+gm2\*\)', Comment.Special), + # p1 Extensions Dialect Tag + (r'\(\*!m2iso\+p1\*\)', Comment.Special), + # XDS Extensions Dialect Tag + (r'\(\*!m2iso\+xds\*\)', Comment.Special), + ], + 'identifiers': [ + (r'([a-zA-Z_$][\w$]*)', Name), + ], + 'prefixed_number_literals': [ + # + # Base-2, whole number + (r'0b[01]+(\'[01]+)*', Number.Bin), + # + # Base-16, whole number + (r'0[ux][0-9A-F]+(\'[0-9A-F]+)*', Number.Hex), + ], + 'plain_number_literals': [ + # + # Base-10, real number with exponent + (r'[0-9]+(\'[0-9]+)*' # integral part + r'\.[0-9]+(\'[0-9]+)*' # fractional part + r'[eE][+-]?[0-9]+(\'[0-9]+)*', # exponent + Number.Float), + # + # Base-10, real number without exponent + (r'[0-9]+(\'[0-9]+)*' # integral part + r'\.[0-9]+(\'[0-9]+)*', # fractional part + Number.Float), + # + # Base-10, whole number + (r'[0-9]+(\'[0-9]+)*', Number.Integer), + ], + 'suffixed_number_literals': [ + # + # Base-8, whole number + (r'[0-7]+B', Number.Oct), + # + # Base-8, character code + (r'[0-7]+C', Number.Oct), + # + # Base-16, number + (r'[0-9A-F]+H', Number.Hex), + ], + 'string_literals': [ + (r'"(\\\\|\\[^\\]|[^"\\])*"', String.Double), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String.Single), + ], + 'digraph_operators': [ + # Dot Product Operator + (r'\*\.', Operator), + # Array Concatenation Operator + (r'\+>', Operator), # M2R10 + ObjM2 + # Inequality Operator + (r'<>', Operator), # ISO + PIM + # Less-Or-Equal, Subset + (r'<=', Operator), + # Greater-Or-Equal, Superset + (r'>=', Operator), + # Identity Operator + (r'==', Operator), # M2R10 + ObjM2 + # Type Conversion Operator + (r'::', Operator), # M2R10 + ObjM2 + # Assignment Symbol + (r':=', Operator), + # Postfix Increment Mutator + (r'\+\+', Operator), # M2R10 + ObjM2 + # Postfix Decrement Mutator + (r'--', Operator), # M2R10 + ObjM2 + ], + 'unigraph_operators': [ + # Arithmetic Operators + (r'[+-]', Operator), + (r'[*/]', Operator), + # ISO 80000-2 compliant Set Difference Operator + (r'\\', Operator), # M2R10 + ObjM2 + # Relational Operators + (r'[=#<>]', Operator), + # Dereferencing Operator + (r'\^', Operator), + # Dereferencing Operator Synonym + (r'@', Operator), # ISO + # Logical AND Operator Synonym + (r'&', Operator), # PIM + ISO + # Logical NOT Operator Synonym + (r'~', Operator), # PIM + ISO + # Smalltalk Message Prefix + (r'`', Operator), # ObjM2 + ], + 'digraph_punctuation': [ + # Range Constructor + (r'\.\.', Punctuation), + # Opening Chevron Bracket + (r'<<', Punctuation), # M2R10 + ISO + # Closing Chevron Bracket + (r'>>', Punctuation), # M2R10 + ISO + # Blueprint Punctuation + (r'->', Punctuation), # M2R10 + ISO + # Distinguish |# and # in M2 R10 + (r'\|#', Punctuation), + # Distinguish ## and # in M2 R10 + (r'##', Punctuation), + # Distinguish |* and * in M2 R10 + (r'\|\*', Punctuation), + ], + 'unigraph_punctuation': [ + # Common Punctuation + (r'[()\[\]{},.:;|]', Punctuation), + # Case Label Separator Synonym + (r'!', Punctuation), # ISO + # Blueprint Punctuation + (r'\?', Punctuation), # M2R10 + ObjM2 + ], + 'comments': [ + # Single Line Comment + (r'^//.*?\n', Comment.Single), # M2R10 + ObjM2 + # Block Comment + (r'\(\*([^$].*?)\*\)', Comment.Multiline), + # Template Block Comment + (r'/\*(.*?)\*/', Comment.Multiline), # M2R10 + ObjM2 + ], + 'pragmas': [ + # ISO Style Pragmas + (r'<\*.*?\*>', Comment.Preproc), # ISO, M2R10 + ObjM2 + # Pascal Style Pragmas + (r'\(\*\$.*?\*\)', Comment.Preproc), # PIM + ], + 'root': [ + include('whitespace'), + include('dialecttags'), + include('pragmas'), + include('comments'), + include('identifiers'), + include('suffixed_number_literals'), # PIM + ISO + include('prefixed_number_literals'), # M2R10 + ObjM2 + include('plain_number_literals'), + include('string_literals'), + include('digraph_punctuation'), + include('digraph_operators'), + include('unigraph_punctuation'), + include('unigraph_operators'), + ] + } + +# C o m m o n D a t a s e t s + + # Common Reserved Words Dataset + common_reserved_words = ( + # 37 common reserved words + 'AND', 'ARRAY', 'BEGIN', 'BY', 'CASE', 'CONST', 'DEFINITION', 'DIV', + 'DO', 'ELSE', 'ELSIF', 'END', 'EXIT', 'FOR', 'FROM', 'IF', + 'IMPLEMENTATION', 'IMPORT', 'IN', 'LOOP', 'MOD', 'MODULE', 'NOT', + 'OF', 'OR', 'POINTER', 'PROCEDURE', 'RECORD', 'REPEAT', 'RETURN', + 'SET', 'THEN', 'TO', 'TYPE', 'UNTIL', 'VAR', 'WHILE', + ) + + # Common Builtins Dataset + common_builtins = ( + # 16 common builtins + 'ABS', 'BOOLEAN', 'CARDINAL', 'CHAR', 'CHR', 'FALSE', 'INTEGER', + 'LONGINT', 'LONGREAL', 'MAX', 'MIN', 'NIL', 'ODD', 'ORD', 'REAL', + 'TRUE', + ) + + # Common Pseudo-Module Builtins Dataset + common_pseudo_builtins = ( + # 4 common pseudo builtins + 'ADDRESS', 'BYTE', 'WORD', 'ADR' + ) + +# P I M M o d u l a - 2 D a t a s e t s + + # Lexemes to Mark as Error Tokens for PIM Modula-2 + pim_lexemes_to_reject = ( + '!', '`', '@', '$', '%', '?', '\\', '==', '++', '--', '::', '*.', + '+>', '->', '<<', '>>', '|#', '##', + ) + + # PIM Modula-2 Additional Reserved Words Dataset + pim_additional_reserved_words = ( + # 3 additional reserved words + 'EXPORT', 'QUALIFIED', 'WITH', + ) + + # PIM Modula-2 Additional Builtins Dataset + pim_additional_builtins = ( + # 16 additional builtins + 'BITSET', 'CAP', 'DEC', 'DISPOSE', 'EXCL', 'FLOAT', 'HALT', 'HIGH', + 'INC', 'INCL', 'NEW', 'NIL', 'PROC', 'SIZE', 'TRUNC', 'VAL', + ) + + # PIM Modula-2 Additional Pseudo-Module Builtins Dataset + pim_additional_pseudo_builtins = ( + # 5 additional pseudo builtins + 'SYSTEM', 'PROCESS', 'TSIZE', 'NEWPROCESS', 'TRANSFER', + ) + +# I S O M o d u l a - 2 D a t a s e t s + + # Lexemes to Mark as Error Tokens for ISO Modula-2 + iso_lexemes_to_reject = ( + '`', '$', '%', '?', '\\', '==', '++', '--', '::', '*.', '+>', '->', + '<<', '>>', '|#', '##', + ) + + # ISO Modula-2 Additional Reserved Words Dataset + iso_additional_reserved_words = ( + # 9 additional reserved words (ISO 10514-1) + 'EXCEPT', 'EXPORT', 'FINALLY', 'FORWARD', 'PACKEDSET', 'QUALIFIED', + 'REM', 'RETRY', 'WITH', + # 10 additional reserved words (ISO 10514-2 & ISO 10514-3) + 'ABSTRACT', 'AS', 'CLASS', 'GUARD', 'INHERIT', 'OVERRIDE', 'READONLY', + 'REVEAL', 'TRACED', 'UNSAFEGUARDED', + ) + + # ISO Modula-2 Additional Builtins Dataset + iso_additional_builtins = ( + # 26 additional builtins (ISO 10514-1) + 'BITSET', 'CAP', 'CMPLX', 'COMPLEX', 'DEC', 'DISPOSE', 'EXCL', 'FLOAT', + 'HALT', 'HIGH', 'IM', 'INC', 'INCL', 'INT', 'INTERRUPTIBLE', 'LENGTH', + 'LFLOAT', 'LONGCOMPLEX', 'NEW', 'PROC', 'PROTECTION', 'RE', 'SIZE', + 'TRUNC', 'UNINTERRUBTIBLE', 'VAL', + # 5 additional builtins (ISO 10514-2 & ISO 10514-3) + 'CREATE', 'DESTROY', 'EMPTY', 'ISMEMBER', 'SELF', + ) + + # ISO Modula-2 Additional Pseudo-Module Builtins Dataset + iso_additional_pseudo_builtins = ( + # 14 additional builtins (SYSTEM) + 'SYSTEM', 'BITSPERLOC', 'LOCSPERBYTE', 'LOCSPERWORD', 'LOC', + 'ADDADR', 'SUBADR', 'DIFADR', 'MAKEADR', 'ADR', + 'ROTATE', 'SHIFT', 'CAST', 'TSIZE', + # 13 additional builtins (COROUTINES) + 'COROUTINES', 'ATTACH', 'COROUTINE', 'CURRENT', 'DETACH', 'HANDLER', + 'INTERRUPTSOURCE', 'IOTRANSFER', 'IsATTACHED', 'LISTEN', + 'NEWCOROUTINE', 'PROT', 'TRANSFER', + # 9 additional builtins (EXCEPTIONS) + 'EXCEPTIONS', 'AllocateSource', 'CurrentNumber', 'ExceptionNumber', + 'ExceptionSource', 'GetMessage', 'IsCurrentSource', + 'IsExceptionalExecution', 'RAISE', + # 3 additional builtins (TERMINATION) + 'TERMINATION', 'IsTerminating', 'HasHalted', + # 4 additional builtins (M2EXCEPTION) + 'M2EXCEPTION', 'M2Exceptions', 'M2Exception', 'IsM2Exception', + 'indexException', 'rangeException', 'caseSelectException', + 'invalidLocation', 'functionException', 'wholeValueException', + 'wholeDivException', 'realValueException', 'realDivException', + 'complexValueException', 'complexDivException', 'protException', + 'sysException', 'coException', 'exException', + ) + +# M o d u l a - 2 R 1 0 D a t a s e t s + + # Lexemes to Mark as Error Tokens for Modula-2 R10 + m2r10_lexemes_to_reject = ( + '!', '`', '@', '$', '%', '&', '<>', + ) + + # Modula-2 R10 reserved words in addition to the common set + m2r10_additional_reserved_words = ( + # 12 additional reserved words + 'ALIAS', 'ARGLIST', 'BLUEPRINT', 'COPY', 'GENLIB', 'INDETERMINATE', + 'NEW', 'NONE', 'OPAQUE', 'REFERENTIAL', 'RELEASE', 'RETAIN', + # 2 additional reserved words with symbolic assembly option + 'ASM', 'REG', + ) + + # Modula-2 R10 builtins in addition to the common set + m2r10_additional_builtins = ( + # 26 additional builtins + 'CARDINAL', 'COUNT', 'EMPTY', 'EXISTS', 'INSERT', 'LENGTH', 'LONGCARD', + 'OCTET', 'PTR', 'PRED', 'READ', 'READNEW', 'REMOVE', 'RETRIEVE', 'SORT', + 'STORE', 'SUBSET', 'SUCC', 'TLIMIT', 'TMAX', 'TMIN', 'TRUE', 'TSIZE', + 'UNICHAR', 'WRITE', 'WRITEF', + ) + + # Modula-2 R10 Additional Pseudo-Module Builtins Dataset + m2r10_additional_pseudo_builtins = ( + # 13 additional builtins (TPROPERTIES) + 'TPROPERTIES', 'PROPERTY', 'LITERAL', 'TPROPERTY', 'TLITERAL', + 'TBUILTIN', 'TDYN', 'TREFC', 'TNIL', 'TBASE', 'TPRECISION', + 'TMAXEXP', 'TMINEXP', + # 4 additional builtins (CONVERSION) + 'CONVERSION', 'TSXFSIZE', 'SXF', 'VAL', + # 35 additional builtins (UNSAFE) + 'UNSAFE', 'CAST', 'INTRINSIC', 'AVAIL', 'ADD', 'SUB', 'ADDC', 'SUBC', + 'FETCHADD', 'FETCHSUB', 'SHL', 'SHR', 'ASHR', 'ROTL', 'ROTR', 'ROTLC', + 'ROTRC', 'BWNOT', 'BWAND', 'BWOR', 'BWXOR', 'BWNAND', 'BWNOR', + 'SETBIT', 'TESTBIT', 'LSBIT', 'MSBIT', 'CSBITS', 'BAIL', 'HALT', + 'TODO', 'FFI', 'ADDR', 'VARGLIST', 'VARGC', + # 11 additional builtins (ATOMIC) + 'ATOMIC', 'INTRINSIC', 'AVAIL', 'SWAP', 'CAS', 'INC', 'DEC', 'BWAND', + 'BWNAND', 'BWOR', 'BWXOR', + # 7 additional builtins (COMPILER) + 'COMPILER', 'DEBUG', 'MODNAME', 'PROCNAME', 'LINENUM', 'DEFAULT', + 'HASH', + # 5 additional builtins (ASSEMBLER) + 'ASSEMBLER', 'REGISTER', 'SETREG', 'GETREG', 'CODE', + ) + +# O b j e c t i v e M o d u l a - 2 D a t a s e t s + + # Lexemes to Mark as Error Tokens for Objective Modula-2 + objm2_lexemes_to_reject = ( + '!', '$', '%', '&', '<>', + ) + + # Objective Modula-2 Extensions + # reserved words in addition to Modula-2 R10 + objm2_additional_reserved_words = ( + # 16 additional reserved words + 'BYCOPY', 'BYREF', 'CLASS', 'CONTINUE', 'CRITICAL', 'INOUT', 'METHOD', + 'ON', 'OPTIONAL', 'OUT', 'PRIVATE', 'PROTECTED', 'PROTOCOL', 'PUBLIC', + 'SUPER', 'TRY', + ) + + # Objective Modula-2 Extensions + # builtins in addition to Modula-2 R10 + objm2_additional_builtins = ( + # 3 additional builtins + 'OBJECT', 'NO', 'YES', + ) + + # Objective Modula-2 Extensions + # pseudo-module builtins in addition to Modula-2 R10 + objm2_additional_pseudo_builtins = ( + # None + ) + +# A g l e t M o d u l a - 2 D a t a s e t s + + # Aglet Extensions + # reserved words in addition to ISO Modula-2 + aglet_additional_reserved_words = ( + # None + ) + + # Aglet Extensions + # builtins in addition to ISO Modula-2 + aglet_additional_builtins = ( + # 9 additional builtins + 'BITSET8', 'BITSET16', 'BITSET32', 'CARDINAL8', 'CARDINAL16', + 'CARDINAL32', 'INTEGER8', 'INTEGER16', 'INTEGER32', + ) + + # Aglet Modula-2 Extensions + # pseudo-module builtins in addition to ISO Modula-2 + aglet_additional_pseudo_builtins = ( + # None + ) + +# G N U M o d u l a - 2 D a t a s e t s + + # GNU Extensions + # reserved words in addition to PIM Modula-2 + gm2_additional_reserved_words = ( + # 10 additional reserved words + 'ASM', '__ATTRIBUTE__', '__BUILTIN__', '__COLUMN__', '__DATE__', + '__FILE__', '__FUNCTION__', '__LINE__', '__MODULE__', 'VOLATILE', + ) + + # GNU Extensions + # builtins in addition to PIM Modula-2 + gm2_additional_builtins = ( + # 21 additional builtins + 'BITSET8', 'BITSET16', 'BITSET32', 'CARDINAL8', 'CARDINAL16', + 'CARDINAL32', 'CARDINAL64', 'COMPLEX32', 'COMPLEX64', 'COMPLEX96', + 'COMPLEX128', 'INTEGER8', 'INTEGER16', 'INTEGER32', 'INTEGER64', + 'REAL8', 'REAL16', 'REAL32', 'REAL96', 'REAL128', 'THROW', + ) + + # GNU Extensions + # pseudo-module builtins in addition to PIM Modula-2 + gm2_additional_pseudo_builtins = ( + # None + ) + +# p 1 M o d u l a - 2 D a t a s e t s + + # p1 Extensions + # reserved words in addition to ISO Modula-2 + p1_additional_reserved_words = ( + # None + ) + + # p1 Extensions + # builtins in addition to ISO Modula-2 + p1_additional_builtins = ( + # None + ) + + # p1 Modula-2 Extensions + # pseudo-module builtins in addition to ISO Modula-2 + p1_additional_pseudo_builtins = ( + # 1 additional builtin + 'BCD', + ) + +# X D S M o d u l a - 2 D a t a s e t s + + # XDS Extensions + # reserved words in addition to ISO Modula-2 + xds_additional_reserved_words = ( + # 1 additional reserved word + 'SEQ', + ) + + # XDS Extensions + # builtins in addition to ISO Modula-2 + xds_additional_builtins = ( + # 9 additional builtins + 'ASH', 'ASSERT', 'DIFFADR_TYPE', 'ENTIER', 'INDEX', 'LEN', + 'LONGCARD', 'SHORTCARD', 'SHORTINT', + ) + + # XDS Modula-2 Extensions + # pseudo-module builtins in addition to ISO Modula-2 + xds_additional_pseudo_builtins = ( + # 22 additional builtins (SYSTEM) + 'PROCESS', 'NEWPROCESS', 'BOOL8', 'BOOL16', 'BOOL32', 'CARD8', + 'CARD16', 'CARD32', 'INT8', 'INT16', 'INT32', 'REF', 'MOVE', + 'FILL', 'GET', 'PUT', 'CC', 'int', 'unsigned', 'size_t', 'void' + # 3 additional builtins (COMPILER) + 'COMPILER', 'OPTION', 'EQUATION' + ) + +# P I M S t a n d a r d L i b r a r y D a t a s e t s + + # PIM Modula-2 Standard Library Modules Dataset + pim_stdlib_module_identifiers = ( + 'Terminal', 'FileSystem', 'InOut', 'RealInOut', 'MathLib0', 'Storage', + ) + + # PIM Modula-2 Standard Library Types Dataset + pim_stdlib_type_identifiers = ( + 'Flag', 'FlagSet', 'Response', 'Command', 'Lock', 'Permission', + 'MediumType', 'File', 'FileProc', 'DirectoryProc', 'FileCommand', + 'DirectoryCommand', + ) + + # PIM Modula-2 Standard Library Procedures Dataset + pim_stdlib_proc_identifiers = ( + 'Read', 'BusyRead', 'ReadAgain', 'Write', 'WriteString', 'WriteLn', + 'Create', 'Lookup', 'Close', 'Delete', 'Rename', 'SetRead', 'SetWrite', + 'SetModify', 'SetOpen', 'Doio', 'SetPos', 'GetPos', 'Length', 'Reset', + 'Again', 'ReadWord', 'WriteWord', 'ReadChar', 'WriteChar', + 'CreateMedium', 'DeleteMedium', 'AssignName', 'DeassignName', + 'ReadMedium', 'LookupMedium', 'OpenInput', 'OpenOutput', 'CloseInput', + 'CloseOutput', 'ReadString', 'ReadInt', 'ReadCard', 'ReadWrd', + 'WriteInt', 'WriteCard', 'WriteOct', 'WriteHex', 'WriteWrd', + 'ReadReal', 'WriteReal', 'WriteFixPt', 'WriteRealOct', 'sqrt', 'exp', + 'ln', 'sin', 'cos', 'arctan', 'entier', 'ALLOCATE', 'DEALLOCATE', + ) + + # PIM Modula-2 Standard Library Variables Dataset + pim_stdlib_var_identifiers = ( + 'Done', 'termCH', 'in', 'out' + ) + + # PIM Modula-2 Standard Library Constants Dataset + pim_stdlib_const_identifiers = ( + 'EOL', + ) + +# I S O S t a n d a r d L i b r a r y D a t a s e t s + + # ISO Modula-2 Standard Library Modules Dataset + iso_stdlib_module_identifiers = ( + # TO DO + ) + + # ISO Modula-2 Standard Library Types Dataset + iso_stdlib_type_identifiers = ( + # TO DO + ) + + # ISO Modula-2 Standard Library Procedures Dataset + iso_stdlib_proc_identifiers = ( + # TO DO + ) + + # ISO Modula-2 Standard Library Variables Dataset + iso_stdlib_var_identifiers = ( + # TO DO + ) + + # ISO Modula-2 Standard Library Constants Dataset + iso_stdlib_const_identifiers = ( + # TO DO + ) + +# M 2 R 1 0 S t a n d a r d L i b r a r y D a t a s e t s + + # Modula-2 R10 Standard Library ADTs Dataset + m2r10_stdlib_adt_identifiers = ( + 'BCD', 'LONGBCD', 'BITSET', 'SHORTBITSET', 'LONGBITSET', + 'LONGLONGBITSET', 'COMPLEX', 'LONGCOMPLEX', 'SHORTCARD', 'LONGLONGCARD', + 'SHORTINT', 'LONGLONGINT', 'POSINT', 'SHORTPOSINT', 'LONGPOSINT', + 'LONGLONGPOSINT', 'BITSET8', 'BITSET16', 'BITSET32', 'BITSET64', + 'BITSET128', 'BS8', 'BS16', 'BS32', 'BS64', 'BS128', 'CARDINAL8', + 'CARDINAL16', 'CARDINAL32', 'CARDINAL64', 'CARDINAL128', 'CARD8', + 'CARD16', 'CARD32', 'CARD64', 'CARD128', 'INTEGER8', 'INTEGER16', + 'INTEGER32', 'INTEGER64', 'INTEGER128', 'INT8', 'INT16', 'INT32', + 'INT64', 'INT128', 'STRING', 'UNISTRING', + ) + + # Modula-2 R10 Standard Library Blueprints Dataset + m2r10_stdlib_blueprint_identifiers = ( + 'ProtoRoot', 'ProtoComputational', 'ProtoNumeric', 'ProtoScalar', + 'ProtoNonScalar', 'ProtoCardinal', 'ProtoInteger', 'ProtoReal', + 'ProtoComplex', 'ProtoVector', 'ProtoTuple', 'ProtoCompArray', + 'ProtoCollection', 'ProtoStaticArray', 'ProtoStaticSet', + 'ProtoStaticString', 'ProtoArray', 'ProtoString', 'ProtoSet', + 'ProtoMultiSet', 'ProtoDictionary', 'ProtoMultiDict', 'ProtoExtension', + 'ProtoIO', 'ProtoCardMath', 'ProtoIntMath', 'ProtoRealMath', + ) + + # Modula-2 R10 Standard Library Modules Dataset + m2r10_stdlib_module_identifiers = ( + 'ASCII', 'BooleanIO', 'CharIO', 'UnicharIO', 'OctetIO', + 'CardinalIO', 'LongCardIO', 'IntegerIO', 'LongIntIO', 'RealIO', + 'LongRealIO', 'BCDIO', 'LongBCDIO', 'CardMath', 'LongCardMath', + 'IntMath', 'LongIntMath', 'RealMath', 'LongRealMath', 'BCDMath', + 'LongBCDMath', 'FileIO', 'FileSystem', 'Storage', 'IOSupport', + ) + + # Modula-2 R10 Standard Library Types Dataset + m2r10_stdlib_type_identifiers = ( + 'File', 'Status', + # TO BE COMPLETED + ) + + # Modula-2 R10 Standard Library Procedures Dataset + m2r10_stdlib_proc_identifiers = ( + 'ALLOCATE', 'DEALLOCATE', 'SIZE', + # TO BE COMPLETED + ) + + # Modula-2 R10 Standard Library Variables Dataset + m2r10_stdlib_var_identifiers = ( + 'stdIn', 'stdOut', 'stdErr', + ) + + # Modula-2 R10 Standard Library Constants Dataset + m2r10_stdlib_const_identifiers = ( + 'pi', 'tau', + ) + +# D i a l e c t s + + # Dialect modes + dialects = ( + 'unknown', + 'm2pim', 'm2iso', 'm2r10', 'objm2', + 'm2iso+aglet', 'm2pim+gm2', 'm2iso+p1', 'm2iso+xds', + ) + +# D a t a b a s e s + + # Lexemes to Mark as Errors Database + lexemes_to_reject_db = { + # Lexemes to reject for unknown dialect + 'unknown': ( + # LEAVE THIS EMPTY + ), + # Lexemes to reject for PIM Modula-2 + 'm2pim': ( + pim_lexemes_to_reject, + ), + # Lexemes to reject for ISO Modula-2 + 'm2iso': ( + iso_lexemes_to_reject, + ), + # Lexemes to reject for Modula-2 R10 + 'm2r10': ( + m2r10_lexemes_to_reject, + ), + # Lexemes to reject for Objective Modula-2 + 'objm2': ( + objm2_lexemes_to_reject, + ), + # Lexemes to reject for Aglet Modula-2 + 'm2iso+aglet': ( + iso_lexemes_to_reject, + ), + # Lexemes to reject for GNU Modula-2 + 'm2pim+gm2': ( + pim_lexemes_to_reject, + ), + # Lexemes to reject for p1 Modula-2 + 'm2iso+p1': ( + iso_lexemes_to_reject, + ), + # Lexemes to reject for XDS Modula-2 + 'm2iso+xds': ( + iso_lexemes_to_reject, + ), + } + + # Reserved Words Database + reserved_words_db = { + # Reserved words for unknown dialect + 'unknown': ( + common_reserved_words, + pim_additional_reserved_words, + iso_additional_reserved_words, + m2r10_additional_reserved_words, + ), + + # Reserved words for PIM Modula-2 + 'm2pim': ( + common_reserved_words, + pim_additional_reserved_words, + ), + + # Reserved words for Modula-2 R10 + 'm2iso': ( + common_reserved_words, + iso_additional_reserved_words, + ), + + # Reserved words for ISO Modula-2 + 'm2r10': ( + common_reserved_words, + m2r10_additional_reserved_words, + ), + + # Reserved words for Objective Modula-2 + 'objm2': ( + common_reserved_words, + m2r10_additional_reserved_words, + objm2_additional_reserved_words, + ), + + # Reserved words for Aglet Modula-2 Extensions + 'm2iso+aglet': ( + common_reserved_words, + iso_additional_reserved_words, + aglet_additional_reserved_words, + ), + + # Reserved words for GNU Modula-2 Extensions + 'm2pim+gm2': ( + common_reserved_words, + pim_additional_reserved_words, + gm2_additional_reserved_words, + ), + + # Reserved words for p1 Modula-2 Extensions + 'm2iso+p1': ( + common_reserved_words, + iso_additional_reserved_words, + p1_additional_reserved_words, + ), + + # Reserved words for XDS Modula-2 Extensions + 'm2iso+xds': ( + common_reserved_words, + iso_additional_reserved_words, + xds_additional_reserved_words, + ), + } + + # Builtins Database + builtins_db = { + # Builtins for unknown dialect + 'unknown': ( + common_builtins, + pim_additional_builtins, + iso_additional_builtins, + m2r10_additional_builtins, + ), + + # Builtins for PIM Modula-2 + 'm2pim': ( + common_builtins, + pim_additional_builtins, + ), + + # Builtins for ISO Modula-2 + 'm2iso': ( + common_builtins, + iso_additional_builtins, + ), + + # Builtins for ISO Modula-2 + 'm2r10': ( + common_builtins, + m2r10_additional_builtins, + ), + + # Builtins for Objective Modula-2 + 'objm2': ( + common_builtins, + m2r10_additional_builtins, + objm2_additional_builtins, + ), + + # Builtins for Aglet Modula-2 Extensions + 'm2iso+aglet': ( + common_builtins, + iso_additional_builtins, + aglet_additional_builtins, + ), + + # Builtins for GNU Modula-2 Extensions + 'm2pim+gm2': ( + common_builtins, + pim_additional_builtins, + gm2_additional_builtins, + ), + + # Builtins for p1 Modula-2 Extensions + 'm2iso+p1': ( + common_builtins, + iso_additional_builtins, + p1_additional_builtins, + ), + + # Builtins for XDS Modula-2 Extensions + 'm2iso+xds': ( + common_builtins, + iso_additional_builtins, + xds_additional_builtins, + ), + } + + # Pseudo-Module Builtins Database + pseudo_builtins_db = { + # Builtins for unknown dialect + 'unknown': ( + common_pseudo_builtins, + pim_additional_pseudo_builtins, + iso_additional_pseudo_builtins, + m2r10_additional_pseudo_builtins, + ), + + # Builtins for PIM Modula-2 + 'm2pim': ( + common_pseudo_builtins, + pim_additional_pseudo_builtins, + ), + + # Builtins for ISO Modula-2 + 'm2iso': ( + common_pseudo_builtins, + iso_additional_pseudo_builtins, + ), + + # Builtins for ISO Modula-2 + 'm2r10': ( + common_pseudo_builtins, + m2r10_additional_pseudo_builtins, + ), + + # Builtins for Objective Modula-2 + 'objm2': ( + common_pseudo_builtins, + m2r10_additional_pseudo_builtins, + objm2_additional_pseudo_builtins, + ), + + # Builtins for Aglet Modula-2 Extensions + 'm2iso+aglet': ( + common_pseudo_builtins, + iso_additional_pseudo_builtins, + aglet_additional_pseudo_builtins, + ), + + # Builtins for GNU Modula-2 Extensions + 'm2pim+gm2': ( + common_pseudo_builtins, + pim_additional_pseudo_builtins, + gm2_additional_pseudo_builtins, + ), + + # Builtins for p1 Modula-2 Extensions + 'm2iso+p1': ( + common_pseudo_builtins, + iso_additional_pseudo_builtins, + p1_additional_pseudo_builtins, + ), + + # Builtins for XDS Modula-2 Extensions + 'm2iso+xds': ( + common_pseudo_builtins, + iso_additional_pseudo_builtins, + xds_additional_pseudo_builtins, + ), + } + + # Standard Library ADTs Database + stdlib_adts_db = { + # Empty entry for unknown dialect + 'unknown': ( + # LEAVE THIS EMPTY + ), + # Standard Library ADTs for PIM Modula-2 + 'm2pim': ( + # No first class library types + ), + + # Standard Library ADTs for ISO Modula-2 + 'm2iso': ( + # No first class library types + ), + + # Standard Library ADTs for Modula-2 R10 + 'm2r10': ( + m2r10_stdlib_adt_identifiers, + ), + + # Standard Library ADTs for Objective Modula-2 + 'objm2': ( + m2r10_stdlib_adt_identifiers, + ), + + # Standard Library ADTs for Aglet Modula-2 + 'm2iso+aglet': ( + # No first class library types + ), + + # Standard Library ADTs for GNU Modula-2 + 'm2pim+gm2': ( + # No first class library types + ), + + # Standard Library ADTs for p1 Modula-2 + 'm2iso+p1': ( + # No first class library types + ), + + # Standard Library ADTs for XDS Modula-2 + 'm2iso+xds': ( + # No first class library types + ), + } + + # Standard Library Modules Database + stdlib_modules_db = { + # Empty entry for unknown dialect + 'unknown': ( + # LEAVE THIS EMPTY + ), + # Standard Library Modules for PIM Modula-2 + 'm2pim': ( + pim_stdlib_module_identifiers, + ), + + # Standard Library Modules for ISO Modula-2 + 'm2iso': ( + iso_stdlib_module_identifiers, + ), + + # Standard Library Modules for Modula-2 R10 + 'm2r10': ( + m2r10_stdlib_blueprint_identifiers, + m2r10_stdlib_module_identifiers, + m2r10_stdlib_adt_identifiers, + ), + + # Standard Library Modules for Objective Modula-2 + 'objm2': ( + m2r10_stdlib_blueprint_identifiers, + m2r10_stdlib_module_identifiers, + ), + + # Standard Library Modules for Aglet Modula-2 + 'm2iso+aglet': ( + iso_stdlib_module_identifiers, + ), + + # Standard Library Modules for GNU Modula-2 + 'm2pim+gm2': ( + pim_stdlib_module_identifiers, + ), + + # Standard Library Modules for p1 Modula-2 + 'm2iso+p1': ( + iso_stdlib_module_identifiers, + ), + + # Standard Library Modules for XDS Modula-2 + 'm2iso+xds': ( + iso_stdlib_module_identifiers, + ), + } + + # Standard Library Types Database + stdlib_types_db = { + # Empty entry for unknown dialect + 'unknown': ( + # LEAVE THIS EMPTY + ), + # Standard Library Types for PIM Modula-2 + 'm2pim': ( + pim_stdlib_type_identifiers, + ), + + # Standard Library Types for ISO Modula-2 + 'm2iso': ( + iso_stdlib_type_identifiers, + ), + + # Standard Library Types for Modula-2 R10 + 'm2r10': ( + m2r10_stdlib_type_identifiers, + ), + + # Standard Library Types for Objective Modula-2 + 'objm2': ( + m2r10_stdlib_type_identifiers, + ), + + # Standard Library Types for Aglet Modula-2 + 'm2iso+aglet': ( + iso_stdlib_type_identifiers, + ), + + # Standard Library Types for GNU Modula-2 + 'm2pim+gm2': ( + pim_stdlib_type_identifiers, + ), + + # Standard Library Types for p1 Modula-2 + 'm2iso+p1': ( + iso_stdlib_type_identifiers, + ), + + # Standard Library Types for XDS Modula-2 + 'm2iso+xds': ( + iso_stdlib_type_identifiers, + ), + } + + # Standard Library Procedures Database + stdlib_procedures_db = { + # Empty entry for unknown dialect + 'unknown': ( + # LEAVE THIS EMPTY + ), + # Standard Library Procedures for PIM Modula-2 + 'm2pim': ( + pim_stdlib_proc_identifiers, + ), + + # Standard Library Procedures for ISO Modula-2 + 'm2iso': ( + iso_stdlib_proc_identifiers, + ), + + # Standard Library Procedures for Modula-2 R10 + 'm2r10': ( + m2r10_stdlib_proc_identifiers, + ), + + # Standard Library Procedures for Objective Modula-2 + 'objm2': ( + m2r10_stdlib_proc_identifiers, + ), + + # Standard Library Procedures for Aglet Modula-2 + 'm2iso+aglet': ( + iso_stdlib_proc_identifiers, + ), + + # Standard Library Procedures for GNU Modula-2 + 'm2pim+gm2': ( + pim_stdlib_proc_identifiers, + ), + + # Standard Library Procedures for p1 Modula-2 + 'm2iso+p1': ( + iso_stdlib_proc_identifiers, + ), + + # Standard Library Procedures for XDS Modula-2 + 'm2iso+xds': ( + iso_stdlib_proc_identifiers, + ), + } + + # Standard Library Variables Database + stdlib_variables_db = { + # Empty entry for unknown dialect + 'unknown': ( + # LEAVE THIS EMPTY + ), + # Standard Library Variables for PIM Modula-2 + 'm2pim': ( + pim_stdlib_var_identifiers, + ), + + # Standard Library Variables for ISO Modula-2 + 'm2iso': ( + iso_stdlib_var_identifiers, + ), + + # Standard Library Variables for Modula-2 R10 + 'm2r10': ( + m2r10_stdlib_var_identifiers, + ), + + # Standard Library Variables for Objective Modula-2 + 'objm2': ( + m2r10_stdlib_var_identifiers, + ), + + # Standard Library Variables for Aglet Modula-2 + 'm2iso+aglet': ( + iso_stdlib_var_identifiers, + ), + + # Standard Library Variables for GNU Modula-2 + 'm2pim+gm2': ( + pim_stdlib_var_identifiers, + ), + + # Standard Library Variables for p1 Modula-2 + 'm2iso+p1': ( + iso_stdlib_var_identifiers, + ), + + # Standard Library Variables for XDS Modula-2 + 'm2iso+xds': ( + iso_stdlib_var_identifiers, + ), + } + + # Standard Library Constants Database + stdlib_constants_db = { + # Empty entry for unknown dialect + 'unknown': ( + # LEAVE THIS EMPTY + ), + # Standard Library Constants for PIM Modula-2 + 'm2pim': ( + pim_stdlib_const_identifiers, + ), + + # Standard Library Constants for ISO Modula-2 + 'm2iso': ( + iso_stdlib_const_identifiers, + ), + + # Standard Library Constants for Modula-2 R10 + 'm2r10': ( + m2r10_stdlib_const_identifiers, + ), + + # Standard Library Constants for Objective Modula-2 + 'objm2': ( + m2r10_stdlib_const_identifiers, + ), + + # Standard Library Constants for Aglet Modula-2 + 'm2iso+aglet': ( + iso_stdlib_const_identifiers, + ), + + # Standard Library Constants for GNU Modula-2 + 'm2pim+gm2': ( + pim_stdlib_const_identifiers, + ), + + # Standard Library Constants for p1 Modula-2 + 'm2iso+p1': ( + iso_stdlib_const_identifiers, + ), + + # Standard Library Constants for XDS Modula-2 + 'm2iso+xds': ( + iso_stdlib_const_identifiers, + ), + } + +# M e t h o d s + + # initialise a lexer instance + def __init__(self, **options): + # + # check dialect options + # + dialects = get_list_opt(options, 'dialect', []) + # + for dialect_option in dialects: + if dialect_option in self.dialects[1:-1]: + # valid dialect option found + self.set_dialect(dialect_option) + break + # + # Fallback Mode (DEFAULT) + else: + # no valid dialect option + self.set_dialect('unknown') + # + self.dialect_set_by_tag = False + # + # check style options + # + styles = get_list_opt(options, 'style', []) + # + # use lowercase mode for Algol style + if 'algol' in styles or 'algol_nu' in styles: + self.algol_publication_mode = True + else: + self.algol_publication_mode = False + # + # Check option flags + # + self.treat_stdlib_adts_as_builtins = get_bool_opt( + options, 'treat_stdlib_adts_as_builtins', True) + # + # call superclass initialiser + RegexLexer.__init__(self, **options) + + # Set lexer to a specified dialect + def set_dialect(self, dialect_id): + # + # if __debug__: + # print 'entered set_dialect with arg: ', dialect_id + # + # check dialect name against known dialects + if dialect_id not in self.dialects: + dialect = 'unknown' # default + else: + dialect = dialect_id + # + # compose lexemes to reject set + lexemes_to_reject_set = set() + # add each list of reject lexemes for this dialect + for list in self.lexemes_to_reject_db[dialect]: + lexemes_to_reject_set.update(set(list)) + # + # compose reserved words set + reswords_set = set() + # add each list of reserved words for this dialect + for list in self.reserved_words_db[dialect]: + reswords_set.update(set(list)) + # + # compose builtins set + builtins_set = set() + # add each list of builtins for this dialect excluding reserved words + for list in self.builtins_db[dialect]: + builtins_set.update(set(list).difference(reswords_set)) + # + # compose pseudo-builtins set + pseudo_builtins_set = set() + # add each list of builtins for this dialect excluding reserved words + for list in self.pseudo_builtins_db[dialect]: + pseudo_builtins_set.update(set(list).difference(reswords_set)) + # + # compose ADTs set + adts_set = set() + # add each list of ADTs for this dialect excluding reserved words + for list in self.stdlib_adts_db[dialect]: + adts_set.update(set(list).difference(reswords_set)) + # + # compose modules set + modules_set = set() + # add each list of builtins for this dialect excluding builtins + for list in self.stdlib_modules_db[dialect]: + modules_set.update(set(list).difference(builtins_set)) + # + # compose types set + types_set = set() + # add each list of types for this dialect excluding builtins + for list in self.stdlib_types_db[dialect]: + types_set.update(set(list).difference(builtins_set)) + # + # compose procedures set + procedures_set = set() + # add each list of procedures for this dialect excluding builtins + for list in self.stdlib_procedures_db[dialect]: + procedures_set.update(set(list).difference(builtins_set)) + # + # compose variables set + variables_set = set() + # add each list of variables for this dialect excluding builtins + for list in self.stdlib_variables_db[dialect]: + variables_set.update(set(list).difference(builtins_set)) + # + # compose constants set + constants_set = set() + # add each list of constants for this dialect excluding builtins + for list in self.stdlib_constants_db[dialect]: + constants_set.update(set(list).difference(builtins_set)) + # + # update lexer state + self.dialect = dialect + self.lexemes_to_reject = lexemes_to_reject_set + self.reserved_words = reswords_set + self.builtins = builtins_set + self.pseudo_builtins = pseudo_builtins_set + self.adts = adts_set + self.modules = modules_set + self.types = types_set + self.procedures = procedures_set + self.variables = variables_set + self.constants = constants_set + # + # if __debug__: + # print 'exiting set_dialect' + # print ' self.dialect: ', self.dialect + # print ' self.lexemes_to_reject: ', self.lexemes_to_reject + # print ' self.reserved_words: ', self.reserved_words + # print ' self.builtins: ', self.builtins + # print ' self.pseudo_builtins: ', self.pseudo_builtins + # print ' self.adts: ', self.adts + # print ' self.modules: ', self.modules + # print ' self.types: ', self.types + # print ' self.procedures: ', self.procedures + # print ' self.variables: ', self.variables + # print ' self.types: ', self.types + # print ' self.constants: ', self.constants + + # Extracts a dialect name from a dialect tag comment string and checks + # the extracted name against known dialects. If a match is found, the + # matching name is returned, otherwise dialect id 'unknown' is returned + def get_dialect_from_dialect_tag(self, dialect_tag): + # + # if __debug__: + # print 'entered get_dialect_from_dialect_tag with arg: ', dialect_tag + # + # constants + left_tag_delim = '(*!' + right_tag_delim = '*)' + left_tag_delim_len = len(left_tag_delim) + right_tag_delim_len = len(right_tag_delim) + indicator_start = left_tag_delim_len + indicator_end = -(right_tag_delim_len) + # + # check comment string for dialect indicator + if len(dialect_tag) > (left_tag_delim_len + right_tag_delim_len) \ + and dialect_tag.startswith(left_tag_delim) \ + and dialect_tag.endswith(right_tag_delim): + # + # if __debug__: + # print 'dialect tag found' + # + # extract dialect indicator + indicator = dialect_tag[indicator_start:indicator_end] + # + # if __debug__: + # print 'extracted: ', indicator + # + # check against known dialects + for index in range(1, len(self.dialects)): + # + # if __debug__: + # print 'dialects[', index, ']: ', self.dialects[index] + # + if indicator == self.dialects[index]: + # + # if __debug__: + # print 'matching dialect found' + # + # indicator matches known dialect + return indicator + else: + # indicator does not match any dialect + return 'unknown' # default + else: + # invalid indicator string + return 'unknown' # default + + # intercept the token stream, modify token attributes and return them + def get_tokens_unprocessed(self, text): + for index, token, value in RegexLexer.get_tokens_unprocessed(self, text): + # + # check for dialect tag if dialect has not been set by tag + if not self.dialect_set_by_tag and token == Comment.Special: + indicated_dialect = self.get_dialect_from_dialect_tag(value) + if indicated_dialect != 'unknown': + # token is a dialect indicator + # reset reserved words and builtins + self.set_dialect(indicated_dialect) + self.dialect_set_by_tag = True + # + # check for reserved words, predefined and stdlib identifiers + if token is Name: + if value in self.reserved_words: + token = Keyword.Reserved + if self.algol_publication_mode: + value = value.lower() + # + elif value in self.builtins: + token = Name.Builtin + if self.algol_publication_mode: + value = value.lower() + # + elif value in self.pseudo_builtins: + token = Name.Builtin.Pseudo + if self.algol_publication_mode: + value = value.lower() + # + elif value in self.adts: + if not self.treat_stdlib_adts_as_builtins: + token = Name.Namespace + else: + token = Name.Builtin.Pseudo + if self.algol_publication_mode: + value = value.lower() + # + elif value in self.modules: + token = Name.Namespace + # + elif value in self.types: + token = Name.Class + # + elif value in self.procedures: + token = Name.Function + # + elif value in self.variables: + token = Name.Variable + # + elif value in self.constants: + token = Name.Constant + # + elif token in Number: + # + # mark prefix number literals as error for PIM and ISO dialects + if self.dialect not in ('unknown', 'm2r10', 'objm2'): + if "'" in value or value[0:2] in ('0b', '0x', '0u'): + token = Error + # + elif self.dialect in ('m2r10', 'objm2'): + # mark base-8 number literals as errors for M2 R10 and ObjM2 + if token is Number.Oct: + token = Error + # mark suffix base-16 literals as errors for M2 R10 and ObjM2 + elif token is Number.Hex and 'H' in value: + token = Error + # mark real numbers with E as errors for M2 R10 and ObjM2 + elif token is Number.Float and 'E' in value: + token = Error + # + elif token in Comment: + # + # mark single line comment as error for PIM and ISO dialects + if token is Comment.Single: + if self.dialect not in ('unknown', 'm2r10', 'objm2'): + token = Error + # + if token is Comment.Preproc: + # mark ISO pragma as error for PIM dialects + if value.startswith('<*') and \ + self.dialect.startswith('m2pim'): + token = Error + # mark PIM pragma as comment for other dialects + elif value.startswith('(*$') and \ + self.dialect != 'unknown' and \ + not self.dialect.startswith('m2pim'): + token = Comment.Multiline + # + else: # token is neither Name nor Comment + # + # mark lexemes matching the dialect's error token set as errors + if value in self.lexemes_to_reject: + token = Error + # + # substitute lexemes when in Algol mode + if self.algol_publication_mode: + if value == '#': + value = '≠' + elif value == '<=': + value = '≤' + elif value == '>=': + value = '≥' + elif value == '==': + value = '≡' + elif value == '*.': + value = '•' + + # return result + yield index, token, value + + def analyse_text(text): + """It's Pascal-like, but does not use FUNCTION -- uses PROCEDURE + instead.""" + + # Check if this looks like Pascal, if not, bail out early + if not ('(*' in text and '*)' in text and ':=' in text): + return + + result = 0 + # Procedure is in Modula2 + if re.search(r'\bPROCEDURE\b', text): + result += 0.6 + + # FUNCTION is only valid in Pascal, but not in Modula2 + if re.search(r'\bFUNCTION\b', text): + result = 0.0 + + return result diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/monte.py b/.venv/lib/python3.8/site-packages/pygments/lexers/monte.py new file mode 100644 index 0000000..4cd8324 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/monte.py @@ -0,0 +1,203 @@ +""" + pygments.lexers.monte + ~~~~~~~~~~~~~~~~~~~~~ + + Lexer for the Monte programming language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.token import Comment, Error, Keyword, Name, Number, Operator, \ + Punctuation, String, Whitespace +from pygments.lexer import RegexLexer, include, words + +__all__ = ['MonteLexer'] + + +# `var` handled separately +# `interface` handled separately +_declarations = ['bind', 'def', 'fn', 'object'] +_methods = ['method', 'to'] +_keywords = [ + 'as', 'break', 'catch', 'continue', 'else', 'escape', 'exit', 'exports', + 'extends', 'finally', 'for', 'guards', 'if', 'implements', 'import', + 'in', 'match', 'meta', 'pass', 'return', 'switch', 'try', 'via', 'when', + 'while', +] +_operators = [ + # Unary + '~', '!', + # Binary + '+', '-', '*', '/', '%', '**', '&', '|', '^', '<<', '>>', + # Binary augmented + '+=', '-=', '*=', '/=', '%=', '**=', '&=', '|=', '^=', '<<=', '>>=', + # Comparison + '==', '!=', '<', '<=', '>', '>=', '<=>', + # Patterns and assignment + ':=', '?', '=~', '!~', '=>', + # Calls and sends + '.', '<-', '->', +] +_escape_pattern = ( + r'(?:\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|' + r'\\["\'\\bftnr])') +# _char = _escape_chars + [('.', String.Char)] +_identifier = r'[_a-zA-Z]\w*' + +_constants = [ + # Void constants + 'null', + # Bool constants + 'false', 'true', + # Double constants + 'Infinity', 'NaN', + # Special objects + 'M', 'Ref', 'throw', 'traceln', +] + +_guards = [ + 'Any', 'Binding', 'Bool', 'Bytes', 'Char', 'DeepFrozen', 'Double', + 'Empty', 'Int', 'List', 'Map', 'Near', 'NullOk', 'Same', 'Selfless', + 'Set', 'Str', 'SubrangeGuard', 'Transparent', 'Void', +] + +_safeScope = [ + '_accumulateList', '_accumulateMap', '_auditedBy', '_bind', + '_booleanFlow', '_comparer', '_equalizer', '_iterForever', '_loop', + '_makeBytes', '_makeDouble', '_makeFinalSlot', '_makeInt', '_makeList', + '_makeMap', '_makeMessageDesc', '_makeOrderedSpace', '_makeParamDesc', + '_makeProtocolDesc', '_makeSourceSpan', '_makeString', '_makeVarSlot', + '_makeVerbFacet', '_mapExtract', '_matchSame', '_quasiMatcher', + '_slotToBinding', '_splitList', '_suchThat', '_switchFailed', + '_validateFor', 'b__quasiParser', 'eval', 'import', 'm__quasiParser', + 'makeBrandPair', 'makeLazySlot', 'safeScope', 'simple__quasiParser', +] + + +class MonteLexer(RegexLexer): + """ + Lexer for the `Monte `_ programming language. + + .. versionadded:: 2.2 + """ + name = 'Monte' + aliases = ['monte'] + filenames = ['*.mt'] + + tokens = { + 'root': [ + # Comments + (r'#[^\n]*\n', Comment), + + # Docstrings + # Apologies for the non-greedy matcher here. + (r'/\*\*.*?\*/', String.Doc), + + # `var` declarations + (r'\bvar\b', Keyword.Declaration, 'var'), + + # `interface` declarations + (r'\binterface\b', Keyword.Declaration, 'interface'), + + # method declarations + (words(_methods, prefix='\\b', suffix='\\b'), + Keyword, 'method'), + + # All other declarations + (words(_declarations, prefix='\\b', suffix='\\b'), + Keyword.Declaration), + + # Keywords + (words(_keywords, prefix='\\b', suffix='\\b'), Keyword), + + # Literals + ('[+-]?0x[_0-9a-fA-F]+', Number.Hex), + (r'[+-]?[_0-9]+\.[_0-9]*([eE][+-]?[_0-9]+)?', Number.Float), + ('[+-]?[_0-9]+', Number.Integer), + ("'", String.Double, 'char'), + ('"', String.Double, 'string'), + + # Quasiliterals + ('`', String.Backtick, 'ql'), + + # Operators + (words(_operators), Operator), + + # Verb operators + (_identifier + '=', Operator.Word), + + # Safe scope constants + (words(_constants, prefix='\\b', suffix='\\b'), + Keyword.Pseudo), + + # Safe scope guards + (words(_guards, prefix='\\b', suffix='\\b'), Keyword.Type), + + # All other safe scope names + (words(_safeScope, prefix='\\b', suffix='\\b'), + Name.Builtin), + + # Identifiers + (_identifier, Name), + + # Punctuation + (r'\(|\)|\{|\}|\[|\]|:|,', Punctuation), + + # Whitespace + (' +', Whitespace), + + # Definite lexer errors + ('=', Error), + ], + 'char': [ + # It is definitely an error to have a char of width == 0. + ("'", Error, 'root'), + (_escape_pattern, String.Escape, 'charEnd'), + ('.', String.Char, 'charEnd'), + ], + 'charEnd': [ + ("'", String.Char, '#pop:2'), + # It is definitely an error to have a char of width > 1. + ('.', Error), + ], + # The state of things coming into an interface. + 'interface': [ + (' +', Whitespace), + (_identifier, Name.Class, '#pop'), + include('root'), + ], + # The state of things coming into a method. + 'method': [ + (' +', Whitespace), + (_identifier, Name.Function, '#pop'), + include('root'), + ], + 'string': [ + ('"', String.Double, 'root'), + (_escape_pattern, String.Escape), + (r'\n', String.Double), + ('.', String.Double), + ], + 'ql': [ + ('`', String.Backtick, 'root'), + (r'\$' + _escape_pattern, String.Escape), + (r'\$\$', String.Escape), + (r'@@', String.Escape), + (r'\$\{', String.Interpol, 'qlNest'), + (r'@\{', String.Interpol, 'qlNest'), + (r'\$' + _identifier, Name), + ('@' + _identifier, Name), + ('.', String.Backtick), + ], + 'qlNest': [ + (r'\}', String.Interpol, '#pop'), + include('root'), + ], + # The state of things immediately following `var`. + 'var': [ + (' +', Whitespace), + (_identifier, Name.Variable, '#pop'), + include('root'), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/mosel.py b/.venv/lib/python3.8/site-packages/pygments/lexers/mosel.py new file mode 100644 index 0000000..62cb3b4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/mosel.py @@ -0,0 +1,447 @@ +""" + pygments.lexers.mosel + ~~~~~~~~~~~~~~~~~~~~~ + + Lexers for the mosel language. + http://www.fico.com/en/products/fico-xpress-optimization + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation + +__all__ = ['MoselLexer'] + +FUNCTIONS = ( + # core functions + '_', + 'abs', + 'arctan', + 'asproc', + 'assert', + 'bitflip', + 'bitneg', + 'bitset', + 'bitshift', + 'bittest', + 'bitval', + 'ceil', + 'cos', + 'create', + 'currentdate', + 'currenttime', + 'cutelt', + 'cutfirst', + 'cuthead', + 'cutlast', + 'cuttail', + 'datablock', + 'delcell', + 'exists', + 'exit', + 'exp', + 'exportprob', + 'fclose', + 'fflush', + 'finalize', + 'findfirst', + 'findlast', + 'floor', + 'fopen', + 'fselect', + 'fskipline', + 'fwrite', + 'fwrite_', + 'fwriteln', + 'fwriteln_', + 'getact', + 'getcoeff', + 'getcoeffs', + 'getdual', + 'getelt', + 'getfid', + 'getfirst', + 'getfname', + 'gethead', + 'getlast', + 'getobjval', + 'getparam', + 'getrcost', + 'getreadcnt', + 'getreverse', + 'getsize', + 'getslack', + 'getsol', + 'gettail', + 'gettype', + 'getvars', + 'isdynamic', + 'iseof', + 'isfinite', + 'ishidden', + 'isinf', + 'isnan', + 'isodd', + 'ln', + 'localsetparam', + 'log', + 'makesos1', + 'makesos2', + 'maxlist', + 'memoryuse', + 'minlist', + 'newmuid', + 'publish', + 'random', + 'read', + 'readln', + 'reset', + 'restoreparam', + 'reverse', + 'round', + 'setcoeff', + 'sethidden', + 'setioerr', + 'setmatherr', + 'setname', + 'setparam', + 'setrandseed', + 'setrange', + 'settype', + 'sin', + 'splithead', + 'splittail', + 'sqrt', + 'strfmt', + 'substr', + 'timestamp', + 'unpublish', + 'versionnum', + 'versionstr', + 'write', + 'write_', + 'writeln', + 'writeln_', + + # mosel exam mmxprs | sed -n -e "s/ [pf][a-z]* \([a-zA-Z0-9_]*\).*/'\1',/p" | sort -u + 'addcut', + 'addcuts', + 'addmipsol', + 'basisstability', + 'calcsolinfo', + 'clearmipdir', + 'clearmodcut', + 'command', + 'copysoltoinit', + 'crossoverlpsol', + 'defdelayedrows', + 'defsecurevecs', + 'delcuts', + 'dropcuts', + 'estimatemarginals', + 'fixglobal', + 'flushmsgq', + 'getbstat', + 'getcnlist', + 'getcplist', + 'getdualray', + 'getiis', + 'getiissense', + 'getiistype', + 'getinfcause', + 'getinfeas', + 'getlb', + 'getlct', + 'getleft', + 'getloadedlinctrs', + 'getloadedmpvars', + 'getname', + 'getprimalray', + 'getprobstat', + 'getrange', + 'getright', + 'getsensrng', + 'getsize', + 'getsol', + 'gettype', + 'getub', + 'getvars', + 'gety', + 'hasfeature', + 'implies', + 'indicator', + 'initglobal', + 'ishidden', + 'isiisvalid', + 'isintegral', + 'loadbasis', + 'loadcuts', + 'loadlpsol', + 'loadmipsol', + 'loadprob', + 'maximise', + 'maximize', + 'minimise', + 'minimize', + 'postsolve', + 'readbasis', + 'readdirs', + 'readsol', + 'refinemipsol', + 'rejectintsol', + 'repairinfeas', + 'repairinfeas_deprec', + 'resetbasis', + 'resetiis', + 'resetsol', + 'savebasis', + 'savemipsol', + 'savesol', + 'savestate', + 'selectsol', + 'setarchconsistency', + 'setbstat', + 'setcallback', + 'setcbcutoff', + 'setgndata', + 'sethidden', + 'setlb', + 'setmipdir', + 'setmodcut', + 'setsol', + 'setub', + 'setucbdata', + 'stopoptimise', + 'stopoptimize', + 'storecut', + 'storecuts', + 'unloadprob', + 'uselastbarsol', + 'writebasis', + 'writedirs', + 'writeprob', + 'writesol', + 'xor', + 'xprs_addctr', + 'xprs_addindic', + + # mosel exam mmsystem | sed -n -e "s/ [pf][a-z]* \([a-zA-Z0-9_]*\).*/'\1',/p" | sort -u + 'addmonths', + 'copytext', + 'cuttext', + 'deltext', + 'endswith', + 'erase', + 'expandpath', + 'fcopy', + 'fdelete', + 'findfiles', + 'findtext', + 'fmove', + 'formattext', + 'getasnumber', + 'getchar', + 'getcwd', + 'getdate', + 'getday', + 'getdaynum', + 'getdays', + 'getdirsep', + 'getdsoparam', + 'getendparse', + 'getenv', + 'getfsize', + 'getfstat', + 'getftime', + 'gethour', + 'getminute', + 'getmonth', + 'getmsec', + 'getoserrmsg', + 'getoserror', + 'getpathsep', + 'getqtype', + 'getsecond', + 'getsepchar', + 'getsize', + 'getstart', + 'getsucc', + 'getsysinfo', + 'getsysstat', + 'gettime', + 'gettmpdir', + 'gettrim', + 'getweekday', + 'getyear', + 'inserttext', + 'isvalid', + 'jointext', + 'makedir', + 'makepath', + 'newtar', + 'newzip', + 'nextfield', + 'openpipe', + 'parseextn', + 'parseint', + 'parsereal', + 'parsetext', + 'pastetext', + 'pathmatch', + 'pathsplit', + 'qsort', + 'quote', + 'readtextline', + 'regmatch', + 'regreplace', + 'removedir', + 'removefiles', + 'setchar', + 'setdate', + 'setday', + 'setdsoparam', + 'setendparse', + 'setenv', + 'sethour', + 'setminute', + 'setmonth', + 'setmsec', + 'setoserror', + 'setqtype', + 'setsecond', + 'setsepchar', + 'setstart', + 'setsucc', + 'settime', + 'settrim', + 'setyear', + 'sleep', + 'splittext', + 'startswith', + 'system', + 'tarlist', + 'textfmt', + 'tolower', + 'toupper', + 'trim', + 'untar', + 'unzip', + 'ziplist', + + # mosel exam mmjobs | sed -n -e "s/ [pf][a-z]* \([a-zA-Z0-9_]*\).*/'\1',/p" | sort -u + 'canceltimer', + 'clearaliases', + 'compile', + 'connect', + 'detach', + 'disconnect', + 'dropnextevent', + 'findxsrvs', + 'getaliases', + 'getannidents', + 'getannotations', + 'getbanner', + 'getclass', + 'getdsoprop', + 'getdsopropnum', + 'getexitcode', + 'getfromgid', + 'getfromid', + 'getfromuid', + 'getgid', + 'gethostalias', + 'getid', + 'getmodprop', + 'getmodpropnum', + 'getnextevent', + 'getnode', + 'getrmtid', + 'getstatus', + 'getsysinfo', + 'gettimer', + 'getuid', + 'getvalue', + 'isqueueempty', + 'load', + 'nullevent', + 'peeknextevent', + 'resetmodpar', + 'run', + 'send', + 'setcontrol', + 'setdefstream', + 'setgid', + 'sethostalias', + 'setmodpar', + 'settimer', + 'setuid', + 'setworkdir', + 'stop', + 'unload', + 'wait', + 'waitexpired', + 'waitfor', + 'waitforend', +) + + +class MoselLexer(RegexLexer): + """ + For the Mosel optimization language. + + .. versionadded:: 2.6 + """ + name = 'Mosel' + aliases = ['mosel'] + filenames = ['*.mos'] + + tokens = { + 'root': [ + (r'\n', Text), + (r'\s+', Text.Whitespace), + (r'!.*?\n', Comment.Single), + (r'\(!(.|\n)*?!\)', Comment.Multiline), + (words(( + 'and', 'as', 'break', 'case', 'count', 'declarations', 'do', + 'dynamic', 'elif', 'else', 'end-', 'end', 'evaluation', 'false', + 'forall', 'forward', 'from', 'function', 'hashmap', 'if', + 'imports', 'include', 'initialisations', 'initializations', 'inter', + 'max', 'min', 'model', 'namespace', 'next', 'not', 'nsgroup', + 'nssearch', 'of', 'options', 'or', 'package', 'parameters', + 'procedure', 'public', 'prod', 'record', 'repeat', 'requirements', + 'return', 'sum', 'then', 'to', 'true', 'union', 'until', 'uses', + 'version', 'while', 'with'), prefix=r'\b', suffix=r'\b'), + Keyword.Builtin), + (words(( + 'range', 'array', 'set', 'list', 'mpvar', 'mpproblem', 'linctr', + 'nlctr', 'integer', 'string', 'real', 'boolean', 'text', 'time', + 'date', 'datetime', 'returned', 'Model', 'Mosel', 'counter', + 'xmldoc', 'is_sos1', 'is_sos2', 'is_integer', 'is_binary', + 'is_continuous', 'is_free', 'is_semcont', 'is_semint', + 'is_partint'), prefix=r'\b', suffix=r'\b'), + Keyword.Type), + (r'(\+|\-|\*|/|=|<=|>=|\||\^|<|>|<>|\.\.|\.|:=|::|:|in|mod|div)', + Operator), + (r'[()\[\]{},;]+', Punctuation), + (words(FUNCTIONS, prefix=r'\b', suffix=r'\b'), Name.Function), + (r'(\d+\.(?!\.)\d*|\.(?!.)\d+)([eE][+-]?\d+)?', Number.Float), + (r'\d+([eE][+-]?\d+)?', Number.Integer), + (r'[+-]?Infinity', Number.Integer), + (r'0[xX][0-9a-fA-F]+', Number), + (r'"', String.Double, 'double_quote'), + (r'\'', String.Single, 'single_quote'), + (r'(\w+|(\.(?!\.)))', Text), + ], + 'single_quote': [ + (r'\'', String.Single, '#pop'), + (r'[^\']+', String.Single), + ], + 'double_quote': [ + (r'(\\"|\\[0-7]{1,3}\D|\\[abfnrtv]|\\\\)', String.Escape), + (r'\"', String.Double, '#pop'), + (r'[^"\\]+', String.Double), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/ncl.py b/.venv/lib/python3.8/site-packages/pygments/lexers/ncl.py new file mode 100644 index 0000000..f9df40b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/ncl.py @@ -0,0 +1,893 @@ +""" + pygments.lexers.ncl + ~~~~~~~~~~~~~~~~~~~ + + Lexers for NCAR Command Language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation + +__all__ = ['NCLLexer'] + + +class NCLLexer(RegexLexer): + """ + Lexer for NCL code. + + .. versionadded:: 2.2 + """ + name = 'NCL' + aliases = ['ncl'] + filenames = ['*.ncl'] + mimetypes = ['text/ncl'] + flags = re.MULTILINE + + tokens = { + 'root': [ + (r';.*\n', Comment), + include('strings'), + include('core'), + (r'[a-zA-Z_]\w*', Name), + include('nums'), + (r'[\s]+', Text), + ], + 'core': [ + # Statements + (words(( + 'begin', 'break', 'continue', 'create', 'defaultapp', 'do', + 'else', 'end', 'external', 'exit', 'True', 'False', 'file', 'function', + 'getvalues', 'graphic', 'group', 'if', 'list', 'load', 'local', + 'new', '_Missing', 'Missing', 'noparent', 'procedure', + 'quit', 'QUIT', 'Quit', 'record', 'return', 'setvalues', 'stop', + 'then', 'while'), prefix=r'\b', suffix=r'\s*\b'), + Keyword), + + # Data Types + (words(( + 'ubyte', 'uint', 'uint64', 'ulong', 'string', 'byte', + 'character', 'double', 'float', 'integer', 'int64', 'logical', + 'long', 'short', 'ushort', 'enumeric', 'numeric', 'snumeric'), + prefix=r'\b', suffix=r'\s*\b'), + Keyword.Type), + + # Operators + (r'[\%^*+\-/<>]', Operator), + + # punctuation: + (r'[\[\]():@$!&|.,\\{}]', Punctuation), + (r'[=:]', Punctuation), + + # Intrinsics + (words(( + 'abs', 'acos', 'addfile', 'addfiles', 'all', 'angmom_atm', 'any', + 'area_conserve_remap', 'area_hi2lores', 'area_poly_sphere', + 'asciiread', 'asciiwrite', 'asin', 'atan', 'atan2', 'attsetvalues', + 'avg', 'betainc', 'bin_avg', 'bin_sum', 'bw_bandpass_filter', + 'cancor', 'cbinread', 'cbinwrite', 'cd_calendar', 'cd_inv_calendar', + 'cdfbin_p', 'cdfbin_pr', 'cdfbin_s', 'cdfbin_xn', 'cdfchi_p', + 'cdfchi_x', 'cdfgam_p', 'cdfgam_x', 'cdfnor_p', 'cdfnor_x', + 'cdft_p', 'cdft_t', 'ceil', 'center_finite_diff', + 'center_finite_diff_n', 'cfftb', 'cfftf', 'cfftf_frq_reorder', + 'charactertodouble', 'charactertofloat', 'charactertointeger', + 'charactertolong', 'charactertoshort', 'charactertostring', + 'chartodouble', 'chartofloat', 'chartoint', 'chartointeger', + 'chartolong', 'chartoshort', 'chartostring', 'chiinv', 'clear', + 'color_index_to_rgba', 'conform', 'conform_dims', 'cos', 'cosh', + 'count_unique_values', 'covcorm', 'covcorm_xy', 'craybinnumrec', + 'craybinrecread', 'create_graphic', 'csa1', 'csa1d', 'csa1s', + 'csa1x', 'csa1xd', 'csa1xs', 'csa2', 'csa2d', 'csa2l', 'csa2ld', + 'csa2ls', 'csa2lx', 'csa2lxd', 'csa2lxs', 'csa2s', 'csa2x', + 'csa2xd', 'csa2xs', 'csa3', 'csa3d', 'csa3l', 'csa3ld', 'csa3ls', + 'csa3lx', 'csa3lxd', 'csa3lxs', 'csa3s', 'csa3x', 'csa3xd', + 'csa3xs', 'csc2s', 'csgetp', 'css2c', 'cssetp', 'cssgrid', 'csstri', + 'csvoro', 'cumsum', 'cz2ccm', 'datatondc', 'day_of_week', + 'day_of_year', 'days_in_month', 'default_fillvalue', 'delete', + 'depth_to_pres', 'destroy', 'determinant', 'dewtemp_trh', + 'dgeevx_lapack', 'dim_acumrun_n', 'dim_avg', 'dim_avg_n', + 'dim_avg_wgt', 'dim_avg_wgt_n', 'dim_cumsum', 'dim_cumsum_n', + 'dim_gamfit_n', 'dim_gbits', 'dim_max', 'dim_max_n', 'dim_median', + 'dim_median_n', 'dim_min', 'dim_min_n', 'dim_num', 'dim_num_n', + 'dim_numrun_n', 'dim_pqsort', 'dim_pqsort_n', 'dim_product', + 'dim_product_n', 'dim_rmsd', 'dim_rmsd_n', 'dim_rmvmean', + 'dim_rmvmean_n', 'dim_rmvmed', 'dim_rmvmed_n', 'dim_spi_n', + 'dim_standardize', 'dim_standardize_n', 'dim_stat4', 'dim_stat4_n', + 'dim_stddev', 'dim_stddev_n', 'dim_sum', 'dim_sum_n', 'dim_sum_wgt', + 'dim_sum_wgt_n', 'dim_variance', 'dim_variance_n', 'dimsizes', + 'doubletobyte', 'doubletochar', 'doubletocharacter', + 'doubletofloat', 'doubletoint', 'doubletointeger', 'doubletolong', + 'doubletoshort', 'dpres_hybrid_ccm', 'dpres_plevel', 'draw', + 'draw_color_palette', 'dsgetp', 'dsgrid2', 'dsgrid2d', 'dsgrid2s', + 'dsgrid3', 'dsgrid3d', 'dsgrid3s', 'dspnt2', 'dspnt2d', 'dspnt2s', + 'dspnt3', 'dspnt3d', 'dspnt3s', 'dssetp', 'dtrend', 'dtrend_msg', + 'dtrend_msg_n', 'dtrend_n', 'dtrend_quadratic', + 'dtrend_quadratic_msg_n', 'dv2uvf', 'dv2uvg', 'dz_height', + 'echo_off', 'echo_on', 'eof2data', 'eof_varimax', 'eofcor', + 'eofcor_pcmsg', 'eofcor_ts', 'eofcov', 'eofcov_pcmsg', 'eofcov_ts', + 'eofunc', 'eofunc_ts', 'eofunc_varimax', 'equiv_sample_size', 'erf', + 'erfc', 'esacr', 'esacv', 'esccr', 'esccv', 'escorc', 'escorc_n', + 'escovc', 'exit', 'exp', 'exp_tapersh', 'exp_tapersh_wgts', + 'exp_tapershC', 'ezfftb', 'ezfftb_n', 'ezfftf', 'ezfftf_n', + 'f2fosh', 'f2foshv', 'f2fsh', 'f2fshv', 'f2gsh', 'f2gshv', 'fabs', + 'fbindirread', 'fbindirwrite', 'fbinnumrec', 'fbinread', + 'fbinrecread', 'fbinrecwrite', 'fbinwrite', 'fft2db', 'fft2df', + 'fftshift', 'fileattdef', 'filechunkdimdef', 'filedimdef', + 'fileexists', 'filegrpdef', 'filevarattdef', 'filevarchunkdef', + 'filevarcompressleveldef', 'filevardef', 'filevardimsizes', + 'filwgts_lancos', 'filwgts_lanczos', 'filwgts_normal', + 'floattobyte', 'floattochar', 'floattocharacter', 'floattoint', + 'floattointeger', 'floattolong', 'floattoshort', 'floor', + 'fluxEddy', 'fo2fsh', 'fo2fshv', 'fourier_info', 'frame', 'fspan', + 'ftcurv', 'ftcurvd', 'ftcurvi', 'ftcurvp', 'ftcurvpi', 'ftcurvps', + 'ftcurvs', 'ftest', 'ftgetp', 'ftkurv', 'ftkurvd', 'ftkurvp', + 'ftkurvpd', 'ftsetp', 'ftsurf', 'g2fsh', 'g2fshv', 'g2gsh', + 'g2gshv', 'gamma', 'gammainc', 'gaus', 'gaus_lobat', + 'gaus_lobat_wgt', 'gc_aangle', 'gc_clkwise', 'gc_dangle', + 'gc_inout', 'gc_latlon', 'gc_onarc', 'gc_pnt2gc', 'gc_qarea', + 'gc_tarea', 'generate_2d_array', 'get_color_index', + 'get_color_rgba', 'get_cpu_time', 'get_isolines', 'get_ncl_version', + 'get_script_name', 'get_script_prefix_name', 'get_sphere_radius', + 'get_unique_values', 'getbitsone', 'getenv', 'getfiledimsizes', + 'getfilegrpnames', 'getfilepath', 'getfilevaratts', + 'getfilevarchunkdimsizes', 'getfilevardims', 'getfilevardimsizes', + 'getfilevarnames', 'getfilevartypes', 'getvaratts', 'getvardims', + 'gradsf', 'gradsg', 'greg2jul', 'grid2triple', 'hlsrgb', 'hsvrgb', + 'hydro', 'hyi2hyo', 'idsfft', 'igradsf', 'igradsg', 'ilapsf', + 'ilapsg', 'ilapvf', 'ilapvg', 'ind', 'ind_resolve', 'int2p', + 'int2p_n', 'integertobyte', 'integertochar', 'integertocharacter', + 'integertoshort', 'inttobyte', 'inttochar', 'inttoshort', + 'inverse_matrix', 'isatt', 'isbigendian', 'isbyte', 'ischar', + 'iscoord', 'isdefined', 'isdim', 'isdimnamed', 'isdouble', + 'isenumeric', 'isfile', 'isfilepresent', 'isfilevar', + 'isfilevaratt', 'isfilevarcoord', 'isfilevardim', 'isfloat', + 'isfunc', 'isgraphic', 'isint', 'isint64', 'isinteger', + 'isleapyear', 'islogical', 'islong', 'ismissing', 'isnan_ieee', + 'isnumeric', 'ispan', 'isproc', 'isshort', 'issnumeric', 'isstring', + 'isubyte', 'isuint', 'isuint64', 'isulong', 'isunlimited', + 'isunsigned', 'isushort', 'isvar', 'jul2greg', 'kmeans_as136', + 'kolsm2_n', 'kron_product', 'lapsf', 'lapsg', 'lapvf', 'lapvg', + 'latlon2utm', 'lclvl', 'lderuvf', 'lderuvg', 'linint1', 'linint1_n', + 'linint2', 'linint2_points', 'linmsg', 'linmsg_n', 'linrood_latwgt', + 'linrood_wgt', 'list_files', 'list_filevars', 'list_hlus', + 'list_procfuncs', 'list_vars', 'ListAppend', 'ListCount', + 'ListGetType', 'ListIndex', 'ListIndexFromName', 'ListPop', + 'ListPush', 'ListSetType', 'loadscript', 'local_max', 'local_min', + 'log', 'log10', 'longtobyte', 'longtochar', 'longtocharacter', + 'longtoint', 'longtointeger', 'longtoshort', 'lspoly', 'lspoly_n', + 'mask', 'max', 'maxind', 'min', 'minind', 'mixed_layer_depth', + 'mixhum_ptd', 'mixhum_ptrh', 'mjo_cross_coh2pha', + 'mjo_cross_segment', 'moc_globe_atl', 'monthday', 'natgrid', + 'natgridd', 'natgrids', 'ncargpath', 'ncargversion', 'ndctodata', + 'ndtooned', 'new', 'NewList', 'ngezlogo', 'nggcog', 'nggetp', + 'nglogo', 'ngsetp', 'NhlAddAnnotation', 'NhlAddData', + 'NhlAddOverlay', 'NhlAddPrimitive', 'NhlAppGetDefaultParentId', + 'NhlChangeWorkstation', 'NhlClassName', 'NhlClearWorkstation', + 'NhlDataPolygon', 'NhlDataPolyline', 'NhlDataPolymarker', + 'NhlDataToNDC', 'NhlDestroy', 'NhlDraw', 'NhlFrame', 'NhlFreeColor', + 'NhlGetBB', 'NhlGetClassResources', 'NhlGetErrorObjectId', + 'NhlGetNamedColorIndex', 'NhlGetParentId', + 'NhlGetParentWorkstation', 'NhlGetWorkspaceObjectId', + 'NhlIsAllocatedColor', 'NhlIsApp', 'NhlIsDataComm', 'NhlIsDataItem', + 'NhlIsDataSpec', 'NhlIsTransform', 'NhlIsView', 'NhlIsWorkstation', + 'NhlName', 'NhlNDCPolygon', 'NhlNDCPolyline', 'NhlNDCPolymarker', + 'NhlNDCToData', 'NhlNewColor', 'NhlNewDashPattern', 'NhlNewMarker', + 'NhlPalGetDefined', 'NhlRemoveAnnotation', 'NhlRemoveData', + 'NhlRemoveOverlay', 'NhlRemovePrimitive', 'NhlSetColor', + 'NhlSetDashPattern', 'NhlSetMarker', 'NhlUpdateData', + 'NhlUpdateWorkstation', 'nice_mnmxintvl', 'nngetaspectd', + 'nngetaspects', 'nngetp', 'nngetsloped', 'nngetslopes', 'nngetwts', + 'nngetwtsd', 'nnpnt', 'nnpntd', 'nnpntend', 'nnpntendd', + 'nnpntinit', 'nnpntinitd', 'nnpntinits', 'nnpnts', 'nnsetp', 'num', + 'obj_anal_ic', 'omega_ccm', 'onedtond', 'overlay', 'paleo_outline', + 'pdfxy_bin', 'poisson_grid_fill', 'pop_remap', 'potmp_insitu_ocn', + 'prcwater_dp', 'pres2hybrid', 'pres_hybrid_ccm', 'pres_sigma', + 'print', 'print_table', 'printFileVarSummary', 'printVarSummary', + 'product', 'pslec', 'pslhor', 'pslhyp', 'qsort', 'rand', + 'random_chi', 'random_gamma', 'random_normal', 'random_setallseed', + 'random_uniform', 'rcm2points', 'rcm2rgrid', 'rdsstoi', + 'read_colormap_file', 'reg_multlin', 'regcoef', 'regCoef_n', + 'regline', 'relhum', 'replace_ieeenan', 'reshape', 'reshape_ind', + 'rgba_to_color_index', 'rgbhls', 'rgbhsv', 'rgbyiq', 'rgrid2rcm', + 'rhomb_trunc', 'rip_cape_2d', 'rip_cape_3d', 'round', 'rtest', + 'runave', 'runave_n', 'set_default_fillvalue', 'set_sphere_radius', + 'setfileoption', 'sfvp2uvf', 'sfvp2uvg', 'shaec', 'shagc', + 'shgetnp', 'shgetp', 'shgrid', 'shorttobyte', 'shorttochar', + 'shorttocharacter', 'show_ascii', 'shsec', 'shsetp', 'shsgc', + 'shsgc_R42', 'sigma2hybrid', 'simpeq', 'simpne', 'sin', + 'sindex_yrmo', 'sinh', 'sizeof', 'sleep', 'smth9', 'snindex_yrmo', + 'solve_linsys', 'span_color_indexes', 'span_color_rgba', + 'sparse_matrix_mult', 'spcorr', 'spcorr_n', 'specx_anal', + 'specxy_anal', 'spei', 'sprintf', 'sprinti', 'sqrt', 'sqsort', + 'srand', 'stat2', 'stat4', 'stat_medrng', 'stat_trim', + 'status_exit', 'stdatmus_p2tdz', 'stdatmus_z2tdp', 'stddev', + 'str_capital', 'str_concat', 'str_fields_count', 'str_get_cols', + 'str_get_dq', 'str_get_field', 'str_get_nl', 'str_get_sq', + 'str_get_tab', 'str_index_of_substr', 'str_insert', 'str_is_blank', + 'str_join', 'str_left_strip', 'str_lower', 'str_match', + 'str_match_ic', 'str_match_ic_regex', 'str_match_ind', + 'str_match_ind_ic', 'str_match_ind_ic_regex', 'str_match_ind_regex', + 'str_match_regex', 'str_right_strip', 'str_split', + 'str_split_by_length', 'str_split_csv', 'str_squeeze', 'str_strip', + 'str_sub_str', 'str_switch', 'str_upper', 'stringtochar', + 'stringtocharacter', 'stringtodouble', 'stringtofloat', + 'stringtoint', 'stringtointeger', 'stringtolong', 'stringtoshort', + 'strlen', 'student_t', 'sum', 'svd_lapack', 'svdcov', 'svdcov_sv', + 'svdstd', 'svdstd_sv', 'system', 'systemfunc', 'tan', 'tanh', + 'taper', 'taper_n', 'tdclrs', 'tdctri', 'tdcudp', 'tdcurv', + 'tddtri', 'tdez2d', 'tdez3d', 'tdgetp', 'tdgrds', 'tdgrid', + 'tdgtrs', 'tdinit', 'tditri', 'tdlbla', 'tdlblp', 'tdlbls', + 'tdline', 'tdlndp', 'tdlnpa', 'tdlpdp', 'tdmtri', 'tdotri', + 'tdpara', 'tdplch', 'tdprpa', 'tdprpi', 'tdprpt', 'tdsetp', + 'tdsort', 'tdstri', 'tdstrs', 'tdttri', 'thornthwaite', 'tobyte', + 'tochar', 'todouble', 'tofloat', 'toint', 'toint64', 'tointeger', + 'tolong', 'toshort', 'tosigned', 'tostring', 'tostring_with_format', + 'totype', 'toubyte', 'touint', 'touint64', 'toulong', 'tounsigned', + 'toushort', 'trend_manken', 'tri_trunc', 'triple2grid', + 'triple2grid2d', 'trop_wmo', 'ttest', 'typeof', 'undef', + 'unique_string', 'update', 'ushorttoint', 'ut_calendar', + 'ut_inv_calendar', 'utm2latlon', 'uv2dv_cfd', 'uv2dvf', 'uv2dvg', + 'uv2sfvpf', 'uv2sfvpg', 'uv2vr_cfd', 'uv2vrdvf', 'uv2vrdvg', + 'uv2vrf', 'uv2vrg', 'v5d_close', 'v5d_create', 'v5d_setLowLev', + 'v5d_setUnits', 'v5d_write', 'v5d_write_var', 'variance', 'vhaec', + 'vhagc', 'vhsec', 'vhsgc', 'vibeta', 'vinth2p', 'vinth2p_ecmwf', + 'vinth2p_ecmwf_nodes', 'vinth2p_nodes', 'vintp2p_ecmwf', 'vr2uvf', + 'vr2uvg', 'vrdv2uvf', 'vrdv2uvg', 'wavelet', 'wavelet_default', + 'weibull', 'wgt_area_smooth', 'wgt_areaave', 'wgt_areaave2', + 'wgt_arearmse', 'wgt_arearmse2', 'wgt_areasum2', 'wgt_runave', + 'wgt_runave_n', 'wgt_vert_avg_beta', 'wgt_volave', 'wgt_volave_ccm', + 'wgt_volrmse', 'wgt_volrmse_ccm', 'where', 'wk_smooth121', 'wmbarb', + 'wmbarbmap', 'wmdrft', 'wmgetp', 'wmlabs', 'wmsetp', 'wmstnm', + 'wmvect', 'wmvectmap', 'wmvlbl', 'wrf_avo', 'wrf_cape_2d', + 'wrf_cape_3d', 'wrf_dbz', 'wrf_eth', 'wrf_helicity', 'wrf_ij_to_ll', + 'wrf_interp_1d', 'wrf_interp_2d_xy', 'wrf_interp_3d_z', + 'wrf_latlon_to_ij', 'wrf_ll_to_ij', 'wrf_omega', 'wrf_pvo', + 'wrf_rh', 'wrf_slp', 'wrf_smooth_2d', 'wrf_td', 'wrf_tk', + 'wrf_updraft_helicity', 'wrf_uvmet', 'wrf_virtual_temp', + 'wrf_wetbulb', 'wrf_wps_close_int', 'wrf_wps_open_int', + 'wrf_wps_rddata_int', 'wrf_wps_rdhead_int', 'wrf_wps_read_int', + 'wrf_wps_write_int', 'write_matrix', 'write_table', 'yiqrgb', + 'z2geouv', 'zonal_mpsi', 'addfiles_GetVar', 'advect_variable', + 'area_conserve_remap_Wrap', 'area_hi2lores_Wrap', + 'array_append_record', 'assignFillValue', 'byte2flt', + 'byte2flt_hdf', 'calcDayAnomTLL', 'calcMonAnomLLLT', + 'calcMonAnomLLT', 'calcMonAnomTLL', 'calcMonAnomTLLL', + 'calculate_monthly_values', 'cd_convert', 'changeCase', + 'changeCaseChar', 'clmDayTLL', 'clmDayTLLL', 'clmMon2clmDay', + 'clmMonLLLT', 'clmMonLLT', 'clmMonTLL', 'clmMonTLLL', 'closest_val', + 'copy_VarAtts', 'copy_VarCoords', 'copy_VarCoords_1', + 'copy_VarCoords_2', 'copy_VarMeta', 'copyatt', 'crossp3', + 'cshstringtolist', 'cssgrid_Wrap', 'dble2flt', 'decimalPlaces', + 'delete_VarAtts', 'dim_avg_n_Wrap', 'dim_avg_wgt_n_Wrap', + 'dim_avg_wgt_Wrap', 'dim_avg_Wrap', 'dim_cumsum_n_Wrap', + 'dim_cumsum_Wrap', 'dim_max_n_Wrap', 'dim_min_n_Wrap', + 'dim_rmsd_n_Wrap', 'dim_rmsd_Wrap', 'dim_rmvmean_n_Wrap', + 'dim_rmvmean_Wrap', 'dim_rmvmed_n_Wrap', 'dim_rmvmed_Wrap', + 'dim_standardize_n_Wrap', 'dim_standardize_Wrap', + 'dim_stddev_n_Wrap', 'dim_stddev_Wrap', 'dim_sum_n_Wrap', + 'dim_sum_wgt_n_Wrap', 'dim_sum_wgt_Wrap', 'dim_sum_Wrap', + 'dim_variance_n_Wrap', 'dim_variance_Wrap', 'dpres_plevel_Wrap', + 'dtrend_leftdim', 'dv2uvF_Wrap', 'dv2uvG_Wrap', 'eof_north', + 'eofcor_Wrap', 'eofcov_Wrap', 'eofunc_north', 'eofunc_ts_Wrap', + 'eofunc_varimax_reorder', 'eofunc_varimax_Wrap', 'eofunc_Wrap', + 'epsZero', 'f2fosh_Wrap', 'f2foshv_Wrap', 'f2fsh_Wrap', + 'f2fshv_Wrap', 'f2gsh_Wrap', 'f2gshv_Wrap', 'fbindirSwap', + 'fbinseqSwap1', 'fbinseqSwap2', 'flt2dble', 'flt2string', + 'fo2fsh_Wrap', 'fo2fshv_Wrap', 'g2fsh_Wrap', 'g2fshv_Wrap', + 'g2gsh_Wrap', 'g2gshv_Wrap', 'generate_resample_indices', + 'generate_sample_indices', 'generate_unique_indices', + 'genNormalDist', 'get1Dindex', 'get1Dindex_Collapse', + 'get1Dindex_Exclude', 'get_file_suffix', 'GetFillColor', + 'GetFillColorIndex', 'getFillValue', 'getind_latlon2d', + 'getVarDimNames', 'getVarFillValue', 'grib_stime2itime', + 'hyi2hyo_Wrap', 'ilapsF_Wrap', 'ilapsG_Wrap', 'ind_nearest_coord', + 'indStrSubset', 'int2dble', 'int2flt', 'int2p_n_Wrap', 'int2p_Wrap', + 'isMonotonic', 'isStrSubset', 'latGau', 'latGauWgt', 'latGlobeF', + 'latGlobeFo', 'latRegWgt', 'linint1_n_Wrap', 'linint1_Wrap', + 'linint2_points_Wrap', 'linint2_Wrap', 'local_max_1d', + 'local_min_1d', 'lonFlip', 'lonGlobeF', 'lonGlobeFo', 'lonPivot', + 'merge_levels_sfc', 'mod', 'month_to_annual', + 'month_to_annual_weighted', 'month_to_season', 'month_to_season12', + 'month_to_seasonN', 'monthly_total_to_daily_mean', 'nameDim', + 'natgrid_Wrap', 'NewCosWeight', 'niceLatLon2D', 'NormCosWgtGlobe', + 'numAsciiCol', 'numAsciiRow', 'numeric2int', + 'obj_anal_ic_deprecated', 'obj_anal_ic_Wrap', 'omega_ccm_driver', + 'omega_to_w', 'oneDtostring', 'pack_values', 'pattern_cor', 'pdfx', + 'pdfxy', 'pdfxy_conform', 'pot_temp', 'pot_vort_hybrid', + 'pot_vort_isobaric', 'pres2hybrid_Wrap', 'print_clock', + 'printMinMax', 'quadroots', 'rcm2points_Wrap', 'rcm2rgrid_Wrap', + 'readAsciiHead', 'readAsciiTable', 'reg_multlin_stats', + 'region_ind', 'regline_stats', 'relhum_ttd', 'replaceSingleChar', + 'RGBtoCmap', 'rgrid2rcm_Wrap', 'rho_mwjf', 'rm_single_dims', + 'rmAnnCycle1D', 'rmInsufData', 'rmMonAnnCycLLLT', 'rmMonAnnCycLLT', + 'rmMonAnnCycTLL', 'runave_n_Wrap', 'runave_Wrap', 'short2flt', + 'short2flt_hdf', 'shsgc_R42_Wrap', 'sign_f90', 'sign_matlab', + 'smth9_Wrap', 'smthClmDayTLL', 'smthClmDayTLLL', 'SqrtCosWeight', + 'stat_dispersion', 'static_stability', 'stdMonLLLT', 'stdMonLLT', + 'stdMonTLL', 'stdMonTLLL', 'symMinMaxPlt', 'table_attach_columns', + 'table_attach_rows', 'time_to_newtime', 'transpose', + 'triple2grid_Wrap', 'ut_convert', 'uv2dvF_Wrap', 'uv2dvG_Wrap', + 'uv2vrF_Wrap', 'uv2vrG_Wrap', 'vr2uvF_Wrap', 'vr2uvG_Wrap', + 'w_to_omega', 'wallClockElapseTime', 'wave_number_spc', + 'wgt_areaave_Wrap', 'wgt_runave_leftdim', 'wgt_runave_n_Wrap', + 'wgt_runave_Wrap', 'wgt_vertical_n', 'wind_component', + 'wind_direction', 'yyyyddd_to_yyyymmdd', 'yyyymm_time', + 'yyyymm_to_yyyyfrac', 'yyyymmdd_time', 'yyyymmdd_to_yyyyddd', + 'yyyymmdd_to_yyyyfrac', 'yyyymmddhh_time', 'yyyymmddhh_to_yyyyfrac', + 'zonal_mpsi_Wrap', 'zonalAve', 'calendar_decode2', 'cd_string', + 'kf_filter', 'run_cor', 'time_axis_labels', 'ut_string', + 'wrf_contour', 'wrf_map', 'wrf_map_overlay', 'wrf_map_overlays', + 'wrf_map_resources', 'wrf_map_zoom', 'wrf_overlay', 'wrf_overlays', + 'wrf_user_getvar', 'wrf_user_ij_to_ll', 'wrf_user_intrp2d', + 'wrf_user_intrp3d', 'wrf_user_latlon_to_ij', 'wrf_user_list_times', + 'wrf_user_ll_to_ij', 'wrf_user_unstagger', 'wrf_user_vert_interp', + 'wrf_vector', 'gsn_add_annotation', 'gsn_add_polygon', + 'gsn_add_polyline', 'gsn_add_polymarker', + 'gsn_add_shapefile_polygons', 'gsn_add_shapefile_polylines', + 'gsn_add_shapefile_polymarkers', 'gsn_add_text', 'gsn_attach_plots', + 'gsn_blank_plot', 'gsn_contour', 'gsn_contour_map', + 'gsn_contour_shade', 'gsn_coordinates', 'gsn_create_labelbar', + 'gsn_create_legend', 'gsn_create_text', + 'gsn_csm_attach_zonal_means', 'gsn_csm_blank_plot', + 'gsn_csm_contour', 'gsn_csm_contour_map', 'gsn_csm_contour_map_ce', + 'gsn_csm_contour_map_overlay', 'gsn_csm_contour_map_polar', + 'gsn_csm_hov', 'gsn_csm_lat_time', 'gsn_csm_map', 'gsn_csm_map_ce', + 'gsn_csm_map_polar', 'gsn_csm_pres_hgt', + 'gsn_csm_pres_hgt_streamline', 'gsn_csm_pres_hgt_vector', + 'gsn_csm_streamline', 'gsn_csm_streamline_contour_map', + 'gsn_csm_streamline_contour_map_ce', + 'gsn_csm_streamline_contour_map_polar', 'gsn_csm_streamline_map', + 'gsn_csm_streamline_map_ce', 'gsn_csm_streamline_map_polar', + 'gsn_csm_streamline_scalar', 'gsn_csm_streamline_scalar_map', + 'gsn_csm_streamline_scalar_map_ce', + 'gsn_csm_streamline_scalar_map_polar', 'gsn_csm_time_lat', + 'gsn_csm_vector', 'gsn_csm_vector_map', 'gsn_csm_vector_map_ce', + 'gsn_csm_vector_map_polar', 'gsn_csm_vector_scalar', + 'gsn_csm_vector_scalar_map', 'gsn_csm_vector_scalar_map_ce', + 'gsn_csm_vector_scalar_map_polar', 'gsn_csm_x2y', 'gsn_csm_x2y2', + 'gsn_csm_xy', 'gsn_csm_xy2', 'gsn_csm_xy3', 'gsn_csm_y', + 'gsn_define_colormap', 'gsn_draw_colormap', 'gsn_draw_named_colors', + 'gsn_histogram', 'gsn_labelbar_ndc', 'gsn_legend_ndc', 'gsn_map', + 'gsn_merge_colormaps', 'gsn_open_wks', 'gsn_panel', 'gsn_polygon', + 'gsn_polygon_ndc', 'gsn_polyline', 'gsn_polyline_ndc', + 'gsn_polymarker', 'gsn_polymarker_ndc', 'gsn_retrieve_colormap', + 'gsn_reverse_colormap', 'gsn_streamline', 'gsn_streamline_map', + 'gsn_streamline_scalar', 'gsn_streamline_scalar_map', 'gsn_table', + 'gsn_text', 'gsn_text_ndc', 'gsn_vector', 'gsn_vector_map', + 'gsn_vector_scalar', 'gsn_vector_scalar_map', 'gsn_xy', 'gsn_y', + 'hsv2rgb', 'maximize_output', 'namedcolor2rgb', 'namedcolor2rgba', + 'reset_device_coordinates', 'span_named_colors'), prefix=r'\b'), + Name.Builtin), + + # Resources + (words(( + 'amDataXF', 'amDataYF', 'amJust', 'amOn', 'amOrthogonalPosF', + 'amParallelPosF', 'amResizeNotify', 'amSide', 'amTrackData', + 'amViewId', 'amZone', 'appDefaultParent', 'appFileSuffix', + 'appResources', 'appSysDir', 'appUsrDir', 'caCopyArrays', + 'caXArray', 'caXCast', 'caXMaxV', 'caXMinV', 'caXMissingV', + 'caYArray', 'caYCast', 'caYMaxV', 'caYMinV', 'caYMissingV', + 'cnCellFillEdgeColor', 'cnCellFillMissingValEdgeColor', + 'cnConpackParams', 'cnConstFEnableFill', 'cnConstFLabelAngleF', + 'cnConstFLabelBackgroundColor', 'cnConstFLabelConstantSpacingF', + 'cnConstFLabelFont', 'cnConstFLabelFontAspectF', + 'cnConstFLabelFontColor', 'cnConstFLabelFontHeightF', + 'cnConstFLabelFontQuality', 'cnConstFLabelFontThicknessF', + 'cnConstFLabelFormat', 'cnConstFLabelFuncCode', 'cnConstFLabelJust', + 'cnConstFLabelOn', 'cnConstFLabelOrthogonalPosF', + 'cnConstFLabelParallelPosF', 'cnConstFLabelPerimColor', + 'cnConstFLabelPerimOn', 'cnConstFLabelPerimSpaceF', + 'cnConstFLabelPerimThicknessF', 'cnConstFLabelSide', + 'cnConstFLabelString', 'cnConstFLabelTextDirection', + 'cnConstFLabelZone', 'cnConstFUseInfoLabelRes', + 'cnExplicitLabelBarLabelsOn', 'cnExplicitLegendLabelsOn', + 'cnExplicitLineLabelsOn', 'cnFillBackgroundColor', 'cnFillColor', + 'cnFillColors', 'cnFillDotSizeF', 'cnFillDrawOrder', 'cnFillMode', + 'cnFillOn', 'cnFillOpacityF', 'cnFillPalette', 'cnFillPattern', + 'cnFillPatterns', 'cnFillScaleF', 'cnFillScales', 'cnFixFillBleed', + 'cnGridBoundFillColor', 'cnGridBoundFillPattern', + 'cnGridBoundFillScaleF', 'cnGridBoundPerimColor', + 'cnGridBoundPerimDashPattern', 'cnGridBoundPerimOn', + 'cnGridBoundPerimThicknessF', 'cnHighLabelAngleF', + 'cnHighLabelBackgroundColor', 'cnHighLabelConstantSpacingF', + 'cnHighLabelCount', 'cnHighLabelFont', 'cnHighLabelFontAspectF', + 'cnHighLabelFontColor', 'cnHighLabelFontHeightF', + 'cnHighLabelFontQuality', 'cnHighLabelFontThicknessF', + 'cnHighLabelFormat', 'cnHighLabelFuncCode', 'cnHighLabelPerimColor', + 'cnHighLabelPerimOn', 'cnHighLabelPerimSpaceF', + 'cnHighLabelPerimThicknessF', 'cnHighLabelString', 'cnHighLabelsOn', + 'cnHighLowLabelOverlapMode', 'cnHighUseLineLabelRes', + 'cnInfoLabelAngleF', 'cnInfoLabelBackgroundColor', + 'cnInfoLabelConstantSpacingF', 'cnInfoLabelFont', + 'cnInfoLabelFontAspectF', 'cnInfoLabelFontColor', + 'cnInfoLabelFontHeightF', 'cnInfoLabelFontQuality', + 'cnInfoLabelFontThicknessF', 'cnInfoLabelFormat', + 'cnInfoLabelFuncCode', 'cnInfoLabelJust', 'cnInfoLabelOn', + 'cnInfoLabelOrthogonalPosF', 'cnInfoLabelParallelPosF', + 'cnInfoLabelPerimColor', 'cnInfoLabelPerimOn', + 'cnInfoLabelPerimSpaceF', 'cnInfoLabelPerimThicknessF', + 'cnInfoLabelSide', 'cnInfoLabelString', 'cnInfoLabelTextDirection', + 'cnInfoLabelZone', 'cnLabelBarEndLabelsOn', 'cnLabelBarEndStyle', + 'cnLabelDrawOrder', 'cnLabelMasking', 'cnLabelScaleFactorF', + 'cnLabelScaleValueF', 'cnLabelScalingMode', 'cnLegendLevelFlags', + 'cnLevelCount', 'cnLevelFlag', 'cnLevelFlags', 'cnLevelSelectionMode', + 'cnLevelSpacingF', 'cnLevels', 'cnLineColor', 'cnLineColors', + 'cnLineDashPattern', 'cnLineDashPatterns', 'cnLineDashSegLenF', + 'cnLineDrawOrder', 'cnLineLabelAngleF', 'cnLineLabelBackgroundColor', + 'cnLineLabelConstantSpacingF', 'cnLineLabelCount', + 'cnLineLabelDensityF', 'cnLineLabelFont', 'cnLineLabelFontAspectF', + 'cnLineLabelFontColor', 'cnLineLabelFontColors', + 'cnLineLabelFontHeightF', 'cnLineLabelFontQuality', + 'cnLineLabelFontThicknessF', 'cnLineLabelFormat', + 'cnLineLabelFuncCode', 'cnLineLabelInterval', 'cnLineLabelPerimColor', + 'cnLineLabelPerimOn', 'cnLineLabelPerimSpaceF', + 'cnLineLabelPerimThicknessF', 'cnLineLabelPlacementMode', + 'cnLineLabelStrings', 'cnLineLabelsOn', 'cnLinePalette', + 'cnLineThicknessF', 'cnLineThicknesses', 'cnLinesOn', + 'cnLowLabelAngleF', 'cnLowLabelBackgroundColor', + 'cnLowLabelConstantSpacingF', 'cnLowLabelCount', 'cnLowLabelFont', + 'cnLowLabelFontAspectF', 'cnLowLabelFontColor', + 'cnLowLabelFontHeightF', 'cnLowLabelFontQuality', + 'cnLowLabelFontThicknessF', 'cnLowLabelFormat', 'cnLowLabelFuncCode', + 'cnLowLabelPerimColor', 'cnLowLabelPerimOn', 'cnLowLabelPerimSpaceF', + 'cnLowLabelPerimThicknessF', 'cnLowLabelString', 'cnLowLabelsOn', + 'cnLowUseHighLabelRes', 'cnMaxDataValueFormat', 'cnMaxLevelCount', + 'cnMaxLevelValF', 'cnMaxPointDistanceF', 'cnMinLevelValF', + 'cnMissingValFillColor', 'cnMissingValFillPattern', + 'cnMissingValFillScaleF', 'cnMissingValPerimColor', + 'cnMissingValPerimDashPattern', 'cnMissingValPerimGridBoundOn', + 'cnMissingValPerimOn', 'cnMissingValPerimThicknessF', + 'cnMonoFillColor', 'cnMonoFillPattern', 'cnMonoFillScale', + 'cnMonoLevelFlag', 'cnMonoLineColor', 'cnMonoLineDashPattern', + 'cnMonoLineLabelFontColor', 'cnMonoLineThickness', 'cnNoDataLabelOn', + 'cnNoDataLabelString', 'cnOutOfRangeFillColor', + 'cnOutOfRangeFillPattern', 'cnOutOfRangeFillScaleF', + 'cnOutOfRangePerimColor', 'cnOutOfRangePerimDashPattern', + 'cnOutOfRangePerimOn', 'cnOutOfRangePerimThicknessF', + 'cnRasterCellSizeF', 'cnRasterMinCellSizeF', 'cnRasterModeOn', + 'cnRasterSampleFactorF', 'cnRasterSmoothingOn', 'cnScalarFieldData', + 'cnSmoothingDistanceF', 'cnSmoothingOn', 'cnSmoothingTensionF', + 'cnSpanFillPalette', 'cnSpanLinePalette', 'ctCopyTables', + 'ctXElementSize', 'ctXMaxV', 'ctXMinV', 'ctXMissingV', 'ctXTable', + 'ctXTableLengths', 'ctXTableType', 'ctYElementSize', 'ctYMaxV', + 'ctYMinV', 'ctYMissingV', 'ctYTable', 'ctYTableLengths', + 'ctYTableType', 'dcDelayCompute', 'errBuffer', + 'errFileName', 'errFilePtr', 'errLevel', 'errPrint', 'errUnitNumber', + 'gsClipOn', 'gsColors', 'gsEdgeColor', 'gsEdgeDashPattern', + 'gsEdgeDashSegLenF', 'gsEdgeThicknessF', 'gsEdgesOn', + 'gsFillBackgroundColor', 'gsFillColor', 'gsFillDotSizeF', + 'gsFillIndex', 'gsFillLineThicknessF', 'gsFillOpacityF', + 'gsFillScaleF', 'gsFont', 'gsFontAspectF', 'gsFontColor', + 'gsFontHeightF', 'gsFontOpacityF', 'gsFontQuality', + 'gsFontThicknessF', 'gsLineColor', 'gsLineDashPattern', + 'gsLineDashSegLenF', 'gsLineLabelConstantSpacingF', 'gsLineLabelFont', + 'gsLineLabelFontAspectF', 'gsLineLabelFontColor', + 'gsLineLabelFontHeightF', 'gsLineLabelFontQuality', + 'gsLineLabelFontThicknessF', 'gsLineLabelFuncCode', + 'gsLineLabelString', 'gsLineOpacityF', 'gsLineThicknessF', + 'gsMarkerColor', 'gsMarkerIndex', 'gsMarkerOpacityF', 'gsMarkerSizeF', + 'gsMarkerThicknessF', 'gsSegments', 'gsTextAngleF', + 'gsTextConstantSpacingF', 'gsTextDirection', 'gsTextFuncCode', + 'gsTextJustification', 'gsnAboveYRefLineBarColors', + 'gsnAboveYRefLineBarFillScales', 'gsnAboveYRefLineBarPatterns', + 'gsnAboveYRefLineColor', 'gsnAddCyclic', 'gsnAttachBorderOn', + 'gsnAttachPlotsXAxis', 'gsnBelowYRefLineBarColors', + 'gsnBelowYRefLineBarFillScales', 'gsnBelowYRefLineBarPatterns', + 'gsnBelowYRefLineColor', 'gsnBoxMargin', 'gsnCenterString', + 'gsnCenterStringFontColor', 'gsnCenterStringFontHeightF', + 'gsnCenterStringFuncCode', 'gsnCenterStringOrthogonalPosF', + 'gsnCenterStringParallelPosF', 'gsnContourLineThicknessesScale', + 'gsnContourNegLineDashPattern', 'gsnContourPosLineDashPattern', + 'gsnContourZeroLineThicknessF', 'gsnDebugWriteFileName', 'gsnDraw', + 'gsnFrame', 'gsnHistogramBarWidthPercent', 'gsnHistogramBinIntervals', + 'gsnHistogramBinMissing', 'gsnHistogramBinWidth', + 'gsnHistogramClassIntervals', 'gsnHistogramCompare', + 'gsnHistogramComputePercentages', + 'gsnHistogramComputePercentagesNoMissing', + 'gsnHistogramDiscreteBinValues', 'gsnHistogramDiscreteClassValues', + 'gsnHistogramHorizontal', 'gsnHistogramMinMaxBinsOn', + 'gsnHistogramNumberOfBins', 'gsnHistogramPercentSign', + 'gsnHistogramSelectNiceIntervals', 'gsnLeftString', + 'gsnLeftStringFontColor', 'gsnLeftStringFontHeightF', + 'gsnLeftStringFuncCode', 'gsnLeftStringOrthogonalPosF', + 'gsnLeftStringParallelPosF', 'gsnMajorLatSpacing', + 'gsnMajorLonSpacing', 'gsnMaskLambertConformal', + 'gsnMaskLambertConformalOutlineOn', 'gsnMaximize', + 'gsnMinorLatSpacing', 'gsnMinorLonSpacing', 'gsnPanelBottom', + 'gsnPanelCenter', 'gsnPanelDebug', 'gsnPanelFigureStrings', + 'gsnPanelFigureStringsBackgroundFillColor', + 'gsnPanelFigureStringsFontHeightF', 'gsnPanelFigureStringsJust', + 'gsnPanelFigureStringsPerimOn', 'gsnPanelLabelBar', 'gsnPanelLeft', + 'gsnPanelMainFont', 'gsnPanelMainFontColor', + 'gsnPanelMainFontHeightF', 'gsnPanelMainString', 'gsnPanelRight', + 'gsnPanelRowSpec', 'gsnPanelScalePlotIndex', 'gsnPanelTop', + 'gsnPanelXF', 'gsnPanelXWhiteSpacePercent', 'gsnPanelYF', + 'gsnPanelYWhiteSpacePercent', 'gsnPaperHeight', 'gsnPaperMargin', + 'gsnPaperOrientation', 'gsnPaperWidth', 'gsnPolar', + 'gsnPolarLabelDistance', 'gsnPolarLabelFont', + 'gsnPolarLabelFontHeightF', 'gsnPolarLabelSpacing', 'gsnPolarTime', + 'gsnPolarUT', 'gsnRightString', 'gsnRightStringFontColor', + 'gsnRightStringFontHeightF', 'gsnRightStringFuncCode', + 'gsnRightStringOrthogonalPosF', 'gsnRightStringParallelPosF', + 'gsnScalarContour', 'gsnScale', 'gsnShape', 'gsnSpreadColorEnd', + 'gsnSpreadColorStart', 'gsnSpreadColors', 'gsnStringFont', + 'gsnStringFontColor', 'gsnStringFontHeightF', 'gsnStringFuncCode', + 'gsnTickMarksOn', 'gsnXAxisIrregular2Linear', 'gsnXAxisIrregular2Log', + 'gsnXRefLine', 'gsnXRefLineColor', 'gsnXRefLineDashPattern', + 'gsnXRefLineThicknessF', 'gsnXYAboveFillColors', 'gsnXYBarChart', + 'gsnXYBarChartBarWidth', 'gsnXYBarChartColors', + 'gsnXYBarChartColors2', 'gsnXYBarChartFillDotSizeF', + 'gsnXYBarChartFillLineThicknessF', 'gsnXYBarChartFillOpacityF', + 'gsnXYBarChartFillScaleF', 'gsnXYBarChartOutlineOnly', + 'gsnXYBarChartOutlineThicknessF', 'gsnXYBarChartPatterns', + 'gsnXYBarChartPatterns2', 'gsnXYBelowFillColors', 'gsnXYFillColors', + 'gsnXYFillOpacities', 'gsnXYLeftFillColors', 'gsnXYRightFillColors', + 'gsnYAxisIrregular2Linear', 'gsnYAxisIrregular2Log', 'gsnYRefLine', + 'gsnYRefLineColor', 'gsnYRefLineColors', 'gsnYRefLineDashPattern', + 'gsnYRefLineDashPatterns', 'gsnYRefLineThicknessF', + 'gsnYRefLineThicknesses', 'gsnZonalMean', 'gsnZonalMeanXMaxF', + 'gsnZonalMeanXMinF', 'gsnZonalMeanYRefLine', 'lbAutoManage', + 'lbBottomMarginF', 'lbBoxCount', 'lbBoxEndCapStyle', 'lbBoxFractions', + 'lbBoxLineColor', 'lbBoxLineDashPattern', 'lbBoxLineDashSegLenF', + 'lbBoxLineThicknessF', 'lbBoxLinesOn', 'lbBoxMajorExtentF', + 'lbBoxMinorExtentF', 'lbBoxSeparatorLinesOn', 'lbBoxSizing', + 'lbFillBackground', 'lbFillColor', 'lbFillColors', 'lbFillDotSizeF', + 'lbFillLineThicknessF', 'lbFillPattern', 'lbFillPatterns', + 'lbFillScaleF', 'lbFillScales', 'lbJustification', 'lbLabelAlignment', + 'lbLabelAngleF', 'lbLabelAutoStride', 'lbLabelBarOn', + 'lbLabelConstantSpacingF', 'lbLabelDirection', 'lbLabelFont', + 'lbLabelFontAspectF', 'lbLabelFontColor', 'lbLabelFontHeightF', + 'lbLabelFontQuality', 'lbLabelFontThicknessF', 'lbLabelFuncCode', + 'lbLabelJust', 'lbLabelOffsetF', 'lbLabelPosition', 'lbLabelStride', + 'lbLabelStrings', 'lbLabelsOn', 'lbLeftMarginF', 'lbMaxLabelLenF', + 'lbMinLabelSpacingF', 'lbMonoFillColor', 'lbMonoFillPattern', + 'lbMonoFillScale', 'lbOrientation', 'lbPerimColor', + 'lbPerimDashPattern', 'lbPerimDashSegLenF', 'lbPerimFill', + 'lbPerimFillColor', 'lbPerimOn', 'lbPerimThicknessF', + 'lbRasterFillOn', 'lbRightMarginF', 'lbTitleAngleF', + 'lbTitleConstantSpacingF', 'lbTitleDirection', 'lbTitleExtentF', + 'lbTitleFont', 'lbTitleFontAspectF', 'lbTitleFontColor', + 'lbTitleFontHeightF', 'lbTitleFontQuality', 'lbTitleFontThicknessF', + 'lbTitleFuncCode', 'lbTitleJust', 'lbTitleOffsetF', 'lbTitleOn', + 'lbTitlePosition', 'lbTitleString', 'lbTopMarginF', 'lgAutoManage', + 'lgBottomMarginF', 'lgBoxBackground', 'lgBoxLineColor', + 'lgBoxLineDashPattern', 'lgBoxLineDashSegLenF', 'lgBoxLineThicknessF', + 'lgBoxLinesOn', 'lgBoxMajorExtentF', 'lgBoxMinorExtentF', + 'lgDashIndex', 'lgDashIndexes', 'lgItemCount', 'lgItemOrder', + 'lgItemPlacement', 'lgItemPositions', 'lgItemType', 'lgItemTypes', + 'lgJustification', 'lgLabelAlignment', 'lgLabelAngleF', + 'lgLabelAutoStride', 'lgLabelConstantSpacingF', 'lgLabelDirection', + 'lgLabelFont', 'lgLabelFontAspectF', 'lgLabelFontColor', + 'lgLabelFontHeightF', 'lgLabelFontQuality', 'lgLabelFontThicknessF', + 'lgLabelFuncCode', 'lgLabelJust', 'lgLabelOffsetF', 'lgLabelPosition', + 'lgLabelStride', 'lgLabelStrings', 'lgLabelsOn', 'lgLeftMarginF', + 'lgLegendOn', 'lgLineColor', 'lgLineColors', 'lgLineDashSegLenF', + 'lgLineDashSegLens', 'lgLineLabelConstantSpacingF', 'lgLineLabelFont', + 'lgLineLabelFontAspectF', 'lgLineLabelFontColor', + 'lgLineLabelFontColors', 'lgLineLabelFontHeightF', + 'lgLineLabelFontHeights', 'lgLineLabelFontQuality', + 'lgLineLabelFontThicknessF', 'lgLineLabelFuncCode', + 'lgLineLabelStrings', 'lgLineLabelsOn', 'lgLineThicknessF', + 'lgLineThicknesses', 'lgMarkerColor', 'lgMarkerColors', + 'lgMarkerIndex', 'lgMarkerIndexes', 'lgMarkerSizeF', 'lgMarkerSizes', + 'lgMarkerThicknessF', 'lgMarkerThicknesses', 'lgMonoDashIndex', + 'lgMonoItemType', 'lgMonoLineColor', 'lgMonoLineDashSegLen', + 'lgMonoLineLabelFontColor', 'lgMonoLineLabelFontHeight', + 'lgMonoLineThickness', 'lgMonoMarkerColor', 'lgMonoMarkerIndex', + 'lgMonoMarkerSize', 'lgMonoMarkerThickness', 'lgOrientation', + 'lgPerimColor', 'lgPerimDashPattern', 'lgPerimDashSegLenF', + 'lgPerimFill', 'lgPerimFillColor', 'lgPerimOn', 'lgPerimThicknessF', + 'lgRightMarginF', 'lgTitleAngleF', 'lgTitleConstantSpacingF', + 'lgTitleDirection', 'lgTitleExtentF', 'lgTitleFont', + 'lgTitleFontAspectF', 'lgTitleFontColor', 'lgTitleFontHeightF', + 'lgTitleFontQuality', 'lgTitleFontThicknessF', 'lgTitleFuncCode', + 'lgTitleJust', 'lgTitleOffsetF', 'lgTitleOn', 'lgTitlePosition', + 'lgTitleString', 'lgTopMarginF', 'mpAreaGroupCount', + 'mpAreaMaskingOn', 'mpAreaNames', 'mpAreaTypes', 'mpBottomAngleF', + 'mpBottomMapPosF', 'mpBottomNDCF', 'mpBottomNPCF', + 'mpBottomPointLatF', 'mpBottomPointLonF', 'mpBottomWindowF', + 'mpCenterLatF', 'mpCenterLonF', 'mpCenterRotF', 'mpCountyLineColor', + 'mpCountyLineDashPattern', 'mpCountyLineDashSegLenF', + 'mpCountyLineThicknessF', 'mpDataBaseVersion', 'mpDataResolution', + 'mpDataSetName', 'mpDefaultFillColor', 'mpDefaultFillPattern', + 'mpDefaultFillScaleF', 'mpDynamicAreaGroups', 'mpEllipticalBoundary', + 'mpFillAreaSpecifiers', 'mpFillBoundarySets', 'mpFillColor', + 'mpFillColors', 'mpFillColors-default', 'mpFillDotSizeF', + 'mpFillDrawOrder', 'mpFillOn', 'mpFillPatternBackground', + 'mpFillPattern', 'mpFillPatterns', 'mpFillPatterns-default', + 'mpFillScaleF', 'mpFillScales', 'mpFillScales-default', + 'mpFixedAreaGroups', 'mpGeophysicalLineColor', + 'mpGeophysicalLineDashPattern', 'mpGeophysicalLineDashSegLenF', + 'mpGeophysicalLineThicknessF', 'mpGreatCircleLinesOn', + 'mpGridAndLimbDrawOrder', 'mpGridAndLimbOn', 'mpGridLatSpacingF', + 'mpGridLineColor', 'mpGridLineDashPattern', 'mpGridLineDashSegLenF', + 'mpGridLineThicknessF', 'mpGridLonSpacingF', 'mpGridMaskMode', + 'mpGridMaxLatF', 'mpGridPolarLonSpacingF', 'mpGridSpacingF', + 'mpInlandWaterFillColor', 'mpInlandWaterFillPattern', + 'mpInlandWaterFillScaleF', 'mpLabelDrawOrder', 'mpLabelFontColor', + 'mpLabelFontHeightF', 'mpLabelsOn', 'mpLambertMeridianF', + 'mpLambertParallel1F', 'mpLambertParallel2F', 'mpLandFillColor', + 'mpLandFillPattern', 'mpLandFillScaleF', 'mpLeftAngleF', + 'mpLeftCornerLatF', 'mpLeftCornerLonF', 'mpLeftMapPosF', + 'mpLeftNDCF', 'mpLeftNPCF', 'mpLeftPointLatF', + 'mpLeftPointLonF', 'mpLeftWindowF', 'mpLimbLineColor', + 'mpLimbLineDashPattern', 'mpLimbLineDashSegLenF', + 'mpLimbLineThicknessF', 'mpLimitMode', 'mpMaskAreaSpecifiers', + 'mpMaskOutlineSpecifiers', 'mpMaxLatF', 'mpMaxLonF', + 'mpMinLatF', 'mpMinLonF', 'mpMonoFillColor', 'mpMonoFillPattern', + 'mpMonoFillScale', 'mpNationalLineColor', 'mpNationalLineDashPattern', + 'mpNationalLineThicknessF', 'mpOceanFillColor', 'mpOceanFillPattern', + 'mpOceanFillScaleF', 'mpOutlineBoundarySets', 'mpOutlineDrawOrder', + 'mpOutlineMaskingOn', 'mpOutlineOn', 'mpOutlineSpecifiers', + 'mpPerimDrawOrder', 'mpPerimLineColor', 'mpPerimLineDashPattern', + 'mpPerimLineDashSegLenF', 'mpPerimLineThicknessF', 'mpPerimOn', + 'mpPolyMode', 'mpProjection', 'mpProvincialLineColor', + 'mpProvincialLineDashPattern', 'mpProvincialLineDashSegLenF', + 'mpProvincialLineThicknessF', 'mpRelativeCenterLat', + 'mpRelativeCenterLon', 'mpRightAngleF', 'mpRightCornerLatF', + 'mpRightCornerLonF', 'mpRightMapPosF', 'mpRightNDCF', + 'mpRightNPCF', 'mpRightPointLatF', 'mpRightPointLonF', + 'mpRightWindowF', 'mpSatelliteAngle1F', 'mpSatelliteAngle2F', + 'mpSatelliteDistF', 'mpShapeMode', 'mpSpecifiedFillColors', + 'mpSpecifiedFillDirectIndexing', 'mpSpecifiedFillPatterns', + 'mpSpecifiedFillPriority', 'mpSpecifiedFillScales', + 'mpTopAngleF', 'mpTopMapPosF', 'mpTopNDCF', 'mpTopNPCF', + 'mpTopPointLatF', 'mpTopPointLonF', 'mpTopWindowF', + 'mpUSStateLineColor', 'mpUSStateLineDashPattern', + 'mpUSStateLineDashSegLenF', 'mpUSStateLineThicknessF', + 'pmAnnoManagers', 'pmAnnoViews', 'pmLabelBarDisplayMode', + 'pmLabelBarHeightF', 'pmLabelBarKeepAspect', 'pmLabelBarOrthogonalPosF', + 'pmLabelBarParallelPosF', 'pmLabelBarSide', 'pmLabelBarWidthF', + 'pmLabelBarZone', 'pmLegendDisplayMode', 'pmLegendHeightF', + 'pmLegendKeepAspect', 'pmLegendOrthogonalPosF', + 'pmLegendParallelPosF', 'pmLegendSide', 'pmLegendWidthF', + 'pmLegendZone', 'pmOverlaySequenceIds', 'pmTickMarkDisplayMode', + 'pmTickMarkZone', 'pmTitleDisplayMode', 'pmTitleZone', + 'prGraphicStyle', 'prPolyType', 'prXArray', 'prYArray', + 'sfCopyData', 'sfDataArray', 'sfDataMaxV', 'sfDataMinV', + 'sfElementNodes', 'sfExchangeDimensions', 'sfFirstNodeIndex', + 'sfMissingValueV', 'sfXArray', 'sfXCActualEndF', 'sfXCActualStartF', + 'sfXCEndIndex', 'sfXCEndSubsetV', 'sfXCEndV', 'sfXCStartIndex', + 'sfXCStartSubsetV', 'sfXCStartV', 'sfXCStride', 'sfXCellBounds', + 'sfYArray', 'sfYCActualEndF', 'sfYCActualStartF', 'sfYCEndIndex', + 'sfYCEndSubsetV', 'sfYCEndV', 'sfYCStartIndex', 'sfYCStartSubsetV', + 'sfYCStartV', 'sfYCStride', 'sfYCellBounds', 'stArrowLengthF', + 'stArrowStride', 'stCrossoverCheckCount', + 'stExplicitLabelBarLabelsOn', 'stLabelBarEndLabelsOn', + 'stLabelFormat', 'stLengthCheckCount', 'stLevelColors', + 'stLevelCount', 'stLevelPalette', 'stLevelSelectionMode', + 'stLevelSpacingF', 'stLevels', 'stLineColor', 'stLineOpacityF', + 'stLineStartStride', 'stLineThicknessF', 'stMapDirection', + 'stMaxLevelCount', 'stMaxLevelValF', 'stMinArrowSpacingF', + 'stMinDistanceF', 'stMinLevelValF', 'stMinLineSpacingF', + 'stMinStepFactorF', 'stMonoLineColor', 'stNoDataLabelOn', + 'stNoDataLabelString', 'stScalarFieldData', 'stScalarMissingValColor', + 'stSpanLevelPalette', 'stStepSizeF', 'stStreamlineDrawOrder', + 'stUseScalarArray', 'stVectorFieldData', 'stZeroFLabelAngleF', + 'stZeroFLabelBackgroundColor', 'stZeroFLabelConstantSpacingF', + 'stZeroFLabelFont', 'stZeroFLabelFontAspectF', + 'stZeroFLabelFontColor', 'stZeroFLabelFontHeightF', + 'stZeroFLabelFontQuality', 'stZeroFLabelFontThicknessF', + 'stZeroFLabelFuncCode', 'stZeroFLabelJust', 'stZeroFLabelOn', + 'stZeroFLabelOrthogonalPosF', 'stZeroFLabelParallelPosF', + 'stZeroFLabelPerimColor', 'stZeroFLabelPerimOn', + 'stZeroFLabelPerimSpaceF', 'stZeroFLabelPerimThicknessF', + 'stZeroFLabelSide', 'stZeroFLabelString', 'stZeroFLabelTextDirection', + 'stZeroFLabelZone', 'tfDoNDCOverlay', 'tfPlotManagerOn', + 'tfPolyDrawList', 'tfPolyDrawOrder', 'tiDeltaF', 'tiMainAngleF', + 'tiMainConstantSpacingF', 'tiMainDirection', 'tiMainFont', + 'tiMainFontAspectF', 'tiMainFontColor', 'tiMainFontHeightF', + 'tiMainFontQuality', 'tiMainFontThicknessF', 'tiMainFuncCode', + 'tiMainJust', 'tiMainOffsetXF', 'tiMainOffsetYF', 'tiMainOn', + 'tiMainPosition', 'tiMainSide', 'tiMainString', 'tiUseMainAttributes', + 'tiXAxisAngleF', 'tiXAxisConstantSpacingF', 'tiXAxisDirection', + 'tiXAxisFont', 'tiXAxisFontAspectF', 'tiXAxisFontColor', + 'tiXAxisFontHeightF', 'tiXAxisFontQuality', 'tiXAxisFontThicknessF', + 'tiXAxisFuncCode', 'tiXAxisJust', 'tiXAxisOffsetXF', + 'tiXAxisOffsetYF', 'tiXAxisOn', 'tiXAxisPosition', 'tiXAxisSide', + 'tiXAxisString', 'tiYAxisAngleF', 'tiYAxisConstantSpacingF', + 'tiYAxisDirection', 'tiYAxisFont', 'tiYAxisFontAspectF', + 'tiYAxisFontColor', 'tiYAxisFontHeightF', 'tiYAxisFontQuality', + 'tiYAxisFontThicknessF', 'tiYAxisFuncCode', 'tiYAxisJust', + 'tiYAxisOffsetXF', 'tiYAxisOffsetYF', 'tiYAxisOn', 'tiYAxisPosition', + 'tiYAxisSide', 'tiYAxisString', 'tmBorderLineColor', + 'tmBorderThicknessF', 'tmEqualizeXYSizes', 'tmLabelAutoStride', + 'tmSciNoteCutoff', 'tmXBAutoPrecision', 'tmXBBorderOn', + 'tmXBDataLeftF', 'tmXBDataRightF', 'tmXBFormat', 'tmXBIrrTensionF', + 'tmXBIrregularPoints', 'tmXBLabelAngleF', 'tmXBLabelConstantSpacingF', + 'tmXBLabelDeltaF', 'tmXBLabelDirection', 'tmXBLabelFont', + 'tmXBLabelFontAspectF', 'tmXBLabelFontColor', 'tmXBLabelFontHeightF', + 'tmXBLabelFontQuality', 'tmXBLabelFontThicknessF', + 'tmXBLabelFuncCode', 'tmXBLabelJust', 'tmXBLabelStride', 'tmXBLabels', + 'tmXBLabelsOn', 'tmXBMajorLengthF', 'tmXBMajorLineColor', + 'tmXBMajorOutwardLengthF', 'tmXBMajorThicknessF', 'tmXBMaxLabelLenF', + 'tmXBMaxTicks', 'tmXBMinLabelSpacingF', 'tmXBMinorLengthF', + 'tmXBMinorLineColor', 'tmXBMinorOn', 'tmXBMinorOutwardLengthF', + 'tmXBMinorPerMajor', 'tmXBMinorThicknessF', 'tmXBMinorValues', + 'tmXBMode', 'tmXBOn', 'tmXBPrecision', 'tmXBStyle', 'tmXBTickEndF', + 'tmXBTickSpacingF', 'tmXBTickStartF', 'tmXBValues', 'tmXMajorGrid', + 'tmXMajorGridLineColor', 'tmXMajorGridLineDashPattern', + 'tmXMajorGridThicknessF', 'tmXMinorGrid', 'tmXMinorGridLineColor', + 'tmXMinorGridLineDashPattern', 'tmXMinorGridThicknessF', + 'tmXTAutoPrecision', 'tmXTBorderOn', 'tmXTDataLeftF', + 'tmXTDataRightF', 'tmXTFormat', 'tmXTIrrTensionF', + 'tmXTIrregularPoints', 'tmXTLabelAngleF', 'tmXTLabelConstantSpacingF', + 'tmXTLabelDeltaF', 'tmXTLabelDirection', 'tmXTLabelFont', + 'tmXTLabelFontAspectF', 'tmXTLabelFontColor', 'tmXTLabelFontHeightF', + 'tmXTLabelFontQuality', 'tmXTLabelFontThicknessF', + 'tmXTLabelFuncCode', 'tmXTLabelJust', 'tmXTLabelStride', 'tmXTLabels', + 'tmXTLabelsOn', 'tmXTMajorLengthF', 'tmXTMajorLineColor', + 'tmXTMajorOutwardLengthF', 'tmXTMajorThicknessF', 'tmXTMaxLabelLenF', + 'tmXTMaxTicks', 'tmXTMinLabelSpacingF', 'tmXTMinorLengthF', + 'tmXTMinorLineColor', 'tmXTMinorOn', 'tmXTMinorOutwardLengthF', + 'tmXTMinorPerMajor', 'tmXTMinorThicknessF', 'tmXTMinorValues', + 'tmXTMode', 'tmXTOn', 'tmXTPrecision', 'tmXTStyle', 'tmXTTickEndF', + 'tmXTTickSpacingF', 'tmXTTickStartF', 'tmXTValues', 'tmXUseBottom', + 'tmYLAutoPrecision', 'tmYLBorderOn', 'tmYLDataBottomF', + 'tmYLDataTopF', 'tmYLFormat', 'tmYLIrrTensionF', + 'tmYLIrregularPoints', 'tmYLLabelAngleF', 'tmYLLabelConstantSpacingF', + 'tmYLLabelDeltaF', 'tmYLLabelDirection', 'tmYLLabelFont', + 'tmYLLabelFontAspectF', 'tmYLLabelFontColor', 'tmYLLabelFontHeightF', + 'tmYLLabelFontQuality', 'tmYLLabelFontThicknessF', + 'tmYLLabelFuncCode', 'tmYLLabelJust', 'tmYLLabelStride', 'tmYLLabels', + 'tmYLLabelsOn', 'tmYLMajorLengthF', 'tmYLMajorLineColor', + 'tmYLMajorOutwardLengthF', 'tmYLMajorThicknessF', 'tmYLMaxLabelLenF', + 'tmYLMaxTicks', 'tmYLMinLabelSpacingF', 'tmYLMinorLengthF', + 'tmYLMinorLineColor', 'tmYLMinorOn', 'tmYLMinorOutwardLengthF', + 'tmYLMinorPerMajor', 'tmYLMinorThicknessF', 'tmYLMinorValues', + 'tmYLMode', 'tmYLOn', 'tmYLPrecision', 'tmYLStyle', 'tmYLTickEndF', + 'tmYLTickSpacingF', 'tmYLTickStartF', 'tmYLValues', 'tmYMajorGrid', + 'tmYMajorGridLineColor', 'tmYMajorGridLineDashPattern', + 'tmYMajorGridThicknessF', 'tmYMinorGrid', 'tmYMinorGridLineColor', + 'tmYMinorGridLineDashPattern', 'tmYMinorGridThicknessF', + 'tmYRAutoPrecision', 'tmYRBorderOn', 'tmYRDataBottomF', + 'tmYRDataTopF', 'tmYRFormat', 'tmYRIrrTensionF', + 'tmYRIrregularPoints', 'tmYRLabelAngleF', 'tmYRLabelConstantSpacingF', + 'tmYRLabelDeltaF', 'tmYRLabelDirection', 'tmYRLabelFont', + 'tmYRLabelFontAspectF', 'tmYRLabelFontColor', 'tmYRLabelFontHeightF', + 'tmYRLabelFontQuality', 'tmYRLabelFontThicknessF', + 'tmYRLabelFuncCode', 'tmYRLabelJust', 'tmYRLabelStride', 'tmYRLabels', + 'tmYRLabelsOn', 'tmYRMajorLengthF', 'tmYRMajorLineColor', + 'tmYRMajorOutwardLengthF', 'tmYRMajorThicknessF', 'tmYRMaxLabelLenF', + 'tmYRMaxTicks', 'tmYRMinLabelSpacingF', 'tmYRMinorLengthF', + 'tmYRMinorLineColor', 'tmYRMinorOn', 'tmYRMinorOutwardLengthF', + 'tmYRMinorPerMajor', 'tmYRMinorThicknessF', 'tmYRMinorValues', + 'tmYRMode', 'tmYROn', 'tmYRPrecision', 'tmYRStyle', 'tmYRTickEndF', + 'tmYRTickSpacingF', 'tmYRTickStartF', 'tmYRValues', 'tmYUseLeft', + 'trGridType', 'trLineInterpolationOn', + 'trXAxisType', 'trXCoordPoints', 'trXInterPoints', 'trXLog', + 'trXMaxF', 'trXMinF', 'trXReverse', 'trXSamples', 'trXTensionF', + 'trYAxisType', 'trYCoordPoints', 'trYInterPoints', 'trYLog', + 'trYMaxF', 'trYMinF', 'trYReverse', 'trYSamples', 'trYTensionF', + 'txAngleF', 'txBackgroundFillColor', 'txConstantSpacingF', 'txDirection', + 'txFont', 'HLU-Fonts', 'txFontAspectF', 'txFontColor', + 'txFontHeightF', 'txFontOpacityF', 'txFontQuality', + 'txFontThicknessF', 'txFuncCode', 'txJust', 'txPerimColor', + 'txPerimDashLengthF', 'txPerimDashPattern', 'txPerimOn', + 'txPerimSpaceF', 'txPerimThicknessF', 'txPosXF', 'txPosYF', + 'txString', 'vcExplicitLabelBarLabelsOn', 'vcFillArrowEdgeColor', + 'vcFillArrowEdgeThicknessF', 'vcFillArrowFillColor', + 'vcFillArrowHeadInteriorXF', 'vcFillArrowHeadMinFracXF', + 'vcFillArrowHeadMinFracYF', 'vcFillArrowHeadXF', 'vcFillArrowHeadYF', + 'vcFillArrowMinFracWidthF', 'vcFillArrowWidthF', 'vcFillArrowsOn', + 'vcFillOverEdge', 'vcGlyphOpacityF', 'vcGlyphStyle', + 'vcLabelBarEndLabelsOn', 'vcLabelFontColor', 'vcLabelFontHeightF', + 'vcLabelsOn', 'vcLabelsUseVectorColor', 'vcLevelColors', + 'vcLevelCount', 'vcLevelPalette', 'vcLevelSelectionMode', + 'vcLevelSpacingF', 'vcLevels', 'vcLineArrowColor', + 'vcLineArrowHeadMaxSizeF', 'vcLineArrowHeadMinSizeF', + 'vcLineArrowThicknessF', 'vcMagnitudeFormat', + 'vcMagnitudeScaleFactorF', 'vcMagnitudeScaleValueF', + 'vcMagnitudeScalingMode', 'vcMapDirection', 'vcMaxLevelCount', + 'vcMaxLevelValF', 'vcMaxMagnitudeF', 'vcMinAnnoAngleF', + 'vcMinAnnoArrowAngleF', 'vcMinAnnoArrowEdgeColor', + 'vcMinAnnoArrowFillColor', 'vcMinAnnoArrowLineColor', + 'vcMinAnnoArrowMinOffsetF', 'vcMinAnnoArrowSpaceF', + 'vcMinAnnoArrowUseVecColor', 'vcMinAnnoBackgroundColor', + 'vcMinAnnoConstantSpacingF', 'vcMinAnnoExplicitMagnitudeF', + 'vcMinAnnoFont', 'vcMinAnnoFontAspectF', 'vcMinAnnoFontColor', + 'vcMinAnnoFontHeightF', 'vcMinAnnoFontQuality', + 'vcMinAnnoFontThicknessF', 'vcMinAnnoFuncCode', 'vcMinAnnoJust', + 'vcMinAnnoOn', 'vcMinAnnoOrientation', 'vcMinAnnoOrthogonalPosF', + 'vcMinAnnoParallelPosF', 'vcMinAnnoPerimColor', 'vcMinAnnoPerimOn', + 'vcMinAnnoPerimSpaceF', 'vcMinAnnoPerimThicknessF', 'vcMinAnnoSide', + 'vcMinAnnoString1', 'vcMinAnnoString1On', 'vcMinAnnoString2', + 'vcMinAnnoString2On', 'vcMinAnnoTextDirection', 'vcMinAnnoZone', + 'vcMinDistanceF', 'vcMinFracLengthF', 'vcMinLevelValF', + 'vcMinMagnitudeF', 'vcMonoFillArrowEdgeColor', + 'vcMonoFillArrowFillColor', 'vcMonoLineArrowColor', + 'vcMonoWindBarbColor', 'vcNoDataLabelOn', 'vcNoDataLabelString', + 'vcPositionMode', 'vcRefAnnoAngleF', 'vcRefAnnoArrowAngleF', + 'vcRefAnnoArrowEdgeColor', 'vcRefAnnoArrowFillColor', + 'vcRefAnnoArrowLineColor', 'vcRefAnnoArrowMinOffsetF', + 'vcRefAnnoArrowSpaceF', 'vcRefAnnoArrowUseVecColor', + 'vcRefAnnoBackgroundColor', 'vcRefAnnoConstantSpacingF', + 'vcRefAnnoExplicitMagnitudeF', 'vcRefAnnoFont', + 'vcRefAnnoFontAspectF', 'vcRefAnnoFontColor', 'vcRefAnnoFontHeightF', + 'vcRefAnnoFontQuality', 'vcRefAnnoFontThicknessF', + 'vcRefAnnoFuncCode', 'vcRefAnnoJust', 'vcRefAnnoOn', + 'vcRefAnnoOrientation', 'vcRefAnnoOrthogonalPosF', + 'vcRefAnnoParallelPosF', 'vcRefAnnoPerimColor', 'vcRefAnnoPerimOn', + 'vcRefAnnoPerimSpaceF', 'vcRefAnnoPerimThicknessF', 'vcRefAnnoSide', + 'vcRefAnnoString1', 'vcRefAnnoString1On', 'vcRefAnnoString2', + 'vcRefAnnoString2On', 'vcRefAnnoTextDirection', 'vcRefAnnoZone', + 'vcRefLengthF', 'vcRefMagnitudeF', 'vcScalarFieldData', + 'vcScalarMissingValColor', 'vcScalarValueFormat', + 'vcScalarValueScaleFactorF', 'vcScalarValueScaleValueF', + 'vcScalarValueScalingMode', 'vcSpanLevelPalette', 'vcUseRefAnnoRes', + 'vcUseScalarArray', 'vcVectorDrawOrder', 'vcVectorFieldData', + 'vcWindBarbCalmCircleSizeF', 'vcWindBarbColor', + 'vcWindBarbLineThicknessF', 'vcWindBarbScaleFactorF', + 'vcWindBarbTickAngleF', 'vcWindBarbTickLengthF', + 'vcWindBarbTickSpacingF', 'vcZeroFLabelAngleF', + 'vcZeroFLabelBackgroundColor', 'vcZeroFLabelConstantSpacingF', + 'vcZeroFLabelFont', 'vcZeroFLabelFontAspectF', + 'vcZeroFLabelFontColor', 'vcZeroFLabelFontHeightF', + 'vcZeroFLabelFontQuality', 'vcZeroFLabelFontThicknessF', + 'vcZeroFLabelFuncCode', 'vcZeroFLabelJust', 'vcZeroFLabelOn', + 'vcZeroFLabelOrthogonalPosF', 'vcZeroFLabelParallelPosF', + 'vcZeroFLabelPerimColor', 'vcZeroFLabelPerimOn', + 'vcZeroFLabelPerimSpaceF', 'vcZeroFLabelPerimThicknessF', + 'vcZeroFLabelSide', 'vcZeroFLabelString', 'vcZeroFLabelTextDirection', + 'vcZeroFLabelZone', 'vfCopyData', 'vfDataArray', + 'vfExchangeDimensions', 'vfExchangeUVData', 'vfMagMaxV', 'vfMagMinV', + 'vfMissingUValueV', 'vfMissingVValueV', 'vfPolarData', + 'vfSingleMissingValue', 'vfUDataArray', 'vfUMaxV', 'vfUMinV', + 'vfVDataArray', 'vfVMaxV', 'vfVMinV', 'vfXArray', 'vfXCActualEndF', + 'vfXCActualStartF', 'vfXCEndIndex', 'vfXCEndSubsetV', 'vfXCEndV', + 'vfXCStartIndex', 'vfXCStartSubsetV', 'vfXCStartV', 'vfXCStride', + 'vfYArray', 'vfYCActualEndF', 'vfYCActualStartF', 'vfYCEndIndex', + 'vfYCEndSubsetV', 'vfYCEndV', 'vfYCStartIndex', 'vfYCStartSubsetV', + 'vfYCStartV', 'vfYCStride', 'vpAnnoManagerId', 'vpClipOn', + 'vpHeightF', 'vpKeepAspect', 'vpOn', 'vpUseSegments', 'vpWidthF', + 'vpXF', 'vpYF', 'wkAntiAlias', 'wkBackgroundColor', 'wkBackgroundOpacityF', + 'wkColorMapLen', 'wkColorMap', 'wkColorModel', 'wkDashTableLength', + 'wkDefGraphicStyleId', 'wkDeviceLowerX', 'wkDeviceLowerY', + 'wkDeviceUpperX', 'wkDeviceUpperY', 'wkFileName', 'wkFillTableLength', + 'wkForegroundColor', 'wkFormat', 'wkFullBackground', 'wkGksWorkId', + 'wkHeight', 'wkMarkerTableLength', 'wkMetaName', 'wkOrientation', + 'wkPDFFileName', 'wkPDFFormat', 'wkPDFResolution', 'wkPSFileName', + 'wkPSFormat', 'wkPSResolution', 'wkPaperHeightF', 'wkPaperSize', + 'wkPaperWidthF', 'wkPause', 'wkTopLevelViews', 'wkViews', + 'wkVisualType', 'wkWidth', 'wkWindowId', 'wkXColorMode', 'wsCurrentSize', + 'wsMaximumSize', 'wsThresholdSize', 'xyComputeXMax', + 'xyComputeXMin', 'xyComputeYMax', 'xyComputeYMin', 'xyCoordData', + 'xyCoordDataSpec', 'xyCurveDrawOrder', 'xyDashPattern', + 'xyDashPatterns', 'xyExplicitLabels', 'xyExplicitLegendLabels', + 'xyLabelMode', 'xyLineColor', 'xyLineColors', 'xyLineDashSegLenF', + 'xyLineLabelConstantSpacingF', 'xyLineLabelFont', + 'xyLineLabelFontAspectF', 'xyLineLabelFontColor', + 'xyLineLabelFontColors', 'xyLineLabelFontHeightF', + 'xyLineLabelFontQuality', 'xyLineLabelFontThicknessF', + 'xyLineLabelFuncCode', 'xyLineThicknessF', 'xyLineThicknesses', + 'xyMarkLineMode', 'xyMarkLineModes', 'xyMarker', 'xyMarkerColor', + 'xyMarkerColors', 'xyMarkerSizeF', 'xyMarkerSizes', + 'xyMarkerThicknessF', 'xyMarkerThicknesses', 'xyMarkers', + 'xyMonoDashPattern', 'xyMonoLineColor', 'xyMonoLineLabelFontColor', + 'xyMonoLineThickness', 'xyMonoMarkLineMode', 'xyMonoMarker', + 'xyMonoMarkerColor', 'xyMonoMarkerSize', 'xyMonoMarkerThickness', + 'xyXIrrTensionF', 'xyXIrregularPoints', 'xyXStyle', 'xyYIrrTensionF', + 'xyYIrregularPoints', 'xyYStyle'), prefix=r'\b'), + Name.Builtin), + + # Booleans + (r'\.(True|False)\.', Name.Builtin), + # Comparing Operators + (r'\.(eq|ne|lt|le|gt|ge|not|and|or|xor)\.', Operator.Word), + ], + + 'strings': [ + (r'(?s)"(\\\\|\\[0-7]+|\\.|[^"\\])*"', String.Double), + ], + + 'nums': [ + (r'\d+(?![.e])(_[a-z]\w+)?', Number.Integer), + (r'[+-]?\d*\.\d+(e[-+]?\d+)?(_[a-z]\w+)?', Number.Float), + (r'[+-]?\d+\.\d*(e[-+]?\d+)?(_[a-z]\w+)?', Number.Float), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/nimrod.py b/.venv/lib/python3.8/site-packages/pygments/lexers/nimrod.py new file mode 100644 index 0000000..ce6ba87 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/nimrod.py @@ -0,0 +1,158 @@ +""" + pygments.lexers.nimrod + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexer for the Nim language (formerly known as Nimrod). + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, default +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Error + +__all__ = ['NimrodLexer'] + + +class NimrodLexer(RegexLexer): + """ + For `Nim `_ source code. + + .. versionadded:: 1.5 + """ + + name = 'Nimrod' + aliases = ['nimrod', 'nim'] + filenames = ['*.nim', '*.nimrod'] + mimetypes = ['text/x-nim'] + + flags = re.MULTILINE | re.IGNORECASE | re.UNICODE + + def underscorize(words): + newWords = [] + new = "" + for word in words: + for ch in word: + new += (ch + "_?") + newWords.append(new) + new = "" + return "|".join(newWords) + + keywords = [ + 'addr', 'and', 'as', 'asm', 'bind', 'block', 'break', 'case', + 'cast', 'concept', 'const', 'continue', 'converter', 'defer', 'discard', + 'distinct', 'div', 'do', 'elif', 'else', 'end', 'enum', 'except', + 'export', 'finally', 'for', 'func', 'if', 'in', 'yield', 'interface', + 'is', 'isnot', 'iterator', 'let', 'macro', 'method', 'mixin', 'mod', + 'not', 'notin', 'object', 'of', 'or', 'out', 'proc', 'ptr', 'raise', + 'ref', 'return', 'shl', 'shr', 'static', 'template', 'try', + 'tuple', 'type', 'using', 'when', 'while', 'xor' + ] + + keywordsPseudo = [ + 'nil', 'true', 'false' + ] + + opWords = [ + 'and', 'or', 'not', 'xor', 'shl', 'shr', 'div', 'mod', 'in', + 'notin', 'is', 'isnot' + ] + + types = [ + 'int', 'int8', 'int16', 'int32', 'int64', 'float', 'float32', 'float64', + 'bool', 'char', 'range', 'array', 'seq', 'set', 'string' + ] + + tokens = { + 'root': [ + (r'##.*$', String.Doc), + (r'#.*$', Comment), + (r'[*=><+\-/@$~&%!?|\\\[\]]', Operator), + (r'\.\.|\.|,|\[\.|\.\]|\{\.|\.\}|\(\.|\.\)|\{|\}|\(|\)|:|\^|`|;', + Punctuation), + + # Strings + (r'(?:[\w]+)"', String, 'rdqs'), + (r'"""', String, 'tdqs'), + ('"', String, 'dqs'), + + # Char + ("'", String.Char, 'chars'), + + # Keywords + (r'(%s)\b' % underscorize(opWords), Operator.Word), + (r'(p_?r_?o_?c_?\s)(?![(\[\]])', Keyword, 'funcname'), + (r'(%s)\b' % underscorize(keywords), Keyword), + (r'(%s)\b' % underscorize(['from', 'import', 'include']), + Keyword.Namespace), + (r'(v_?a_?r)\b', Keyword.Declaration), + (r'(%s)\b' % underscorize(types), Keyword.Type), + (r'(%s)\b' % underscorize(keywordsPseudo), Keyword.Pseudo), + # Identifiers + (r'\b((?![_\d])\w)(((?!_)\w)|(_(?!_)\w))*', Name), + # Numbers + (r'[0-9][0-9_]*(?=([e.]|\'f(32|64)))', + Number.Float, ('float-suffix', 'float-number')), + (r'0x[a-f0-9][a-f0-9_]*', Number.Hex, 'int-suffix'), + (r'0b[01][01_]*', Number.Bin, 'int-suffix'), + (r'0o[0-7][0-7_]*', Number.Oct, 'int-suffix'), + (r'[0-9][0-9_]*', Number.Integer, 'int-suffix'), + # Whitespace + (r'\s+', Text), + (r'.+$', Error), + ], + 'chars': [ + (r'\\([\\abcefnrtvl"\']|x[a-f0-9]{2}|[0-9]{1,3})', String.Escape), + (r"'", String.Char, '#pop'), + (r".", String.Char) + ], + 'strings': [ + (r'(?`_ source. + + .. versionadded:: 2.0 + """ + + name = 'Nit' + aliases = ['nit'] + filenames = ['*.nit'] + tokens = { + 'root': [ + (r'#.*?$', Comment.Single), + (words(( + 'package', 'module', 'import', 'class', 'abstract', 'interface', + 'universal', 'enum', 'end', 'fun', 'type', 'init', 'redef', + 'isa', 'do', 'readable', 'writable', 'var', 'intern', 'extern', + 'public', 'protected', 'private', 'intrude', 'if', 'then', + 'else', 'while', 'loop', 'for', 'in', 'and', 'or', 'not', + 'implies', 'return', 'continue', 'break', 'abort', 'assert', + 'new', 'is', 'once', 'super', 'self', 'true', 'false', 'nullable', + 'null', 'as', 'isset', 'label', '__debug__'), suffix=r'(?=[\r\n\t( ])'), + Keyword), + (r'[A-Z]\w*', Name.Class), + (r'"""(([^\'\\]|\\.)|\\r|\\n)*((\{\{?)?(""?\{\{?)*""""*)', String), # Simple long string + (r'\'\'\'(((\\.|[^\'\\])|\\r|\\n)|\'((\\.|[^\'\\])|\\r|\\n)|' + r'\'\'((\\.|[^\'\\])|\\r|\\n))*\'\'\'', String), # Simple long string alt + (r'"""(([^\'\\]|\\.)|\\r|\\n)*((""?)?(\{\{?""?)*\{\{\{\{*)', String), # Start long string + (r'\}\}\}(((\\.|[^\'\\])|\\r|\\n))*(""?)?(\{\{?""?)*\{\{\{\{*', String), # Mid long string + (r'\}\}\}(((\\.|[^\'\\])|\\r|\\n))*(\{\{?)?(""?\{\{?)*""""*', String), # End long string + (r'"(\\.|([^"}{\\]))*"', String), # Simple String + (r'"(\\.|([^"}{\\]))*\{', String), # Start string + (r'\}(\\.|([^"}{\\]))*\{', String), # Mid String + (r'\}(\\.|([^"}{\\]))*"', String), # End String + (r'(\'[^\'\\]\')|(\'\\.\')', String.Char), + (r'[0-9]+', Number.Integer), + (r'[0-9]*.[0-9]+', Number.Float), + (r'0(x|X)[0-9A-Fa-f]+', Number.Hex), + (r'[a-z]\w*', Name), + (r'_\w+', Name.Variable.Instance), + (r'==|!=|<==>|>=|>>|>|<=|<<|<|\+|-|=|/|\*|%|\+=|-=|!|@', Operator), + (r'\(|\)|\[|\]|,|\.\.\.|\.\.|\.|::|:', Punctuation), + (r'`\{[^`]*`\}', Text), # Extern blocks won't be Lexed by Nit + (r'[\r\n\t ]+', Text), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/nix.py b/.venv/lib/python3.8/site-packages/pygments/lexers/nix.py new file mode 100644 index 0000000..bd7afe7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/nix.py @@ -0,0 +1,135 @@ +""" + pygments.lexers.nix + ~~~~~~~~~~~~~~~~~~~ + + Lexers for the NixOS Nix language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Literal + +__all__ = ['NixLexer'] + + +class NixLexer(RegexLexer): + """ + For the `Nix language `_. + + .. versionadded:: 2.0 + """ + + name = 'Nix' + aliases = ['nixos', 'nix'] + filenames = ['*.nix'] + mimetypes = ['text/x-nix'] + + flags = re.MULTILINE | re.UNICODE + + keywords = ['rec', 'with', 'let', 'in', 'inherit', 'assert', 'if', + 'else', 'then', '...'] + builtins = ['import', 'abort', 'baseNameOf', 'dirOf', 'isNull', 'builtins', + 'map', 'removeAttrs', 'throw', 'toString', 'derivation'] + operators = ['++', '+', '?', '.', '!', '//', '==', + '!=', '&&', '||', '->', '='] + + punctuations = ["(", ")", "[", "]", ";", "{", "}", ":", ",", "@"] + + tokens = { + 'root': [ + # comments starting with # + (r'#.*$', Comment.Single), + + # multiline comments + (r'/\*', Comment.Multiline, 'comment'), + + # whitespace + (r'\s+', Text), + + # keywords + ('(%s)' % '|'.join(re.escape(entry) + '\\b' for entry in keywords), Keyword), + + # highlight the builtins + ('(%s)' % '|'.join(re.escape(entry) + '\\b' for entry in builtins), + Name.Builtin), + + (r'\b(true|false|null)\b', Name.Constant), + + # operators + ('(%s)' % '|'.join(re.escape(entry) for entry in operators), + Operator), + + # word operators + (r'\b(or|and)\b', Operator.Word), + + # punctuations + ('(%s)' % '|'.join(re.escape(entry) for entry in punctuations), Punctuation), + + # integers + (r'[0-9]+', Number.Integer), + + # strings + (r'"', String.Double, 'doublequote'), + (r"''", String.Single, 'singlequote'), + + # paths + (r'[\w.+-]*(\/[\w.+-]+)+', Literal), + (r'\<[\w.+-]+(\/[\w.+-]+)*\>', Literal), + + # urls + (r'[a-zA-Z][a-zA-Z0-9\+\-\.]*\:[\w%/?:@&=+$,\\.!~*\'-]+', Literal), + + # names of variables + (r'[\w-]+\s*=', String.Symbol), + (r'[a-zA-Z_][\w\'-]*', Text), + + ], + 'comment': [ + (r'[^/*]+', Comment.Multiline), + (r'/\*', Comment.Multiline, '#push'), + (r'\*/', Comment.Multiline, '#pop'), + (r'[*/]', Comment.Multiline), + ], + 'singlequote': [ + (r"'''", String.Escape), + (r"''\$\{", String.Escape), + (r"''\n", String.Escape), + (r"''\r", String.Escape), + (r"''\t", String.Escape), + (r"''", String.Single, '#pop'), + (r'\$\{', String.Interpol, 'antiquote'), + (r"[^']", String.Single), + ], + 'doublequote': [ + (r'\\', String.Escape), + (r'\\"', String.Escape), + (r'\\$\{', String.Escape), + (r'"', String.Double, '#pop'), + (r'\$\{', String.Interpol, 'antiquote'), + (r'[^"]', String.Double), + ], + 'antiquote': [ + (r"\}", String.Interpol, '#pop'), + # TODO: we should probably escape also here ''${ \${ + (r"\$\{", String.Interpol, '#push'), + include('root'), + ], + } + + def analyse_text(text): + rv = 0.0 + # TODO: let/in + if re.search(r'import.+?<[^>]+>', text): + rv += 0.4 + if re.search(r'mkDerivation\s+(\(|\{|rec)', text): + rv += 0.4 + if re.search(r'=\s+mkIf\s+', text): + rv += 0.4 + if re.search(r'\{[a-zA-Z,\s]+\}:', text): + rv += 0.1 + return rv diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/oberon.py b/.venv/lib/python3.8/site-packages/pygments/lexers/oberon.py new file mode 100644 index 0000000..7010e91 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/oberon.py @@ -0,0 +1,120 @@ +""" + pygments.lexers.oberon + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Oberon family languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation + +__all__ = ['ComponentPascalLexer'] + + +class ComponentPascalLexer(RegexLexer): + """ + For `Component Pascal `_ source code. + + .. versionadded:: 2.1 + """ + name = 'Component Pascal' + aliases = ['componentpascal', 'cp'] + filenames = ['*.cp', '*.cps'] + mimetypes = ['text/x-component-pascal'] + + flags = re.MULTILINE | re.DOTALL + + tokens = { + 'root': [ + include('whitespace'), + include('comments'), + include('punctuation'), + include('numliterals'), + include('strings'), + include('operators'), + include('builtins'), + include('identifiers'), + ], + 'whitespace': [ + (r'\n+', Text), # blank lines + (r'\s+', Text), # whitespace + ], + 'comments': [ + (r'\(\*([^$].*?)\*\)', Comment.Multiline), + # TODO: nested comments (* (* ... *) ... (* ... *) *) not supported! + ], + 'punctuation': [ + (r'[()\[\]{},.:;|]', Punctuation), + ], + 'numliterals': [ + (r'[0-9A-F]+X\b', Number.Hex), # char code + (r'[0-9A-F]+[HL]\b', Number.Hex), # hexadecimal number + (r'[0-9]+\.[0-9]+E[+-][0-9]+', Number.Float), # real number + (r'[0-9]+\.[0-9]+', Number.Float), # real number + (r'[0-9]+', Number.Integer), # decimal whole number + ], + 'strings': [ + (r"'[^\n']*'", String), # single quoted string + (r'"[^\n"]*"', String), # double quoted string + ], + 'operators': [ + # Arithmetic Operators + (r'[+-]', Operator), + (r'[*/]', Operator), + # Relational Operators + (r'[=#<>]', Operator), + # Dereferencing Operator + (r'\^', Operator), + # Logical AND Operator + (r'&', Operator), + # Logical NOT Operator + (r'~', Operator), + # Assignment Symbol + (r':=', Operator), + # Range Constructor + (r'\.\.', Operator), + (r'\$', Operator), + ], + 'identifiers': [ + (r'([a-zA-Z_$][\w$]*)', Name), + ], + 'builtins': [ + (words(( + 'ANYPTR', 'ANYREC', 'BOOLEAN', 'BYTE', 'CHAR', 'INTEGER', 'LONGINT', + 'REAL', 'SET', 'SHORTCHAR', 'SHORTINT', 'SHORTREAL' + ), suffix=r'\b'), Keyword.Type), + (words(( + 'ABS', 'ABSTRACT', 'ARRAY', 'ASH', 'ASSERT', 'BEGIN', 'BITS', 'BY', + 'CAP', 'CASE', 'CHR', 'CLOSE', 'CONST', 'DEC', 'DIV', 'DO', 'ELSE', + 'ELSIF', 'EMPTY', 'END', 'ENTIER', 'EXCL', 'EXIT', 'EXTENSIBLE', 'FOR', + 'HALT', 'IF', 'IMPORT', 'IN', 'INC', 'INCL', 'IS', 'LEN', 'LIMITED', + 'LONG', 'LOOP', 'MAX', 'MIN', 'MOD', 'MODULE', 'NEW', 'ODD', 'OF', + 'OR', 'ORD', 'OUT', 'POINTER', 'PROCEDURE', 'RECORD', 'REPEAT', 'RETURN', + 'SHORT', 'SHORTCHAR', 'SHORTINT', 'SIZE', 'THEN', 'TYPE', 'TO', 'UNTIL', + 'VAR', 'WHILE', 'WITH' + ), suffix=r'\b'), Keyword.Reserved), + (r'(TRUE|FALSE|NIL|INF)\b', Keyword.Constant), + ] + } + + def analyse_text(text): + """The only other lexer using .cp is the C++ one, so we check if for + a few common Pascal keywords here. Those are unfortunately quite + common across various business languages as well.""" + result = 0 + if 'BEGIN' in text: + result += 0.01 + if 'END' in text: + result += 0.01 + if 'PROCEDURE' in text: + result += 0.01 + if 'END' in text: + result += 0.01 + + return result diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/objective.py b/.venv/lib/python3.8/site-packages/pygments/lexers/objective.py new file mode 100644 index 0000000..a4cc44b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/objective.py @@ -0,0 +1,503 @@ +""" + pygments.lexers.objective + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Objective-C family languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, bygroups, using, this, words, \ + inherit, default +from pygments.token import Text, Keyword, Name, String, Operator, \ + Number, Punctuation, Literal, Comment + +from pygments.lexers.c_cpp import CLexer, CppLexer + +__all__ = ['ObjectiveCLexer', 'ObjectiveCppLexer', 'LogosLexer', 'SwiftLexer'] + + +def objective(baselexer): + """ + Generate a subclass of baselexer that accepts the Objective-C syntax + extensions. + """ + + # Have to be careful not to accidentally match JavaDoc/Doxygen syntax here, + # since that's quite common in ordinary C/C++ files. It's OK to match + # JavaDoc/Doxygen keywords that only apply to Objective-C, mind. + # + # The upshot of this is that we CANNOT match @class or @interface + _oc_keywords = re.compile(r'@(?:end|implementation|protocol)') + + # Matches [ ? identifier ( identifier ? ] | identifier? : ) + # (note the identifier is *optional* when there is a ':'!) + _oc_message = re.compile(r'\[\s*[a-zA-Z_]\w*\s+' + r'(?:[a-zA-Z_]\w*\s*\]|' + r'(?:[a-zA-Z_]\w*)?:)') + + class GeneratedObjectiveCVariant(baselexer): + """ + Implements Objective-C syntax on top of an existing C family lexer. + """ + + tokens = { + 'statements': [ + (r'@"', String, 'string'), + (r'@(YES|NO)', Number), + (r"@'(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])'", String.Char), + (r'@(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+[lL]?', Number.Float), + (r'@(\d+\.\d*|\.\d+|\d+[fF])[fF]?', Number.Float), + (r'@0x[0-9a-fA-F]+[Ll]?', Number.Hex), + (r'@0[0-7]+[Ll]?', Number.Oct), + (r'@\d+[Ll]?', Number.Integer), + (r'@\(', Literal, 'literal_number'), + (r'@\[', Literal, 'literal_array'), + (r'@\{', Literal, 'literal_dictionary'), + (words(( + '@selector', '@private', '@protected', '@public', '@encode', + '@synchronized', '@try', '@throw', '@catch', '@finally', + '@end', '@property', '@synthesize', '__bridge', '__bridge_transfer', + '__autoreleasing', '__block', '__weak', '__strong', 'weak', 'strong', + 'copy', 'retain', 'assign', 'unsafe_unretained', 'atomic', 'nonatomic', + 'readonly', 'readwrite', 'setter', 'getter', 'typeof', 'in', + 'out', 'inout', 'release', 'class', '@dynamic', '@optional', + '@required', '@autoreleasepool', '@import'), suffix=r'\b'), + Keyword), + (words(('id', 'instancetype', 'Class', 'IMP', 'SEL', 'BOOL', + 'IBOutlet', 'IBAction', 'unichar'), suffix=r'\b'), + Keyword.Type), + (r'@(true|false|YES|NO)\n', Name.Builtin), + (r'(YES|NO|nil|self|super)\b', Name.Builtin), + # Carbon types + (r'(Boolean|UInt8|SInt8|UInt16|SInt16|UInt32|SInt32)\b', Keyword.Type), + # Carbon built-ins + (r'(TRUE|FALSE)\b', Name.Builtin), + (r'(@interface|@implementation)(\s+)', bygroups(Keyword, Text), + ('#pop', 'oc_classname')), + (r'(@class|@protocol)(\s+)', bygroups(Keyword, Text), + ('#pop', 'oc_forward_classname')), + # @ can also prefix other expressions like @{...} or @(...) + (r'@', Punctuation), + inherit, + ], + 'oc_classname': [ + # interface definition that inherits + (r'([a-zA-Z$_][\w$]*)(\s*:\s*)([a-zA-Z$_][\w$]*)?(\s*)(\{)', + bygroups(Name.Class, Text, Name.Class, Text, Punctuation), + ('#pop', 'oc_ivars')), + (r'([a-zA-Z$_][\w$]*)(\s*:\s*)([a-zA-Z$_][\w$]*)?', + bygroups(Name.Class, Text, Name.Class), '#pop'), + # interface definition for a category + (r'([a-zA-Z$_][\w$]*)(\s*)(\([a-zA-Z$_][\w$]*\))(\s*)(\{)', + bygroups(Name.Class, Text, Name.Label, Text, Punctuation), + ('#pop', 'oc_ivars')), + (r'([a-zA-Z$_][\w$]*)(\s*)(\([a-zA-Z$_][\w$]*\))', + bygroups(Name.Class, Text, Name.Label), '#pop'), + # simple interface / implementation + (r'([a-zA-Z$_][\w$]*)(\s*)(\{)', + bygroups(Name.Class, Text, Punctuation), ('#pop', 'oc_ivars')), + (r'([a-zA-Z$_][\w$]*)', Name.Class, '#pop') + ], + 'oc_forward_classname': [ + (r'([a-zA-Z$_][\w$]*)(\s*,\s*)', + bygroups(Name.Class, Text), 'oc_forward_classname'), + (r'([a-zA-Z$_][\w$]*)(\s*;?)', + bygroups(Name.Class, Text), '#pop') + ], + 'oc_ivars': [ + include('whitespace'), + include('statements'), + (';', Punctuation), + (r'\{', Punctuation, '#push'), + (r'\}', Punctuation, '#pop'), + ], + 'root': [ + # methods + (r'^([-+])(\s*)' # method marker + r'(\(.*?\))?(\s*)' # return type + r'([a-zA-Z$_][\w$]*:?)', # begin of method name + bygroups(Punctuation, Text, using(this), + Text, Name.Function), + 'method'), + inherit, + ], + 'method': [ + include('whitespace'), + # TODO unsure if ellipses are allowed elsewhere, see + # discussion in Issue 789 + (r',', Punctuation), + (r'\.\.\.', Punctuation), + (r'(\(.*?\))(\s*)([a-zA-Z$_][\w$]*)', + bygroups(using(this), Text, Name.Variable)), + (r'[a-zA-Z$_][\w$]*:', Name.Function), + (';', Punctuation, '#pop'), + (r'\{', Punctuation, 'function'), + default('#pop'), + ], + 'literal_number': [ + (r'\(', Punctuation, 'literal_number_inner'), + (r'\)', Literal, '#pop'), + include('statement'), + ], + 'literal_number_inner': [ + (r'\(', Punctuation, '#push'), + (r'\)', Punctuation, '#pop'), + include('statement'), + ], + 'literal_array': [ + (r'\[', Punctuation, 'literal_array_inner'), + (r'\]', Literal, '#pop'), + include('statement'), + ], + 'literal_array_inner': [ + (r'\[', Punctuation, '#push'), + (r'\]', Punctuation, '#pop'), + include('statement'), + ], + 'literal_dictionary': [ + (r'\}', Literal, '#pop'), + include('statement'), + ], + } + + def analyse_text(text): + if _oc_keywords.search(text): + return 1.0 + elif '@"' in text: # strings + return 0.8 + elif re.search('@[0-9]+', text): + return 0.7 + elif _oc_message.search(text): + return 0.8 + return 0 + + def get_tokens_unprocessed(self, text): + from pygments.lexers._cocoa_builtins import COCOA_INTERFACES, \ + COCOA_PROTOCOLS, COCOA_PRIMITIVES + + for index, token, value in \ + baselexer.get_tokens_unprocessed(self, text): + if token is Name or token is Name.Class: + if value in COCOA_INTERFACES or value in COCOA_PROTOCOLS \ + or value in COCOA_PRIMITIVES: + token = Name.Builtin.Pseudo + + yield index, token, value + + return GeneratedObjectiveCVariant + + +class ObjectiveCLexer(objective(CLexer)): + """ + For Objective-C source code with preprocessor directives. + """ + + name = 'Objective-C' + aliases = ['objective-c', 'objectivec', 'obj-c', 'objc'] + filenames = ['*.m', '*.h'] + mimetypes = ['text/x-objective-c'] + priority = 0.05 # Lower than C + + +class ObjectiveCppLexer(objective(CppLexer)): + """ + For Objective-C++ source code with preprocessor directives. + """ + + name = 'Objective-C++' + aliases = ['objective-c++', 'objectivec++', 'obj-c++', 'objc++'] + filenames = ['*.mm', '*.hh'] + mimetypes = ['text/x-objective-c++'] + priority = 0.05 # Lower than C++ + + +class LogosLexer(ObjectiveCppLexer): + """ + For Logos + Objective-C source code with preprocessor directives. + + .. versionadded:: 1.6 + """ + + name = 'Logos' + aliases = ['logos'] + filenames = ['*.x', '*.xi', '*.xm', '*.xmi'] + mimetypes = ['text/x-logos'] + priority = 0.25 + + tokens = { + 'statements': [ + (r'(%orig|%log)\b', Keyword), + (r'(%c)\b(\()(\s*)([a-zA-Z$_][\w$]*)(\s*)(\))', + bygroups(Keyword, Punctuation, Text, Name.Class, Text, Punctuation)), + (r'(%init)\b(\()', + bygroups(Keyword, Punctuation), 'logos_init_directive'), + (r'(%init)(?=\s*;)', bygroups(Keyword)), + (r'(%hook|%group)(\s+)([a-zA-Z$_][\w$]+)', + bygroups(Keyword, Text, Name.Class), '#pop'), + (r'(%subclass)(\s+)', bygroups(Keyword, Text), + ('#pop', 'logos_classname')), + inherit, + ], + 'logos_init_directive': [ + (r'\s+', Text), + (',', Punctuation, ('logos_init_directive', '#pop')), + (r'([a-zA-Z$_][\w$]*)(\s*)(=)(\s*)([^);]*)', + bygroups(Name.Class, Text, Punctuation, Text, Text)), + (r'([a-zA-Z$_][\w$]*)', Name.Class), + (r'\)', Punctuation, '#pop'), + ], + 'logos_classname': [ + (r'([a-zA-Z$_][\w$]*)(\s*:\s*)([a-zA-Z$_][\w$]*)?', + bygroups(Name.Class, Text, Name.Class), '#pop'), + (r'([a-zA-Z$_][\w$]*)', Name.Class, '#pop') + ], + 'root': [ + (r'(%subclass)(\s+)', bygroups(Keyword, Text), + 'logos_classname'), + (r'(%hook|%group)(\s+)([a-zA-Z$_][\w$]+)', + bygroups(Keyword, Text, Name.Class)), + (r'(%config)(\s*\(\s*)(\w+)(\s*=)(.*?)(\)\s*)', + bygroups(Keyword, Text, Name.Variable, Text, String, Text)), + (r'(%ctor)(\s*)(\{)', bygroups(Keyword, Text, Punctuation), + 'function'), + (r'(%new)(\s*)(\()(.*?)(\))', + bygroups(Keyword, Text, Keyword, String, Keyword)), + (r'(\s*)(%end)(\s*)', bygroups(Text, Keyword, Text)), + inherit, + ], + } + + _logos_keywords = re.compile(r'%(?:hook|ctor|init|c\()') + + def analyse_text(text): + if LogosLexer._logos_keywords.search(text): + return 1.0 + return 0 + + +class SwiftLexer(RegexLexer): + """ + For `Swift `_ source. + + .. versionadded:: 2.0 + """ + name = 'Swift' + filenames = ['*.swift'] + aliases = ['swift'] + mimetypes = ['text/x-swift'] + + tokens = { + 'root': [ + # Whitespace and Comments + (r'\n', Text), + (r'\s+', Text), + (r'//', Comment.Single, 'comment-single'), + (r'/\*', Comment.Multiline, 'comment-multi'), + (r'#(if|elseif|else|endif|available)\b', Comment.Preproc, 'preproc'), + + # Keywords + include('keywords'), + + # Global Types + (words(( + 'Array', 'AutoreleasingUnsafeMutablePointer', 'BidirectionalReverseView', + 'Bit', 'Bool', 'CFunctionPointer', 'COpaquePointer', 'CVaListPointer', + 'Character', 'ClosedInterval', 'CollectionOfOne', 'ContiguousArray', + 'Dictionary', 'DictionaryGenerator', 'DictionaryIndex', 'Double', + 'EmptyCollection', 'EmptyGenerator', 'EnumerateGenerator', + 'EnumerateSequence', 'FilterCollectionView', + 'FilterCollectionViewIndex', 'FilterGenerator', 'FilterSequenceView', + 'Float', 'Float80', 'FloatingPointClassification', 'GeneratorOf', + 'GeneratorOfOne', 'GeneratorSequence', 'HalfOpenInterval', 'HeapBuffer', + 'HeapBufferStorage', 'ImplicitlyUnwrappedOptional', 'IndexingGenerator', + 'Int', 'Int16', 'Int32', 'Int64', 'Int8', 'LazyBidirectionalCollection', + 'LazyForwardCollection', 'LazyRandomAccessCollection', + 'LazySequence', 'MapCollectionView', 'MapSequenceGenerator', + 'MapSequenceView', 'MirrorDisposition', 'ObjectIdentifier', 'OnHeap', + 'Optional', 'PermutationGenerator', 'QuickLookObject', + 'RandomAccessReverseView', 'Range', 'RangeGenerator', 'RawByte', 'Repeat', + 'ReverseBidirectionalIndex', 'ReverseRandomAccessIndex', 'SequenceOf', + 'SinkOf', 'Slice', 'StaticString', 'StrideThrough', 'StrideThroughGenerator', + 'StrideTo', 'StrideToGenerator', 'String', 'UInt', 'UInt16', 'UInt32', + 'UInt64', 'UInt8', 'UTF16', 'UTF32', 'UTF8', 'UnicodeDecodingResult', + 'UnicodeScalar', 'Unmanaged', 'UnsafeBufferPointer', + 'UnsafeBufferPointerGenerator', 'UnsafeMutableBufferPointer', + 'UnsafeMutablePointer', 'UnsafePointer', 'Zip2', 'ZipGenerator2', + # Protocols + 'AbsoluteValuable', 'AnyObject', 'ArrayLiteralConvertible', + 'BidirectionalIndexType', 'BitwiseOperationsType', + 'BooleanLiteralConvertible', 'BooleanType', 'CVarArgType', + 'CollectionType', 'Comparable', 'DebugPrintable', + 'DictionaryLiteralConvertible', 'Equatable', + 'ExtendedGraphemeClusterLiteralConvertible', + 'ExtensibleCollectionType', 'FloatLiteralConvertible', + 'FloatingPointType', 'ForwardIndexType', 'GeneratorType', 'Hashable', + 'IntegerArithmeticType', 'IntegerLiteralConvertible', 'IntegerType', + 'IntervalType', 'MirrorType', 'MutableCollectionType', 'MutableSliceable', + 'NilLiteralConvertible', 'OutputStreamType', 'Printable', + 'RandomAccessIndexType', 'RangeReplaceableCollectionType', + 'RawOptionSetType', 'RawRepresentable', 'Reflectable', 'SequenceType', + 'SignedIntegerType', 'SignedNumberType', 'SinkType', 'Sliceable', + 'Streamable', 'Strideable', 'StringInterpolationConvertible', + 'StringLiteralConvertible', 'UnicodeCodecType', + 'UnicodeScalarLiteralConvertible', 'UnsignedIntegerType', + '_ArrayBufferType', '_BidirectionalIndexType', '_CocoaStringType', + '_CollectionType', '_Comparable', '_ExtensibleCollectionType', + '_ForwardIndexType', '_Incrementable', '_IntegerArithmeticType', + '_IntegerType', '_ObjectiveCBridgeable', '_RandomAccessIndexType', + '_RawOptionSetType', '_SequenceType', '_Sequence_Type', + '_SignedIntegerType', '_SignedNumberType', '_Sliceable', '_Strideable', + '_SwiftNSArrayRequiredOverridesType', '_SwiftNSArrayType', + '_SwiftNSCopyingType', '_SwiftNSDictionaryRequiredOverridesType', + '_SwiftNSDictionaryType', '_SwiftNSEnumeratorType', + '_SwiftNSFastEnumerationType', '_SwiftNSStringRequiredOverridesType', + '_SwiftNSStringType', '_UnsignedIntegerType', + # Variables + 'C_ARGC', 'C_ARGV', 'Process', + # Typealiases + 'Any', 'AnyClass', 'BooleanLiteralType', 'CBool', 'CChar', 'CChar16', + 'CChar32', 'CDouble', 'CFloat', 'CInt', 'CLong', 'CLongLong', 'CShort', + 'CSignedChar', 'CUnsignedInt', 'CUnsignedLong', 'CUnsignedShort', + 'CWideChar', 'ExtendedGraphemeClusterType', 'Float32', 'Float64', + 'FloatLiteralType', 'IntMax', 'IntegerLiteralType', 'StringLiteralType', + 'UIntMax', 'UWord', 'UnicodeScalarType', 'Void', 'Word', + # Foundation/Cocoa + 'NSErrorPointer', 'NSObjectProtocol', 'Selector'), suffix=r'\b'), + Name.Builtin), + # Functions + (words(( + 'abs', 'advance', 'alignof', 'alignofValue', 'assert', 'assertionFailure', + 'contains', 'count', 'countElements', 'debugPrint', 'debugPrintln', + 'distance', 'dropFirst', 'dropLast', 'dump', 'enumerate', 'equal', + 'extend', 'fatalError', 'filter', 'find', 'first', 'getVaList', 'indices', + 'insert', 'isEmpty', 'join', 'last', 'lazy', 'lexicographicalCompare', + 'map', 'max', 'maxElement', 'min', 'minElement', 'numericCast', 'overlaps', + 'partition', 'precondition', 'preconditionFailure', 'prefix', 'print', + 'println', 'reduce', 'reflect', 'removeAll', 'removeAtIndex', 'removeLast', + 'removeRange', 'reverse', 'sizeof', 'sizeofValue', 'sort', 'sorted', + 'splice', 'split', 'startsWith', 'stride', 'strideof', 'strideofValue', + 'suffix', 'swap', 'toDebugString', 'toString', 'transcode', + 'underestimateCount', 'unsafeAddressOf', 'unsafeBitCast', 'unsafeDowncast', + 'withExtendedLifetime', 'withUnsafeMutablePointer', + 'withUnsafeMutablePointers', 'withUnsafePointer', 'withUnsafePointers', + 'withVaList'), suffix=r'\b'), + Name.Builtin.Pseudo), + + # Implicit Block Variables + (r'\$\d+', Name.Variable), + + # Binary Literal + (r'0b[01_]+', Number.Bin), + # Octal Literal + (r'0o[0-7_]+', Number.Oct), + # Hexadecimal Literal + (r'0x[0-9a-fA-F_]+', Number.Hex), + # Decimal Literal + (r'[0-9][0-9_]*(\.[0-9_]+[eE][+\-]?[0-9_]+|' + r'\.[0-9_]*|[eE][+\-]?[0-9_]+)', Number.Float), + (r'[0-9][0-9_]*', Number.Integer), + # String Literal + (r'"', String, 'string'), + + # Operators and Punctuation + (r'[(){}\[\].,:;=@#`?]|->|[<&?](?=\w)|(?<=\w)[>!?]', Punctuation), + (r'[/=\-+!*%<>&|^?~]+', Operator), + + # Identifier + (r'[a-zA-Z_]\w*', Name) + ], + 'keywords': [ + (words(( + 'as', 'async', 'await', 'break', 'case', 'catch', 'continue', 'default', 'defer', + 'do', 'else', 'fallthrough', 'for', 'guard', 'if', 'in', 'is', + 'repeat', 'return', '#selector', 'switch', 'throw', 'try', + 'where', 'while'), suffix=r'\b'), + Keyword), + (r'@availability\([^)]+\)', Keyword.Reserved), + (words(( + 'associativity', 'convenience', 'dynamic', 'didSet', 'final', + 'get', 'indirect', 'infix', 'inout', 'lazy', 'left', 'mutating', + 'none', 'nonmutating', 'optional', 'override', 'postfix', + 'precedence', 'prefix', 'Protocol', 'required', 'rethrows', + 'right', 'set', 'throws', 'Type', 'unowned', 'weak', 'willSet', + '@availability', '@autoclosure', '@noreturn', + '@NSApplicationMain', '@NSCopying', '@NSManaged', '@objc', + '@UIApplicationMain', '@IBAction', '@IBDesignable', + '@IBInspectable', '@IBOutlet'), suffix=r'\b'), + Keyword.Reserved), + (r'(as|dynamicType|false|is|nil|self|Self|super|true|__COLUMN__' + r'|__FILE__|__FUNCTION__|__LINE__|_' + r'|#(?:file|line|column|function))\b', Keyword.Constant), + (r'import\b', Keyword.Declaration, 'module'), + (r'(class|enum|extension|struct|protocol)(\s+)([a-zA-Z_]\w*)', + bygroups(Keyword.Declaration, Text, Name.Class)), + (r'(func)(\s+)([a-zA-Z_]\w*)', + bygroups(Keyword.Declaration, Text, Name.Function)), + (r'(var|let)(\s+)([a-zA-Z_]\w*)', bygroups(Keyword.Declaration, + Text, Name.Variable)), + (words(( + 'actor', 'associatedtype', 'class', 'deinit', 'enum', 'extension', 'func', 'import', + 'init', 'internal', 'let', 'operator', 'private', 'protocol', 'public', + 'static', 'struct', 'subscript', 'typealias', 'var'), suffix=r'\b'), + Keyword.Declaration) + ], + 'comment': [ + (r':param: [a-zA-Z_]\w*|:returns?:|(FIXME|MARK|TODO):', + Comment.Special) + ], + + # Nested + 'comment-single': [ + (r'\n', Text, '#pop'), + include('comment'), + (r'[^\n]', Comment.Single) + ], + 'comment-multi': [ + include('comment'), + (r'[^*/]', Comment.Multiline), + (r'/\*', Comment.Multiline, '#push'), + (r'\*/', Comment.Multiline, '#pop'), + (r'[*/]', Comment.Multiline) + ], + 'module': [ + (r'\n', Text, '#pop'), + (r'[a-zA-Z_]\w*', Name.Class), + include('root') + ], + 'preproc': [ + (r'\n', Text, '#pop'), + include('keywords'), + (r'[A-Za-z]\w*', Comment.Preproc), + include('root') + ], + 'string': [ + (r'\\\(', String.Interpol, 'string-intp'), + (r'"', String, '#pop'), + (r"""\\['"\\nrt]|\\x[0-9a-fA-F]{2}|\\[0-7]{1,3}""" + r"""|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}""", String.Escape), + (r'[^\\"]+', String), + (r'\\', String) + ], + 'string-intp': [ + (r'\(', String.Interpol, '#push'), + (r'\)', String.Interpol, '#pop'), + include('root') + ] + } + + def get_tokens_unprocessed(self, text): + from pygments.lexers._cocoa_builtins import COCOA_INTERFACES, \ + COCOA_PROTOCOLS, COCOA_PRIMITIVES + + for index, token, value in \ + RegexLexer.get_tokens_unprocessed(self, text): + if token is Name or token is Name.Class: + if value in COCOA_INTERFACES or value in COCOA_PROTOCOLS \ + or value in COCOA_PRIMITIVES: + token = Name.Builtin.Pseudo + + yield index, token, value diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/ooc.py b/.venv/lib/python3.8/site-packages/pygments/lexers/ooc.py new file mode 100644 index 0000000..0c74cde --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/ooc.py @@ -0,0 +1,84 @@ +""" + pygments.lexers.ooc + ~~~~~~~~~~~~~~~~~~~ + + Lexers for the Ooc language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, bygroups, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation + +__all__ = ['OocLexer'] + + +class OocLexer(RegexLexer): + """ + For `Ooc `_ source code + + .. versionadded:: 1.2 + """ + name = 'Ooc' + aliases = ['ooc'] + filenames = ['*.ooc'] + mimetypes = ['text/x-ooc'] + + tokens = { + 'root': [ + (words(( + 'class', 'interface', 'implement', 'abstract', 'extends', 'from', + 'this', 'super', 'new', 'const', 'final', 'static', 'import', + 'use', 'extern', 'inline', 'proto', 'break', 'continue', + 'fallthrough', 'operator', 'if', 'else', 'for', 'while', 'do', + 'switch', 'case', 'as', 'in', 'version', 'return', 'true', + 'false', 'null'), prefix=r'\b', suffix=r'\b'), + Keyword), + (r'include\b', Keyword, 'include'), + (r'(cover)([ \t]+)(from)([ \t]+)(\w+[*@]?)', + bygroups(Keyword, Text, Keyword, Text, Name.Class)), + (r'(func)((?:[ \t]|\\\n)+)(~[a-z_]\w*)', + bygroups(Keyword, Text, Name.Function)), + (r'\bfunc\b', Keyword), + # Note: %= and ^= not listed on http://ooc-lang.org/syntax + (r'//.*', Comment), + (r'(?s)/\*.*?\*/', Comment.Multiline), + (r'(==?|\+=?|-[=>]?|\*=?|/=?|:=|!=?|%=?|\?|>{1,3}=?|<{1,3}=?|\.\.|' + r'&&?|\|\|?|\^=?)', Operator), + (r'(\.)([ \t]*)([a-z]\w*)', bygroups(Operator, Text, + Name.Function)), + (r'[A-Z][A-Z0-9_]+', Name.Constant), + (r'[A-Z]\w*([@*]|\[[ \t]*\])?', Name.Class), + + (r'([a-z]\w*(?:~[a-z]\w*)?)((?:[ \t]|\\\n)*)(?=\()', + bygroups(Name.Function, Text)), + (r'[a-z]\w*', Name.Variable), + + # : introduces types + (r'[:(){}\[\];,]', Punctuation), + + (r'0x[0-9a-fA-F]+', Number.Hex), + (r'0c[0-9]+', Number.Oct), + (r'0b[01]+', Number.Bin), + (r'[0-9_]\.[0-9_]*(?!\.)', Number.Float), + (r'[0-9_]+', Number.Decimal), + + (r'"(?:\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\"])*"', + String.Double), + (r"'(?:\\.|\\[0-9]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])'", + String.Char), + (r'@', Punctuation), # pointer dereference + (r'\.', Punctuation), # imports or chain operator + + (r'\\[ \t\n]', Text), + (r'[ \t]+', Text), + ], + 'include': [ + (r'[\w/]+', Name), + (r',', Punctuation), + (r'[ \t]', Text), + (r'[;\n]', Text, '#pop'), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/other.py b/.venv/lib/python3.8/site-packages/pygments/lexers/other.py new file mode 100644 index 0000000..b093008 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/other.py @@ -0,0 +1,40 @@ +""" + pygments.lexers.other + ~~~~~~~~~~~~~~~~~~~~~ + + Just export lexer classes previously contained in this module. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexers.sql import SqlLexer, MySqlLexer, SqliteConsoleLexer +from pygments.lexers.shell import BashLexer, BashSessionLexer, BatchLexer, \ + TcshLexer +from pygments.lexers.robotframework import RobotFrameworkLexer +from pygments.lexers.testing import GherkinLexer +from pygments.lexers.esoteric import BrainfuckLexer, BefungeLexer, RedcodeLexer +from pygments.lexers.prolog import LogtalkLexer +from pygments.lexers.snobol import SnobolLexer +from pygments.lexers.rebol import RebolLexer +from pygments.lexers.configs import KconfigLexer, Cfengine3Lexer +from pygments.lexers.modeling import ModelicaLexer +from pygments.lexers.scripting import AppleScriptLexer, MOOCodeLexer, \ + HybrisLexer +from pygments.lexers.graphics import PostScriptLexer, GnuplotLexer, \ + AsymptoteLexer, PovrayLexer +from pygments.lexers.business import ABAPLexer, OpenEdgeLexer, \ + GoodDataCLLexer, MaqlLexer +from pygments.lexers.automation import AutoItLexer, AutohotkeyLexer +from pygments.lexers.dsls import ProtoBufLexer, BroLexer, PuppetLexer, \ + MscgenLexer, VGLLexer +from pygments.lexers.basic import CbmBasicV2Lexer +from pygments.lexers.pawn import SourcePawnLexer, PawnLexer +from pygments.lexers.ecl import ECLLexer +from pygments.lexers.urbi import UrbiscriptLexer +from pygments.lexers.smalltalk import SmalltalkLexer, NewspeakLexer +from pygments.lexers.installers import NSISLexer, RPMSpecLexer +from pygments.lexers.textedit import AwkLexer +from pygments.lexers.smv import NuSMVLexer + +__all__ = [] diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/parasail.py b/.venv/lib/python3.8/site-packages/pygments/lexers/parasail.py new file mode 100644 index 0000000..49d8d67 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/parasail.py @@ -0,0 +1,78 @@ +""" + pygments.lexers.parasail + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexer for ParaSail. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Literal + +__all__ = ['ParaSailLexer'] + + +class ParaSailLexer(RegexLexer): + """ + For `ParaSail `_ source code. + + .. versionadded:: 2.1 + """ + + name = 'ParaSail' + aliases = ['parasail'] + filenames = ['*.psi', '*.psl'] + mimetypes = ['text/x-parasail'] + + flags = re.MULTILINE + + tokens = { + 'root': [ + (r'[^\S\n]+', Text), + (r'//.*?\n', Comment.Single), + (r'\b(and|or|xor)=', Operator.Word), + (r'\b(and(\s+then)?|or(\s+else)?|xor|rem|mod|' + r'(is|not)\s+null)\b', + Operator.Word), + # Keywords + (r'\b(abs|abstract|all|block|class|concurrent|const|continue|' + r'each|end|exit|extends|exports|forward|func|global|implements|' + r'import|in|interface|is|lambda|locked|new|not|null|of|op|' + r'optional|private|queued|ref|return|reverse|separate|some|' + r'type|until|var|with|' + # Control flow + r'if|then|else|elsif|case|for|while|loop)\b', + Keyword.Reserved), + (r'(abstract\s+)?(interface|class|op|func|type)', + Keyword.Declaration), + # Literals + (r'"[^"]*"', String), + (r'\\[\'ntrf"0]', String.Escape), + (r'#[a-zA-Z]\w*', Literal), # Enumeration + include('numbers'), + (r"'[^']'", String.Char), + (r'[a-zA-Z]\w*', Name), + # Operators and Punctuation + (r'(<==|==>|<=>|\*\*=|<\|=|<<=|>>=|==|!=|=\?|<=|>=|' + r'\*\*|<<|>>|=>|:=|\+=|-=|\*=|\|=|\||/=|\+|-|\*|/|' + r'\.\.|<\.\.|\.\.<|<\.\.<)', + Operator), + (r'(<|>|\[|\]|\(|\)|\||:|;|,|.|\{|\}|->)', + Punctuation), + (r'\n+', Text), + ], + 'numbers': [ + (r'\d[0-9_]*#[0-9a-fA-F][0-9a-fA-F_]*#', Number.Hex), # any base + (r'0[xX][0-9a-fA-F][0-9a-fA-F_]*', Number.Hex), # C-like hex + (r'0[bB][01][01_]*', Number.Bin), # C-like bin + (r'\d[0-9_]*\.\d[0-9_]*[eE][+-]\d[0-9_]*', # float exp + Number.Float), + (r'\d[0-9_]*\.\d[0-9_]*', Number.Float), # float + (r'\d[0-9_]*', Number.Integer), # integer + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/parsers.py b/.venv/lib/python3.8/site-packages/pygments/lexers/parsers.py new file mode 100644 index 0000000..0009082 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/parsers.py @@ -0,0 +1,799 @@ +""" + pygments.lexers.parsers + ~~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for parser generators. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, DelegatingLexer, \ + include, bygroups, using +from pygments.token import Punctuation, Other, Text, Comment, Operator, \ + Keyword, Name, String, Number, Whitespace +from pygments.lexers.jvm import JavaLexer +from pygments.lexers.c_cpp import CLexer, CppLexer +from pygments.lexers.objective import ObjectiveCLexer +from pygments.lexers.d import DLexer +from pygments.lexers.dotnet import CSharpLexer +from pygments.lexers.ruby import RubyLexer +from pygments.lexers.python import PythonLexer +from pygments.lexers.perl import PerlLexer + +__all__ = ['RagelLexer', 'RagelEmbeddedLexer', 'RagelCLexer', 'RagelDLexer', + 'RagelCppLexer', 'RagelObjectiveCLexer', 'RagelRubyLexer', + 'RagelJavaLexer', 'AntlrLexer', 'AntlrPythonLexer', + 'AntlrPerlLexer', 'AntlrRubyLexer', 'AntlrCppLexer', + 'AntlrCSharpLexer', 'AntlrObjectiveCLexer', + 'AntlrJavaLexer', 'AntlrActionScriptLexer', + 'TreetopLexer', 'EbnfLexer'] + + +class RagelLexer(RegexLexer): + """ + A pure `Ragel `_ lexer. Use this for + fragments of Ragel. For ``.rl`` files, use RagelEmbeddedLexer instead + (or one of the language-specific subclasses). + + .. versionadded:: 1.1 + """ + + name = 'Ragel' + aliases = ['ragel'] + filenames = [] + + tokens = { + 'whitespace': [ + (r'\s+', Whitespace) + ], + 'comments': [ + (r'\#.*$', Comment), + ], + 'keywords': [ + (r'(access|action|alphtype)\b', Keyword), + (r'(getkey|write|machine|include)\b', Keyword), + (r'(any|ascii|extend|alpha|digit|alnum|lower|upper)\b', Keyword), + (r'(xdigit|cntrl|graph|print|punct|space|zlen|empty)\b', Keyword) + ], + 'numbers': [ + (r'0x[0-9A-Fa-f]+', Number.Hex), + (r'[+-]?[0-9]+', Number.Integer), + ], + 'literals': [ + (r'"(\\\\|\\[^\\]|[^"\\])*"', String.Double), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String.Single), + (r'\[(\\\\|\\[^\\]|[^\\\]])*\]', String), # square bracket literals + (r'/(?!\*)(\\\\|\\[^\\]|[^/\\])*/', String.Regex), # regular expressions + ], + 'identifiers': [ + (r'[a-zA-Z_]\w*', Name.Variable), + ], + 'operators': [ + (r',', Operator), # Join + (r'\||&|--?', Operator), # Union, Intersection and Subtraction + (r'\.|<:|:>>?', Operator), # Concatention + (r':', Operator), # Label + (r'->', Operator), # Epsilon Transition + (r'(>|\$|%|<|@|<>)(/|eof\b)', Operator), # EOF Actions + (r'(>|\$|%|<|@|<>)(!|err\b)', Operator), # Global Error Actions + (r'(>|\$|%|<|@|<>)(\^|lerr\b)', Operator), # Local Error Actions + (r'(>|\$|%|<|@|<>)(~|to\b)', Operator), # To-State Actions + (r'(>|\$|%|<|@|<>)(\*|from\b)', Operator), # From-State Actions + (r'>|@|\$|%', Operator), # Transition Actions and Priorities + (r'\*|\?|\+|\{[0-9]*,[0-9]*\}', Operator), # Repetition + (r'!|\^', Operator), # Negation + (r'\(|\)', Operator), # Grouping + ], + 'root': [ + include('literals'), + include('whitespace'), + include('comments'), + include('keywords'), + include('numbers'), + include('identifiers'), + include('operators'), + (r'\{', Punctuation, 'host'), + (r'=', Operator), + (r';', Punctuation), + ], + 'host': [ + (r'(' + r'|'.join(( # keep host code in largest possible chunks + r'[^{}\'"/#]+', # exclude unsafe characters + r'[^\\]\\[{}]', # allow escaped { or } + + # strings and comments may safely contain unsafe characters + r'"(\\\\|\\[^\\]|[^"\\])*"', + r"'(\\\\|\\[^\\]|[^'\\])*'", + r'//.*$\n?', # single line comment + r'/\*(.|\n)*?\*/', # multi-line javadoc-style comment + r'\#.*$\n?', # ruby comment + + # regular expression: There's no reason for it to start + # with a * and this stops confusion with comments. + r'/(?!\*)(\\\\|\\[^\\]|[^/\\])*/', + + # / is safe now that we've handled regex and javadoc comments + r'/', + )) + r')+', Other), + + (r'\{', Punctuation, '#push'), + (r'\}', Punctuation, '#pop'), + ], + } + + +class RagelEmbeddedLexer(RegexLexer): + """ + A lexer for `Ragel`_ embedded in a host language file. + + This will only highlight Ragel statements. If you want host language + highlighting then call the language-specific Ragel lexer. + + .. versionadded:: 1.1 + """ + + name = 'Embedded Ragel' + aliases = ['ragel-em'] + filenames = ['*.rl'] + + tokens = { + 'root': [ + (r'(' + r'|'.join(( # keep host code in largest possible chunks + r'[^%\'"/#]+', # exclude unsafe characters + r'%(?=[^%]|$)', # a single % sign is okay, just not 2 of them + + # strings and comments may safely contain unsafe characters + r'"(\\\\|\\[^\\]|[^"\\])*"', + r"'(\\\\|\\[^\\]|[^'\\])*'", + r'/\*(.|\n)*?\*/', # multi-line javadoc-style comment + r'//.*$\n?', # single line comment + r'\#.*$\n?', # ruby/ragel comment + r'/(?!\*)(\\\\|\\[^\\]|[^/\\])*/', # regular expression + + # / is safe now that we've handled regex and javadoc comments + r'/', + )) + r')+', Other), + + # Single Line FSM. + # Please don't put a quoted newline in a single line FSM. + # That's just mean. It will break this. + (r'(%%)(?![{%])(.*)($|;)(\n?)', bygroups(Punctuation, + using(RagelLexer), + Punctuation, Text)), + + # Multi Line FSM. + (r'(%%%%|%%)\{', Punctuation, 'multi-line-fsm'), + ], + 'multi-line-fsm': [ + (r'(' + r'|'.join(( # keep ragel code in largest possible chunks. + r'(' + r'|'.join(( + r'[^}\'"\[/#]', # exclude unsafe characters + r'\}(?=[^%]|$)', # } is okay as long as it's not followed by % + r'\}%(?=[^%]|$)', # ...well, one %'s okay, just not two... + r'[^\\]\\[{}]', # ...and } is okay if it's escaped + + # allow / if it's preceded with one of these symbols + # (ragel EOF actions) + r'(>|\$|%|<|@|<>)/', + + # specifically allow regex followed immediately by * + # so it doesn't get mistaken for a comment + r'/(?!\*)(\\\\|\\[^\\]|[^/\\])*/\*', + + # allow / as long as it's not followed by another / or by a * + r'/(?=[^/*]|$)', + + # We want to match as many of these as we can in one block. + # Not sure if we need the + sign here, + # does it help performance? + )) + r')+', + + # strings and comments may safely contain unsafe characters + r'"(\\\\|\\[^\\]|[^"\\])*"', + r"'(\\\\|\\[^\\]|[^'\\])*'", + r"\[(\\\\|\\[^\\]|[^\]\\])*\]", # square bracket literal + r'/\*(.|\n)*?\*/', # multi-line javadoc-style comment + r'//.*$\n?', # single line comment + r'\#.*$\n?', # ruby/ragel comment + )) + r')+', using(RagelLexer)), + + (r'\}%%', Punctuation, '#pop'), + ] + } + + def analyse_text(text): + return '@LANG: indep' in text + + +class RagelRubyLexer(DelegatingLexer): + """ + A lexer for `Ragel`_ in a Ruby host file. + + .. versionadded:: 1.1 + """ + + name = 'Ragel in Ruby Host' + aliases = ['ragel-ruby', 'ragel-rb'] + filenames = ['*.rl'] + + def __init__(self, **options): + super().__init__(RubyLexer, RagelEmbeddedLexer, **options) + + def analyse_text(text): + return '@LANG: ruby' in text + + +class RagelCLexer(DelegatingLexer): + """ + A lexer for `Ragel`_ in a C host file. + + .. versionadded:: 1.1 + """ + + name = 'Ragel in C Host' + aliases = ['ragel-c'] + filenames = ['*.rl'] + + def __init__(self, **options): + super().__init__(CLexer, RagelEmbeddedLexer, **options) + + def analyse_text(text): + return '@LANG: c' in text + + +class RagelDLexer(DelegatingLexer): + """ + A lexer for `Ragel`_ in a D host file. + + .. versionadded:: 1.1 + """ + + name = 'Ragel in D Host' + aliases = ['ragel-d'] + filenames = ['*.rl'] + + def __init__(self, **options): + super().__init__(DLexer, RagelEmbeddedLexer, **options) + + def analyse_text(text): + return '@LANG: d' in text + + +class RagelCppLexer(DelegatingLexer): + """ + A lexer for `Ragel`_ in a CPP host file. + + .. versionadded:: 1.1 + """ + + name = 'Ragel in CPP Host' + aliases = ['ragel-cpp'] + filenames = ['*.rl'] + + def __init__(self, **options): + super().__init__(CppLexer, RagelEmbeddedLexer, **options) + + def analyse_text(text): + return '@LANG: c++' in text + + +class RagelObjectiveCLexer(DelegatingLexer): + """ + A lexer for `Ragel`_ in an Objective C host file. + + .. versionadded:: 1.1 + """ + + name = 'Ragel in Objective C Host' + aliases = ['ragel-objc'] + filenames = ['*.rl'] + + def __init__(self, **options): + super().__init__(ObjectiveCLexer, RagelEmbeddedLexer, **options) + + def analyse_text(text): + return '@LANG: objc' in text + + +class RagelJavaLexer(DelegatingLexer): + """ + A lexer for `Ragel`_ in a Java host file. + + .. versionadded:: 1.1 + """ + + name = 'Ragel in Java Host' + aliases = ['ragel-java'] + filenames = ['*.rl'] + + def __init__(self, **options): + super().__init__(JavaLexer, RagelEmbeddedLexer, **options) + + def analyse_text(text): + return '@LANG: java' in text + + +class AntlrLexer(RegexLexer): + """ + Generic `ANTLR`_ Lexer. + Should not be called directly, instead + use DelegatingLexer for your target language. + + .. versionadded:: 1.1 + + .. _ANTLR: http://www.antlr.org/ + """ + + name = 'ANTLR' + aliases = ['antlr'] + filenames = [] + + _id = r'[A-Za-z]\w*' + _TOKEN_REF = r'[A-Z]\w*' + _RULE_REF = r'[a-z]\w*' + _STRING_LITERAL = r'\'(?:\\\\|\\\'|[^\']*)\'' + _INT = r'[0-9]+' + + tokens = { + 'whitespace': [ + (r'\s+', Whitespace), + ], + 'comments': [ + (r'//.*$', Comment), + (r'/\*(.|\n)*?\*/', Comment), + ], + 'root': [ + include('whitespace'), + include('comments'), + + (r'(lexer|parser|tree)?(\s*)(grammar\b)(\s*)(' + _id + ')(;)', + bygroups(Keyword, Whitespace, Keyword, Whitespace, Name.Class, + Punctuation)), + # optionsSpec + (r'options\b', Keyword, 'options'), + # tokensSpec + (r'tokens\b', Keyword, 'tokens'), + # attrScope + (r'(scope)(\s*)(' + _id + r')(\s*)(\{)', + bygroups(Keyword, Whitespace, Name.Variable, Whitespace, + Punctuation), 'action'), + # exception + (r'(catch|finally)\b', Keyword, 'exception'), + # action + (r'(@' + _id + r')(\s*)(::)?(\s*)(' + _id + r')(\s*)(\{)', + bygroups(Name.Label, Whitespace, Punctuation, Whitespace, + Name.Label, Whitespace, Punctuation), 'action'), + # rule + (r'((?:protected|private|public|fragment)\b)?(\s*)(' + _id + ')(!)?', + bygroups(Keyword, Whitespace, Name.Label, Punctuation), + ('rule-alts', 'rule-prelims')), + ], + 'exception': [ + (r'\n', Whitespace, '#pop'), + (r'\s', Whitespace), + include('comments'), + + (r'\[', Punctuation, 'nested-arg-action'), + (r'\{', Punctuation, 'action'), + ], + 'rule-prelims': [ + include('whitespace'), + include('comments'), + + (r'returns\b', Keyword), + (r'\[', Punctuation, 'nested-arg-action'), + (r'\{', Punctuation, 'action'), + # throwsSpec + (r'(throws)(\s+)(' + _id + ')', + bygroups(Keyword, Whitespace, Name.Label)), + (r'(,)(\s*)(' + _id + ')', + bygroups(Punctuation, Whitespace, Name.Label)), # Additional throws + # optionsSpec + (r'options\b', Keyword, 'options'), + # ruleScopeSpec - scope followed by target language code or name of action + # TODO finish implementing other possibilities for scope + # L173 ANTLRv3.g from ANTLR book + (r'(scope)(\s+)(\{)', bygroups(Keyword, Whitespace, Punctuation), + 'action'), + (r'(scope)(\s+)(' + _id + r')(\s*)(;)', + bygroups(Keyword, Whitespace, Name.Label, Whitespace, Punctuation)), + # ruleAction + (r'(@' + _id + r')(\s*)(\{)', + bygroups(Name.Label, Whitespace, Punctuation), 'action'), + # finished prelims, go to rule alts! + (r':', Punctuation, '#pop') + ], + 'rule-alts': [ + include('whitespace'), + include('comments'), + + # These might need to go in a separate 'block' state triggered by ( + (r'options\b', Keyword, 'options'), + (r':', Punctuation), + + # literals + (r'"(\\\\|\\[^\\]|[^"\\])*"', String.Double), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String.Single), + (r'<<([^>]|>[^>])>>', String), + # identifiers + # Tokens start with capital letter. + (r'\$?[A-Z_]\w*', Name.Constant), + # Rules start with small letter. + (r'\$?[a-z_]\w*', Name.Variable), + # operators + (r'(\+|\||->|=>|=|\(|\)|\.\.|\.|\?|\*|\^|!|\#|~)', Operator), + (r',', Punctuation), + (r'\[', Punctuation, 'nested-arg-action'), + (r'\{', Punctuation, 'action'), + (r';', Punctuation, '#pop') + ], + 'tokens': [ + include('whitespace'), + include('comments'), + (r'\{', Punctuation), + (r'(' + _TOKEN_REF + r')(\s*)(=)?(\s*)(' + _STRING_LITERAL + + r')?(\s*)(;)', + bygroups(Name.Label, Whitespace, Punctuation, Whitespace, + String, Whitespace, Punctuation)), + (r'\}', Punctuation, '#pop'), + ], + 'options': [ + include('whitespace'), + include('comments'), + (r'\{', Punctuation), + (r'(' + _id + r')(\s*)(=)(\s*)(' + + '|'.join((_id, _STRING_LITERAL, _INT, r'\*')) + r')(\s*)(;)', + bygroups(Name.Variable, Whitespace, Punctuation, Whitespace, + Text, Whitespace, Punctuation)), + (r'\}', Punctuation, '#pop'), + ], + 'action': [ + (r'(' + r'|'.join(( # keep host code in largest possible chunks + r'[^${}\'"/\\]+', # exclude unsafe characters + + # strings and comments may safely contain unsafe characters + r'"(\\\\|\\[^\\]|[^"\\])*"', + r"'(\\\\|\\[^\\]|[^'\\])*'", + r'//.*$\n?', # single line comment + r'/\*(.|\n)*?\*/', # multi-line javadoc-style comment + + # regular expression: There's no reason for it to start + # with a * and this stops confusion with comments. + r'/(?!\*)(\\\\|\\[^\\]|[^/\\])*/', + + # backslashes are okay, as long as we are not backslashing a % + r'\\(?!%)', + + # Now that we've handled regex and javadoc comments + # it's safe to let / through. + r'/', + )) + r')+', Other), + (r'(\\)(%)', bygroups(Punctuation, Other)), + (r'(\$[a-zA-Z]+)(\.?)(text|value)?', + bygroups(Name.Variable, Punctuation, Name.Property)), + (r'\{', Punctuation, '#push'), + (r'\}', Punctuation, '#pop'), + ], + 'nested-arg-action': [ + (r'(' + r'|'.join(( # keep host code in largest possible chunks. + r'[^$\[\]\'"/]+', # exclude unsafe characters + + # strings and comments may safely contain unsafe characters + r'"(\\\\|\\[^\\]|[^"\\])*"', + r"'(\\\\|\\[^\\]|[^'\\])*'", + r'//.*$\n?', # single line comment + r'/\*(.|\n)*?\*/', # multi-line javadoc-style comment + + # regular expression: There's no reason for it to start + # with a * and this stops confusion with comments. + r'/(?!\*)(\\\\|\\[^\\]|[^/\\])*/', + + # Now that we've handled regex and javadoc comments + # it's safe to let / through. + r'/', + )) + r')+', Other), + + + (r'\[', Punctuation, '#push'), + (r'\]', Punctuation, '#pop'), + (r'(\$[a-zA-Z]+)(\.?)(text|value)?', + bygroups(Name.Variable, Punctuation, Name.Property)), + (r'(\\\\|\\\]|\\\[|[^\[\]])+', Other), + ] + } + + def analyse_text(text): + return re.search(r'^\s*grammar\s+[a-zA-Z0-9]+\s*;', text, re.M) + + +# http://www.antlr.org/wiki/display/ANTLR3/Code+Generation+Targets + +class AntlrCppLexer(DelegatingLexer): + """ + `ANTLR`_ with CPP Target + + .. versionadded:: 1.1 + """ + + name = 'ANTLR With CPP Target' + aliases = ['antlr-cpp'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + super().__init__(CppLexer, AntlrLexer, **options) + + def analyse_text(text): + return AntlrLexer.analyse_text(text) and \ + re.search(r'^\s*language\s*=\s*C\s*;', text, re.M) + + +class AntlrObjectiveCLexer(DelegatingLexer): + """ + `ANTLR`_ with Objective-C Target + + .. versionadded:: 1.1 + """ + + name = 'ANTLR With ObjectiveC Target' + aliases = ['antlr-objc'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + super().__init__(ObjectiveCLexer, AntlrLexer, **options) + + def analyse_text(text): + return AntlrLexer.analyse_text(text) and \ + re.search(r'^\s*language\s*=\s*ObjC\s*;', text) + + +class AntlrCSharpLexer(DelegatingLexer): + """ + `ANTLR`_ with C# Target + + .. versionadded:: 1.1 + """ + + name = 'ANTLR With C# Target' + aliases = ['antlr-csharp', 'antlr-c#'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + super().__init__(CSharpLexer, AntlrLexer, **options) + + def analyse_text(text): + return AntlrLexer.analyse_text(text) and \ + re.search(r'^\s*language\s*=\s*CSharp2\s*;', text, re.M) + + +class AntlrPythonLexer(DelegatingLexer): + """ + `ANTLR`_ with Python Target + + .. versionadded:: 1.1 + """ + + name = 'ANTLR With Python Target' + aliases = ['antlr-python'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + super().__init__(PythonLexer, AntlrLexer, **options) + + def analyse_text(text): + return AntlrLexer.analyse_text(text) and \ + re.search(r'^\s*language\s*=\s*Python\s*;', text, re.M) + + +class AntlrJavaLexer(DelegatingLexer): + """ + `ANTLR`_ with Java Target + + .. versionadded:: 1. + """ + + name = 'ANTLR With Java Target' + aliases = ['antlr-java'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + super().__init__(JavaLexer, AntlrLexer, **options) + + def analyse_text(text): + # Antlr language is Java by default + return AntlrLexer.analyse_text(text) and 0.9 + + +class AntlrRubyLexer(DelegatingLexer): + """ + `ANTLR`_ with Ruby Target + + .. versionadded:: 1.1 + """ + + name = 'ANTLR With Ruby Target' + aliases = ['antlr-ruby', 'antlr-rb'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + super().__init__(RubyLexer, AntlrLexer, **options) + + def analyse_text(text): + return AntlrLexer.analyse_text(text) and \ + re.search(r'^\s*language\s*=\s*Ruby\s*;', text, re.M) + + +class AntlrPerlLexer(DelegatingLexer): + """ + `ANTLR`_ with Perl Target + + .. versionadded:: 1.1 + """ + + name = 'ANTLR With Perl Target' + aliases = ['antlr-perl'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + super().__init__(PerlLexer, AntlrLexer, **options) + + def analyse_text(text): + return AntlrLexer.analyse_text(text) and \ + re.search(r'^\s*language\s*=\s*Perl5\s*;', text, re.M) + + +class AntlrActionScriptLexer(DelegatingLexer): + """ + `ANTLR`_ with ActionScript Target + + .. versionadded:: 1.1 + """ + + name = 'ANTLR With ActionScript Target' + aliases = ['antlr-actionscript', 'antlr-as'] + filenames = ['*.G', '*.g'] + + def __init__(self, **options): + from pygments.lexers.actionscript import ActionScriptLexer + super().__init__(ActionScriptLexer, AntlrLexer, **options) + + def analyse_text(text): + return AntlrLexer.analyse_text(text) and \ + re.search(r'^\s*language\s*=\s*ActionScript\s*;', text, re.M) + + +class TreetopBaseLexer(RegexLexer): + """ + A base lexer for `Treetop `_ grammars. + Not for direct use; use TreetopLexer instead. + + .. versionadded:: 1.6 + """ + + tokens = { + 'root': [ + include('space'), + (r'require[ \t]+[^\n\r]+[\n\r]', Other), + (r'module\b', Keyword.Namespace, 'module'), + (r'grammar\b', Keyword, 'grammar'), + ], + 'module': [ + include('space'), + include('end'), + (r'module\b', Keyword, '#push'), + (r'grammar\b', Keyword, 'grammar'), + (r'[A-Z]\w*(?:::[A-Z]\w*)*', Name.Namespace), + ], + 'grammar': [ + include('space'), + include('end'), + (r'rule\b', Keyword, 'rule'), + (r'include\b', Keyword, 'include'), + (r'[A-Z]\w*', Name), + ], + 'include': [ + include('space'), + (r'[A-Z]\w*(?:::[A-Z]\w*)*', Name.Class, '#pop'), + ], + 'rule': [ + include('space'), + include('end'), + (r'"(\\\\|\\[^\\]|[^"\\])*"', String.Double), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String.Single), + (r'([A-Za-z_]\w*)(:)', bygroups(Name.Label, Punctuation)), + (r'[A-Za-z_]\w*', Name), + (r'[()]', Punctuation), + (r'[?+*/&!~]', Operator), + (r'\[(?:\\.|\[:\^?[a-z]+:\]|[^\\\]])+\]', String.Regex), + (r'([0-9]*)(\.\.)([0-9]*)', + bygroups(Number.Integer, Operator, Number.Integer)), + (r'(<)([^>]+)(>)', bygroups(Punctuation, Name.Class, Punctuation)), + (r'\{', Punctuation, 'inline_module'), + (r'\.', String.Regex), + ], + 'inline_module': [ + (r'\{', Other, 'ruby'), + (r'\}', Punctuation, '#pop'), + (r'[^{}]+', Other), + ], + 'ruby': [ + (r'\{', Other, '#push'), + (r'\}', Other, '#pop'), + (r'[^{}]+', Other), + ], + 'space': [ + (r'[ \t\n\r]+', Whitespace), + (r'#[^\n]*', Comment.Single), + ], + 'end': [ + (r'end\b', Keyword, '#pop'), + ], + } + + +class TreetopLexer(DelegatingLexer): + """ + A lexer for `Treetop `_ grammars. + + .. versionadded:: 1.6 + """ + + name = 'Treetop' + aliases = ['treetop'] + filenames = ['*.treetop', '*.tt'] + + def __init__(self, **options): + super().__init__(RubyLexer, TreetopBaseLexer, **options) + + +class EbnfLexer(RegexLexer): + """ + Lexer for `ISO/IEC 14977 EBNF + `_ + grammars. + + .. versionadded:: 2.0 + """ + + name = 'EBNF' + aliases = ['ebnf'] + filenames = ['*.ebnf'] + mimetypes = ['text/x-ebnf'] + + tokens = { + 'root': [ + include('whitespace'), + include('comment_start'), + include('identifier'), + (r'=', Operator, 'production'), + ], + 'production': [ + include('whitespace'), + include('comment_start'), + include('identifier'), + (r'"[^"]*"', String.Double), + (r"'[^']*'", String.Single), + (r'(\?[^?]*\?)', Name.Entity), + (r'[\[\]{}(),|]', Punctuation), + (r'-', Operator), + (r';', Punctuation, '#pop'), + (r'\.', Punctuation, '#pop'), + ], + 'whitespace': [ + (r'\s+', Text), + ], + 'comment_start': [ + (r'\(\*', Comment.Multiline, 'comment'), + ], + 'comment': [ + (r'[^*)]', Comment.Multiline), + include('comment_start'), + (r'\*\)', Comment.Multiline, '#pop'), + (r'[*)]', Comment.Multiline), + ], + 'identifier': [ + (r'([a-zA-Z][\w \-]*)', Keyword), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/pascal.py b/.venv/lib/python3.8/site-packages/pygments/lexers/pascal.py new file mode 100644 index 0000000..0d1ac3f --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/pascal.py @@ -0,0 +1,643 @@ +""" + pygments.lexers.pascal + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Pascal family languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import Lexer, RegexLexer, include, bygroups, words, \ + using, this, default +from pygments.util import get_bool_opt, get_list_opt +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Error +from pygments.scanner import Scanner + +# compatibility import +from pygments.lexers.modula2 import Modula2Lexer + +__all__ = ['DelphiLexer', 'AdaLexer'] + + +class DelphiLexer(Lexer): + """ + For `Delphi `_ (Borland Object Pascal), + Turbo Pascal and Free Pascal source code. + + Additional options accepted: + + `turbopascal` + Highlight Turbo Pascal specific keywords (default: ``True``). + `delphi` + Highlight Borland Delphi specific keywords (default: ``True``). + `freepascal` + Highlight Free Pascal specific keywords (default: ``True``). + `units` + A list of units that should be considered builtin, supported are + ``System``, ``SysUtils``, ``Classes`` and ``Math``. + Default is to consider all of them builtin. + """ + name = 'Delphi' + aliases = ['delphi', 'pas', 'pascal', 'objectpascal'] + filenames = ['*.pas', '*.dpr'] + mimetypes = ['text/x-pascal'] + + TURBO_PASCAL_KEYWORDS = ( + 'absolute', 'and', 'array', 'asm', 'begin', 'break', 'case', + 'const', 'constructor', 'continue', 'destructor', 'div', 'do', + 'downto', 'else', 'end', 'file', 'for', 'function', 'goto', + 'if', 'implementation', 'in', 'inherited', 'inline', 'interface', + 'label', 'mod', 'nil', 'not', 'object', 'of', 'on', 'operator', + 'or', 'packed', 'procedure', 'program', 'record', 'reintroduce', + 'repeat', 'self', 'set', 'shl', 'shr', 'string', 'then', 'to', + 'type', 'unit', 'until', 'uses', 'var', 'while', 'with', 'xor' + ) + + DELPHI_KEYWORDS = ( + 'as', 'class', 'except', 'exports', 'finalization', 'finally', + 'initialization', 'is', 'library', 'on', 'property', 'raise', + 'threadvar', 'try' + ) + + FREE_PASCAL_KEYWORDS = ( + 'dispose', 'exit', 'false', 'new', 'true' + ) + + BLOCK_KEYWORDS = { + 'begin', 'class', 'const', 'constructor', 'destructor', 'end', + 'finalization', 'function', 'implementation', 'initialization', + 'label', 'library', 'operator', 'procedure', 'program', 'property', + 'record', 'threadvar', 'type', 'unit', 'uses', 'var' + } + + FUNCTION_MODIFIERS = { + 'alias', 'cdecl', 'export', 'inline', 'interrupt', 'nostackframe', + 'pascal', 'register', 'safecall', 'softfloat', 'stdcall', + 'varargs', 'name', 'dynamic', 'near', 'virtual', 'external', + 'override', 'assembler' + } + + # XXX: those aren't global. but currently we know no way for defining + # them just for the type context. + DIRECTIVES = { + 'absolute', 'abstract', 'assembler', 'cppdecl', 'default', 'far', + 'far16', 'forward', 'index', 'oldfpccall', 'private', 'protected', + 'published', 'public' + } + + BUILTIN_TYPES = { + 'ansichar', 'ansistring', 'bool', 'boolean', 'byte', 'bytebool', + 'cardinal', 'char', 'comp', 'currency', 'double', 'dword', + 'extended', 'int64', 'integer', 'iunknown', 'longbool', 'longint', + 'longword', 'pansichar', 'pansistring', 'pbool', 'pboolean', + 'pbyte', 'pbytearray', 'pcardinal', 'pchar', 'pcomp', 'pcurrency', + 'pdate', 'pdatetime', 'pdouble', 'pdword', 'pextended', 'phandle', + 'pint64', 'pinteger', 'plongint', 'plongword', 'pointer', + 'ppointer', 'pshortint', 'pshortstring', 'psingle', 'psmallint', + 'pstring', 'pvariant', 'pwidechar', 'pwidestring', 'pword', + 'pwordarray', 'pwordbool', 'real', 'real48', 'shortint', + 'shortstring', 'single', 'smallint', 'string', 'tclass', 'tdate', + 'tdatetime', 'textfile', 'thandle', 'tobject', 'ttime', 'variant', + 'widechar', 'widestring', 'word', 'wordbool' + } + + BUILTIN_UNITS = { + 'System': ( + 'abs', 'acquireexceptionobject', 'addr', 'ansitoutf8', + 'append', 'arctan', 'assert', 'assigned', 'assignfile', + 'beginthread', 'blockread', 'blockwrite', 'break', 'chdir', + 'chr', 'close', 'closefile', 'comptocurrency', 'comptodouble', + 'concat', 'continue', 'copy', 'cos', 'dec', 'delete', + 'dispose', 'doubletocomp', 'endthread', 'enummodules', + 'enumresourcemodules', 'eof', 'eoln', 'erase', 'exceptaddr', + 'exceptobject', 'exclude', 'exit', 'exp', 'filepos', 'filesize', + 'fillchar', 'finalize', 'findclasshinstance', 'findhinstance', + 'findresourcehinstance', 'flush', 'frac', 'freemem', + 'get8087cw', 'getdir', 'getlasterror', 'getmem', + 'getmemorymanager', 'getmodulefilename', 'getvariantmanager', + 'halt', 'hi', 'high', 'inc', 'include', 'initialize', 'insert', + 'int', 'ioresult', 'ismemorymanagerset', 'isvariantmanagerset', + 'length', 'ln', 'lo', 'low', 'mkdir', 'move', 'new', 'odd', + 'olestrtostring', 'olestrtostrvar', 'ord', 'paramcount', + 'paramstr', 'pi', 'pos', 'pred', 'ptr', 'pucs4chars', 'random', + 'randomize', 'read', 'readln', 'reallocmem', + 'releaseexceptionobject', 'rename', 'reset', 'rewrite', 'rmdir', + 'round', 'runerror', 'seek', 'seekeof', 'seekeoln', + 'set8087cw', 'setlength', 'setlinebreakstyle', + 'setmemorymanager', 'setstring', 'settextbuf', + 'setvariantmanager', 'sin', 'sizeof', 'slice', 'sqr', 'sqrt', + 'str', 'stringofchar', 'stringtoolestr', 'stringtowidechar', + 'succ', 'swap', 'trunc', 'truncate', 'typeinfo', + 'ucs4stringtowidestring', 'unicodetoutf8', 'uniquestring', + 'upcase', 'utf8decode', 'utf8encode', 'utf8toansi', + 'utf8tounicode', 'val', 'vararrayredim', 'varclear', + 'widecharlentostring', 'widecharlentostrvar', + 'widechartostring', 'widechartostrvar', + 'widestringtoucs4string', 'write', 'writeln' + ), + 'SysUtils': ( + 'abort', 'addexitproc', 'addterminateproc', 'adjustlinebreaks', + 'allocmem', 'ansicomparefilename', 'ansicomparestr', + 'ansicomparetext', 'ansidequotedstr', 'ansiextractquotedstr', + 'ansilastchar', 'ansilowercase', 'ansilowercasefilename', + 'ansipos', 'ansiquotedstr', 'ansisamestr', 'ansisametext', + 'ansistrcomp', 'ansistricomp', 'ansistrlastchar', 'ansistrlcomp', + 'ansistrlicomp', 'ansistrlower', 'ansistrpos', 'ansistrrscan', + 'ansistrscan', 'ansistrupper', 'ansiuppercase', + 'ansiuppercasefilename', 'appendstr', 'assignstr', 'beep', + 'booltostr', 'bytetocharindex', 'bytetocharlen', 'bytetype', + 'callterminateprocs', 'changefileext', 'charlength', + 'chartobyteindex', 'chartobytelen', 'comparemem', 'comparestr', + 'comparetext', 'createdir', 'createguid', 'currentyear', + 'currtostr', 'currtostrf', 'date', 'datetimetofiledate', + 'datetimetostr', 'datetimetostring', 'datetimetosystemtime', + 'datetimetotimestamp', 'datetostr', 'dayofweek', 'decodedate', + 'decodedatefully', 'decodetime', 'deletefile', 'directoryexists', + 'diskfree', 'disksize', 'disposestr', 'encodedate', 'encodetime', + 'exceptionerrormessage', 'excludetrailingbackslash', + 'excludetrailingpathdelimiter', 'expandfilename', + 'expandfilenamecase', 'expanduncfilename', 'extractfiledir', + 'extractfiledrive', 'extractfileext', 'extractfilename', + 'extractfilepath', 'extractrelativepath', 'extractshortpathname', + 'fileage', 'fileclose', 'filecreate', 'filedatetodatetime', + 'fileexists', 'filegetattr', 'filegetdate', 'fileisreadonly', + 'fileopen', 'fileread', 'filesearch', 'fileseek', 'filesetattr', + 'filesetdate', 'filesetreadonly', 'filewrite', 'finalizepackage', + 'findclose', 'findcmdlineswitch', 'findfirst', 'findnext', + 'floattocurr', 'floattodatetime', 'floattodecimal', 'floattostr', + 'floattostrf', 'floattotext', 'floattotextfmt', 'fmtloadstr', + 'fmtstr', 'forcedirectories', 'format', 'formatbuf', 'formatcurr', + 'formatdatetime', 'formatfloat', 'freeandnil', 'getcurrentdir', + 'getenvironmentvariable', 'getfileversion', 'getformatsettings', + 'getlocaleformatsettings', 'getmodulename', 'getpackagedescription', + 'getpackageinfo', 'gettime', 'guidtostring', 'incamonth', + 'includetrailingbackslash', 'includetrailingpathdelimiter', + 'incmonth', 'initializepackage', 'interlockeddecrement', + 'interlockedexchange', 'interlockedexchangeadd', + 'interlockedincrement', 'inttohex', 'inttostr', 'isdelimiter', + 'isequalguid', 'isleapyear', 'ispathdelimiter', 'isvalidident', + 'languages', 'lastdelimiter', 'loadpackage', 'loadstr', + 'lowercase', 'msecstotimestamp', 'newstr', 'nextcharindex', 'now', + 'outofmemoryerror', 'quotedstr', 'raiselastoserror', + 'raiselastwin32error', 'removedir', 'renamefile', 'replacedate', + 'replacetime', 'safeloadlibrary', 'samefilename', 'sametext', + 'setcurrentdir', 'showexception', 'sleep', 'stralloc', 'strbufsize', + 'strbytetype', 'strcat', 'strcharlength', 'strcomp', 'strcopy', + 'strdispose', 'strecopy', 'strend', 'strfmt', 'stricomp', + 'stringreplace', 'stringtoguid', 'strlcat', 'strlcomp', 'strlcopy', + 'strlen', 'strlfmt', 'strlicomp', 'strlower', 'strmove', 'strnew', + 'strnextchar', 'strpas', 'strpcopy', 'strplcopy', 'strpos', + 'strrscan', 'strscan', 'strtobool', 'strtobooldef', 'strtocurr', + 'strtocurrdef', 'strtodate', 'strtodatedef', 'strtodatetime', + 'strtodatetimedef', 'strtofloat', 'strtofloatdef', 'strtoint', + 'strtoint64', 'strtoint64def', 'strtointdef', 'strtotime', + 'strtotimedef', 'strupper', 'supports', 'syserrormessage', + 'systemtimetodatetime', 'texttofloat', 'time', 'timestamptodatetime', + 'timestamptomsecs', 'timetostr', 'trim', 'trimleft', 'trimright', + 'tryencodedate', 'tryencodetime', 'tryfloattocurr', 'tryfloattodatetime', + 'trystrtobool', 'trystrtocurr', 'trystrtodate', 'trystrtodatetime', + 'trystrtofloat', 'trystrtoint', 'trystrtoint64', 'trystrtotime', + 'unloadpackage', 'uppercase', 'widecomparestr', 'widecomparetext', + 'widefmtstr', 'wideformat', 'wideformatbuf', 'widelowercase', + 'widesamestr', 'widesametext', 'wideuppercase', 'win32check', + 'wraptext' + ), + 'Classes': ( + 'activateclassgroup', 'allocatehwnd', 'bintohex', 'checksynchronize', + 'collectionsequal', 'countgenerations', 'deallocatehwnd', 'equalrect', + 'extractstrings', 'findclass', 'findglobalcomponent', 'getclass', + 'groupdescendantswith', 'hextobin', 'identtoint', + 'initinheritedcomponent', 'inttoident', 'invalidpoint', + 'isuniqueglobalcomponentname', 'linestart', 'objectbinarytotext', + 'objectresourcetotext', 'objecttexttobinary', 'objecttexttoresource', + 'pointsequal', 'readcomponentres', 'readcomponentresex', + 'readcomponentresfile', 'rect', 'registerclass', 'registerclassalias', + 'registerclasses', 'registercomponents', 'registerintegerconsts', + 'registernoicon', 'registernonactivex', 'smallpoint', 'startclassgroup', + 'teststreamformat', 'unregisterclass', 'unregisterclasses', + 'unregisterintegerconsts', 'unregistermoduleclasses', + 'writecomponentresfile' + ), + 'Math': ( + 'arccos', 'arccosh', 'arccot', 'arccoth', 'arccsc', 'arccsch', 'arcsec', + 'arcsech', 'arcsin', 'arcsinh', 'arctan2', 'arctanh', 'ceil', + 'comparevalue', 'cosecant', 'cosh', 'cot', 'cotan', 'coth', 'csc', + 'csch', 'cycletodeg', 'cycletograd', 'cycletorad', 'degtocycle', + 'degtograd', 'degtorad', 'divmod', 'doubledecliningbalance', + 'ensurerange', 'floor', 'frexp', 'futurevalue', 'getexceptionmask', + 'getprecisionmode', 'getroundmode', 'gradtocycle', 'gradtodeg', + 'gradtorad', 'hypot', 'inrange', 'interestpayment', 'interestrate', + 'internalrateofreturn', 'intpower', 'isinfinite', 'isnan', 'iszero', + 'ldexp', 'lnxp1', 'log10', 'log2', 'logn', 'max', 'maxintvalue', + 'maxvalue', 'mean', 'meanandstddev', 'min', 'minintvalue', 'minvalue', + 'momentskewkurtosis', 'netpresentvalue', 'norm', 'numberofperiods', + 'payment', 'periodpayment', 'poly', 'popnstddev', 'popnvariance', + 'power', 'presentvalue', 'radtocycle', 'radtodeg', 'radtograd', + 'randg', 'randomrange', 'roundto', 'samevalue', 'sec', 'secant', + 'sech', 'setexceptionmask', 'setprecisionmode', 'setroundmode', + 'sign', 'simpleroundto', 'sincos', 'sinh', 'slndepreciation', 'stddev', + 'sum', 'sumint', 'sumofsquares', 'sumsandsquares', 'syddepreciation', + 'tan', 'tanh', 'totalvariance', 'variance' + ) + } + + ASM_REGISTERS = { + 'ah', 'al', 'ax', 'bh', 'bl', 'bp', 'bx', 'ch', 'cl', 'cr0', + 'cr1', 'cr2', 'cr3', 'cr4', 'cs', 'cx', 'dh', 'di', 'dl', 'dr0', + 'dr1', 'dr2', 'dr3', 'dr4', 'dr5', 'dr6', 'dr7', 'ds', 'dx', + 'eax', 'ebp', 'ebx', 'ecx', 'edi', 'edx', 'es', 'esi', 'esp', + 'fs', 'gs', 'mm0', 'mm1', 'mm2', 'mm3', 'mm4', 'mm5', 'mm6', + 'mm7', 'si', 'sp', 'ss', 'st0', 'st1', 'st2', 'st3', 'st4', 'st5', + 'st6', 'st7', 'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', + 'xmm6', 'xmm7' + } + + ASM_INSTRUCTIONS = { + 'aaa', 'aad', 'aam', 'aas', 'adc', 'add', 'and', 'arpl', 'bound', + 'bsf', 'bsr', 'bswap', 'bt', 'btc', 'btr', 'bts', 'call', 'cbw', + 'cdq', 'clc', 'cld', 'cli', 'clts', 'cmc', 'cmova', 'cmovae', + 'cmovb', 'cmovbe', 'cmovc', 'cmovcxz', 'cmove', 'cmovg', + 'cmovge', 'cmovl', 'cmovle', 'cmovna', 'cmovnae', 'cmovnb', + 'cmovnbe', 'cmovnc', 'cmovne', 'cmovng', 'cmovnge', 'cmovnl', + 'cmovnle', 'cmovno', 'cmovnp', 'cmovns', 'cmovnz', 'cmovo', + 'cmovp', 'cmovpe', 'cmovpo', 'cmovs', 'cmovz', 'cmp', 'cmpsb', + 'cmpsd', 'cmpsw', 'cmpxchg', 'cmpxchg486', 'cmpxchg8b', 'cpuid', + 'cwd', 'cwde', 'daa', 'das', 'dec', 'div', 'emms', 'enter', 'hlt', + 'ibts', 'icebp', 'idiv', 'imul', 'in', 'inc', 'insb', 'insd', + 'insw', 'int', 'int01', 'int03', 'int1', 'int3', 'into', 'invd', + 'invlpg', 'iret', 'iretd', 'iretw', 'ja', 'jae', 'jb', 'jbe', + 'jc', 'jcxz', 'jcxz', 'je', 'jecxz', 'jg', 'jge', 'jl', 'jle', + 'jmp', 'jna', 'jnae', 'jnb', 'jnbe', 'jnc', 'jne', 'jng', 'jnge', + 'jnl', 'jnle', 'jno', 'jnp', 'jns', 'jnz', 'jo', 'jp', 'jpe', + 'jpo', 'js', 'jz', 'lahf', 'lar', 'lcall', 'lds', 'lea', 'leave', + 'les', 'lfs', 'lgdt', 'lgs', 'lidt', 'ljmp', 'lldt', 'lmsw', + 'loadall', 'loadall286', 'lock', 'lodsb', 'lodsd', 'lodsw', + 'loop', 'loope', 'loopne', 'loopnz', 'loopz', 'lsl', 'lss', 'ltr', + 'mov', 'movd', 'movq', 'movsb', 'movsd', 'movsw', 'movsx', + 'movzx', 'mul', 'neg', 'nop', 'not', 'or', 'out', 'outsb', 'outsd', + 'outsw', 'pop', 'popa', 'popad', 'popaw', 'popf', 'popfd', 'popfw', + 'push', 'pusha', 'pushad', 'pushaw', 'pushf', 'pushfd', 'pushfw', + 'rcl', 'rcr', 'rdmsr', 'rdpmc', 'rdshr', 'rdtsc', 'rep', 'repe', + 'repne', 'repnz', 'repz', 'ret', 'retf', 'retn', 'rol', 'ror', + 'rsdc', 'rsldt', 'rsm', 'sahf', 'sal', 'salc', 'sar', 'sbb', + 'scasb', 'scasd', 'scasw', 'seta', 'setae', 'setb', 'setbe', + 'setc', 'setcxz', 'sete', 'setg', 'setge', 'setl', 'setle', + 'setna', 'setnae', 'setnb', 'setnbe', 'setnc', 'setne', 'setng', + 'setnge', 'setnl', 'setnle', 'setno', 'setnp', 'setns', 'setnz', + 'seto', 'setp', 'setpe', 'setpo', 'sets', 'setz', 'sgdt', 'shl', + 'shld', 'shr', 'shrd', 'sidt', 'sldt', 'smi', 'smint', 'smintold', + 'smsw', 'stc', 'std', 'sti', 'stosb', 'stosd', 'stosw', 'str', + 'sub', 'svdc', 'svldt', 'svts', 'syscall', 'sysenter', 'sysexit', + 'sysret', 'test', 'ud1', 'ud2', 'umov', 'verr', 'verw', 'wait', + 'wbinvd', 'wrmsr', 'wrshr', 'xadd', 'xbts', 'xchg', 'xlat', + 'xlatb', 'xor' + } + + def __init__(self, **options): + Lexer.__init__(self, **options) + self.keywords = set() + if get_bool_opt(options, 'turbopascal', True): + self.keywords.update(self.TURBO_PASCAL_KEYWORDS) + if get_bool_opt(options, 'delphi', True): + self.keywords.update(self.DELPHI_KEYWORDS) + if get_bool_opt(options, 'freepascal', True): + self.keywords.update(self.FREE_PASCAL_KEYWORDS) + self.builtins = set() + for unit in get_list_opt(options, 'units', list(self.BUILTIN_UNITS)): + self.builtins.update(self.BUILTIN_UNITS[unit]) + + def get_tokens_unprocessed(self, text): + scanner = Scanner(text, re.DOTALL | re.MULTILINE | re.IGNORECASE) + stack = ['initial'] + in_function_block = False + in_property_block = False + was_dot = False + next_token_is_function = False + next_token_is_property = False + collect_labels = False + block_labels = set() + brace_balance = [0, 0] + + while not scanner.eos: + token = Error + + if stack[-1] == 'initial': + if scanner.scan(r'\s+'): + token = Text + elif scanner.scan(r'\{.*?\}|\(\*.*?\*\)'): + if scanner.match.startswith('$'): + token = Comment.Preproc + else: + token = Comment.Multiline + elif scanner.scan(r'//.*?$'): + token = Comment.Single + elif scanner.scan(r'[-+*\/=<>:;,.@\^]'): + token = Operator + # stop label highlighting on next ";" + if collect_labels and scanner.match == ';': + collect_labels = False + elif scanner.scan(r'[\(\)\[\]]+'): + token = Punctuation + # abort function naming ``foo = Function(...)`` + next_token_is_function = False + # if we are in a function block we count the open + # braces because ootherwise it's impossible to + # determine the end of the modifier context + if in_function_block or in_property_block: + if scanner.match == '(': + brace_balance[0] += 1 + elif scanner.match == ')': + brace_balance[0] -= 1 + elif scanner.match == '[': + brace_balance[1] += 1 + elif scanner.match == ']': + brace_balance[1] -= 1 + elif scanner.scan(r'[A-Za-z_][A-Za-z_0-9]*'): + lowercase_name = scanner.match.lower() + if lowercase_name == 'result': + token = Name.Builtin.Pseudo + elif lowercase_name in self.keywords: + token = Keyword + # if we are in a special block and a + # block ending keyword occours (and the parenthesis + # is balanced) we end the current block context + if (in_function_block or in_property_block) and \ + lowercase_name in self.BLOCK_KEYWORDS and \ + brace_balance[0] <= 0 and \ + brace_balance[1] <= 0: + in_function_block = False + in_property_block = False + brace_balance = [0, 0] + block_labels = set() + if lowercase_name in ('label', 'goto'): + collect_labels = True + elif lowercase_name == 'asm': + stack.append('asm') + elif lowercase_name == 'property': + in_property_block = True + next_token_is_property = True + elif lowercase_name in ('procedure', 'operator', + 'function', 'constructor', + 'destructor'): + in_function_block = True + next_token_is_function = True + # we are in a function block and the current name + # is in the set of registered modifiers. highlight + # it as pseudo keyword + elif in_function_block and \ + lowercase_name in self.FUNCTION_MODIFIERS: + token = Keyword.Pseudo + # if we are in a property highlight some more + # modifiers + elif in_property_block and \ + lowercase_name in ('read', 'write'): + token = Keyword.Pseudo + next_token_is_function = True + # if the last iteration set next_token_is_function + # to true we now want this name highlighted as + # function. so do that and reset the state + elif next_token_is_function: + # Look if the next token is a dot. If yes it's + # not a function, but a class name and the + # part after the dot a function name + if scanner.test(r'\s*\.\s*'): + token = Name.Class + # it's not a dot, our job is done + else: + token = Name.Function + next_token_is_function = False + # same for properties + elif next_token_is_property: + token = Name.Property + next_token_is_property = False + # Highlight this token as label and add it + # to the list of known labels + elif collect_labels: + token = Name.Label + block_labels.add(scanner.match.lower()) + # name is in list of known labels + elif lowercase_name in block_labels: + token = Name.Label + elif lowercase_name in self.BUILTIN_TYPES: + token = Keyword.Type + elif lowercase_name in self.DIRECTIVES: + token = Keyword.Pseudo + # builtins are just builtins if the token + # before isn't a dot + elif not was_dot and lowercase_name in self.builtins: + token = Name.Builtin + else: + token = Name + elif scanner.scan(r"'"): + token = String + stack.append('string') + elif scanner.scan(r'\#(\d+|\$[0-9A-Fa-f]+)'): + token = String.Char + elif scanner.scan(r'\$[0-9A-Fa-f]+'): + token = Number.Hex + elif scanner.scan(r'\d+(?![eE]|\.[^.])'): + token = Number.Integer + elif scanner.scan(r'\d+(\.\d+([eE][+-]?\d+)?|[eE][+-]?\d+)'): + token = Number.Float + else: + # if the stack depth is deeper than once, pop + if len(stack) > 1: + stack.pop() + scanner.get_char() + + elif stack[-1] == 'string': + if scanner.scan(r"''"): + token = String.Escape + elif scanner.scan(r"'"): + token = String + stack.pop() + elif scanner.scan(r"[^']*"): + token = String + else: + scanner.get_char() + stack.pop() + + elif stack[-1] == 'asm': + if scanner.scan(r'\s+'): + token = Text + elif scanner.scan(r'end'): + token = Keyword + stack.pop() + elif scanner.scan(r'\{.*?\}|\(\*.*?\*\)'): + if scanner.match.startswith('$'): + token = Comment.Preproc + else: + token = Comment.Multiline + elif scanner.scan(r'//.*?$'): + token = Comment.Single + elif scanner.scan(r"'"): + token = String + stack.append('string') + elif scanner.scan(r'@@[A-Za-z_][A-Za-z_0-9]*'): + token = Name.Label + elif scanner.scan(r'[A-Za-z_][A-Za-z_0-9]*'): + lowercase_name = scanner.match.lower() + if lowercase_name in self.ASM_INSTRUCTIONS: + token = Keyword + elif lowercase_name in self.ASM_REGISTERS: + token = Name.Builtin + else: + token = Name + elif scanner.scan(r'[-+*\/=<>:;,.@\^]+'): + token = Operator + elif scanner.scan(r'[\(\)\[\]]+'): + token = Punctuation + elif scanner.scan(r'\$[0-9A-Fa-f]+'): + token = Number.Hex + elif scanner.scan(r'\d+(?![eE]|\.[^.])'): + token = Number.Integer + elif scanner.scan(r'\d+(\.\d+([eE][+-]?\d+)?|[eE][+-]?\d+)'): + token = Number.Float + else: + scanner.get_char() + stack.pop() + + # save the dot!!!11 + if scanner.match.strip(): + was_dot = scanner.match == '.' + yield scanner.start_pos, token, scanner.match or '' + + +class AdaLexer(RegexLexer): + """ + For Ada source code. + + .. versionadded:: 1.3 + """ + + name = 'Ada' + aliases = ['ada', 'ada95', 'ada2005'] + filenames = ['*.adb', '*.ads', '*.ada'] + mimetypes = ['text/x-ada'] + + flags = re.MULTILINE | re.IGNORECASE + + tokens = { + 'root': [ + (r'[^\S\n]+', Text), + (r'--.*?\n', Comment.Single), + (r'[^\S\n]+', Text), + (r'function|procedure|entry', Keyword.Declaration, 'subprogram'), + (r'(subtype|type)(\s+)(\w+)', + bygroups(Keyword.Declaration, Text, Keyword.Type), 'type_def'), + (r'task|protected', Keyword.Declaration), + (r'(subtype)(\s+)', bygroups(Keyword.Declaration, Text)), + (r'(end)(\s+)', bygroups(Keyword.Reserved, Text), 'end'), + (r'(pragma)(\s+)(\w+)', bygroups(Keyword.Reserved, Text, + Comment.Preproc)), + (r'(true|false|null)\b', Keyword.Constant), + (words(( + 'Address', 'Byte', 'Boolean', 'Character', 'Controlled', 'Count', + 'Cursor', 'Duration', 'File_Mode', 'File_Type', 'Float', 'Generator', + 'Integer', 'Long_Float', 'Long_Integer', 'Long_Long_Float', + 'Long_Long_Integer', 'Natural', 'Positive', 'Reference_Type', + 'Short_Float', 'Short_Integer', 'Short_Short_Float', + 'Short_Short_Integer', 'String', 'Wide_Character', 'Wide_String'), + suffix=r'\b'), + Keyword.Type), + (r'(and(\s+then)?|in|mod|not|or(\s+else)|rem)\b', Operator.Word), + (r'generic|private', Keyword.Declaration), + (r'package', Keyword.Declaration, 'package'), + (r'array\b', Keyword.Reserved, 'array_def'), + (r'(with|use)(\s+)', bygroups(Keyword.Namespace, Text), 'import'), + (r'(\w+)(\s*)(:)(\s*)(constant)', + bygroups(Name.Constant, Text, Punctuation, Text, + Keyword.Reserved)), + (r'<<\w+>>', Name.Label), + (r'(\w+)(\s*)(:)(\s*)(declare|begin|loop|for|while)', + bygroups(Name.Label, Text, Punctuation, Text, Keyword.Reserved)), + (words(( + 'abort', 'abs', 'abstract', 'accept', 'access', 'aliased', 'all', + 'array', 'at', 'begin', 'body', 'case', 'constant', 'declare', + 'delay', 'delta', 'digits', 'do', 'else', 'elsif', 'end', 'entry', + 'exception', 'exit', 'interface', 'for', 'goto', 'if', 'is', 'limited', + 'loop', 'new', 'null', 'of', 'or', 'others', 'out', 'overriding', + 'pragma', 'protected', 'raise', 'range', 'record', 'renames', 'requeue', + 'return', 'reverse', 'select', 'separate', 'some', 'subtype', + 'synchronized', 'task', 'tagged', 'terminate', 'then', 'type', 'until', + 'when', 'while', 'xor'), prefix=r'\b', suffix=r'\b'), + Keyword.Reserved), + (r'"[^"]*"', String), + include('attribute'), + include('numbers'), + (r"'[^']'", String.Character), + (r'(\w+)(\s*|[(,])', bygroups(Name, using(this))), + (r"(<>|=>|:=|[()|:;,.'])", Punctuation), + (r'[*<>+=/&-]', Operator), + (r'\n+', Text), + ], + 'numbers': [ + (r'[0-9_]+#[0-9a-f_\.]+#', Number.Hex), + (r'[0-9_]+\.[0-9_]*', Number.Float), + (r'[0-9_]+', Number.Integer), + ], + 'attribute': [ + (r"(')(\w+)", bygroups(Punctuation, Name.Attribute)), + ], + 'subprogram': [ + (r'\(', Punctuation, ('#pop', 'formal_part')), + (r';', Punctuation, '#pop'), + (r'is\b', Keyword.Reserved, '#pop'), + (r'"[^"]+"|\w+', Name.Function), + include('root'), + ], + 'end': [ + ('(if|case|record|loop|select)', Keyword.Reserved), + (r'"[^"]+"|[\w.]+', Name.Function), + (r'\s+', Text), + (';', Punctuation, '#pop'), + ], + 'type_def': [ + (r';', Punctuation, '#pop'), + (r'\(', Punctuation, 'formal_part'), + (r'with|and|use', Keyword.Reserved), + (r'array\b', Keyword.Reserved, ('#pop', 'array_def')), + (r'record\b', Keyword.Reserved, ('record_def')), + (r'(null record)(;)', bygroups(Keyword.Reserved, Punctuation), '#pop'), + include('root'), + ], + 'array_def': [ + (r';', Punctuation, '#pop'), + (r'(\w+)(\s+)(range)', bygroups(Keyword.Type, Text, Keyword.Reserved)), + include('root'), + ], + 'record_def': [ + (r'end record', Keyword.Reserved, '#pop'), + include('root'), + ], + 'import': [ + (r'[\w.]+', Name.Namespace, '#pop'), + default('#pop'), + ], + 'formal_part': [ + (r'\)', Punctuation, '#pop'), + (r'\w+', Name.Variable), + (r',|:[^=]', Punctuation), + (r'(in|not|null|out|access)\b', Keyword.Reserved), + include('root'), + ], + 'package': [ + ('body', Keyword.Declaration), + (r'is\s+new|renames', Keyword.Reserved), + ('is', Keyword.Reserved, '#pop'), + (';', Punctuation, '#pop'), + (r'\(', Punctuation, 'package_instantiation'), + (r'([\w.]+)', Name.Class), + include('root'), + ], + 'package_instantiation': [ + (r'("[^"]+"|\w+)(\s+)(=>)', bygroups(Name.Variable, Text, Punctuation)), + (r'[\w.\'"]', Text), + (r'\)', Punctuation, '#pop'), + include('root'), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/pawn.py b/.venv/lib/python3.8/site-packages/pygments/lexers/pawn.py new file mode 100644 index 0000000..5a303e4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/pawn.py @@ -0,0 +1,202 @@ +""" + pygments.lexers.pawn + ~~~~~~~~~~~~~~~~~~~~ + + Lexers for the Pawn languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation +from pygments.util import get_bool_opt + +__all__ = ['SourcePawnLexer', 'PawnLexer'] + + +class SourcePawnLexer(RegexLexer): + """ + For SourcePawn source code with preprocessor directives. + + .. versionadded:: 1.6 + """ + name = 'SourcePawn' + aliases = ['sp'] + filenames = ['*.sp'] + mimetypes = ['text/x-sourcepawn'] + + #: optional Comment or Whitespace + _ws = r'(?:\s|//.*?\n|/\*.*?\*/)+' + #: only one /* */ style comment + _ws1 = r'\s*(?:/[*].*?[*]/\s*)*' + + tokens = { + 'root': [ + # preprocessor directives: without whitespace + (r'^#if\s+0', Comment.Preproc, 'if0'), + ('^#', Comment.Preproc, 'macro'), + # or with whitespace + ('^' + _ws1 + r'#if\s+0', Comment.Preproc, 'if0'), + ('^' + _ws1 + '#', Comment.Preproc, 'macro'), + (r'\n', Text), + (r'\s+', Text), + (r'\\\n', Text), # line continuation + (r'/(\\\n)?/(\n|(.|\n)*?[^\\]\n)', Comment.Single), + (r'/(\\\n)?\*(.|\n)*?\*(\\\n)?/', Comment.Multiline), + (r'[{}]', Punctuation), + (r'L?"', String, 'string'), + (r"L?'(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])'", String.Char), + (r'(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+[LlUu]*', Number.Float), + (r'(\d+\.\d*|\.\d+|\d+[fF])[fF]?', Number.Float), + (r'0x[0-9a-fA-F]+[LlUu]*', Number.Hex), + (r'0[0-7]+[LlUu]*', Number.Oct), + (r'\d+[LlUu]*', Number.Integer), + (r'[~!%^&*+=|?:<>/-]', Operator), + (r'[()\[\],.;]', Punctuation), + (r'(case|const|continue|native|' + r'default|else|enum|for|if|new|operator|' + r'public|return|sizeof|static|decl|struct|switch)\b', Keyword), + (r'(bool|Float)\b', Keyword.Type), + (r'(true|false)\b', Keyword.Constant), + (r'[a-zA-Z_]\w*', Name), + ], + 'string': [ + (r'"', String, '#pop'), + (r'\\([\\abfnrtv"\']|x[a-fA-F0-9]{2,4}|[0-7]{1,3})', String.Escape), + (r'[^\\"\n]+', String), # all other characters + (r'\\\n', String), # line continuation + (r'\\', String), # stray backslash + ], + 'macro': [ + (r'[^/\n]+', Comment.Preproc), + (r'/\*(.|\n)*?\*/', Comment.Multiline), + (r'//.*?\n', Comment.Single, '#pop'), + (r'/', Comment.Preproc), + (r'(?<=\\)\n', Comment.Preproc), + (r'\n', Comment.Preproc, '#pop'), + ], + 'if0': [ + (r'^\s*#if.*?(?/-]', Operator), + (r'[()\[\],.;]', Punctuation), + (r'(switch|case|default|const|new|static|char|continue|break|' + r'if|else|for|while|do|operator|enum|' + r'public|return|sizeof|tagof|state|goto)\b', Keyword), + (r'(bool|Float)\b', Keyword.Type), + (r'(true|false)\b', Keyword.Constant), + (r'[a-zA-Z_]\w*', Name), + ], + 'string': [ + (r'"', String, '#pop'), + (r'\\([\\abfnrtv"\']|x[a-fA-F0-9]{2,4}|[0-7]{1,3})', String.Escape), + (r'[^\\"\n]+', String), # all other characters + (r'\\\n', String), # line continuation + (r'\\', String), # stray backslash + ], + 'macro': [ + (r'[^/\n]+', Comment.Preproc), + (r'/\*(.|\n)*?\*/', Comment.Multiline), + (r'//.*?\n', Comment.Single, '#pop'), + (r'/', Comment.Preproc), + (r'(?<=\\)\n', Comment.Preproc), + (r'\n', Comment.Preproc, '#pop'), + ], + 'if0': [ + (r'^\s*#if.*?(?`_ source code. + """ + + name = 'Perl' + aliases = ['perl', 'pl'] + filenames = ['*.pl', '*.pm', '*.t', '*.perl'] + mimetypes = ['text/x-perl', 'application/x-perl'] + + flags = re.DOTALL | re.MULTILINE + # TODO: give this to a perl guy who knows how to parse perl... + tokens = { + 'balanced-regex': [ + (r'/(\\\\|\\[^\\]|[^\\/])*/[egimosx]*', String.Regex, '#pop'), + (r'!(\\\\|\\[^\\]|[^\\!])*![egimosx]*', String.Regex, '#pop'), + (r'\\(\\\\|[^\\])*\\[egimosx]*', String.Regex, '#pop'), + (r'\{(\\\\|\\[^\\]|[^\\}])*\}[egimosx]*', String.Regex, '#pop'), + (r'<(\\\\|\\[^\\]|[^\\>])*>[egimosx]*', String.Regex, '#pop'), + (r'\[(\\\\|\\[^\\]|[^\\\]])*\][egimosx]*', String.Regex, '#pop'), + (r'\((\\\\|\\[^\\]|[^\\)])*\)[egimosx]*', String.Regex, '#pop'), + (r'@(\\\\|\\[^\\]|[^\\@])*@[egimosx]*', String.Regex, '#pop'), + (r'%(\\\\|\\[^\\]|[^\\%])*%[egimosx]*', String.Regex, '#pop'), + (r'\$(\\\\|\\[^\\]|[^\\$])*\$[egimosx]*', String.Regex, '#pop'), + ], + 'root': [ + (r'\A\#!.+?$', Comment.Hashbang), + (r'\#.*?$', Comment.Single), + (r'^=[a-zA-Z0-9]+\s+.*?\n=cut', Comment.Multiline), + (words(( + 'case', 'continue', 'do', 'else', 'elsif', 'for', 'foreach', + 'if', 'last', 'my', 'next', 'our', 'redo', 'reset', 'then', + 'unless', 'until', 'while', 'print', 'new', 'BEGIN', + 'CHECK', 'INIT', 'END', 'return'), suffix=r'\b'), + Keyword), + (r'(format)(\s+)(\w+)(\s*)(=)(\s*\n)', + bygroups(Keyword, Text, Name, Text, Punctuation, Text), 'format'), + (r'(eq|lt|gt|le|ge|ne|not|and|or|cmp)\b', Operator.Word), + # common delimiters + (r's/(\\\\|\\[^\\]|[^\\/])*/(\\\\|\\[^\\]|[^\\/])*/[egimosx]*', + String.Regex), + (r's!(\\\\|\\!|[^!])*!(\\\\|\\!|[^!])*![egimosx]*', String.Regex), + (r's\\(\\\\|[^\\])*\\(\\\\|[^\\])*\\[egimosx]*', String.Regex), + (r's@(\\\\|\\[^\\]|[^\\@])*@(\\\\|\\[^\\]|[^\\@])*@[egimosx]*', + String.Regex), + (r's%(\\\\|\\[^\\]|[^\\%])*%(\\\\|\\[^\\]|[^\\%])*%[egimosx]*', + String.Regex), + # balanced delimiters + (r's\{(\\\\|\\[^\\]|[^\\}])*\}\s*', String.Regex, 'balanced-regex'), + (r's<(\\\\|\\[^\\]|[^\\>])*>\s*', String.Regex, 'balanced-regex'), + (r's\[(\\\\|\\[^\\]|[^\\\]])*\]\s*', String.Regex, + 'balanced-regex'), + (r's\((\\\\|\\[^\\]|[^\\)])*\)\s*', String.Regex, + 'balanced-regex'), + + (r'm?/(\\\\|\\[^\\]|[^\\/\n])*/[gcimosx]*', String.Regex), + (r'm(?=[/!\\{<\[(@%$])', String.Regex, 'balanced-regex'), + (r'((?<==~)|(?<=\())\s*/(\\\\|\\[^\\]|[^\\/])*/[gcimosx]*', + String.Regex), + (r'\s+', Text), + (words(( + 'abs', 'accept', 'alarm', 'atan2', 'bind', 'binmode', 'bless', 'caller', 'chdir', + 'chmod', 'chomp', 'chop', 'chown', 'chr', 'chroot', 'close', 'closedir', 'connect', + 'continue', 'cos', 'crypt', 'dbmclose', 'dbmopen', 'defined', 'delete', 'die', + 'dump', 'each', 'endgrent', 'endhostent', 'endnetent', 'endprotoent', + 'endpwent', 'endservent', 'eof', 'eval', 'exec', 'exists', 'exit', 'exp', 'fcntl', + 'fileno', 'flock', 'fork', 'format', 'formline', 'getc', 'getgrent', 'getgrgid', + 'getgrnam', 'gethostbyaddr', 'gethostbyname', 'gethostent', 'getlogin', + 'getnetbyaddr', 'getnetbyname', 'getnetent', 'getpeername', 'getpgrp', + 'getppid', 'getpriority', 'getprotobyname', 'getprotobynumber', + 'getprotoent', 'getpwent', 'getpwnam', 'getpwuid', 'getservbyname', + 'getservbyport', 'getservent', 'getsockname', 'getsockopt', 'glob', 'gmtime', + 'goto', 'grep', 'hex', 'import', 'index', 'int', 'ioctl', 'join', 'keys', 'kill', 'last', + 'lc', 'lcfirst', 'length', 'link', 'listen', 'local', 'localtime', 'log', 'lstat', + 'map', 'mkdir', 'msgctl', 'msgget', 'msgrcv', 'msgsnd', 'my', 'next', 'oct', 'open', + 'opendir', 'ord', 'our', 'pack', 'pipe', 'pop', 'pos', 'printf', + 'prototype', 'push', 'quotemeta', 'rand', 'read', 'readdir', + 'readline', 'readlink', 'readpipe', 'recv', 'redo', 'ref', 'rename', + 'reverse', 'rewinddir', 'rindex', 'rmdir', 'scalar', 'seek', 'seekdir', + 'select', 'semctl', 'semget', 'semop', 'send', 'setgrent', 'sethostent', 'setnetent', + 'setpgrp', 'setpriority', 'setprotoent', 'setpwent', 'setservent', + 'setsockopt', 'shift', 'shmctl', 'shmget', 'shmread', 'shmwrite', 'shutdown', + 'sin', 'sleep', 'socket', 'socketpair', 'sort', 'splice', 'split', 'sprintf', 'sqrt', + 'srand', 'stat', 'study', 'substr', 'symlink', 'syscall', 'sysopen', 'sysread', + 'sysseek', 'system', 'syswrite', 'tell', 'telldir', 'tie', 'tied', 'time', 'times', 'tr', + 'truncate', 'uc', 'ucfirst', 'umask', 'undef', 'unlink', 'unpack', 'unshift', 'untie', + 'utime', 'values', 'vec', 'wait', 'waitpid', 'wantarray', 'warn', 'write'), suffix=r'\b'), + Name.Builtin), + (r'((__(DATA|DIE|WARN)__)|(STD(IN|OUT|ERR)))\b', Name.Builtin.Pseudo), + (r'(<<)([\'"]?)([a-zA-Z_]\w*)(\2;?\n.*?\n)(\3)(\n)', + bygroups(String, String, String.Delimiter, String, String.Delimiter, Text)), + (r'__END__', Comment.Preproc, 'end-part'), + (r'\$\^[ADEFHILMOPSTWX]', Name.Variable.Global), + (r"\$[\\\"\[\]'&`+*.,;=%~?@$!<>(^|/-](?!\w)", Name.Variable.Global), + (r'[$@%#]+', Name.Variable, 'varname'), + (r'0_?[0-7]+(_[0-7]+)*', Number.Oct), + (r'0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*', Number.Hex), + (r'0b[01]+(_[01]+)*', Number.Bin), + (r'(?i)(\d*(_\d*)*\.\d+(_\d*)*|\d+(_\d*)*\.\d+(_\d*)*)(e[+-]?\d+)?', + Number.Float), + (r'(?i)\d+(_\d*)*e[+-]?\d+(_\d*)*', Number.Float), + (r'\d+(_\d+)*', Number.Integer), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String), + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + (r'`(\\\\|\\[^\\]|[^`\\])*`', String.Backtick), + (r'<([^\s>]+)>', String.Regex), + (r'(q|qq|qw|qr|qx)\{', String.Other, 'cb-string'), + (r'(q|qq|qw|qr|qx)\(', String.Other, 'rb-string'), + (r'(q|qq|qw|qr|qx)\[', String.Other, 'sb-string'), + (r'(q|qq|qw|qr|qx)\<', String.Other, 'lt-string'), + (r'(q|qq|qw|qr|qx)([\W_])(.|\n)*?\2', String.Other), + (r'(package)(\s+)([a-zA-Z_]\w*(?:::[a-zA-Z_]\w*)*)', + bygroups(Keyword, Text, Name.Namespace)), + (r'(use|require|no)(\s+)([a-zA-Z_]\w*(?:::[a-zA-Z_]\w*)*)', + bygroups(Keyword, Text, Name.Namespace)), + (r'(sub)(\s+)', bygroups(Keyword, Text), 'funcname'), + (words(( + 'no', 'package', 'require', 'use'), suffix=r'\b'), + Keyword), + (r'(\[\]|\*\*|::|<<|>>|>=|<=>|<=|={3}|!=|=~|' + r'!~|&&?|\|\||\.{1,3})', Operator), + (r'[-+/*%=<>&^|!\\~]=?', Operator), + (r'[()\[\]:;,<>/?{}]', Punctuation), # yes, there's no shortage + # of punctuation in Perl! + (r'(?=\w)', Name, 'name'), + ], + 'format': [ + (r'\.\n', String.Interpol, '#pop'), + (r'[^\n]*\n', String.Interpol), + ], + 'varname': [ + (r'\s+', Text), + (r'\{', Punctuation, '#pop'), # hash syntax? + (r'\)|,', Punctuation, '#pop'), # argument specifier + (r'\w+::', Name.Namespace), + (r'[\w:]+', Name.Variable, '#pop'), + ], + 'name': [ + (r'[a-zA-Z_]\w*(::[a-zA-Z_]\w*)*(::)?(?=\s*->)', Name.Namespace, '#pop'), + (r'[a-zA-Z_]\w*(::[a-zA-Z_]\w*)*::', Name.Namespace, '#pop'), + (r'[\w:]+', Name, '#pop'), + (r'[A-Z_]+(?=\W)', Name.Constant, '#pop'), + (r'(?=\W)', Text, '#pop'), + ], + 'funcname': [ + (r'[a-zA-Z_]\w*[!?]?', Name.Function), + (r'\s+', Text), + # argument declaration + (r'(\([$@%]*\))(\s*)', bygroups(Punctuation, Text)), + (r';', Punctuation, '#pop'), + (r'.*?\{', Punctuation, '#pop'), + ], + 'cb-string': [ + (r'\\[{}\\]', String.Other), + (r'\\', String.Other), + (r'\{', String.Other, 'cb-string'), + (r'\}', String.Other, '#pop'), + (r'[^{}\\]+', String.Other) + ], + 'rb-string': [ + (r'\\[()\\]', String.Other), + (r'\\', String.Other), + (r'\(', String.Other, 'rb-string'), + (r'\)', String.Other, '#pop'), + (r'[^()]+', String.Other) + ], + 'sb-string': [ + (r'\\[\[\]\\]', String.Other), + (r'\\', String.Other), + (r'\[', String.Other, 'sb-string'), + (r'\]', String.Other, '#pop'), + (r'[^\[\]]+', String.Other) + ], + 'lt-string': [ + (r'\\[<>\\]', String.Other), + (r'\\', String.Other), + (r'\<', String.Other, 'lt-string'), + (r'\>', String.Other, '#pop'), + (r'[^<>]+', String.Other) + ], + 'end-part': [ + (r'.+', Comment.Preproc, '#pop') + ] + } + + def analyse_text(text): + if shebang_matches(text, r'perl'): + return True + + result = 0 + + if re.search(r'(?:my|our)\s+[$@%(]', text): + result += 0.9 + + if ':=' in text: + # := is not valid Perl, but it appears in unicon, so we should + # become less confident if we think we found Perl with := + result /= 2 + + return result + + +class Perl6Lexer(ExtendedRegexLexer): + """ + For `Raku `_ (a.k.a. Perl 6) source code. + + .. versionadded:: 2.0 + """ + + name = 'Perl6' + aliases = ['perl6', 'pl6', 'raku'] + filenames = ['*.pl', '*.pm', '*.nqp', '*.p6', '*.6pl', '*.p6l', '*.pl6', + '*.6pm', '*.p6m', '*.pm6', '*.t', '*.raku', '*.rakumod', + '*.rakutest', '*.rakudoc'] + mimetypes = ['text/x-perl6', 'application/x-perl6'] + flags = re.MULTILINE | re.DOTALL | re.UNICODE + + PERL6_IDENTIFIER_RANGE = r"['\w:-]" + + PERL6_KEYWORDS = ( + #Phasers + 'BEGIN','CATCH','CHECK','CLOSE','CONTROL','DOC','END','ENTER','FIRST', + 'INIT','KEEP','LAST','LEAVE','NEXT','POST','PRE','QUIT','UNDO', + #Keywords + 'anon','augment','but','class','constant','default','does','else', + 'elsif','enum','for','gather','given','grammar','has','if','import', + 'is','let','loop','made','make','method','module','multi','my','need', + 'orwith','our','proceed','proto','repeat','require','return', + 'return-rw','returns','role','rule','state','sub','submethod','subset', + 'succeed','supersede','token','try','unit','unless','until','use', + 'when','while','with','without', + #Traits + 'export','native','repr','required','rw','symbol', + ) + + PERL6_BUILTINS = ( + 'ACCEPTS','abs','abs2rel','absolute','accept','accessed','acos', + 'acosec','acosech','acosh','acotan','acotanh','acquire','act','action', + 'actions','add','add_attribute','add_enum_value','add_fallback', + 'add_method','add_parent','add_private_method','add_role','add_trustee', + 'adverb','after','all','allocate','allof','allowed','alternative-names', + 'annotations','antipair','antipairs','any','anyof','app_lifetime', + 'append','arch','archname','args','arity','Array','asec','asech','asin', + 'asinh','ASSIGN-KEY','ASSIGN-POS','assuming','ast','at','atan','atan2', + 'atanh','AT-KEY','atomic-assign','atomic-dec-fetch','atomic-fetch', + 'atomic-fetch-add','atomic-fetch-dec','atomic-fetch-inc', + 'atomic-fetch-sub','atomic-inc-fetch','AT-POS','attributes','auth', + 'await','backtrace','Bag','BagHash','bail-out','base','basename', + 'base-repeating','batch','BIND-KEY','BIND-POS','bind-stderr', + 'bind-stdin','bind-stdout','bind-udp','bits','bless','block','Bool', + 'bool-only','bounds','break','Bridge','broken','BUILD','build-date', + 'bytes','cache','callframe','calling-package','CALL-ME','callsame', + 'callwith','can','cancel','candidates','cando','can-ok','canonpath', + 'caps','caption','Capture','cas','catdir','categorize','categorize-list', + 'catfile','catpath','cause','ceiling','cglobal','changed','Channel', + 'chars','chdir','child','child-name','child-typename','chmod','chomp', + 'chop','chr','chrs','chunks','cis','classify','classify-list','cleanup', + 'clone','close','closed','close-stdin','cmp-ok','code','codes','collate', + 'column','comb','combinations','command','comment','compiler','Complex', + 'compose','compose_type','composer','condition','config', + 'configure_destroy','configure_type_checking','conj','connect', + 'constraints','construct','contains','contents','copy','cos','cosec', + 'cosech','cosh','cotan','cotanh','count','count-only','cpu-cores', + 'cpu-usage','CREATE','create_type','cross','cue','curdir','curupdir','d', + 'Date','DateTime','day','daycount','day-of-month','day-of-week', + 'day-of-year','days-in-month','declaration','decode','decoder','deepmap', + 'default','defined','DEFINITE','delayed','DELETE-KEY','DELETE-POS', + 'denominator','desc','DESTROY','destroyers','devnull','diag', + 'did-you-mean','die','dies-ok','dir','dirname','dir-sep','DISTROnames', + 'do','does','does-ok','done','done-testing','duckmap','dynamic','e', + 'eager','earlier','elems','emit','enclosing','encode','encoder', + 'encoding','end','ends-with','enum_from_value','enum_value_list', + 'enum_values','enums','eof','EVAL','eval-dies-ok','EVALFILE', + 'eval-lives-ok','exception','excludes-max','excludes-min','EXISTS-KEY', + 'EXISTS-POS','exit','exitcode','exp','expected','explicitly-manage', + 'expmod','extension','f','fail','fails-like','fc','feature','file', + 'filename','find_method','find_method_qualified','finish','first','flat', + 'flatmap','flip','floor','flunk','flush','fmt','format','formatter', + 'freeze','from','from-list','from-loop','from-posix','full', + 'full-barrier','get','get_value','getc','gist','got','grab','grabpairs', + 'grep','handle','handled','handles','hardware','has_accessor','Hash', + 'head','headers','hh-mm-ss','hidden','hides','hour','how','hyper','id', + 'illegal','im','in','indent','index','indices','indir','infinite', + 'infix','infix:<+>','infix:<->','install_method_cache','Instant', + 'instead','Int','int-bounds','interval','in-timezone','invalid-str', + 'invert','invocant','IO','IO::Notification.watch-path','is_trusted', + 'is_type','isa','is-absolute','isa-ok','is-approx','is-deeply', + 'is-hidden','is-initial-thread','is-int','is-lazy','is-leap-year', + 'isNaN','isnt','is-prime','is-relative','is-routine','is-setting', + 'is-win','item','iterator','join','keep','kept','KERNELnames','key', + 'keyof','keys','kill','kv','kxxv','l','lang','last','lastcall','later', + 'lazy','lc','leading','level','like','line','lines','link','List', + 'listen','live','lives-ok','local','lock','log','log10','lookup','lsb', + 'made','MAIN','make','Map','match','max','maxpairs','merge','message', + 'method','method_table','methods','migrate','min','minmax','minpairs', + 'minute','misplaced','Mix','MixHash','mkdir','mode','modified','month', + 'move','mro','msb','multi','multiness','my','name','named','named_names', + 'narrow','nativecast','native-descriptor','nativesizeof','new','new_type', + 'new-from-daycount','new-from-pairs','next','nextcallee','next-handle', + 'nextsame','nextwith','NFC','NFD','NFKC','NFKD','nl-in','nl-out', + 'nodemap','nok','none','norm','not','note','now','nude','Num', + 'numerator','Numeric','of','offset','offset-in-hours','offset-in-minutes', + 'ok','old','on-close','one','on-switch','open','opened','operation', + 'optional','ord','ords','orig','os-error','osname','out-buffer','pack', + 'package','package-kind','package-name','packages','pair','pairs', + 'pairup','parameter','params','parent','parent-name','parents','parse', + 'parse-base','parsefile','parse-names','parts','pass','path','path-sep', + 'payload','peer-host','peer-port','periods','perl','permutations','phaser', + 'pick','pickpairs','pid','placeholder','plan','plus','polar','poll', + 'polymod','pop','pos','positional','posix','postfix','postmatch', + 'precomp-ext','precomp-target','pred','prefix','prematch','prepend', + 'print','printf','print-nl','print-to','private','private_method_table', + 'proc','produce','Promise','prompt','protect','pull-one','push', + 'push-all','push-at-least','push-exactly','push-until-lazy','put', + 'qualifier-type','quit','r','race','radix','rand','range','Rat','raw', + 're','read','readchars','readonly','ready','Real','reallocate','reals', + 'reason','rebless','receive','recv','redispatcher','redo','reduce', + 'rel2abs','relative','release','rename','repeated','replacement', + 'report','reserved','resolve','restore','result','resume','rethrow', + 'reverse','right','rindex','rmdir','role','roles_to_compose','rolish', + 'roll','rootdir','roots','rotate','rotor','round','roundrobin', + 'routine-type','run','rwx','s','samecase','samemark','samewith','say', + 'schedule-on','scheduler','scope','sec','sech','second','seek','self', + 'send','Set','set_hidden','set_name','set_package','set_rw','set_value', + 'SetHash','set-instruments','setup_finalization','shape','share','shell', + 'shift','sibling','sigil','sign','signal','signals','signature','sin', + 'sinh','sink','sink-all','skip','skip-at-least','skip-at-least-pull-one', + 'skip-one','skip-rest','sleep','sleep-timer','sleep-until','Slip','slurp', + 'slurp-rest','slurpy','snap','snapper','so','socket-host','socket-port', + 'sort','source','source-package','spawn','SPEC','splice','split', + 'splitdir','splitpath','sprintf','spurt','sqrt','squish','srand','stable', + 'start','started','starts-with','status','stderr','stdout','Str', + 'sub_signature','subbuf','subbuf-rw','subname','subparse','subst', + 'subst-mutate','substr','substr-eq','substr-rw','subtest','succ','sum', + 'Supply','symlink','t','tail','take','take-rw','tan','tanh','tap', + 'target','target-name','tc','tclc','tell','then','throttle','throw', + 'throws-like','timezone','tmpdir','to','today','todo','toggle','to-posix', + 'total','trailing','trans','tree','trim','trim-leading','trim-trailing', + 'truncate','truncated-to','trusts','try_acquire','trying','twigil','type', + 'type_captures','typename','uc','udp','uncaught_handler','unimatch', + 'uniname','uninames','uniparse','uniprop','uniprops','unique','unival', + 'univals','unlike','unlink','unlock','unpack','unpolar','unshift', + 'unwrap','updir','USAGE','use-ok','utc','val','value','values','VAR', + 'variable','verbose-config','version','VMnames','volume','vow','w','wait', + 'warn','watch','watch-path','week','weekday-of-month','week-number', + 'week-year','WHAT','when','WHERE','WHEREFORE','WHICH','WHO', + 'whole-second','WHY','wordcase','words','workaround','wrap','write', + 'write-to','x','yada','year','yield','yyyy-mm-dd','z','zip','zip-latest', + + ) + + PERL6_BUILTIN_CLASSES = ( + #Booleans + 'False','True', + #Classes + 'Any','Array','Associative','AST','atomicint','Attribute','Backtrace', + 'Backtrace::Frame','Bag','Baggy','BagHash','Blob','Block','Bool','Buf', + 'Callable','CallFrame','Cancellation','Capture','CArray','Channel','Code', + 'compiler','Complex','ComplexStr','Cool','CurrentThreadScheduler', + 'Cursor','Date','Dateish','DateTime','Distro','Duration','Encoding', + 'Exception','Failure','FatRat','Grammar','Hash','HyperWhatever','Instant', + 'Int','int16','int32','int64','int8','IntStr','IO','IO::ArgFiles', + 'IO::CatHandle','IO::Handle','IO::Notification','IO::Path', + 'IO::Path::Cygwin','IO::Path::QNX','IO::Path::Unix','IO::Path::Win32', + 'IO::Pipe','IO::Socket','IO::Socket::Async','IO::Socket::INET','IO::Spec', + 'IO::Spec::Cygwin','IO::Spec::QNX','IO::Spec::Unix','IO::Spec::Win32', + 'IO::Special','Iterable','Iterator','Junction','Kernel','Label','List', + 'Lock','Lock::Async','long','longlong','Macro','Map','Match', + 'Metamodel::AttributeContainer','Metamodel::C3MRO','Metamodel::ClassHOW', + 'Metamodel::EnumHOW','Metamodel::Finalization','Metamodel::MethodContainer', + 'Metamodel::MROBasedMethodDispatch','Metamodel::MultipleInheritance', + 'Metamodel::Naming','Metamodel::Primitives','Metamodel::PrivateMethodContainer', + 'Metamodel::RoleContainer','Metamodel::Trusting','Method','Mix','MixHash', + 'Mixy','Mu','NFC','NFD','NFKC','NFKD','Nil','Num','num32','num64', + 'Numeric','NumStr','ObjAt','Order','Pair','Parameter','Perl','Pod::Block', + 'Pod::Block::Code','Pod::Block::Comment','Pod::Block::Declarator', + 'Pod::Block::Named','Pod::Block::Para','Pod::Block::Table','Pod::Heading', + 'Pod::Item','Pointer','Positional','PositionalBindFailover','Proc', + 'Proc::Async','Promise','Proxy','PseudoStash','QuantHash','Range','Rat', + 'Rational','RatStr','Real','Regex','Routine','Scalar','Scheduler', + 'Semaphore','Seq','Set','SetHash','Setty','Signature','size_t','Slip', + 'Stash','Str','StrDistance','Stringy','Sub','Submethod','Supplier', + 'Supplier::Preserving','Supply','Systemic','Tap','Telemetry', + 'Telemetry::Instrument::Thread','Telemetry::Instrument::Usage', + 'Telemetry::Period','Telemetry::Sampler','Thread','ThreadPoolScheduler', + 'UInt','uint16','uint32','uint64','uint8','Uni','utf8','Variable', + 'Version','VM','Whatever','WhateverCode','WrapHandle' + ) + + PERL6_OPERATORS = ( + 'X', 'Z', 'after', 'also', 'and', 'andthen', 'before', 'cmp', 'div', + 'eq', 'eqv', 'extra', 'ff', 'fff', 'ge', 'gt', 'le', 'leg', 'lt', 'm', + 'mm', 'mod', 'ne', 'or', 'orelse', 'rx', 's', 'tr', 'x', 'xor', 'xx', + '++', '--', '**', '!', '+', '-', '~', '?', '|', '||', '+^', '~^', '?^', + '^', '*', '/', '%', '%%', '+&', '+<', '+>', '~&', '~<', '~>', '?&', + 'gcd', 'lcm', '+', '-', '+|', '+^', '~|', '~^', '?|', '?^', + '~', '&', '^', 'but', 'does', '<=>', '..', '..^', '^..', '^..^', + '!=', '==', '<', '<=', '>', '>=', '~~', '===', '!eqv', + '&&', '||', '^^', '//', 'min', 'max', '??', '!!', 'ff', 'fff', 'so', + 'not', '<==', '==>', '<<==', '==>>','unicmp', + ) + + # Perl 6 has a *lot* of possible bracketing characters + # this list was lifted from STD.pm6 (https://github.com/perl6/std) + PERL6_BRACKETS = { + '\u0028': '\u0029', '\u003c': '\u003e', '\u005b': '\u005d', + '\u007b': '\u007d', '\u00ab': '\u00bb', '\u0f3a': '\u0f3b', + '\u0f3c': '\u0f3d', '\u169b': '\u169c', '\u2018': '\u2019', + '\u201a': '\u2019', '\u201b': '\u2019', '\u201c': '\u201d', + '\u201e': '\u201d', '\u201f': '\u201d', '\u2039': '\u203a', + '\u2045': '\u2046', '\u207d': '\u207e', '\u208d': '\u208e', + '\u2208': '\u220b', '\u2209': '\u220c', '\u220a': '\u220d', + '\u2215': '\u29f5', '\u223c': '\u223d', '\u2243': '\u22cd', + '\u2252': '\u2253', '\u2254': '\u2255', '\u2264': '\u2265', + '\u2266': '\u2267', '\u2268': '\u2269', '\u226a': '\u226b', + '\u226e': '\u226f', '\u2270': '\u2271', '\u2272': '\u2273', + '\u2274': '\u2275', '\u2276': '\u2277', '\u2278': '\u2279', + '\u227a': '\u227b', '\u227c': '\u227d', '\u227e': '\u227f', + '\u2280': '\u2281', '\u2282': '\u2283', '\u2284': '\u2285', + '\u2286': '\u2287', '\u2288': '\u2289', '\u228a': '\u228b', + '\u228f': '\u2290', '\u2291': '\u2292', '\u2298': '\u29b8', + '\u22a2': '\u22a3', '\u22a6': '\u2ade', '\u22a8': '\u2ae4', + '\u22a9': '\u2ae3', '\u22ab': '\u2ae5', '\u22b0': '\u22b1', + '\u22b2': '\u22b3', '\u22b4': '\u22b5', '\u22b6': '\u22b7', + '\u22c9': '\u22ca', '\u22cb': '\u22cc', '\u22d0': '\u22d1', + '\u22d6': '\u22d7', '\u22d8': '\u22d9', '\u22da': '\u22db', + '\u22dc': '\u22dd', '\u22de': '\u22df', '\u22e0': '\u22e1', + '\u22e2': '\u22e3', '\u22e4': '\u22e5', '\u22e6': '\u22e7', + '\u22e8': '\u22e9', '\u22ea': '\u22eb', '\u22ec': '\u22ed', + '\u22f0': '\u22f1', '\u22f2': '\u22fa', '\u22f3': '\u22fb', + '\u22f4': '\u22fc', '\u22f6': '\u22fd', '\u22f7': '\u22fe', + '\u2308': '\u2309', '\u230a': '\u230b', '\u2329': '\u232a', + '\u23b4': '\u23b5', '\u2768': '\u2769', '\u276a': '\u276b', + '\u276c': '\u276d', '\u276e': '\u276f', '\u2770': '\u2771', + '\u2772': '\u2773', '\u2774': '\u2775', '\u27c3': '\u27c4', + '\u27c5': '\u27c6', '\u27d5': '\u27d6', '\u27dd': '\u27de', + '\u27e2': '\u27e3', '\u27e4': '\u27e5', '\u27e6': '\u27e7', + '\u27e8': '\u27e9', '\u27ea': '\u27eb', '\u2983': '\u2984', + '\u2985': '\u2986', '\u2987': '\u2988', '\u2989': '\u298a', + '\u298b': '\u298c', '\u298d': '\u298e', '\u298f': '\u2990', + '\u2991': '\u2992', '\u2993': '\u2994', '\u2995': '\u2996', + '\u2997': '\u2998', '\u29c0': '\u29c1', '\u29c4': '\u29c5', + '\u29cf': '\u29d0', '\u29d1': '\u29d2', '\u29d4': '\u29d5', + '\u29d8': '\u29d9', '\u29da': '\u29db', '\u29f8': '\u29f9', + '\u29fc': '\u29fd', '\u2a2b': '\u2a2c', '\u2a2d': '\u2a2e', + '\u2a34': '\u2a35', '\u2a3c': '\u2a3d', '\u2a64': '\u2a65', + '\u2a79': '\u2a7a', '\u2a7d': '\u2a7e', '\u2a7f': '\u2a80', + '\u2a81': '\u2a82', '\u2a83': '\u2a84', '\u2a8b': '\u2a8c', + '\u2a91': '\u2a92', '\u2a93': '\u2a94', '\u2a95': '\u2a96', + '\u2a97': '\u2a98', '\u2a99': '\u2a9a', '\u2a9b': '\u2a9c', + '\u2aa1': '\u2aa2', '\u2aa6': '\u2aa7', '\u2aa8': '\u2aa9', + '\u2aaa': '\u2aab', '\u2aac': '\u2aad', '\u2aaf': '\u2ab0', + '\u2ab3': '\u2ab4', '\u2abb': '\u2abc', '\u2abd': '\u2abe', + '\u2abf': '\u2ac0', '\u2ac1': '\u2ac2', '\u2ac3': '\u2ac4', + '\u2ac5': '\u2ac6', '\u2acd': '\u2ace', '\u2acf': '\u2ad0', + '\u2ad1': '\u2ad2', '\u2ad3': '\u2ad4', '\u2ad5': '\u2ad6', + '\u2aec': '\u2aed', '\u2af7': '\u2af8', '\u2af9': '\u2afa', + '\u2e02': '\u2e03', '\u2e04': '\u2e05', '\u2e09': '\u2e0a', + '\u2e0c': '\u2e0d', '\u2e1c': '\u2e1d', '\u2e20': '\u2e21', + '\u3008': '\u3009', '\u300a': '\u300b', '\u300c': '\u300d', + '\u300e': '\u300f', '\u3010': '\u3011', '\u3014': '\u3015', + '\u3016': '\u3017', '\u3018': '\u3019', '\u301a': '\u301b', + '\u301d': '\u301e', '\ufd3e': '\ufd3f', '\ufe17': '\ufe18', + '\ufe35': '\ufe36', '\ufe37': '\ufe38', '\ufe39': '\ufe3a', + '\ufe3b': '\ufe3c', '\ufe3d': '\ufe3e', '\ufe3f': '\ufe40', + '\ufe41': '\ufe42', '\ufe43': '\ufe44', '\ufe47': '\ufe48', + '\ufe59': '\ufe5a', '\ufe5b': '\ufe5c', '\ufe5d': '\ufe5e', + '\uff08': '\uff09', '\uff1c': '\uff1e', '\uff3b': '\uff3d', + '\uff5b': '\uff5d', '\uff5f': '\uff60', '\uff62': '\uff63', + } + + def _build_word_match(words, boundary_regex_fragment=None, prefix='', suffix=''): + if boundary_regex_fragment is None: + return r'\b(' + prefix + r'|'.join(re.escape(x) for x in words) + \ + suffix + r')\b' + else: + return r'(? 0: + next_open_pos = text.find(opening_chars, search_pos + n_chars) + next_close_pos = text.find(closing_chars, search_pos + n_chars) + + if next_close_pos == -1: + next_close_pos = len(text) + nesting_level = 0 + elif next_open_pos != -1 and next_open_pos < next_close_pos: + nesting_level += 1 + search_pos = next_open_pos + else: # next_close_pos < next_open_pos + nesting_level -= 1 + search_pos = next_close_pos + + end_pos = next_close_pos + + if end_pos < 0: # if we didn't find a closer, just highlight the + # rest of the text in this class + end_pos = len(text) + + if adverbs is not None and re.search(r':to\b', adverbs): + heredoc_terminator = text[match.start('delimiter') + n_chars:end_pos] + end_heredoc = re.search(r'^\s*' + re.escape(heredoc_terminator) + + r'\s*$', text[end_pos:], re.MULTILINE) + + if end_heredoc: + end_pos += end_heredoc.end() + else: + end_pos = len(text) + + yield match.start(), token_class, text[match.start():end_pos + n_chars] + context.pos = end_pos + n_chars + + return callback + + def opening_brace_callback(lexer, match, context): + stack = context.stack + + yield match.start(), Text, context.text[match.start():match.end()] + context.pos = match.end() + + # if we encounter an opening brace and we're one level + # below a token state, it means we need to increment + # the nesting level for braces so we know later when + # we should return to the token rules. + if len(stack) > 2 and stack[-2] == 'token': + context.perl6_token_nesting_level += 1 + + def closing_brace_callback(lexer, match, context): + stack = context.stack + + yield match.start(), Text, context.text[match.start():match.end()] + context.pos = match.end() + + # if we encounter a free closing brace and we're one level + # below a token state, it means we need to check the nesting + # level to see if we need to return to the token state. + if len(stack) > 2 and stack[-2] == 'token': + context.perl6_token_nesting_level -= 1 + if context.perl6_token_nesting_level == 0: + stack.pop() + + def embedded_perl6_callback(lexer, match, context): + context.perl6_token_nesting_level = 1 + yield match.start(), Text, context.text[match.start():match.end()] + context.pos = match.end() + context.stack.append('root') + + # If you're modifying these rules, be careful if you need to process '{' or '}' + # characters. We have special logic for processing these characters (due to the fact + # that you can nest Perl 6 code in regex blocks), so if you need to process one of + # them, make sure you also process the corresponding one! + tokens = { + 'common': [ + (r'#[`|=](?P(?P[' + ''.join(PERL6_BRACKETS) + r'])(?P=first_char)*)', + brackets_callback(Comment.Multiline)), + (r'#[^\n]*$', Comment.Single), + (r'^(\s*)=begin\s+(\w+)\b.*?^\1=end\s+\2', Comment.Multiline), + (r'^(\s*)=for.*?\n\s*?\n', Comment.Multiline), + (r'^=.*?\n\s*?\n', Comment.Multiline), + (r'(regex|token|rule)(\s*' + PERL6_IDENTIFIER_RANGE + '+:sym)', + bygroups(Keyword, Name), 'token-sym-brackets'), + (r'(regex|token|rule)(?!' + PERL6_IDENTIFIER_RANGE + r')(\s*' + PERL6_IDENTIFIER_RANGE + '+)?', + bygroups(Keyword, Name), 'pre-token'), + # deal with a special case in the Perl 6 grammar (role q { ... }) + (r'(role)(\s+)(q)(\s*)', bygroups(Keyword, Text, Name, Text)), + (_build_word_match(PERL6_KEYWORDS, PERL6_IDENTIFIER_RANGE), Keyword), + (_build_word_match(PERL6_BUILTIN_CLASSES, PERL6_IDENTIFIER_RANGE, suffix='(?::[UD])?'), + Name.Builtin), + (_build_word_match(PERL6_BUILTINS, PERL6_IDENTIFIER_RANGE), Name.Builtin), + # copied from PerlLexer + (r'[$@%&][.^:?=!~]?' + PERL6_IDENTIFIER_RANGE + '+(?:<<.*?>>|<.*?>|«.*?»)*', + Name.Variable), + (r'\$[!/](?:<<.*?>>|<.*?>|«.*?»)*', Name.Variable.Global), + (r'::\?\w+', Name.Variable.Global), + (r'[$@%&]\*' + PERL6_IDENTIFIER_RANGE + '+(?:<<.*?>>|<.*?>|«.*?»)*', + Name.Variable.Global), + (r'\$(?:<.*?>)+', Name.Variable), + (r'(?:q|qq|Q)[a-zA-Z]?\s*(?P:[\w\s:]+)?\s*(?P(?P[^0-9a-zA-Z:\s])' + r'(?P=first_char)*)', brackets_callback(String)), + # copied from PerlLexer + (r'0_?[0-7]+(_[0-7]+)*', Number.Oct), + (r'0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*', Number.Hex), + (r'0b[01]+(_[01]+)*', Number.Bin), + (r'(?i)(\d*(_\d*)*\.\d+(_\d*)*|\d+(_\d*)*\.\d+(_\d*)*)(e[+-]?\d+)?', + Number.Float), + (r'(?i)\d+(_\d*)*e[+-]?\d+(_\d*)*', Number.Float), + (r'\d+(_\d+)*', Number.Integer), + (r'(?<=~~)\s*/(?:\\\\|\\/|.)*?/', String.Regex), + (r'(?<=[=(,])\s*/(?:\\\\|\\/|.)*?/', String.Regex), + (r'm\w+(?=\()', Name), + (r'(?:m|ms|rx)\s*(?P:[\w\s:]+)?\s*(?P(?P[^\w:\s])' + r'(?P=first_char)*)', brackets_callback(String.Regex)), + (r'(?:s|ss|tr)\s*(?::[\w\s:]+)?\s*/(?:\\\\|\\/|.)*?/(?:\\\\|\\/|.)*?/', + String.Regex), + (r'<[^\s=].*?\S>', String), + (_build_word_match(PERL6_OPERATORS), Operator), + (r'\w' + PERL6_IDENTIFIER_RANGE + '*', Name), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String), + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + ], + 'root': [ + include('common'), + (r'\{', opening_brace_callback), + (r'\}', closing_brace_callback), + (r'.+?', Text), + ], + 'pre-token': [ + include('common'), + (r'\{', Text, ('#pop', 'token')), + (r'.+?', Text), + ], + 'token-sym-brackets': [ + (r'(?P(?P[' + ''.join(PERL6_BRACKETS) + '])(?P=first_char)*)', + brackets_callback(Name), ('#pop', 'pre-token')), + default(('#pop', 'pre-token')), + ], + 'token': [ + (r'\}', Text, '#pop'), + (r'(?<=:)(?:my|our|state|constant|temp|let).*?;', using(this)), + # make sure that quotes in character classes aren't treated as strings + (r'<(?:[-!?+.]\s*)?\[.*?\]>', String.Regex), + # make sure that '#' characters in quotes aren't treated as comments + (r"(?my|our)\s+)?(?:module|class|role|enum|grammar)', line) + if class_decl: + if saw_perl_decl or class_decl.group('scope') is not None: + return True + rating = 0.05 + continue + break + + if ':=' in text: + # Same logic as above for PerlLexer + rating /= 2 + + return rating + + def __init__(self, **options): + super().__init__(**options) + self.encoding = options.get('encoding', 'utf-8') diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/php.py b/.venv/lib/python3.8/site-packages/pygments/lexers/php.py new file mode 100644 index 0000000..3ba299a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/php.py @@ -0,0 +1,320 @@ +""" + pygments.lexers.php + ~~~~~~~~~~~~~~~~~~~ + + Lexers for PHP and related languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import Lexer, RegexLexer, include, bygroups, default, \ + using, this, words, do_insertions +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Other, Generic +from pygments.util import get_bool_opt, get_list_opt, shebang_matches + +__all__ = ['ZephirLexer', 'PsyshConsoleLexer', 'PhpLexer'] + +line_re = re.compile('.*?\n') + + +class ZephirLexer(RegexLexer): + """ + For `Zephir language `_ source code. + + Zephir is a compiled high level language aimed + to the creation of C-extensions for PHP. + + .. versionadded:: 2.0 + """ + + name = 'Zephir' + aliases = ['zephir'] + filenames = ['*.zep'] + + zephir_keywords = ['fetch', 'echo', 'isset', 'empty'] + zephir_type = ['bit', 'bits', 'string'] + + flags = re.DOTALL | re.MULTILINE + + tokens = { + 'commentsandwhitespace': [ + (r'\s+', Text), + (r'//.*?\n', Comment.Single), + (r'/\*.*?\*/', Comment.Multiline) + ], + 'slashstartsregex': [ + include('commentsandwhitespace'), + (r'/(\\.|[^[/\\\n]|\[(\\.|[^\]\\\n])*])+/' + r'([gim]+\b|\B)', String.Regex, '#pop'), + (r'/', Operator, '#pop'), + default('#pop') + ], + 'badregex': [ + (r'\n', Text, '#pop') + ], + 'root': [ + (r'^(?=\s|/)', Text, 'slashstartsregex'), + include('commentsandwhitespace'), + (r'\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|' + r'(<<|>>>?|==?|!=?|->|[-<>+*%&|^/])=?', Operator, 'slashstartsregex'), + (r'[{(\[;,]', Punctuation, 'slashstartsregex'), + (r'[})\].]', Punctuation), + (r'(for|in|while|do|break|return|continue|switch|case|default|if|else|loop|' + r'require|inline|throw|try|catch|finally|new|delete|typeof|instanceof|void|' + r'namespace|use|extends|this|fetch|isset|unset|echo|fetch|likely|unlikely|' + r'empty)\b', Keyword, 'slashstartsregex'), + (r'(var|let|with|function)\b', Keyword.Declaration, 'slashstartsregex'), + (r'(abstract|boolean|bool|char|class|const|double|enum|export|extends|final|' + r'native|goto|implements|import|int|string|interface|long|ulong|char|uchar|' + r'float|unsigned|private|protected|public|short|static|self|throws|reverse|' + r'transient|volatile)\b', Keyword.Reserved), + (r'(true|false|null|undefined)\b', Keyword.Constant), + (r'(Array|Boolean|Date|_REQUEST|_COOKIE|_SESSION|' + r'_GET|_POST|_SERVER|this|stdClass|range|count|iterator|' + r'window)\b', Name.Builtin), + (r'[$a-zA-Z_][\w\\]*', Name.Other), + (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float), + (r'0x[0-9a-fA-F]+', Number.Hex), + (r'[0-9]+', Number.Integer), + (r'"(\\\\|\\[^\\]|[^"\\])*"', String.Double), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String.Single), + ] + } + + +class PsyshConsoleLexer(Lexer): + """ + For `PsySH`_ console output, such as: + + .. sourcecode:: psysh + + >>> $greeting = function($name): string { + ... return "Hello, {$name}"; + ... }; + => Closure($name): string {#2371 …3} + >>> $greeting('World') + => "Hello, World" + + .. _PsySH: https://psysh.org/ + .. versionadded:: 2.7 + """ + name = 'PsySH console session for PHP' + aliases = ['psysh'] + + def __init__(self, **options): + options['startinline'] = True + Lexer.__init__(self, **options) + + def get_tokens_unprocessed(self, text): + phplexer = PhpLexer(**self.options) + curcode = '' + insertions = [] + for match in line_re.finditer(text): + line = match.group() + if line.startswith('>>> ') or line.startswith('... '): + insertions.append((len(curcode), + [(0, Generic.Prompt, line[:4])])) + curcode += line[4:] + elif line.rstrip() == '...': + insertions.append((len(curcode), + [(0, Generic.Prompt, '...')])) + curcode += line[3:] + else: + if curcode: + yield from do_insertions( + insertions, phplexer.get_tokens_unprocessed(curcode)) + curcode = '' + insertions = [] + yield match.start(), Generic.Output, line + if curcode: + yield from do_insertions(insertions, + phplexer.get_tokens_unprocessed(curcode)) + + +class PhpLexer(RegexLexer): + """ + For `PHP `_ source code. + For PHP embedded in HTML, use the `HtmlPhpLexer`. + + Additional options accepted: + + `startinline` + If given and ``True`` the lexer starts highlighting with + php code (i.e.: no starting ``>> from pygments.lexers._php_builtins import MODULES + >>> MODULES.keys() + ['PHP Options/Info', 'Zip', 'dba', ...] + + In fact the names of those modules match the module names from + the php documentation. + """ + + name = 'PHP' + aliases = ['php', 'php3', 'php4', 'php5'] + filenames = ['*.php', '*.php[345]', '*.inc'] + mimetypes = ['text/x-php'] + + # Note that a backslash is included in the following two patterns + # PHP uses a backslash as a namespace separator + _ident_char = r'[\\\w]|[^\x00-\x7f]' + _ident_begin = r'(?:[\\_a-z]|[^\x00-\x7f])' + _ident_end = r'(?:' + _ident_char + ')*' + _ident_inner = _ident_begin + _ident_end + + flags = re.IGNORECASE | re.DOTALL | re.MULTILINE + tokens = { + 'root': [ + (r'<\?(php)?', Comment.Preproc, 'php'), + (r'[^<]+', Other), + (r'<', Other) + ], + 'php': [ + (r'\?>', Comment.Preproc, '#pop'), + (r'(<<<)([\'"]?)(' + _ident_inner + r')(\2\n.*?\n\s*)(\3)(;?)(\n)', + bygroups(String, String, String.Delimiter, String, String.Delimiter, + Punctuation, Text)), + (r'\s+', Text), + (r'#.*?\n', Comment.Single), + (r'//.*?\n', Comment.Single), + # put the empty comment here, it is otherwise seen as + # the start of a docstring + (r'/\*\*/', Comment.Multiline), + (r'/\*\*.*?\*/', String.Doc), + (r'/\*.*?\*/', Comment.Multiline), + (r'(->|::)(\s*)(' + _ident_inner + ')', + bygroups(Operator, Text, Name.Attribute)), + (r'[~!%^&*+=|:.<>/@-]+', Operator), + (r'\?', Operator), # don't add to the charclass above! + (r'[\[\]{}();,]+', Punctuation), + (r'(class)(\s+)', bygroups(Keyword, Text), 'classname'), + (r'(function)(\s*)(?=\()', bygroups(Keyword, Text)), + (r'(function)(\s+)(&?)(\s*)', + bygroups(Keyword, Text, Operator, Text), 'functionname'), + (r'(const)(\s+)(' + _ident_inner + ')', + bygroups(Keyword, Text, Name.Constant)), + (r'(and|E_PARSE|old_function|E_ERROR|or|as|E_WARNING|parent|' + r'eval|PHP_OS|break|exit|case|extends|PHP_VERSION|cfunction|' + r'FALSE|print|for|require|continue|foreach|require_once|' + r'declare|return|default|static|do|switch|die|stdClass|' + r'echo|else|TRUE|elseif|var|empty|if|xor|enddeclare|include|' + r'virtual|endfor|include_once|while|endforeach|global|' + r'endif|list|endswitch|new|endwhile|not|' + r'array|E_ALL|NULL|final|php_user_filter|interface|' + r'implements|public|private|protected|abstract|clone|try|' + r'catch|throw|this|use|namespace|trait|yield|' + r'finally)\b', Keyword), + (r'(true|false|null)\b', Keyword.Constant), + include('magicconstants'), + (r'\$\{\$+' + _ident_inner + r'\}', Name.Variable), + (r'\$+' + _ident_inner, Name.Variable), + (_ident_inner, Name.Other), + (r'(\d+\.\d*|\d*\.\d+)(e[+-]?[0-9]+)?', Number.Float), + (r'\d+e[+-]?[0-9]+', Number.Float), + (r'0[0-7]+', Number.Oct), + (r'0x[a-f0-9]+', Number.Hex), + (r'\d+', Number.Integer), + (r'0b[01]+', Number.Bin), + (r"'([^'\\]*(?:\\.[^'\\]*)*)'", String.Single), + (r'`([^`\\]*(?:\\.[^`\\]*)*)`', String.Backtick), + (r'"', String.Double, 'string'), + ], + 'magicfuncs': [ + # source: http://php.net/manual/en/language.oop5.magic.php + (words(( + '__construct', '__destruct', '__call', '__callStatic', '__get', '__set', + '__isset', '__unset', '__sleep', '__wakeup', '__toString', '__invoke', + '__set_state', '__clone', '__debugInfo',), suffix=r'\b'), + Name.Function.Magic), + ], + 'magicconstants': [ + # source: http://php.net/manual/en/language.constants.predefined.php + (words(( + '__LINE__', '__FILE__', '__DIR__', '__FUNCTION__', '__CLASS__', + '__TRAIT__', '__METHOD__', '__NAMESPACE__',), + suffix=r'\b'), + Name.Constant), + ], + 'classname': [ + (_ident_inner, Name.Class, '#pop') + ], + 'functionname': [ + include('magicfuncs'), + (_ident_inner, Name.Function, '#pop'), + default('#pop') + ], + 'string': [ + (r'"', String.Double, '#pop'), + (r'[^{$"\\]+', String.Double), + (r'\\([nrt"$\\]|[0-7]{1,3}|x[0-9a-f]{1,2})', String.Escape), + (r'\$' + _ident_inner + r'(\[\S+?\]|->' + _ident_inner + ')?', + String.Interpol), + (r'(\{\$\{)(.*?)(\}\})', + bygroups(String.Interpol, using(this, _startinline=True), + String.Interpol)), + (r'(\{)(\$.*?)(\})', + bygroups(String.Interpol, using(this, _startinline=True), + String.Interpol)), + (r'(\$\{)(\S+)(\})', + bygroups(String.Interpol, Name.Variable, String.Interpol)), + (r'[${\\]', String.Double) + ], + } + + def __init__(self, **options): + self.funcnamehighlighting = get_bool_opt( + options, 'funcnamehighlighting', True) + self.disabledmodules = get_list_opt( + options, 'disabledmodules', ['unknown']) + self.startinline = get_bool_opt(options, 'startinline', False) + + # private option argument for the lexer itself + if '_startinline' in options: + self.startinline = options.pop('_startinline') + + # collect activated functions in a set + self._functions = set() + if self.funcnamehighlighting: + from pygments.lexers._php_builtins import MODULES + for key, value in MODULES.items(): + if key not in self.disabledmodules: + self._functions.update(value) + RegexLexer.__init__(self, **options) + + def get_tokens_unprocessed(self, text): + stack = ['root'] + if self.startinline: + stack.append('php') + for index, token, value in \ + RegexLexer.get_tokens_unprocessed(self, text, stack): + if token is Name.Other: + if value in self._functions: + yield index, Name.Builtin, value + continue + yield index, token, value + + def analyse_text(text): + if shebang_matches(text, r'php'): + return True + rv = 0.0 + if re.search(r'<\?(?!xml)', text): + rv += 0.3 + return rv diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/pointless.py b/.venv/lib/python3.8/site-packages/pygments/lexers/pointless.py new file mode 100644 index 0000000..c340107 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/pointless.py @@ -0,0 +1,70 @@ +""" + pygments.lexers.pointless + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Pointless. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, words +from pygments.token import Comment, Error, Keyword, Name, Number, Operator, \ + Punctuation, String, Text + +__all__ = ['PointlessLexer'] + + +class PointlessLexer(RegexLexer): + """ + For `Pointless `_ source code. + + .. versionadded:: 2.7 + """ + + name = 'Pointless' + aliases = ['pointless'] + filenames = ['*.ptls'] + + ops = words([ + "+", "-", "*", "/", "**", "%", "+=", "-=", "*=", + "/=", "**=", "%=", "|>", "=", "==", "!=", "<", ">", + "<=", ">=", "=>", "$", "++", + ]) + + keywords = words([ + "if", "then", "else", "where", "with", "cond", + "case", "and", "or", "not", "in", "as", "for", + "requires", "throw", "try", "catch", "when", + "yield", "upval", + ], suffix=r'\b') + + tokens = { + 'root': [ + (r'[ \n\r]+', Text), + (r'--.*$', Comment.Single), + (r'"""', String, 'multiString'), + (r'"', String, 'string'), + (r'[\[\](){}:;,.]', Punctuation), + (ops, Operator), + (keywords, Keyword), + (r'\d+|\d*\.\d+', Number), + (r'(true|false)\b', Name.Builtin), + (r'[A-Z][a-zA-Z0-9]*\b', String.Symbol), + (r'output\b', Name.Variable.Magic), + (r'(export|import)\b', Keyword.Namespace), + (r'[a-z][a-zA-Z0-9]*\b', Name.Variable) + ], + 'multiString': [ + (r'\\.', String.Escape), + (r'"""', String, '#pop'), + (r'"', String), + (r'[^\\"]+', String), + ], + 'string': [ + (r'\\.', String.Escape), + (r'"', String, '#pop'), + (r'\n', Error), + (r'[^\\"]+', String), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/pony.py b/.venv/lib/python3.8/site-packages/pygments/lexers/pony.py new file mode 100644 index 0000000..0cd5dbd --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/pony.py @@ -0,0 +1,93 @@ +""" + pygments.lexers.pony + ~~~~~~~~~~~~~~~~~~~~ + + Lexers for Pony and related languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, bygroups, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation + +__all__ = ['PonyLexer'] + + +class PonyLexer(RegexLexer): + """ + For Pony source code. + + .. versionadded:: 2.4 + """ + + name = 'Pony' + aliases = ['pony'] + filenames = ['*.pony'] + + _caps = r'(iso|trn|ref|val|box|tag)' + + tokens = { + 'root': [ + (r'\n', Text), + (r'[^\S\n]+', Text), + (r'//.*\n', Comment.Single), + (r'/\*', Comment.Multiline, 'nested_comment'), + (r'"""(?:.|\n)*?"""', String.Doc), + (r'"', String, 'string'), + (r'\'.*\'', String.Char), + (r'=>|[]{}:().~;,|&!^?[]', Punctuation), + (words(( + 'addressof', 'and', 'as', 'consume', 'digestof', 'is', 'isnt', + 'not', 'or'), + suffix=r'\b'), + Operator.Word), + (r'!=|==|<<|>>|[-+/*%=<>]', Operator), + (words(( + 'box', 'break', 'compile_error', 'compile_intrinsic', + 'continue', 'do', 'else', 'elseif', 'embed', 'end', 'error', + 'for', 'if', 'ifdef', 'in', 'iso', 'lambda', 'let', 'match', + 'object', 'recover', 'ref', 'repeat', 'return', 'tag', 'then', + 'this', 'trn', 'try', 'until', 'use', 'var', 'val', 'where', + 'while', 'with', '#any', '#read', '#send', '#share'), + suffix=r'\b'), + Keyword), + (r'(actor|class|struct|primitive|interface|trait|type)((?:\s)+)', + bygroups(Keyword, Text), 'typename'), + (r'(new|fun|be)((?:\s)+)', bygroups(Keyword, Text), 'methodname'), + (words(( + 'I8', 'U8', 'I16', 'U16', 'I32', 'U32', 'I64', 'U64', 'I128', + 'U128', 'ILong', 'ULong', 'ISize', 'USize', 'F32', 'F64', + 'Bool', 'Pointer', 'None', 'Any', 'Array', 'String', + 'Iterator'), + suffix=r'\b'), + Name.Builtin.Type), + (r'_?[A-Z]\w*', Name.Type), + (r'(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+', Number.Float), + (r'0x[0-9a-fA-F]+', Number.Hex), + (r'\d+', Number.Integer), + (r'(true|false)\b', Name.Builtin), + (r'_\d*', Name), + (r'_?[a-z][\w\']*', Name) + ], + 'typename': [ + (_caps + r'?((?:\s)*)(_?[A-Z]\w*)', + bygroups(Keyword, Text, Name.Class), '#pop') + ], + 'methodname': [ + (_caps + r'?((?:\s)*)(_?[a-z]\w*)', + bygroups(Keyword, Text, Name.Function), '#pop') + ], + 'nested_comment': [ + (r'[^*/]+', Comment.Multiline), + (r'/\*', Comment.Multiline, '#push'), + (r'\*/', Comment.Multiline, '#pop'), + (r'[*/]', Comment.Multiline) + ], + 'string': [ + (r'"', String, '#pop'), + (r'\\"', String), + (r'[^\\"]+', String) + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/praat.py b/.venv/lib/python3.8/site-packages/pygments/lexers/praat.py new file mode 100644 index 0000000..8fbae8c --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/praat.py @@ -0,0 +1,301 @@ +""" + pygments.lexers.praat + ~~~~~~~~~~~~~~~~~~~~~ + + Lexer for Praat + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, words, bygroups, include +from pygments.token import Name, Text, Comment, Keyword, String, Punctuation, Number, \ + Operator + +__all__ = ['PraatLexer'] + + +class PraatLexer(RegexLexer): + """ + For `Praat `_ scripts. + + .. versionadded:: 2.1 + """ + + name = 'Praat' + aliases = ['praat'] + filenames = ['*.praat', '*.proc', '*.psc'] + + keywords = ( + 'if', 'then', 'else', 'elsif', 'elif', 'endif', 'fi', 'for', 'from', 'to', + 'endfor', 'endproc', 'while', 'endwhile', 'repeat', 'until', 'select', 'plus', + 'minus', 'demo', 'assert', 'stopwatch', 'nocheck', 'nowarn', 'noprogress', + 'editor', 'endeditor', 'clearinfo', + ) + + functions_string = ( + 'backslashTrigraphsToUnicode', 'chooseDirectory', 'chooseReadFile', + 'chooseWriteFile', 'date', 'demoKey', 'do', 'environment', 'extractLine', + 'extractWord', 'fixed', 'info', 'left', 'mid', 'percent', 'readFile', 'replace', + 'replace_regex', 'right', 'selected', 'string', 'unicodeToBackslashTrigraphs', + ) + + functions_numeric = ( + 'abs', 'appendFile', 'appendFileLine', 'appendInfo', 'appendInfoLine', 'arccos', + 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh', 'barkToHertz', + 'beginPause', 'beginSendPraat', 'besselI', 'besselK', 'beta', 'beta2', + 'binomialP', 'binomialQ', 'boolean', 'ceiling', 'chiSquareP', 'chiSquareQ', + 'choice', 'comment', 'cos', 'cosh', 'createDirectory', 'deleteFile', + 'demoClicked', 'demoClickedIn', 'demoCommandKeyPressed', + 'demoExtraControlKeyPressed', 'demoInput', 'demoKeyPressed', + 'demoOptionKeyPressed', 'demoShiftKeyPressed', 'demoShow', 'demoWaitForInput', + 'demoWindowTitle', 'demoX', 'demoY', 'differenceLimensToPhon', 'do', 'editor', + 'endPause', 'endSendPraat', 'endsWith', 'erb', 'erbToHertz', 'erf', 'erfc', + 'exitScript', 'exp', 'extractNumber', 'fileReadable', 'fisherP', 'fisherQ', + 'floor', 'gaussP', 'gaussQ', 'hertzToBark', 'hertzToErb', 'hertzToMel', + 'hertzToSemitones', 'imax', 'imin', 'incompleteBeta', 'incompleteGammaP', 'index', + 'index_regex', 'integer', 'invBinomialP', 'invBinomialQ', 'invChiSquareQ', 'invFisherQ', + 'invGaussQ', 'invSigmoid', 'invStudentQ', 'length', 'ln', 'lnBeta', 'lnGamma', + 'log10', 'log2', 'max', 'melToHertz', 'min', 'minusObject', 'natural', 'number', + 'numberOfColumns', 'numberOfRows', 'numberOfSelected', 'objectsAreIdentical', + 'option', 'optionMenu', 'pauseScript', 'phonToDifferenceLimens', 'plusObject', + 'positive', 'randomBinomial', 'randomGauss', 'randomInteger', 'randomPoisson', + 'randomUniform', 'real', 'readFile', 'removeObject', 'rindex', 'rindex_regex', + 'round', 'runScript', 'runSystem', 'runSystem_nocheck', 'selectObject', + 'selected', 'semitonesToHertz', 'sentence', 'sentencetext', 'sigmoid', 'sin', 'sinc', + 'sincpi', 'sinh', 'soundPressureToPhon', 'sqrt', 'startsWith', 'studentP', + 'studentQ', 'tan', 'tanh', 'text', 'variableExists', 'word', 'writeFile', 'writeFileLine', + 'writeInfo', 'writeInfoLine', + ) + + functions_array = ( + 'linear', 'randomGauss', 'randomInteger', 'randomUniform', 'zero', + ) + + objects = ( + 'Activation', 'AffineTransform', 'AmplitudeTier', 'Art', 'Artword', + 'Autosegment', 'BarkFilter', 'BarkSpectrogram', 'CCA', 'Categories', + 'Cepstrogram', 'Cepstrum', 'Cepstrumc', 'ChebyshevSeries', 'ClassificationTable', + 'Cochleagram', 'Collection', 'ComplexSpectrogram', 'Configuration', 'Confusion', + 'ContingencyTable', 'Corpus', 'Correlation', 'Covariance', + 'CrossCorrelationTable', 'CrossCorrelationTables', 'DTW', 'DataModeler', + 'Diagonalizer', 'Discriminant', 'Dissimilarity', 'Distance', 'Distributions', + 'DurationTier', 'EEG', 'ERP', 'ERPTier', 'EditCostsTable', 'EditDistanceTable', + 'Eigen', 'Excitation', 'Excitations', 'ExperimentMFC', 'FFNet', 'FeatureWeights', + 'FileInMemory', 'FilesInMemory', 'Formant', 'FormantFilter', 'FormantGrid', + 'FormantModeler', 'FormantPoint', 'FormantTier', 'GaussianMixture', 'HMM', + 'HMM_Observation', 'HMM_ObservationSequence', 'HMM_State', 'HMM_StateSequence', + 'Harmonicity', 'ISpline', 'Index', 'Intensity', 'IntensityTier', 'IntervalTier', + 'KNN', 'KlattGrid', 'KlattTable', 'LFCC', 'LPC', 'Label', 'LegendreSeries', + 'LinearRegression', 'LogisticRegression', 'LongSound', 'Ltas', 'MFCC', 'MSpline', + 'ManPages', 'Manipulation', 'Matrix', 'MelFilter', 'MelSpectrogram', + 'MixingMatrix', 'Movie', 'Network', 'Object', 'OTGrammar', 'OTHistory', 'OTMulti', + 'PCA', 'PairDistribution', 'ParamCurve', 'Pattern', 'Permutation', 'Photo', + 'Pitch', 'PitchModeler', 'PitchTier', 'PointProcess', 'Polygon', 'Polynomial', + 'PowerCepstrogram', 'PowerCepstrum', 'Procrustes', 'RealPoint', 'RealTier', + 'ResultsMFC', 'Roots', 'SPINET', 'SSCP', 'SVD', 'Salience', 'ScalarProduct', + 'Similarity', 'SimpleString', 'SortedSetOfString', 'Sound', 'Speaker', + 'Spectrogram', 'Spectrum', 'SpectrumTier', 'SpeechSynthesizer', 'SpellingChecker', + 'Strings', 'StringsIndex', 'Table', 'TableOfReal', 'TextGrid', 'TextInterval', + 'TextPoint', 'TextTier', 'Tier', 'Transition', 'VocalTract', 'VocalTractTier', + 'Weight', 'WordList', + ) + + variables_numeric = ( + 'macintosh', 'windows', 'unix', 'praatVersion', 'pi', 'e', 'undefined', + ) + + variables_string = ( + 'praatVersion', 'tab', 'shellDirectory', 'homeDirectory', + 'preferencesDirectory', 'newline', 'temporaryDirectory', + 'defaultDirectory', + ) + + object_attributes = ( + 'ncol', 'nrow', 'xmin', 'ymin', 'xmax', 'ymax', 'nx', 'ny', 'dx', 'dy', + ) + + tokens = { + 'root': [ + (r'(\s+)(#.*?$)', bygroups(Text, Comment.Single)), + (r'^#.*?$', Comment.Single), + (r';[^\n]*', Comment.Single), + (r'\s+', Text), + + (r'\bprocedure\b', Keyword, 'procedure_definition'), + (r'\bcall\b', Keyword, 'procedure_call'), + (r'@', Name.Function, 'procedure_call'), + + include('function_call'), + + (words(keywords, suffix=r'\b'), Keyword), + + (r'(\bform\b)(\s+)([^\n]+)', + bygroups(Keyword, Text, String), 'old_form'), + + (r'(print(?:line|tab)?|echo|exit|asserterror|pause|send(?:praat|socket)|' + r'include|execute|system(?:_nocheck)?)(\s+)', + bygroups(Keyword, Text), 'string_unquoted'), + + (r'(goto|label)(\s+)(\w+)', bygroups(Keyword, Text, Name.Label)), + + include('variable_name'), + include('number'), + + (r'"', String, 'string'), + + (words((objects), suffix=r'(?=\s+\S+\n)'), Name.Class, 'string_unquoted'), + + (r'\b[A-Z]', Keyword, 'command'), + (r'(\.{3}|[)(,])', Punctuation), + ], + 'command': [ + (r'( ?[\w()-]+ ?)', Keyword), + + include('string_interpolated'), + + (r'\.{3}', Keyword, ('#pop', 'old_arguments')), + (r':', Keyword, ('#pop', 'comma_list')), + (r'\s', Text, '#pop'), + ], + 'procedure_call': [ + (r'\s+', Text), + (r'([\w.]+)(:|\s*\()', + bygroups(Name.Function, Text), '#pop'), + (r'([\w.]+)', Name.Function, ('#pop', 'old_arguments')), + ], + 'procedure_definition': [ + (r'\s', Text), + (r'([\w.]+)(\s*?[(:])', + bygroups(Name.Function, Text), '#pop'), + (r'([\w.]+)([^\n]*)', + bygroups(Name.Function, Text), '#pop'), + ], + 'function_call': [ + (words(functions_string, suffix=r'\$(?=\s*[:(])'), Name.Function, 'function'), + (words(functions_array, suffix=r'#(?=\s*[:(])'), Name.Function, 'function'), + (words(functions_numeric, suffix=r'(?=\s*[:(])'), Name.Function, 'function'), + ], + 'function': [ + (r'\s+', Text), + (r':', Punctuation, ('#pop', 'comma_list')), + (r'\s*\(', Punctuation, ('#pop', 'comma_list')), + ], + 'comma_list': [ + (r'(\s*\n\s*)(\.{3})', bygroups(Text, Punctuation)), + + (r'(\s*[])\n])', Text, '#pop'), + + (r'\s+', Text), + (r'"', String, 'string'), + (r'\b(if|then|else|fi|endif)\b', Keyword), + + include('function_call'), + include('variable_name'), + include('operator'), + include('number'), + + (r'[()]', Text), + (r',', Punctuation), + ], + 'old_arguments': [ + (r'\n', Text, '#pop'), + + include('variable_name'), + include('operator'), + include('number'), + + (r'"', String, 'string'), + (r'[^\n]', Text), + ], + 'number': [ + (r'\n', Text, '#pop'), + (r'\b\d+(\.\d*)?([eE][-+]?\d+)?%?', Number), + ], + 'object_reference': [ + include('string_interpolated'), + (r'([a-z][a-zA-Z0-9_]*|\d+)', Name.Builtin), + + (words(object_attributes, prefix=r'\.'), Name.Builtin, '#pop'), + + (r'\$', Name.Builtin), + (r'\[', Text, '#pop'), + ], + 'variable_name': [ + include('operator'), + include('number'), + + (words(variables_string, suffix=r'\$'), Name.Variable.Global), + (words(variables_numeric, + suffix=r'(?=[^a-zA-Z0-9_."\'$#\[:(]|\s|^|$)'), + Name.Variable.Global), + + (words(objects, prefix=r'\b', suffix=r"(_)"), + bygroups(Name.Builtin, Name.Builtin), + 'object_reference'), + + (r'\.?_?[a-z][\w.]*(\$|#)?', Text), + (r'[\[\]]', Punctuation, 'comma_list'), + + include('string_interpolated'), + ], + 'operator': [ + (r'([+\/*<>=!-]=?|[&*|][&*|]?|\^|<>)', Operator), + (r'(?', Punctuation), + (r'"(?:\\x[0-9a-fA-F]+\\|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|' + r'\\[0-7]+\\|\\["\\abcefnrstv]|[^\\"])*"', String.Double), + (r"'(?:''|[^'])*'", String.Atom), # quoted atom + # Needs to not be followed by an atom. + # (r'=(?=\s|[a-zA-Z\[])', Operator), + (r'is\b', Operator), + (r'(<|>|=<|>=|==|=:=|=|/|//|\*|\+|-)(?=\s|[a-zA-Z0-9\[])', + Operator), + (r'(mod|div|not)\b', Operator), + (r'_', Keyword), # The don't-care variable + (r'([a-z]+)(:)', bygroups(Name.Namespace, Punctuation)), + (r'([a-z\u00c0-\u1fff\u3040-\ud7ff\ue000-\uffef]' + r'[\w$\u00c0-\u1fff\u3040-\ud7ff\ue000-\uffef]*)' + r'(\s*)(:-|-->)', + bygroups(Name.Function, Text, Operator)), # function defn + (r'([a-z\u00c0-\u1fff\u3040-\ud7ff\ue000-\uffef]' + r'[\w$\u00c0-\u1fff\u3040-\ud7ff\ue000-\uffef]*)' + r'(\s*)(\()', + bygroups(Name.Function, Text, Punctuation)), + (r'[a-z\u00c0-\u1fff\u3040-\ud7ff\ue000-\uffef]' + r'[\w$\u00c0-\u1fff\u3040-\ud7ff\ue000-\uffef]*', + String.Atom), # atom, characters + # This one includes ! + (r'[#&*+\-./:<=>?@\\^~\u00a1-\u00bf\u2010-\u303f]+', + String.Atom), # atom, graphics + (r'[A-Z_]\w*', Name.Variable), + (r'\s+|[\u2000-\u200f\ufff0-\ufffe\uffef]', Text), + ], + 'nested-comment': [ + (r'\*/', Comment.Multiline, '#pop'), + (r'/\*', Comment.Multiline, '#push'), + (r'[^*/]+', Comment.Multiline), + (r'[*/]', Comment.Multiline), + ], + } + + def analyse_text(text): + return ':-' in text + + +class LogtalkLexer(RegexLexer): + """ + For `Logtalk `_ source code. + + .. versionadded:: 0.10 + """ + + name = 'Logtalk' + aliases = ['logtalk'] + filenames = ['*.lgt', '*.logtalk'] + mimetypes = ['text/x-logtalk'] + + tokens = { + 'root': [ + # Directives + (r'^\s*:-\s', Punctuation, 'directive'), + # Comments + (r'%.*?\n', Comment), + (r'/\*(.|\n)*?\*/', Comment), + # Whitespace + (r'\n', Text), + (r'\s+', Text), + # Numbers + (r"0'[\\]?.", Number), + (r'0b[01]+', Number.Bin), + (r'0o[0-7]+', Number.Oct), + (r'0x[0-9a-fA-F]+', Number.Hex), + (r'\d+\.?\d*((e|E)(\+|-)?\d+)?', Number), + # Variables + (r'([A-Z_][a-zA-Z0-9_]*)', Name.Variable), + # Event handlers + (r'(after|before)(?=[(])', Keyword), + # Message forwarding handler + (r'forward(?=[(])', Keyword), + # Execution-context methods + (r'(context|parameter|this|se(lf|nder))(?=[(])', Keyword), + # Reflection + (r'(current_predicate|predicate_property)(?=[(])', Keyword), + # DCGs and term expansion + (r'(expand_(goal|term)|(goal|term)_expansion|phrase)(?=[(])', Keyword), + # Entity + (r'(abolish|c(reate|urrent))_(object|protocol|category)(?=[(])', Keyword), + (r'(object|protocol|category)_property(?=[(])', Keyword), + # Entity relations + (r'co(mplements_object|nforms_to_protocol)(?=[(])', Keyword), + (r'extends_(object|protocol|category)(?=[(])', Keyword), + (r'imp(lements_protocol|orts_category)(?=[(])', Keyword), + (r'(instantiat|specializ)es_class(?=[(])', Keyword), + # Events + (r'(current_event|(abolish|define)_events)(?=[(])', Keyword), + # Flags + (r'(create|current|set)_logtalk_flag(?=[(])', Keyword), + # Compiling, loading, and library paths + (r'logtalk_(compile|l(ibrary_path|oad|oad_context)|make(_target_action)?)(?=[(])', Keyword), + (r'\blogtalk_make\b', Keyword), + # Database + (r'(clause|retract(all)?)(?=[(])', Keyword), + (r'a(bolish|ssert(a|z))(?=[(])', Keyword), + # Control constructs + (r'(ca(ll|tch)|throw)(?=[(])', Keyword), + (r'(fa(il|lse)|true|(instantiation|system)_error)\b', Keyword), + (r'(type|domain|existence|permission|representation|evaluation|resource|syntax)_error(?=[(])', Keyword), + # All solutions + (r'((bag|set)of|f(ind|or)all)(?=[(])', Keyword), + # Multi-threading predicates + (r'threaded(_(ca(ll|ncel)|once|ignore|exit|peek|wait|notify))?(?=[(])', Keyword), + # Engine predicates + (r'threaded_engine(_(create|destroy|self|next|next_reified|yield|post|fetch))?(?=[(])', Keyword), + # Term unification + (r'(subsumes_term|unify_with_occurs_check)(?=[(])', Keyword), + # Term creation and decomposition + (r'(functor|arg|copy_term|numbervars|term_variables)(?=[(])', Keyword), + # Evaluable functors + (r'(div|rem|m(ax|in|od)|abs|sign)(?=[(])', Keyword), + (r'float(_(integer|fractional)_part)?(?=[(])', Keyword), + (r'(floor|t(an|runcate)|round|ceiling)(?=[(])', Keyword), + # Other arithmetic functors + (r'(cos|a(cos|sin|tan|tan2)|exp|log|s(in|qrt)|xor)(?=[(])', Keyword), + # Term testing + (r'(var|atom(ic)?|integer|float|c(allable|ompound)|n(onvar|umber)|ground|acyclic_term)(?=[(])', Keyword), + # Term comparison + (r'compare(?=[(])', Keyword), + # Stream selection and control + (r'(curren|se)t_(in|out)put(?=[(])', Keyword), + (r'(open|close)(?=[(])', Keyword), + (r'flush_output(?=[(])', Keyword), + (r'(at_end_of_stream|flush_output)\b', Keyword), + (r'(stream_property|at_end_of_stream|set_stream_position)(?=[(])', Keyword), + # Character and byte input/output + (r'(nl|(get|peek|put)_(byte|c(har|ode)))(?=[(])', Keyword), + (r'\bnl\b', Keyword), + # Term input/output + (r'read(_term)?(?=[(])', Keyword), + (r'write(q|_(canonical|term))?(?=[(])', Keyword), + (r'(current_)?op(?=[(])', Keyword), + (r'(current_)?char_conversion(?=[(])', Keyword), + # Atomic term processing + (r'atom_(length|c(hars|o(ncat|des)))(?=[(])', Keyword), + (r'(char_code|sub_atom)(?=[(])', Keyword), + (r'number_c(har|ode)s(?=[(])', Keyword), + # Implementation defined hooks functions + (r'(se|curren)t_prolog_flag(?=[(])', Keyword), + (r'\bhalt\b', Keyword), + (r'halt(?=[(])', Keyword), + # Message sending operators + (r'(::|:|\^\^)', Operator), + # External call + (r'[{}]', Keyword), + # Logic and control + (r'(ignore|once)(?=[(])', Keyword), + (r'\brepeat\b', Keyword), + # Sorting + (r'(key)?sort(?=[(])', Keyword), + # Bitwise functors + (r'(>>|<<|/\\|\\\\|\\)', Operator), + # Predicate aliases + (r'\bas\b', Operator), + # Arithemtic evaluation + (r'\bis\b', Keyword), + # Arithemtic comparison + (r'(=:=|=\\=|<|=<|>=|>)', Operator), + # Term creation and decomposition + (r'=\.\.', Operator), + # Term unification + (r'(=|\\=)', Operator), + # Term comparison + (r'(==|\\==|@=<|@<|@>=|@>)', Operator), + # Evaluable functors + (r'(//|[-+*/])', Operator), + (r'\b(e|pi|div|mod|rem)\b', Operator), + # Other arithemtic functors + (r'\b\*\*\b', Operator), + # DCG rules + (r'-->', Operator), + # Control constructs + (r'([!;]|->)', Operator), + # Logic and control + (r'\\+', Operator), + # Mode operators + (r'[?@]', Operator), + # Existential quantifier + (r'\^', Operator), + # Strings + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + # Punctuation + (r'[()\[\],.|]', Text), + # Atoms + (r"[a-z][a-zA-Z0-9_]*", Text), + (r"'", String, 'quoted_atom'), + ], + + 'quoted_atom': [ + (r"''", String), + (r"'", String, '#pop'), + (r'\\([\\abfnrtv"\']|(x[a-fA-F0-9]+|[0-7]+)\\)', String.Escape), + (r"[^\\'\n]+", String), + (r'\\', String), + ], + + 'directive': [ + # Conditional compilation directives + (r'(el)?if(?=[(])', Keyword, 'root'), + (r'(e(lse|ndif))(?=[.])', Keyword, 'root'), + # Entity directives + (r'(category|object|protocol)(?=[(])', Keyword, 'entityrelations'), + (r'(end_(category|object|protocol))(?=[.])', Keyword, 'root'), + # Predicate scope directives + (r'(public|protected|private)(?=[(])', Keyword, 'root'), + # Other directives + (r'e(n(coding|sure_loaded)|xport)(?=[(])', Keyword, 'root'), + (r'in(clude|itialization|fo)(?=[(])', Keyword, 'root'), + (r'(built_in|dynamic|synchronized|threaded)(?=[.])', Keyword, 'root'), + (r'(alias|d(ynamic|iscontiguous)|m(eta_(non_terminal|predicate)|ode|ultifile)|s(et_(logtalk|prolog)_flag|ynchronized))(?=[(])', Keyword, 'root'), + (r'op(?=[(])', Keyword, 'root'), + (r'(c(alls|oinductive)|module|reexport|use(s|_module))(?=[(])', Keyword, 'root'), + (r'[a-z][a-zA-Z0-9_]*(?=[(])', Text, 'root'), + (r'[a-z][a-zA-Z0-9_]*(?=[.])', Text, 'root'), + ], + + 'entityrelations': [ + (r'(complements|extends|i(nstantiates|mp(lements|orts))|specializes)(?=[(])', Keyword), + # Numbers + (r"0'[\\]?.", Number), + (r'0b[01]+', Number.Bin), + (r'0o[0-7]+', Number.Oct), + (r'0x[0-9a-fA-F]+', Number.Hex), + (r'\d+\.?\d*((e|E)(\+|-)?\d+)?', Number), + # Variables + (r'([A-Z_][a-zA-Z0-9_]*)', Name.Variable), + # Atoms + (r"[a-z][a-zA-Z0-9_]*", Text), + (r"'", String, 'quoted_atom'), + # Strings + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + # End of entity-opening directive + (r'([)]\.)', Text, 'root'), + # Scope operator + (r'(::)', Operator), + # Punctuation + (r'[()\[\],.|]', Text), + # Comments + (r'%.*?\n', Comment), + (r'/\*(.|\n)*?\*/', Comment), + # Whitespace + (r'\n', Text), + (r'\s+', Text), + ] + } + + def analyse_text(text): + if ':- object(' in text: + return 1.0 + elif ':- protocol(' in text: + return 1.0 + elif ':- category(' in text: + return 1.0 + elif re.search(r'^:-\s[a-z]', text, re.M): + return 0.9 + else: + return 0.0 diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/promql.py b/.venv/lib/python3.8/site-packages/pygments/lexers/promql.py new file mode 100644 index 0000000..b9646d4 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/promql.py @@ -0,0 +1,182 @@ +""" + pygments.lexers.promql + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexer for Prometheus Query Language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, bygroups, default, words +from pygments.token import ( + Comment, + Keyword, + Name, + Number, + Operator, + Punctuation, + String, + Whitespace, +) + +__all__ = ["PromQLLexer"] + + +class PromQLLexer(RegexLexer): + """ + For `PromQL `_ queries. + + For details about the grammar see: + https://github.com/prometheus/prometheus/tree/master/promql/parser + + .. versionadded: 2.7 + """ + + name = "PromQL" + aliases = ["promql"] + filenames = ["*.promql"] + + base_keywords = ( + words( + ( + "bool", + "by", + "group_left", + "group_right", + "ignoring", + "offset", + "on", + "without", + ), + suffix=r"\b", + ), + Keyword, + ) + + aggregator_keywords = ( + words( + ( + "sum", + "min", + "max", + "avg", + "group", + "stddev", + "stdvar", + "count", + "count_values", + "bottomk", + "topk", + "quantile", + ), + suffix=r"\b", + ), + Keyword, + ) + + function_keywords = ( + words( + ( + "abs", + "absent", + "absent_over_time", + "avg_over_time", + "ceil", + "changes", + "clamp_max", + "clamp_min", + "count_over_time", + "day_of_month", + "day_of_week", + "days_in_month", + "delta", + "deriv", + "exp", + "floor", + "histogram_quantile", + "holt_winters", + "hour", + "idelta", + "increase", + "irate", + "label_join", + "label_replace", + "ln", + "log10", + "log2", + "max_over_time", + "min_over_time", + "minute", + "month", + "predict_linear", + "quantile_over_time", + "rate", + "resets", + "round", + "scalar", + "sort", + "sort_desc", + "sqrt", + "stddev_over_time", + "stdvar_over_time", + "sum_over_time", + "time", + "timestamp", + "vector", + "year", + ), + suffix=r"\b", + ), + Keyword.Reserved, + ) + + tokens = { + "root": [ + (r"\n", Whitespace), + (r"\s+", Whitespace), + (r",", Punctuation), + # Keywords + base_keywords, + aggregator_keywords, + function_keywords, + # Offsets + (r"[1-9][0-9]*[smhdwy]", String), + # Numbers + (r"-?[0-9]+\.[0-9]+", Number.Float), + (r"-?[0-9]+", Number.Integer), + # Comments + (r"#.*?$", Comment.Single), + # Operators + (r"(\+|\-|\*|\/|\%|\^)", Operator), + (r"==|!=|>=|<=|<|>", Operator), + (r"and|or|unless", Operator.Word), + # Metrics + (r"[_a-zA-Z][a-zA-Z0-9_]+", Name.Variable), + # Params + (r'(["\'])(.*?)(["\'])', bygroups(Punctuation, String, Punctuation)), + # Other states + (r"\(", Operator, "function"), + (r"\)", Operator), + (r"\{", Punctuation, "labels"), + (r"\[", Punctuation, "range"), + ], + "labels": [ + (r"\}", Punctuation, "#pop"), + (r"\n", Whitespace), + (r"\s+", Whitespace), + (r",", Punctuation), + (r'([_a-zA-Z][a-zA-Z0-9_]*?)(\s*?)(=~|!=|=|!~)(\s*?)("|\')(.*?)("|\')', + bygroups(Name.Label, Whitespace, Operator, Whitespace, + Punctuation, String, Punctuation)), + ], + "range": [ + (r"\]", Punctuation, "#pop"), + (r"[1-9][0-9]*[smhdwy]", String), + ], + "function": [ + (r"\)", Operator, "#pop"), + (r"\(", Operator, "#push"), + default("#pop"), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/python.py b/.venv/lib/python3.8/site-packages/pygments/lexers/python.py new file mode 100644 index 0000000..2901d7b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/python.py @@ -0,0 +1,1188 @@ +""" + pygments.lexers.python + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Python and related languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import keyword + +from pygments.lexer import Lexer, RegexLexer, include, bygroups, using, \ + default, words, combined, do_insertions, this +from pygments.util import get_bool_opt, shebang_matches +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Generic, Other, Error +from pygments import unistring as uni + +__all__ = ['PythonLexer', 'PythonConsoleLexer', 'PythonTracebackLexer', + 'Python2Lexer', 'Python2TracebackLexer', + 'CythonLexer', 'DgLexer', 'NumPyLexer'] + +line_re = re.compile('.*?\n') + + +class PythonLexer(RegexLexer): + """ + For `Python `_ source code (version 3.x). + + .. versionadded:: 0.10 + + .. versionchanged:: 2.5 + This is now the default ``PythonLexer``. It is still available as the + alias ``Python3Lexer``. + """ + + name = 'Python' + aliases = ['python', 'py', 'sage', 'python3', 'py3'] + filenames = [ + '*.py', + '*.pyw', + # Jython + '*.jy', + # Sage + '*.sage', + # SCons + '*.sc', + 'SConstruct', + 'SConscript', + # Skylark/Starlark (used by Bazel, Buck, and Pants) + '*.bzl', + 'BUCK', + 'BUILD', + 'BUILD.bazel', + 'WORKSPACE', + # Twisted Application infrastructure + '*.tac', + ] + mimetypes = ['text/x-python', 'application/x-python', + 'text/x-python3', 'application/x-python3'] + + flags = re.MULTILINE | re.UNICODE + + uni_name = "[%s][%s]*" % (uni.xid_start, uni.xid_continue) + + def innerstring_rules(ttype): + return [ + # the old style '%s' % (...) string formatting (still valid in Py3) + (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?' + '[hlL]?[E-GXc-giorsaux%]', String.Interpol), + # the new style '{}'.format(...) string formatting + (r'\{' + r'((\w+)((\.\w+)|(\[[^\]]+\]))*)?' # field name + r'(\![sra])?' # conversion + r'(\:(.?[<>=\^])?[-+ ]?#?0?(\d+)?,?(\.\d+)?[E-GXb-gnosx%]?)?' + r'\}', String.Interpol), + + # backslashes, quotes and formatting signs must be parsed one at a time + (r'[^\\\'"%{\n]+', ttype), + (r'[\'"\\]', ttype), + # unhandled string formatting sign + (r'%|(\{{1,2})', ttype) + # newlines are an error (use "nl" state) + ] + + def fstring_rules(ttype): + return [ + # Assuming that a '}' is the closing brace after format specifier. + # Sadly, this means that we won't detect syntax error. But it's + # more important to parse correct syntax correctly, than to + # highlight invalid syntax. + (r'\}', String.Interpol), + (r'\{', String.Interpol, 'expr-inside-fstring'), + # backslashes, quotes and formatting signs must be parsed one at a time + (r'[^\\\'"{}\n]+', ttype), + (r'[\'"\\]', ttype), + # newlines are an error (use "nl" state) + ] + + tokens = { + 'root': [ + (r'\n', Text), + (r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")', + bygroups(Text, String.Affix, String.Doc)), + (r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')", + bygroups(Text, String.Affix, String.Doc)), + (r'\A#!.+$', Comment.Hashbang), + (r'#.*$', Comment.Single), + (r'\\\n', Text), + (r'\\', Text), + include('keywords'), + include('soft-keywords'), + (r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'), + (r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'), + (r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text), + 'fromimport'), + (r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text), + 'import'), + include('expr'), + ], + 'expr': [ + # raw f-strings + ('(?i)(rf|fr)(""")', + bygroups(String.Affix, String.Double), + combined('rfstringescape', 'tdqf')), + ("(?i)(rf|fr)(''')", + bygroups(String.Affix, String.Single), + combined('rfstringescape', 'tsqf')), + ('(?i)(rf|fr)(")', + bygroups(String.Affix, String.Double), + combined('rfstringescape', 'dqf')), + ("(?i)(rf|fr)(')", + bygroups(String.Affix, String.Single), + combined('rfstringescape', 'sqf')), + # non-raw f-strings + ('([fF])(""")', bygroups(String.Affix, String.Double), + combined('fstringescape', 'tdqf')), + ("([fF])(''')", bygroups(String.Affix, String.Single), + combined('fstringescape', 'tsqf')), + ('([fF])(")', bygroups(String.Affix, String.Double), + combined('fstringescape', 'dqf')), + ("([fF])(')", bygroups(String.Affix, String.Single), + combined('fstringescape', 'sqf')), + # raw strings + ('(?i)(rb|br|r)(""")', + bygroups(String.Affix, String.Double), 'tdqs'), + ("(?i)(rb|br|r)(''')", + bygroups(String.Affix, String.Single), 'tsqs'), + ('(?i)(rb|br|r)(")', + bygroups(String.Affix, String.Double), 'dqs'), + ("(?i)(rb|br|r)(')", + bygroups(String.Affix, String.Single), 'sqs'), + # non-raw strings + ('([uUbB]?)(""")', bygroups(String.Affix, String.Double), + combined('stringescape', 'tdqs')), + ("([uUbB]?)(''')", bygroups(String.Affix, String.Single), + combined('stringescape', 'tsqs')), + ('([uUbB]?)(")', bygroups(String.Affix, String.Double), + combined('stringescape', 'dqs')), + ("([uUbB]?)(')", bygroups(String.Affix, String.Single), + combined('stringescape', 'sqs')), + (r'[^\S\n]+', Text), + include('numbers'), + (r'!=|==|<<|>>|:=|[-~+/*%=<>&^|.]', Operator), + (r'[]{}:(),;[]', Punctuation), + (r'(in|is|and|or|not)\b', Operator.Word), + include('expr-keywords'), + include('builtins'), + include('magicfuncs'), + include('magicvars'), + include('name'), + ], + 'expr-inside-fstring': [ + (r'[{([]', Punctuation, 'expr-inside-fstring-inner'), + # without format specifier + (r'(=\s*)?' # debug (https://bugs.python.org/issue36817) + r'(\![sraf])?' # conversion + r'\}', String.Interpol, '#pop'), + # with format specifier + # we'll catch the remaining '}' in the outer scope + (r'(=\s*)?' # debug (https://bugs.python.org/issue36817) + r'(\![sraf])?' # conversion + r':', String.Interpol, '#pop'), + (r'\s+', Text), # allow new lines + include('expr'), + ], + 'expr-inside-fstring-inner': [ + (r'[{([]', Punctuation, 'expr-inside-fstring-inner'), + (r'[])}]', Punctuation, '#pop'), + (r'\s+', Text), # allow new lines + include('expr'), + ], + 'expr-keywords': [ + # Based on https://docs.python.org/3/reference/expressions.html + (words(( + 'async for', 'await', 'else', 'for', 'if', 'lambda', + 'yield', 'yield from'), suffix=r'\b'), + Keyword), + (words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant), + ], + 'keywords': [ + (words(( + 'assert', 'async', 'await', 'break', 'continue', 'del', 'elif', + 'else', 'except', 'finally', 'for', 'global', 'if', 'lambda', + 'pass', 'raise', 'nonlocal', 'return', 'try', 'while', 'yield', + 'yield from', 'as', 'with'), suffix=r'\b'), + Keyword), + (words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant), + ], + 'soft-keywords': [ + # `match`, `case` and `_` soft keywords + (r'(^[ \t]*)' # at beginning of line + possible indentation + r'(match|case)\b' # a possible keyword + r'(?![ \t]*(?:' # not followed by... + r'[:,;=^&|@~)\]}]|(?:' + # characters and keywords that mean this isn't + r'|'.join(keyword.kwlist) + r')\b))', # pattern matching + bygroups(Text, Keyword), 'soft-keywords-inner'), + ], + 'soft-keywords-inner': [ + # optional `_` keyword + (r'(\s+)([^\n_]*)(_\b)', bygroups(Text, using(this), Keyword)), + default('#pop') + ], + 'builtins': [ + (words(( + '__import__', 'abs', 'all', 'any', 'bin', 'bool', 'bytearray', + 'breakpoint', 'bytes', 'chr', 'classmethod', 'compile', 'complex', + 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'filter', + 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', + 'hash', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', + 'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview', + 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', + 'property', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', + 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', + 'type', 'vars', 'zip'), prefix=r'(?`_ source code. + + .. versionchanged:: 2.5 + This class has been renamed from ``PythonLexer``. ``PythonLexer`` now + refers to the Python 3 variant. File name patterns like ``*.py`` have + been moved to Python 3 as well. + """ + + name = 'Python 2.x' + aliases = ['python2', 'py2'] + filenames = [] # now taken over by PythonLexer (3.x) + mimetypes = ['text/x-python2', 'application/x-python2'] + + def innerstring_rules(ttype): + return [ + # the old style '%s' % (...) string formatting + (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?' + '[hlL]?[E-GXc-giorsux%]', String.Interpol), + # backslashes, quotes and formatting signs must be parsed one at a time + (r'[^\\\'"%\n]+', ttype), + (r'[\'"\\]', ttype), + # unhandled string formatting sign + (r'%', ttype), + # newlines are an error (use "nl" state) + ] + + tokens = { + 'root': [ + (r'\n', Text), + (r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")', + bygroups(Text, String.Affix, String.Doc)), + (r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')", + bygroups(Text, String.Affix, String.Doc)), + (r'[^\S\n]+', Text), + (r'\A#!.+$', Comment.Hashbang), + (r'#.*$', Comment.Single), + (r'[]{}:(),;[]', Punctuation), + (r'\\\n', Text), + (r'\\', Text), + (r'(in|is|and|or|not)\b', Operator.Word), + (r'!=|==|<<|>>|[-~+/*%=<>&^|.]', Operator), + include('keywords'), + (r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'), + (r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'), + (r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text), + 'fromimport'), + (r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text), + 'import'), + include('builtins'), + include('magicfuncs'), + include('magicvars'), + include('backtick'), + ('([rR]|[uUbB][rR]|[rR][uUbB])(""")', + bygroups(String.Affix, String.Double), 'tdqs'), + ("([rR]|[uUbB][rR]|[rR][uUbB])(''')", + bygroups(String.Affix, String.Single), 'tsqs'), + ('([rR]|[uUbB][rR]|[rR][uUbB])(")', + bygroups(String.Affix, String.Double), 'dqs'), + ("([rR]|[uUbB][rR]|[rR][uUbB])(')", + bygroups(String.Affix, String.Single), 'sqs'), + ('([uUbB]?)(""")', bygroups(String.Affix, String.Double), + combined('stringescape', 'tdqs')), + ("([uUbB]?)(''')", bygroups(String.Affix, String.Single), + combined('stringescape', 'tsqs')), + ('([uUbB]?)(")', bygroups(String.Affix, String.Double), + combined('stringescape', 'dqs')), + ("([uUbB]?)(')", bygroups(String.Affix, String.Single), + combined('stringescape', 'sqs')), + include('name'), + include('numbers'), + ], + 'keywords': [ + (words(( + 'assert', 'break', 'continue', 'del', 'elif', 'else', 'except', + 'exec', 'finally', 'for', 'global', 'if', 'lambda', 'pass', + 'print', 'raise', 'return', 'try', 'while', 'yield', + 'yield from', 'as', 'with'), suffix=r'\b'), + Keyword), + ], + 'builtins': [ + (words(( + '__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', + 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', + 'cmp', 'coerce', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod', + 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', + 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', + 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', + 'list', 'locals', 'long', 'map', 'max', 'min', 'next', 'object', + 'oct', 'open', 'ord', 'pow', 'property', 'range', 'raw_input', 'reduce', + 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', + 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', + 'unichr', 'unicode', 'vars', 'xrange', 'zip'), + prefix=r'(?>> a = 'foo' + >>> print a + foo + >>> 1 / 0 + Traceback (most recent call last): + File "", line 1, in + ZeroDivisionError: integer division or modulo by zero + + Additional options: + + `python3` + Use Python 3 lexer for code. Default is ``True``. + + .. versionadded:: 1.0 + .. versionchanged:: 2.5 + Now defaults to ``True``. + """ + name = 'Python console session' + aliases = ['pycon'] + mimetypes = ['text/x-python-doctest'] + + def __init__(self, **options): + self.python3 = get_bool_opt(options, 'python3', True) + Lexer.__init__(self, **options) + + def get_tokens_unprocessed(self, text): + if self.python3: + pylexer = PythonLexer(**self.options) + tblexer = PythonTracebackLexer(**self.options) + else: + pylexer = Python2Lexer(**self.options) + tblexer = Python2TracebackLexer(**self.options) + + curcode = '' + insertions = [] + curtb = '' + tbindex = 0 + tb = 0 + for match in line_re.finditer(text): + line = match.group() + if line.startswith('>>> ') or line.startswith('... '): + tb = 0 + insertions.append((len(curcode), + [(0, Generic.Prompt, line[:4])])) + curcode += line[4:] + elif line.rstrip() == '...' and not tb: + # only a new >>> prompt can end an exception block + # otherwise an ellipsis in place of the traceback frames + # will be mishandled + insertions.append((len(curcode), + [(0, Generic.Prompt, '...')])) + curcode += line[3:] + else: + if curcode: + yield from do_insertions( + insertions, pylexer.get_tokens_unprocessed(curcode)) + curcode = '' + insertions = [] + if (line.startswith('Traceback (most recent call last):') or + re.match(' File "[^"]+", line \\d+\\n$', line)): + tb = 1 + curtb = line + tbindex = match.start() + elif line == 'KeyboardInterrupt\n': + yield match.start(), Name.Class, line + elif tb: + curtb += line + if not (line.startswith(' ') or line.strip() == '...'): + tb = 0 + for i, t, v in tblexer.get_tokens_unprocessed(curtb): + yield tbindex+i, t, v + curtb = '' + else: + yield match.start(), Generic.Output, line + if curcode: + yield from do_insertions(insertions, + pylexer.get_tokens_unprocessed(curcode)) + if curtb: + for i, t, v in tblexer.get_tokens_unprocessed(curtb): + yield tbindex+i, t, v + + +class PythonTracebackLexer(RegexLexer): + """ + For Python 3.x tracebacks, with support for chained exceptions. + + .. versionadded:: 1.0 + + .. versionchanged:: 2.5 + This is now the default ``PythonTracebackLexer``. It is still available + as the alias ``Python3TracebackLexer``. + """ + + name = 'Python Traceback' + aliases = ['pytb', 'py3tb'] + filenames = ['*.pytb', '*.py3tb'] + mimetypes = ['text/x-python-traceback', 'text/x-python3-traceback'] + + tokens = { + 'root': [ + (r'\n', Text), + (r'^Traceback \(most recent call last\):\n', Generic.Traceback, 'intb'), + (r'^During handling of the above exception, another ' + r'exception occurred:\n\n', Generic.Traceback), + (r'^The above exception was the direct cause of the ' + r'following exception:\n\n', Generic.Traceback), + (r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'), + (r'^.*\n', Other), + ], + 'intb': [ + (r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)', + bygroups(Text, Name.Builtin, Text, Number, Text, Name, Text)), + (r'^( File )("[^"]+")(, line )(\d+)(\n)', + bygroups(Text, Name.Builtin, Text, Number, Text)), + (r'^( )(.+)(\n)', + bygroups(Text, using(PythonLexer), Text), 'markers'), + (r'^([ \t]*)(\.\.\.)(\n)', + bygroups(Text, Comment, Text)), # for doctests... + (r'^([^:]+)(: )(.+)(\n)', + bygroups(Generic.Error, Text, Name, Text), '#pop'), + (r'^([a-zA-Z_][\w.]*)(:?\n)', + bygroups(Generic.Error, Text), '#pop') + ], + 'markers': [ + # Either `PEP 657 ` + # error locations in Python 3.11+, or single-caret markers + # for syntax errors before that. + (r'^( {4,})([~^]+)(\n)', + bygroups(Text, Punctuation.Marker, Text), + '#pop'), + default('#pop'), + ], + } + + +Python3TracebackLexer = PythonTracebackLexer + + +class Python2TracebackLexer(RegexLexer): + """ + For Python tracebacks. + + .. versionadded:: 0.7 + + .. versionchanged:: 2.5 + This class has been renamed from ``PythonTracebackLexer``. + ``PythonTracebackLexer`` now refers to the Python 3 variant. + """ + + name = 'Python 2.x Traceback' + aliases = ['py2tb'] + filenames = ['*.py2tb'] + mimetypes = ['text/x-python2-traceback'] + + tokens = { + 'root': [ + # Cover both (most recent call last) and (innermost last) + # The optional ^C allows us to catch keyboard interrupt signals. + (r'^(\^C)?(Traceback.*\n)', + bygroups(Text, Generic.Traceback), 'intb'), + # SyntaxError starts with this. + (r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'), + (r'^.*\n', Other), + ], + 'intb': [ + (r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)', + bygroups(Text, Name.Builtin, Text, Number, Text, Name, Text)), + (r'^( File )("[^"]+")(, line )(\d+)(\n)', + bygroups(Text, Name.Builtin, Text, Number, Text)), + (r'^( )(.+)(\n)', + bygroups(Text, using(Python2Lexer), Text), 'marker'), + (r'^([ \t]*)(\.\.\.)(\n)', + bygroups(Text, Comment, Text)), # for doctests... + (r'^([^:]+)(: )(.+)(\n)', + bygroups(Generic.Error, Text, Name, Text), '#pop'), + (r'^([a-zA-Z_]\w*)(:?\n)', + bygroups(Generic.Error, Text), '#pop') + ], + 'marker': [ + # For syntax errors. + (r'( {4,})(\^)', bygroups(Text, Punctuation.Marker), '#pop'), + default('#pop'), + ], + } + + +class CythonLexer(RegexLexer): + """ + For Pyrex and `Cython `_ source code. + + .. versionadded:: 1.1 + """ + + name = 'Cython' + aliases = ['cython', 'pyx', 'pyrex'] + filenames = ['*.pyx', '*.pxd', '*.pxi'] + mimetypes = ['text/x-cython', 'application/x-cython'] + + tokens = { + 'root': [ + (r'\n', Text), + (r'^(\s*)("""(?:.|\n)*?""")', bygroups(Text, String.Doc)), + (r"^(\s*)('''(?:.|\n)*?''')", bygroups(Text, String.Doc)), + (r'[^\S\n]+', Text), + (r'#.*$', Comment), + (r'[]{}:(),;[]', Punctuation), + (r'\\\n', Text), + (r'\\', Text), + (r'(in|is|and|or|not)\b', Operator.Word), + (r'(<)([a-zA-Z0-9.?]+)(>)', + bygroups(Punctuation, Keyword.Type, Punctuation)), + (r'!=|==|<<|>>|[-~+/*%=<>&^|.?]', Operator), + (r'(from)(\d+)(<=)(\s+)(<)(\d+)(:)', + bygroups(Keyword, Number.Integer, Operator, Name, Operator, + Name, Punctuation)), + include('keywords'), + (r'(def|property)(\s+)', bygroups(Keyword, Text), 'funcname'), + (r'(cp?def)(\s+)', bygroups(Keyword, Text), 'cdef'), + # (should actually start a block with only cdefs) + (r'(cdef)(:)', bygroups(Keyword, Punctuation)), + (r'(class|struct)(\s+)', bygroups(Keyword, Text), 'classname'), + (r'(from)(\s+)', bygroups(Keyword, Text), 'fromimport'), + (r'(c?import)(\s+)', bygroups(Keyword, Text), 'import'), + include('builtins'), + include('backtick'), + ('(?:[rR]|[uU][rR]|[rR][uU])"""', String, 'tdqs'), + ("(?:[rR]|[uU][rR]|[rR][uU])'''", String, 'tsqs'), + ('(?:[rR]|[uU][rR]|[rR][uU])"', String, 'dqs'), + ("(?:[rR]|[uU][rR]|[rR][uU])'", String, 'sqs'), + ('[uU]?"""', String, combined('stringescape', 'tdqs')), + ("[uU]?'''", String, combined('stringescape', 'tsqs')), + ('[uU]?"', String, combined('stringescape', 'dqs')), + ("[uU]?'", String, combined('stringescape', 'sqs')), + include('name'), + include('numbers'), + ], + 'keywords': [ + (words(( + 'assert', 'async', 'await', 'break', 'by', 'continue', 'ctypedef', 'del', 'elif', + 'else', 'except', 'except?', 'exec', 'finally', 'for', 'fused', 'gil', + 'global', 'if', 'include', 'lambda', 'nogil', 'pass', 'print', + 'raise', 'return', 'try', 'while', 'yield', 'as', 'with'), suffix=r'\b'), + Keyword), + (r'(DEF|IF|ELIF|ELSE)\b', Comment.Preproc), + ], + 'builtins': [ + (words(( + '__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bint', + 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', + 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'delattr', + 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', + 'file', 'filter', 'float', 'frozenset', 'getattr', 'globals', + 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', + 'issubclass', 'iter', 'len', 'list', 'locals', 'long', 'map', 'max', + 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'property', 'Py_ssize_t', + 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', + 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', + 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'unsigned', + 'vars', 'xrange', 'zip'), prefix=r'(?`_, + a functional and object-oriented programming language + running on the CPython 3 VM. + + .. versionadded:: 1.6 + """ + name = 'dg' + aliases = ['dg'] + filenames = ['*.dg'] + mimetypes = ['text/x-dg'] + + tokens = { + 'root': [ + (r'\s+', Text), + (r'#.*?$', Comment.Single), + + (r'(?i)0b[01]+', Number.Bin), + (r'(?i)0o[0-7]+', Number.Oct), + (r'(?i)0x[0-9a-f]+', Number.Hex), + (r'(?i)[+-]?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?j?', Number.Float), + (r'(?i)[+-]?[0-9]+e[+-]?\d+j?', Number.Float), + (r'(?i)[+-]?[0-9]+j?', Number.Integer), + + (r"(?i)(br|r?b?)'''", String, combined('stringescape', 'tsqs', 'string')), + (r'(?i)(br|r?b?)"""', String, combined('stringescape', 'tdqs', 'string')), + (r"(?i)(br|r?b?)'", String, combined('stringescape', 'sqs', 'string')), + (r'(?i)(br|r?b?)"', String, combined('stringescape', 'dqs', 'string')), + + (r"`\w+'*`", Operator), + (r'\b(and|in|is|or|where)\b', Operator.Word), + (r'[!$%&*+\-./:<-@\\^|~;,]+', Operator), + + (words(( + 'bool', 'bytearray', 'bytes', 'classmethod', 'complex', 'dict', 'dict\'', + 'float', 'frozenset', 'int', 'list', 'list\'', 'memoryview', 'object', + 'property', 'range', 'set', 'set\'', 'slice', 'staticmethod', 'str', + 'super', 'tuple', 'tuple\'', 'type'), + prefix=r'(?`_. + + Reference for implementing this: «Meta Object Facility (MOF) 2.0 + Query/View/Transformation Specification», Version 1.1 - January 2011 + (http://www.omg.org/spec/QVT/1.1/), see §8.4, «Concrete Syntax» in + particular. + + Notable tokens assignments: + + - Name.Class is assigned to the identifier following any of the following + keywords: metamodel, class, exception, primitive, enum, transformation + or library + + - Name.Function is assigned to the names of mappings and queries + + - Name.Builtin.Pseudo is assigned to the pre-defined variables 'this', + 'self' and 'result'. + """ + # With obvious borrowings & inspiration from the Java, Python and C lexers + + name = 'QVTO' + aliases = ['qvto', 'qvt'] + filenames = ['*.qvto'] + + tokens = { + 'root': [ + (r'\n', Text), + (r'[^\S\n]+', Text), + (r'(--|//)(\s*)(directive:)?(.*)$', + bygroups(Comment, Comment, Comment.Preproc, Comment)), + # Uncomment the following if you want to distinguish between + # '/*' and '/**', à la javadoc + # (r'/[*]{2}(.|\n)*?[*]/', Comment.Multiline), + (r'/[*](.|\n)*?[*]/', Comment.Multiline), + (r'\\\n', Text), + (r'(and|not|or|xor|##?)\b', Operator.Word), + (r'(:{1,2}=|[-+]=)\b', Operator.Word), + (r'(@|<<|>>)\b', Keyword), # stereotypes + (r'!=|<>|==|=|!->|->|>=|<=|[.]{3}|[+/*%=<>&|.~]', Operator), + (r'[]{}:(),;[]', Punctuation), + (r'(true|false|unlimited|null)\b', Keyword.Constant), + (r'(this|self|result)\b', Name.Builtin.Pseudo), + (r'(var)\b', Keyword.Declaration), + (r'(from|import)\b', Keyword.Namespace, 'fromimport'), + (r'(metamodel|class|exception|primitive|enum|transformation|' + r'library)(\s+)(\w+)', + bygroups(Keyword.Word, Text, Name.Class)), + (r'(exception)(\s+)(\w+)', + bygroups(Keyword.Word, Text, Name.Exception)), + (r'(main)\b', Name.Function), + (r'(mapping|helper|query)(\s+)', + bygroups(Keyword.Declaration, Text), 'operation'), + (r'(assert)(\s+)\b', bygroups(Keyword, Text), 'assert'), + (r'(Bag|Collection|Dict|OrderedSet|Sequence|Set|Tuple|List)\b', + Keyword.Type), + include('keywords'), + ('"', String, combined('stringescape', 'dqs')), + ("'", String, combined('stringescape', 'sqs')), + include('name'), + include('numbers'), + # (r'([a-zA-Z_]\w*)(::)([a-zA-Z_]\w*)', + # bygroups(Text, Text, Text)), + ], + + 'fromimport': [ + (r'(?:[ \t]|\\\n)+', Text), + (r'[a-zA-Z_][\w.]*', Name.Namespace), + default('#pop'), + ], + + 'operation': [ + (r'::', Text), + (r'(.*::)([a-zA-Z_]\w*)([ \t]*)(\()', + bygroups(Text, Name.Function, Text, Punctuation), '#pop') + ], + + 'assert': [ + (r'(warning|error|fatal)\b', Keyword, '#pop'), + default('#pop'), # all else: go back + ], + + 'keywords': [ + (words(( + 'abstract', 'access', 'any', 'assert', 'blackbox', 'break', + 'case', 'collect', 'collectNested', 'collectOne', 'collectselect', + 'collectselectOne', 'composes', 'compute', 'configuration', + 'constructor', 'continue', 'datatype', 'default', 'derived', + 'disjuncts', 'do', 'elif', 'else', 'end', 'endif', 'except', + 'exists', 'extends', 'forAll', 'forEach', 'forOne', 'from', 'if', + 'implies', 'in', 'inherits', 'init', 'inout', 'intermediate', + 'invresolve', 'invresolveIn', 'invresolveone', 'invresolveoneIn', + 'isUnique', 'iterate', 'late', 'let', 'literal', 'log', 'map', + 'merges', 'modeltype', 'new', 'object', 'one', 'ordered', 'out', + 'package', 'population', 'property', 'raise', 'readonly', + 'references', 'refines', 'reject', 'resolve', 'resolveIn', + 'resolveone', 'resolveoneIn', 'return', 'select', 'selectOne', + 'sortedBy', 'static', 'switch', 'tag', 'then', 'try', 'typedef', + 'unlimited', 'uses', 'when', 'where', 'while', 'with', 'xcollect', + 'xmap', 'xselect'), suffix=r'\b'), Keyword), + ], + + # There is no need to distinguish between String.Single and + # String.Double: 'strings' is factorised for 'dqs' and 'sqs' + 'strings': [ + (r'[^\\\'"\n]+', String), + # quotes, percents and backslashes must be parsed one at a time + (r'[\'"\\]', String), + ], + 'stringescape': [ + (r'\\([\\btnfr"\']|u[0-3][0-7]{2}|u[0-7]{1,2})', String.Escape) + ], + 'dqs': [ # double-quoted string + (r'"', String, '#pop'), + (r'\\\\|\\"', String.Escape), + include('strings') + ], + 'sqs': [ # single-quoted string + (r"'", String, '#pop'), + (r"\\\\|\\'", String.Escape), + include('strings') + ], + 'name': [ + (r'[a-zA-Z_]\w*', Name), + ], + # numbers: excerpt taken from the python lexer + 'numbers': [ + (r'(\d+\.\d*|\d*\.\d+)([eE][+-]?[0-9]+)?', Number.Float), + (r'\d+[eE][+-]?[0-9]+', Number.Float), + (r'\d+', Number.Integer) + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/r.py b/.venv/lib/python3.8/site-packages/pygments/lexers/r.py new file mode 100644 index 0000000..44168a7 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/r.py @@ -0,0 +1,190 @@ +""" + pygments.lexers.r + ~~~~~~~~~~~~~~~~~ + + Lexers for the R/S languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import Lexer, RegexLexer, include, do_insertions +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Generic + +__all__ = ['RConsoleLexer', 'SLexer', 'RdLexer'] + + +line_re = re.compile('.*?\n') + + +class RConsoleLexer(Lexer): + """ + For R console transcripts or R CMD BATCH output files. + """ + + name = 'RConsole' + aliases = ['rconsole', 'rout'] + filenames = ['*.Rout'] + + def get_tokens_unprocessed(self, text): + slexer = SLexer(**self.options) + + current_code_block = '' + insertions = [] + + for match in line_re.finditer(text): + line = match.group() + if line.startswith('>') or line.startswith('+'): + # Colorize the prompt as such, + # then put rest of line into current_code_block + insertions.append((len(current_code_block), + [(0, Generic.Prompt, line[:2])])) + current_code_block += line[2:] + else: + # We have reached a non-prompt line! + # If we have stored prompt lines, need to process them first. + if current_code_block: + # Weave together the prompts and highlight code. + yield from do_insertions( + insertions, slexer.get_tokens_unprocessed(current_code_block)) + # Reset vars for next code block. + current_code_block = '' + insertions = [] + # Now process the actual line itself, this is output from R. + yield match.start(), Generic.Output, line + + # If we happen to end on a code block with nothing after it, need to + # process the last code block. This is neither elegant nor DRY so + # should be changed. + if current_code_block: + yield from do_insertions( + insertions, slexer.get_tokens_unprocessed(current_code_block)) + + +class SLexer(RegexLexer): + """ + For S, S-plus, and R source code. + + .. versionadded:: 0.10 + """ + + name = 'S' + aliases = ['splus', 's', 'r'] + filenames = ['*.S', '*.R', '.Rhistory', '.Rprofile', '.Renviron'] + mimetypes = ['text/S-plus', 'text/S', 'text/x-r-source', 'text/x-r', + 'text/x-R', 'text/x-r-history', 'text/x-r-profile'] + + valid_name = r'`[^`\\]*(?:\\.[^`\\]*)*`|(?:[a-zA-Z]|\.[A-Za-z_.])[\w.]*|\.' + tokens = { + 'comments': [ + (r'#.*$', Comment.Single), + ], + 'valid_name': [ + (valid_name, Name), + ], + 'punctuation': [ + (r'\[{1,2}|\]{1,2}|\(|\)|;|,', Punctuation), + ], + 'keywords': [ + (r'(if|else|for|while|repeat|in|next|break|return|switch|function)' + r'(?![\w.])', + Keyword.Reserved), + ], + 'operators': [ + (r'<>?|-|==|<=|>=|<|>|&&?|!=|\|\|?|\?', Operator), + (r'\*|\+|\^|/|!|%[^%]*%|=|~|\$|@|:{1,3}', Operator), + ], + 'builtin_symbols': [ + (r'(NULL|NA(_(integer|real|complex|character)_)?|' + r'letters|LETTERS|Inf|TRUE|FALSE|NaN|pi|\.\.(\.|[0-9]+))' + r'(?![\w.])', + Keyword.Constant), + (r'(T|F)\b', Name.Builtin.Pseudo), + ], + 'numbers': [ + # hex number + (r'0[xX][a-fA-F0-9]+([pP][0-9]+)?[Li]?', Number.Hex), + # decimal number + (r'[+-]?([0-9]+(\.[0-9]+)?|\.[0-9]+|\.)([eE][+-]?[0-9]+)?[Li]?', + Number), + ], + 'statements': [ + include('comments'), + # whitespaces + (r'\s+', Text), + (r'\'', String, 'string_squote'), + (r'\"', String, 'string_dquote'), + include('builtin_symbols'), + include('valid_name'), + include('numbers'), + include('keywords'), + include('punctuation'), + include('operators'), + ], + 'root': [ + # calls: + (r'(%s)\s*(?=\()' % valid_name, Name.Function), + include('statements'), + # blocks: + (r'\{|\}', Punctuation), + # (r'\{', Punctuation, 'block'), + (r'.', Text), + ], + # 'block': [ + # include('statements'), + # ('\{', Punctuation, '#push'), + # ('\}', Punctuation, '#pop') + # ], + 'string_squote': [ + (r'([^\'\\]|\\.)*\'', String, '#pop'), + ], + 'string_dquote': [ + (r'([^"\\]|\\.)*"', String, '#pop'), + ], + } + + def analyse_text(text): + if re.search(r'[a-z0-9_\])\s]<-(?!-)', text): + return 0.11 + + +class RdLexer(RegexLexer): + """ + Pygments Lexer for R documentation (Rd) files + + This is a very minimal implementation, highlighting little more + than the macros. A description of Rd syntax is found in `Writing R + Extensions `_ + and `Parsing Rd files `_. + + .. versionadded:: 1.6 + """ + name = 'Rd' + aliases = ['rd'] + filenames = ['*.Rd'] + mimetypes = ['text/x-r-doc'] + + # To account for verbatim / LaTeX-like / and R-like areas + # would require parsing. + tokens = { + 'root': [ + # catch escaped brackets and percent sign + (r'\\[\\{}%]', String.Escape), + # comments + (r'%.*$', Comment), + # special macros with no arguments + (r'\\(?:cr|l?dots|R|tab)\b', Keyword.Constant), + # macros + (r'\\[a-zA-Z]+\b', Keyword), + # special preprocessor macros + (r'^\s*#(?:ifn?def|endif).*\b', Comment.Preproc), + # non-escaped brackets + (r'[{}]', Name.Builtin), + # everything else + (r'[^\\%\n{}]+', Text), + (r'.', Text), + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/rdf.py b/.venv/lib/python3.8/site-packages/pygments/lexers/rdf.py new file mode 100644 index 0000000..bd7a4f6 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/rdf.py @@ -0,0 +1,462 @@ +""" + pygments.lexers.rdf + ~~~~~~~~~~~~~~~~~~~ + + Lexers for semantic web and RDF query languages and markup. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, bygroups, default +from pygments.token import Keyword, Punctuation, String, Number, Operator, Generic, \ + Whitespace, Name, Literal, Comment, Text + +__all__ = ['SparqlLexer', 'TurtleLexer', 'ShExCLexer'] + + +class SparqlLexer(RegexLexer): + """ + Lexer for `SPARQL `_ query language. + + .. versionadded:: 2.0 + """ + name = 'SPARQL' + aliases = ['sparql'] + filenames = ['*.rq', '*.sparql'] + mimetypes = ['application/sparql-query'] + + # character group definitions :: + + PN_CHARS_BASE_GRP = ('a-zA-Z' + '\u00c0-\u00d6' + '\u00d8-\u00f6' + '\u00f8-\u02ff' + '\u0370-\u037d' + '\u037f-\u1fff' + '\u200c-\u200d' + '\u2070-\u218f' + '\u2c00-\u2fef' + '\u3001-\ud7ff' + '\uf900-\ufdcf' + '\ufdf0-\ufffd') + + PN_CHARS_U_GRP = (PN_CHARS_BASE_GRP + '_') + + PN_CHARS_GRP = (PN_CHARS_U_GRP + + r'\-' + + r'0-9' + + '\u00b7' + + '\u0300-\u036f' + + '\u203f-\u2040') + + HEX_GRP = '0-9A-Fa-f' + + PN_LOCAL_ESC_CHARS_GRP = r' _~.\-!$&"()*+,;=/?#@%' + + # terminal productions :: + + PN_CHARS_BASE = '[' + PN_CHARS_BASE_GRP + ']' + + PN_CHARS_U = '[' + PN_CHARS_U_GRP + ']' + + PN_CHARS = '[' + PN_CHARS_GRP + ']' + + HEX = '[' + HEX_GRP + ']' + + PN_LOCAL_ESC_CHARS = '[' + PN_LOCAL_ESC_CHARS_GRP + ']' + + IRIREF = r'<(?:[^<>"{}|^`\\\x00-\x20])*>' + + BLANK_NODE_LABEL = '_:[0-9' + PN_CHARS_U_GRP + '](?:[' + PN_CHARS_GRP + \ + '.]*' + PN_CHARS + ')?' + + PN_PREFIX = PN_CHARS_BASE + '(?:[' + PN_CHARS_GRP + '.]*' + PN_CHARS + ')?' + + VARNAME = '[0-9' + PN_CHARS_U_GRP + '][' + PN_CHARS_U_GRP + \ + '0-9\u00b7\u0300-\u036f\u203f-\u2040]*' + + PERCENT = '%' + HEX + HEX + + PN_LOCAL_ESC = r'\\' + PN_LOCAL_ESC_CHARS + + PLX = '(?:' + PERCENT + ')|(?:' + PN_LOCAL_ESC + ')' + + PN_LOCAL = ('(?:[' + PN_CHARS_U_GRP + ':0-9' + ']|' + PLX + ')' + + '(?:(?:[' + PN_CHARS_GRP + '.:]|' + PLX + ')*(?:[' + + PN_CHARS_GRP + ':]|' + PLX + '))?') + + EXPONENT = r'[eE][+-]?\d+' + + # Lexer token definitions :: + + tokens = { + 'root': [ + (r'\s+', Text), + # keywords :: + (r'(?i)(select|construct|describe|ask|where|filter|group\s+by|minus|' + r'distinct|reduced|from\s+named|from|order\s+by|desc|asc|limit|' + r'offset|values|bindings|load|into|clear|drop|create|add|move|copy|' + r'insert\s+data|delete\s+data|delete\s+where|with|delete|insert|' + r'using\s+named|using|graph|default|named|all|optional|service|' + r'silent|bind|undef|union|not\s+in|in|as|having|to|prefix|base)\b', Keyword), + (r'(a)\b', Keyword), + # IRIs :: + ('(' + IRIREF + ')', Name.Label), + # blank nodes :: + ('(' + BLANK_NODE_LABEL + ')', Name.Label), + # # variables :: + ('[?$]' + VARNAME, Name.Variable), + # prefixed names :: + (r'(' + PN_PREFIX + r')?(\:)(' + PN_LOCAL + r')?', + bygroups(Name.Namespace, Punctuation, Name.Tag)), + # function names :: + (r'(?i)(str|lang|langmatches|datatype|bound|iri|uri|bnode|rand|abs|' + r'ceil|floor|round|concat|strlen|ucase|lcase|encode_for_uri|' + r'contains|strstarts|strends|strbefore|strafter|year|month|day|' + r'hours|minutes|seconds|timezone|tz|now|uuid|struuid|md5|sha1|sha256|sha384|' + r'sha512|coalesce|if|strlang|strdt|sameterm|isiri|isuri|isblank|' + r'isliteral|isnumeric|regex|substr|replace|exists|not\s+exists|' + r'count|sum|min|max|avg|sample|group_concat|separator)\b', + Name.Function), + # boolean literals :: + (r'(true|false)', Keyword.Constant), + # double literals :: + (r'[+\-]?(\d+\.\d*' + EXPONENT + r'|\.?\d+' + EXPONENT + ')', Number.Float), + # decimal literals :: + (r'[+\-]?(\d+\.\d*|\.\d+)', Number.Float), + # integer literals :: + (r'[+\-]?\d+', Number.Integer), + # operators :: + (r'(\|\||&&|=|\*|\-|\+|/|!=|<=|>=|!|<|>)', Operator), + # punctuation characters :: + (r'[(){}.;,:^\[\]]', Punctuation), + # line comments :: + (r'#[^\n]*', Comment), + # strings :: + (r'"""', String, 'triple-double-quoted-string'), + (r'"', String, 'single-double-quoted-string'), + (r"'''", String, 'triple-single-quoted-string'), + (r"'", String, 'single-single-quoted-string'), + ], + 'triple-double-quoted-string': [ + (r'"""', String, 'end-of-string'), + (r'[^\\]+', String), + (r'\\', String, 'string-escape'), + ], + 'single-double-quoted-string': [ + (r'"', String, 'end-of-string'), + (r'[^"\\\n]+', String), + (r'\\', String, 'string-escape'), + ], + 'triple-single-quoted-string': [ + (r"'''", String, 'end-of-string'), + (r'[^\\]+', String), + (r'\\', String.Escape, 'string-escape'), + ], + 'single-single-quoted-string': [ + (r"'", String, 'end-of-string'), + (r"[^'\\\n]+", String), + (r'\\', String, 'string-escape'), + ], + 'string-escape': [ + (r'u' + HEX + '{4}', String.Escape, '#pop'), + (r'U' + HEX + '{8}', String.Escape, '#pop'), + (r'.', String.Escape, '#pop'), + ], + 'end-of-string': [ + (r'(@)([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)', + bygroups(Operator, Name.Function), '#pop:2'), + (r'\^\^', Operator, '#pop:2'), + default('#pop:2'), + ], + } + + +class TurtleLexer(RegexLexer): + """ + Lexer for `Turtle `_ data language. + + .. versionadded:: 2.1 + """ + name = 'Turtle' + aliases = ['turtle'] + filenames = ['*.ttl'] + mimetypes = ['text/turtle', 'application/x-turtle'] + + # character group definitions :: + PN_CHARS_BASE_GRP = ('a-zA-Z' + '\u00c0-\u00d6' + '\u00d8-\u00f6' + '\u00f8-\u02ff' + '\u0370-\u037d' + '\u037f-\u1fff' + '\u200c-\u200d' + '\u2070-\u218f' + '\u2c00-\u2fef' + '\u3001-\ud7ff' + '\uf900-\ufdcf' + '\ufdf0-\ufffd') + + PN_CHARS_U_GRP = (PN_CHARS_BASE_GRP + '_') + + PN_CHARS_GRP = (PN_CHARS_U_GRP + + r'\-' + + r'0-9' + + '\u00b7' + + '\u0300-\u036f' + + '\u203f-\u2040') + + PN_CHARS = '[' + PN_CHARS_GRP + ']' + + PN_CHARS_BASE = '[' + PN_CHARS_BASE_GRP + ']' + + PN_PREFIX = PN_CHARS_BASE + '(?:[' + PN_CHARS_GRP + '.]*' + PN_CHARS + ')?' + + HEX_GRP = '0-9A-Fa-f' + + HEX = '[' + HEX_GRP + ']' + + PERCENT = '%' + HEX + HEX + + PN_LOCAL_ESC_CHARS_GRP = r' _~.\-!$&"()*+,;=/?#@%' + + PN_LOCAL_ESC_CHARS = '[' + PN_LOCAL_ESC_CHARS_GRP + ']' + + PN_LOCAL_ESC = r'\\' + PN_LOCAL_ESC_CHARS + + PLX = '(?:' + PERCENT + ')|(?:' + PN_LOCAL_ESC + ')' + + PN_LOCAL = ('(?:[' + PN_CHARS_U_GRP + ':0-9' + ']|' + PLX + ')' + + '(?:(?:[' + PN_CHARS_GRP + '.:]|' + PLX + ')*(?:[' + + PN_CHARS_GRP + ':]|' + PLX + '))?') + + patterns = { + 'PNAME_NS': r'((?:[a-zA-Z][\w-]*)?\:)', # Simplified character range + 'IRIREF': r'(<[^<>"{}|^`\\\x00-\x20]*>)' + } + + tokens = { + 'root': [ + (r'\s+', Text), + + # Base / prefix + (r'(@base|BASE)(\s+)%(IRIREF)s(\s*)(\.?)' % patterns, + bygroups(Keyword, Whitespace, Name.Variable, Whitespace, + Punctuation)), + (r'(@prefix|PREFIX)(\s+)%(PNAME_NS)s(\s+)%(IRIREF)s(\s*)(\.?)' % patterns, + bygroups(Keyword, Whitespace, Name.Namespace, Whitespace, + Name.Variable, Whitespace, Punctuation)), + + # The shorthand predicate 'a' + (r'(?<=\s)a(?=\s)', Keyword.Type), + + # IRIREF + (r'%(IRIREF)s' % patterns, Name.Variable), + + # PrefixedName + (r'(' + PN_PREFIX + r')?(\:)(' + PN_LOCAL + r')?', + bygroups(Name.Namespace, Punctuation, Name.Tag)), + + # Comment + (r'#[^\n]+', Comment), + + (r'\b(true|false)\b', Literal), + (r'[+\-]?\d*\.\d+', Number.Float), + (r'[+\-]?\d*(:?\.\d+)?E[+\-]?\d+', Number.Float), + (r'[+\-]?\d+', Number.Integer), + (r'[\[\](){}.;,:^]', Punctuation), + + (r'"""', String, 'triple-double-quoted-string'), + (r'"', String, 'single-double-quoted-string'), + (r"'''", String, 'triple-single-quoted-string'), + (r"'", String, 'single-single-quoted-string'), + ], + 'triple-double-quoted-string': [ + (r'"""', String, 'end-of-string'), + (r'[^\\]+', String), + (r'\\', String, 'string-escape'), + ], + 'single-double-quoted-string': [ + (r'"', String, 'end-of-string'), + (r'[^"\\\n]+', String), + (r'\\', String, 'string-escape'), + ], + 'triple-single-quoted-string': [ + (r"'''", String, 'end-of-string'), + (r'[^\\]+', String), + (r'\\', String, 'string-escape'), + ], + 'single-single-quoted-string': [ + (r"'", String, 'end-of-string'), + (r"[^'\\\n]+", String), + (r'\\', String, 'string-escape'), + ], + 'string-escape': [ + (r'.', String, '#pop'), + ], + 'end-of-string': [ + (r'(@)([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)', + bygroups(Operator, Generic.Emph), '#pop:2'), + + (r'(\^\^)%(IRIREF)s' % patterns, bygroups(Operator, Generic.Emph), '#pop:2'), + + default('#pop:2'), + + ], + } + + # Turtle and Tera Term macro files share the same file extension + # but each has a recognizable and distinct syntax. + def analyse_text(text): + for t in ('@base ', 'BASE ', '@prefix ', 'PREFIX '): + if re.search(r'^\s*%s' % t, text): + return 0.80 + + +class ShExCLexer(RegexLexer): + """ + Lexer for `ShExC `_ shape expressions language syntax. + """ + name = 'ShExC' + aliases = ['shexc', 'shex'] + filenames = ['*.shex'] + mimetypes = ['text/shex'] + + # character group definitions :: + + PN_CHARS_BASE_GRP = ('a-zA-Z' + '\u00c0-\u00d6' + '\u00d8-\u00f6' + '\u00f8-\u02ff' + '\u0370-\u037d' + '\u037f-\u1fff' + '\u200c-\u200d' + '\u2070-\u218f' + '\u2c00-\u2fef' + '\u3001-\ud7ff' + '\uf900-\ufdcf' + '\ufdf0-\ufffd') + + PN_CHARS_U_GRP = (PN_CHARS_BASE_GRP + '_') + + PN_CHARS_GRP = (PN_CHARS_U_GRP + + r'\-' + + r'0-9' + + '\u00b7' + + '\u0300-\u036f' + + '\u203f-\u2040') + + HEX_GRP = '0-9A-Fa-f' + + PN_LOCAL_ESC_CHARS_GRP = r"_~.\-!$&'()*+,;=/?#@%" + + # terminal productions :: + + PN_CHARS_BASE = '[' + PN_CHARS_BASE_GRP + ']' + + PN_CHARS_U = '[' + PN_CHARS_U_GRP + ']' + + PN_CHARS = '[' + PN_CHARS_GRP + ']' + + HEX = '[' + HEX_GRP + ']' + + PN_LOCAL_ESC_CHARS = '[' + PN_LOCAL_ESC_CHARS_GRP + ']' + + UCHAR_NO_BACKSLASH = '(?:u' + HEX + '{4}|U' + HEX + '{8})' + + UCHAR = r'\\' + UCHAR_NO_BACKSLASH + + IRIREF = r'<(?:[^\x00-\x20<>"{}|^`\\]|' + UCHAR + ')*>' + + BLANK_NODE_LABEL = '_:[0-9' + PN_CHARS_U_GRP + '](?:[' + PN_CHARS_GRP + \ + '.]*' + PN_CHARS + ')?' + + PN_PREFIX = PN_CHARS_BASE + '(?:[' + PN_CHARS_GRP + '.]*' + PN_CHARS + ')?' + + PERCENT = '%' + HEX + HEX + + PN_LOCAL_ESC = r'\\' + PN_LOCAL_ESC_CHARS + + PLX = '(?:' + PERCENT + ')|(?:' + PN_LOCAL_ESC + ')' + + PN_LOCAL = ('(?:[' + PN_CHARS_U_GRP + ':0-9' + ']|' + PLX + ')' + + '(?:(?:[' + PN_CHARS_GRP + '.:]|' + PLX + ')*(?:[' + + PN_CHARS_GRP + ':]|' + PLX + '))?') + + EXPONENT = r'[eE][+-]?\d+' + + # Lexer token definitions :: + + tokens = { + 'root': [ + (r'\s+', Text), + # keywords :: + (r'(?i)(base|prefix|start|external|' + r'literal|iri|bnode|nonliteral|length|minlength|maxlength|' + r'mininclusive|minexclusive|maxinclusive|maxexclusive|' + r'totaldigits|fractiondigits|' + r'closed|extra)\b', Keyword), + (r'(a)\b', Keyword), + # IRIs :: + ('(' + IRIREF + ')', Name.Label), + # blank nodes :: + ('(' + BLANK_NODE_LABEL + ')', Name.Label), + # prefixed names :: + (r'(' + PN_PREFIX + r')?(\:)(' + PN_LOCAL + ')?', + bygroups(Name.Namespace, Punctuation, Name.Tag)), + # boolean literals :: + (r'(true|false)', Keyword.Constant), + # double literals :: + (r'[+\-]?(\d+\.\d*' + EXPONENT + r'|\.?\d+' + EXPONENT + ')', Number.Float), + # decimal literals :: + (r'[+\-]?(\d+\.\d*|\.\d+)', Number.Float), + # integer literals :: + (r'[+\-]?\d+', Number.Integer), + # operators :: + (r'[@|$&=*+?^\-~]', Operator), + # operator keywords :: + (r'(?i)(and|or|not)\b', Operator.Word), + # punctuation characters :: + (r'[(){}.;,:^\[\]]', Punctuation), + # line comments :: + (r'#[^\n]*', Comment), + # strings :: + (r'"""', String, 'triple-double-quoted-string'), + (r'"', String, 'single-double-quoted-string'), + (r"'''", String, 'triple-single-quoted-string'), + (r"'", String, 'single-single-quoted-string'), + ], + 'triple-double-quoted-string': [ + (r'"""', String, 'end-of-string'), + (r'[^\\]+', String), + (r'\\', String, 'string-escape'), + ], + 'single-double-quoted-string': [ + (r'"', String, 'end-of-string'), + (r'[^"\\\n]+', String), + (r'\\', String, 'string-escape'), + ], + 'triple-single-quoted-string': [ + (r"'''", String, 'end-of-string'), + (r'[^\\]+', String), + (r'\\', String.Escape, 'string-escape'), + ], + 'single-single-quoted-string': [ + (r"'", String, 'end-of-string'), + (r"[^'\\\n]+", String), + (r'\\', String, 'string-escape'), + ], + 'string-escape': [ + (UCHAR_NO_BACKSLASH, String.Escape, '#pop'), + (r'.', String.Escape, '#pop'), + ], + 'end-of-string': [ + (r'(@)([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)', + bygroups(Operator, Name.Function), '#pop:2'), + (r'\^\^', Operator, '#pop:2'), + default('#pop:2'), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/rebol.py b/.venv/lib/python3.8/site-packages/pygments/lexers/rebol.py new file mode 100644 index 0000000..57480a1 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/rebol.py @@ -0,0 +1,430 @@ +""" + pygments.lexers.rebol + ~~~~~~~~~~~~~~~~~~~~~ + + Lexers for the REBOL and related languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, bygroups +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Generic, Whitespace + +__all__ = ['RebolLexer', 'RedLexer'] + + +class RebolLexer(RegexLexer): + """ + A `REBOL `_ lexer. + + .. versionadded:: 1.1 + """ + name = 'REBOL' + aliases = ['rebol'] + filenames = ['*.r', '*.r3', '*.reb'] + mimetypes = ['text/x-rebol'] + + flags = re.IGNORECASE | re.MULTILINE + + escape_re = r'(?:\^\([0-9a-f]{1,4}\)*)' + + def word_callback(lexer, match): + word = match.group() + + if re.match(".*:$", word): + yield match.start(), Generic.Subheading, word + elif re.match( + r'(native|alias|all|any|as-string|as-binary|bind|bound\?|case|' + r'catch|checksum|comment|debase|dehex|exclude|difference|disarm|' + r'either|else|enbase|foreach|remove-each|form|free|get|get-env|if|' + r'in|intersect|loop|minimum-of|maximum-of|mold|new-line|' + r'new-line\?|not|now|prin|print|reduce|compose|construct|repeat|' + r'reverse|save|script\?|set|shift|switch|throw|to-hex|trace|try|' + r'type\?|union|unique|unless|unprotect|unset|until|use|value\?|' + r'while|compress|decompress|secure|open|close|read|read-io|' + r'write-io|write|update|query|wait|input\?|exp|log-10|log-2|' + r'log-e|square-root|cosine|sine|tangent|arccosine|arcsine|' + r'arctangent|protect|lowercase|uppercase|entab|detab|connected\?|' + r'browse|launch|stats|get-modes|set-modes|to-local-file|' + r'to-rebol-file|encloak|decloak|create-link|do-browser|bind\?|' + r'hide|draw|show|size-text|textinfo|offset-to-caret|' + r'caret-to-offset|local-request-file|rgb-to-hsv|hsv-to-rgb|' + r'crypt-strength\?|dh-make-key|dh-generate-key|dh-compute-key|' + r'dsa-make-key|dsa-generate-key|dsa-make-signature|' + r'dsa-verify-signature|rsa-make-key|rsa-generate-key|' + r'rsa-encrypt)$', word): + yield match.start(), Name.Builtin, word + elif re.match( + r'(add|subtract|multiply|divide|remainder|power|and~|or~|xor~|' + r'minimum|maximum|negate|complement|absolute|random|head|tail|' + r'next|back|skip|at|pick|first|second|third|fourth|fifth|sixth|' + r'seventh|eighth|ninth|tenth|last|path|find|select|make|to|copy\*|' + r'insert|remove|change|poke|clear|trim|sort|min|max|abs|cp|' + r'copy)$', word): + yield match.start(), Name.Function, word + elif re.match( + r'(error|source|input|license|help|install|echo|Usage|with|func|' + r'throw-on-error|function|does|has|context|probe|\?\?|as-pair|' + r'mod|modulo|round|repend|about|set-net|append|join|rejoin|reform|' + r'remold|charset|array|replace|move|extract|forskip|forall|alter|' + r'first+|also|take|for|forever|dispatch|attempt|what-dir|' + r'change-dir|clean-path|list-dir|dirize|rename|split-path|delete|' + r'make-dir|delete-dir|in-dir|confirm|dump-obj|upgrade|what|' + r'build-tag|process-source|build-markup|decode-cgi|read-cgi|' + r'write-user|save-user|set-user-name|protect-system|parse-xml|' + r'cvs-date|cvs-version|do-boot|get-net-info|desktop|layout|' + r'scroll-para|get-face|alert|set-face|uninstall|unfocus|' + r'request-dir|center-face|do-events|net-error|decode-url|' + r'parse-header|parse-header-date|parse-email-addrs|import-email|' + r'send|build-attach-body|resend|show-popup|hide-popup|open-events|' + r'find-key-face|do-face|viewtop|confine|find-window|' + r'insert-event-func|remove-event-func|inform|dump-pane|dump-face|' + r'flag-face|deflag-face|clear-fields|read-net|vbug|path-thru|' + r'read-thru|load-thru|do-thru|launch-thru|load-image|' + r'request-download|do-face-alt|set-font|set-para|get-style|' + r'set-style|make-face|stylize|choose|hilight-text|hilight-all|' + r'unlight-text|focus|scroll-drag|clear-face|reset-face|scroll-face|' + r'resize-face|load-stock|load-stock-block|notify|request|flash|' + r'request-color|request-pass|request-text|request-list|' + r'request-date|request-file|dbug|editor|link-relative-path|' + r'emailer|parse-error)$', word): + yield match.start(), Keyword.Namespace, word + elif re.match( + r'(halt|quit|do|load|q|recycle|call|run|ask|parse|view|unview|' + r'return|exit|break)$', word): + yield match.start(), Name.Exception, word + elif re.match('REBOL$', word): + yield match.start(), Generic.Heading, word + elif re.match("to-.*", word): + yield match.start(), Keyword, word + elif re.match(r'(\+|-|\*|/|//|\*\*|and|or|xor|=\?|=|==|<>|<|>|<=|>=)$', + word): + yield match.start(), Operator, word + elif re.match(r".*\?$", word): + yield match.start(), Keyword, word + elif re.match(r".*\!$", word): + yield match.start(), Keyword.Type, word + elif re.match("'.*", word): + yield match.start(), Name.Variable.Instance, word # lit-word + elif re.match("#.*", word): + yield match.start(), Name.Label, word # issue + elif re.match("%.*", word): + yield match.start(), Name.Decorator, word # file + else: + yield match.start(), Name.Variable, word + + tokens = { + 'root': [ + (r'[^R]+', Comment), + (r'REBOL\s+\[', Generic.Strong, 'script'), + (r'R', Comment) + ], + 'script': [ + (r'\s+', Text), + (r'#"', String.Char, 'char'), + (r'#\{[0-9a-f]*\}', Number.Hex), + (r'2#\{', Number.Hex, 'bin2'), + (r'64#\{[0-9a-z+/=\s]*\}', Number.Hex), + (r'"', String, 'string'), + (r'\{', String, 'string2'), + (r';#+.*\n', Comment.Special), + (r';\*+.*\n', Comment.Preproc), + (r';.*\n', Comment), + (r'%"', Name.Decorator, 'stringFile'), + (r'%[^(^{")\s\[\]]+', Name.Decorator), + (r'[+-]?([a-z]{1,3})?\$\d+(\.\d+)?', Number.Float), # money + (r'[+-]?\d+\:\d+(\:\d+)?(\.\d+)?', String.Other), # time + (r'\d+[\-/][0-9a-z]+[\-/]\d+(\/\d+\:\d+((\:\d+)?' + r'([.\d+]?([+-]?\d+:\d+)?)?)?)?', String.Other), # date + (r'\d+(\.\d+)+\.\d+', Keyword.Constant), # tuple + (r'\d+X\d+', Keyword.Constant), # pair + (r'[+-]?\d+(\'\d+)?([.,]\d*)?E[+-]?\d+', Number.Float), + (r'[+-]?\d+(\'\d+)?[.,]\d*', Number.Float), + (r'[+-]?\d+(\'\d+)?', Number), + (r'[\[\]()]', Generic.Strong), + (r'[a-z]+[^(^{"\s:)]*://[^(^{"\s)]*', Name.Decorator), # url + (r'mailto:[^(^{"@\s)]+@[^(^{"@\s)]+', Name.Decorator), # url + (r'[^(^{"@\s)]+@[^(^{"@\s)]+', Name.Decorator), # email + (r'comment\s"', Comment, 'commentString1'), + (r'comment\s\{', Comment, 'commentString2'), + (r'comment\s\[', Comment, 'commentBlock'), + (r'comment\s[^(\s{"\[]+', Comment), + (r'/[^(^{")\s/[\]]*', Name.Attribute), + (r'([^(^{")\s/[\]]+)(?=[:({"\s/\[\]])', word_callback), + (r'<[\w:.-]*>', Name.Tag), + (r'<[^(<>\s")]+', Name.Tag, 'tag'), + (r'([^(^{")\s]+)', Text), + ], + 'string': [ + (r'[^(^")]+', String), + (escape_re, String.Escape), + (r'[(|)]+', String), + (r'\^.', String.Escape), + (r'"', String, '#pop'), + ], + 'string2': [ + (r'[^(^{})]+', String), + (escape_re, String.Escape), + (r'[(|)]+', String), + (r'\^.', String.Escape), + (r'\{', String, '#push'), + (r'\}', String, '#pop'), + ], + 'stringFile': [ + (r'[^(^")]+', Name.Decorator), + (escape_re, Name.Decorator), + (r'\^.', Name.Decorator), + (r'"', Name.Decorator, '#pop'), + ], + 'char': [ + (escape_re + '"', String.Char, '#pop'), + (r'\^."', String.Char, '#pop'), + (r'."', String.Char, '#pop'), + ], + 'tag': [ + (escape_re, Name.Tag), + (r'"', Name.Tag, 'tagString'), + (r'[^(<>\r\n")]+', Name.Tag), + (r'>', Name.Tag, '#pop'), + ], + 'tagString': [ + (r'[^(^")]+', Name.Tag), + (escape_re, Name.Tag), + (r'[(|)]+', Name.Tag), + (r'\^.', Name.Tag), + (r'"', Name.Tag, '#pop'), + ], + 'tuple': [ + (r'(\d+\.)+', Keyword.Constant), + (r'\d+', Keyword.Constant, '#pop'), + ], + 'bin2': [ + (r'\s+', Number.Hex), + (r'([01]\s*){8}', Number.Hex), + (r'\}', Number.Hex, '#pop'), + ], + 'commentString1': [ + (r'[^(^")]+', Comment), + (escape_re, Comment), + (r'[(|)]+', Comment), + (r'\^.', Comment), + (r'"', Comment, '#pop'), + ], + 'commentString2': [ + (r'[^(^{})]+', Comment), + (escape_re, Comment), + (r'[(|)]+', Comment), + (r'\^.', Comment), + (r'\{', Comment, '#push'), + (r'\}', Comment, '#pop'), + ], + 'commentBlock': [ + (r'\[', Comment, '#push'), + (r'\]', Comment, '#pop'), + (r'"', Comment, "commentString1"), + (r'\{', Comment, "commentString2"), + (r'[^(\[\]"{)]+', Comment), + ], + } + + def analyse_text(text): + """ + Check if code contains REBOL header and so it probably not R code + """ + if re.match(r'^\s*REBOL\s*\[', text, re.IGNORECASE): + # The code starts with REBOL header + return 1.0 + elif re.search(r'\s*REBOL\s*\[', text, re.IGNORECASE): + # The code contains REBOL header but also some text before it + return 0.5 + + +class RedLexer(RegexLexer): + """ + A `Red-language `_ lexer. + + .. versionadded:: 2.0 + """ + name = 'Red' + aliases = ['red', 'red/system'] + filenames = ['*.red', '*.reds'] + mimetypes = ['text/x-red', 'text/x-red-system'] + + flags = re.IGNORECASE | re.MULTILINE + + escape_re = r'(?:\^\([0-9a-f]{1,4}\)*)' + + def word_callback(lexer, match): + word = match.group() + + if re.match(".*:$", word): + yield match.start(), Generic.Subheading, word + elif re.match(r'(if|unless|either|any|all|while|until|loop|repeat|' + r'foreach|forall|func|function|does|has|switch|' + r'case|reduce|compose|get|set|print|prin|equal\?|' + r'not-equal\?|strict-equal\?|lesser\?|greater\?|lesser-or-equal\?|' + r'greater-or-equal\?|same\?|not|type\?|stats|' + r'bind|union|replace|charset|routine)$', word): + yield match.start(), Name.Builtin, word + elif re.match(r'(make|random|reflect|to|form|mold|absolute|add|divide|multiply|negate|' + r'power|remainder|round|subtract|even\?|odd\?|and~|complement|or~|xor~|' + r'append|at|back|change|clear|copy|find|head|head\?|index\?|insert|' + r'length\?|next|pick|poke|remove|reverse|select|sort|skip|swap|tail|tail\?|' + r'take|trim|create|close|delete|modify|open|open\?|query|read|rename|' + r'update|write)$', word): + yield match.start(), Name.Function, word + elif re.match(r'(yes|on|no|off|true|false|tab|cr|lf|newline|escape|slash|sp|space|null|' + r'none|crlf|dot|null-byte)$', word): + yield match.start(), Name.Builtin.Pseudo, word + elif re.match(r'(#system-global|#include|#enum|#define|#either|#if|#import|#export|' + r'#switch|#default|#get-definition)$', word): + yield match.start(), Keyword.Namespace, word + elif re.match(r'(system|halt|quit|quit-return|do|load|q|recycle|call|run|ask|parse|' + r'raise-error|return|exit|break|alias|push|pop|probe|\?\?|spec-of|body-of|' + r'quote|forever)$', word): + yield match.start(), Name.Exception, word + elif re.match(r'(action\?|block\?|char\?|datatype\?|file\?|function\?|get-path\?|zero\?|' + r'get-word\?|integer\?|issue\?|lit-path\?|lit-word\?|logic\?|native\?|' + r'op\?|paren\?|path\?|refinement\?|set-path\?|set-word\?|string\?|unset\?|' + r'any-struct\?|none\?|word\?|any-series\?)$', word): + yield match.start(), Keyword, word + elif re.match(r'(JNICALL|stdcall|cdecl|infix)$', word): + yield match.start(), Keyword.Namespace, word + elif re.match("to-.*", word): + yield match.start(), Keyword, word + elif re.match(r'(\+|-\*\*|-|\*\*|//|/|\*|and|or|xor|=\?|===|==|=|<>|<=|>=|' + r'<<<|>>>|<<|>>|<|>%)$', word): + yield match.start(), Operator, word + elif re.match(r".*\!$", word): + yield match.start(), Keyword.Type, word + elif re.match("'.*", word): + yield match.start(), Name.Variable.Instance, word # lit-word + elif re.match("#.*", word): + yield match.start(), Name.Label, word # issue + elif re.match("%.*", word): + yield match.start(), Name.Decorator, word # file + elif re.match(":.*", word): + yield match.start(), Generic.Subheading, word # get-word + else: + yield match.start(), Name.Variable, word + + tokens = { + 'root': [ + (r'[^R]+', Comment), + (r'Red/System\s+\[', Generic.Strong, 'script'), + (r'Red\s+\[', Generic.Strong, 'script'), + (r'R', Comment) + ], + 'script': [ + (r'\s+', Text), + (r'#"', String.Char, 'char'), + (r'#\{[0-9a-f\s]*\}', Number.Hex), + (r'2#\{', Number.Hex, 'bin2'), + (r'64#\{[0-9a-z+/=\s]*\}', Number.Hex), + (r'([0-9a-f]+)(h)((\s)|(?=[\[\]{}"()]))', + bygroups(Number.Hex, Name.Variable, Whitespace)), + (r'"', String, 'string'), + (r'\{', String, 'string2'), + (r';#+.*\n', Comment.Special), + (r';\*+.*\n', Comment.Preproc), + (r';.*\n', Comment), + (r'%"', Name.Decorator, 'stringFile'), + (r'%[^(^{")\s\[\]]+', Name.Decorator), + (r'[+-]?([a-z]{1,3})?\$\d+(\.\d+)?', Number.Float), # money + (r'[+-]?\d+\:\d+(\:\d+)?(\.\d+)?', String.Other), # time + (r'\d+[\-/][0-9a-z]+[\-/]\d+(/\d+:\d+((:\d+)?' + r'([\.\d+]?([+-]?\d+:\d+)?)?)?)?', String.Other), # date + (r'\d+(\.\d+)+\.\d+', Keyword.Constant), # tuple + (r'\d+X\d+', Keyword.Constant), # pair + (r'[+-]?\d+(\'\d+)?([.,]\d*)?E[+-]?\d+', Number.Float), + (r'[+-]?\d+(\'\d+)?[.,]\d*', Number.Float), + (r'[+-]?\d+(\'\d+)?', Number), + (r'[\[\]()]', Generic.Strong), + (r'[a-z]+[^(^{"\s:)]*://[^(^{"\s)]*', Name.Decorator), # url + (r'mailto:[^(^{"@\s)]+@[^(^{"@\s)]+', Name.Decorator), # url + (r'[^(^{"@\s)]+@[^(^{"@\s)]+', Name.Decorator), # email + (r'comment\s"', Comment, 'commentString1'), + (r'comment\s\{', Comment, 'commentString2'), + (r'comment\s\[', Comment, 'commentBlock'), + (r'comment\s[^(\s{"\[]+', Comment), + (r'/[^(^{^")\s/[\]]*', Name.Attribute), + (r'([^(^{^")\s/[\]]+)(?=[:({"\s/\[\]])', word_callback), + (r'<[\w:.-]*>', Name.Tag), + (r'<[^(<>\s")]+', Name.Tag, 'tag'), + (r'([^(^{")\s]+)', Text), + ], + 'string': [ + (r'[^(^")]+', String), + (escape_re, String.Escape), + (r'[(|)]+', String), + (r'\^.', String.Escape), + (r'"', String, '#pop'), + ], + 'string2': [ + (r'[^(^{})]+', String), + (escape_re, String.Escape), + (r'[(|)]+', String), + (r'\^.', String.Escape), + (r'\{', String, '#push'), + (r'\}', String, '#pop'), + ], + 'stringFile': [ + (r'[^(^")]+', Name.Decorator), + (escape_re, Name.Decorator), + (r'\^.', Name.Decorator), + (r'"', Name.Decorator, '#pop'), + ], + 'char': [ + (escape_re + '"', String.Char, '#pop'), + (r'\^."', String.Char, '#pop'), + (r'."', String.Char, '#pop'), + ], + 'tag': [ + (escape_re, Name.Tag), + (r'"', Name.Tag, 'tagString'), + (r'[^(<>\r\n")]+', Name.Tag), + (r'>', Name.Tag, '#pop'), + ], + 'tagString': [ + (r'[^(^")]+', Name.Tag), + (escape_re, Name.Tag), + (r'[(|)]+', Name.Tag), + (r'\^.', Name.Tag), + (r'"', Name.Tag, '#pop'), + ], + 'tuple': [ + (r'(\d+\.)+', Keyword.Constant), + (r'\d+', Keyword.Constant, '#pop'), + ], + 'bin2': [ + (r'\s+', Number.Hex), + (r'([01]\s*){8}', Number.Hex), + (r'\}', Number.Hex, '#pop'), + ], + 'commentString1': [ + (r'[^(^")]+', Comment), + (escape_re, Comment), + (r'[(|)]+', Comment), + (r'\^.', Comment), + (r'"', Comment, '#pop'), + ], + 'commentString2': [ + (r'[^(^{})]+', Comment), + (escape_re, Comment), + (r'[(|)]+', Comment), + (r'\^.', Comment), + (r'\{', Comment, '#push'), + (r'\}', Comment, '#pop'), + ], + 'commentBlock': [ + (r'\[', Comment, '#push'), + (r'\]', Comment, '#pop'), + (r'"', Comment, "commentString1"), + (r'\{', Comment, "commentString2"), + (r'[^(\[\]"{)]+', Comment), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/resource.py b/.venv/lib/python3.8/site-packages/pygments/lexers/resource.py new file mode 100644 index 0000000..3ed176a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/resource.py @@ -0,0 +1,84 @@ +""" + pygments.lexers.resource + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexer for resource definition files. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, bygroups, words +from pygments.token import Comment, String, Number, Operator, Text, \ + Keyword, Name + +__all__ = ['ResourceLexer'] + + +class ResourceLexer(RegexLexer): + """Lexer for `ICU Resource bundles + `_. + + .. versionadded:: 2.0 + """ + name = 'ResourceBundle' + aliases = ['resourcebundle', 'resource'] + filenames = [] + + _types = (':table', ':array', ':string', ':bin', ':import', ':intvector', + ':int', ':alias') + + flags = re.MULTILINE | re.IGNORECASE + tokens = { + 'root': [ + (r'//.*?$', Comment), + (r'"', String, 'string'), + (r'-?\d+', Number.Integer), + (r'[,{}]', Operator), + (r'([^\s{:]+)(\s*)(%s?)' % '|'.join(_types), + bygroups(Name, Text, Keyword)), + (r'\s+', Text), + (words(_types), Keyword), + ], + 'string': [ + (r'(\\x[0-9a-f]{2}|\\u[0-9a-f]{4}|\\U00[0-9a-f]{6}|' + r'\\[0-7]{1,3}|\\c.|\\[abtnvfre\'"?\\]|\\\{|[^"{\\])+', String), + (r'\{', String.Escape, 'msgname'), + (r'"', String, '#pop') + ], + 'msgname': [ + (r'([^{},]+)(\s*)', bygroups(Name, String.Escape), ('#pop', 'message')) + ], + 'message': [ + (r'\{', String.Escape, 'msgname'), + (r'\}', String.Escape, '#pop'), + (r'(,)(\s*)([a-z]+)(\s*\})', + bygroups(Operator, String.Escape, Keyword, String.Escape), '#pop'), + (r'(,)(\s*)([a-z]+)(\s*)(,)(\s*)(offset)(\s*)(:)(\s*)(-?\d+)(\s*)', + bygroups(Operator, String.Escape, Keyword, String.Escape, Operator, + String.Escape, Operator.Word, String.Escape, Operator, + String.Escape, Number.Integer, String.Escape), 'choice'), + (r'(,)(\s*)([a-z]+)(\s*)(,)(\s*)', + bygroups(Operator, String.Escape, Keyword, String.Escape, Operator, + String.Escape), 'choice'), + (r'\s+', String.Escape) + ], + 'choice': [ + (r'(=|<|>|<=|>=|!=)(-?\d+)(\s*\{)', + bygroups(Operator, Number.Integer, String.Escape), 'message'), + (r'([a-z]+)(\s*\{)', bygroups(Keyword.Type, String.Escape), 'str'), + (r'\}', String.Escape, ('#pop', '#pop')), + (r'\s+', String.Escape) + ], + 'str': [ + (r'\}', String.Escape, '#pop'), + (r'\{', String.Escape, 'msgname'), + (r'[^{}]+', String) + ] + } + + def analyse_text(text): + if text.startswith('root:table'): + return 1.0 diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/ride.py b/.venv/lib/python3.8/site-packages/pygments/lexers/ride.py new file mode 100644 index 0000000..07cc1ef --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/ride.py @@ -0,0 +1,138 @@ +""" + pygments.lexers.ride + ~~~~~~~~~~~~~~~~~~~~ + + Lexer for the Ride programming language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, words, include +from pygments.token import Comment, Keyword, Name, Number, Punctuation, String, Text + +__all__ = ['RideLexer'] + + +class RideLexer(RegexLexer): + """ + For `Ride `_ + source code. + + .. versionadded:: 2.6 + """ + + name = 'Ride' + aliases = ['ride'] + filenames = ['*.ride'] + mimetypes = ['text/x-ride'] + + validName = r'[a-zA-Z_][a-zA-Z0-9_\']*' + + builtinOps = ( + '||', '|', '>=', '>', '==', '!', + '=', '<=', '<', '::', ':+', ':', '!=', '/', + '.', '=>', '-', '+', '*', '&&', '%', '++', + ) + + globalVariablesName = ( + 'NOALG', 'MD5', 'SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512', + 'SHA3224', 'SHA3256', 'SHA3384', 'SHA3512', 'nil', 'this', 'unit', + 'height', 'lastBlock', 'Buy', 'Sell', 'CEILING', 'FLOOR', 'DOWN', + 'HALFDOWN', 'HALFEVEN', 'HALFUP', 'UP', + ) + + typesName = ( + 'Unit', 'Int', 'Boolean', 'ByteVector', 'String', 'Address', 'Alias', + 'Transfer', 'AssetPair', 'DataEntry', 'Order', 'Transaction', + 'GenesisTransaction', 'PaymentTransaction', 'ReissueTransaction', + 'BurnTransaction', 'MassTransferTransaction', 'ExchangeTransaction', + 'TransferTransaction', 'SetAssetScriptTransaction', + 'InvokeScriptTransaction', 'IssueTransaction', 'LeaseTransaction', + 'LeaseCancelTransaction', 'CreateAliasTransaction', + 'SetScriptTransaction', 'SponsorFeeTransaction', 'DataTransaction', + 'WriteSet', 'AttachedPayment', 'ScriptTransfer', 'TransferSet', + 'ScriptResult', 'Invocation', 'Asset', 'BlockInfo', 'Issue', 'Reissue', + 'Burn', 'NoAlg', 'Md5', 'Sha1', 'Sha224', 'Sha256', 'Sha384', 'Sha512', + 'Sha3224', 'Sha3256', 'Sha3384', 'Sha3512', 'BinaryEntry', + 'BooleanEntry', 'IntegerEntry', 'StringEntry', 'List', 'Ceiling', + 'Down', 'Floor', 'HalfDown', 'HalfEven', 'HalfUp', 'Up', + ) + + functionsName = ( + 'fraction', 'size', 'toBytes', 'take', 'drop', 'takeRight', 'dropRight', + 'toString', 'isDefined', 'extract', 'throw', 'getElement', 'value', + 'cons', 'toUtf8String', 'toInt', 'indexOf', 'lastIndexOf', 'split', + 'parseInt', 'parseIntValue', 'keccak256', 'blake2b256', 'sha256', + 'sigVerify', 'toBase58String', 'fromBase58String', 'toBase64String', + 'fromBase64String', 'transactionById', 'transactionHeightById', + 'getInteger', 'getBoolean', 'getBinary', 'getString', + 'addressFromPublicKey', 'addressFromString', 'addressFromRecipient', + 'assetBalance', 'wavesBalance', 'getIntegerValue', 'getBooleanValue', + 'getBinaryValue', 'getStringValue', 'addressFromStringValue', + 'assetInfo', 'rsaVerify', 'checkMerkleProof', 'median', + 'valueOrElse', 'valueOrErrorMessage', 'contains', 'log', 'pow', + 'toBase16String', 'fromBase16String', 'blockInfoByHeight', + 'transferTransactionById', + ) + + reservedWords = words(( + 'match', 'case', 'else', 'func', 'if', + 'let', 'then', '@Callable', '@Verifier', + ), suffix=r'\b') + + tokens = { + 'root': [ + # Comments + (r'#.*', Comment.Single), + # Whitespace + (r'\s+', Text), + # Strings + (r'"', String, 'doublequote'), + (r'utf8\'', String, 'utf8quote'), + (r'base(58|64|16)\'', String, 'singlequote'), + # Keywords + (reservedWords, Keyword.Reserved), + (r'\{-#.*?#-\}', Keyword.Reserved), + (r'FOLD<\d+>', Keyword.Reserved), + # Types + (words(typesName), Keyword.Type), + # Main + # (specialName, Keyword.Reserved), + # Prefix Operators + (words(builtinOps, prefix=r'\(', suffix=r'\)'), Name.Function), + # Infix Operators + (words(builtinOps), Name.Function), + (words(globalVariablesName), Name.Function), + (words(functionsName), Name.Function), + # Numbers + include('numbers'), + # Variable Names + (validName, Name.Variable), + # Parens + (r'[,()\[\]{}]', Punctuation), + ], + + 'doublequote': [ + (r'\\u[0-9a-fA-F]{4}', String.Escape), + (r'\\[nrfvb\\"]', String.Escape), + (r'[^"]', String), + (r'"', String, '#pop'), + ], + + 'utf8quote': [ + (r'\\u[0-9a-fA-F]{4}', String.Escape), + (r'\\[nrfvb\\\']', String.Escape), + (r'[^\']', String), + (r'\'', String, '#pop'), + ], + + 'singlequote': [ + (r'[^\']', String), + (r'\'', String, '#pop'), + ], + + 'numbers': [ + (r'_?\d+', Number.Integer), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/rita.py b/.venv/lib/python3.8/site-packages/pygments/lexers/rita.py new file mode 100644 index 0000000..f8f517b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/rita.py @@ -0,0 +1,44 @@ +""" + pygments.lexers.rita + ~~~~~~~~~~~~~~~~~~~~ + + Lexers for RITA language + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, bygroups, using, this, \ + inherit, words +from pygments.token import Comment, Operator, Keyword, Name, Literal, Punctuation, Text, Whitespace + +__all__ = ['RitaLexer'] + + +class RitaLexer(RegexLexer): + """ + Lexer for `RITA `_ + + .. versionadded:: 2.11 + """ + name = 'Rita' + filenames = ['*.rita'] + aliases = ['rita'] + mimetypes = ['text/rita'] + + tokens = { + 'root': [ + (r'\n', Whitespace), + (r'\s+', Whitespace), + (r'#(.*?)\n', Comment.Single), + (r'@(.*?)\n', Operator), # Yes, whole line as an operator + (r'"(\w|\d|\s|(\\")|[\'_\-./,\?\!])+?"', Literal), + (r'\'(\w|\d|\s|(\\\')|["_\-./,\?\!])+?\'', Literal), + (r'([A-Z_]+)', Keyword), + (r'([a-z0-9_]+)', Name), + (r'((->)|[!?+*|=])', Operator), + (r'[\(\),\{\}]', Punctuation) + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/rnc.py b/.venv/lib/python3.8/site-packages/pygments/lexers/rnc.py new file mode 100644 index 0000000..cc8950a --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/rnc.py @@ -0,0 +1,66 @@ +""" + pygments.lexers.rnc + ~~~~~~~~~~~~~~~~~~~ + + Lexer for Relax-NG Compact syntax + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Punctuation + +__all__ = ['RNCCompactLexer'] + + +class RNCCompactLexer(RegexLexer): + """ + For `RelaxNG-compact `_ syntax. + + .. versionadded:: 2.2 + """ + + name = 'Relax-NG Compact' + aliases = ['rng-compact', 'rnc'] + filenames = ['*.rnc'] + + tokens = { + 'root': [ + (r'namespace\b', Keyword.Namespace), + (r'(?:default|datatypes)\b', Keyword.Declaration), + (r'##.*$', Comment.Preproc), + (r'#.*$', Comment.Single), + (r'"[^"]*"', String.Double), + # TODO single quoted strings and escape sequences outside of + # double-quoted strings + (r'(?:element|attribute|mixed)\b', Keyword.Declaration, 'variable'), + (r'(text\b|xsd:[^ ]+)', Keyword.Type, 'maybe_xsdattributes'), + (r'[,?&*=|~]|>>', Operator), + (r'[(){}]', Punctuation), + (r'.', Text), + ], + + # a variable has been declared using `element` or `attribute` + 'variable': [ + (r'[^{]+', Name.Variable), + (r'\{', Punctuation, '#pop'), + ], + + # after an xsd: declaration there may be attributes + 'maybe_xsdattributes': [ + (r'\{', Punctuation, 'xsdattributes'), + (r'\}', Punctuation, '#pop'), + (r'.', Text), + ], + + # attributes take the form { key1 = value1 key2 = value2 ... } + 'xsdattributes': [ + (r'[^ =}]', Name.Attribute), + (r'=', Operator), + (r'"[^"]*"', String.Double), + (r'\}', Punctuation, '#pop'), + (r'.', Text), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/roboconf.py b/.venv/lib/python3.8/site-packages/pygments/lexers/roboconf.py new file mode 100644 index 0000000..4380113 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/roboconf.py @@ -0,0 +1,81 @@ +""" + pygments.lexers.roboconf + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Roboconf DSL. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, words, re +from pygments.token import Text, Operator, Keyword, Name, Comment + +__all__ = ['RoboconfGraphLexer', 'RoboconfInstancesLexer'] + + +class RoboconfGraphLexer(RegexLexer): + """ + Lexer for `Roboconf `_ graph files. + + .. versionadded:: 2.1 + """ + name = 'Roboconf Graph' + aliases = ['roboconf-graph'] + filenames = ['*.graph'] + + flags = re.IGNORECASE | re.MULTILINE + tokens = { + 'root': [ + # Skip white spaces + (r'\s+', Text), + + # There is one operator + (r'=', Operator), + + # Keywords + (words(('facet', 'import'), suffix=r'\s*\b', prefix=r'\b'), Keyword), + (words(( + 'installer', 'extends', 'exports', 'imports', 'facets', + 'children'), suffix=r'\s*:?', prefix=r'\b'), Name), + + # Comments + (r'#.*\n', Comment), + + # Default + (r'[^#]', Text), + (r'.*\n', Text) + ] + } + + +class RoboconfInstancesLexer(RegexLexer): + """ + Lexer for `Roboconf `_ instances files. + + .. versionadded:: 2.1 + """ + name = 'Roboconf Instances' + aliases = ['roboconf-instances'] + filenames = ['*.instances'] + + flags = re.IGNORECASE | re.MULTILINE + tokens = { + 'root': [ + + # Skip white spaces + (r'\s+', Text), + + # Keywords + (words(('instance of', 'import'), suffix=r'\s*\b', prefix=r'\b'), Keyword), + (words(('name', 'count'), suffix=r's*:?', prefix=r'\b'), Name), + (r'\s*[\w.-]+\s*:', Name), + + # Comments + (r'#.*\n', Comment), + + # Default + (r'[^#]', Text), + (r'.*\n', Text) + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/robotframework.py b/.venv/lib/python3.8/site-packages/pygments/lexers/robotframework.py new file mode 100644 index 0000000..3c212f5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/robotframework.py @@ -0,0 +1,551 @@ +""" + pygments.lexers.robotframework + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexer for Robot Framework. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +# Copyright 2012 Nokia Siemens Networks Oyj +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from pygments.lexer import Lexer +from pygments.token import Token + +__all__ = ['RobotFrameworkLexer'] + + +HEADING = Token.Generic.Heading +SETTING = Token.Keyword.Namespace +IMPORT = Token.Name.Namespace +TC_KW_NAME = Token.Generic.Subheading +KEYWORD = Token.Name.Function +ARGUMENT = Token.String +VARIABLE = Token.Name.Variable +COMMENT = Token.Comment +SEPARATOR = Token.Punctuation +SYNTAX = Token.Punctuation +GHERKIN = Token.Generic.Emph +ERROR = Token.Error + + +def normalize(string, remove=''): + string = string.lower() + for char in remove + ' ': + if char in string: + string = string.replace(char, '') + return string + + +class RobotFrameworkLexer(Lexer): + """ + For `Robot Framework `_ test data. + + Supports both space and pipe separated plain text formats. + + .. versionadded:: 1.6 + """ + name = 'RobotFramework' + aliases = ['robotframework'] + filenames = ['*.robot'] + mimetypes = ['text/x-robotframework'] + + def __init__(self, **options): + options['tabsize'] = 2 + options['encoding'] = 'UTF-8' + Lexer.__init__(self, **options) + + def get_tokens_unprocessed(self, text): + row_tokenizer = RowTokenizer() + var_tokenizer = VariableTokenizer() + index = 0 + for row in text.splitlines(): + for value, token in row_tokenizer.tokenize(row): + for value, token in var_tokenizer.tokenize(value, token): + if value: + yield index, token, str(value) + index += len(value) + + +class VariableTokenizer: + + def tokenize(self, string, token): + var = VariableSplitter(string, identifiers='$@%&') + if var.start < 0 or token in (COMMENT, ERROR): + yield string, token + return + for value, token in self._tokenize(var, string, token): + if value: + yield value, token + + def _tokenize(self, var, string, orig_token): + before = string[:var.start] + yield before, orig_token + yield var.identifier + '{', SYNTAX + yield from self.tokenize(var.base, VARIABLE) + yield '}', SYNTAX + if var.index is not None: + yield '[', SYNTAX + yield from self.tokenize(var.index, VARIABLE) + yield ']', SYNTAX + yield from self.tokenize(string[var.end:], orig_token) + + +class RowTokenizer: + + def __init__(self): + self._table = UnknownTable() + self._splitter = RowSplitter() + testcases = TestCaseTable() + settings = SettingTable(testcases.set_default_template) + variables = VariableTable() + keywords = KeywordTable() + self._tables = {'settings': settings, 'setting': settings, + 'metadata': settings, + 'variables': variables, 'variable': variables, + 'testcases': testcases, 'testcase': testcases, + 'tasks': testcases, 'task': testcases, + 'keywords': keywords, 'keyword': keywords, + 'userkeywords': keywords, 'userkeyword': keywords} + + def tokenize(self, row): + commented = False + heading = False + for index, value in enumerate(self._splitter.split(row)): + # First value, and every second after that, is a separator. + index, separator = divmod(index-1, 2) + if value.startswith('#'): + commented = True + elif index == 0 and value.startswith('*'): + self._table = self._start_table(value) + heading = True + yield from self._tokenize(value, index, commented, + separator, heading) + self._table.end_row() + + def _start_table(self, header): + name = normalize(header, remove='*') + return self._tables.get(name, UnknownTable()) + + def _tokenize(self, value, index, commented, separator, heading): + if commented: + yield value, COMMENT + elif separator: + yield value, SEPARATOR + elif heading: + yield value, HEADING + else: + yield from self._table.tokenize(value, index) + + +class RowSplitter: + _space_splitter = re.compile('( {2,})') + _pipe_splitter = re.compile(r'((?:^| +)\|(?: +|$))') + + def split(self, row): + splitter = (row.startswith('| ') and self._split_from_pipes + or self._split_from_spaces) + yield from splitter(row) + yield '\n' + + def _split_from_spaces(self, row): + yield '' # Start with (pseudo)separator similarly as with pipes + yield from self._space_splitter.split(row) + + def _split_from_pipes(self, row): + _, separator, rest = self._pipe_splitter.split(row, 1) + yield separator + while self._pipe_splitter.search(rest): + cell, separator, rest = self._pipe_splitter.split(rest, 1) + yield cell + yield separator + yield rest + + +class Tokenizer: + _tokens = None + + def __init__(self): + self._index = 0 + + def tokenize(self, value): + values_and_tokens = self._tokenize(value, self._index) + self._index += 1 + if isinstance(values_and_tokens, type(Token)): + values_and_tokens = [(value, values_and_tokens)] + return values_and_tokens + + def _tokenize(self, value, index): + index = min(index, len(self._tokens) - 1) + return self._tokens[index] + + def _is_assign(self, value): + if value.endswith('='): + value = value[:-1].strip() + var = VariableSplitter(value, identifiers='$@&') + return var.start == 0 and var.end == len(value) + + +class Comment(Tokenizer): + _tokens = (COMMENT,) + + +class Setting(Tokenizer): + _tokens = (SETTING, ARGUMENT) + _keyword_settings = ('suitesetup', 'suiteprecondition', 'suiteteardown', + 'suitepostcondition', 'testsetup', 'tasksetup', 'testprecondition', + 'testteardown','taskteardown', 'testpostcondition', 'testtemplate', 'tasktemplate') + _import_settings = ('library', 'resource', 'variables') + _other_settings = ('documentation', 'metadata', 'forcetags', 'defaulttags', + 'testtimeout','tasktimeout') + _custom_tokenizer = None + + def __init__(self, template_setter=None): + Tokenizer.__init__(self) + self._template_setter = template_setter + + def _tokenize(self, value, index): + if index == 1 and self._template_setter: + self._template_setter(value) + if index == 0: + normalized = normalize(value) + if normalized in self._keyword_settings: + self._custom_tokenizer = KeywordCall(support_assign=False) + elif normalized in self._import_settings: + self._custom_tokenizer = ImportSetting() + elif normalized not in self._other_settings: + return ERROR + elif self._custom_tokenizer: + return self._custom_tokenizer.tokenize(value) + return Tokenizer._tokenize(self, value, index) + + +class ImportSetting(Tokenizer): + _tokens = (IMPORT, ARGUMENT) + + +class TestCaseSetting(Setting): + _keyword_settings = ('setup', 'precondition', 'teardown', 'postcondition', + 'template') + _import_settings = () + _other_settings = ('documentation', 'tags', 'timeout') + + def _tokenize(self, value, index): + if index == 0: + type = Setting._tokenize(self, value[1:-1], index) + return [('[', SYNTAX), (value[1:-1], type), (']', SYNTAX)] + return Setting._tokenize(self, value, index) + + +class KeywordSetting(TestCaseSetting): + _keyword_settings = ('teardown',) + _other_settings = ('documentation', 'arguments', 'return', 'timeout', 'tags') + + +class Variable(Tokenizer): + _tokens = (SYNTAX, ARGUMENT) + + def _tokenize(self, value, index): + if index == 0 and not self._is_assign(value): + return ERROR + return Tokenizer._tokenize(self, value, index) + + +class KeywordCall(Tokenizer): + _tokens = (KEYWORD, ARGUMENT) + + def __init__(self, support_assign=True): + Tokenizer.__init__(self) + self._keyword_found = not support_assign + self._assigns = 0 + + def _tokenize(self, value, index): + if not self._keyword_found and self._is_assign(value): + self._assigns += 1 + return SYNTAX # VariableTokenizer tokenizes this later. + if self._keyword_found: + return Tokenizer._tokenize(self, value, index - self._assigns) + self._keyword_found = True + return GherkinTokenizer().tokenize(value, KEYWORD) + + +class GherkinTokenizer: + _gherkin_prefix = re.compile('^(Given|When|Then|And) ', re.IGNORECASE) + + def tokenize(self, value, token): + match = self._gherkin_prefix.match(value) + if not match: + return [(value, token)] + end = match.end() + return [(value[:end], GHERKIN), (value[end:], token)] + + +class TemplatedKeywordCall(Tokenizer): + _tokens = (ARGUMENT,) + + +class ForLoop(Tokenizer): + + def __init__(self): + Tokenizer.__init__(self) + self._in_arguments = False + + def _tokenize(self, value, index): + token = self._in_arguments and ARGUMENT or SYNTAX + if value.upper() in ('IN', 'IN RANGE'): + self._in_arguments = True + return token + + +class _Table: + _tokenizer_class = None + + def __init__(self, prev_tokenizer=None): + self._tokenizer = self._tokenizer_class() + self._prev_tokenizer = prev_tokenizer + self._prev_values_on_row = [] + + def tokenize(self, value, index): + if self._continues(value, index): + self._tokenizer = self._prev_tokenizer + yield value, SYNTAX + else: + yield from self._tokenize(value, index) + self._prev_values_on_row.append(value) + + def _continues(self, value, index): + return value == '...' and all(self._is_empty(t) + for t in self._prev_values_on_row) + + def _is_empty(self, value): + return value in ('', '\\') + + def _tokenize(self, value, index): + return self._tokenizer.tokenize(value) + + def end_row(self): + self.__init__(prev_tokenizer=self._tokenizer) + + +class UnknownTable(_Table): + _tokenizer_class = Comment + + def _continues(self, value, index): + return False + + +class VariableTable(_Table): + _tokenizer_class = Variable + + +class SettingTable(_Table): + _tokenizer_class = Setting + + def __init__(self, template_setter, prev_tokenizer=None): + _Table.__init__(self, prev_tokenizer) + self._template_setter = template_setter + + def _tokenize(self, value, index): + if index == 0 and normalize(value) == 'testtemplate': + self._tokenizer = Setting(self._template_setter) + return _Table._tokenize(self, value, index) + + def end_row(self): + self.__init__(self._template_setter, prev_tokenizer=self._tokenizer) + + +class TestCaseTable(_Table): + _setting_class = TestCaseSetting + _test_template = None + _default_template = None + + @property + def _tokenizer_class(self): + if self._test_template or (self._default_template and + self._test_template is not False): + return TemplatedKeywordCall + return KeywordCall + + def _continues(self, value, index): + return index > 0 and _Table._continues(self, value, index) + + def _tokenize(self, value, index): + if index == 0: + if value: + self._test_template = None + return GherkinTokenizer().tokenize(value, TC_KW_NAME) + if index == 1 and self._is_setting(value): + if self._is_template(value): + self._test_template = False + self._tokenizer = self._setting_class(self.set_test_template) + else: + self._tokenizer = self._setting_class() + if index == 1 and self._is_for_loop(value): + self._tokenizer = ForLoop() + if index == 1 and self._is_empty(value): + return [(value, SYNTAX)] + return _Table._tokenize(self, value, index) + + def _is_setting(self, value): + return value.startswith('[') and value.endswith(']') + + def _is_template(self, value): + return normalize(value) == '[template]' + + def _is_for_loop(self, value): + return value.startswith(':') and normalize(value, remove=':') == 'for' + + def set_test_template(self, template): + self._test_template = self._is_template_set(template) + + def set_default_template(self, template): + self._default_template = self._is_template_set(template) + + def _is_template_set(self, template): + return normalize(template) not in ('', '\\', 'none', '${empty}') + + +class KeywordTable(TestCaseTable): + _tokenizer_class = KeywordCall + _setting_class = KeywordSetting + + def _is_template(self, value): + return False + + +# Following code copied directly from Robot Framework 2.7.5. + +class VariableSplitter: + + def __init__(self, string, identifiers): + self.identifier = None + self.base = None + self.index = None + self.start = -1 + self.end = -1 + self._identifiers = identifiers + self._may_have_internal_variables = False + try: + self._split(string) + except ValueError: + pass + else: + self._finalize() + + def get_replaced_base(self, variables): + if self._may_have_internal_variables: + return variables.replace_string(self.base) + return self.base + + def _finalize(self): + self.identifier = self._variable_chars[0] + self.base = ''.join(self._variable_chars[2:-1]) + self.end = self.start + len(self._variable_chars) + if self._has_list_or_dict_variable_index(): + self.index = ''.join(self._list_and_dict_variable_index_chars[1:-1]) + self.end += len(self._list_and_dict_variable_index_chars) + + def _has_list_or_dict_variable_index(self): + return self._list_and_dict_variable_index_chars\ + and self._list_and_dict_variable_index_chars[-1] == ']' + + def _split(self, string): + start_index, max_index = self._find_variable(string) + self.start = start_index + self._open_curly = 1 + self._state = self._variable_state + self._variable_chars = [string[start_index], '{'] + self._list_and_dict_variable_index_chars = [] + self._string = string + start_index += 2 + for index, char in enumerate(string[start_index:]): + index += start_index # Giving start to enumerate only in Py 2.6+ + try: + self._state(char, index) + except StopIteration: + return + if index == max_index and not self._scanning_list_variable_index(): + return + + def _scanning_list_variable_index(self): + return self._state in [self._waiting_list_variable_index_state, + self._list_variable_index_state] + + def _find_variable(self, string): + max_end_index = string.rfind('}') + if max_end_index == -1: + raise ValueError('No variable end found') + if self._is_escaped(string, max_end_index): + return self._find_variable(string[:max_end_index]) + start_index = self._find_start_index(string, 1, max_end_index) + if start_index == -1: + raise ValueError('No variable start found') + return start_index, max_end_index + + def _find_start_index(self, string, start, end): + index = string.find('{', start, end) - 1 + if index < 0: + return -1 + if self._start_index_is_ok(string, index): + return index + return self._find_start_index(string, index+2, end) + + def _start_index_is_ok(self, string, index): + return string[index] in self._identifiers\ + and not self._is_escaped(string, index) + + def _is_escaped(self, string, index): + escaped = False + while index > 0 and string[index-1] == '\\': + index -= 1 + escaped = not escaped + return escaped + + def _variable_state(self, char, index): + self._variable_chars.append(char) + if char == '}' and not self._is_escaped(self._string, index): + self._open_curly -= 1 + if self._open_curly == 0: + if not self._is_list_or_dict_variable(): + raise StopIteration + self._state = self._waiting_list_variable_index_state + elif char in self._identifiers: + self._state = self._internal_variable_start_state + + def _is_list_or_dict_variable(self): + return self._variable_chars[0] in ('@','&') + + def _internal_variable_start_state(self, char, index): + self._state = self._variable_state + if char == '{': + self._variable_chars.append(char) + self._open_curly += 1 + self._may_have_internal_variables = True + else: + self._variable_state(char, index) + + def _waiting_list_variable_index_state(self, char, index): + if char != '[': + raise StopIteration + self._list_and_dict_variable_index_chars.append(char) + self._state = self._list_variable_index_state + + def _list_variable_index_state(self, char, index): + self._list_and_dict_variable_index_chars.append(char) + if char == ']': + raise StopIteration diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/ruby.py b/.venv/lib/python3.8/site-packages/pygments/lexers/ruby.py new file mode 100644 index 0000000..2b2f923 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/ruby.py @@ -0,0 +1,523 @@ +""" + pygments.lexers.ruby + ~~~~~~~~~~~~~~~~~~~~ + + Lexers for Ruby and related languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import Lexer, RegexLexer, ExtendedRegexLexer, include, \ + bygroups, default, LexerContext, do_insertions, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Error, Generic +from pygments.util import shebang_matches + +__all__ = ['RubyLexer', 'RubyConsoleLexer', 'FancyLexer'] + +line_re = re.compile('.*?\n') + + +RUBY_OPERATORS = ( + '*', '**', '-', '+', '-@', '+@', '/', '%', '&', '|', '^', '`', '~', + '[]', '[]=', '<<', '>>', '<', '<>', '<=>', '>', '>=', '==', '===' +) + + +class RubyLexer(ExtendedRegexLexer): + """ + For `Ruby `_ source code. + """ + + name = 'Ruby' + aliases = ['ruby', 'rb', 'duby'] + filenames = ['*.rb', '*.rbw', 'Rakefile', '*.rake', '*.gemspec', + '*.rbx', '*.duby', 'Gemfile', 'Vagrantfile'] + mimetypes = ['text/x-ruby', 'application/x-ruby'] + + flags = re.DOTALL | re.MULTILINE + + def heredoc_callback(self, match, ctx): + # okay, this is the hardest part of parsing Ruby... + # match: 1 = <<[-~]?, 2 = quote? 3 = name 4 = quote? 5 = rest of line + + start = match.start(1) + yield start, Operator, match.group(1) # <<[-~]? + yield match.start(2), String.Heredoc, match.group(2) # quote ", ', ` + yield match.start(3), String.Delimiter, match.group(3) # heredoc name + yield match.start(4), String.Heredoc, match.group(4) # quote again + + heredocstack = ctx.__dict__.setdefault('heredocstack', []) + outermost = not bool(heredocstack) + heredocstack.append((match.group(1) in ('<<-', '<<~'), match.group(3))) + + ctx.pos = match.start(5) + ctx.end = match.end(5) + # this may find other heredocs, so limit the recursion depth + if len(heredocstack) < 100: + yield from self.get_tokens_unprocessed(context=ctx) + else: + yield ctx.pos, String.Heredoc, match.group(5) + ctx.pos = match.end() + + if outermost: + # this is the outer heredoc again, now we can process them all + for tolerant, hdname in heredocstack: + lines = [] + for match in line_re.finditer(ctx.text, ctx.pos): + if tolerant: + check = match.group().strip() + else: + check = match.group().rstrip() + if check == hdname: + for amatch in lines: + yield amatch.start(), String.Heredoc, amatch.group() + yield match.start(), String.Delimiter, match.group() + ctx.pos = match.end() + break + else: + lines.append(match) + else: + # end of heredoc not found -- error! + for amatch in lines: + yield amatch.start(), Error, amatch.group() + ctx.end = len(ctx.text) + del heredocstack[:] + + def gen_rubystrings_rules(): + def intp_regex_callback(self, match, ctx): + yield match.start(1), String.Regex, match.group(1) # begin + nctx = LexerContext(match.group(3), 0, ['interpolated-regex']) + for i, t, v in self.get_tokens_unprocessed(context=nctx): + yield match.start(3)+i, t, v + yield match.start(4), String.Regex, match.group(4) # end[mixounse]* + ctx.pos = match.end() + + def intp_string_callback(self, match, ctx): + yield match.start(1), String.Other, match.group(1) + nctx = LexerContext(match.group(3), 0, ['interpolated-string']) + for i, t, v in self.get_tokens_unprocessed(context=nctx): + yield match.start(3)+i, t, v + yield match.start(4), String.Other, match.group(4) # end + ctx.pos = match.end() + + states = {} + states['strings'] = [ + # easy ones + (r'\:@{0,2}[a-zA-Z_]\w*[!?]?', String.Symbol), + (words(RUBY_OPERATORS, prefix=r'\:@{0,2}'), String.Symbol), + (r":'(\\\\|\\[^\\]|[^'\\])*'", String.Symbol), + (r':"', String.Symbol, 'simple-sym'), + (r'([a-zA-Z_]\w*)(:)(?!:)', + bygroups(String.Symbol, Punctuation)), # Since Ruby 1.9 + (r'"', String.Double, 'simple-string-double'), + (r"'", String.Single, 'simple-string-single'), + (r'(?', '<>', 'ab'): + states[name+'-intp-string'] = [ + (r'\\[\\' + bracecc + ']', String.Other), + (lbrace, String.Other, '#push'), + (rbrace, String.Other, '#pop'), + include('string-intp-escaped'), + (r'[\\#' + bracecc + ']', String.Other), + (r'[^\\#' + bracecc + ']+', String.Other), + ] + states['strings'].append((r'%[QWx]?' + lbrace, String.Other, + name+'-intp-string')) + states[name+'-string'] = [ + (r'\\[\\' + bracecc + ']', String.Other), + (lbrace, String.Other, '#push'), + (rbrace, String.Other, '#pop'), + (r'[\\#' + bracecc + ']', String.Other), + (r'[^\\#' + bracecc + ']+', String.Other), + ] + states['strings'].append((r'%[qsw]' + lbrace, String.Other, + name+'-string')) + states[name+'-regex'] = [ + (r'\\[\\' + bracecc + ']', String.Regex), + (lbrace, String.Regex, '#push'), + (rbrace + '[mixounse]*', String.Regex, '#pop'), + include('string-intp'), + (r'[\\#' + bracecc + ']', String.Regex), + (r'[^\\#' + bracecc + ']+', String.Regex), + ] + states['strings'].append((r'%r' + lbrace, String.Regex, + name+'-regex')) + + # these must come after %! + states['strings'] += [ + # %r regex + (r'(%r([\W_]))((?:\\\2|(?!\2).)*)(\2[mixounse]*)', + intp_regex_callback), + # regular fancy strings with qsw + (r'%[qsw]([\W_])((?:\\\1|(?!\1).)*)\1', String.Other), + (r'(%[QWx]([\W_]))((?:\\\2|(?!\2).)*)(\2)', + intp_string_callback), + # special forms of fancy strings after operators or + # in method calls with braces + (r'(?<=[-+/*%=<>&!^|~,(])(\s*)(%([\t ])(?:(?:\\\3|(?!\3).)*)\3)', + bygroups(Text, String.Other, None)), + # and because of fixed width lookbehinds the whole thing a + # second time for line startings... + (r'^(\s*)(%([\t ])(?:(?:\\\3|(?!\3).)*)\3)', + bygroups(Text, String.Other, None)), + # all regular fancy strings without qsw + (r'(%([^a-zA-Z0-9\s]))((?:\\\2|(?!\2).)*)(\2)', + intp_string_callback), + ] + + return states + + tokens = { + 'root': [ + (r'\A#!.+?$', Comment.Hashbang), + (r'#.*?$', Comment.Single), + (r'=begin\s.*?\n=end.*?$', Comment.Multiline), + # keywords + (words(( + 'BEGIN', 'END', 'alias', 'begin', 'break', 'case', 'defined?', + 'do', 'else', 'elsif', 'end', 'ensure', 'for', 'if', 'in', 'next', 'redo', + 'rescue', 'raise', 'retry', 'return', 'super', 'then', 'undef', + 'unless', 'until', 'when', 'while', 'yield'), suffix=r'\b'), + Keyword), + # start of function, class and module names + (r'(module)(\s+)([a-zA-Z_]\w*' + r'(?:::[a-zA-Z_]\w*)*)', + bygroups(Keyword, Text, Name.Namespace)), + (r'(def)(\s+)', bygroups(Keyword, Text), 'funcname'), + (r'def(?=[*%&^`~+-/\[<>=])', Keyword, 'funcname'), + (r'(class)(\s+)', bygroups(Keyword, Text), 'classname'), + # special methods + (words(( + 'initialize', 'new', 'loop', 'include', 'extend', 'raise', 'attr_reader', + 'attr_writer', 'attr_accessor', 'attr', 'catch', 'throw', 'private', + 'module_function', 'public', 'protected', 'true', 'false', 'nil'), + suffix=r'\b'), + Keyword.Pseudo), + (r'(not|and|or)\b', Operator.Word), + (words(( + 'autoload', 'block_given', 'const_defined', 'eql', 'equal', 'frozen', 'include', + 'instance_of', 'is_a', 'iterator', 'kind_of', 'method_defined', 'nil', + 'private_method_defined', 'protected_method_defined', + 'public_method_defined', 'respond_to', 'tainted'), suffix=r'\?'), + Name.Builtin), + (r'(chomp|chop|exit|gsub|sub)!', Name.Builtin), + (words(( + 'Array', 'Float', 'Integer', 'String', '__id__', '__send__', 'abort', + 'ancestors', 'at_exit', 'autoload', 'binding', 'callcc', 'caller', + 'catch', 'chomp', 'chop', 'class_eval', 'class_variables', + 'clone', 'const_defined?', 'const_get', 'const_missing', 'const_set', + 'constants', 'display', 'dup', 'eval', 'exec', 'exit', 'extend', 'fail', 'fork', + 'format', 'freeze', 'getc', 'gets', 'global_variables', 'gsub', + 'hash', 'id', 'included_modules', 'inspect', 'instance_eval', + 'instance_method', 'instance_methods', + 'instance_variable_get', 'instance_variable_set', 'instance_variables', + 'lambda', 'load', 'local_variables', 'loop', + 'method', 'method_missing', 'methods', 'module_eval', 'name', + 'object_id', 'open', 'p', 'print', 'printf', 'private_class_method', + 'private_instance_methods', + 'private_methods', 'proc', 'protected_instance_methods', + 'protected_methods', 'public_class_method', + 'public_instance_methods', 'public_methods', + 'putc', 'puts', 'raise', 'rand', 'readline', 'readlines', 'require', + 'scan', 'select', 'self', 'send', 'set_trace_func', 'singleton_methods', 'sleep', + 'split', 'sprintf', 'srand', 'sub', 'syscall', 'system', 'taint', + 'test', 'throw', 'to_a', 'to_s', 'trace_var', 'trap', 'untaint', + 'untrace_var', 'warn'), prefix=r'(?~!:])|' + r'(?<=(?:\s|;)when\s)|' + r'(?<=(?:\s|;)or\s)|' + r'(?<=(?:\s|;)and\s)|' + r'(?<=\.index\s)|' + r'(?<=\.scan\s)|' + r'(?<=\.sub\s)|' + r'(?<=\.sub!\s)|' + r'(?<=\.gsub\s)|' + r'(?<=\.gsub!\s)|' + r'(?<=\.match\s)|' + r'(?<=(?:\s|;)if\s)|' + r'(?<=(?:\s|;)elsif\s)|' + r'(?<=^when\s)|' + r'(?<=^index\s)|' + r'(?<=^scan\s)|' + r'(?<=^sub\s)|' + r'(?<=^gsub\s)|' + r'(?<=^sub!\s)|' + r'(?<=^gsub!\s)|' + r'(?<=^match\s)|' + r'(?<=^if\s)|' + r'(?<=^elsif\s)' + r')(\s*)(/)', bygroups(Text, String.Regex), 'multiline-regex'), + # multiline regex (in method calls or subscripts) + (r'(?<=\(|,|\[)/', String.Regex, 'multiline-regex'), + # multiline regex (this time the funny no whitespace rule) + (r'(\s+)(/)(?![\s=])', bygroups(Text, String.Regex), + 'multiline-regex'), + # lex numbers and ignore following regular expressions which + # are division operators in fact (grrrr. i hate that. any + # better ideas?) + # since pygments 0.7 we also eat a "?" operator after numbers + # so that the char operator does not work. Chars are not allowed + # there so that you can use the ternary operator. + # stupid example: + # x>=0?n[x]:"" + (r'(0_?[0-7]+(?:_[0-7]+)*)(\s*)([/?])?', + bygroups(Number.Oct, Text, Operator)), + (r'(0x[0-9A-Fa-f]+(?:_[0-9A-Fa-f]+)*)(\s*)([/?])?', + bygroups(Number.Hex, Text, Operator)), + (r'(0b[01]+(?:_[01]+)*)(\s*)([/?])?', + bygroups(Number.Bin, Text, Operator)), + (r'([\d]+(?:_\d+)*)(\s*)([/?])?', + bygroups(Number.Integer, Text, Operator)), + # Names + (r'@@[a-zA-Z_]\w*', Name.Variable.Class), + (r'@[a-zA-Z_]\w*', Name.Variable.Instance), + (r'\$\w+', Name.Variable.Global), + (r'\$[!@&`\'+~=/\\,;.<>_*$?:"^-]', Name.Variable.Global), + (r'\$-[0adFiIlpvw]', Name.Variable.Global), + (r'::', Operator), + include('strings'), + # chars + (r'\?(\\[MC]-)*' # modifiers + r'(\\([\\abefnrstv#"\']|x[a-fA-F0-9]{1,2}|[0-7]{1,3})|\S)' + r'(?!\w)', + String.Char), + (r'[A-Z]\w+', Name.Constant), + # this is needed because ruby attributes can look + # like keywords (class) or like this: ` ?!? + (words(RUBY_OPERATORS, prefix=r'(\.|::)'), + bygroups(Operator, Name.Operator)), + (r'(\.|::)([a-zA-Z_]\w*[!?]?|[*%&^`~+\-/\[<>=])', + bygroups(Operator, Name)), + (r'[a-zA-Z_]\w*[!?]?', Name), + (r'(\[|\]|\*\*|<>?|>=|<=|<=>|=~|={3}|' + r'!~|&&?|\|\||\.{1,3})', Operator), + (r'[-+/*%=<>&!^|~]=?', Operator), + (r'[(){};,/?:\\]', Punctuation), + (r'\s+', Text) + ], + 'funcname': [ + (r'\(', Punctuation, 'defexpr'), + (r'(?:([a-zA-Z_]\w*)(\.))?' # optional scope name, like "self." + r'(' + r'[a-zA-Z\u0080-\uffff][a-zA-Z0-9_\u0080-\uffff]*[!?=]?' # method name + r'|!=|!~|=~|\*\*?|[-+!~]@?|[/%&|^]|<=>|<[<=]?|>[>=]?|===?' # or operator override + r'|\[\]=?' # or element reference/assignment override + r'|`' # or the undocumented backtick override + r')', + bygroups(Name.Class, Operator, Name.Function), '#pop'), + default('#pop') + ], + 'classname': [ + (r'\(', Punctuation, 'defexpr'), + (r'<<', Operator, '#pop'), + (r'[A-Z_]\w*', Name.Class, '#pop'), + default('#pop') + ], + 'defexpr': [ + (r'(\))(\.|::)?', bygroups(Punctuation, Operator), '#pop'), + (r'\(', Operator, '#push'), + include('root') + ], + 'in-intp': [ + (r'\{', String.Interpol, '#push'), + (r'\}', String.Interpol, '#pop'), + include('root'), + ], + 'string-intp': [ + (r'#\{', String.Interpol, 'in-intp'), + (r'#@@?[a-zA-Z_]\w*', String.Interpol), + (r'#\$[a-zA-Z_]\w*', String.Interpol) + ], + 'string-intp-escaped': [ + include('string-intp'), + (r'\\([\\abefnrstv#"\']|x[a-fA-F0-9]{1,2}|[0-7]{1,3})', + String.Escape) + ], + 'interpolated-regex': [ + include('string-intp'), + (r'[\\#]', String.Regex), + (r'[^\\#]+', String.Regex), + ], + 'interpolated-string': [ + include('string-intp'), + (r'[\\#]', String.Other), + (r'[^\\#]+', String.Other), + ], + 'multiline-regex': [ + include('string-intp'), + (r'\\\\', String.Regex), + (r'\\/', String.Regex), + (r'[\\#]', String.Regex), + (r'[^\\/#]+', String.Regex), + (r'/[mixounse]*', String.Regex, '#pop'), + ], + 'end-part': [ + (r'.+', Comment.Preproc, '#pop') + ] + } + tokens.update(gen_rubystrings_rules()) + + def analyse_text(text): + return shebang_matches(text, r'ruby(1\.\d)?') + + +class RubyConsoleLexer(Lexer): + """ + For Ruby interactive console (**irb**) output like: + + .. sourcecode:: rbcon + + irb(main):001:0> a = 1 + => 1 + irb(main):002:0> puts a + 1 + => nil + """ + name = 'Ruby irb session' + aliases = ['rbcon', 'irb'] + mimetypes = ['text/x-ruby-shellsession'] + + _prompt_re = re.compile(r'irb\([a-zA-Z_]\w*\):\d{3}:\d+[>*"\'] ' + r'|>> |\?> ') + + def get_tokens_unprocessed(self, text): + rblexer = RubyLexer(**self.options) + + curcode = '' + insertions = [] + for match in line_re.finditer(text): + line = match.group() + m = self._prompt_re.match(line) + if m is not None: + end = m.end() + insertions.append((len(curcode), + [(0, Generic.Prompt, line[:end])])) + curcode += line[end:] + else: + if curcode: + yield from do_insertions( + insertions, rblexer.get_tokens_unprocessed(curcode)) + curcode = '' + insertions = [] + yield match.start(), Generic.Output, line + if curcode: + yield from do_insertions( + insertions, rblexer.get_tokens_unprocessed(curcode)) + + +class FancyLexer(RegexLexer): + """ + Pygments Lexer For `Fancy `_. + + Fancy is a self-hosted, pure object-oriented, dynamic, + class-based, concurrent general-purpose programming language + running on Rubinius, the Ruby VM. + + .. versionadded:: 1.5 + """ + name = 'Fancy' + filenames = ['*.fy', '*.fancypack'] + aliases = ['fancy', 'fy'] + mimetypes = ['text/x-fancysrc'] + + tokens = { + # copied from PerlLexer: + 'balanced-regex': [ + (r'/(\\\\|\\[^\\]|[^/\\])*/[egimosx]*', String.Regex, '#pop'), + (r'!(\\\\|\\[^\\]|[^!\\])*![egimosx]*', String.Regex, '#pop'), + (r'\\(\\\\|[^\\])*\\[egimosx]*', String.Regex, '#pop'), + (r'\{(\\\\|\\[^\\]|[^}\\])*\}[egimosx]*', String.Regex, '#pop'), + (r'<(\\\\|\\[^\\]|[^>\\])*>[egimosx]*', String.Regex, '#pop'), + (r'\[(\\\\|\\[^\\]|[^\]\\])*\][egimosx]*', String.Regex, '#pop'), + (r'\((\\\\|\\[^\\]|[^)\\])*\)[egimosx]*', String.Regex, '#pop'), + (r'@(\\\\|\\[^\\]|[^@\\])*@[egimosx]*', String.Regex, '#pop'), + (r'%(\\\\|\\[^\\]|[^%\\])*%[egimosx]*', String.Regex, '#pop'), + (r'\$(\\\\|\\[^\\]|[^$\\])*\$[egimosx]*', String.Regex, '#pop'), + ], + 'root': [ + (r'\s+', Text), + + # balanced delimiters (copied from PerlLexer): + (r's\{(\\\\|\\[^\\]|[^}\\])*\}\s*', String.Regex, 'balanced-regex'), + (r's<(\\\\|\\[^\\]|[^>\\])*>\s*', String.Regex, 'balanced-regex'), + (r's\[(\\\\|\\[^\\]|[^\]\\])*\]\s*', String.Regex, 'balanced-regex'), + (r's\((\\\\|\\[^\\]|[^)\\])*\)\s*', String.Regex, 'balanced-regex'), + (r'm?/(\\\\|\\[^\\]|[^///\n])*/[gcimosx]*', String.Regex), + (r'm(?=[/!\\{<\[(@%$])', String.Regex, 'balanced-regex'), + + # Comments + (r'#(.*?)\n', Comment.Single), + # Symbols + (r'\'([^\'\s\[\](){}]+|\[\])', String.Symbol), + # Multi-line DoubleQuotedString + (r'"""(\\\\|\\[^\\]|[^\\])*?"""', String), + # DoubleQuotedString + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + # keywords + (r'(def|class|try|catch|finally|retry|return|return_local|match|' + r'case|->|=>)\b', Keyword), + # constants + (r'(self|super|nil|false|true)\b', Name.Constant), + (r'[(){};,/?|:\\]', Punctuation), + # names + (words(( + 'Object', 'Array', 'Hash', 'Directory', 'File', 'Class', 'String', + 'Number', 'Enumerable', 'FancyEnumerable', 'Block', 'TrueClass', + 'NilClass', 'FalseClass', 'Tuple', 'Symbol', 'Stack', 'Set', + 'FancySpec', 'Method', 'Package', 'Range'), suffix=r'\b'), + Name.Builtin), + # functions + (r'[a-zA-Z](\w|[-+?!=*/^><%])*:', Name.Function), + # operators, must be below functions + (r'[-+*/~,<>=&!?%^\[\].$]+', Operator), + (r'[A-Z]\w*', Name.Constant), + (r'@[a-zA-Z_]\w*', Name.Variable.Instance), + (r'@@[a-zA-Z_]\w*', Name.Variable.Class), + ('@@?', Operator), + (r'[a-zA-Z_]\w*', Name), + # numbers - / checks are necessary to avoid mismarking regexes, + # see comment in RubyLexer + (r'(0[oO]?[0-7]+(?:_[0-7]+)*)(\s*)([/?])?', + bygroups(Number.Oct, Text, Operator)), + (r'(0[xX][0-9A-Fa-f]+(?:_[0-9A-Fa-f]+)*)(\s*)([/?])?', + bygroups(Number.Hex, Text, Operator)), + (r'(0[bB][01]+(?:_[01]+)*)(\s*)([/?])?', + bygroups(Number.Bin, Text, Operator)), + (r'([\d]+(?:_\d+)*)(\s*)([/?])?', + bygroups(Number.Integer, Text, Operator)), + (r'\d+([eE][+-]?[0-9]+)|\d+\.\d+([eE][+-]?[0-9]+)?', Number.Float), + (r'\d+', Number.Integer) + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/rust.py b/.venv/lib/python3.8/site-packages/pygments/lexers/rust.py new file mode 100644 index 0000000..d01f73e --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/rust.py @@ -0,0 +1,222 @@ +""" + pygments.lexers.rust + ~~~~~~~~~~~~~~~~~~~~ + + Lexers for the Rust language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, include, bygroups, words, default +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Whitespace + +__all__ = ['RustLexer'] + + +class RustLexer(RegexLexer): + """ + Lexer for the Rust programming language (version 1.47). + + .. versionadded:: 1.6 + """ + name = 'Rust' + filenames = ['*.rs', '*.rs.in'] + aliases = ['rust', 'rs'] + mimetypes = ['text/rust', 'text/x-rust'] + + keyword_types = (words(( + 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', + 'usize', 'isize', 'f32', 'f64', 'char', 'str', 'bool', + ), suffix=r'\b'), Keyword.Type) + + builtin_funcs_types = (words(( + 'Copy', 'Send', 'Sized', 'Sync', 'Unpin', + 'Drop', 'Fn', 'FnMut', 'FnOnce', 'drop', + 'Box', 'ToOwned', 'Clone', + 'PartialEq', 'PartialOrd', 'Eq', 'Ord', + 'AsRef', 'AsMut', 'Into', 'From', 'Default', + 'Iterator', 'Extend', 'IntoIterator', 'DoubleEndedIterator', + 'ExactSizeIterator', + 'Option', 'Some', 'None', + 'Result', 'Ok', 'Err', + 'String', 'ToString', 'Vec', + ), suffix=r'\b'), Name.Builtin) + + builtin_macros = (words(( + 'asm', 'assert', 'assert_eq', 'assert_ne', 'cfg', 'column', + 'compile_error', 'concat', 'concat_idents', 'dbg', 'debug_assert', + 'debug_assert_eq', 'debug_assert_ne', 'env', 'eprint', 'eprintln', + 'file', 'format', 'format_args', 'format_args_nl', 'global_asm', + 'include', 'include_bytes', 'include_str', + 'is_aarch64_feature_detected', + 'is_arm_feature_detected', + 'is_mips64_feature_detected', + 'is_mips_feature_detected', + 'is_powerpc64_feature_detected', + 'is_powerpc_feature_detected', + 'is_x86_feature_detected', + 'line', 'llvm_asm', 'log_syntax', 'macro_rules', 'matches', + 'module_path', 'option_env', 'panic', 'print', 'println', 'stringify', + 'thread_local', 'todo', 'trace_macros', 'unimplemented', 'unreachable', + 'vec', 'write', 'writeln', + ), suffix=r'!'), Name.Function.Magic) + + tokens = { + 'root': [ + # rust allows a file to start with a shebang, but if the first line + # starts with #![ then it's not a shebang but a crate attribute. + (r'#![^[\r\n].*$', Comment.Preproc), + default('base'), + ], + 'base': [ + # Whitespace and Comments + (r'\n', Whitespace), + (r'\s+', Whitespace), + (r'//!.*?\n', String.Doc), + (r'///(\n|[^/].*?\n)', String.Doc), + (r'//(.*?)\n', Comment.Single), + (r'/\*\*(\n|[^/*])', String.Doc, 'doccomment'), + (r'/\*!', String.Doc, 'doccomment'), + (r'/\*', Comment.Multiline, 'comment'), + + # Macro parameters + (r"""\$([a-zA-Z_]\w*|\(,?|\),?|,?)""", Comment.Preproc), + # Keywords + (words(('as', 'async', 'await', 'box', 'const', 'crate', 'dyn', + 'else', 'extern', 'for', 'if', 'impl', 'in', 'loop', + 'match', 'move', 'mut', 'pub', 'ref', 'return', 'static', + 'super', 'trait', 'unsafe', 'use', 'where', 'while'), + suffix=r'\b'), Keyword), + (words(('abstract', 'become', 'do', 'final', 'macro', 'override', + 'priv', 'typeof', 'try', 'unsized', 'virtual', 'yield'), + suffix=r'\b'), Keyword.Reserved), + (r'(true|false)\b', Keyword.Constant), + (r'self\b', Name.Builtin.Pseudo), + (r'mod\b', Keyword, 'modname'), + (r'let\b', Keyword.Declaration), + (r'fn\b', Keyword, 'funcname'), + (r'(struct|enum|type|union)\b', Keyword, 'typename'), + (r'(default)(\s+)(type|fn)\b', bygroups(Keyword, Text, Keyword)), + keyword_types, + (r'[sS]elf\b', Name.Builtin.Pseudo), + # Prelude (taken from Rust's src/libstd/prelude.rs) + builtin_funcs_types, + builtin_macros, + # Path seperators, so types don't catch them. + (r'::\b', Text), + # Types in positions. + (r'(?::|->)', Text, 'typename'), + # Labels + (r'(break|continue)(\b\s*)(\'[A-Za-z_]\w*)?', + bygroups(Keyword, Text.Whitespace, Name.Label)), + + # Character literals + (r"""'(\\['"\\nrt]|\\x[0-7][0-9a-fA-F]|\\0""" + r"""|\\u\{[0-9a-fA-F]{1,6}\}|.)'""", + String.Char), + (r"""b'(\\['"\\nrt]|\\x[0-9a-fA-F]{2}|\\0""" + r"""|\\u\{[0-9a-fA-F]{1,6}\}|.)'""", + String.Char), + + # Binary literals + (r'0b[01_]+', Number.Bin, 'number_lit'), + # Octal literals + (r'0o[0-7_]+', Number.Oct, 'number_lit'), + # Hexadecimal literals + (r'0[xX][0-9a-fA-F_]+', Number.Hex, 'number_lit'), + # Decimal literals + (r'[0-9][0-9_]*(\.[0-9_]+[eE][+\-]?[0-9_]+|' + r'\.[0-9_]*(?!\.)|[eE][+\-]?[0-9_]+)', Number.Float, + 'number_lit'), + (r'[0-9][0-9_]*', Number.Integer, 'number_lit'), + + # String literals + (r'b"', String, 'bytestring'), + (r'"', String, 'string'), + (r'(?s)b?r(#*)".*?"\1', String), + + # Lifetime names + (r"'", Operator, 'lifetime'), + + # Operators and Punctuation + (r'\.\.=?', Operator), + (r'[{}()\[\],.;]', Punctuation), + (r'[+\-*/%&|<>^!~@=:?]', Operator), + + # Identifiers + (r'[a-zA-Z_]\w*', Name), + # Raw identifiers + (r'r#[a-zA-Z_]\w*', Name), + + # Attributes + (r'#!?\[', Comment.Preproc, 'attribute['), + + # Misc + # Lone hashes: not used in Rust syntax, but allowed in macro + # arguments, most famously for quote::quote!() + (r'#', Text), + ], + 'comment': [ + (r'[^*/]+', Comment.Multiline), + (r'/\*', Comment.Multiline, '#push'), + (r'\*/', Comment.Multiline, '#pop'), + (r'[*/]', Comment.Multiline), + ], + 'doccomment': [ + (r'[^*/]+', String.Doc), + (r'/\*', String.Doc, '#push'), + (r'\*/', String.Doc, '#pop'), + (r'[*/]', String.Doc), + ], + 'modname': [ + (r'\s+', Text), + (r'[a-zA-Z_]\w*', Name.Namespace, '#pop'), + default('#pop'), + ], + 'funcname': [ + (r'\s+', Text), + (r'[a-zA-Z_]\w*', Name.Function, '#pop'), + default('#pop'), + ], + 'typename': [ + (r'\s+', Text), + (r'&', Keyword.Pseudo), + (r"'", Operator, 'lifetime'), + builtin_funcs_types, + keyword_types, + (r'[a-zA-Z_]\w*', Name.Class, '#pop'), + default('#pop'), + ], + 'lifetime': [ + (r"(static|_)", Name.Builtin), + (r"[a-zA-Z_]+\w*", Name.Attribute), + default('#pop'), + ], + 'number_lit': [ + (r'[ui](8|16|32|64|size)', Keyword, '#pop'), + (r'f(32|64)', Keyword, '#pop'), + default('#pop'), + ], + 'string': [ + (r'"', String, '#pop'), + (r"""\\['"\\nrt]|\\x[0-7][0-9a-fA-F]|\\0""" + r"""|\\u\{[0-9a-fA-F]{1,6}\}""", String.Escape), + (r'[^\\"]+', String), + (r'\\', String), + ], + 'bytestring': [ + (r"""\\x[89a-fA-F][0-9a-fA-F]""", String.Escape), + include('string'), + ], + 'attribute_common': [ + (r'"', String, 'string'), + (r'\[', Comment.Preproc, 'attribute['), + ], + 'attribute[': [ + include('attribute_common'), + (r'\]', Comment.Preproc, '#pop'), + (r'[^"\]\[]+', Comment.Preproc), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/sas.py b/.venv/lib/python3.8/site-packages/pygments/lexers/sas.py new file mode 100644 index 0000000..7d7f9d3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/sas.py @@ -0,0 +1,227 @@ +""" + pygments.lexers.sas + ~~~~~~~~~~~~~~~~~~~ + + Lexer for SAS. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +from pygments.lexer import RegexLexer, include, words +from pygments.token import Comment, Keyword, Name, Number, String, Text, \ + Other, Generic + +__all__ = ['SASLexer'] + + +class SASLexer(RegexLexer): + """ + For `SAS `_ files. + + .. versionadded:: 2.2 + """ + # Syntax from syntax/sas.vim by James Kidd + + name = 'SAS' + aliases = ['sas'] + filenames = ['*.SAS', '*.sas'] + mimetypes = ['text/x-sas', 'text/sas', 'application/x-sas'] + flags = re.IGNORECASE | re.MULTILINE + + builtins_macros = ( + "bquote", "nrbquote", "cmpres", "qcmpres", "compstor", "datatyp", + "display", "do", "else", "end", "eval", "global", "goto", "if", + "index", "input", "keydef", "label", "left", "length", "let", + "local", "lowcase", "macro", "mend", "nrquote", + "nrstr", "put", "qleft", "qlowcase", "qscan", + "qsubstr", "qsysfunc", "qtrim", "quote", "qupcase", "scan", + "str", "substr", "superq", "syscall", "sysevalf", "sysexec", + "sysfunc", "sysget", "syslput", "sysprod", "sysrc", "sysrput", + "then", "to", "trim", "unquote", "until", "upcase", "verify", + "while", "window" + ) + + builtins_conditionals = ( + "do", "if", "then", "else", "end", "until", "while" + ) + + builtins_statements = ( + "abort", "array", "attrib", "by", "call", "cards", "cards4", + "catname", "continue", "datalines", "datalines4", "delete", "delim", + "delimiter", "display", "dm", "drop", "endsas", "error", "file", + "filename", "footnote", "format", "goto", "in", "infile", "informat", + "input", "keep", "label", "leave", "length", "libname", "link", + "list", "lostcard", "merge", "missing", "modify", "options", "output", + "out", "page", "put", "redirect", "remove", "rename", "replace", + "retain", "return", "select", "set", "skip", "startsas", "stop", + "title", "update", "waitsas", "where", "window", "x", "systask" + ) + + builtins_sql = ( + "add", "and", "alter", "as", "cascade", "check", "create", + "delete", "describe", "distinct", "drop", "foreign", "from", + "group", "having", "index", "insert", "into", "in", "key", "like", + "message", "modify", "msgtype", "not", "null", "on", "or", + "order", "primary", "references", "reset", "restrict", "select", + "set", "table", "unique", "update", "validate", "view", "where" + ) + + builtins_functions = ( + "abs", "addr", "airy", "arcos", "arsin", "atan", "attrc", + "attrn", "band", "betainv", "blshift", "bnot", "bor", + "brshift", "bxor", "byte", "cdf", "ceil", "cexist", "cinv", + "close", "cnonct", "collate", "compbl", "compound", + "compress", "cos", "cosh", "css", "curobs", "cv", "daccdb", + "daccdbsl", "daccsl", "daccsyd", "dacctab", "dairy", "date", + "datejul", "datepart", "datetime", "day", "dclose", "depdb", + "depdbsl", "depsl", "depsyd", + "deptab", "dequote", "dhms", "dif", "digamma", + "dim", "dinfo", "dnum", "dopen", "doptname", "doptnum", + "dread", "dropnote", "dsname", "erf", "erfc", "exist", "exp", + "fappend", "fclose", "fcol", "fdelete", "fetch", "fetchobs", + "fexist", "fget", "fileexist", "filename", "fileref", + "finfo", "finv", "fipname", "fipnamel", "fipstate", "floor", + "fnonct", "fnote", "fopen", "foptname", "foptnum", "fpoint", + "fpos", "fput", "fread", "frewind", "frlen", "fsep", "fuzz", + "fwrite", "gaminv", "gamma", "getoption", "getvarc", "getvarn", + "hbound", "hms", "hosthelp", "hour", "ibessel", "index", + "indexc", "indexw", "input", "inputc", "inputn", "int", + "intck", "intnx", "intrr", "irr", "jbessel", "juldate", + "kurtosis", "lag", "lbound", "left", "length", "lgamma", + "libname", "libref", "log", "log10", "log2", "logpdf", "logpmf", + "logsdf", "lowcase", "max", "mdy", "mean", "min", "minute", + "mod", "month", "mopen", "mort", "n", "netpv", "nmiss", + "normal", "note", "npv", "open", "ordinal", "pathname", + "pdf", "peek", "peekc", "pmf", "point", "poisson", "poke", + "probbeta", "probbnml", "probchi", "probf", "probgam", + "probhypr", "probit", "probnegb", "probnorm", "probt", + "put", "putc", "putn", "qtr", "quote", "ranbin", "rancau", + "ranexp", "rangam", "range", "rank", "rannor", "ranpoi", + "rantbl", "rantri", "ranuni", "repeat", "resolve", "reverse", + "rewind", "right", "round", "saving", "scan", "sdf", "second", + "sign", "sin", "sinh", "skewness", "soundex", "spedis", + "sqrt", "std", "stderr", "stfips", "stname", "stnamel", + "substr", "sum", "symget", "sysget", "sysmsg", "sysprod", + "sysrc", "system", "tan", "tanh", "time", "timepart", "tinv", + "tnonct", "today", "translate", "tranwrd", "trigamma", + "trim", "trimn", "trunc", "uniform", "upcase", "uss", "var", + "varfmt", "varinfmt", "varlabel", "varlen", "varname", + "varnum", "varray", "varrayx", "vartype", "verify", "vformat", + "vformatd", "vformatdx", "vformatn", "vformatnx", "vformatw", + "vformatwx", "vformatx", "vinarray", "vinarrayx", "vinformat", + "vinformatd", "vinformatdx", "vinformatn", "vinformatnx", + "vinformatw", "vinformatwx", "vinformatx", "vlabel", + "vlabelx", "vlength", "vlengthx", "vname", "vnamex", "vtype", + "vtypex", "weekday", "year", "yyq", "zipfips", "zipname", + "zipnamel", "zipstate" + ) + + tokens = { + 'root': [ + include('comments'), + include('proc-data'), + include('cards-datalines'), + include('logs'), + include('general'), + (r'.', Text), + ], + # SAS is multi-line regardless, but * is ended by ; + 'comments': [ + (r'^\s*\*.*?;', Comment), + (r'/\*.*?\*/', Comment), + (r'^\s*\*(.|\n)*?;', Comment.Multiline), + (r'/[*](.|\n)*?[*]/', Comment.Multiline), + ], + # Special highlight for proc, data, quit, run + 'proc-data': [ + (r'(^|;)\s*(proc \w+|data|run|quit)[\s;]', + Keyword.Reserved), + ], + # Special highlight cards and datalines + 'cards-datalines': [ + (r'^\s*(datalines|cards)\s*;\s*$', Keyword, 'data'), + ], + 'data': [ + (r'(.|\n)*^\s*;\s*$', Other, '#pop'), + ], + # Special highlight for put NOTE|ERROR|WARNING (order matters) + 'logs': [ + (r'\n?^\s*%?put ', Keyword, 'log-messages'), + ], + 'log-messages': [ + (r'NOTE(:|-).*', Generic, '#pop'), + (r'WARNING(:|-).*', Generic.Emph, '#pop'), + (r'ERROR(:|-).*', Generic.Error, '#pop'), + include('general'), + ], + 'general': [ + include('keywords'), + include('vars-strings'), + include('special'), + include('numbers'), + ], + # Keywords, statements, functions, macros + 'keywords': [ + (words(builtins_statements, + prefix = r'\b', + suffix = r'\b'), + Keyword), + (words(builtins_sql, + prefix = r'\b', + suffix = r'\b'), + Keyword), + (words(builtins_conditionals, + prefix = r'\b', + suffix = r'\b'), + Keyword), + (words(builtins_macros, + prefix = r'%', + suffix = r'\b'), + Name.Builtin), + (words(builtins_functions, + prefix = r'\b', + suffix = r'\('), + Name.Builtin), + ], + # Strings and user-defined variables and macros (order matters) + 'vars-strings': [ + (r'&[a-z_]\w{0,31}\.?', Name.Variable), + (r'%[a-z_]\w{0,31}', Name.Function), + (r'\'', String, 'string_squote'), + (r'"', String, 'string_dquote'), + ], + 'string_squote': [ + ('\'', String, '#pop'), + (r'\\\\|\\"|\\\n', String.Escape), + # AFAIK, macro variables are not evaluated in single quotes + # (r'&', Name.Variable, 'validvar'), + (r'[^$\'\\]+', String), + (r'[$\'\\]', String), + ], + 'string_dquote': [ + (r'"', String, '#pop'), + (r'\\\\|\\"|\\\n', String.Escape), + (r'&', Name.Variable, 'validvar'), + (r'[^$&"\\]+', String), + (r'[$"\\]', String), + ], + 'validvar': [ + (r'[a-z_]\w{0,31}\.?', Name.Variable, '#pop'), + ], + # SAS numbers and special variables + 'numbers': [ + (r'\b[+-]?([0-9]+(\.[0-9]+)?|\.[0-9]+|\.)(E[+-]?[0-9]+)?i?\b', + Number), + ], + 'special': [ + (r'(null|missing|_all_|_automatic_|_character_|_n_|' + r'_infile_|_name_|_null_|_numeric_|_user_|_webout_)', + Keyword.Constant), + ], + # 'operators': [ + # (r'(-|=|<=|>=|<|>|<>|&|!=|' + # r'\||\*|\+|\^|/|!|~|~=)', Operator) + # ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/savi.py b/.venv/lib/python3.8/site-packages/pygments/lexers/savi.py new file mode 100644 index 0000000..88fddab --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/savi.py @@ -0,0 +1,159 @@ +""" + pygments.lexers.savi + ~~~~~~~~~~~~~~~~~~~~ + + Lexer for Savi. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, bygroups, include +from pygments.token import \ + Whitespace, Keyword, Name, String, Number, \ + Operator, Punctuation, Comment, Generic, Error + +__all__ = ['SaviLexer'] + +# The canonical version of this file can be found in the following repository, +# where it is kept in sync with any language changes, as well as the other +# pygments-like lexers that are maintained for use with other tools: +# - https://github.com/savi-lang/savi/blob/main/tooling/pygments/lexers/savi.py +# +# If you're changing this file in the pygments repository, please ensure that +# any changes you make are also propagated to the official Savi repository, +# in order to avoid accidental clobbering of your changes later when an update +# from the Savi repository flows forward into the pygments repository. +# +# If you're changing this file in the Savi repository, please ensure that +# any changes you make are also reflected in the other pygments-like lexers +# (rouge, vscode, etc) so that all of the lexers can be kept cleanly in sync. + +class SaviLexer(RegexLexer): + """ + For `Savi `_ source code. + + .. versionadded: 2.10 + """ + + name = 'Savi' + aliases = ['savi'] + filenames = ['*.savi'] + + tokens = { + "root": [ + # Line Comment + (r'//.*?$', Comment.Single), + + # Doc Comment + (r'::.*?$', Comment.Single), + + # Capability Operator + (r'(\')(\w+)(?=[^\'])', bygroups(Operator, Name)), + + # Double-Quote String + (r'\w?"', String.Double, "string.double"), + + # Single-Char String + (r"'", String.Char, "string.char"), + + # Class (or other type) + (r'([_A-Z]\w*)', Name.Class), + + # Declare + (r'^([ \t]*)(:\w+)', + bygroups(Whitespace, Name.Tag), + "decl"), + + # Error-Raising Calls/Names + (r'((\w+|\+|\-|\*)\!)', Generic.Deleted), + + # Numeric Values + (r'\b\d([\d_]*(\.[\d_]+)?)\b', Number), + + # Hex Numeric Values + (r'\b0x([0-9a-fA-F_]+)\b', Number.Hex), + + # Binary Numeric Values + (r'\b0b([01_]+)\b', Number.Bin), + + # Function Call (with braces) + (r'\w+(?=\()', Name.Function), + + # Function Call (with receiver) + (r'(\.)(\s*)(\w+)', bygroups(Punctuation, Whitespace, Name.Function)), + + # Function Call (with self receiver) + (r'(@)(\w+)', bygroups(Punctuation, Name.Function)), + + # Parenthesis + (r'\(', Punctuation, "root"), + (r'\)', Punctuation, "#pop"), + + # Brace + (r'\{', Punctuation, "root"), + (r'\}', Punctuation, "#pop"), + + # Bracket + (r'\[', Punctuation, "root"), + (r'(\])(\!)', bygroups(Punctuation, Generic.Deleted), "#pop"), + (r'\]', Punctuation, "#pop"), + + # Punctuation + (r'[,;:\.@]', Punctuation), + + # Piping Operators + (r'(\|\>)', Operator), + + # Branching Operators + (r'(\&\&|\|\||\?\?|\&\?|\|\?|\.\?)', Operator), + + # Comparison Operators + (r'(\<\=\>|\=\~|\=\=|\<\=|\>\=|\<|\>)', Operator), + + # Arithmetic Operators + (r'(\+|\-|\/|\*|\%)', Operator), + + # Assignment Operators + (r'(\=)', Operator), + + # Other Operators + (r'(\!|\<\<|\<|\&|\|)', Operator), + + # Identifiers + (r'\b\w+\b', Name), + + # Whitespace + (r'[ \t\r]+\n*|\n+', Whitespace), + ], + + # Declare (nested rules) + "decl": [ + (r'\b[a-z_]\w*\b(?!\!)', Keyword.Declaration), + (r':', Punctuation, "#pop"), + (r'\n', Whitespace, "#pop"), + include("root"), + ], + + # Double-Quote String (nested rules) + "string.double": [ + (r'\\u[0-9a-fA-F]{4}', String.Escape), + (r'\\x[0-9a-fA-F]{2}', String.Escape), + (r'\\[bfnrt\\\']', String.Escape), + (r'\\"', String.Escape), + (r'"', String.Double, "#pop"), + (r'[^\\"]+', String.Double), + (r'.', Error), + ], + + # Single-Char String (nested rules) + "string.char": [ + (r'\\u[0-9a-fA-F]{4}', String.Escape), + (r'\\x[0-9a-fA-F]{2}', String.Escape), + (r'\\[bfnrt\\\']', String.Escape), + (r"\\'", String.Escape), + (r"'", String.Char, "#pop"), + (r"[^\\']+", String.Char), + (r'.', Error), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/scdoc.py b/.venv/lib/python3.8/site-packages/pygments/lexers/scdoc.py new file mode 100644 index 0000000..48b0682 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/scdoc.py @@ -0,0 +1,82 @@ +""" + pygments.lexers.scdoc + ~~~~~~~~~~~~~~~~~~~~~ + + Lexer for scdoc, a simple man page generator. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, bygroups, \ + using, this +from pygments.token import Text, Comment, Keyword, String, \ + Generic + + +__all__ = ['ScdocLexer'] + + +class ScdocLexer(RegexLexer): + """ + `scdoc` is a simple man page generator for POSIX systems written in C99. + https://git.sr.ht/~sircmpwn/scdoc + + .. versionadded:: 2.5 + """ + name = 'scdoc' + aliases = ['scdoc', 'scd'] + filenames = ['*.scd', '*.scdoc'] + flags = re.MULTILINE + + tokens = { + 'root': [ + # comment + (r'^(;.+\n)', bygroups(Comment)), + + # heading with pound prefix + (r'^(#)([^#].+\n)', bygroups(Generic.Heading, Text)), + (r'^(#{2})(.+\n)', bygroups(Generic.Subheading, Text)), + # bulleted lists + (r'^(\s*)([*-])(\s)(.+\n)', + bygroups(Text, Keyword, Text, using(this, state='inline'))), + # numbered lists + (r'^(\s*)(\.+\.)( .+\n)', + bygroups(Text, Keyword, using(this, state='inline'))), + # quote + (r'^(\s*>\s)(.+\n)', bygroups(Keyword, Generic.Emph)), + # text block + (r'^(```\n)([\w\W]*?)(^```$)', bygroups(String, Text, String)), + + include('inline'), + ], + 'inline': [ + # escape + (r'\\.', Text), + # underlines + (r'(\s)(_[^_]+_)(\W|\n)', bygroups(Text, Generic.Emph, Text)), + # bold + (r'(\s)(\*[^*]+\*)(\W|\n)', bygroups(Text, Generic.Strong, Text)), + # inline code + (r'`[^`]+`', String.Backtick), + + # general text, must come last! + (r'[^\\\s]+', Text), + (r'.', Text), + ], + } + + def analyse_text(text): + """This is very similar to markdown, save for the escape characters + needed for * and _.""" + result = 0 + + if '\\*' in text: + result += 0.01 + + if '\\_' in text: + result += 0.01 + + return result diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/scripting.py b/.venv/lib/python3.8/site-packages/pygments/lexers/scripting.py new file mode 100644 index 0000000..9a1e63d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/scripting.py @@ -0,0 +1,1283 @@ +""" + pygments.lexers.scripting + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexer for scripting and embedded languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, include, bygroups, default, combined, \ + words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Error, Whitespace, Other +from pygments.util import get_bool_opt, get_list_opt + +__all__ = ['LuaLexer', 'MoonScriptLexer', 'ChaiscriptLexer', 'LSLLexer', + 'AppleScriptLexer', 'RexxLexer', 'MOOCodeLexer', 'HybrisLexer', + 'EasytrieveLexer', 'JclLexer', 'MiniScriptLexer'] + + +class LuaLexer(RegexLexer): + """ + For `Lua `_ source code. + + Additional options accepted: + + `func_name_highlighting` + If given and ``True``, highlight builtin function names + (default: ``True``). + `disabled_modules` + If given, must be a list of module names whose function names + should not be highlighted. By default all modules are highlighted. + + To get a list of allowed modules have a look into the + `_lua_builtins` module: + + .. sourcecode:: pycon + + >>> from pygments.lexers._lua_builtins import MODULES + >>> MODULES.keys() + ['string', 'coroutine', 'modules', 'io', 'basic', ...] + """ + + name = 'Lua' + aliases = ['lua'] + filenames = ['*.lua', '*.wlua'] + mimetypes = ['text/x-lua', 'application/x-lua'] + + _comment_multiline = r'(?:--\[(?P=*)\[[\w\W]*?\](?P=level)\])' + _comment_single = r'(?:--.*$)' + _space = r'(?:\s+)' + _s = r'(?:%s|%s|%s)' % (_comment_multiline, _comment_single, _space) + _name = r'(?:[^\W\d]\w*)' + + tokens = { + 'root': [ + # Lua allows a file to start with a shebang. + (r'#!.*', Comment.Preproc), + default('base'), + ], + 'ws': [ + (_comment_multiline, Comment.Multiline), + (_comment_single, Comment.Single), + (_space, Text), + ], + 'base': [ + include('ws'), + + (r'(?i)0x[\da-f]*(\.[\da-f]*)?(p[+-]?\d+)?', Number.Hex), + (r'(?i)(\d*\.\d+|\d+\.\d*)(e[+-]?\d+)?', Number.Float), + (r'(?i)\d+e[+-]?\d+', Number.Float), + (r'\d+', Number.Integer), + + # multiline strings + (r'(?s)\[(=*)\[.*?\]\1\]', String), + + (r'::', Punctuation, 'label'), + (r'\.{3}', Punctuation), + (r'[=<>|~&+\-*/%#^]+|\.\.', Operator), + (r'[\[\]{}().,:;]', Punctuation), + (r'(and|or|not)\b', Operator.Word), + + ('(break|do|else|elseif|end|for|if|in|repeat|return|then|until|' + r'while)\b', Keyword.Reserved), + (r'goto\b', Keyword.Reserved, 'goto'), + (r'(local)\b', Keyword.Declaration), + (r'(true|false|nil)\b', Keyword.Constant), + + (r'(function)\b', Keyword.Reserved, 'funcname'), + + (r'[A-Za-z_]\w*(\.[A-Za-z_]\w*)?', Name), + + ("'", String.Single, combined('stringescape', 'sqs')), + ('"', String.Double, combined('stringescape', 'dqs')) + ], + + 'funcname': [ + include('ws'), + (r'[.:]', Punctuation), + (r'%s(?=%s*[.:])' % (_name, _s), Name.Class), + (_name, Name.Function, '#pop'), + # inline function + (r'\(', Punctuation, '#pop'), + ], + + 'goto': [ + include('ws'), + (_name, Name.Label, '#pop'), + ], + + 'label': [ + include('ws'), + (r'::', Punctuation, '#pop'), + (_name, Name.Label), + ], + + 'stringescape': [ + (r'\\([abfnrtv\\"\']|[\r\n]{1,2}|z\s*|x[0-9a-fA-F]{2}|\d{1,3}|' + r'u\{[0-9a-fA-F]+\})', String.Escape), + ], + + 'sqs': [ + (r"'", String.Single, '#pop'), + (r"[^\\']+", String.Single), + ], + + 'dqs': [ + (r'"', String.Double, '#pop'), + (r'[^\\"]+', String.Double), + ] + } + + def __init__(self, **options): + self.func_name_highlighting = get_bool_opt( + options, 'func_name_highlighting', True) + self.disabled_modules = get_list_opt(options, 'disabled_modules', []) + + self._functions = set() + if self.func_name_highlighting: + from pygments.lexers._lua_builtins import MODULES + for mod, func in MODULES.items(): + if mod not in self.disabled_modules: + self._functions.update(func) + RegexLexer.__init__(self, **options) + + def get_tokens_unprocessed(self, text): + for index, token, value in \ + RegexLexer.get_tokens_unprocessed(self, text): + if token is Name: + if value in self._functions: + yield index, Name.Builtin, value + continue + elif '.' in value: + a, b = value.split('.') + yield index, Name, a + yield index + len(a), Punctuation, '.' + yield index + len(a) + 1, Name, b + continue + yield index, token, value + +class MoonScriptLexer(LuaLexer): + """ + For `MoonScript `_ source code. + + .. versionadded:: 1.5 + """ + + name = 'MoonScript' + aliases = ['moonscript', 'moon'] + filenames = ['*.moon'] + mimetypes = ['text/x-moonscript', 'application/x-moonscript'] + + tokens = { + 'root': [ + (r'#!(.*?)$', Comment.Preproc), + default('base'), + ], + 'base': [ + ('--.*$', Comment.Single), + (r'(?i)(\d*\.\d+|\d+\.\d*)(e[+-]?\d+)?', Number.Float), + (r'(?i)\d+e[+-]?\d+', Number.Float), + (r'(?i)0x[0-9a-f]*', Number.Hex), + (r'\d+', Number.Integer), + (r'\n', Whitespace), + (r'[^\S\n]+', Text), + (r'(?s)\[(=*)\[.*?\]\1\]', String), + (r'(->|=>)', Name.Function), + (r':[a-zA-Z_]\w*', Name.Variable), + (r'(==|!=|~=|<=|>=|\.\.\.|\.\.|[=+\-*/%^<>#!.\\:])', Operator), + (r'[;,]', Punctuation), + (r'[\[\]{}()]', Keyword.Type), + (r'[a-zA-Z_]\w*:', Name.Variable), + (words(( + 'class', 'extends', 'if', 'then', 'super', 'do', 'with', + 'import', 'export', 'while', 'elseif', 'return', 'for', 'in', + 'from', 'when', 'using', 'else', 'and', 'or', 'not', 'switch', + 'break'), suffix=r'\b'), + Keyword), + (r'(true|false|nil)\b', Keyword.Constant), + (r'(and|or|not)\b', Operator.Word), + (r'(self)\b', Name.Builtin.Pseudo), + (r'@@?([a-zA-Z_]\w*)?', Name.Variable.Class), + (r'[A-Z]\w*', Name.Class), # proper name + (r'[A-Za-z_]\w*(\.[A-Za-z_]\w*)?', Name), + ("'", String.Single, combined('stringescape', 'sqs')), + ('"', String.Double, combined('stringescape', 'dqs')) + ], + 'stringescape': [ + (r'''\\([abfnrtv\\"']|\d{1,3})''', String.Escape) + ], + 'sqs': [ + ("'", String.Single, '#pop'), + ("[^']+", String) + ], + 'dqs': [ + ('"', String.Double, '#pop'), + ('[^"]+', String) + ] + } + + def get_tokens_unprocessed(self, text): + # set . as Operator instead of Punctuation + for index, token, value in LuaLexer.get_tokens_unprocessed(self, text): + if token == Punctuation and value == ".": + token = Operator + yield index, token, value + + +class ChaiscriptLexer(RegexLexer): + """ + For `ChaiScript `_ source code. + + .. versionadded:: 2.0 + """ + + name = 'ChaiScript' + aliases = ['chaiscript', 'chai'] + filenames = ['*.chai'] + mimetypes = ['text/x-chaiscript', 'application/x-chaiscript'] + + flags = re.DOTALL | re.MULTILINE + + tokens = { + 'commentsandwhitespace': [ + (r'\s+', Text), + (r'//.*?\n', Comment.Single), + (r'/\*.*?\*/', Comment.Multiline), + (r'^\#.*?\n', Comment.Single) + ], + 'slashstartsregex': [ + include('commentsandwhitespace'), + (r'/(\\.|[^[/\\\n]|\[(\\.|[^\]\\\n])*])+/' + r'([gim]+\b|\B)', String.Regex, '#pop'), + (r'(?=/)', Text, ('#pop', 'badregex')), + default('#pop') + ], + 'badregex': [ + (r'\n', Text, '#pop') + ], + 'root': [ + include('commentsandwhitespace'), + (r'\n', Text), + (r'[^\S\n]+', Text), + (r'\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|\.\.' + r'(<<|>>>?|==?|!=?|[-<>+*%&|^/])=?', Operator, 'slashstartsregex'), + (r'[{(\[;,]', Punctuation, 'slashstartsregex'), + (r'[})\].]', Punctuation), + (r'[=+\-*/]', Operator), + (r'(for|in|while|do|break|return|continue|if|else|' + r'throw|try|catch' + r')\b', Keyword, 'slashstartsregex'), + (r'(var)\b', Keyword.Declaration, 'slashstartsregex'), + (r'(attr|def|fun)\b', Keyword.Reserved), + (r'(true|false)\b', Keyword.Constant), + (r'(eval|throw)\b', Name.Builtin), + (r'`\S+`', Name.Builtin), + (r'[$a-zA-Z_]\w*', Name.Other), + (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float), + (r'0x[0-9a-fA-F]+', Number.Hex), + (r'[0-9]+', Number.Integer), + (r'"', String.Double, 'dqstring'), + (r"'(\\\\|\\[^\\]|[^'\\])*'", String.Single), + ], + 'dqstring': [ + (r'\$\{[^"}]+?\}', String.Interpol), + (r'\$', String.Double), + (r'\\\\', String.Double), + (r'\\"', String.Double), + (r'[^\\"$]+', String.Double), + (r'"', String.Double, '#pop'), + ], + } + + +class LSLLexer(RegexLexer): + """ + For Second Life's Linden Scripting Language source code. + + .. versionadded:: 2.0 + """ + + name = 'LSL' + aliases = ['lsl'] + filenames = ['*.lsl'] + mimetypes = ['text/x-lsl'] + + flags = re.MULTILINE + + lsl_keywords = r'\b(?:do|else|for|if|jump|return|while)\b' + lsl_types = r'\b(?:float|integer|key|list|quaternion|rotation|string|vector)\b' + lsl_states = r'\b(?:(?:state)\s+\w+|default)\b' + lsl_events = r'\b(?:state_(?:entry|exit)|touch(?:_(?:start|end))?|(?:land_)?collision(?:_(?:start|end))?|timer|listen|(?:no_)?sensor|control|(?:not_)?at_(?:rot_)?target|money|email|run_time_permissions|changed|attach|dataserver|moving_(?:start|end)|link_message|(?:on|object)_rez|remote_data|http_re(?:sponse|quest)|path_update|transaction_result)\b' + lsl_functions_builtin = r'\b(?:ll(?:ReturnObjectsBy(?:ID|Owner)|Json(?:2List|[GS]etValue|ValueType)|Sin|Cos|Tan|Atan2|Sqrt|Pow|Abs|Fabs|Frand|Floor|Ceil|Round|Vec(?:Mag|Norm|Dist)|Rot(?:Between|2(?:Euler|Fwd|Left|Up))|(?:Euler|Axes)2Rot|Whisper|(?:Region|Owner)?Say|Shout|Listen(?:Control|Remove)?|Sensor(?:Repeat|Remove)?|Detected(?:Name|Key|Owner|Type|Pos|Vel|Grab|Rot|Group|LinkNumber)|Die|Ground|Wind|(?:[GS]et)(?:AnimationOverride|MemoryLimit|PrimMediaParams|ParcelMusicURL|Object(?:Desc|Name)|PhysicsMaterial|Status|Scale|Color|Alpha|Texture|Pos|Rot|Force|Torque)|ResetAnimationOverride|(?:Scale|Offset|Rotate)Texture|(?:Rot)?Target(?:Remove)?|(?:Stop)?MoveToTarget|Apply(?:Rotational)?Impulse|Set(?:KeyframedMotion|ContentType|RegionPos|(?:Angular)?Velocity|Buoyancy|HoverHeight|ForceAndTorque|TimerEvent|ScriptState|Damage|TextureAnim|Sound(?:Queueing|Radius)|Vehicle(?:Type|(?:Float|Vector|Rotation)Param)|(?:Touch|Sit)?Text|Camera(?:Eye|At)Offset|PrimitiveParams|ClickAction|Link(?:Alpha|Color|PrimitiveParams(?:Fast)?|Texture(?:Anim)?|Camera|Media)|RemoteScriptAccessPin|PayPrice|LocalRot)|ScaleByFactor|Get(?:(?:Max|Min)ScaleFactor|ClosestNavPoint|StaticPath|SimStats|Env|PrimitiveParams|Link(?:PrimitiveParams|Number(?:OfSides)?|Key|Name|Media)|HTTPHeader|FreeURLs|Object(?:Details|PermMask|PrimCount)|Parcel(?:MaxPrims|Details|Prim(?:Count|Owners))|Attached|(?:SPMax|Free|Used)Memory|Region(?:Name|TimeDilation|FPS|Corner|AgentCount)|Root(?:Position|Rotation)|UnixTime|(?:Parcel|Region)Flags|(?:Wall|GMT)clock|SimulatorHostname|BoundingBox|GeometricCenter|Creator|NumberOf(?:Prims|NotecardLines|Sides)|Animation(?:List)?|(?:Camera|Local)(?:Pos|Rot)|Vel|Accel|Omega|Time(?:stamp|OfDay)|(?:Object|CenterOf)?Mass|MassMKS|Energy|Owner|(?:Owner)?Key|SunDirection|Texture(?:Offset|Scale|Rot)|Inventory(?:Number|Name|Key|Type|Creator|PermMask)|Permissions(?:Key)?|StartParameter|List(?:Length|EntryType)|Date|Agent(?:Size|Info|Language|List)|LandOwnerAt|NotecardLine|Script(?:Name|State))|(?:Get|Reset|GetAndReset)Time|PlaySound(?:Slave)?|LoopSound(?:Master|Slave)?|(?:Trigger|Stop|Preload)Sound|(?:(?:Get|Delete)Sub|Insert)String|To(?:Upper|Lower)|Give(?:InventoryList|Money)|RezObject|(?:Stop)?LookAt|Sleep|CollisionFilter|(?:Take|Release)Controls|DetachFromAvatar|AttachToAvatar(?:Temp)?|InstantMessage|(?:GetNext)?Email|StopHover|MinEventDelay|RotLookAt|String(?:Length|Trim)|(?:Start|Stop)Animation|TargetOmega|RequestPermissions|(?:Create|Break)Link|BreakAllLinks|(?:Give|Remove)Inventory|Water|PassTouches|Request(?:Agent|Inventory)Data|TeleportAgent(?:Home|GlobalCoords)?|ModifyLand|CollisionSound|ResetScript|MessageLinked|PushObject|PassCollisions|AxisAngle2Rot|Rot2(?:Axis|Angle)|A(?:cos|sin)|AngleBetween|AllowInventoryDrop|SubStringIndex|List2(?:CSV|Integer|Json|Float|String|Key|Vector|Rot|List(?:Strided)?)|DeleteSubList|List(?:Statistics|Sort|Randomize|(?:Insert|Find|Replace)List)|EdgeOfWorld|AdjustSoundVolume|Key2Name|TriggerSoundLimited|EjectFromLand|(?:CSV|ParseString)2List|OverMyLand|SameGroup|UnSit|Ground(?:Slope|Normal|Contour)|GroundRepel|(?:Set|Remove)VehicleFlags|(?:AvatarOn)?(?:Link)?SitTarget|Script(?:Danger|Profiler)|Dialog|VolumeDetect|ResetOtherScript|RemoteLoadScriptPin|(?:Open|Close)RemoteDataChannel|SendRemoteData|RemoteDataReply|(?:Integer|String)ToBase64|XorBase64|Log(?:10)?|Base64To(?:String|Integer)|ParseStringKeepNulls|RezAtRoot|RequestSimulatorData|ForceMouselook|(?:Load|Release|(?:E|Une)scape)URL|ParcelMedia(?:CommandList|Query)|ModPow|MapDestination|(?:RemoveFrom|AddTo|Reset)Land(?:Pass|Ban)List|(?:Set|Clear)CameraParams|HTTP(?:Request|Response)|TextBox|DetectedTouch(?:UV|Face|Pos|(?:N|Bin)ormal|ST)|(?:MD5|SHA1|DumpList2)String|Request(?:Secure)?URL|Clear(?:Prim|Link)Media|(?:Link)?ParticleSystem|(?:Get|Request)(?:Username|DisplayName)|RegionSayTo|CastRay|GenerateKey|TransferLindenDollars|ManageEstateAccess|(?:Create|Delete)Character|ExecCharacterCmd|Evade|FleeFrom|NavigateTo|PatrolPoints|Pursue|UpdateCharacter|WanderWithin))\b' + lsl_constants_float = r'\b(?:DEG_TO_RAD|PI(?:_BY_TWO)?|RAD_TO_DEG|SQRT2|TWO_PI)\b' + lsl_constants_integer = r'\b(?:JSON_APPEND|STATUS_(?:PHYSICS|ROTATE_[XYZ]|PHANTOM|SANDBOX|BLOCK_GRAB(?:_OBJECT)?|(?:DIE|RETURN)_AT_EDGE|CAST_SHADOWS|OK|MALFORMED_PARAMS|TYPE_MISMATCH|BOUNDS_ERROR|NOT_(?:FOUND|SUPPORTED)|INTERNAL_ERROR|WHITELIST_FAILED)|AGENT(?:_(?:BY_(?:LEGACY_|USER)NAME|FLYING|ATTACHMENTS|SCRIPTED|MOUSELOOK|SITTING|ON_OBJECT|AWAY|WALKING|IN_AIR|TYPING|CROUCHING|BUSY|ALWAYS_RUN|AUTOPILOT|LIST_(?:PARCEL(?:_OWNER)?|REGION)))?|CAMERA_(?:PITCH|DISTANCE|BEHINDNESS_(?:ANGLE|LAG)|(?:FOCUS|POSITION)(?:_(?:THRESHOLD|LOCKED|LAG))?|FOCUS_OFFSET|ACTIVE)|ANIM_ON|LOOP|REVERSE|PING_PONG|SMOOTH|ROTATE|SCALE|ALL_SIDES|LINK_(?:ROOT|SET|ALL_(?:OTHERS|CHILDREN)|THIS)|ACTIVE|PASSIVE|SCRIPTED|CONTROL_(?:FWD|BACK|(?:ROT_)?(?:LEFT|RIGHT)|UP|DOWN|(?:ML_)?LBUTTON)|PERMISSION_(?:RETURN_OBJECTS|DEBIT|OVERRIDE_ANIMATIONS|SILENT_ESTATE_MANAGEMENT|TAKE_CONTROLS|TRIGGER_ANIMATION|ATTACH|CHANGE_LINKS|(?:CONTROL|TRACK)_CAMERA|TELEPORT)|INVENTORY_(?:TEXTURE|SOUND|OBJECT|SCRIPT|LANDMARK|CLOTHING|NOTECARD|BODYPART|ANIMATION|GESTURE|ALL|NONE)|CHANGED_(?:INVENTORY|COLOR|SHAPE|SCALE|TEXTURE|LINK|ALLOWED_DROP|OWNER|REGION(?:_START)?|TELEPORT|MEDIA)|OBJECT_(?:(?:PHYSICS|SERVER|STREAMING)_COST|UNKNOWN_DETAIL|CHARACTER_TIME|PHANTOM|PHYSICS|TEMP_ON_REZ|NAME|DESC|POS|PRIM_EQUIVALENCE|RETURN_(?:PARCEL(?:_OWNER)?|REGION)|ROO?T|VELOCITY|OWNER|GROUP|CREATOR|ATTACHED_POINT|RENDER_WEIGHT|PATHFINDING_TYPE|(?:RUNNING|TOTAL)_SCRIPT_COUNT|SCRIPT_(?:MEMORY|TIME))|TYPE_(?:INTEGER|FLOAT|STRING|KEY|VECTOR|ROTATION|INVALID)|(?:DEBUG|PUBLIC)_CHANNEL|ATTACH_(?:AVATAR_CENTER|CHEST|HEAD|BACK|PELVIS|MOUTH|CHIN|NECK|NOSE|BELLY|[LR](?:SHOULDER|HAND|FOOT|EAR|EYE|[UL](?:ARM|LEG)|HIP)|(?:LEFT|RIGHT)_PEC|HUD_(?:CENTER_[12]|TOP_(?:RIGHT|CENTER|LEFT)|BOTTOM(?:_(?:RIGHT|LEFT))?))|LAND_(?:LEVEL|RAISE|LOWER|SMOOTH|NOISE|REVERT)|DATA_(?:ONLINE|NAME|BORN|SIM_(?:POS|STATUS|RATING)|PAYINFO)|PAYMENT_INFO_(?:ON_FILE|USED)|REMOTE_DATA_(?:CHANNEL|REQUEST|REPLY)|PSYS_(?:PART_(?:BF_(?:ZERO|ONE(?:_MINUS_(?:DEST_COLOR|SOURCE_(ALPHA|COLOR)))?|DEST_COLOR|SOURCE_(ALPHA|COLOR))|BLEND_FUNC_(DEST|SOURCE)|FLAGS|(?:START|END)_(?:COLOR|ALPHA|SCALE|GLOW)|MAX_AGE|(?:RIBBON|WIND|INTERP_(?:COLOR|SCALE)|BOUNCE|FOLLOW_(?:SRC|VELOCITY)|TARGET_(?:POS|LINEAR)|EMISSIVE)_MASK)|SRC_(?:MAX_AGE|PATTERN|ANGLE_(?:BEGIN|END)|BURST_(?:RATE|PART_COUNT|RADIUS|SPEED_(?:MIN|MAX))|ACCEL|TEXTURE|TARGET_KEY|OMEGA|PATTERN_(?:DROP|EXPLODE|ANGLE(?:_CONE(?:_EMPTY)?)?)))|VEHICLE_(?:REFERENCE_FRAME|TYPE_(?:NONE|SLED|CAR|BOAT|AIRPLANE|BALLOON)|(?:LINEAR|ANGULAR)_(?:FRICTION_TIMESCALE|MOTOR_DIRECTION)|LINEAR_MOTOR_OFFSET|HOVER_(?:HEIGHT|EFFICIENCY|TIMESCALE)|BUOYANCY|(?:LINEAR|ANGULAR)_(?:DEFLECTION_(?:EFFICIENCY|TIMESCALE)|MOTOR_(?:DECAY_)?TIMESCALE)|VERTICAL_ATTRACTION_(?:EFFICIENCY|TIMESCALE)|BANKING_(?:EFFICIENCY|MIX|TIMESCALE)|FLAG_(?:NO_DEFLECTION_UP|LIMIT_(?:ROLL_ONLY|MOTOR_UP)|HOVER_(?:(?:WATER|TERRAIN|UP)_ONLY|GLOBAL_HEIGHT)|MOUSELOOK_(?:STEER|BANK)|CAMERA_DECOUPLED))|PRIM_(?:TYPE(?:_(?:BOX|CYLINDER|PRISM|SPHERE|TORUS|TUBE|RING|SCULPT))?|HOLE_(?:DEFAULT|CIRCLE|SQUARE|TRIANGLE)|MATERIAL(?:_(?:STONE|METAL|GLASS|WOOD|FLESH|PLASTIC|RUBBER))?|SHINY_(?:NONE|LOW|MEDIUM|HIGH)|BUMP_(?:NONE|BRIGHT|DARK|WOOD|BARK|BRICKS|CHECKER|CONCRETE|TILE|STONE|DISKS|GRAVEL|BLOBS|SIDING|LARGETILE|STUCCO|SUCTION|WEAVE)|TEXGEN_(?:DEFAULT|PLANAR)|SCULPT_(?:TYPE_(?:SPHERE|TORUS|PLANE|CYLINDER|MASK)|FLAG_(?:MIRROR|INVERT))|PHYSICS(?:_(?:SHAPE_(?:CONVEX|NONE|PRIM|TYPE)))?|(?:POS|ROT)_LOCAL|SLICE|TEXT|FLEXIBLE|POINT_LIGHT|TEMP_ON_REZ|PHANTOM|POSITION|SIZE|ROTATION|TEXTURE|NAME|OMEGA|DESC|LINK_TARGET|COLOR|BUMP_SHINY|FULLBRIGHT|TEXGEN|GLOW|MEDIA_(?:ALT_IMAGE_ENABLE|CONTROLS|(?:CURRENT|HOME)_URL|AUTO_(?:LOOP|PLAY|SCALE|ZOOM)|FIRST_CLICK_INTERACT|(?:WIDTH|HEIGHT)_PIXELS|WHITELIST(?:_ENABLE)?|PERMS_(?:INTERACT|CONTROL)|PARAM_MAX|CONTROLS_(?:STANDARD|MINI)|PERM_(?:NONE|OWNER|GROUP|ANYONE)|MAX_(?:URL_LENGTH|WHITELIST_(?:SIZE|COUNT)|(?:WIDTH|HEIGHT)_PIXELS)))|MASK_(?:BASE|OWNER|GROUP|EVERYONE|NEXT)|PERM_(?:TRANSFER|MODIFY|COPY|MOVE|ALL)|PARCEL_(?:MEDIA_COMMAND_(?:STOP|PAUSE|PLAY|LOOP|TEXTURE|URL|TIME|AGENT|UNLOAD|AUTO_ALIGN|TYPE|SIZE|DESC|LOOP_SET)|FLAG_(?:ALLOW_(?:FLY|(?:GROUP_)?SCRIPTS|LANDMARK|TERRAFORM|DAMAGE|CREATE_(?:GROUP_)?OBJECTS)|USE_(?:ACCESS_(?:GROUP|LIST)|BAN_LIST|LAND_PASS_LIST)|LOCAL_SOUND_ONLY|RESTRICT_PUSHOBJECT|ALLOW_(?:GROUP|ALL)_OBJECT_ENTRY)|COUNT_(?:TOTAL|OWNER|GROUP|OTHER|SELECTED|TEMP)|DETAILS_(?:NAME|DESC|OWNER|GROUP|AREA|ID|SEE_AVATARS))|LIST_STAT_(?:MAX|MIN|MEAN|MEDIAN|STD_DEV|SUM(?:_SQUARES)?|NUM_COUNT|GEOMETRIC_MEAN|RANGE)|PAY_(?:HIDE|DEFAULT)|REGION_FLAG_(?:ALLOW_DAMAGE|FIXED_SUN|BLOCK_TERRAFORM|SANDBOX|DISABLE_(?:COLLISIONS|PHYSICS)|BLOCK_FLY|ALLOW_DIRECT_TELEPORT|RESTRICT_PUSHOBJECT)|HTTP_(?:METHOD|MIMETYPE|BODY_(?:MAXLENGTH|TRUNCATED)|CUSTOM_HEADER|PRAGMA_NO_CACHE|VERBOSE_THROTTLE|VERIFY_CERT)|STRING_(?:TRIM(?:_(?:HEAD|TAIL))?)|CLICK_ACTION_(?:NONE|TOUCH|SIT|BUY|PAY|OPEN(?:_MEDIA)?|PLAY|ZOOM)|TOUCH_INVALID_FACE|PROFILE_(?:NONE|SCRIPT_MEMORY)|RC_(?:DATA_FLAGS|DETECT_PHANTOM|GET_(?:LINK_NUM|NORMAL|ROOT_KEY)|MAX_HITS|REJECT_(?:TYPES|AGENTS|(?:NON)?PHYSICAL|LAND))|RCERR_(?:CAST_TIME_EXCEEDED|SIM_PERF_LOW|UNKNOWN)|ESTATE_ACCESS_(?:ALLOWED_(?:AGENT|GROUP)_(?:ADD|REMOVE)|BANNED_AGENT_(?:ADD|REMOVE))|DENSITY|FRICTION|RESTITUTION|GRAVITY_MULTIPLIER|KFM_(?:COMMAND|CMD_(?:PLAY|STOP|PAUSE|SET_MODE)|MODE|FORWARD|LOOP|PING_PONG|REVERSE|DATA|ROTATION|TRANSLATION)|ERR_(?:GENERIC|PARCEL_PERMISSIONS|MALFORMED_PARAMS|RUNTIME_PERMISSIONS|THROTTLED)|CHARACTER_(?:CMD_(?:(?:SMOOTH_)?STOP|JUMP)|DESIRED_(?:TURN_)?SPEED|RADIUS|STAY_WITHIN_PARCEL|LENGTH|ORIENTATION|ACCOUNT_FOR_SKIPPED_FRAMES|AVOIDANCE_MODE|TYPE(?:_(?:[A-D]|NONE))?|MAX_(?:DECEL|TURN_RADIUS|(?:ACCEL|SPEED)))|PURSUIT_(?:OFFSET|FUZZ_FACTOR|GOAL_TOLERANCE|INTERCEPT)|REQUIRE_LINE_OF_SIGHT|FORCE_DIRECT_PATH|VERTICAL|HORIZONTAL|AVOID_(?:CHARACTERS|DYNAMIC_OBSTACLES|NONE)|PU_(?:EVADE_(?:HIDDEN|SPOTTED)|FAILURE_(?:DYNAMIC_PATHFINDING_DISABLED|INVALID_(?:GOAL|START)|NO_(?:NAVMESH|VALID_DESTINATION)|OTHER|TARGET_GONE|(?:PARCEL_)?UNREACHABLE)|(?:GOAL|SLOWDOWN_DISTANCE)_REACHED)|TRAVERSAL_TYPE(?:_(?:FAST|NONE|SLOW))?|CONTENT_TYPE_(?:ATOM|FORM|HTML|JSON|LLSD|RSS|TEXT|XHTML|XML)|GCNP_(?:RADIUS|STATIC)|(?:PATROL|WANDER)_PAUSE_AT_WAYPOINTS|OPT_(?:AVATAR|CHARACTER|EXCLUSION_VOLUME|LEGACY_LINKSET|MATERIAL_VOLUME|OTHER|STATIC_OBSTACLE|WALKABLE)|SIM_STAT_PCT_CHARS_STEPPED)\b' + lsl_constants_integer_boolean = r'\b(?:FALSE|TRUE)\b' + lsl_constants_rotation = r'\b(?:ZERO_ROTATION)\b' + lsl_constants_string = r'\b(?:EOF|JSON_(?:ARRAY|DELETE|FALSE|INVALID|NULL|NUMBER|OBJECT|STRING|TRUE)|NULL_KEY|TEXTURE_(?:BLANK|DEFAULT|MEDIA|PLYWOOD|TRANSPARENT)|URL_REQUEST_(?:GRANTED|DENIED))\b' + lsl_constants_vector = r'\b(?:TOUCH_INVALID_(?:TEXCOORD|VECTOR)|ZERO_VECTOR)\b' + lsl_invalid_broken = r'\b(?:LAND_(?:LARGE|MEDIUM|SMALL)_BRUSH)\b' + lsl_invalid_deprecated = r'\b(?:ATTACH_[LR]PEC|DATA_RATING|OBJECT_ATTACHMENT_(?:GEOMETRY_BYTES|SURFACE_AREA)|PRIM_(?:CAST_SHADOWS|MATERIAL_LIGHT|TYPE_LEGACY)|PSYS_SRC_(?:INNER|OUTER)ANGLE|VEHICLE_FLAG_NO_FLY_UP|ll(?:Cloud|Make(?:Explosion|Fountain|Smoke|Fire)|RemoteDataSetRegion|Sound(?:Preload)?|XorBase64Strings(?:Correct)?))\b' + lsl_invalid_illegal = r'\b(?:event)\b' + lsl_invalid_unimplemented = r'\b(?:CHARACTER_(?:MAX_ANGULAR_(?:ACCEL|SPEED)|TURN_SPEED_MULTIPLIER)|PERMISSION_(?:CHANGE_(?:JOINTS|PERMISSIONS)|RELEASE_OWNERSHIP|REMAP_CONTROLS)|PRIM_PHYSICS_MATERIAL|PSYS_SRC_OBJ_REL_MASK|ll(?:CollisionSprite|(?:Stop)?PointAt|(?:(?:Refresh|Set)Prim)URL|(?:Take|Release)Camera|RemoteLoadScript))\b' + lsl_reserved_godmode = r'\b(?:ll(?:GodLikeRezObject|Set(?:Inventory|Object)PermMask))\b' + lsl_reserved_log = r'\b(?:print)\b' + lsl_operators = r'\+\+|\-\-|<<|>>|&&?|\|\|?|\^|~|[!%<>=*+\-/]=?' + + tokens = { + 'root': + [ + (r'//.*?\n', Comment.Single), + (r'/\*', Comment.Multiline, 'comment'), + (r'"', String.Double, 'string'), + (lsl_keywords, Keyword), + (lsl_types, Keyword.Type), + (lsl_states, Name.Class), + (lsl_events, Name.Builtin), + (lsl_functions_builtin, Name.Function), + (lsl_constants_float, Keyword.Constant), + (lsl_constants_integer, Keyword.Constant), + (lsl_constants_integer_boolean, Keyword.Constant), + (lsl_constants_rotation, Keyword.Constant), + (lsl_constants_string, Keyword.Constant), + (lsl_constants_vector, Keyword.Constant), + (lsl_invalid_broken, Error), + (lsl_invalid_deprecated, Error), + (lsl_invalid_illegal, Error), + (lsl_invalid_unimplemented, Error), + (lsl_reserved_godmode, Keyword.Reserved), + (lsl_reserved_log, Keyword.Reserved), + (r'\b([a-zA-Z_]\w*)\b', Name.Variable), + (r'(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d*', Number.Float), + (r'(\d+\.\d*|\.\d+)', Number.Float), + (r'0[xX][0-9a-fA-F]+', Number.Hex), + (r'\d+', Number.Integer), + (lsl_operators, Operator), + (r':=?', Error), + (r'[,;{}()\[\]]', Punctuation), + (r'\n+', Whitespace), + (r'\s+', Whitespace) + ], + 'comment': + [ + (r'[^*/]+', Comment.Multiline), + (r'/\*', Comment.Multiline, '#push'), + (r'\*/', Comment.Multiline, '#pop'), + (r'[*/]', Comment.Multiline) + ], + 'string': + [ + (r'\\([nt"\\])', String.Escape), + (r'"', String.Double, '#pop'), + (r'\\.', Error), + (r'[^"\\]+', String.Double), + ] + } + + +class AppleScriptLexer(RegexLexer): + """ + For `AppleScript source code + `_, + including `AppleScript Studio + `_. + Contributed by Andreas Amann . + + .. versionadded:: 1.0 + """ + + name = 'AppleScript' + aliases = ['applescript'] + filenames = ['*.applescript'] + + flags = re.MULTILINE | re.DOTALL + + Identifiers = r'[a-zA-Z]\w*' + + # XXX: use words() for all of these + Literals = ('AppleScript', 'current application', 'false', 'linefeed', + 'missing value', 'pi', 'quote', 'result', 'return', 'space', + 'tab', 'text item delimiters', 'true', 'version') + Classes = ('alias ', 'application ', 'boolean ', 'class ', 'constant ', + 'date ', 'file ', 'integer ', 'list ', 'number ', 'POSIX file ', + 'real ', 'record ', 'reference ', 'RGB color ', 'script ', + 'text ', 'unit types', '(?:Unicode )?text', 'string') + BuiltIn = ('attachment', 'attribute run', 'character', 'day', 'month', + 'paragraph', 'word', 'year') + HandlerParams = ('about', 'above', 'against', 'apart from', 'around', + 'aside from', 'at', 'below', 'beneath', 'beside', + 'between', 'for', 'given', 'instead of', 'on', 'onto', + 'out of', 'over', 'since') + Commands = ('ASCII (character|number)', 'activate', 'beep', 'choose URL', + 'choose application', 'choose color', 'choose file( name)?', + 'choose folder', 'choose from list', + 'choose remote application', 'clipboard info', + 'close( access)?', 'copy', 'count', 'current date', 'delay', + 'delete', 'display (alert|dialog)', 'do shell script', + 'duplicate', 'exists', 'get eof', 'get volume settings', + 'info for', 'launch', 'list (disks|folder)', 'load script', + 'log', 'make', 'mount volume', 'new', 'offset', + 'open( (for access|location))?', 'path to', 'print', 'quit', + 'random number', 'read', 'round', 'run( script)?', + 'say', 'scripting components', + 'set (eof|the clipboard to|volume)', 'store script', + 'summarize', 'system attribute', 'system info', + 'the clipboard', 'time to GMT', 'write', 'quoted form') + References = ('(in )?back of', '(in )?front of', '[0-9]+(st|nd|rd|th)', + 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', + 'seventh', 'eighth', 'ninth', 'tenth', 'after', 'back', + 'before', 'behind', 'every', 'front', 'index', 'last', + 'middle', 'some', 'that', 'through', 'thru', 'where', 'whose') + Operators = ("and", "or", "is equal", "equals", "(is )?equal to", "is not", + "isn't", "isn't equal( to)?", "is not equal( to)?", + "doesn't equal", "does not equal", "(is )?greater than", + "comes after", "is not less than or equal( to)?", + "isn't less than or equal( to)?", "(is )?less than", + "comes before", "is not greater than or equal( to)?", + "isn't greater than or equal( to)?", + "(is )?greater than or equal( to)?", "is not less than", + "isn't less than", "does not come before", + "doesn't come before", "(is )?less than or equal( to)?", + "is not greater than", "isn't greater than", + "does not come after", "doesn't come after", "starts? with", + "begins? with", "ends? with", "contains?", "does not contain", + "doesn't contain", "is in", "is contained by", "is not in", + "is not contained by", "isn't contained by", "div", "mod", + "not", "(a )?(ref( to)?|reference to)", "is", "does") + Control = ('considering', 'else', 'error', 'exit', 'from', 'if', + 'ignoring', 'in', 'repeat', 'tell', 'then', 'times', 'to', + 'try', 'until', 'using terms from', 'while', 'whith', + 'with timeout( of)?', 'with transaction', 'by', 'continue', + 'end', 'its?', 'me', 'my', 'return', 'of', 'as') + Declarations = ('global', 'local', 'prop(erty)?', 'set', 'get') + Reserved = ('but', 'put', 'returning', 'the') + StudioClasses = ('action cell', 'alert reply', 'application', 'box', + 'browser( cell)?', 'bundle', 'button( cell)?', 'cell', + 'clip view', 'color well', 'color-panel', + 'combo box( item)?', 'control', + 'data( (cell|column|item|row|source))?', 'default entry', + 'dialog reply', 'document', 'drag info', 'drawer', + 'event', 'font(-panel)?', 'formatter', + 'image( (cell|view))?', 'matrix', 'menu( item)?', 'item', + 'movie( view)?', 'open-panel', 'outline view', 'panel', + 'pasteboard', 'plugin', 'popup button', + 'progress indicator', 'responder', 'save-panel', + 'scroll view', 'secure text field( cell)?', 'slider', + 'sound', 'split view', 'stepper', 'tab view( item)?', + 'table( (column|header cell|header view|view))', + 'text( (field( cell)?|view))?', 'toolbar( item)?', + 'user-defaults', 'view', 'window') + StudioEvents = ('accept outline drop', 'accept table drop', 'action', + 'activated', 'alert ended', 'awake from nib', 'became key', + 'became main', 'begin editing', 'bounds changed', + 'cell value', 'cell value changed', 'change cell value', + 'change item value', 'changed', 'child of item', + 'choose menu item', 'clicked', 'clicked toolbar item', + 'closed', 'column clicked', 'column moved', + 'column resized', 'conclude drop', 'data representation', + 'deminiaturized', 'dialog ended', 'document nib name', + 'double clicked', 'drag( (entered|exited|updated))?', + 'drop', 'end editing', 'exposed', 'idle', 'item expandable', + 'item value', 'item value changed', 'items changed', + 'keyboard down', 'keyboard up', 'launched', + 'load data representation', 'miniaturized', 'mouse down', + 'mouse dragged', 'mouse entered', 'mouse exited', + 'mouse moved', 'mouse up', 'moved', + 'number of browser rows', 'number of items', + 'number of rows', 'open untitled', 'opened', 'panel ended', + 'parameters updated', 'plugin loaded', 'prepare drop', + 'prepare outline drag', 'prepare outline drop', + 'prepare table drag', 'prepare table drop', + 'read from file', 'resigned active', 'resigned key', + 'resigned main', 'resized( sub views)?', + 'right mouse down', 'right mouse dragged', + 'right mouse up', 'rows changed', 'scroll wheel', + 'selected tab view item', 'selection changed', + 'selection changing', 'should begin editing', + 'should close', 'should collapse item', + 'should end editing', 'should expand item', + 'should open( untitled)?', + 'should quit( after last window closed)?', + 'should select column', 'should select item', + 'should select row', 'should select tab view item', + 'should selection change', 'should zoom', 'shown', + 'update menu item', 'update parameters', + 'update toolbar item', 'was hidden', 'was miniaturized', + 'will become active', 'will close', 'will dismiss', + 'will display browser cell', 'will display cell', + 'will display item cell', 'will display outline cell', + 'will finish launching', 'will hide', 'will miniaturize', + 'will move', 'will open', 'will pop up', 'will quit', + 'will resign active', 'will resize( sub views)?', + 'will select tab view item', 'will show', 'will zoom', + 'write to file', 'zoomed') + StudioCommands = ('animate', 'append', 'call method', 'center', + 'close drawer', 'close panel', 'display', + 'display alert', 'display dialog', 'display panel', 'go', + 'hide', 'highlight', 'increment', 'item for', + 'load image', 'load movie', 'load nib', 'load panel', + 'load sound', 'localized string', 'lock focus', 'log', + 'open drawer', 'path for', 'pause', 'perform action', + 'play', 'register', 'resume', 'scroll', 'select( all)?', + 'show', 'size to fit', 'start', 'step back', + 'step forward', 'stop', 'synchronize', 'unlock focus', + 'update') + StudioProperties = ('accepts arrow key', 'action method', 'active', + 'alignment', 'allowed identifiers', + 'allows branch selection', 'allows column reordering', + 'allows column resizing', 'allows column selection', + 'allows customization', + 'allows editing text attributes', + 'allows empty selection', 'allows mixed state', + 'allows multiple selection', 'allows reordering', + 'allows undo', 'alpha( value)?', 'alternate image', + 'alternate increment value', 'alternate title', + 'animation delay', 'associated file name', + 'associated object', 'auto completes', 'auto display', + 'auto enables items', 'auto repeat', + 'auto resizes( outline column)?', + 'auto save expanded items', 'auto save name', + 'auto save table columns', 'auto saves configuration', + 'auto scroll', 'auto sizes all columns to fit', + 'auto sizes cells', 'background color', 'bezel state', + 'bezel style', 'bezeled', 'border rect', 'border type', + 'bordered', 'bounds( rotation)?', 'box type', + 'button returned', 'button type', + 'can choose directories', 'can choose files', + 'can draw', 'can hide', + 'cell( (background color|size|type))?', 'characters', + 'class', 'click count', 'clicked( data)? column', + 'clicked data item', 'clicked( data)? row', + 'closeable', 'collating', 'color( (mode|panel))', + 'command key down', 'configuration', + 'content(s| (size|view( margins)?))?', 'context', + 'continuous', 'control key down', 'control size', + 'control tint', 'control view', + 'controller visible', 'coordinate system', + 'copies( on scroll)?', 'corner view', 'current cell', + 'current column', 'current( field)? editor', + 'current( menu)? item', 'current row', + 'current tab view item', 'data source', + 'default identifiers', 'delta (x|y|z)', + 'destination window', 'directory', 'display mode', + 'displayed cell', 'document( (edited|rect|view))?', + 'double value', 'dragged column', 'dragged distance', + 'dragged items', 'draws( cell)? background', + 'draws grid', 'dynamically scrolls', 'echos bullets', + 'edge', 'editable', 'edited( data)? column', + 'edited data item', 'edited( data)? row', 'enabled', + 'enclosing scroll view', 'ending page', + 'error handling', 'event number', 'event type', + 'excluded from windows menu', 'executable path', + 'expanded', 'fax number', 'field editor', 'file kind', + 'file name', 'file type', 'first responder', + 'first visible column', 'flipped', 'floating', + 'font( panel)?', 'formatter', 'frameworks path', + 'frontmost', 'gave up', 'grid color', 'has data items', + 'has horizontal ruler', 'has horizontal scroller', + 'has parent data item', 'has resize indicator', + 'has shadow', 'has sub menu', 'has vertical ruler', + 'has vertical scroller', 'header cell', 'header view', + 'hidden', 'hides when deactivated', 'highlights by', + 'horizontal line scroll', 'horizontal page scroll', + 'horizontal ruler view', 'horizontally resizable', + 'icon image', 'id', 'identifier', + 'ignores multiple clicks', + 'image( (alignment|dims when disabled|frame style|scaling))?', + 'imports graphics', 'increment value', + 'indentation per level', 'indeterminate', 'index', + 'integer value', 'intercell spacing', 'item height', + 'key( (code|equivalent( modifier)?|window))?', + 'knob thickness', 'label', 'last( visible)? column', + 'leading offset', 'leaf', 'level', 'line scroll', + 'loaded', 'localized sort', 'location', 'loop mode', + 'main( (bunde|menu|window))?', 'marker follows cell', + 'matrix mode', 'maximum( content)? size', + 'maximum visible columns', + 'menu( form representation)?', 'miniaturizable', + 'miniaturized', 'minimized image', 'minimized title', + 'minimum column width', 'minimum( content)? size', + 'modal', 'modified', 'mouse down state', + 'movie( (controller|file|rect))?', 'muted', 'name', + 'needs display', 'next state', 'next text', + 'number of tick marks', 'only tick mark values', + 'opaque', 'open panel', 'option key down', + 'outline table column', 'page scroll', 'pages across', + 'pages down', 'palette label', 'pane splitter', + 'parent data item', 'parent window', 'pasteboard', + 'path( (names|separator))?', 'playing', + 'plays every frame', 'plays selection only', 'position', + 'preferred edge', 'preferred type', 'pressure', + 'previous text', 'prompt', 'properties', + 'prototype cell', 'pulls down', 'rate', + 'released when closed', 'repeated', + 'requested print time', 'required file type', + 'resizable', 'resized column', 'resource path', + 'returns records', 'reuses columns', 'rich text', + 'roll over', 'row height', 'rulers visible', + 'save panel', 'scripts path', 'scrollable', + 'selectable( identifiers)?', 'selected cell', + 'selected( data)? columns?', 'selected data items?', + 'selected( data)? rows?', 'selected item identifier', + 'selection by rect', 'send action on arrow key', + 'sends action when done editing', 'separates columns', + 'separator item', 'sequence number', 'services menu', + 'shared frameworks path', 'shared support path', + 'sheet', 'shift key down', 'shows alpha', + 'shows state by', 'size( mode)?', + 'smart insert delete enabled', 'sort case sensitivity', + 'sort column', 'sort order', 'sort type', + 'sorted( data rows)?', 'sound', 'source( mask)?', + 'spell checking enabled', 'starting page', 'state', + 'string value', 'sub menu', 'super menu', 'super view', + 'tab key traverses cells', 'tab state', 'tab type', + 'tab view', 'table view', 'tag', 'target( printer)?', + 'text color', 'text container insert', + 'text container origin', 'text returned', + 'tick mark position', 'time stamp', + 'title(d| (cell|font|height|position|rect))?', + 'tool tip', 'toolbar', 'trailing offset', 'transparent', + 'treat packages as directories', 'truncated labels', + 'types', 'unmodified characters', 'update views', + 'use sort indicator', 'user defaults', + 'uses data source', 'uses ruler', + 'uses threaded animation', + 'uses title from previous column', 'value wraps', + 'version', + 'vertical( (line scroll|page scroll|ruler view))?', + 'vertically resizable', 'view', + 'visible( document rect)?', 'volume', 'width', 'window', + 'windows menu', 'wraps', 'zoomable', 'zoomed') + + tokens = { + 'root': [ + (r'\s+', Text), + (r'¬\n', String.Escape), + (r"'s\s+", Text), # This is a possessive, consider moving + (r'(--|#).*?$', Comment), + (r'\(\*', Comment.Multiline, 'comment'), + (r'[(){}!,.:]', Punctuation), + (r'(«)([^»]+)(»)', + bygroups(Text, Name.Builtin, Text)), + (r'\b((?:considering|ignoring)\s*)' + r'(application responses|case|diacriticals|hyphens|' + r'numeric strings|punctuation|white space)', + bygroups(Keyword, Name.Builtin)), + (r'(-|\*|\+|&|≠|>=?|<=?|=|≥|≤|/|÷|\^)', Operator), + (r"\b(%s)\b" % '|'.join(Operators), Operator.Word), + (r'^(\s*(?:on|end)\s+)' + r'(%s)' % '|'.join(StudioEvents[::-1]), + bygroups(Keyword, Name.Function)), + (r'^(\s*)(in|on|script|to)(\s+)', bygroups(Text, Keyword, Text)), + (r'\b(as )(%s)\b' % '|'.join(Classes), + bygroups(Keyword, Name.Class)), + (r'\b(%s)\b' % '|'.join(Literals), Name.Constant), + (r'\b(%s)\b' % '|'.join(Commands), Name.Builtin), + (r'\b(%s)\b' % '|'.join(Control), Keyword), + (r'\b(%s)\b' % '|'.join(Declarations), Keyword), + (r'\b(%s)\b' % '|'.join(Reserved), Name.Builtin), + (r'\b(%s)s?\b' % '|'.join(BuiltIn), Name.Builtin), + (r'\b(%s)\b' % '|'.join(HandlerParams), Name.Builtin), + (r'\b(%s)\b' % '|'.join(StudioProperties), Name.Attribute), + (r'\b(%s)s?\b' % '|'.join(StudioClasses), Name.Builtin), + (r'\b(%s)\b' % '|'.join(StudioCommands), Name.Builtin), + (r'\b(%s)\b' % '|'.join(References), Name.Builtin), + (r'"(\\\\|\\[^\\]|[^"\\])*"', String.Double), + (r'\b(%s)\b' % Identifiers, Name.Variable), + (r'[-+]?(\d+\.\d*|\d*\.\d+)(E[-+][0-9]+)?', Number.Float), + (r'[-+]?\d+', Number.Integer), + ], + 'comment': [ + (r'\(\*', Comment.Multiline, '#push'), + (r'\*\)', Comment.Multiline, '#pop'), + ('[^*(]+', Comment.Multiline), + ('[*(]', Comment.Multiline), + ], + } + + +class RexxLexer(RegexLexer): + """ + `Rexx `_ is a scripting language available for + a wide range of different platforms with its roots found on mainframe + systems. It is popular for I/O- and data based tasks and can act as glue + language to bind different applications together. + + .. versionadded:: 2.0 + """ + name = 'Rexx' + aliases = ['rexx', 'arexx'] + filenames = ['*.rexx', '*.rex', '*.rx', '*.arexx'] + mimetypes = ['text/x-rexx'] + flags = re.IGNORECASE + + tokens = { + 'root': [ + (r'\s+', Whitespace), + (r'/\*', Comment.Multiline, 'comment'), + (r'"', String, 'string_double'), + (r"'", String, 'string_single'), + (r'[0-9]+(\.[0-9]+)?(e[+-]?[0-9])?', Number), + (r'([a-z_]\w*)(\s*)(:)(\s*)(procedure)\b', + bygroups(Name.Function, Whitespace, Operator, Whitespace, + Keyword.Declaration)), + (r'([a-z_]\w*)(\s*)(:)', + bygroups(Name.Label, Whitespace, Operator)), + include('function'), + include('keyword'), + include('operator'), + (r'[a-z_]\w*', Text), + ], + 'function': [ + (words(( + 'abbrev', 'abs', 'address', 'arg', 'b2x', 'bitand', 'bitor', 'bitxor', + 'c2d', 'c2x', 'center', 'charin', 'charout', 'chars', 'compare', + 'condition', 'copies', 'd2c', 'd2x', 'datatype', 'date', 'delstr', + 'delword', 'digits', 'errortext', 'form', 'format', 'fuzz', 'insert', + 'lastpos', 'left', 'length', 'linein', 'lineout', 'lines', 'max', + 'min', 'overlay', 'pos', 'queued', 'random', 'reverse', 'right', 'sign', + 'sourceline', 'space', 'stream', 'strip', 'substr', 'subword', 'symbol', + 'time', 'trace', 'translate', 'trunc', 'value', 'verify', 'word', + 'wordindex', 'wordlength', 'wordpos', 'words', 'x2b', 'x2c', 'x2d', + 'xrange'), suffix=r'(\s*)(\()'), + bygroups(Name.Builtin, Whitespace, Operator)), + ], + 'keyword': [ + (r'(address|arg|by|call|do|drop|else|end|exit|for|forever|if|' + r'interpret|iterate|leave|nop|numeric|off|on|options|parse|' + r'pull|push|queue|return|say|select|signal|to|then|trace|until|' + r'while)\b', Keyword.Reserved), + ], + 'operator': [ + (r'(-|//|/|\(|\)|\*\*|\*|\\<<|\\<|\\==|\\=|\\>>|\\>|\\|\|\||\||' + r'&&|&|%|\+|<<=|<<|<=|<>|<|==|=|><|>=|>>=|>>|>|¬<<|¬<|¬==|¬=|' + r'¬>>|¬>|¬|\.|,)', Operator), + ], + 'string_double': [ + (r'[^"\n]+', String), + (r'""', String), + (r'"', String, '#pop'), + (r'\n', Text, '#pop'), # Stray linefeed also terminates strings. + ], + 'string_single': [ + (r'[^\'\n]+', String), + (r'\'\'', String), + (r'\'', String, '#pop'), + (r'\n', Text, '#pop'), # Stray linefeed also terminates strings. + ], + 'comment': [ + (r'[^*]+', Comment.Multiline), + (r'\*/', Comment.Multiline, '#pop'), + (r'\*', Comment.Multiline), + ] + } + + _c = lambda s: re.compile(s, re.MULTILINE) + _ADDRESS_COMMAND_PATTERN = _c(r'^\s*address\s+command\b') + _ADDRESS_PATTERN = _c(r'^\s*address\s+') + _DO_WHILE_PATTERN = _c(r'^\s*do\s+while\b') + _IF_THEN_DO_PATTERN = _c(r'^\s*if\b.+\bthen\s+do\s*$') + _PROCEDURE_PATTERN = _c(r'^\s*([a-z_]\w*)(\s*)(:)(\s*)(procedure)\b') + _ELSE_DO_PATTERN = _c(r'\belse\s+do\s*$') + _PARSE_ARG_PATTERN = _c(r'^\s*parse\s+(upper\s+)?(arg|value)\b') + PATTERNS_AND_WEIGHTS = ( + (_ADDRESS_COMMAND_PATTERN, 0.2), + (_ADDRESS_PATTERN, 0.05), + (_DO_WHILE_PATTERN, 0.1), + (_ELSE_DO_PATTERN, 0.1), + (_IF_THEN_DO_PATTERN, 0.1), + (_PROCEDURE_PATTERN, 0.5), + (_PARSE_ARG_PATTERN, 0.2), + ) + + def analyse_text(text): + """ + Check for inital comment and patterns that distinguish Rexx from other + C-like languages. + """ + if re.search(r'/\*\**\s*rexx', text, re.IGNORECASE): + # Header matches MVS Rexx requirements, this is certainly a Rexx + # script. + return 1.0 + elif text.startswith('/*'): + # Header matches general Rexx requirements; the source code might + # still be any language using C comments such as C++, C# or Java. + lowerText = text.lower() + result = sum(weight + for (pattern, weight) in RexxLexer.PATTERNS_AND_WEIGHTS + if pattern.search(lowerText)) + 0.01 + return min(result, 1.0) + + +class MOOCodeLexer(RegexLexer): + """ + For `MOOCode `_ (the MOO scripting + language). + + .. versionadded:: 0.9 + """ + name = 'MOOCode' + filenames = ['*.moo'] + aliases = ['moocode', 'moo'] + mimetypes = ['text/x-moocode'] + + tokens = { + 'root': [ + # Numbers + (r'(0|[1-9][0-9_]*)', Number.Integer), + # Strings + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + # exceptions + (r'(E_PERM|E_DIV)', Name.Exception), + # db-refs + (r'((#[-0-9]+)|(\$\w+))', Name.Entity), + # Keywords + (r'\b(if|else|elseif|endif|for|endfor|fork|endfork|while' + r'|endwhile|break|continue|return|try' + r'|except|endtry|finally|in)\b', Keyword), + # builtins + (r'(random|length)', Name.Builtin), + # special variables + (r'(player|caller|this|args)', Name.Variable.Instance), + # skip whitespace + (r'\s+', Text), + (r'\n', Text), + # other operators + (r'([!;=,{}&|:.\[\]@()<>?]+)', Operator), + # function call + (r'(\w+)(\()', bygroups(Name.Function, Operator)), + # variables + (r'(\w+)', Text), + ] + } + + +class HybrisLexer(RegexLexer): + """ + For `Hybris `_ source code. + + .. versionadded:: 1.4 + """ + + name = 'Hybris' + aliases = ['hybris', 'hy'] + filenames = ['*.hy', '*.hyb'] + mimetypes = ['text/x-hybris', 'application/x-hybris'] + + flags = re.MULTILINE | re.DOTALL + + tokens = { + 'root': [ + # method names + (r'^(\s*(?:function|method|operator\s+)+?)' + r'([a-zA-Z_]\w*)' + r'(\s*)(\()', bygroups(Keyword, Name.Function, Text, Operator)), + (r'[^\S\n]+', Text), + (r'//.*?\n', Comment.Single), + (r'/\*.*?\*/', Comment.Multiline), + (r'@[a-zA-Z_][\w.]*', Name.Decorator), + (r'(break|case|catch|next|default|do|else|finally|for|foreach|of|' + r'unless|if|new|return|switch|me|throw|try|while)\b', Keyword), + (r'(extends|private|protected|public|static|throws|function|method|' + r'operator)\b', Keyword.Declaration), + (r'(true|false|null|__FILE__|__LINE__|__VERSION__|__LIB_PATH__|' + r'__INC_PATH__)\b', Keyword.Constant), + (r'(class|struct)(\s+)', + bygroups(Keyword.Declaration, Text), 'class'), + (r'(import|include)(\s+)', + bygroups(Keyword.Namespace, Text), 'import'), + (words(( + 'gc_collect', 'gc_mm_items', 'gc_mm_usage', 'gc_collect_threshold', + 'urlencode', 'urldecode', 'base64encode', 'base64decode', 'sha1', 'crc32', + 'sha2', 'md5', 'md5_file', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', + 'cosh', 'exp', 'fabs', 'floor', 'fmod', 'log', 'log10', 'pow', 'sin', + 'sinh', 'sqrt', 'tan', 'tanh', 'isint', 'isfloat', 'ischar', 'isstring', + 'isarray', 'ismap', 'isalias', 'typeof', 'sizeof', 'toint', 'tostring', + 'fromxml', 'toxml', 'binary', 'pack', 'load', 'eval', 'var_names', + 'var_values', 'user_functions', 'dyn_functions', 'methods', 'call', + 'call_method', 'mknod', 'mkfifo', 'mount', 'umount2', 'umount', 'ticks', + 'usleep', 'sleep', 'time', 'strtime', 'strdate', 'dllopen', 'dlllink', + 'dllcall', 'dllcall_argv', 'dllclose', 'env', 'exec', 'fork', 'getpid', + 'wait', 'popen', 'pclose', 'exit', 'kill', 'pthread_create', + 'pthread_create_argv', 'pthread_exit', 'pthread_join', 'pthread_kill', + 'smtp_send', 'http_get', 'http_post', 'http_download', 'socket', 'bind', + 'listen', 'accept', 'getsockname', 'getpeername', 'settimeout', 'connect', + 'server', 'recv', 'send', 'close', 'print', 'println', 'printf', 'input', + 'readline', 'serial_open', 'serial_fcntl', 'serial_get_attr', + 'serial_get_ispeed', 'serial_get_ospeed', 'serial_set_attr', + 'serial_set_ispeed', 'serial_set_ospeed', 'serial_write', 'serial_read', + 'serial_close', 'xml_load', 'xml_parse', 'fopen', 'fseek', 'ftell', + 'fsize', 'fread', 'fwrite', 'fgets', 'fclose', 'file', 'readdir', + 'pcre_replace', 'size', 'pop', 'unmap', 'has', 'keys', 'values', + 'length', 'find', 'substr', 'replace', 'split', 'trim', 'remove', + 'contains', 'join'), suffix=r'\b'), + Name.Builtin), + (words(( + 'MethodReference', 'Runner', 'Dll', 'Thread', 'Pipe', 'Process', + 'Runnable', 'CGI', 'ClientSocket', 'Socket', 'ServerSocket', + 'File', 'Console', 'Directory', 'Exception'), suffix=r'\b'), + Keyword.Type), + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + (r"'\\.'|'[^\\]'|'\\u[0-9a-f]{4}'", String.Char), + (r'(\.)([a-zA-Z_]\w*)', + bygroups(Operator, Name.Attribute)), + (r'[a-zA-Z_]\w*:', Name.Label), + (r'[a-zA-Z_$]\w*', Name), + (r'[~^*!%&\[\](){}<>|+=:;,./?\-@]+', Operator), + (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float), + (r'0x[0-9a-f]+', Number.Hex), + (r'[0-9]+L?', Number.Integer), + (r'\n', Text), + ], + 'class': [ + (r'[a-zA-Z_]\w*', Name.Class, '#pop') + ], + 'import': [ + (r'[\w.]+\*?', Name.Namespace, '#pop') + ], + } + + def analyse_text(text): + """public method and private method don't seem to be quite common + elsewhere.""" + result = 0 + if re.search(r'\b(?:public|private)\s+method\b', text): + result += 0.01 + return result + + + +class EasytrieveLexer(RegexLexer): + """ + Easytrieve Plus is a programming language for extracting, filtering and + converting sequential data. Furthermore it can layout data for reports. + It is mainly used on mainframe platforms and can access several of the + mainframe's native file formats. It is somewhat comparable to awk. + + .. versionadded:: 2.1 + """ + name = 'Easytrieve' + aliases = ['easytrieve'] + filenames = ['*.ezt', '*.mac'] + mimetypes = ['text/x-easytrieve'] + flags = 0 + + # Note: We cannot use r'\b' at the start and end of keywords because + # Easytrieve Plus delimiter characters are: + # + # * space ( ) + # * apostrophe (') + # * period (.) + # * comma (,) + # * paranthesis ( and ) + # * colon (:) + # + # Additionally words end once a '*' appears, indicatins a comment. + _DELIMITERS = r' \'.,():\n' + _DELIMITERS_OR_COMENT = _DELIMITERS + '*' + _DELIMITER_PATTERN = '[' + _DELIMITERS + ']' + _DELIMITER_PATTERN_CAPTURE = '(' + _DELIMITER_PATTERN + ')' + _NON_DELIMITER_OR_COMMENT_PATTERN = '[^' + _DELIMITERS_OR_COMENT + ']' + _OPERATORS_PATTERN = '[.+\\-/=\\[\\](){}<>;,&%¬]' + _KEYWORDS = [ + 'AFTER-BREAK', 'AFTER-LINE', 'AFTER-SCREEN', 'AIM', 'AND', 'ATTR', + 'BEFORE', 'BEFORE-BREAK', 'BEFORE-LINE', 'BEFORE-SCREEN', 'BUSHU', + 'BY', 'CALL', 'CASE', 'CHECKPOINT', 'CHKP', 'CHKP-STATUS', 'CLEAR', + 'CLOSE', 'COL', 'COLOR', 'COMMIT', 'CONTROL', 'COPY', 'CURSOR', 'D', + 'DECLARE', 'DEFAULT', 'DEFINE', 'DELETE', 'DENWA', 'DISPLAY', 'DLI', + 'DO', 'DUPLICATE', 'E', 'ELSE', 'ELSE-IF', 'END', 'END-CASE', + 'END-DO', 'END-IF', 'END-PROC', 'ENDPAGE', 'ENDTABLE', 'ENTER', 'EOF', + 'EQ', 'ERROR', 'EXIT', 'EXTERNAL', 'EZLIB', 'F1', 'F10', 'F11', 'F12', + 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F2', 'F20', 'F21', + 'F22', 'F23', 'F24', 'F25', 'F26', 'F27', 'F28', 'F29', 'F3', 'F30', + 'F31', 'F32', 'F33', 'F34', 'F35', 'F36', 'F4', 'F5', 'F6', 'F7', + 'F8', 'F9', 'FETCH', 'FILE-STATUS', 'FILL', 'FINAL', 'FIRST', + 'FIRST-DUP', 'FOR', 'GE', 'GET', 'GO', 'GOTO', 'GQ', 'GR', 'GT', + 'HEADING', 'HEX', 'HIGH-VALUES', 'IDD', 'IDMS', 'IF', 'IN', 'INSERT', + 'JUSTIFY', 'KANJI-DATE', 'KANJI-DATE-LONG', 'KANJI-TIME', 'KEY', + 'KEY-PRESSED', 'KOKUGO', 'KUN', 'LAST-DUP', 'LE', 'LEVEL', 'LIKE', + 'LINE', 'LINE-COUNT', 'LINE-NUMBER', 'LINK', 'LIST', 'LOW-VALUES', + 'LQ', 'LS', 'LT', 'MACRO', 'MASK', 'MATCHED', 'MEND', 'MESSAGE', + 'MOVE', 'MSTART', 'NE', 'NEWPAGE', 'NOMASK', 'NOPRINT', 'NOT', + 'NOTE', 'NOVERIFY', 'NQ', 'NULL', 'OF', 'OR', 'OTHERWISE', 'PA1', + 'PA2', 'PA3', 'PAGE-COUNT', 'PAGE-NUMBER', 'PARM-REGISTER', + 'PATH-ID', 'PATTERN', 'PERFORM', 'POINT', 'POS', 'PRIMARY', 'PRINT', + 'PROCEDURE', 'PROGRAM', 'PUT', 'READ', 'RECORD', 'RECORD-COUNT', + 'RECORD-LENGTH', 'REFRESH', 'RELEASE', 'RENUM', 'REPEAT', 'REPORT', + 'REPORT-INPUT', 'RESHOW', 'RESTART', 'RETRIEVE', 'RETURN-CODE', + 'ROLLBACK', 'ROW', 'S', 'SCREEN', 'SEARCH', 'SECONDARY', 'SELECT', + 'SEQUENCE', 'SIZE', 'SKIP', 'SOKAKU', 'SORT', 'SQL', 'STOP', 'SUM', + 'SYSDATE', 'SYSDATE-LONG', 'SYSIN', 'SYSIPT', 'SYSLST', 'SYSPRINT', + 'SYSSNAP', 'SYSTIME', 'TALLY', 'TERM-COLUMNS', 'TERM-NAME', + 'TERM-ROWS', 'TERMINATION', 'TITLE', 'TO', 'TRANSFER', 'TRC', + 'UNIQUE', 'UNTIL', 'UPDATE', 'UPPERCASE', 'USER', 'USERID', 'VALUE', + 'VERIFY', 'W', 'WHEN', 'WHILE', 'WORK', 'WRITE', 'X', 'XDM', 'XRST' + ] + + tokens = { + 'root': [ + (r'\*.*\n', Comment.Single), + (r'\n+', Whitespace), + # Macro argument + (r'&' + _NON_DELIMITER_OR_COMMENT_PATTERN + r'+\.', Name.Variable, + 'after_macro_argument'), + # Macro call + (r'%' + _NON_DELIMITER_OR_COMMENT_PATTERN + r'+', Name.Variable), + (r'(FILE|MACRO|REPORT)(\s+)', + bygroups(Keyword.Declaration, Whitespace), 'after_declaration'), + (r'(JOB|PARM)' + r'(' + _DELIMITER_PATTERN + r')', + bygroups(Keyword.Declaration, Operator)), + (words(_KEYWORDS, suffix=_DELIMITER_PATTERN_CAPTURE), + bygroups(Keyword.Reserved, Operator)), + (_OPERATORS_PATTERN, Operator), + # Procedure declaration + (r'(' + _NON_DELIMITER_OR_COMMENT_PATTERN + r'+)(\s*)(\.?)(\s*)(PROC)(\s*\n)', + bygroups(Name.Function, Whitespace, Operator, Whitespace, + Keyword.Declaration, Whitespace)), + (r'[0-9]+\.[0-9]*', Number.Float), + (r'[0-9]+', Number.Integer), + (r"'(''|[^'])*'", String), + (r'\s+', Whitespace), + # Everything else just belongs to a name + (_NON_DELIMITER_OR_COMMENT_PATTERN + r'+', Name), + ], + 'after_declaration': [ + (_NON_DELIMITER_OR_COMMENT_PATTERN + r'+', Name.Function), + default('#pop'), + ], + 'after_macro_argument': [ + (r'\*.*\n', Comment.Single, '#pop'), + (r'\s+', Whitespace, '#pop'), + (_OPERATORS_PATTERN, Operator, '#pop'), + (r"'(''|[^'])*'", String, '#pop'), + # Everything else just belongs to a name + (_NON_DELIMITER_OR_COMMENT_PATTERN + r'+', Name), + ], + } + _COMMENT_LINE_REGEX = re.compile(r'^\s*\*') + _MACRO_HEADER_REGEX = re.compile(r'^\s*MACRO') + + def analyse_text(text): + """ + Perform a structural analysis for basic Easytrieve constructs. + """ + result = 0.0 + lines = text.split('\n') + hasEndProc = False + hasHeaderComment = False + hasFile = False + hasJob = False + hasProc = False + hasParm = False + hasReport = False + + def isCommentLine(line): + return EasytrieveLexer._COMMENT_LINE_REGEX.match(lines[0]) is not None + + def isEmptyLine(line): + return not bool(line.strip()) + + # Remove possible empty lines and header comments. + while lines and (isEmptyLine(lines[0]) or isCommentLine(lines[0])): + if not isEmptyLine(lines[0]): + hasHeaderComment = True + del lines[0] + + if EasytrieveLexer._MACRO_HEADER_REGEX.match(lines[0]): + # Looks like an Easytrieve macro. + result = 0.4 + if hasHeaderComment: + result += 0.4 + else: + # Scan the source for lines starting with indicators. + for line in lines: + words = line.split() + if (len(words) >= 2): + firstWord = words[0] + if not hasReport: + if not hasJob: + if not hasFile: + if not hasParm: + if firstWord == 'PARM': + hasParm = True + if firstWord == 'FILE': + hasFile = True + if firstWord == 'JOB': + hasJob = True + elif firstWord == 'PROC': + hasProc = True + elif firstWord == 'END-PROC': + hasEndProc = True + elif firstWord == 'REPORT': + hasReport = True + + # Weight the findings. + if hasJob and (hasProc == hasEndProc): + if hasHeaderComment: + result += 0.1 + if hasParm: + if hasProc: + # Found PARM, JOB and PROC/END-PROC: + # pretty sure this is Easytrieve. + result += 0.8 + else: + # Found PARAM and JOB: probably this is Easytrieve + result += 0.5 + else: + # Found JOB and possibly other keywords: might be Easytrieve + result += 0.11 + if hasParm: + # Note: PARAM is not a proper English word, so this is + # regarded a much better indicator for Easytrieve than + # the other words. + result += 0.2 + if hasFile: + result += 0.01 + if hasReport: + result += 0.01 + assert 0.0 <= result <= 1.0 + return result + + +class JclLexer(RegexLexer): + """ + `Job Control Language (JCL) + `_ + is a scripting language used on mainframe platforms to instruct the system + on how to run a batch job or start a subsystem. It is somewhat + comparable to MS DOS batch and Unix shell scripts. + + .. versionadded:: 2.1 + """ + name = 'JCL' + aliases = ['jcl'] + filenames = ['*.jcl'] + mimetypes = ['text/x-jcl'] + flags = re.IGNORECASE + + tokens = { + 'root': [ + (r'//\*.*\n', Comment.Single), + (r'//', Keyword.Pseudo, 'statement'), + (r'/\*', Keyword.Pseudo, 'jes2_statement'), + # TODO: JES3 statement + (r'.*\n', Other) # Input text or inline code in any language. + ], + 'statement': [ + (r'\s*\n', Whitespace, '#pop'), + (r'([a-z]\w*)(\s+)(exec|job)(\s*)', + bygroups(Name.Label, Whitespace, Keyword.Reserved, Whitespace), + 'option'), + (r'[a-z]\w*', Name.Variable, 'statement_command'), + (r'\s+', Whitespace, 'statement_command'), + ], + 'statement_command': [ + (r'\s+(command|cntl|dd|endctl|endif|else|include|jcllib|' + r'output|pend|proc|set|then|xmit)\s+', Keyword.Reserved, 'option'), + include('option') + ], + 'jes2_statement': [ + (r'\s*\n', Whitespace, '#pop'), + (r'\$', Keyword, 'option'), + (r'\b(jobparam|message|netacct|notify|output|priority|route|' + r'setup|signoff|xeq|xmit)\b', Keyword, 'option'), + ], + 'option': [ + # (r'\n', Text, 'root'), + (r'\*', Name.Builtin), + (r'[\[\](){}<>;,]', Punctuation), + (r'[-+*/=&%]', Operator), + (r'[a-z_]\w*', Name), + (r'\d+\.\d*', Number.Float), + (r'\.\d+', Number.Float), + (r'\d+', Number.Integer), + (r"'", String, 'option_string'), + (r'[ \t]+', Whitespace, 'option_comment'), + (r'\.', Punctuation), + ], + 'option_string': [ + (r"(\n)(//)", bygroups(Text, Keyword.Pseudo)), + (r"''", String), + (r"[^']", String), + (r"'", String, '#pop'), + ], + 'option_comment': [ + # (r'\n', Text, 'root'), + (r'.+', Comment.Single), + ] + } + + _JOB_HEADER_PATTERN = re.compile(r'^//[a-z#$@][a-z0-9#$@]{0,7}\s+job(\s+.*)?$', + re.IGNORECASE) + + def analyse_text(text): + """ + Recognize JCL job by header. + """ + result = 0.0 + lines = text.split('\n') + if len(lines) > 0: + if JclLexer._JOB_HEADER_PATTERN.match(lines[0]): + result = 1.0 + assert 0.0 <= result <= 1.0 + return result + + +class MiniScriptLexer(RegexLexer): + """ + For `MiniScript `_ source code. + + .. versionadded:: 2.6 + """ + + name = 'MiniScript' + aliases = ['miniscript', 'ms'] + filenames = ['*.ms'] + mimetypes = ['text/x-minicript', 'application/x-miniscript'] + + tokens = { + 'root': [ + (r'#!(.*?)$', Comment.Preproc), + default('base'), + ], + 'base': [ + ('//.*$', Comment.Single), + (r'(?i)(\d*\.\d+|\d+\.\d*)(e[+-]?\d+)?', Number), + (r'(?i)\d+e[+-]?\d+', Number), + (r'\d+', Number), + (r'\n', Text), + (r'[^\S\n]+', Text), + (r'"', String, 'string_double'), + (r'(==|!=|<=|>=|[=+\-*/%^<>.:])', Operator), + (r'[;,\[\]{}()]', Punctuation), + (words(( + 'break', 'continue', 'else', 'end', 'for', 'function', 'if', + 'in', 'isa', 'then', 'repeat', 'return', 'while'), suffix=r'\b'), + Keyword), + (words(( + 'abs', 'acos', 'asin', 'atan', 'ceil', 'char', 'cos', 'floor', + 'log', 'round', 'rnd', 'pi', 'sign', 'sin', 'sqrt', 'str', 'tan', + 'hasIndex', 'indexOf', 'len', 'val', 'code', 'remove', 'lower', + 'upper', 'replace', 'split', 'indexes', 'values', 'join', 'sum', + 'sort', 'shuffle', 'push', 'pop', 'pull', 'range', + 'print', 'input', 'time', 'wait', 'locals', 'globals', 'outer', + 'yield'), suffix=r'\b'), + Name.Builtin), + (r'(true|false|null)\b', Keyword.Constant), + (r'(and|or|not|new)\b', Operator.Word), + (r'(self|super|__isa)\b', Name.Builtin.Pseudo), + (r'[a-zA-Z_]\w*', Name.Variable) + ], + 'string_double': [ + (r'[^"\n]+', String), + (r'""', String), + (r'"', String, '#pop'), + (r'\n', Text, '#pop'), # Stray linefeed also terminates strings. + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/sgf.py b/.venv/lib/python3.8/site-packages/pygments/lexers/sgf.py new file mode 100644 index 0000000..35c90ff --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/sgf.py @@ -0,0 +1,61 @@ +""" + pygments.lexers.sgf + ~~~~~~~~~~~~~~~~~~~ + + Lexer for Smart Game Format (sgf) file format. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, bygroups +from pygments.token import Name, Literal, String, Text, Punctuation, Whitespace + +__all__ = ["SmartGameFormatLexer"] + + +class SmartGameFormatLexer(RegexLexer): + """ + Lexer for Smart Game Format (sgf) file format. + + The format is used to store game records of board games for two players + (mainly Go game). + For more information about the definition of the format, see: + https://www.red-bean.com/sgf/ + + .. versionadded:: 2.4 + """ + name = 'SmartGameFormat' + aliases = ['sgf'] + filenames = ['*.sgf'] + + tokens = { + 'root': [ + (r'[():;]+', Punctuation), + # tokens: + (r'(A[BW]|AE|AN|AP|AR|AS|[BW]L|BM|[BW]R|[BW]S|[BW]T|CA|CH|CP|CR|' + r'DD|DM|DO|DT|EL|EV|EX|FF|FG|G[BW]|GC|GM|GN|HA|HO|ID|IP|IT|IY|KM|' + r'KO|LB|LN|LT|L|MA|MN|M|N|OB|OM|ON|OP|OT|OV|P[BW]|PC|PL|PM|RE|RG|' + r'RO|RU|SO|SC|SE|SI|SL|SO|SQ|ST|SU|SZ|T[BW]|TC|TE|TM|TR|UC|US|VW|' + r'V|[BW]|C)', + Name.Builtin), + # number: + (r'(\[)([0-9.]+)(\])', + bygroups(Punctuation, Literal.Number, Punctuation)), + # date: + (r'(\[)([0-9]{4}-[0-9]{2}-[0-9]{2})(\])', + bygroups(Punctuation, Literal.Date, Punctuation)), + # point: + (r'(\[)([a-z]{2})(\])', + bygroups(Punctuation, String, Punctuation)), + # double points: + (r'(\[)([a-z]{2})(:)([a-z]{2})(\])', + bygroups(Punctuation, String, Punctuation, String, Punctuation)), + + (r'(\[)([\w\s#()+,\-.:?]+)(\])', + bygroups(Punctuation, String, Punctuation)), + (r'(\[)(\s.*)(\])', + bygroups(Punctuation, Whitespace, Punctuation)), + (r'\s+', Whitespace) + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/shell.py b/.venv/lib/python3.8/site-packages/pygments/lexers/shell.py new file mode 100644 index 0000000..fd26a4b --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/shell.py @@ -0,0 +1,913 @@ +""" + pygments.lexers.shell + ~~~~~~~~~~~~~~~~~~~~~ + + Lexers for various shells. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import Lexer, RegexLexer, do_insertions, bygroups, \ + include, default, this, using, words +from pygments.token import Punctuation, \ + Text, Comment, Operator, Keyword, Name, String, Number, Generic +from pygments.util import shebang_matches + + +__all__ = ['BashLexer', 'BashSessionLexer', 'TcshLexer', 'BatchLexer', + 'SlurmBashLexer', 'MSDOSSessionLexer', 'PowerShellLexer', + 'PowerShellSessionLexer', 'TcshSessionLexer', 'FishShellLexer', + 'ExeclineLexer'] + +line_re = re.compile('.*?\n') + + +class BashLexer(RegexLexer): + """ + Lexer for (ba|k|z|)sh shell scripts. + + .. versionadded:: 0.6 + """ + + name = 'Bash' + aliases = ['bash', 'sh', 'ksh', 'zsh', 'shell'] + filenames = ['*.sh', '*.ksh', '*.bash', '*.ebuild', '*.eclass', + '*.exheres-0', '*.exlib', '*.zsh', + '.bashrc', 'bashrc', '.bash_*', 'bash_*', 'zshrc', '.zshrc', + '.kshrc', 'kshrc', + 'PKGBUILD'] + mimetypes = ['application/x-sh', 'application/x-shellscript', 'text/x-shellscript'] + + tokens = { + 'root': [ + include('basic'), + (r'`', String.Backtick, 'backticks'), + include('data'), + include('interp'), + ], + 'interp': [ + (r'\$\(\(', Keyword, 'math'), + (r'\$\(', Keyword, 'paren'), + (r'\$\{#?', String.Interpol, 'curly'), + (r'\$[a-zA-Z_]\w*', Name.Variable), # user variable + (r'\$(?:\d+|[#$?!_*@-])', Name.Variable), # builtin + (r'\$', Text), + ], + 'basic': [ + (r'\b(if|fi|else|while|in|do|done|for|then|return|function|case|' + r'select|continue|until|esac|elif)(\s*)\b', + bygroups(Keyword, Text)), + (r'\b(alias|bg|bind|break|builtin|caller|cd|command|compgen|' + r'complete|declare|dirs|disown|echo|enable|eval|exec|exit|' + r'export|false|fc|fg|getopts|hash|help|history|jobs|kill|let|' + r'local|logout|popd|printf|pushd|pwd|read|readonly|set|shift|' + r'shopt|source|suspend|test|time|times|trap|true|type|typeset|' + r'ulimit|umask|unalias|unset|wait)(?=[\s)`])', + Name.Builtin), + (r'\A#!.+\n', Comment.Hashbang), + (r'#.*\n', Comment.Single), + (r'\\[\w\W]', String.Escape), + (r'(\b\w+)(\s*)(\+?=)', bygroups(Name.Variable, Text, Operator)), + (r'[\[\]{}()=]', Operator), + (r'<<<', Operator), # here-string + (r'<<-?\s*(\'?)\\?(\w+)[\w\W]+?\2', String), + (r'&&|\|\|', Operator), + ], + 'data': [ + (r'(?s)\$?"(\\.|[^"\\$])*"', String.Double), + (r'"', String.Double, 'string'), + (r"(?s)\$'(\\\\|\\[0-7]+|\\.|[^'\\])*'", String.Single), + (r"(?s)'.*?'", String.Single), + (r';', Punctuation), + (r'&', Punctuation), + (r'\|', Punctuation), + (r'\s+', Text), + (r'\d+\b', Number), + (r'[^=\s\[\]{}()$"\'`\\<&|;]+', Text), + (r'<', Text), + ], + 'string': [ + (r'"', String.Double, '#pop'), + (r'(?s)(\\\\|\\[0-7]+|\\.|[^"\\$])+', String.Double), + include('interp'), + ], + 'curly': [ + (r'\}', String.Interpol, '#pop'), + (r':-', Keyword), + (r'\w+', Name.Variable), + (r'[^}:"\'`$\\]+', Punctuation), + (r':', Punctuation), + include('root'), + ], + 'paren': [ + (r'\)', Keyword, '#pop'), + include('root'), + ], + 'math': [ + (r'\)\)', Keyword, '#pop'), + (r'[-+*/%^|&]|\*\*|\|\|', Operator), + (r'\d+#\d+', Number), + (r'\d+#(?! )', Number), + (r'\d+', Number), + include('root'), + ], + 'backticks': [ + (r'`', String.Backtick, '#pop'), + include('root'), + ], + } + + def analyse_text(text): + if shebang_matches(text, r'(ba|z|)sh'): + return 1 + if text.startswith('$ '): + return 0.2 + + +class SlurmBashLexer(BashLexer): + """ + Lexer for (ba|k|z|)sh Slurm scripts. + + .. versionadded:: 2.4 + """ + + name = 'Slurm' + aliases = ['slurm', 'sbatch'] + filenames = ['*.sl'] + mimetypes = [] + EXTRA_KEYWORDS = {'srun'} + + def get_tokens_unprocessed(self, text): + for index, token, value in BashLexer.get_tokens_unprocessed(self, text): + if token is Text and value in self.EXTRA_KEYWORDS: + yield index, Name.Builtin, value + elif token is Comment.Single and 'SBATCH' in value: + yield index, Keyword.Pseudo, value + else: + yield index, token, value + + +class ShellSessionBaseLexer(Lexer): + """ + Base lexer for shell sessions. + + .. versionadded:: 2.1 + """ + + _venv = re.compile(r'^(\([^)]*\))(\s*)') + + def get_tokens_unprocessed(self, text): + innerlexer = self._innerLexerCls(**self.options) + + pos = 0 + curcode = '' + insertions = [] + backslash_continuation = False + + for match in line_re.finditer(text): + line = match.group() + + venv_match = self._venv.match(line) + if venv_match: + venv = venv_match.group(1) + venv_whitespace = venv_match.group(2) + insertions.append((len(curcode), + [(0, Generic.Prompt.VirtualEnv, venv)])) + if venv_whitespace: + insertions.append((len(curcode), + [(0, Text, venv_whitespace)])) + line = line[venv_match.end():] + + m = self._ps1rgx.match(line) + if m: + # To support output lexers (say diff output), the output + # needs to be broken by prompts whenever the output lexer + # changes. + if not insertions: + pos = match.start() + + insertions.append((len(curcode), + [(0, Generic.Prompt, m.group(1))])) + curcode += m.group(2) + backslash_continuation = curcode.endswith('\\\n') + elif backslash_continuation: + if line.startswith(self._ps2): + insertions.append((len(curcode), + [(0, Generic.Prompt, line[:len(self._ps2)])])) + curcode += line[len(self._ps2):] + else: + curcode += line + backslash_continuation = curcode.endswith('\\\n') + else: + if insertions: + toks = innerlexer.get_tokens_unprocessed(curcode) + for i, t, v in do_insertions(insertions, toks): + yield pos+i, t, v + yield match.start(), Generic.Output, line + insertions = [] + curcode = '' + if insertions: + for i, t, v in do_insertions(insertions, + innerlexer.get_tokens_unprocessed(curcode)): + yield pos+i, t, v + + +class BashSessionLexer(ShellSessionBaseLexer): + """ + Lexer for Bash shell sessions, i.e. command lines, including a + prompt, interspersed with output. + + .. versionadded:: 1.1 + """ + + name = 'Bash Session' + aliases = ['console', 'shell-session'] + filenames = ['*.sh-session', '*.shell-session'] + mimetypes = ['application/x-shell-session', 'application/x-sh-session'] + + _innerLexerCls = BashLexer + _ps1rgx = re.compile( + r'^((?:(?:\[.*?\])|(?:\(\S+\))?(?:| |sh\S*?|\w+\S+[@:]\S+(?:\s+\S+)' \ + r'?|\[\S+[@:][^\n]+\].+))\s*[$#%]\s*)(.*\n?)') + _ps2 = '> ' + + +class BatchLexer(RegexLexer): + """ + Lexer for the DOS/Windows Batch file format. + + .. versionadded:: 0.7 + """ + name = 'Batchfile' + aliases = ['batch', 'bat', 'dosbatch', 'winbatch'] + filenames = ['*.bat', '*.cmd'] + mimetypes = ['application/x-dos-batch'] + + flags = re.MULTILINE | re.IGNORECASE + + _nl = r'\n\x1a' + _punct = r'&<>|' + _ws = r'\t\v\f\r ,;=\xa0' + _nlws = r'\s\x1a\xa0,;=' + _space = r'(?:(?:(?:\^[%s])?[%s])+)' % (_nl, _ws) + _keyword_terminator = (r'(?=(?:\^[%s]?)?[%s+./:[\\\]]|[%s%s(])' % + (_nl, _ws, _nl, _punct)) + _token_terminator = r'(?=\^?[%s]|[%s%s])' % (_ws, _punct, _nl) + _start_label = r'((?:(?<=^[^:])|^[^:]?)[%s]*)(:)' % _ws + _label = r'(?:(?:[^%s%s+:^]|\^[%s]?[\w\W])*)' % (_nlws, _punct, _nl) + _label_compound = r'(?:(?:[^%s%s+:^)]|\^[%s]?[^)])*)' % (_nlws, _punct, _nl) + _number = r'(?:-?(?:0[0-7]+|0x[\da-f]+|\d+)%s)' % _token_terminator + _opword = r'(?:equ|geq|gtr|leq|lss|neq)' + _string = r'(?:"[^%s"]*(?:"|(?=[%s])))' % (_nl, _nl) + _variable = (r'(?:(?:%%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|' + r'[^%%:%s]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%%%s^]|' + r'\^[^%%%s])[^=%s]*=(?:[^%%%s^]|\^[^%%%s])*)?)?%%))|' + r'(?:\^?![^!:%s]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:' + r'[^!%s^]|\^[^!%s])[^=%s]*=(?:[^!%s^]|\^[^!%s])*)?)?\^?!))' % + (_nl, _nl, _nl, _nl, _nl, _nl, _nl, _nl, _nl, _nl, _nl, _nl)) + _core_token = r'(?:(?:(?:\^[%s]?)?[^"%s%s])+)' % (_nl, _nlws, _punct) + _core_token_compound = r'(?:(?:(?:\^[%s]?)?[^"%s%s)])+)' % (_nl, _nlws, _punct) + _token = r'(?:[%s]+|%s)' % (_punct, _core_token) + _token_compound = r'(?:[%s]+|%s)' % (_punct, _core_token_compound) + _stoken = (r'(?:[%s]+|(?:%s|%s|%s)+)' % + (_punct, _string, _variable, _core_token)) + + def _make_begin_state(compound, _core_token=_core_token, + _core_token_compound=_core_token_compound, + _keyword_terminator=_keyword_terminator, + _nl=_nl, _punct=_punct, _string=_string, + _space=_space, _start_label=_start_label, + _stoken=_stoken, _token_terminator=_token_terminator, + _variable=_variable, _ws=_ws): + rest = '(?:%s|%s|[^"%%%s%s%s])*' % (_string, _variable, _nl, _punct, + ')' if compound else '') + rest_of_line = r'(?:(?:[^%s^]|\^[%s]?[\w\W])*)' % (_nl, _nl) + rest_of_line_compound = r'(?:(?:[^%s^)]|\^[%s]?[^)])*)' % (_nl, _nl) + set_space = r'((?:(?:\^[%s]?)?[^\S\n])*)' % _nl + suffix = '' + if compound: + _keyword_terminator = r'(?:(?=\))|%s)' % _keyword_terminator + _token_terminator = r'(?:(?=\))|%s)' % _token_terminator + suffix = '/compound' + return [ + ((r'\)', Punctuation, '#pop') if compound else + (r'\)((?=\()|%s)%s' % (_token_terminator, rest_of_line), + Comment.Single)), + (r'(?=%s)' % _start_label, Text, 'follow%s' % suffix), + (_space, using(this, state='text')), + include('redirect%s' % suffix), + (r'[%s]+' % _nl, Text), + (r'\(', Punctuation, 'root/compound'), + (r'@+', Punctuation), + (r'((?:for|if|rem)(?:(?=(?:\^[%s]?)?/)|(?:(?!\^)|' + r'(?<=m))(?:(?=\()|%s)))(%s?%s?(?:\^[%s]?)?/(?:\^[%s]?)?\?)' % + (_nl, _token_terminator, _space, + _core_token_compound if compound else _core_token, _nl, _nl), + bygroups(Keyword, using(this, state='text')), + 'follow%s' % suffix), + (r'(goto%s)(%s(?:\^[%s]?)?/(?:\^[%s]?)?\?%s)' % + (_keyword_terminator, rest, _nl, _nl, rest), + bygroups(Keyword, using(this, state='text')), + 'follow%s' % suffix), + (words(('assoc', 'break', 'cd', 'chdir', 'cls', 'color', 'copy', + 'date', 'del', 'dir', 'dpath', 'echo', 'endlocal', 'erase', + 'exit', 'ftype', 'keys', 'md', 'mkdir', 'mklink', 'move', + 'path', 'pause', 'popd', 'prompt', 'pushd', 'rd', 'ren', + 'rename', 'rmdir', 'setlocal', 'shift', 'start', 'time', + 'title', 'type', 'ver', 'verify', 'vol'), + suffix=_keyword_terminator), Keyword, 'follow%s' % suffix), + (r'(call)(%s?)(:)' % _space, + bygroups(Keyword, using(this, state='text'), Punctuation), + 'call%s' % suffix), + (r'call%s' % _keyword_terminator, Keyword), + (r'(for%s(?!\^))(%s)(/f%s)' % + (_token_terminator, _space, _token_terminator), + bygroups(Keyword, using(this, state='text'), Keyword), + ('for/f', 'for')), + (r'(for%s(?!\^))(%s)(/l%s)' % + (_token_terminator, _space, _token_terminator), + bygroups(Keyword, using(this, state='text'), Keyword), + ('for/l', 'for')), + (r'for%s(?!\^)' % _token_terminator, Keyword, ('for2', 'for')), + (r'(goto%s)(%s?)(:?)' % (_keyword_terminator, _space), + bygroups(Keyword, using(this, state='text'), Punctuation), + 'label%s' % suffix), + (r'(if(?:(?=\()|%s)(?!\^))(%s?)((?:/i%s)?)(%s?)((?:not%s)?)(%s?)' % + (_token_terminator, _space, _token_terminator, _space, + _token_terminator, _space), + bygroups(Keyword, using(this, state='text'), Keyword, + using(this, state='text'), Keyword, + using(this, state='text')), ('(?', 'if')), + (r'rem(((?=\()|%s)%s?%s?.*|%s%s)' % + (_token_terminator, _space, _stoken, _keyword_terminator, + rest_of_line_compound if compound else rest_of_line), + Comment.Single, 'follow%s' % suffix), + (r'(set%s)%s(/a)' % (_keyword_terminator, set_space), + bygroups(Keyword, using(this, state='text'), Keyword), + 'arithmetic%s' % suffix), + (r'(set%s)%s((?:/p)?)%s((?:(?:(?:\^[%s]?)?[^"%s%s^=%s]|' + r'\^[%s]?[^"=])+)?)((?:(?:\^[%s]?)?=)?)' % + (_keyword_terminator, set_space, set_space, _nl, _nl, _punct, + ')' if compound else '', _nl, _nl), + bygroups(Keyword, using(this, state='text'), Keyword, + using(this, state='text'), using(this, state='variable'), + Punctuation), + 'follow%s' % suffix), + default('follow%s' % suffix) + ] + + def _make_follow_state(compound, _label=_label, + _label_compound=_label_compound, _nl=_nl, + _space=_space, _start_label=_start_label, + _token=_token, _token_compound=_token_compound, + _ws=_ws): + suffix = '/compound' if compound else '' + state = [] + if compound: + state.append((r'(?=\))', Text, '#pop')) + state += [ + (r'%s([%s]*)(%s)(.*)' % + (_start_label, _ws, _label_compound if compound else _label), + bygroups(Text, Punctuation, Text, Name.Label, Comment.Single)), + include('redirect%s' % suffix), + (r'(?=[%s])' % _nl, Text, '#pop'), + (r'\|\|?|&&?', Punctuation, '#pop'), + include('text') + ] + return state + + def _make_arithmetic_state(compound, _nl=_nl, _punct=_punct, + _string=_string, _variable=_variable, + _ws=_ws, _nlws=_nlws): + op = r'=+\-*/!~' + state = [] + if compound: + state.append((r'(?=\))', Text, '#pop')) + state += [ + (r'0[0-7]+', Number.Oct), + (r'0x[\da-f]+', Number.Hex), + (r'\d+', Number.Integer), + (r'[(),]+', Punctuation), + (r'([%s]|%%|\^\^)+' % op, Operator), + (r'(%s|%s|(\^[%s]?)?[^()%s%%\^"%s%s]|\^[%s]?%s)+' % + (_string, _variable, _nl, op, _nlws, _punct, _nlws, + r'[^)]' if compound else r'[\w\W]'), + using(this, state='variable')), + (r'(?=[\x00|&])', Text, '#pop'), + include('follow') + ] + return state + + def _make_call_state(compound, _label=_label, + _label_compound=_label_compound): + state = [] + if compound: + state.append((r'(?=\))', Text, '#pop')) + state.append((r'(:?)(%s)' % (_label_compound if compound else _label), + bygroups(Punctuation, Name.Label), '#pop')) + return state + + def _make_label_state(compound, _label=_label, + _label_compound=_label_compound, _nl=_nl, + _punct=_punct, _string=_string, _variable=_variable): + state = [] + if compound: + state.append((r'(?=\))', Text, '#pop')) + state.append((r'(%s?)((?:%s|%s|\^[%s]?%s|[^"%%^%s%s%s])*)' % + (_label_compound if compound else _label, _string, + _variable, _nl, r'[^)]' if compound else r'[\w\W]', _nl, + _punct, r')' if compound else ''), + bygroups(Name.Label, Comment.Single), '#pop')) + return state + + def _make_redirect_state(compound, + _core_token_compound=_core_token_compound, + _nl=_nl, _punct=_punct, _stoken=_stoken, + _string=_string, _space=_space, + _variable=_variable, _nlws=_nlws): + stoken_compound = (r'(?:[%s]+|(?:%s|%s|%s)+)' % + (_punct, _string, _variable, _core_token_compound)) + return [ + (r'((?:(?<=[%s])\d)?)(>>?&|<&)([%s]*)(\d)' % + (_nlws, _nlws), + bygroups(Number.Integer, Punctuation, Text, Number.Integer)), + (r'((?:(?<=[%s])(?>?|<)(%s?%s)' % + (_nlws, _nl, _space, stoken_compound if compound else _stoken), + bygroups(Number.Integer, Punctuation, using(this, state='text'))) + ] + + tokens = { + 'root': _make_begin_state(False), + 'follow': _make_follow_state(False), + 'arithmetic': _make_arithmetic_state(False), + 'call': _make_call_state(False), + 'label': _make_label_state(False), + 'redirect': _make_redirect_state(False), + 'root/compound': _make_begin_state(True), + 'follow/compound': _make_follow_state(True), + 'arithmetic/compound': _make_arithmetic_state(True), + 'call/compound': _make_call_state(True), + 'label/compound': _make_label_state(True), + 'redirect/compound': _make_redirect_state(True), + 'variable-or-escape': [ + (_variable, Name.Variable), + (r'%%%%|\^[%s]?(\^!|[\w\W])' % _nl, String.Escape) + ], + 'string': [ + (r'"', String.Double, '#pop'), + (_variable, Name.Variable), + (r'\^!|%%', String.Escape), + (r'[^"%%^%s]+|[%%^]' % _nl, String.Double), + default('#pop') + ], + 'sqstring': [ + include('variable-or-escape'), + (r'[^%]+|%', String.Single) + ], + 'bqstring': [ + include('variable-or-escape'), + (r'[^%]+|%', String.Backtick) + ], + 'text': [ + (r'"', String.Double, 'string'), + include('variable-or-escape'), + (r'[^"%%^%s%s\d)]+|.' % (_nlws, _punct), Text) + ], + 'variable': [ + (r'"', String.Double, 'string'), + include('variable-or-escape'), + (r'[^"%%^%s]+|.' % _nl, Name.Variable) + ], + 'for': [ + (r'(%s)(in)(%s)(\()' % (_space, _space), + bygroups(using(this, state='text'), Keyword, + using(this, state='text'), Punctuation), '#pop'), + include('follow') + ], + 'for2': [ + (r'\)', Punctuation), + (r'(%s)(do%s)' % (_space, _token_terminator), + bygroups(using(this, state='text'), Keyword), '#pop'), + (r'[%s]+' % _nl, Text), + include('follow') + ], + 'for/f': [ + (r'(")((?:%s|[^"])*?")([%s]*)(\))' % (_variable, _nlws), + bygroups(String.Double, using(this, state='string'), Text, + Punctuation)), + (r'"', String.Double, ('#pop', 'for2', 'string')), + (r"('(?:%%%%|%s|[\w\W])*?')([%s]*)(\))" % (_variable, _nlws), + bygroups(using(this, state='sqstring'), Text, Punctuation)), + (r'(`(?:%%%%|%s|[\w\W])*?`)([%s]*)(\))' % (_variable, _nlws), + bygroups(using(this, state='bqstring'), Text, Punctuation)), + include('for2') + ], + 'for/l': [ + (r'-?\d+', Number.Integer), + include('for2') + ], + 'if': [ + (r'((?:cmdextversion|errorlevel)%s)(%s)(\d+)' % + (_token_terminator, _space), + bygroups(Keyword, using(this, state='text'), + Number.Integer), '#pop'), + (r'(defined%s)(%s)(%s)' % (_token_terminator, _space, _stoken), + bygroups(Keyword, using(this, state='text'), + using(this, state='variable')), '#pop'), + (r'(exist%s)(%s%s)' % (_token_terminator, _space, _stoken), + bygroups(Keyword, using(this, state='text')), '#pop'), + (r'(%s%s)(%s)(%s%s)' % (_number, _space, _opword, _space, _number), + bygroups(using(this, state='arithmetic'), Operator.Word, + using(this, state='arithmetic')), '#pop'), + (_stoken, using(this, state='text'), ('#pop', 'if2')), + ], + 'if2': [ + (r'(%s?)(==)(%s?%s)' % (_space, _space, _stoken), + bygroups(using(this, state='text'), Operator, + using(this, state='text')), '#pop'), + (r'(%s)(%s)(%s%s)' % (_space, _opword, _space, _stoken), + bygroups(using(this, state='text'), Operator.Word, + using(this, state='text')), '#pop') + ], + '(?': [ + (_space, using(this, state='text')), + (r'\(', Punctuation, ('#pop', 'else?', 'root/compound')), + default('#pop') + ], + 'else?': [ + (_space, using(this, state='text')), + (r'else%s' % _token_terminator, Keyword, '#pop'), + default('#pop') + ] + } + + +class MSDOSSessionLexer(ShellSessionBaseLexer): + """ + Lexer for MS DOS shell sessions, i.e. command lines, including a + prompt, interspersed with output. + + .. versionadded:: 2.1 + """ + + name = 'MSDOS Session' + aliases = ['doscon'] + filenames = [] + mimetypes = [] + + _innerLexerCls = BatchLexer + _ps1rgx = re.compile(r'^([^>]*>)(.*\n?)') + _ps2 = 'More? ' + + +class TcshLexer(RegexLexer): + """ + Lexer for tcsh scripts. + + .. versionadded:: 0.10 + """ + + name = 'Tcsh' + aliases = ['tcsh', 'csh'] + filenames = ['*.tcsh', '*.csh'] + mimetypes = ['application/x-csh'] + + tokens = { + 'root': [ + include('basic'), + (r'\$\(', Keyword, 'paren'), + (r'\$\{#?', Keyword, 'curly'), + (r'`', String.Backtick, 'backticks'), + include('data'), + ], + 'basic': [ + (r'\b(if|endif|else|while|then|foreach|case|default|' + r'continue|goto|breaksw|end|switch|endsw)\s*\b', + Keyword), + (r'\b(alias|alloc|bg|bindkey|break|builtins|bye|caller|cd|chdir|' + r'complete|dirs|echo|echotc|eval|exec|exit|fg|filetest|getxvers|' + r'glob|getspath|hashstat|history|hup|inlib|jobs|kill|' + r'limit|log|login|logout|ls-F|migrate|newgrp|nice|nohup|notify|' + r'onintr|popd|printenv|pushd|rehash|repeat|rootnode|popd|pushd|' + r'set|shift|sched|setenv|setpath|settc|setty|setxvers|shift|' + r'source|stop|suspend|source|suspend|telltc|time|' + r'umask|unalias|uncomplete|unhash|universe|unlimit|unset|unsetenv|' + r'ver|wait|warp|watchlog|where|which)\s*\b', + Name.Builtin), + (r'#.*', Comment), + (r'\\[\w\W]', String.Escape), + (r'(\b\w+)(\s*)(=)', bygroups(Name.Variable, Text, Operator)), + (r'[\[\]{}()=]+', Operator), + (r'<<\s*(\'?)\\?(\w+)[\w\W]+?\2', String), + (r';', Punctuation), + ], + 'data': [ + (r'(?s)"(\\\\|\\[0-7]+|\\.|[^"\\])*"', String.Double), + (r"(?s)'(\\\\|\\[0-7]+|\\.|[^'\\])*'", String.Single), + (r'\s+', Text), + (r'[^=\s\[\]{}()$"\'`\\;#]+', Text), + (r'\d+(?= |\Z)', Number), + (r'\$#?(\w+|.)', Name.Variable), + ], + 'curly': [ + (r'\}', Keyword, '#pop'), + (r':-', Keyword), + (r'\w+', Name.Variable), + (r'[^}:"\'`$]+', Punctuation), + (r':', Punctuation), + include('root'), + ], + 'paren': [ + (r'\)', Keyword, '#pop'), + include('root'), + ], + 'backticks': [ + (r'`', String.Backtick, '#pop'), + include('root'), + ], + } + + +class TcshSessionLexer(ShellSessionBaseLexer): + """ + Lexer for Tcsh sessions, i.e. command lines, including a + prompt, interspersed with output. + + .. versionadded:: 2.1 + """ + + name = 'Tcsh Session' + aliases = ['tcshcon'] + filenames = [] + mimetypes = [] + + _innerLexerCls = TcshLexer + _ps1rgx = re.compile(r'^([^>]+>)(.*\n?)') + _ps2 = '? ' + + +class PowerShellLexer(RegexLexer): + """ + For Windows PowerShell code. + + .. versionadded:: 1.5 + """ + name = 'PowerShell' + aliases = ['powershell', 'pwsh', 'posh', 'ps1', 'psm1'] + filenames = ['*.ps1', '*.psm1'] + mimetypes = ['text/x-powershell'] + + flags = re.DOTALL | re.IGNORECASE | re.MULTILINE + + keywords = ( + 'while validateset validaterange validatepattern validatelength ' + 'validatecount until trap switch return ref process param parameter in ' + 'if global: function foreach for finally filter end elseif else ' + 'dynamicparam do default continue cmdletbinding break begin alias \\? ' + '% #script #private #local #global mandatory parametersetname position ' + 'valuefrompipeline valuefrompipelinebypropertyname ' + 'valuefromremainingarguments helpmessage try catch throw').split() + + operators = ( + 'and as band bnot bor bxor casesensitive ccontains ceq cge cgt cle ' + 'clike clt cmatch cne cnotcontains cnotlike cnotmatch contains ' + 'creplace eq exact f file ge gt icontains ieq ige igt ile ilike ilt ' + 'imatch ine inotcontains inotlike inotmatch ireplace is isnot le like ' + 'lt match ne not notcontains notlike notmatch or regex replace ' + 'wildcard').split() + + verbs = ( + 'write where watch wait use update unregister unpublish unprotect ' + 'unlock uninstall undo unblock trace test tee take sync switch ' + 'suspend submit stop step start split sort skip show set send select ' + 'search scroll save revoke resume restore restart resolve resize ' + 'reset request repair rename remove register redo receive read push ' + 'publish protect pop ping out optimize open new move mount merge ' + 'measure lock limit join invoke install initialize import hide group ' + 'grant get format foreach find export expand exit enter enable edit ' + 'dismount disconnect disable deny debug cxnew copy convertto ' + 'convertfrom convert connect confirm compress complete compare close ' + 'clear checkpoint block backup assert approve aggregate add').split() + + aliases_ = ( + 'ac asnp cat cd cfs chdir clc clear clhy cli clp cls clv cnsn ' + 'compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo epal ' + 'epcsv epsn erase etsn exsn fc fhx fl foreach ft fw gal gbp gc gci gcm ' + 'gcs gdr ghy gi gjb gl gm gmo gp gps gpv group gsn gsnp gsv gu gv gwmi ' + 'h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ' + 'ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv ' + 'oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo ' + 'rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc select ' + 'set shcm si sl sleep sls sort sp spjb spps spsv start sujb sv swmi tee ' + 'trcm type wget where wjb write').split() + + commenthelp = ( + 'component description example externalhelp forwardhelpcategory ' + 'forwardhelptargetname functionality inputs link ' + 'notes outputs parameter remotehelprunspace role synopsis').split() + + tokens = { + 'root': [ + # we need to count pairs of parentheses for correct highlight + # of '$(...)' blocks in strings + (r'\(', Punctuation, 'child'), + (r'\s+', Text), + (r'^(\s*#[#\s]*)(\.(?:%s))([^\n]*$)' % '|'.join(commenthelp), + bygroups(Comment, String.Doc, Comment)), + (r'#[^\n]*?$', Comment), + (r'(<|<)#', Comment.Multiline, 'multline'), + (r'@"\n', String.Heredoc, 'heredoc-double'), + (r"@'\n.*?\n'@", String.Heredoc), + # escaped syntax + (r'`[\'"$@-]', Punctuation), + (r'"', String.Double, 'string'), + (r"'([^']|'')*'", String.Single), + (r'(\$|@@|@)((global|script|private|env):)?\w+', + Name.Variable), + (r'(%s)\b' % '|'.join(keywords), Keyword), + (r'-(%s)\b' % '|'.join(operators), Operator), + (r'(%s)-[a-z_]\w*\b' % '|'.join(verbs), Name.Builtin), + (r'(%s)\s' % '|'.join(aliases_), Name.Builtin), + (r'\[[a-z_\[][\w. `,\[\]]*\]', Name.Constant), # .net [type]s + (r'-[a-z_]\w*', Name), + (r'\w+', Name), + (r'[.,;:@{}\[\]$()=+*/\\&%!~?^`|<>-]', Punctuation), + ], + 'child': [ + (r'\)', Punctuation, '#pop'), + include('root'), + ], + 'multline': [ + (r'[^#&.]+', Comment.Multiline), + (r'#(>|>)', Comment.Multiline, '#pop'), + (r'\.(%s)' % '|'.join(commenthelp), String.Doc), + (r'[#&.]', Comment.Multiline), + ], + 'string': [ + (r"`[0abfnrtv'\"$`]", String.Escape), + (r'[^$`"]+', String.Double), + (r'\$\(', Punctuation, 'child'), + (r'""', String.Double), + (r'[`$]', String.Double), + (r'"', String.Double, '#pop'), + ], + 'heredoc-double': [ + (r'\n"@', String.Heredoc, '#pop'), + (r'\$\(', Punctuation, 'child'), + (r'[^@\n]+"]', String.Heredoc), + (r".", String.Heredoc), + ] + } + + +class PowerShellSessionLexer(ShellSessionBaseLexer): + """ + Lexer for PowerShell sessions, i.e. command lines, including a + prompt, interspersed with output. + + .. versionadded:: 2.1 + """ + + name = 'PowerShell Session' + aliases = ['pwsh-session', 'ps1con'] + filenames = [] + mimetypes = [] + + _innerLexerCls = PowerShellLexer + _ps1rgx = re.compile(r'^((?:\[[^]]+\]: )?PS[^>]*> ?)(.*\n?)') + _ps2 = '>> ' + + +class FishShellLexer(RegexLexer): + """ + Lexer for Fish shell scripts. + + .. versionadded:: 2.1 + """ + + name = 'Fish' + aliases = ['fish', 'fishshell'] + filenames = ['*.fish', '*.load'] + mimetypes = ['application/x-fish'] + + tokens = { + 'root': [ + include('basic'), + include('data'), + include('interp'), + ], + 'interp': [ + (r'\$\(\(', Keyword, 'math'), + (r'\(', Keyword, 'paren'), + (r'\$#?(\w+|.)', Name.Variable), + ], + 'basic': [ + (r'\b(begin|end|if|else|while|break|for|in|return|function|block|' + r'case|continue|switch|not|and|or|set|echo|exit|pwd|true|false|' + r'cd|count|test)(\s*)\b', + bygroups(Keyword, Text)), + (r'\b(alias|bg|bind|breakpoint|builtin|command|commandline|' + r'complete|contains|dirh|dirs|emit|eval|exec|fg|fish|fish_config|' + r'fish_indent|fish_pager|fish_prompt|fish_right_prompt|' + r'fish_update_completions|fishd|funced|funcsave|functions|help|' + r'history|isatty|jobs|math|mimedb|nextd|open|popd|prevd|psub|' + r'pushd|random|read|set_color|source|status|trap|type|ulimit|' + r'umask|vared|fc|getopts|hash|kill|printf|time|wait)\s*\b(?!\.)', + Name.Builtin), + (r'#.*\n', Comment), + (r'\\[\w\W]', String.Escape), + (r'(\b\w+)(\s*)(=)', bygroups(Name.Variable, Text, Operator)), + (r'[\[\]()=]', Operator), + (r'<<-?\s*(\'?)\\?(\w+)[\w\W]+?\2', String), + ], + 'data': [ + (r'(?s)\$?"(\\\\|\\[0-7]+|\\.|[^"\\$])*"', String.Double), + (r'"', String.Double, 'string'), + (r"(?s)\$'(\\\\|\\[0-7]+|\\.|[^'\\])*'", String.Single), + (r"(?s)'.*?'", String.Single), + (r';', Punctuation), + (r'&|\||\^|<|>', Operator), + (r'\s+', Text), + (r'\d+(?= |\Z)', Number), + (r'[^=\s\[\]{}()$"\'`\\<&|;]+', Text), + ], + 'string': [ + (r'"', String.Double, '#pop'), + (r'(?s)(\\\\|\\[0-7]+|\\.|[^"\\$])+', String.Double), + include('interp'), + ], + 'paren': [ + (r'\)', Keyword, '#pop'), + include('root'), + ], + 'math': [ + (r'\)\)', Keyword, '#pop'), + (r'[-+*/%^|&]|\*\*|\|\|', Operator), + (r'\d+#\d+', Number), + (r'\d+#(?! )', Number), + (r'\d+', Number), + include('root'), + ], + } + +class ExeclineLexer(RegexLexer): + """ + Lexer for Laurent Bercot's execline language + (https://skarnet.org/software/execline). + + .. versionadded:: 2.7 + """ + + name = 'execline' + aliases = ['execline'] + filenames = ['*.exec'] + + tokens = { + 'root': [ + include('basic'), + include('data'), + include('interp') + ], + 'interp': [ + (r'\$\{', String.Interpol, 'curly'), + (r'\$[\w@#]+', Name.Variable), # user variable + (r'\$', Text), + ], + 'basic': [ + (r'\b(background|backtick|cd|define|dollarat|elgetopt|' + r'elgetpositionals|elglob|emptyenv|envfile|exec|execlineb|' + r'exit|export|fdblock|fdclose|fdmove|fdreserve|fdswap|' + r'forbacktickx|foreground|forstdin|forx|getcwd|getpid|heredoc|' + r'homeof|if|ifelse|ifte|ifthenelse|importas|loopwhilex|' + r'multidefine|multisubstitute|pipeline|piperw|posix-cd|' + r'redirfd|runblock|shift|trap|tryexec|umask|unexport|wait|' + r'withstdinas)\b', Name.Builtin), + (r'\A#!.+\n', Comment.Hashbang), + (r'#.*\n', Comment.Single), + (r'[{}]', Operator) + ], + 'data': [ + (r'(?s)"(\\.|[^"\\$])*"', String.Double), + (r'"', String.Double, 'string'), + (r'\s+', Text), + (r'[^\s{}$"\\]+', Text) + ], + 'string': [ + (r'"', String.Double, '#pop'), + (r'(?s)(\\\\|\\.|[^"\\$])+', String.Double), + include('interp'), + ], + 'curly': [ + (r'\}', String.Interpol, '#pop'), + (r'[\w#@]+', Name.Variable), + include('root') + ] + + } + + def analyse_text(text): + if shebang_matches(text, r'execlineb'): + return 1 diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/sieve.py b/.venv/lib/python3.8/site-packages/pygments/lexers/sieve.py new file mode 100644 index 0000000..6fa33d3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/sieve.py @@ -0,0 +1,68 @@ +""" + pygments.lexers.sieve + ~~~~~~~~~~~~~~~~~~~~~ + + Lexer for Sieve file format. + + https://tools.ietf.org/html/rfc5228 + https://tools.ietf.org/html/rfc5173 + https://tools.ietf.org/html/rfc5229 + https://tools.ietf.org/html/rfc5230 + https://tools.ietf.org/html/rfc5232 + https://tools.ietf.org/html/rfc5235 + https://tools.ietf.org/html/rfc5429 + https://tools.ietf.org/html/rfc8580 + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, bygroups +from pygments.token import Comment, Name, Literal, String, Text, Punctuation, Keyword + +__all__ = ["SieveLexer"] + + +class SieveLexer(RegexLexer): + """ + Lexer for sieve format. + """ + name = 'Sieve' + filenames = ['*.siv', '*.sieve'] + aliases = ['sieve'] + + tokens = { + 'root': [ + (r'\s+', Text), + (r'[();,{}\[\]]', Punctuation), + # import: + (r'(?i)require', + Keyword.Namespace), + # tags: + (r'(?i)(:)(addresses|all|contains|content|create|copy|comparator|count|days|detail|domain|fcc|flags|from|handle|importance|is|localpart|length|lowerfirst|lower|matches|message|mime|options|over|percent|quotewildcard|raw|regex|specialuse|subject|text|under|upperfirst|upper|value)', + bygroups(Name.Tag, Name.Tag)), + # tokens: + (r'(?i)(address|addflag|allof|anyof|body|discard|elsif|else|envelope|ereject|exists|false|fileinto|if|hasflag|header|keep|notify_method_capability|notify|not|redirect|reject|removeflag|setflag|size|spamtest|stop|string|true|vacation|virustest)', + Name.Builtin), + (r'(?i)set', + Keyword.Declaration), + # number: + (r'([0-9.]+)([kmgKMG])?', + bygroups(Literal.Number, Literal.Number)), + # comment: + (r'#.*$', + Comment.Single), + (r'/\*.*\*/', + Comment.Multiline), + # string: + (r'"[^"]*?"', + String), + # text block: + (r'text:', + Name.Tag, 'text'), + ], + 'text': [ + (r'[^.].*?\n', String), + (r'^\.', Punctuation, "#pop"), + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/slash.py b/.venv/lib/python3.8/site-packages/pygments/lexers/slash.py new file mode 100644 index 0000000..df0e23d --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/slash.py @@ -0,0 +1,184 @@ +""" + pygments.lexers.slash + ~~~~~~~~~~~~~~~~~~~~~ + + Lexer for the `Slash `_ programming + language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import ExtendedRegexLexer, bygroups, DelegatingLexer +from pygments.token import Name, Number, String, Comment, Punctuation, \ + Other, Keyword, Operator, Whitespace + +__all__ = ['SlashLexer'] + + +class SlashLanguageLexer(ExtendedRegexLexer): + _nkw = r'(?=[^a-zA-Z_0-9])' + + def move_state(new_state): + return ("#pop", new_state) + + def right_angle_bracket(lexer, match, ctx): + if len(ctx.stack) > 1 and ctx.stack[-2] == "string": + ctx.stack.pop() + yield match.start(), String.Interpol, '}' + ctx.pos = match.end() + pass + + tokens = { + "root": [ + (r"<%=", Comment.Preproc, move_state("slash")), + (r"<%!!", Comment.Preproc, move_state("slash")), + (r"<%#.*?%>", Comment.Multiline), + (r"<%", Comment.Preproc, move_state("slash")), + (r".|\n", Other), + ], + "string": [ + (r"\\", String.Escape, move_state("string_e")), + (r"\"", String, move_state("slash")), + (r"#\{", String.Interpol, "slash"), + (r'.|\n', String), + ], + "string_e": [ + (r'n', String.Escape, move_state("string")), + (r't', String.Escape, move_state("string")), + (r'r', String.Escape, move_state("string")), + (r'e', String.Escape, move_state("string")), + (r'x[a-fA-F0-9]{2}', String.Escape, move_state("string")), + (r'.', String.Escape, move_state("string")), + ], + "regexp": [ + (r'}[a-z]*', String.Regex, move_state("slash")), + (r'\\(.|\n)', String.Regex), + (r'{', String.Regex, "regexp_r"), + (r'.|\n', String.Regex), + ], + "regexp_r": [ + (r'}[a-z]*', String.Regex, "#pop"), + (r'\\(.|\n)', String.Regex), + (r'{', String.Regex, "regexp_r"), + ], + "slash": [ + (r"%>", Comment.Preproc, move_state("root")), + (r"\"", String, move_state("string")), + (r"'[a-zA-Z0-9_]+", String), + (r'%r{', String.Regex, move_state("regexp")), + (r'/\*.*?\*/', Comment.Multiline), + (r"(#|//).*?\n", Comment.Single), + (r'-?[0-9]+e[+-]?[0-9]+', Number.Float), + (r'-?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?', Number.Float), + (r'-?[0-9]+', Number.Integer), + (r'nil'+_nkw, Name.Builtin), + (r'true'+_nkw, Name.Builtin), + (r'false'+_nkw, Name.Builtin), + (r'self'+_nkw, Name.Builtin), + (r'(class)(\s+)([A-Z][a-zA-Z0-9_\']*)', + bygroups(Keyword, Whitespace, Name.Class)), + (r'class'+_nkw, Keyword), + (r'extends'+_nkw, Keyword), + (r'(def)(\s+)(self)(\s*)(\.)(\s*)([a-z_][a-zA-Z0-9_\']*=?|<<|>>|==|<=>|<=|<|>=|>|\+|-(self)?|~(self)?|\*|/|%|^|&&|&|\||\[\]=?)', + bygroups(Keyword, Whitespace, Name.Builtin, Whitespace, Punctuation, Whitespace, Name.Function)), + (r'(def)(\s+)([a-z_][a-zA-Z0-9_\']*=?|<<|>>|==|<=>|<=|<|>=|>|\+|-(self)?|~(self)?|\*|/|%|^|&&|&|\||\[\]=?)', + bygroups(Keyword, Whitespace, Name.Function)), + (r'def'+_nkw, Keyword), + (r'if'+_nkw, Keyword), + (r'elsif'+_nkw, Keyword), + (r'else'+_nkw, Keyword), + (r'unless'+_nkw, Keyword), + (r'for'+_nkw, Keyword), + (r'in'+_nkw, Keyword), + (r'while'+_nkw, Keyword), + (r'until'+_nkw, Keyword), + (r'and'+_nkw, Keyword), + (r'or'+_nkw, Keyword), + (r'not'+_nkw, Keyword), + (r'lambda'+_nkw, Keyword), + (r'try'+_nkw, Keyword), + (r'catch'+_nkw, Keyword), + (r'return'+_nkw, Keyword), + (r'next'+_nkw, Keyword), + (r'last'+_nkw, Keyword), + (r'throw'+_nkw, Keyword), + (r'use'+_nkw, Keyword), + (r'switch'+_nkw, Keyword), + (r'\\', Keyword), + (r'λ', Keyword), + (r'__FILE__'+_nkw, Name.Builtin.Pseudo), + (r'__LINE__'+_nkw, Name.Builtin.Pseudo), + (r'[A-Z][a-zA-Z0-9_\']*'+_nkw, Name.Constant), + (r'[a-z_][a-zA-Z0-9_\']*'+_nkw, Name), + (r'@[a-z_][a-zA-Z0-9_\']*'+_nkw, Name.Variable.Instance), + (r'@@[a-z_][a-zA-Z0-9_\']*'+_nkw, Name.Variable.Class), + (r'\(', Punctuation), + (r'\)', Punctuation), + (r'\[', Punctuation), + (r'\]', Punctuation), + (r'\{', Punctuation), + (r'\}', right_angle_bracket), + (r';', Punctuation), + (r',', Punctuation), + (r'<<=', Operator), + (r'>>=', Operator), + (r'<<', Operator), + (r'>>', Operator), + (r'==', Operator), + (r'!=', Operator), + (r'=>', Operator), + (r'=', Operator), + (r'<=>', Operator), + (r'<=', Operator), + (r'>=', Operator), + (r'<', Operator), + (r'>', Operator), + (r'\+\+', Operator), + (r'\+=', Operator), + (r'-=', Operator), + (r'\*\*=', Operator), + (r'\*=', Operator), + (r'\*\*', Operator), + (r'\*', Operator), + (r'/=', Operator), + (r'\+', Operator), + (r'-', Operator), + (r'/', Operator), + (r'%=', Operator), + (r'%', Operator), + (r'^=', Operator), + (r'&&=', Operator), + (r'&=', Operator), + (r'&&', Operator), + (r'&', Operator), + (r'\|\|=', Operator), + (r'\|=', Operator), + (r'\|\|', Operator), + (r'\|', Operator), + (r'!', Operator), + (r'\.\.\.', Operator), + (r'\.\.', Operator), + (r'\.', Operator), + (r'::', Operator), + (r':', Operator), + (r'(\s|\n)+', Whitespace), + (r'[a-z_][a-zA-Z0-9_\']*', Name.Variable), + ], + } + + +class SlashLexer(DelegatingLexer): + """ + Lexer for the Slash programming language. + + .. versionadded:: 2.4 + """ + + name = 'Slash' + aliases = ['slash'] + filenames = ['*.sla'] + + def __init__(self, **options): + from pygments.lexers.web import HtmlLexer + super().__init__(HtmlLexer, SlashLanguageLexer, **options) diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/smalltalk.py b/.venv/lib/python3.8/site-packages/pygments/lexers/smalltalk.py new file mode 100644 index 0000000..ebb3311 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/smalltalk.py @@ -0,0 +1,194 @@ +""" + pygments.lexers.smalltalk + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Smalltalk and related languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, include, bygroups, default +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation + +__all__ = ['SmalltalkLexer', 'NewspeakLexer'] + + +class SmalltalkLexer(RegexLexer): + """ + For `Smalltalk `_ syntax. + Contributed by Stefan Matthias Aust. + Rewritten by Nils Winter. + + .. versionadded:: 0.10 + """ + name = 'Smalltalk' + filenames = ['*.st'] + aliases = ['smalltalk', 'squeak', 'st'] + mimetypes = ['text/x-smalltalk'] + + tokens = { + 'root': [ + (r'(<)(\w+:)(.*?)(>)', bygroups(Text, Keyword, Text, Text)), + include('squeak fileout'), + include('whitespaces'), + include('method definition'), + (r'(\|)([\w\s]*)(\|)', bygroups(Operator, Name.Variable, Operator)), + include('objects'), + (r'\^|\:=|\_', Operator), + # temporaries + (r'[\]({}.;!]', Text), + ], + 'method definition': [ + # Not perfect can't allow whitespaces at the beginning and the + # without breaking everything + (r'([a-zA-Z]+\w*:)(\s*)(\w+)', + bygroups(Name.Function, Text, Name.Variable)), + (r'^(\b[a-zA-Z]+\w*\b)(\s*)$', bygroups(Name.Function, Text)), + (r'^([-+*/\\~<>=|&!?,@%]+)(\s*)(\w+)(\s*)$', + bygroups(Name.Function, Text, Name.Variable, Text)), + ], + 'blockvariables': [ + include('whitespaces'), + (r'(:)(\s*)(\w+)', + bygroups(Operator, Text, Name.Variable)), + (r'\|', Operator, '#pop'), + default('#pop'), # else pop + ], + 'literals': [ + (r"'(''|[^'])*'", String, 'afterobject'), + (r'\$.', String.Char, 'afterobject'), + (r'#\(', String.Symbol, 'parenth'), + (r'\)', Text, 'afterobject'), + (r'(\d+r)?-?\d+(\.\d+)?(e-?\d+)?', Number, 'afterobject'), + ], + '_parenth_helper': [ + include('whitespaces'), + (r'(\d+r)?-?\d+(\.\d+)?(e-?\d+)?', Number), + (r'[-+*/\\~<>=|&#!?,@%\w:]+', String.Symbol), + # literals + (r"'(''|[^'])*'", String), + (r'\$.', String.Char), + (r'#*\(', String.Symbol, 'inner_parenth'), + ], + 'parenth': [ + # This state is a bit tricky since + # we can't just pop this state + (r'\)', String.Symbol, ('root', 'afterobject')), + include('_parenth_helper'), + ], + 'inner_parenth': [ + (r'\)', String.Symbol, '#pop'), + include('_parenth_helper'), + ], + 'whitespaces': [ + # skip whitespace and comments + (r'\s+', Text), + (r'"(""|[^"])*"', Comment), + ], + 'objects': [ + (r'\[', Text, 'blockvariables'), + (r'\]', Text, 'afterobject'), + (r'\b(self|super|true|false|nil|thisContext)\b', + Name.Builtin.Pseudo, 'afterobject'), + (r'\b[A-Z]\w*(?!:)\b', Name.Class, 'afterobject'), + (r'\b[a-z]\w*(?!:)\b', Name.Variable, 'afterobject'), + (r'#("(""|[^"])*"|[-+*/\\~<>=|&!?,@%]+|[\w:]+)', + String.Symbol, 'afterobject'), + include('literals'), + ], + 'afterobject': [ + (r'! !$', Keyword, '#pop'), # squeak chunk delimiter + include('whitespaces'), + (r'\b(ifTrue:|ifFalse:|whileTrue:|whileFalse:|timesRepeat:)', + Name.Builtin, '#pop'), + (r'\b(new\b(?!:))', Name.Builtin), + (r'\:=|\_', Operator, '#pop'), + (r'\b[a-zA-Z]+\w*:', Name.Function, '#pop'), + (r'\b[a-zA-Z]+\w*', Name.Function), + (r'\w+:?|[-+*/\\~<>=|&!?,@%]+', Name.Function, '#pop'), + (r'\.', Punctuation, '#pop'), + (r';', Punctuation), + (r'[\])}]', Text), + (r'[\[({]', Text, '#pop'), + ], + 'squeak fileout': [ + # Squeak fileout format (optional) + (r'^"(""|[^"])*"!', Keyword), + (r"^'(''|[^'])*'!", Keyword), + (r'^(!)(\w+)( commentStamp: )(.*?)( prior: .*?!\n)(.*?)(!)', + bygroups(Keyword, Name.Class, Keyword, String, Keyword, Text, Keyword)), + (r"^(!)(\w+(?: class)?)( methodsFor: )('(?:''|[^'])*')(.*?!)", + bygroups(Keyword, Name.Class, Keyword, String, Keyword)), + (r'^(\w+)( subclass: )(#\w+)' + r'(\s+instanceVariableNames: )(.*?)' + r'(\s+classVariableNames: )(.*?)' + r'(\s+poolDictionaries: )(.*?)' + r'(\s+category: )(.*?)(!)', + bygroups(Name.Class, Keyword, String.Symbol, Keyword, String, Keyword, + String, Keyword, String, Keyword, String, Keyword)), + (r'^(\w+(?: class)?)(\s+instanceVariableNames: )(.*?)(!)', + bygroups(Name.Class, Keyword, String, Keyword)), + (r'(!\n)(\].*)(! !)$', bygroups(Keyword, Text, Keyword)), + (r'! !$', Keyword), + ], + } + + +class NewspeakLexer(RegexLexer): + """ + For `Newspeak `_ syntax. + + .. versionadded:: 1.1 + """ + name = 'Newspeak' + filenames = ['*.ns2'] + aliases = ['newspeak', ] + mimetypes = ['text/x-newspeak'] + + tokens = { + 'root': [ + (r'\b(Newsqueak2)\b', Keyword.Declaration), + (r"'[^']*'", String), + (r'\b(class)(\s+)(\w+)(\s*)', + bygroups(Keyword.Declaration, Text, Name.Class, Text)), + (r'\b(mixin|self|super|private|public|protected|nil|true|false)\b', + Keyword), + (r'(\w+\:)(\s*)([a-zA-Z_]\w+)', + bygroups(Name.Function, Text, Name.Variable)), + (r'(\w+)(\s*)(=)', + bygroups(Name.Attribute, Text, Operator)), + (r'<\w+>', Comment.Special), + include('expressionstat'), + include('whitespace') + ], + + 'expressionstat': [ + (r'(\d+\.\d*|\.\d+|\d+[fF])[fF]?', Number.Float), + (r'\d+', Number.Integer), + (r':\w+', Name.Variable), + (r'(\w+)(::)', bygroups(Name.Variable, Operator)), + (r'\w+:', Name.Function), + (r'\w+', Name.Variable), + (r'\(|\)', Punctuation), + (r'\[|\]', Punctuation), + (r'\{|\}', Punctuation), + + (r'(\^|\+|\/|~|\*|<|>|=|@|%|\||&|\?|!|,|-|:)', Operator), + (r'\.|;', Punctuation), + include('whitespace'), + include('literals'), + ], + 'literals': [ + (r'\$.', String), + (r"'[^']*'", String), + (r"#'[^']*'", String.Symbol), + (r"#\w+:?", String.Symbol), + (r"#(\+|\/|~|\*|<|>|=|@|%|\||&|\?|!|,|-)+", String.Symbol) + ], + 'whitespace': [ + (r'\s+', Text), + (r'"[^"]*"', Comment) + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/smithy.py b/.venv/lib/python3.8/site-packages/pygments/lexers/smithy.py new file mode 100644 index 0000000..0f0a912 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/smithy.py @@ -0,0 +1,79 @@ +""" + pygments.lexers.smithy + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for the Smithy IDL. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, bygroups, words +from pygments.token import Text, Comment, Keyword, Name, String, \ + Number, Whitespace, Punctuation + +__all__ = ['SmithyLexer'] + + +class SmithyLexer(RegexLexer): + """ + For Smithy IDL + + .. versionadded:: 2.10 + """ + name = 'Smithy' + filenames = ['*.smithy'] + aliases = ['smithy'] + + flags = re.MULTILINE | re.UNICODE + unquoted = r'[A-Za-z0-9_\.#$-]+' + identifier = r"[A-Za-z0-9_\.#$-]+" + + simple_shapes = ( + 'use', 'byte', 'short', 'integer', 'long', 'float', 'document', + 'double', 'bigInteger', 'bigDecimal', 'boolean', 'blob', 'string', + 'timestamp', + ) + + aggregate_shapes = ( + 'apply', 'list', 'map', 'set', 'structure', 'union', 'resource', + 'operation', 'service', 'trait' + ) + + tokens = { + 'root': [ + (r'///.*$', Comment.Multiline), + (r'//.*$', Comment), + (r'@[0-9a-zA-Z\.#-]*', Name.Decorator), + (r'(=)', Name.Decorator), + (r'^(\$version)(:)(.+)', + bygroups(Keyword.Declaration, Name.Decorator, Name.Class)), + (r'^(namespace)(\s+' + identifier + r')\b', + bygroups(Keyword.Declaration, Name.Class)), + (words(simple_shapes, + prefix=r'^', suffix=r'(\s+' + identifier + r')\b'), + bygroups(Keyword.Declaration, Name.Class)), + (words(aggregate_shapes, + prefix=r'^', suffix=r'(\s+' + identifier + r')'), + bygroups(Keyword.Declaration, Name.Class)), + (r'^(metadata)(\s+.+)(\s*)(=)', + bygroups(Keyword.Declaration, Name.Class, Whitespace, Name.Decorator)), + (r"(true|false|null)", Keyword.Constant), + (r"(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)", Number), + (identifier + ":", Name.Label), + (identifier, Name.Variable.Class), + (r'\[', Text, "#push"), + (r'\]', Text, "#pop"), + (r'\(', Text, "#push"), + (r'\)', Text, "#pop"), + (r'\{', Text, "#push"), + (r'\}', Text, "#pop"), + (r'"{3}(\\\\|\n|\\")*"{3}', String.Doc), + (r'"(\\\\|\n|\\"|[^"])*"', String.Double), + (r"'(\\\\|\n|\\'|[^'])*'", String.Single), + (r'[:,]+', Punctuation), + (r'\s+', Whitespace), + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/smv.py b/.venv/lib/python3.8/site-packages/pygments/lexers/smv.py new file mode 100644 index 0000000..a4cbf94 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/smv.py @@ -0,0 +1,78 @@ +""" + pygments.lexers.smv + ~~~~~~~~~~~~~~~~~~~ + + Lexers for the SMV languages. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, words +from pygments.token import Comment, Keyword, Name, Number, Operator, \ + Punctuation, Text + +__all__ = ['NuSMVLexer'] + + +class NuSMVLexer(RegexLexer): + """ + Lexer for the NuSMV language. + + .. versionadded:: 2.2 + """ + + name = 'NuSMV' + aliases = ['nusmv'] + filenames = ['*.smv'] + mimetypes = [] + + tokens = { + 'root': [ + # Comments + (r'(?s)\/\-\-.*?\-\-/', Comment), + (r'--.*\n', Comment), + + # Reserved + (words(('MODULE', 'DEFINE', 'MDEFINE', 'CONSTANTS', 'VAR', 'IVAR', + 'FROZENVAR', 'INIT', 'TRANS', 'INVAR', 'SPEC', 'CTLSPEC', + 'LTLSPEC', 'PSLSPEC', 'COMPUTE', 'NAME', 'INVARSPEC', + 'FAIRNESS', 'JUSTICE', 'COMPASSION', 'ISA', 'ASSIGN', + 'CONSTRAINT', 'SIMPWFF', 'CTLWFF', 'LTLWFF', 'PSLWFF', + 'COMPWFF', 'IN', 'MIN', 'MAX', 'MIRROR', 'PRED', + 'PREDICATES'), suffix=r'(?![\w$#-])'), + Keyword.Declaration), + (r'process(?![\w$#-])', Keyword), + (words(('array', 'of', 'boolean', 'integer', 'real', 'word'), + suffix=r'(?![\w$#-])'), Keyword.Type), + (words(('case', 'esac'), suffix=r'(?![\w$#-])'), Keyword), + (words(('word1', 'bool', 'signed', 'unsigned', 'extend', 'resize', + 'sizeof', 'uwconst', 'swconst', 'init', 'self', 'count', + 'abs', 'max', 'min'), suffix=r'(?![\w$#-])'), + Name.Builtin), + (words(('EX', 'AX', 'EF', 'AF', 'EG', 'AG', 'E', 'F', 'O', 'G', + 'H', 'X', 'Y', 'Z', 'A', 'U', 'S', 'V', 'T', 'BU', 'EBF', + 'ABF', 'EBG', 'ABG', 'next', 'mod', 'union', 'in', 'xor', + 'xnor'), suffix=r'(?![\w$#-])'), + Operator.Word), + (words(('TRUE', 'FALSE'), suffix=r'(?![\w$#-])'), Keyword.Constant), + + # Names + (r'[a-zA-Z_][\w$#-]*', Name.Variable), + + # Operators + (r':=', Operator), + (r'[-&|+*/<>!=]', Operator), + + # Literals + (r'\-?\d+\b', Number.Integer), + (r'0[su][bB]\d*_[01_]+', Number.Bin), + (r'0[su][oO]\d*_[0-7_]+', Number.Oct), + (r'0[su][dD]\d*_[\d_]+', Number.Decimal), + (r'0[su][hH]\d*_[\da-fA-F_]+', Number.Hex), + + # Whitespace, punctuation and the rest + (r'\s+', Text.Whitespace), + (r'[()\[\]{};?:.,]', Punctuation), + ], + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/snobol.py b/.venv/lib/python3.8/site-packages/pygments/lexers/snobol.py new file mode 100644 index 0000000..b5719c3 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/snobol.py @@ -0,0 +1,82 @@ +""" + pygments.lexers.snobol + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for the SNOBOL language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, bygroups +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation + +__all__ = ['SnobolLexer'] + + +class SnobolLexer(RegexLexer): + """ + Lexer for the SNOBOL4 programming language. + + Recognizes the common ASCII equivalents of the original SNOBOL4 operators. + Does not require spaces around binary operators. + + .. versionadded:: 1.5 + """ + + name = "Snobol" + aliases = ["snobol"] + filenames = ['*.snobol'] + mimetypes = ['text/x-snobol'] + + tokens = { + # root state, start of line + # comments, continuation lines, and directives start in column 1 + # as do labels + 'root': [ + (r'\*.*\n', Comment), + (r'[+.] ', Punctuation, 'statement'), + (r'-.*\n', Comment), + (r'END\s*\n', Name.Label, 'heredoc'), + (r'[A-Za-z$][\w$]*', Name.Label, 'statement'), + (r'\s+', Text, 'statement'), + ], + # statement state, line after continuation or label + 'statement': [ + (r'\s*\n', Text, '#pop'), + (r'\s+', Text), + (r'(?<=[^\w.])(LT|LE|EQ|NE|GE|GT|INTEGER|IDENT|DIFFER|LGT|SIZE|' + r'REPLACE|TRIM|DUPL|REMDR|DATE|TIME|EVAL|APPLY|OPSYN|LOAD|UNLOAD|' + r'LEN|SPAN|BREAK|ANY|NOTANY|TAB|RTAB|REM|POS|RPOS|FAIL|FENCE|' + r'ABORT|ARB|ARBNO|BAL|SUCCEED|INPUT|OUTPUT|TERMINAL)(?=[^\w.])', + Name.Builtin), + (r'[A-Za-z][\w.]*', Name), + # ASCII equivalents of original operators + # | for the EBCDIC equivalent, ! likewise + # \ for EBCDIC negation + (r'\*\*|[?$.!%*/#+\-@|&\\=]', Operator), + (r'"[^"]*"', String), + (r"'[^']*'", String), + # Accept SPITBOL syntax for real numbers + # as well as Macro SNOBOL4 + (r'[0-9]+(?=[^.EeDd])', Number.Integer), + (r'[0-9]+(\.[0-9]*)?([EDed][-+]?[0-9]+)?', Number.Float), + # Goto + (r':', Punctuation, 'goto'), + (r'[()<>,;]', Punctuation), + ], + # Goto block + 'goto': [ + (r'\s*\n', Text, "#pop:2"), + (r'\s+', Text), + (r'F|S', Keyword), + (r'(\()([A-Za-z][\w.]*)(\))', + bygroups(Punctuation, Name.Label, Punctuation)) + ], + # everything after the END statement is basically one + # big heredoc. + 'heredoc': [ + (r'.*\n', String.Heredoc) + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/solidity.py b/.venv/lib/python3.8/site-packages/pygments/lexers/solidity.py new file mode 100644 index 0000000..0c42586 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/solidity.py @@ -0,0 +1,91 @@ +""" + pygments.lexers.solidity + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Lexers for Solidity. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, bygroups, include, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Whitespace + +__all__ = ['SolidityLexer'] + + +class SolidityLexer(RegexLexer): + """ + For Solidity source code. + + .. versionadded:: 2.5 + """ + + name = 'Solidity' + aliases = ['solidity'] + filenames = ['*.sol'] + mimetypes = [] + + flags = re.MULTILINE | re.UNICODE + + datatype = ( + r'\b(address|bool|(?:(?:bytes|hash|int|string|uint)(?:8|16|24|32|40|48|56|64' + r'|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208' + r'|216|224|232|240|248|256)?))\b' + ) + + tokens = { + 'root': [ + include('whitespace'), + include('comments'), + (r'\bpragma\s+solidity\b', Keyword, 'pragma'), + (r'\b(contract)(\s+)([a-zA-Z_]\w*)', + bygroups(Keyword, Whitespace, Name.Entity)), + (datatype + r'(\s+)((?:external|public|internal|private)\s+)?' + + r'([a-zA-Z_]\w*)', + bygroups(Keyword.Type, Whitespace, Keyword, Name.Variable)), + (r'\b(enum|event|function|struct)(\s+)([a-zA-Z_]\w*)', + bygroups(Keyword.Type, Whitespace, Name.Variable)), + (r'\b(msg|block|tx)\.([A-Za-z_][a-zA-Z0-9_]*)\b', Keyword), + (words(( + 'block', 'break', 'constant', 'constructor', 'continue', + 'contract', 'do', 'else', 'external', 'false', 'for', + 'function', 'if', 'import', 'inherited', 'internal', 'is', + 'library', 'mapping', 'memory', 'modifier', 'msg', 'new', + 'payable', 'private', 'public', 'require', 'return', + 'returns', 'struct', 'suicide', 'throw', 'this', 'true', + 'tx', 'var', 'while'), prefix=r'\b', suffix=r'\b'), + Keyword.Type), + (words(('keccak256',), prefix=r'\b', suffix=r'\b'), Name.Builtin), + (datatype, Keyword.Type), + include('constants'), + (r'[a-zA-Z_]\w*', Text), + (r'[!<=>+*/-]', Operator), + (r'[.;:{}(),\[\]]', Punctuation) + ], + 'comments': [ + (r'//(\n|[\w\W]*?[^\\]\n)', Comment.Single), + (r'/(\\\n)?[*][\w\W]*?[*](\\\n)?/', Comment.Multiline), + (r'/(\\\n)?[*][\w\W]*', Comment.Multiline) + ], + 'constants': [ + (r'("(\\"|.)*?")', String.Double), + (r"('(\\'|.)*?')", String.Single), + (r'\b0[xX][0-9a-fA-F]+\b', Number.Hex), + (r'\b\d+\b', Number.Decimal), + ], + 'pragma': [ + include('whitespace'), + include('comments'), + (r'(\^|>=|<)(\s*)(\d+\.\d+\.\d+)', + bygroups(Operator, Whitespace, Keyword)), + (r';', Punctuation, '#pop') + ], + 'whitespace': [ + (r'\s+', Whitespace), + (r'\n', Whitespace) + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/sophia.py b/.venv/lib/python3.8/site-packages/pygments/lexers/sophia.py new file mode 100644 index 0000000..5410369 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/sophia.py @@ -0,0 +1,103 @@ +""" + pygments.lexers.sophia + ~~~~~~~~~~~~~~~~~~~~~~ + + Lexer for Sophia. + + Derived from pygments/lexers/reason.py. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from pygments.lexer import RegexLexer, include, default, words +from pygments.token import Comment, Keyword, Name, Number, Operator, \ + Punctuation, String, Text + +__all__ = ['SophiaLexer'] + +class SophiaLexer(RegexLexer): + """ + A `Sophia `_ lexer. + + .. versionadded:: 2.11 + """ + + name = 'Sophia' + aliases = ['sophia'] + filenames = ['*.aes'] + mimetypes = [] + + keywords = ( + 'contract', 'include', 'let', 'switch', 'type', 'record', 'datatype', + 'if', 'elif', 'else', 'function', 'stateful', 'payable', 'public', + 'entrypoint', 'private', 'indexed', 'namespace', 'interface', 'main', + 'using', 'as', 'for', 'hiding', + ) + + builtins = ('state', 'put', 'abort', 'require') + + word_operators = ('mod', 'band', 'bor', 'bxor', 'bnot') + + primitive_types = ('int', 'address', 'bool', 'bits', 'bytes', 'string', + 'list', 'option', 'char', 'unit', 'map', 'event', + 'hash', 'signature', 'oracle', 'oracle_query') + + tokens = { + 'escape-sequence': [ + (r'\\[\\"\'ntbr]', String.Escape), + (r'\\[0-9]{3}', String.Escape), + (r'\\x[0-9a-fA-F]{2}', String.Escape), + ], + 'root': [ + (r'\s+', Text.Whitespace), + (r'(true|false)\b', Keyword.Constant), + (r'\b([A-Z][\w\']*)(?=\s*\.)', Name.Class, 'dotted'), + (r'\b([A-Z][\w\']*)', Name.Function), + (r'//.*?\n', Comment.Single), + (r'\/\*(?!/)', Comment.Multiline, 'comment'), + + (r'0[xX][\da-fA-F][\da-fA-F_]*', Number.Hex), + (r'#[\da-fA-F][\da-fA-F_]*', Name.Label), + (r'\d[\d_]*', Number.Integer), + + (words(keywords, suffix=r'\b'), Keyword), + (words(builtins, suffix=r'\b'), Name.Builtin), + (words(word_operators, prefix=r'\b', suffix=r'\b'), Operator.Word), + (words(primitive_types, prefix=r'\b', suffix=r'\b'), Keyword.Type), + + (r'[=!<>+\\*/:&|?~@^-]', Operator.Word), + (r'[.;:{}(),\[\]]', Punctuation), + + (r"(ak_|ok_|oq_|ct_)[\w']*", Name.Label), + (r"[^\W\d][\w']*", Name), + + (r"'(?:(\\[\\\"'ntbr ])|(\\[0-9]{3})|(\\x[0-9a-fA-F]{2}))'", + String.Char), + (r"'.'", String.Char), + (r"'[a-z][\w]*", Name.Variable), + + (r'"', String.Double, 'string') + ], + 'comment': [ + (r'[^/*]+', Comment.Multiline), + (r'\/\*', Comment.Multiline, '#push'), + (r'\*\/', Comment.Multiline, '#pop'), + (r'\*', Comment.Multiline), + ], + 'string': [ + (r'[^\\"]+', String.Double), + include('escape-sequence'), + (r'\\\n', String.Double), + (r'"', String.Double, '#pop'), + ], + 'dotted': [ + (r'\s+', Text), + (r'\.', Punctuation), + (r'[A-Z][\w\']*(?=\s*\.)', Name.Function), + (r'[A-Z][\w\']*', Name.Function, '#pop'), + (r'[a-z_][\w\']*', Name, '#pop'), + default('#pop'), + ], + } + diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/special.py b/.venv/lib/python3.8/site-packages/pygments/lexers/special.py new file mode 100644 index 0000000..bff6652 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/special.py @@ -0,0 +1,119 @@ +""" + pygments.lexers.special + ~~~~~~~~~~~~~~~~~~~~~~~ + + Special lexers. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import ast +import re + +from pygments.lexer import Lexer +from pygments.token import Token, Error, Text, Generic +from pygments.util import get_choice_opt + + +__all__ = ['TextLexer', 'OutputLexer', 'RawTokenLexer'] + + +class TextLexer(Lexer): + """ + "Null" lexer, doesn't highlight anything. + """ + name = 'Text only' + aliases = ['text'] + filenames = ['*.txt'] + mimetypes = ['text/plain'] + priority = 0.01 + + def get_tokens_unprocessed(self, text): + yield 0, Text, text + + def analyse_text(text): + return TextLexer.priority + + +class OutputLexer(Lexer): + """ + Simple lexer that highlights everything as ``Token.Generic.Output``. + + .. versionadded:: 2.10 + """ + name = 'Text output' + aliases = ['output'] + + def get_tokens_unprocessed(self, text): + yield 0, Generic.Output, text + + +_ttype_cache = {} + +line_re = re.compile('.*?\n') + + +class RawTokenLexer(Lexer): + """ + Recreate a token stream formatted with the `RawTokenFormatter`. + + Additional options accepted: + + `compress` + If set to ``"gz"`` or ``"bz2"``, decompress the token stream with + the given compression algorithm before lexing (default: ``""``). + """ + name = 'Raw token data' + aliases = [] + filenames = [] + mimetypes = ['application/x-pygments-tokens'] + + def __init__(self, **options): + self.compress = get_choice_opt(options, 'compress', + ['', 'none', 'gz', 'bz2'], '') + Lexer.__init__(self, **options) + + def get_tokens(self, text): + if self.compress: + if isinstance(text, str): + text = text.encode('latin1') + try: + if self.compress == 'gz': + import gzip + text = gzip.decompress(text) + elif self.compress == 'bz2': + import bz2 + text = bz2.decompress(text) + except OSError: + yield Error, text.decode('latin1') + if isinstance(text, bytes): + text = text.decode('latin1') + + # do not call Lexer.get_tokens() because stripping is not optional. + text = text.strip('\n') + '\n' + for i, t, v in self.get_tokens_unprocessed(text): + yield t, v + + def get_tokens_unprocessed(self, text): + length = 0 + for match in line_re.finditer(text): + try: + ttypestr, val = match.group().rstrip().split('\t', 1) + ttype = _ttype_cache.get(ttypestr) + if not ttype: + ttype = Token + ttypes = ttypestr.split('.')[1:] + for ttype_ in ttypes: + if not ttype_ or not ttype_[0].isupper(): + raise ValueError('malformed token name') + ttype = getattr(ttype, ttype_) + _ttype_cache[ttypestr] = ttype + val = ast.literal_eval(val) + if not isinstance(val, str): + raise ValueError('expected str') + except (SyntaxError, ValueError): + val = match.group() + ttype = Error + yield length, ttype, val + length += len(val) diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/spice.py b/.venv/lib/python3.8/site-packages/pygments/lexers/spice.py new file mode 100644 index 0000000..51552b5 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/spice.py @@ -0,0 +1,60 @@ +""" + pygments.lexers.spice + ~~~~~~~~~~~~~~~~~~~~~ + + Lexers for the Spice programming language. + + :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from pygments.lexer import RegexLexer, bygroups, words +from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ + Number, Punctuation, Whitespace + +__all__ = ['SpiceLexer'] + + +class SpiceLexer(RegexLexer): + """ + For `Spice `_ source. + + .. versionadded:: 2.11 + """ + name = 'Spice' + filenames = ['*.spice'] + aliases = ['spice', 'spicelang'] + mimetypes = ['text/x-spice'] + + flags = re.MULTILINE | re.UNICODE + + tokens = { + 'root': [ + (r'\n', Whitespace), + (r'\s+', Whitespace), + (r'\\\n', Text), # line continuations + (r'//(.*?)\n', Comment.Single), + (r'/(\\\n)?[*](.|\n)*?[*](\\\n)?/', Comment.Multiline), + (r'(import|as)\b', Keyword.Namespace), + (r'(f|p|type|struct|const)\b', Keyword.Declaration), + (words(('if', 'else', 'for', 'foreach', 'while', 'break', 'continue', 'return', 'new', 'ext'), suffix=r'\b'), Keyword), + (r'(true|false)\b', Keyword.Constant), + (words(('printf', 'sizeof'), suffix=r'\b(\()'), bygroups(Name.Builtin, Punctuation)), + (words(('double', 'int', 'short', 'long', 'byte', 'char', 'string', 'bool', 'dyn'), suffix=r'\b'), Keyword.Type), + # double_lit + (r'\d+(\.\d+[eE][+\-]?\d+|\.\d*|[eE][+\-]?\d+)', Number.Double), + (r'\.\d+([eE][+\-]?\d+)?', Number.Double), + # int_lit + (r'(0|[1-9][0-9]*)', Number.Integer), + # StringLiteral + # -- interpreted_string_lit + (r'"(\\\\|\\[^\\]|[^"\\])*"', String), + # Tokens + (r'(<<=|>>=|<<|>>|<=|>=|\+=|-=|\*=|/=|&&|\|\||&|\||\+\+|--|\%|==|!=|[.]{3}|[+\-*/&])', Operator), + (r'[|<>=!()\[\]{}.,;:\?]', Punctuation), + # identifier + (r'[^\W\d]\w*', Name.Other), + ] + } diff --git a/.venv/lib/python3.8/site-packages/pygments/lexers/sql.py b/.venv/lib/python3.8/site-packages/pygments/lexers/sql.py new file mode 100644 index 0000000..752f135 --- /dev/null +++ b/.venv/lib/python3.8/site-packages/pygments/lexers/sql.py @@ -0,0 +1,839 @@ +""" + pygments.lexers.sql + ~~~~~~~~~~~~~~~~~~~ + + Lexers for various SQL dialects and related interactive sessions. + + Postgres specific lexers: + + `PostgresLexer` + A SQL lexer for the PostgreSQL dialect. Differences w.r.t. the SQL + lexer are: + + - keywords and data types list parsed from the PG docs (run the + `_postgres_builtins` module to update them); + - Content of $-strings parsed using a specific lexer, e.g. the content + of a PL/Python function is parsed using the Python lexer; + - parse PG specific constructs: E-strings, $-strings, U&-strings, + different operators and punctuation. + + `PlPgsqlLexer` + A lexer for the PL/pgSQL language. Adds a few specific construct on + top of the PG SQL lexer (such as <

' : '\U0001d4ab', + '\\' : '\U0001d4ac', + '\\' : '\U0000211b', + '\\' : '\U0001d4ae', + '\\' : '\U0001d4af', + '\\' : '\U0001d4b0', + '\\' : '\U0001d4b1', + '\\' : '\U0001d4b2', + '\\' : '\U0001d4b3', + '\\' : '\U0001d4b4', + '\\' : '\U0001d4b5', + '\\' : '\U0001d5ba', + '\\' : '\U0001d5bb', + '\\' : '\U0001d5bc', + '\\' : '\U0001d5bd', + '\\' : '\U0001d5be', + '\\' : '\U0001d5bf', + '\\' : '\U0001d5c0', + '\\' : '\U0001d5c1', + '\\' : '\U0001d5c2', + '\\' : '\U0001d5c3', + '\\' : '\U0001d5c4', + '\\' : '\U0001d5c5', + '\\' : '\U0001d5c6', + '\\' : '\U0001d5c7', + '\\' : '\U0001d5c8', + '\\